diff --git a/pom.xml b/pom.xml index 9be26b5f7f..38c81dacb6 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,7 @@ 42.5.4 9.15.1 3.1.4 + 1.18.34 @@ -90,6 +91,12 @@ caffeine ${caffeine.version} + + org.projectlombok + lombok + ${lombok.version} + provided + diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index ccd1a7a101..59ec146921 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -8177,128 +8177,9 @@ public class Character extends AbstractCharacterObject { //ItemFactory saveItems and monsterbook.saveCards are the most time consuming here. public synchronized void saveCharToDB(Connection con) throws SQLException { Server.getInstance().updateCharacterEntry(this); + saveCharacter(con); - try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET level = ?, fame = ?, str = ?, dex = ?, luk = ?, `int` = ?, exp = ?, gachaexp = ?, hp = ?, mp = ?, maxhp = ?, maxmp = ?, sp = ?, ap = ?, gm = ?, skincolor = ?, gender = ?, job = ?, hair = ?, face = ?, map = ?, meso = ?, hpMpUsed = ?, spawnpoint = ?, party = ?, buddyCapacity = ?, messengerid = ?, messengerposition = ?, mountlevel = ?, mountexp = ?, mounttiredness= ?, equipslots = ?, useslots = ?, setupslots = ?, etcslots = ?, monsterbookcover = ?, vanquisherStage = ?, dojoPoints = ?, lastDojoStage = ?, finishedDojoTutorial = ?, vanquisherKills = ?, matchcardwins = ?, matchcardlosses = ?, matchcardties = ?, omokwins = ?, omoklosses = ?, omokties = ?, dataString = ?, fquest = ?, jailexpire = ?, partnerId = ?, marriageItemId = ?, lastExpGainTime = ?, ariantPoints = ?, partySearch = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) { - ps.setInt(1, level); // thanks CanIGetaPR for noticing an unnecessary "level" limitation when persisting DB data - ps.setInt(2, fame); - - effLock.lock(); - statWlock.lock(); - try { - ps.setInt(3, str); - ps.setInt(4, dex); - ps.setInt(5, luk); - ps.setInt(6, int_); - ps.setInt(7, Math.abs(exp.get())); - ps.setInt(8, Math.abs(gachaexp.get())); - ps.setInt(9, hp); - ps.setInt(10, mp); - ps.setInt(11, maxhp); - ps.setInt(12, maxmp); - - StringBuilder sps = new StringBuilder(); - for (int j : remainingSp) { - sps.append(j); - sps.append(","); - } - String sp = sps.toString(); - ps.setString(13, sp.substring(0, sp.length() - 1)); - - ps.setInt(14, remainingAp); - } finally { - statWlock.unlock(); - effLock.unlock(); - } - - ps.setInt(15, gmLevel); - ps.setInt(16, skinColor.getId()); - ps.setInt(17, gender); - ps.setInt(18, job.getId()); - ps.setInt(19, hair); - ps.setInt(20, face); - if (map == null || (cashshop != null && cashshop.isOpened())) { - ps.setInt(21, mapid); - } else { - if (map.getForcedReturnId() != MapId.NONE) { - ps.setInt(21, map.getForcedReturnId()); - } else { - ps.setInt(21, getHp() < 1 ? map.getReturnMapId() : map.getId()); - } - } - ps.setInt(22, meso.get()); - ps.setInt(23, hpMpApUsed); - if (map == null || map.getId() == MapId.CRIMSONWOOD_VALLEY_1 || map.getId() == MapId.CRIMSONWOOD_VALLEY_2) { // reset to first spawnpoint on those maps - ps.setInt(24, 0); - } else { - Portal closest = map.findClosestPlayerSpawnpoint(getPosition()); - if (closest != null) { - ps.setInt(24, closest.getId()); - } else { - ps.setInt(24, 0); - } - } - - prtLock.lock(); - try { - if (party != null) { - ps.setInt(25, party.getId()); - } else { - ps.setInt(25, -1); - } - } finally { - prtLock.unlock(); - } - - ps.setInt(26, buddylist.getCapacity()); - if (messenger != null) { - ps.setInt(27, messenger.getId()); - ps.setInt(28, messengerposition); - } else { - ps.setInt(27, 0); - ps.setInt(28, 4); - } - if (maplemount != null) { - ps.setInt(29, maplemount.getLevel()); - ps.setInt(30, maplemount.getExp()); - ps.setInt(31, maplemount.getTiredness()); - } else { - ps.setInt(29, 1); - ps.setInt(30, 0); - ps.setInt(31, 0); - } - for (int i = 1; i < 5; i++) { - ps.setInt(i + 31, getSlots(i)); - } - - monsterbook.saveCards(con, id); - - ps.setInt(36, bookCover); - ps.setInt(37, vanquisherStage); - ps.setInt(38, dojoPoints); - ps.setInt(39, dojoStage); - ps.setInt(40, finishedDojoTutorial ? 1 : 0); - ps.setInt(41, vanquisherKills); - ps.setInt(42, matchcardwins); - ps.setInt(43, matchcardlosses); - ps.setInt(44, matchcardties); - ps.setInt(45, omokwins); - ps.setInt(46, omoklosses); - ps.setInt(47, omokties); - ps.setString(48, dataString); - ps.setInt(49, quest_fame); - ps.setLong(50, jailExpiration); - ps.setInt(51, partnerId); - ps.setInt(52, marriageItemid); - ps.setTimestamp(53, new Timestamp(lastExpGainTime)); - ps.setInt(54, ariantPoints); - ps.setBoolean(55, canRecvPartySearchInvite); - ps.setInt(56, id); - - int updateRows = ps.executeUpdate(); - if (updateRows < 1) { - throw new RuntimeException("Character not in database (" + id + ")"); - } - } + monsterbook.saveCards(con, id); List petList = new LinkedList<>(); petLock.lock(); @@ -8556,6 +8437,193 @@ public class Character extends AbstractCharacterObject { } } + private void saveCharacter(Connection con) throws SQLException { + SaveStats stats = getSaveStats(); + try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET level = ?, fame = ?, str = ?, dex = ?, luk = ?, `int` = ?, exp = ?, gachaexp = ?, hp = ?, mp = ?, maxhp = ?, maxmp = ?, sp = ?, ap = ?, gm = ?, skincolor = ?, gender = ?, job = ?, hair = ?, face = ?, map = ?, meso = ?, hpMpUsed = ?, spawnpoint = ?, party = ?, buddyCapacity = ?, messengerid = ?, messengerposition = ?, mountlevel = ?, mountexp = ?, mounttiredness= ?, equipslots = ?, useslots = ?, setupslots = ?, etcslots = ?, monsterbookcover = ?, vanquisherStage = ?, dojoPoints = ?, lastDojoStage = ?, finishedDojoTutorial = ?, vanquisherKills = ?, matchcardwins = ?, matchcardlosses = ?, matchcardties = ?, omokwins = ?, omoklosses = ?, omokties = ?, dataString = ?, fquest = ?, jailexpire = ?, partnerId = ?, marriageItemId = ?, lastExpGainTime = ?, ariantPoints = ?, partySearch = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) { + ps.setInt(1, stats.level()); + ps.setInt(2, stats.fame()); + ps.setInt(3, stats.str()); + ps.setInt(4, stats.dex()); + ps.setInt(5, stats.luk()); + ps.setInt(6, stats.int_()); + ps.setInt(7, stats.exp()); + ps.setInt(8, stats.gachaExp()); + ps.setInt(9, stats.hp()); + ps.setInt(10, stats.mp()); + ps.setInt(11, stats.maxHp()); + ps.setInt(12, stats.maxMp()); + ps.setString(13, stats.sp()); + ps.setInt(14, stats.ap()); + ps.setInt(15, stats.gmLevel()); + ps.setInt(16, stats.skin()); + ps.setInt(17, stats.gender()); + ps.setInt(18, stats.job()); + ps.setInt(19, stats.hair()); + ps.setInt(20, stats.face()); + ps.setInt(21, stats.mapId()); + ps.setInt(22, stats.meso()); + ps.setInt(23, stats.hpMpApUsed()); + ps.setInt(24, stats.spawnPoint()); + ps.setInt(25, stats.party()); + ps.setInt(26, stats.buddyCapacity()); + ps.setInt(27, stats.messenger()); + ps.setInt(28, stats.messengerPosition()); + ps.setInt(29, stats.mountLevel()); + ps.setInt(30, stats.mountExp()); + ps.setInt(31, stats.mountTiredness()); + ps.setInt(32, stats.equipSlots()); + ps.setInt(33, stats.useSlots()); + ps.setInt(34, stats.setupSlots()); + ps.setInt(35, stats.etcSlots()); + ps.setInt(36, stats.monsterBookCover()); + ps.setInt(37, stats.dojoVanquisherStage()); + ps.setInt(38, stats.dojoPoints()); + ps.setInt(39, stats.dojoStage()); + ps.setInt(40, stats.dojoTutorialComplete() ? 1 : 0); + ps.setInt(41, stats.dojoVanquisherKills()); + ps.setInt(42, stats.matchCardWins()); + ps.setInt(43, stats.matchCardLosses()); + ps.setInt(44, stats.matchCardTies()); + ps.setInt(45, stats.omokWins()); + ps.setInt(46, stats.omokLosses()); + ps.setInt(47, stats.omokTies()); + ps.setString(48, stats.dataString()); + ps.setInt(49, stats.questFame()); + ps.setLong(50, stats.jailExpiration()); + ps.setInt(51, stats.partnerId()); + ps.setInt(52, stats.marriageItemId()); + ps.setTimestamp(53, new Timestamp(stats.lastExpGainTime())); + ps.setInt(54, stats.ariantPoints()); + ps.setBoolean(55, stats.canRecvPartySearchInvite()); + ps.setInt(56, stats.id()); + + int updateRows = ps.executeUpdate(); + if (updateRows < 1) { + throw new RuntimeException("Character not in database (" + id + ")"); + } + } + } + + private SaveStats getSaveStats() { + SaveStats.SaveStatsBuilder builder = SaveStats.builder() + .id(id) + .level(level) + .fame(fame) + .gmLevel(gmLevel) + .skin(skinColor.getId()) + .gender(gender) + .job(job.getId()) + .hair(hair) + .face(face) + .meso(meso.get()) + .hpMpApUsed(hpMpApUsed) + .mapId(getSaveMap()) + .spawnPoint(getSaveSpawnpoint()) + .buddyCapacity(buddylist.getCapacity()) + .monsterBookCover(bookCover) + .dojoVanquisherStage(vanquisherStage) + .dojoPoints(dojoPoints) + .dojoStage(dojoStage) + .dojoTutorialComplete(finishedDojoTutorial) + .dojoVanquisherKills(vanquisherKills) + .matchCardWins(matchcardwins) + .matchCardLosses(matchcardlosses) + .matchCardTies(matchcardties) + .omokWins(omokwins) + .omokLosses(omoklosses) + .omokTies(omokties) + .dataString(dataString) + .questFame(quest_fame) + .jailExpiration(jailExpiration) + .partnerId(partnerId) + .marriageItemId(marriageItemid) + .lastExpGainTime(lastExpGainTime) + .ariantPoints(ariantPoints) + .canRecvPartySearchInvite(canRecvPartySearchInvite) + .party(getPartyId()) + .equipSlots(getSlots(InventoryType.EQUIP.getType())) + .useSlots(getSlots(InventoryType.USE.getType())) + .setupSlots(getSlots(InventoryType.SETUP.getType())) + .etcSlots(getSlots(InventoryType.ETC.getType())); + + effLock.lock(); + statWlock.lock(); + try { + builder.str(str) + .dex(dex) + .int_(int_) + .luk(luk) + .exp(Math.abs(exp.get())) + .gachaExp(Math.abs(gachaexp.get())) + .hp(hp) + .mp(mp) + .maxHp(maxhp) + .maxMp(maxmp) + .ap(remainingAp); + + StringBuilder sps = new StringBuilder(); + for (int j : remainingSp) { + sps.append(j); + sps.append(","); + } + String sp = sps.toString(); + builder.sp(sp.substring(0, sp.length() - 1)); + } finally { + statWlock.unlock(); + effLock.unlock(); + } + + + if (messenger != null) { + builder.messenger(messenger.getId()) + .messengerPosition(messengerposition); + } else { + builder.messenger(0) + .messengerPosition(4); + } + + if (maplemount != null) { + builder.mountLevel(maplemount.getLevel()) + .mountExp(maplemount.getExp()) + .mountTiredness(maplemount.getTiredness()); + } else { + builder.mountLevel(1) + .mountExp(0) + .mountTiredness(0); + } + + return builder.build(); + } + + private int getSaveMap() { + if (map == null || (cashshop != null && cashshop.isOpened())) { + return mapid; + } + + if (map.getForcedReturnId() != MapId.NONE) { + return map.getForcedReturnId(); + } + + if (getHp() < 1) { + return map.getReturnMapId(); + } + + return map.getId(); + } + + private int getSaveSpawnpoint() { + if (map == null || map.getId() == MapId.CRIMSONWOOD_VALLEY_1 || map.getId() == MapId.CRIMSONWOOD_VALLEY_2) { // TODO: clean up. Shouldn't hardcode these maps. + return 0; + } + + Portal closest = map.findClosestPlayerSpawnpoint(getPosition()); + if (closest == null) { + return 0; + } + + return closest.getId(); + } + private void saveCooldowns(Connection con) throws SQLException { deleteWhereCharacterId(con, "DELETE FROM cooldowns WHERE charid = ?"); diff --git a/src/main/java/client/SaveStats.java b/src/main/java/client/SaveStats.java new file mode 100644 index 0000000000..9bae227511 --- /dev/null +++ b/src/main/java/client/SaveStats.java @@ -0,0 +1,64 @@ +package client; + +import lombok.Builder; + +@Builder +public record SaveStats( + int id, + int level, + int fame, + int str, + int dex, + int luk, + int int_, + int exp, + int gachaExp, + int hp, + int mp, + int maxHp, + int maxMp, + String sp, + int ap, + int gmLevel, + int skin, + int gender, + int job, + int hair, + int face, + int mapId, + int meso, + int hpMpApUsed, + int spawnPoint, + int party, + int buddyCapacity, + int messenger, + int messengerPosition, + int mountLevel, + int mountExp, + int mountTiredness, + int equipSlots, + int useSlots, + int setupSlots, + int etcSlots, + int monsterBookCover, + int dojoVanquisherStage, + int dojoPoints, + int dojoStage, + boolean dojoTutorialComplete, + int dojoVanquisherKills, + int matchCardWins, + int matchCardLosses, + int matchCardTies, + int omokWins, + int omokLosses, + int omokTies, + String dataString, + int questFame, + long jailExpiration, + int partnerId, + int marriageItemId, + long lastExpGainTime, + int ariantPoints, + boolean canRecvPartySearchInvite +) { +} diff --git a/src/main/java/database/character/CharacterSaver.java b/src/main/java/database/character/CharacterSaver.java index 841bea8da7..cee135084b 100644 --- a/src/main/java/database/character/CharacterSaver.java +++ b/src/main/java/database/character/CharacterSaver.java @@ -25,7 +25,7 @@ public class CharacterSaver { log.debug("Saving chr {}", chr.getName()); try (Connection con = DatabaseConnection.getConnection()) { con.setAutoCommit(false); - con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); + con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); try { chr.saveCharToDB(con); con.commit();