Refactor SkillbookChanceFetcher & add logs

Less static variables and generally cleaner code
This commit is contained in:
P0nk
2025-07-06 10:17:21 +02:00
parent 02f45397ce
commit 89dfc37551

View File

@@ -1,7 +1,6 @@
package tools.mapletools; package tools.mapletools;
import server.life.MonsterStats; import server.life.MonsterStats;
import tools.Pair;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -10,41 +9,97 @@ import java.nio.file.Path;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* @author RonanLana
* <p> * <p>
* This application traces missing meso drop data on the underlying DB (that must be * This application normalizes skillbook drop chances on the underlying DB (that must be
* defined on the DatabaseConnection file of this project) and generates a * defined on the DatabaseConnection file of this project) and generates a
* SQL file that proposes missing drop entries for the drop_data table. * SQL file that proposes missing drop entries for the drop_data table.
* <p> * <p>
* The meso range is calculated accordingly with the target mob stats, such as level * The drop chances are calculated based on the mob level. Bosses get a higher drop chance.
* and if it's a boss or not, similarly as how it has been done for the actual meso * Legend skillbooks gain a boost in drop chance (for some reason).
* drops. *
* @author RonanLana
* @author Ponk
*/ */
public class SkillbookChanceFetcher { public class SkillbookChanceFetcher {
private static final Path OUTPUT_FILE = ToolConstants.getOutputFile("skillbook_drop_data.sql"); private static final Path OUTPUT_FILE = ToolConstants.getOutputFile("skillbook_drop_data.sql");
private static final Map<Pair<Integer, Integer>, Integer> skillbookChances = new HashMap<>();
private static PrintWriter printWriter; private record DropIdentifier(int mobId, int itemId) {}
private static Map<Integer, MonsterStats> mobStats;
private static List<Map.Entry<Pair<Integer, Integer>, Integer>> sortedSkillbookChances() { public static void main(String[] args) {
List<Map.Entry<Pair<Integer, Integer>, Integer>> skillbookChancesList = new ArrayList<>(skillbookChances.entrySet()); Instant start = Instant.now();
skillbookChancesList.sort((o1, o2) -> { System.out.println("Fetching all skillbook drops from the database...");
if (o1.getKey().getLeft().equals(o2.getKey().getLeft())) { List<DropIdentifier> skillbookDrops = fetchAllSkillbookDrops();
return o1.getKey().getRight() < o2.getKey().getRight() ? -1 : (o1.getKey().getRight().equals(o2.getKey().getRight()) ? 0 : 1); System.out.printf("Found %d skillbook drops%n", skillbookDrops.size());
// load mob stats from WZ
Map<Integer, MonsterStats> mobStats = MonsterStatFetcher.getAllMonsterStats();
System.out.println("Calculating drop chances...");
Map<DropIdentifier, Integer> skillbookChances = calculateSkillbookDropChances(skillbookDrops, mobStats);
System.out.println("Writing output file...");
generateSkillbookChanceUpdateFile(skillbookChances);
Duration totalDuration = Duration.between(start, Instant.now());
System.out.printf("Done! Total elapsed time: %d ms", totalDuration.toMillis());
} }
return (o1.getKey().getLeft() < o2.getKey().getLeft()) ? -1 : 1; private static List<DropIdentifier> fetchAllSkillbookDrops() {
}); String sql = """
SELECT dropperid, itemid
FROM drop_data
WHERE itemid >= 2280000
AND itemid < 2300000""";
try (Connection con = SimpleDatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
List<DropIdentifier> skillbookDrops = new ArrayList<>();
while (rs.next()) {
int mobId = rs.getInt("dropperid");
int itemId = rs.getInt("itemid");
skillbookDrops.add(new DropIdentifier(mobId, itemId));
}
return skillbookDrops;
} catch (Exception e) {
throw new RuntimeException("Failed to fetch all skillbook drops", e);
}
}
return skillbookChancesList; private static Map<DropIdentifier, Integer> calculateSkillbookDropChances(List<DropIdentifier> skillbookDrops,
Map<Integer, MonsterStats> mobStats) {
Map<DropIdentifier, Integer> skillbookChances = new HashMap<>();
for (DropIdentifier drop : skillbookDrops) {
int expectedChance = 250;
if (mobStats.get(drop.mobId) != null) {
int level = mobStats.get(drop.mobId).getLevel();
expectedChance *= Math.max(2, (level - 80) / 15);
if (mobStats.get(drop.mobId).isBoss()) {
expectedChance *= 20;
}
} else {
expectedChance = 1287;
}
if (isLegendSkillUpgradeBook(drop.itemId)) { // drop rate of Legends seems to be higher than explorers, in retrospect from values in DB
expectedChance *= 3;
}
skillbookChances.put(drop, expectedChance);
}
return skillbookChances;
} }
private static boolean isLegendSkillUpgradeBook(int itemid) { private static boolean isLegendSkillUpgradeBook(int itemid) {
@@ -52,77 +107,37 @@ public class SkillbookChanceFetcher {
return (itemidBranch == 228 && itemid >= 2280013 || itemidBranch == 229 && itemid >= 2290126); // drop rate of Legends are higher return (itemidBranch == 228 && itemid >= 2280013 || itemidBranch == 229 && itemid >= 2290126); // drop rate of Legends are higher
} }
private static void fetchSkillbookDropChances() { private static void generateSkillbookChanceUpdateFile(Map<DropIdentifier, Integer> skillbookChances) {
Connection con = SimpleDatabaseConnection.getConnection();
try {
PreparedStatement ps = con.prepareStatement("SELECT dropperid, itemid FROM drop_data WHERE itemid >= 2280000 AND itemid < 2300000");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
int mobid = rs.getInt("dropperid");
int itemid = rs.getInt("itemid");
int expectedChance = 250;
if (mobStats.get(mobid) != null) {
int level = mobStats.get(mobid).getLevel();
expectedChance *= Math.max(2, (level - 80) / 15);
if (mobStats.get(mobid).isBoss()) {
expectedChance *= 20;
} else {
expectedChance *= 1;
}
} else {
expectedChance = 1287;
}
if (isLegendSkillUpgradeBook(itemid)) { // drop rate of Legends seems to be higher than explorers, in retrospect from values in DB
expectedChance *= 3;
}
skillbookChances.put(new Pair<>(mobid, itemid), expectedChance);
}
rs.close();
ps.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void printSkillbookChanceUpdateSqlHeader() {
printWriter.println(" # SQL File autogenerated from the MapleSkillbookChanceFetcher feature by Ronan Lana.");
printWriter.println(" # Generated data takes into account mob stats such as level and boss for the chance rates.");
printWriter.println();
printWriter.println(" REPLACE INTO drop_data (`dropperid`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`) VALUES");
}
private static void generateSkillbookChanceUpdateFile() {
try (PrintWriter pw = new PrintWriter(Files.newOutputStream(OUTPUT_FILE))) { try (PrintWriter pw = new PrintWriter(Files.newOutputStream(OUTPUT_FILE))) {
printWriter = pw; printSkillbookChanceUpdateSqlHeader(pw);
printSkillbookChanceUpdateSqlHeader(); List<Map.Entry<DropIdentifier, Integer>> skillbookChancesList = sortedSkillbookChances(skillbookChances);
for (Map.Entry<DropIdentifier, Integer> e : skillbookChancesList) {
List<Map.Entry<Pair<Integer, Integer>, Integer>> skillbookChancesList = sortedSkillbookChances(); pw.println("(%d, %d, 1, 1, 0, %d),".formatted(e.getKey().mobId, e.getKey().itemId, e.getValue()));
for (Map.Entry<Pair<Integer, Integer>, Integer> e : skillbookChancesList) {
printWriter.println("(" + e.getKey().getLeft() + ", " + e.getKey().getRight() + ", 1, 1, 0, "
+ e.getValue() + "),");
} }
} catch (IOException ioe) { } catch (IOException e) {
ioe.printStackTrace(); throw new RuntimeException("Failed to write output file", e);
} }
} }
public static void main(String[] args) { private static void printSkillbookChanceUpdateSqlHeader(PrintWriter pw) {
// load mob stats from WZ pw.println(" # SQL File autogenerated from the SkillbookChanceFetcher feature by Ronan Lana.");
mobStats = MonsterStatFetcher.getAllMonsterStats(); pw.println(" # Generated data takes into account mob stats such as level and boss for the chance rates.");
pw.println();
fetchSkillbookDropChances(); pw.println(" REPLACE INTO drop_data (`dropperid`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`) VALUES");
generateSkillbookChanceUpdateFile(); }
private static List<Map.Entry<DropIdentifier, Integer>> sortedSkillbookChances(
Map<DropIdentifier, Integer> skillbookChances) {
List<Map.Entry<DropIdentifier, Integer>> skillbookChancesList = new ArrayList<>(skillbookChances.entrySet());
skillbookChancesList.sort(mobIdAscThenItemIdAscComparator());
return skillbookChancesList;
}
private static Comparator<Map.Entry<DropIdentifier, Integer>> mobIdAscThenItemIdAscComparator() {
return Comparator.comparing((Map.Entry<DropIdentifier, Integer> entry) -> entry.getKey().mobId)
.thenComparing(entry -> entry.getKey().itemId);
} }
} }