From bc6593fd8105c633112d087f068294cb73a90f20 Mon Sep 17 00:00:00 2001 From: P0nk Date: Sat, 10 Jul 2021 18:07:46 +0200 Subject: [PATCH] Move MapleArrowFetcher to the main module --- .../java/server/life/MapleLifeFactory.java | 4 +- src/main/java/tools/DatabaseConnection.java | 4 + .../java/tools/mapletools/ArrowFetcher.java | 220 ++++++++++++++++++ .../tools/mapletools/MonsterStatFetcher.java | 143 ++++++++++++ .../mapletools/SimpleDatabaseConnection.java | 30 +++ .../java/tools/mapletools/ToolConstants.java | 12 + tools/input/.gitignore | 2 + tools/output/.gitignore | 2 + 8 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 src/main/java/tools/mapletools/ArrowFetcher.java create mode 100644 src/main/java/tools/mapletools/MonsterStatFetcher.java create mode 100644 src/main/java/tools/mapletools/SimpleDatabaseConnection.java create mode 100644 src/main/java/tools/mapletools/ToolConstants.java create mode 100644 tools/input/.gitignore create mode 100644 tools/output/.gitignore diff --git a/src/main/java/server/life/MapleLifeFactory.java b/src/main/java/server/life/MapleLifeFactory.java index d8a8914341..556a8953c9 100644 --- a/src/main/java/server/life/MapleLifeFactory.java +++ b/src/main/java/server/life/MapleLifeFactory.java @@ -322,7 +322,7 @@ public class MapleLifeFactory { private int id; private byte chance, x; - private loseItem(int id, byte chance, byte x) { + public loseItem(int id, byte chance, byte x) { this.id = id; this.chance = chance; this.x = x; @@ -347,7 +347,7 @@ public class MapleLifeFactory { private int removeAfter; private int hp; - private selfDestruction(byte action, int removeAfter, int hp) { + public selfDestruction(byte action, int removeAfter, int hp) { this.action = action; this.removeAfter = removeAfter; this.hp = hp; diff --git a/src/main/java/tools/DatabaseConnection.java b/src/main/java/tools/DatabaseConnection.java index ba74ee0f18..0246152ea3 100644 --- a/src/main/java/tools/DatabaseConnection.java +++ b/src/main/java/tools/DatabaseConnection.java @@ -63,6 +63,10 @@ public class DatabaseConnection { * @return true if connection to the database initiated successfully, false if not successful */ public static boolean initializeConnectionPool() { + if (dataSource != null) { + return true; + } + log.info("Initializing connection pool..."); final HikariConfig config = getConfig(); Instant initStart = Instant.now(); diff --git a/src/main/java/tools/mapletools/ArrowFetcher.java b/src/main/java/tools/mapletools/ArrowFetcher.java new file mode 100644 index 0000000000..d492414a1a --- /dev/null +++ b/src/main/java/tools/mapletools/ArrowFetcher.java @@ -0,0 +1,220 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2019 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package tools.mapletools; + +import server.life.MapleMonsterStats; +import tools.Pair; + +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * @author RonanLana + *

+ * This application traces arrow drop data on the underlying DB (that must be + * defined on the DatabaseConnection file of this project) and generates a SQL file + * that proposes updated arrow quantitty on drop entries for the drop_data table. + *

+ * The arrow quantity range is calculated accordingly with the target mob stats, such + * as level and if it's a boss or not. + */ + +public class ArrowFetcher { + private static final String OUTPUT_FILE_NAME = "arrow_drop_data.sql"; + private static final int MIN_ARROW_ID = 2060000; + private static final int MAX_ARROW_ID = 2061004; + private static final float CORRECTION_FACTOR = 2.2f; + + private static final Map> mobRange = new HashMap<>(); + private static PrintWriter printWriter; + private static Map mobStats; + + private static Pair calcArrowRange(int level, boolean boss) { + int minRange, maxRange; + + // MIN range + minRange = (int) Math.ceil(((2.870503597 * level) - 1.870503597) * (boss ? 1.4 : 1.0) / CORRECTION_FACTOR); + + // MAX range + maxRange = (int) Math.ceil(1.25 * minRange); + + return new Pair<>(minRange, maxRange); + } + + private static void calcAllMobsArrowRange() { + System.out.print("Calculating range... "); + + for (Entry mobStat : mobStats.entrySet()) { + MapleMonsterStats mms = mobStat.getValue(); + Pair arrowRange; + + arrowRange = calcArrowRange(mms.getLevel(), mms.isBoss()); + mobRange.put(mobStat.getKey(), arrowRange); + } + + System.out.println("done!"); + } + + private static void printSqlHeader() { + printWriter.println(" # SQL File autogenerated from the MapleArrowFetcher feature by Ronan Lana."); + printWriter.println(" # Generated data takes into account mob stats such as level and boss for the raw arrow ranges."); + printWriter.println(" # Only current arrows entries on the DB it was compiled are being updated here."); + printWriter.println(); + + printWriter.println("UPDATE drop_data"); + printWriter.println("SET minimum_quantity = CASE"); + } + + private static void printSqlMiddle() { + printWriter.println(" ELSE minimum_quantity END,"); + printWriter.println(" maximum_quantity = CASE"); + } + + private static void printSqlFooter() { + printWriter.println(" ELSE maximum_quantity END"); + printWriter.println(";"); + } + + private static void updateSqlMobArrowMinEntry(int[] entry) { + printWriter.println(" WHEN dropperid = " + entry[0] + " AND itemid = " + entry[1] + " THEN " + entry[2]); + } + + private static void updateSqlMobArrowMaxEntry(int[] entry) { + printWriter.println(" WHEN dropperid = " + entry[0] + " AND itemid = " + entry[1] + " THEN " + entry[3]); + } + + private static List getArrowEntryValues(Map> existingEntries) { + List entryValues = new ArrayList<>(200); + + List>> listEntries = new ArrayList<>(existingEntries.entrySet()); + + listEntries.sort((o1, o2) -> { + int val1 = o1.getKey(); + int val2 = o2.getKey(); + return Integer.compare(val1, val2); + }); + + for (Entry> ee : listEntries) { + int mobid = ee.getKey(); + Pair mr = mobRange.get(mobid); + + for (Integer itemid : ee.getValue()) { + int itemWeight = (itemid % 10) + 1; + + int[] values = new int[4]; + values[0] = mobid; + values[1] = itemid; + + values[2] = (int) Math.ceil(mr.getLeft() / itemWeight); // weighted min quantity + values[3] = (int) Math.ceil(mr.getRight() / itemWeight); // weighted max quantity + + entryValues.add(values); + } + } + + return entryValues; + } + + private static void updateMobsArrowRange() { + System.out.print("Generating updated ranges... "); + final Connection con = SimpleDatabaseConnection.getConnection(); + + Map> existingEntries = new HashMap<>(200); + + try { + // select all arrow drop entries on the DB, to update their values + PreparedStatement ps = con.prepareStatement("SELECT dropperid, itemid FROM drop_data WHERE itemid >= " + MIN_ARROW_ID + " AND itemid <= " + MAX_ARROW_ID + " ORDER BY itemid;"); + ResultSet rs = ps.executeQuery(); + + if (rs.isBeforeFirst()) { + while (rs.next()) { + int mobid = rs.getInt(1); + int itemid = rs.getInt(2); + + if (mobRange.containsKey(mobid)) { + List em = existingEntries.get(mobid); + + if (em == null) { + em = new ArrayList<>(2); + existingEntries.put(mobid, em); + } + + em.add(itemid); + } + } + + if (!existingEntries.isEmpty()) { + List entryValues = getArrowEntryValues(existingEntries); + + printWriter = new PrintWriter(ToolConstants.getOutputFile(OUTPUT_FILE_NAME), StandardCharsets.UTF_8); + printSqlHeader(); + + for (int[] arrowEntry : entryValues) { + updateSqlMobArrowMinEntry(arrowEntry); + } + + printSqlMiddle(); + + for (int[] arrowEntry : entryValues) { + updateSqlMobArrowMaxEntry(arrowEntry); + } + + printSqlFooter(); + + printWriter.close(); + } else { + throw new Exception("NO DATA"); + } + + } else { + throw new Exception("NO DATA"); + } + + rs.close(); + ps.close(); + con.close(); + + System.out.println("done!"); + + } catch (Exception e) { + if (e.getMessage() != null && e.getMessage().equals("NO DATA")) { + System.out.println("failed! The DB has no arrow entry to be updated."); + } else { + e.printStackTrace(); + } + } + } + + public static void main(String[] args) { + // load mob stats from WZ + mobStats = MonsterStatFetcher.getAllMonsterStats(); + + calcAllMobsArrowRange(); + updateMobsArrowRange(); + } +} diff --git a/src/main/java/tools/mapletools/MonsterStatFetcher.java b/src/main/java/tools/mapletools/MonsterStatFetcher.java new file mode 100644 index 0000000000..61cbe5a2cc --- /dev/null +++ b/src/main/java/tools/mapletools/MonsterStatFetcher.java @@ -0,0 +1,143 @@ +package tools.mapletools; + +import provider.*; +import provider.wz.MapleDataType; +import provider.wz.WZFiles; +import server.life.Element; +import server.life.ElementalEffectiveness; +import server.life.MapleLifeFactory.BanishInfo; +import server.life.MapleLifeFactory.loseItem; +import server.life.MapleLifeFactory.selfDestruction; +import server.life.MapleMonsterStats; +import tools.Pair; + +import java.util.*; + +public class MonsterStatFetcher { + private static final MapleDataProvider data = MapleDataProviderFactory.getDataProvider(WZFiles.MOB); + private static final MapleDataProvider stringDataWZ = MapleDataProviderFactory.getDataProvider(WZFiles.STRING); + private static final MapleData mobStringData = stringDataWZ.getData("Mob.img"); + private static final Map monsterStats = new HashMap<>(); + + static Map getAllMonsterStats() { + MapleDataDirectoryEntry root = data.getRoot(); + + System.out.print("Parsing mob stats... "); + for (MapleDataFileEntry mFile : root.getFiles()) { + try { + String fileName = mFile.getName(); + + //System.out.println("Parsing '" + fileName + "'"); + MapleData monsterData = data.getData(fileName); + if (monsterData == null) { + continue; + } + + Integer mid = getMonsterId(fileName); + + MapleData monsterInfoData = monsterData.getChildByPath("info"); + MapleMonsterStats stats = new MapleMonsterStats(); + stats.setHp(MapleDataTool.getIntConvert("maxHP", monsterInfoData)); + stats.setFriendly(MapleDataTool.getIntConvert("damagedByMob", monsterInfoData, 0) == 1); + stats.setPADamage(MapleDataTool.getIntConvert("PADamage", monsterInfoData)); + stats.setPDDamage(MapleDataTool.getIntConvert("PDDamage", monsterInfoData)); + stats.setMADamage(MapleDataTool.getIntConvert("MADamage", monsterInfoData)); + stats.setMDDamage(MapleDataTool.getIntConvert("MDDamage", monsterInfoData)); + stats.setMp(MapleDataTool.getIntConvert("maxMP", monsterInfoData, 0)); + stats.setExp(MapleDataTool.getIntConvert("exp", monsterInfoData, 0)); + stats.setLevel(MapleDataTool.getIntConvert("level", monsterInfoData)); + stats.setRemoveAfter(MapleDataTool.getIntConvert("removeAfter", monsterInfoData, 0)); + stats.setBoss(MapleDataTool.getIntConvert("boss", monsterInfoData, 0) > 0); + stats.setExplosiveReward(MapleDataTool.getIntConvert("explosiveReward", monsterInfoData, 0) > 0); + stats.setFfaLoot(MapleDataTool.getIntConvert("publicReward", monsterInfoData, 0) > 0); + stats.setUndead(MapleDataTool.getIntConvert("undead", monsterInfoData, 0) > 0); + stats.setName(MapleDataTool.getString(mid + "/name", mobStringData, "MISSINGNO")); + stats.setBuffToGive(MapleDataTool.getIntConvert("buff", monsterInfoData, -1)); + stats.setCP(MapleDataTool.getIntConvert("getCP", monsterInfoData, 0)); + stats.setRemoveOnMiss(MapleDataTool.getIntConvert("removeOnMiss", monsterInfoData, 0) > 0); + + MapleData special = monsterInfoData.getChildByPath("coolDamage"); + if (special != null) { + int coolDmg = MapleDataTool.getIntConvert("coolDamage", monsterInfoData); + int coolProb = MapleDataTool.getIntConvert("coolDamageProb", monsterInfoData, 0); + stats.setCool(new Pair<>(coolDmg, coolProb)); + } + special = monsterInfoData.getChildByPath("loseItem"); + if (special != null) { + for (MapleData liData : special.getChildren()) { + stats.addLoseItem(new loseItem(MapleDataTool.getInt(liData.getChildByPath("id")), (byte) MapleDataTool.getInt(liData.getChildByPath("prop")), (byte) MapleDataTool.getInt(liData.getChildByPath("x")))); + } + } + special = monsterInfoData.getChildByPath("selfDestruction"); + if (special != null) { + stats.setSelfDestruction(new selfDestruction((byte) MapleDataTool.getInt(special.getChildByPath("action")), MapleDataTool.getIntConvert("removeAfter", special, -1), MapleDataTool.getIntConvert("hp", special, -1))); + } + MapleData firstAttackData = monsterInfoData.getChildByPath("firstAttack"); + int firstAttack = 0; + if (firstAttackData != null) { + if (firstAttackData.getType() == MapleDataType.FLOAT) { + firstAttack = Math.round(MapleDataTool.getFloat(firstAttackData)); + } else { + firstAttack = MapleDataTool.getInt(firstAttackData); + } + } + stats.setFirstAttack(firstAttack > 0); + stats.setDropPeriod(MapleDataTool.getIntConvert("dropItemPeriod", monsterInfoData, 0) * 10000); + + stats.setTagColor(MapleDataTool.getIntConvert("hpTagColor", monsterInfoData, 0)); + stats.setTagBgColor(MapleDataTool.getIntConvert("hpTagBgcolor", monsterInfoData, 0)); + + for (MapleData idata : monsterData) { + if (!idata.getName().equals("info")) { + int delay = 0; + for (MapleData pic : idata.getChildren()) { + delay += MapleDataTool.getIntConvert("delay", pic, 0); + } + stats.setAnimationTime(idata.getName(), delay); + } + } + MapleData reviveInfo = monsterInfoData.getChildByPath("revive"); + if (reviveInfo != null) { + List revives = new LinkedList<>(); + for (MapleData data_ : reviveInfo) { + revives.add(MapleDataTool.getInt(data_)); + } + stats.setRevives(revives); + } + decodeElementalString(stats, MapleDataTool.getString("elemAttr", monsterInfoData, "")); + MapleData monsterSkillData = monsterInfoData.getChildByPath("skill"); + if (monsterSkillData != null) { + int i = 0; + List> skills = new ArrayList<>(); + while (monsterSkillData.getChildByPath(Integer.toString(i)) != null) { + skills.add(new Pair<>(MapleDataTool.getInt(i + "/skill", monsterSkillData, 0), MapleDataTool.getInt(i + "/level", monsterSkillData, 0))); + i++; + } + stats.setSkills(skills); + } + MapleData banishData = monsterInfoData.getChildByPath("ban"); + if (banishData != null) { + stats.setBanishInfo(new BanishInfo(MapleDataTool.getString("banMsg", banishData), MapleDataTool.getInt("banMap/0/field", banishData, -1), MapleDataTool.getString("banMap/0/portal", banishData, "sp"))); + } + + monsterStats.put(mid, stats); + } catch(NullPointerException npe) { + //System.out.println("[SEVERE] " + mFile.getName() + " failed to load. Issue: " + npe.getMessage() + "\n\n"); + } + } + + System.out.println("Done parsing mob stats!"); + return monsterStats; + } + + private static int getMonsterId(String fileName) { + return Integer.parseInt(fileName.substring(0, 7)); + } + + private static void decodeElementalString(MapleMonsterStats stats, String elemAttr) { + for (int i = 0; i < elemAttr.length(); i += 2) { + stats.setEffectiveness(Element.getFromChar(elemAttr.charAt(i)), ElementalEffectiveness.getByNumber(Integer.valueOf(String.valueOf(elemAttr.charAt(i + 1))))); + } + } + +} diff --git a/src/main/java/tools/mapletools/SimpleDatabaseConnection.java b/src/main/java/tools/mapletools/SimpleDatabaseConnection.java new file mode 100644 index 0000000000..80f5f1bf88 --- /dev/null +++ b/src/main/java/tools/mapletools/SimpleDatabaseConnection.java @@ -0,0 +1,30 @@ +package tools.mapletools; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configurator; +import tools.DatabaseConnection; + +import java.sql.Connection; +import java.sql.SQLException; + +final class SimpleDatabaseConnection { + private SimpleDatabaseConnection() {} + + static Connection getConnection() { + muffleLogging(); + DatabaseConnection.initializeConnectionPool(); + + try { + return DatabaseConnection.getConnection(); + } catch (SQLException e) { + throw new IllegalStateException("Failed to get database connection", e); + } + } + + private static void muffleLogging() { + final Level minimumVisibleLevel = Level.WARN; + Configurator.setLevel(LogManager.getLogger(com.zaxxer.hikari.HikariDataSource.class).getName(), minimumVisibleLevel); + Configurator.setRootLevel(minimumVisibleLevel); + } +} diff --git a/src/main/java/tools/mapletools/ToolConstants.java b/src/main/java/tools/mapletools/ToolConstants.java new file mode 100644 index 0000000000..aa097172cf --- /dev/null +++ b/src/main/java/tools/mapletools/ToolConstants.java @@ -0,0 +1,12 @@ +package tools.mapletools; + +import java.io.File; + +public class ToolConstants { + public static final File INPUT_DIRECTORY = new File("tools/input"); + public static final File OUTPUT_DIRECTORY = new File("tools/output"); + + public static File getOutputFile(String fileName) { + return new File(OUTPUT_DIRECTORY, fileName); + } +} diff --git a/tools/input/.gitignore b/tools/input/.gitignore new file mode 100644 index 0000000000..c96a04f008 --- /dev/null +++ b/tools/input/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/tools/output/.gitignore b/tools/output/.gitignore new file mode 100644 index 0000000000..c96a04f008 --- /dev/null +++ b/tools/output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file