diff --git a/config.yaml b/config.yaml index c34d9848f0..48d43f3aa8 100644 --- a/config.yaml +++ b/config.yaml @@ -234,6 +234,7 @@ server: USE_STARTING_AP_4: false #Use early-GMS 4/4/4/4 starting stats. To overcome AP shortage, this gives 4AP/5AP at 1st/2nd job advancements. USE_AUTOBAN: false #Commands the server to detect infractors automatically. USE_AUTOBAN_LOG: true #Log autoban related messages. Still logs even with USE_AUTOBAN disabled. + USE_EXP_GAIN_LOG: true #Logs characters exp gains; logs world rate & coupon exp, total gained exp, and current exp, level can be calculated from "ExpTable". USE_AUTOSAVE: true #Enables server autosaving feature (saves characters to DB each 1 hour). USE_SERVER_AUTOASSIGNER: false #HeavenMS-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments. USE_REFRESH_RANK_MOVE: true diff --git a/database/sql/1-db_database.sql b/database/sql/1-db_database.sql index c24fc76912..639ca90b80 100644 --- a/database/sql/1-db_database.sql +++ b/database/sql/1-db_database.sql @@ -21484,6 +21484,17 @@ CREATE TABLE IF NOT EXISTS `worldtransfers` ( INDEX (characterid) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; +CREATE TABLE IF NOT EXISTS `characterexplogs` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `world_exp_rate` INT, + `exp_coupon` INT, + `gained_exp` BIGINT, + `current_exp` INT, + `exp_gain_time` DATETIME, + `charid` int(11) NOT NULL, + PRIMARY KEY (`id`), + FOREIGN KEY (`charid`) REFERENCES `characters`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; ALTER TABLE `dueyitems` ADD CONSTRAINT `dueyitems_ibfk_1` FOREIGN KEY (`PackageId`) REFERENCES `dueypackages` (`PackageId`) ON DELETE CASCADE; diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 289abd8396..53ea8df391 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -150,6 +150,8 @@ public class Character extends AbstractCharacterObject { private final AtomicInteger gachaexp = new AtomicInteger(); private final AtomicInteger meso = new AtomicInteger(); private final AtomicInteger chair = new AtomicInteger(-1); + private long totalExpGained = 0; + private static final LinkedList> expGainLogQueue = new LinkedList<>(); private int merchantmeso; private BuddyList buddylist; private EventInstanceManager eventInstance = null; @@ -323,6 +325,45 @@ public class Character extends AbstractCharacterObject { setPosition(new Point(0, 0)); } + static { + if (YamlConfig.config.server.USE_EXP_GAIN_LOG) { + SaveExpLogToDBThread expThread = new SaveExpLogToDBThread(); + expThread.setPriority(Thread.MIN_PRIORITY); // Set to the lowest priority + expThread.start(); + } + } + + // Nested static class for saving exp logs to the database + private static class SaveExpLogToDBThread extends Thread { + @Override + public void run() { + while(true) { + try { + if (expGainLogQueue.isEmpty()) { + synchronized (expGainLogQueue) { + expGainLogQueue.wait(); + } + } + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO characterexplogs (world_exp_rate, exp_coupon, gained_exp, current_exp, exp_gain_time, charid) VALUES (?, ?, ?, ?, ?, ?)")) { + while (!expGainLogQueue.isEmpty()) { + Map psMap = expGainLogQueue.poll(); + ps.setInt(1, (int) psMap.get("world_exp_rate")); + ps.setInt(2, (int) psMap.get("exp_coupon")); + ps.setLong(3,(long) psMap.get("gained_exp")); + ps.setInt(4, (int) psMap.get("current_exp")); + ps.setTimestamp(5, (Timestamp) psMap.get("exp_gain_time")); + ps.setInt(6, (int) psMap.get("charid")); + ps.executeUpdate(); + } + } + } catch (SQLException | InterruptedException e) { + e.printStackTrace(); + } + } + } + } + private static Job getJobStyleInternal(int jobid, byte opt) { int jobtype = jobid / 100; @@ -3092,6 +3133,7 @@ public class Character extends AbstractCharacterObject { leftover = nextExp - Integer.MAX_VALUE; } updateSingleStat(Stat.EXP, exp.addAndGet((int) total)); + totalExpGained += total; if (show) { announceExpGain(gain, equip, party, inChat, white); } @@ -3107,11 +3149,29 @@ public class Character extends AbstractCharacterObject { if (leftover > 0) { gainExpInternal(leftover, equip, party, false, inChat, white); } else { + saveExpLogToDB(); lastExpGainTime = System.currentTimeMillis(); } } } + private void saveExpLogToDB() { + if (YamlConfig.config.server.USE_EXP_GAIN_LOG) { + Map psMap = new HashMap<>(); + psMap.put("world_exp_rate", getWorldServer().getExpRate()); + psMap.put("exp_coupon", expCoupon); + psMap.put("gained_exp", totalExpGained); + psMap.put("current_exp", exp.get()); + psMap.put("charid", id); + psMap.put("exp_gain_time", new Timestamp(System.currentTimeMillis())); + expGainLogQueue.add(psMap); + synchronized (expGainLogQueue){ + expGainLogQueue.notifyAll(); + } + totalExpGained = 0; + } + } + private Pair applyFame(int delta) { petLock.lock(); try { diff --git a/src/main/java/config/ServerConfig.java b/src/main/java/config/ServerConfig.java index 499ac738ef..d7050da6a6 100644 --- a/src/main/java/config/ServerConfig.java +++ b/src/main/java/config/ServerConfig.java @@ -82,6 +82,7 @@ public class ServerConfig { public boolean USE_STARTING_AP_4; public boolean USE_AUTOBAN; public boolean USE_AUTOBAN_LOG; + public boolean USE_EXP_GAIN_LOG; public boolean USE_AUTOSAVE; public boolean USE_SERVER_AUTOASSIGNER; public boolean USE_REFRESH_RANK_MOVE;