/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package tools.export; import client.inventory.MapleInventoryType; import constants.GameConstants; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.Scanner; /** * * @author Itzik */ public class DropRetriever { private static final ArrayList<DropEntry> drops = new ArrayList<>(); private static final String BASE_URL = "http://maplearchive.com/"; private static final String MONSTER_PAGE = "mob-wp.php"; private static final String ITEM_PAGE = "item-wp.php"; private static int currPage = 1; public static String VERSION = "Unknown"; private static int parsedMobs = 0; private static int droppingMobs = 0; private static int undroppingMobs = 0; private static int exceptions = 0; private static boolean parsePage(final String url) { try { URL page = new URL(url); String temp_data; try (InputStream is = page.openStream(); Scanner s = new Scanner(is)) { temp_data = ""; while (s.hasNext()) { temp_data += s.nextLine() + "\n"; } } if (temp_data.contains("<title>Maple Archive @ GMS") && temp_data.contains("$)C!$ MapleStory Library")) { VERSION = getStringBetween(temp_data, "<title>Maple Archive @ GMS ", " $)C!$ MapleStory Library"); } if (!temp_data.contains("class=\"mobImage\"")) { return false; } while (temp_data.contains("class=\"mobImage\"")) { try { String monster_section; if (!temp_data.contains("<div class=\"entityBox\">")) { monster_section = getStringBetween(temp_data, "class=\"mobImage\"", "<div class=\"pagination\">"); // Who cares, it works } else { monster_section = getStringBetween(temp_data, "class=\"mobImage\"", "<div class=\"entityBox\">"); } parseMonsterSection(monster_section); temp_data = trimUntil(temp_data, "<div class=\"entityBox\">"); if (temp_data == null) { break; } } catch (StringIndexOutOfBoundsException ex) { System.out.println("An exception was found. Skipping."); exceptions++; break; } } System.out.println("Parsing page " + currPage + " complete."); if (currPage % 10 == 0) { System.out.println(); System.out.println("Status so far:"); System.out.println("Monsters: " + parsedMobs + " || Monsters with drops: " + droppingMobs + " || Monsters without drops: " + undroppingMobs + " || Items: " + drops.size() + " || Errors: " + exceptions); System.out.println(); } } catch (MalformedURLException mue) { System.out.println("A malware informed url has been found and cannot be accessed: " + url); exceptions++; } catch (IOException ioe) { System.out.println("Error reading from URL: " + ioe.getLocalizedMessage()); exceptions++; } return true; } public static void dumpQuery() { String filename = "MapleArchive-Drops-v" + VERSION + ".sql"; try { File output = new File(filename); try (BufferedWriter bw = new BufferedWriter(new FileWriter(output)); PrintWriter pw = new PrintWriter(bw)) { StringBuilder sb = new StringBuilder(); pw.write("TRUNCATE TABLE `drop_data`;\r\n"); pw.write("INSERT INTO `drop_data` (`dropperid`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`) VALUES "); for (Iterator<DropEntry> i = drops.iterator(); i.hasNext();) { DropEntry de = i.next(); if (!pw.toString().contains(de.getQuerySegment())) { pw.write(de.getQuerySegment()); } if (i.hasNext()) { pw.write(", \r\n"); } } pw.write(sb.toString()); } } catch (IOException ioe) { System.out.println("Error writing to file: " + ioe.getLocalizedMessage()); } } public static String getStringBetween(final String line, final String start, final String end) { int start_offset = line.indexOf(start) + start.length(); return line.substring(start_offset, line.substring(start_offset).indexOf(end) + start_offset); } public static void main(final String[] args) { long startTime = System.currentTimeMillis(); System.out.println("MapleArchive Drop Retriever"); System.out.println("---------------------------------------"); System.out.println("Average proccess time: 9 hours."); System.out.println(); currPage = 1; while (true) { System.out.println("Parsing page " + currPage); if (!parsePage(BASE_URL + MONSTER_PAGE + "?page=" + currPage)) { break; } currPage++; } long dataEndTime = System.currentTimeMillis(); System.out.println("Retrieving drop data is complete."); System.out.println(); System.out.println("Creating an sql query and saving into file."); long sqlStartTime = System.currentTimeMillis(); dumpQuery(); long sqlEndTime = System.currentTimeMillis(); System.out.println("Sql query is ready."); System.out.println("------------------------"); System.out.println("Process finished!"); System.out.println("Total monsters parsed: " + parsedMobs + " || Total monsters with drops: " + droppingMobs + "|| Total monsters without drops: " + undroppingMobs + " || Total items: " + drops.size() + " || Total errors: " + exceptions); System.out.println("Data reading time: " + ((dataEndTime - startTime) / 1000) + " seconds || SQL creation time: " + ((sqlEndTime - sqlStartTime) / 1000) + " seconds"); System.out.println("Total time: " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds."); System.out.println("------------------------"); } private static void parseItemSection(final String html_data, final int MonsterId, final boolean isBoss) { String temp_data = html_data; while (temp_data.contains("AJAXLoad('Item', 'id=")) { int ItemId = Integer.parseInt(getStringBetween(temp_data, "AJAXLoad('Item', 'id=", "');\">")); int Quest; try { Quest = getQuestById(ItemId); } catch (IOException ex) { Quest = 0; } DropEntry drop = new DropEntry(ItemId, MonsterId, Quest, isBoss); if (!drops.contains(drop)) { drops.add(drop); } if (temp_data.contains("javascript:return ")) { temp_data = trimUntil(temp_data, "javascript:return "); } else { return; } } } private static void parseMonsterSection(final String html_data) { try { parsedMobs++; int MonsterId = Integer.parseInt(getStringBetween(html_data, "alt=\"Mob:", "\" />")); // Will it blend? ;-) boolean isBoss = false; String BossString = getStringBetween(html_data, "<tr><td class=\"statName\"><b>Boss:</b></td><td class=\"statValue\">", "</td></tr>"); if (BossString.equalsIgnoreCase("No")) { isBoss = false; } else if (BossString.equalsIgnoreCase("Yes")) { isBoss = true; } if (getStringBetween(html_data, "<td class=\"tdDrops\" ", "</td>").contains("<ul><li>None/Unknown</li></ul>")) { undroppingMobs++; return; } // Parse Equipment drops if (html_data.contains(">Equipment</a>")) { parseItemSection(getStringBetween(html_data, ">Equipment</a>", "</ul></li>"), MonsterId, isBoss); } // Parse Potion drops if (html_data.contains(">Potion</a>")) { parseItemSection(getStringBetween(html_data, ">Potion</a>", "</ul></li>"), MonsterId, isBoss); } // Parse Food drops if (html_data.contains(">Food</a>")) { parseItemSection(getStringBetween(html_data, ">Food</a>", "</ul></li>"), MonsterId, isBoss); } // Parse Arrow drops if (html_data.contains(">Arrows</a>")) { parseItemSection(getStringBetween(html_data, ">Arrows</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Bullet drops if (html_data.contains(">Bullet</a>")) { parseItemSection(getStringBetween(html_data, ">Bullet</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Throwing Star drops if (html_data.contains(">Throwing Star</a>")) { parseItemSection(getStringBetween(html_data, ">Throwing Star</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Status Removal Potions drops (Anidote, tonic, etc.) if (html_data.contains(">Status Removal Potion</a>")) { parseItemSection(getStringBetween(html_data, ">Status Removal Potion</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Mastery Book drops if (html_data.contains(">Mastery Book</a>")) { parseItemSection(getStringBetween(html_data, ">Mastery Book</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Skill Book drops if (html_data.contains(">Skill Book</a>")) { parseItemSection(getStringBetween(html_data, ">Skill Book</a>", "</ul></li>"), MonsterId, isBoss); } // Parse Misc. Box (The hell is that? :O) if (html_data.contains(">Misc. Box</a>")) { parseItemSection(getStringBetween(html_data, ">Misc. Box</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Summoning Sack drops if (html_data.contains(">Summoning Sack</a>")) { parseItemSection(getStringBetween(html_data, ">Summoning Sack</a>", "</ul></li>"), MonsterId, isBoss); } // Parse Familiar drops if (html_data.contains(">Familiar</a>")) { parseItemSection(getStringBetween(html_data, ">Familiar</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Item Pot drops if (html_data.contains(">Item Pot</a>")) { parseItemSection(getStringBetween(html_data, ">Item Pot</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Jett Core Modifier drops if (html_data.contains(">Jett Core Modifier</a>")) { parseItemSection(getStringBetween(html_data, ">Jett Core Modifier</a>", "</ul></li>"), MonsterId, isBoss); } //Parse Recipe drops if (html_data.contains(">Recipe</a>")) { parseItemSection(getStringBetween(html_data, ">Recipe</a>", "</ul></li>"), MonsterId, isBoss); } // Parse Setup drops if (html_data.contains(">Setup</a>")) { parseItemSection(getStringBetween(html_data, ">Setup</a>", "</ul></li>"), MonsterId, isBoss); } // Parse ETC drops if (html_data.contains(">Etc</a>")) { parseItemSection(getStringBetween(html_data, ">Etc</a>", "</ul></li>"), MonsterId, isBoss); } } catch (StringIndexOutOfBoundsException ex) { System.out.println("Uh oh! Something went wrong. Skipping this one..."); exceptions++; } droppingMobs++; } public static int getQuestById(final int itemId) throws IOException { int quest; try { URL page = new URL(BASE_URL + ITEM_PAGE + "?id=" + itemId); String temp_data; try (InputStream is = page.openStream(); Scanner s = new Scanner(is)) { temp_data = ""; while (s.hasNext()) { temp_data += s.nextLine() + "\n"; } } if (!temp_data.contains("<i>Required for quests <a class=\"loadLink\"")) { return 0; } quest = Integer.parseInt(getStringBetween(temp_data, "AJAXLoad('Quest', 'id=", "');\">")); } catch (MalformedURLException | NumberFormatException ex) { return 0; } return quest; } public static String trimUntil(final String line, final String until) { int until_pos = line.indexOf(until); if (until_pos == -1) { return null; } else { return line.substring(until_pos + until.length()); } } private static class DropEntry { private final int itemId; private final int monsterId; private final int chance; private int mindrop; private int maxdrop; private final int quest; private final boolean isBoss; public DropEntry(final int itemId, final int monsterId, final int quest, final boolean isBoss) { this.itemId = itemId; this.monsterId = monsterId; mindrop = 1; maxdrop = 1; if (getChance(itemId) != 0) { chance = getChance(itemId) * 10; } else { chance = calculateChance(itemId) / 1000; } this.quest = quest; this.isBoss = isBoss; } private int calculateChance(final int itemId) { MapleInventoryType mit = GameConstants.getInventoryType(itemId); int number = (itemId / 1000) % 1000; switch (mit) { case EQUIP: if (isBoss) { return 300000; } return 7000; case USE: if (isBoss) { mindrop = 1; maxdrop = 4; } switch (number) { case 0: // Normal potions mindrop = 1; maxdrop = 5; return 100000; case 1: // watermelons, pills, speed potions, etc case 2: // same thing return 50000; case 3: // advanced potions from crafting (should not drop) case 4: // same thing case 11: // poison mushroom case 28: // cool items case 30: // return scrolls case 46: // gallant scrolls return 0; case 10: // strange potions like apples, eggs case 12: // drakes blood, sap of ancient tree (rare use) case 20: // salad, fried chicken, dews case 22: // air bubbles and stuff. ALSO nependeath honey but oh well case 50: // antidotes and stuff case 290: // mastery books return 10000; case 40: case 41: case 43: case 44: case 48: // pet scrolls case 100: // summon bags case 101: // summon bags case 102: // summon bags case 109: // summon bags case 120: // pet food case 211: // cliffs special potion case 240: // rings case 270: // pheromone, additional weird stuff case 310: // teleport rock case 320: // weird drops case 390: // weird case 430: // quiz things? compass? case 440: // jukebox case 460: // magnifying glass case 470: // golden hammer case 490: // crystanol case 500: // sp reset return 0; case 47: // tablets from dragon rider return 250000; case 49: // clean slats, potential scroll, ees case 70: // throwing stars case 210: // rare monster piece drops case 330: // bullets return 1000; case 60: // bow arrows case 61: // crossbow arrows mindrop = 10; maxdrop = 50; return 20000; case 213: // boss transfrom return 300000; case 280: // skill books return 200000; case 381: // monster book things case 382: case 383: case 384: case 385: case 386: case 387: case 388: return 20000; case 510: // recipes case 511: case 512: return 10000; default: return 100000; } case ETC: switch (number) { case 0: // monster pieces return 400000; case 4: // crystal ores case 130: // simulators case 131: // manuals return 10000; case 30: // game pieces return 50000; case 32: // misc items return 250000; default: return 10000; } default: return 10000; } } private static int getChance(int id) { switch (id / 10000) { case 100: // Hat switch (id) { case 1003023: // Targa Hat (INT) case 1003024: // Targa Hat (LUK) case 1003025: // Scarlion (DEX) case 1003026: // Scarlion (STR) case 1002357: // Zakum Helmet case 1002390: // Zakum Helmet 2 case 1002430: // Zakum Helmet 3 case 1003112: // Chaos Zakum Helmet case 1003361: // Super Zakum Helmet case 1003439: // Pink Zakum helmet return 2; } case 104: // Topwear case 105: // Overall case 106: // Pants case 107: // Shoes case 108: // Gloves case 109: // Shield case 110: // Cape case 111: // Ring case 112: // Pendant switch (id) { case 1122000: // Horntail Necklace case 1122076: // Chaos Horntail Necklace return 2; case 1122011: // Timeless Pendant (30) case 1122012: // Timeless Pendant (140) return 2; } case 130: // One Handed Sword case 131: // One Handed Axe case 132: // One Handed Blunt Weapon case 133: // Dagger case 134: // Katara case 137: // Wand case 138: // Staff case 140: // One Handed Sword and Two Handed Sword case 141: // Two Handed Axe case 142: // Two Handed Blunt Weapon case 143: // Spear case 144: // Pole Arm case 145: // Bow case 146: // Crossbow case 147: // Claw case 148: // Knuckle case 149: // Gun case 150: // Shovel (Professions) case 151: // Pickaxe (Professions) case 152: // Dual Bowgun case 153: // Cannon return 5; case 135: // Magic Arrows case 233: // Bullets and Capsules return 15; case 204: // Scrolls switch (id) { case 2049000: // Chaos Scroll case 2049100: // Chaos Scroll 60% case 2049116: // Miraculous Chaos Scroll 60% case 2049117: // Chaos Scroll 60% case 2049119: // Incredible Chaos Scroll 60% case 2049122: // Chaos Scroll of Goodness 50% case 2049409: // Legendary Black Dragon Chaos Scroll return 1; } return 2; case 206: // Arrows return 30; case 228: // Skillbook case 229: // Mastery book switch (id) { case 2290096: // Maple Hero 20 case 2290125: // Maple Hero 30 return 2; } return 5; case 251: // recipe switch (id / 1000) { case 2510: // equipment return 2; case 2511: // accessories return 1; case 2512: // potions return 5; } return 1; case 286: // Familiar case 287: // Familiar return 10; case 301: // Chair return 1; case 399: // Quest Items return -1; } switch (id / 1000000) { case 1: // Equipment that hasn't been stated above. return 3; case 2: switch (id) { case 2000004: // Elixir case 2000005: // Power Elixir case 2000006: // Mana Elixir return 15; case 2000000: // Red Potion case 2000002: // White Potion case 2000003: // Blue Potion case 2001001: // Ice Cream Pop case 2002000: // Dexterity Potion case 2002001: // Speed Potion case 2002003: // Wizard Potion case 2002004: // Warrior Potion case 2002006: // Warrior Pill case 2002011: // Pain Reliever case 2010009: // Green Apple case 2012001: // Fairy's Honey case 2012002: // Sap of Ancient Tree case 2022001: // Red Bean Porridge case 2020013: // Reindeer Milk case 2020014: // Sunrise Dew case 2020015: // Sunset Dew case 2022142: // Mind & Heart Medicine case 2022186: // Soft White Bun return 10; case 2060000: // Arrow for Bow case 2061000: // Arrow for Crossbow case 2060001: // Bronze Arrow for Bow case 2061001: // Bronze Arrow for Crossbow return 15; case 2070000: // Subi Throwing-Stars case 2070001: // Wolbi Throwing-Stars case 2070002: // Mokbi Throwing-Stars case 2070003: // Kumbi Throwing-Stars case 2070004: // Tobi Throwing-Stars case 2070005: // Steely Throwing-Knives case 2070006: // Ilbi Throwing-Stars case 2070007: // Hwabi Throwing-Stars case 2070008: // Snowball case 2070009: // Wooden Top case 2070010: // Icicle return 10; default: return 5; } case 4: switch (id / 1000) { case 4000: // monster piece(s) case 4001: // quest piece(s) case 4002: // stamp(s) case 4003: // processing case 4004: // crystal ore(s) case 4005: // crystal(s) case 4006: // magic rock & summoning rock case 4007: // magic powder(s) case 4010: // basic ore(s) case 4011: // plates case 4020: // advanced ore(s) case 4021: // jewel case 4022: // herbalism seeds case 4023: // herbalism oils case 4024: // herbalism bottles case 4025: // herbalism coagulants case 4030: // omok pieces, tetris pieces case 4031: // quest items case 4032: // ??? case 4033: // ??? case 4055: // surgery coupons case 4080: // omok set case 4130: // production stimulator case 4140: // ??? case 4160: // pet command guides case 4161: // ??? case 4162: // ??? case 4170: // pigmy eggs, etc. case 4210: // rings case 4211: // regular invitation case 4212: // premium invitation case 4213: // ??? case 4214: // wedding receipt case 4220: // ??? case 4250: // item-production case 4251: // item-production case 4260: // item-production case 4280: // treasure boxes case 4290: // effects case 4300: // ??? case 4310: // shop currency case 4320: // ??? case 4330: // profession bags return 55; default: return 55; } } return 0; } public String getQuerySegment() { StringBuilder sb = new StringBuilder(); sb.append("("); sb.append(monsterId); sb.append(", "); sb.append(itemId); sb.append(", "); sb.append(mindrop); sb.append(", "); sb.append(maxdrop); sb.append(", "); sb.append(quest); // Quest ID sb.append(", "); sb.append(chance); sb.append(")"); return sb.toString(); } } }