Restructure chr saving - gather stats in record

This commit is contained in:
P0nk
2024-09-14 11:48:32 +02:00
parent 23a8a33e5f
commit abbec8120e
4 changed files with 261 additions and 122 deletions

View File

@@ -72,6 +72,7 @@
<postgresql.version>42.5.4</postgresql.version> <!-- PostgreSQL JDBC driver -->
<flyway.version>9.15.1</flyway.version> <!-- Database migration -->
<caffeine.version>3.1.4</caffeine.version> <!-- Caching -->
<lombok.version>1.18.34</lombok.version> <!-- Code generation -->
</properties>
<dependencies>
@@ -90,6 +91,12 @@
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Database -->
<dependency>

View File

@@ -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<Pet> 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 = ?");

View File

@@ -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
) {
}

View File

@@ -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();