Move MapleArrowFetcher to the main module
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
220
src/main/java/tools/mapletools/ArrowFetcher.java
Normal file
220
src/main/java/tools/mapletools/ArrowFetcher.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<Integer, Pair<Integer, Integer>> mobRange = new HashMap<>();
|
||||
private static PrintWriter printWriter;
|
||||
private static Map<Integer, MapleMonsterStats> mobStats;
|
||||
|
||||
private static Pair<Integer, Integer> 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<Integer, MapleMonsterStats> mobStat : mobStats.entrySet()) {
|
||||
MapleMonsterStats mms = mobStat.getValue();
|
||||
Pair<Integer, Integer> 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<int[]> getArrowEntryValues(Map<Integer, List<Integer>> existingEntries) {
|
||||
List<int[]> entryValues = new ArrayList<>(200);
|
||||
|
||||
List<Entry<Integer, List<Integer>>> listEntries = new ArrayList<>(existingEntries.entrySet());
|
||||
|
||||
listEntries.sort((o1, o2) -> {
|
||||
int val1 = o1.getKey();
|
||||
int val2 = o2.getKey();
|
||||
return Integer.compare(val1, val2);
|
||||
});
|
||||
|
||||
for (Entry<Integer, List<Integer>> ee : listEntries) {
|
||||
int mobid = ee.getKey();
|
||||
Pair<Integer, Integer> 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<Integer, List<Integer>> 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<Integer> em = existingEntries.get(mobid);
|
||||
|
||||
if (em == null) {
|
||||
em = new ArrayList<>(2);
|
||||
existingEntries.put(mobid, em);
|
||||
}
|
||||
|
||||
em.add(itemid);
|
||||
}
|
||||
}
|
||||
|
||||
if (!existingEntries.isEmpty()) {
|
||||
List<int[]> 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();
|
||||
}
|
||||
}
|
||||
143
src/main/java/tools/mapletools/MonsterStatFetcher.java
Normal file
143
src/main/java/tools/mapletools/MonsterStatFetcher.java
Normal file
@@ -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<Integer, MapleMonsterStats> monsterStats = new HashMap<>();
|
||||
|
||||
static Map<Integer, MapleMonsterStats> 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<Integer> 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<Pair<Integer, Integer>> 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)))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
30
src/main/java/tools/mapletools/SimpleDatabaseConnection.java
Normal file
30
src/main/java/tools/mapletools/SimpleDatabaseConnection.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
12
src/main/java/tools/mapletools/ToolConstants.java
Normal file
12
src/main/java/tools/mapletools/ToolConstants.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
2
tools/input/.gitignore
vendored
Normal file
2
tools/input/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
2
tools/output/.gitignore
vendored
Normal file
2
tools/output/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
Reference in New Issue
Block a user