package tools.mapletools; import org.apache.commons.io.FileUtils; import provider.wz.WZFiles; import server.ItemInformationProvider; import tools.DatabaseConnection; import tools.Pair; import java.io.*; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; /** * @author RonanLana *

* This application haves 2 objectives: fetch missing drop data relevant to quests, * and update the questid from items that are labeled as "Quest Item" on the DB. *

* Running it should generate a report file under "output" folder with the search results. *

* Estimated parse time: 1 minute */ public class QuestItemFetcher { private static final File OUTPUT_FILE = ToolConstants.getOutputFile("quest_report.txt"); private static final int INITIAL_STRING_LENGTH = 50; private static final int INITIAL_LENGTH = 200; private static final boolean DISPLAY_EXTRA_INFO = true; // display items with zero quantity over the quest act WZ private static final Connection con = SimpleDatabaseConnection.getConnection(); private static final Map> startQuestItems = new HashMap<>(INITIAL_LENGTH); private static final Map> completeQuestItems = new HashMap<>(INITIAL_LENGTH); private static final Map> zeroedStartQuestItems = new HashMap<>(); private static final Map> zeroedCompleteQuestItems = new HashMap<>(); private static final Map mixedQuestidItems = new HashMap<>(); private static final Set limitedQuestids = new HashSet<>(); private static ItemInformationProvider ii; private static PrintWriter printWriter = null; private static BufferedReader bufferedReader = null; private static byte status = 0; private static int questId = -1; private static int isCompleteState = 0; private static int currentItemid = 0; private static int currentCount = 0; private static String getName(String token) { int i, j; char[] dest; String d; i = token.lastIndexOf("name"); i = token.indexOf("\"", i) + 1; //lower bound of the string j = token.indexOf("\"", i); //upper bound if (j < i) { return "0"; //node value containing 'name' in it's scope, cheap fix since we don't deal with strings anyway } dest = new char[INITIAL_STRING_LENGTH]; token.getChars(i, j, dest, 0); d = new String(dest); return (d.trim()); } private static String getValue(String token) { int i, j; char[] dest; String d; i = token.lastIndexOf("value"); i = token.indexOf("\"", i) + 1; //lower bound of the string j = token.indexOf("\"", i); //upper bound dest = new char[INITIAL_STRING_LENGTH]; token.getChars(i, j, dest, 0); d = new String(dest); return (d.trim()); } private static void forwardCursor(int st) { String line = null; try { while (status >= st && (line = bufferedReader.readLine()) != null) { simpleToken(line); } } catch (Exception e) { e.printStackTrace(); } } private static void simpleToken(String token) { if (token.contains("/imgdir")) { status -= 1; } else if (token.contains("imgdir")) { status += 1; } } private static void inspectQuestItemList(int st) { String line = null; try { while (status >= st && (line = bufferedReader.readLine()) != null) { readItemToken(line); } } catch (Exception e) { e.printStackTrace(); } } private static void processCurrentItem() { try { if (ii.isQuestItem(currentItemid)) { if (currentCount != 0) { if (isCompleteState == 1) { if (currentCount < 0) { Set qi = completeQuestItems.get(questId); if (qi == null) { Set newSet = new HashSet<>(); newSet.add(currentItemid); completeQuestItems.put(questId, newSet); } else { qi.add(currentItemid); } } } else { if (currentCount > 0) { Set qi = startQuestItems.get(questId); if (qi == null) { Set newSet = new HashSet<>(); newSet.add(currentItemid); startQuestItems.put(questId, newSet); } else { qi.add(currentItemid); } } } } else { if (isCompleteState == 1) { Set qi = zeroedCompleteQuestItems.get(questId); if (qi == null) { Set newSet = new HashSet<>(); newSet.add(currentItemid); zeroedCompleteQuestItems.put(questId, newSet); } else { qi.add(currentItemid); } } else { Set qi = zeroedStartQuestItems.get(questId); if (qi == null) { Set newSet = new HashSet<>(); newSet.add(currentItemid); zeroedStartQuestItems.put(questId, newSet); } else { qi.add(currentItemid); } } } } } catch (Exception e) { } } private static void readItemToken(String token) { if (token.contains("/imgdir")) { status -= 1; processCurrentItem(); currentItemid = 0; currentCount = 0; } else if (token.contains("imgdir")) { status += 1; } else { String d = getName(token); if (d.equals("id")) { currentItemid = Integer.parseInt(getValue(token)); } else if (d.equals("count")) { currentCount = Integer.parseInt(getValue(token)); } } } private static void translateActToken(String token) { String d; int temp; if (token.contains("/imgdir")) { status -= 1; } else if (token.contains("imgdir")) { if (status == 1) { //getting QuestId d = getName(token); questId = Integer.parseInt(d); } else if (status == 2) { //start/complete d = getName(token); isCompleteState = Integer.parseInt(d); } else if (status == 3) { d = getName(token); if (d.contains("item")) { temp = status; inspectQuestItemList(temp); } else { forwardCursor(status); } } status += 1; } else { if (status == 3) { d = getName(token); if (d.equals("end")) { limitedQuestids.add(questId); } } } } private static void translateCheckToken(String token) { String d; if (token.contains("/imgdir")) { status -= 1; } else if (token.contains("imgdir")) { if (status == 1) { //getting QuestId d = getName(token); questId = Integer.parseInt(d); } else if (status == 2) { //start/complete d = getName(token); isCompleteState = Integer.parseInt(d); } else if (status == 3) { forwardCursor(status); } status += 1; } else { if (status == 3) { d = getName(token); if (d.equals("end")) { limitedQuestids.add(questId); } } } } private static void calculateQuestItemDiff() { // This will remove started quest items from the "to complete" item set. for (Map.Entry> qd : startQuestItems.entrySet()) { for (Integer qi : qd.getValue()) { Set questSet = completeQuestItems.get(qd.getKey()); if (questSet != null) { if (questSet.remove(qi)) { if (completeQuestItems.isEmpty()) { completeQuestItems.remove(qd.getKey()); } } } } } } private static List> getPairsQuestItem() { // quest items not gained at WZ's quest start List> list = new ArrayList<>(INITIAL_LENGTH); for (Map.Entry> qd : completeQuestItems.entrySet()) { for (Integer qi : qd.getValue()) { list.add(new Pair<>(qi, qd.getKey())); } } return list; } private static String getTableName(boolean dropdata) { return dropdata ? "drop_data" : "reactordrops"; } private static void filterQuestDropsOnTable(Pair iq, List> itemsWithQuest, boolean dropdata) throws SQLException { PreparedStatement ps = con.prepareStatement("SELECT questid FROM " + getTableName(dropdata) + " WHERE itemid = ?;"); ps.setInt(1, iq.getLeft()); ResultSet rs = ps.executeQuery(); if (rs.isBeforeFirst()) { while (rs.next()) { int curQuest = rs.getInt(1); if (curQuest != iq.getRight()) { Set sqSet = startQuestItems.get(curQuest); if (sqSet != null && sqSet.contains(iq.getLeft())) { continue; } int[] mixed = new int[3]; mixed[0] = iq.getLeft(); mixed[1] = curQuest; mixed[2] = iq.getRight(); mixedQuestidItems.put(iq.getLeft(), mixed); } } itemsWithQuest.remove(iq); } rs.close(); ps.close(); } private static void filterQuestDropsOnDB(List> itemsWithQuest) throws SQLException { List> copyItemsWithQuest = new ArrayList<>(itemsWithQuest); try { for (Pair iq : copyItemsWithQuest) { filterQuestDropsOnTable(iq, itemsWithQuest, true); filterQuestDropsOnTable(iq, itemsWithQuest, false); } } catch (SQLException e) { e.printStackTrace(); } } private static void filterDirectorySearchMatchingData(String path, List> itemsWithQuest) { Iterator iter = FileUtils.iterateFiles(new File(path), new String[]{"sql", "js", "txt", "java"}, true); while (iter.hasNext()) { File file = iter.next(); fileSearchMatchingData(file, itemsWithQuest); } } private static boolean foundMatchingDataOnFile(String fileContent, String searchStr) { return fileContent.contains(searchStr); } private static void fileSearchMatchingData(File file, List> itemsWithQuest) { try { String fileContent = FileUtils.readFileToString(file, StandardCharsets.UTF_8); List> copyItemsWithQuest = new ArrayList<>(itemsWithQuest); for (Pair iq : copyItemsWithQuest) { if (foundMatchingDataOnFile(fileContent, String.valueOf(iq.getLeft()))) { itemsWithQuest.remove(iq); } } } catch (IOException ioe) { System.out.println("Failed to read file: " + file.getAbsolutePath()); ioe.printStackTrace(); } } private static void printReportFileHeader() { printWriter.println(" # Report File autogenerated from the MapleQuestItemFetcher feature by Ronan Lana."); printWriter.println(" # Generated data takes into account several data info from the underlying DB, server source files and the server-side WZ.xmls."); printWriter.println(); } private static List> getSortedMapEntries0(Map map) { List> list = new ArrayList<>(map.size()); list.addAll(map.entrySet()); list.sort((o1, o2) -> o1.getKey() - o2.getKey()); return list; } private static List> getSortedMapEntries1(Map map) { List> list = new ArrayList<>(map.size()); list.addAll(map.entrySet()); list.sort((o1, o2) -> o1.getKey() - o2.getKey()); return list; } private static List>> getSortedMapEntries2(Map> map) { List>> list = new ArrayList<>(map.size()); for (Map.Entry> e : map.entrySet()) { List il = new ArrayList<>(2); il.addAll(e.getValue()); il.sort((o1, o2) -> o1 - o2); list.add(new Pair<>(e.getKey(), il)); } list.sort((o1, o2) -> o1.getLeft() - o2.getLeft()); return list; } private static String getExpiredStringLabel(int questid) { return (!limitedQuestids.contains(questid) ? "" : " EXPIRED"); } private static void reportQuestItemData() { // This will reference one line at a time String line = null; String fileName = null; try { System.out.println("Reading WZs..."); fileName = WZFiles.QUEST.getFilePath() + "/Check.img.xml"; InputStreamReader fileReader = new InputStreamReader(new FileInputStream(fileName), StandardCharsets.UTF_8); bufferedReader = new BufferedReader(fileReader); while ((line = bufferedReader.readLine()) != null) { translateCheckToken(line); // fetch expired quests through here as well } bufferedReader.close(); fileReader.close(); fileName = WZFiles.QUEST.getFilePath() + "/Act.img.xml"; fileReader = new InputStreamReader(new FileInputStream(fileName), StandardCharsets.UTF_8); bufferedReader = new BufferedReader(fileReader); while ((line = bufferedReader.readLine()) != null) { translateActToken(line); } bufferedReader.close(); fileReader.close(); System.out.println("Calculating table diffs..."); calculateQuestItemDiff(); System.out.println("Filtering drops on DB..."); List> itemsWithQuest = getPairsQuestItem(); filterQuestDropsOnDB(itemsWithQuest); con.close(); System.out.println("Filtering drops on project files..."); // finally, filter whether this item is mentioned on the source code or not. filterDirectorySearchMatchingData("scripts", itemsWithQuest); filterDirectorySearchMatchingData("sql", itemsWithQuest); filterDirectorySearchMatchingData("src", itemsWithQuest); System.out.println("Reporting results..."); // report suspects of missing quest drop data, as well as those drop data that may have incorrect questids. printWriter = new PrintWriter(OUTPUT_FILE, StandardCharsets.UTF_8); printReportFileHeader(); if (!mixedQuestidItems.isEmpty()) { printWriter.println("INCORRECT QUESTIDS ON DB"); for (Map.Entry emqi : getSortedMapEntries1(mixedQuestidItems)) { int[] mqi = emqi.getValue(); printWriter.println(mqi[0] + " : " + mqi[1] + " -> " + mqi[2] + getExpiredStringLabel(mqi[2])); } printWriter.println("\n\n\n\n\n"); } if (!itemsWithQuest.isEmpty()) { Map mapIwq = new HashMap<>(itemsWithQuest.size()); for (Pair iwq : itemsWithQuest) { mapIwq.put(iwq.getLeft(), iwq.getRight()); } printWriter.println("ITEMS WITH NO QUEST DROP DATA ON DB"); for (Map.Entry iwq : getSortedMapEntries0(mapIwq)) { printWriter.println(iwq.getKey() + " - " + iwq.getValue() + getExpiredStringLabel(iwq.getValue())); } printWriter.println("\n\n\n\n\n"); } if (DISPLAY_EXTRA_INFO) { if (!zeroedStartQuestItems.isEmpty()) { printWriter.println("START QUEST ITEMS WITH ZERO QUANTITY"); for (Pair> iwq : getSortedMapEntries2(zeroedStartQuestItems)) { printWriter.println(iwq.getLeft() + getExpiredStringLabel(iwq.getLeft()) + ":"); for (Integer i : iwq.getRight()) { printWriter.println(" " + i); } printWriter.println(); } printWriter.println("\n\n\n\n\n"); } if (!zeroedCompleteQuestItems.isEmpty()) { printWriter.println("COMPLETE QUEST ITEMS WITH ZERO QUANTITY"); for (Pair> iwq : getSortedMapEntries2(zeroedCompleteQuestItems)) { printWriter.println(iwq.getLeft() + getExpiredStringLabel(iwq.getLeft()) + ":"); for (Integer i : iwq.getRight()) { printWriter.println(" " + i); } printWriter.println(); } printWriter.println("\n\n\n\n\n"); } } printWriter.close(); System.out.println("Done!"); } catch (FileNotFoundException ex) { System.out.println("Unable to open file '" + fileName + "'"); } catch (IOException ex) { System.out.println("Error reading file '" + fileName + "'"); } catch (SQLException e) { System.out.println("Warning: Could not establish connection to database to report quest data."); System.out.println(e.getMessage()); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { DatabaseConnection.initializeConnectionPool(); // ItemInformationProvider loads some unrelated db data ii = ItemInformationProvider.getInstance(); reportQuestItemData(); } }