From 23a8a33e5f5a16c01b1ecb17867e1fdaa79c4388 Mon Sep 17 00:00:00 2001 From: P0nk Date: Fri, 13 Sep 2024 23:59:29 +0200 Subject: [PATCH] Move transaction handling to CharacterSaver --- src/main/java/client/Character.java | 762 +++++++++--------- .../database/character/CharacterSaver.java | 30 +- 2 files changed, 398 insertions(+), 394 deletions(-) diff --git a/src/main/java/client/Character.java b/src/main/java/client/Character.java index 2064574463..ccd1a7a101 100644 --- a/src/main/java/client/Character.java +++ b/src/main/java/client/Character.java @@ -8175,408 +8175,384 @@ public class Character extends AbstractCharacterObject { } //ItemFactory saveItems and monsterbook.saveCards are the most time consuming here. - public synchronized void saveCharToDB() { - if (!loggedIn) { - return; - } - - Calendar c = Calendar.getInstance(); - log.debug("Saving chr {}", name); - + public synchronized void saveCharToDB(Connection con) throws SQLException { Server.getInstance().updateCharacterEntry(this); - try (Connection con = DatabaseConnection.getConnection()) { - con.setAutoCommit(false); - con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + 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 { - 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); + 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); - 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 + ")"); - } + 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)); - List petList = new LinkedList<>(); - petLock.lock(); - try { - for (int i = 0; i < 3; i++) { - if (pets[i] != null) { - petList.add(pets[i]); - } - } - } finally { - petLock.unlock(); - } - - for (Pet pet : petList) { - pet.saveToDb(); - } - - for (Entry> es : getExcluded().entrySet()) { // this set is already protected - try (PreparedStatement psIgnore = con.prepareStatement("DELETE FROM petignores WHERE petid=?")) { - psIgnore.setInt(1, es.getKey()); - psIgnore.executeUpdate(); - } - - try (PreparedStatement psIgnore = con.prepareStatement("INSERT INTO petignores (petid, itemid) VALUES (?, ?)")) { - psIgnore.setInt(1, es.getKey()); - for (Integer x : es.getValue()) { - psIgnore.setInt(2, x); - psIgnore.addBatch(); - } - psIgnore.executeBatch(); - } - } - - // Key config - deleteWhereCharacterId(con, "DELETE FROM keymap WHERE characterid = ?"); - try (PreparedStatement psKey = con.prepareStatement("INSERT INTO keymap (characterid, `key`, `type`, `action`) VALUES (?, ?, ?, ?)")) { - psKey.setInt(1, id); - - Set> keybindingItems = Collections.unmodifiableSet(keymap.entrySet()); - for (Entry keybinding : keybindingItems) { - psKey.setInt(2, keybinding.getKey()); - psKey.setInt(3, keybinding.getValue().getType()); - psKey.setInt(4, keybinding.getValue().getAction()); - psKey.addBatch(); - } - psKey.executeBatch(); - } - - // No quickslots, or no change. - boolean bQuickslotEquals = this.m_pQuickslotKeyMapped == null || (this.m_aQuickslotLoaded != null && Arrays.equals(this.m_pQuickslotKeyMapped.GetKeybindings(), this.m_aQuickslotLoaded)); - if (!bQuickslotEquals) { - long nQuickslotKeymapped = LongTool.BytesToLong(this.m_pQuickslotKeyMapped.GetKeybindings()); - - try (final PreparedStatement psQuick = con.prepareStatement("INSERT INTO quickslotkeymapped (accountid, keymap) VALUES (?, ?) ON DUPLICATE KEY UPDATE keymap = ?;")) { - psQuick.setInt(1, this.getAccountID()); - psQuick.setLong(2, nQuickslotKeymapped); - psQuick.setLong(3, nQuickslotKeymapped); - psQuick.executeUpdate(); - } - } - - // Skill macros - deleteWhereCharacterId(con, "DELETE FROM skillmacros WHERE characterid = ?"); - try (PreparedStatement psMacro = con.prepareStatement("INSERT INTO skillmacros (characterid, skill1, skill2, skill3, name, shout, position) VALUES (?, ?, ?, ?, ?, ?, ?)")) { - psMacro.setInt(1, getId()); - for (int i = 0; i < 5; i++) { - SkillMacro macro = skillMacros[i]; - if (macro != null) { - psMacro.setInt(2, macro.getSkill1()); - psMacro.setInt(3, macro.getSkill2()); - psMacro.setInt(4, macro.getSkill3()); - psMacro.setString(5, macro.getName()); - psMacro.setInt(6, macro.getShout()); - psMacro.setInt(7, i); - psMacro.addBatch(); - } - } - psMacro.executeBatch(); - } - - List> itemsWithType = new ArrayList<>(); - for (Inventory iv : inventory) { - for (Item item : iv.list()) { - itemsWithType.add(new Pair<>(item, iv.getType())); - } - } - - // Items - ItemFactory.INVENTORY.saveItems(itemsWithType, id, con); - - // Skills - try (PreparedStatement psSkill = con.prepareStatement("REPLACE INTO skills (characterid, skillid, skilllevel, masterlevel, expiration) VALUES (?, ?, ?, ?, ?)")) { - psSkill.setInt(1, id); - for (Entry skill : skills.entrySet()) { - psSkill.setInt(2, skill.getKey().getId()); - psSkill.setInt(3, skill.getValue().skillevel); - psSkill.setInt(4, skill.getValue().masterlevel); - psSkill.setLong(5, skill.getValue().expiration); - psSkill.addBatch(); - } - psSkill.executeBatch(); - } - - // Saved locations - deleteWhereCharacterId(con, "DELETE FROM savedlocations WHERE characterid = ?"); - try (PreparedStatement psLoc = con.prepareStatement("INSERT INTO savedlocations (characterid, `locationtype`, `map`, `portal`) VALUES (?, ?, ?, ?)")) { - psLoc.setInt(1, id); - for (SavedLocationType savedLocationType : SavedLocationType.values()) { - if (savedLocations[savedLocationType.ordinal()] != null) { - psLoc.setString(2, savedLocationType.name()); - psLoc.setInt(3, savedLocations[savedLocationType.ordinal()].getMapId()); - psLoc.setInt(4, savedLocations[savedLocationType.ordinal()].getPortal()); - psLoc.addBatch(); - } - } - psLoc.executeBatch(); - } - - deleteWhereCharacterId(con, "DELETE FROM trocklocations WHERE characterid = ?"); - - // Vip teleport rocks - try (PreparedStatement psVip = con.prepareStatement("INSERT INTO trocklocations(characterid, mapid, vip) VALUES (?, ?, 0)")) { - for (int i = 0; i < getTrockSize(); i++) { - if (trockmaps.get(i) != MapId.NONE) { - psVip.setInt(1, getId()); - psVip.setInt(2, trockmaps.get(i)); - psVip.addBatch(); - } - } - psVip.executeBatch(); - } - - // Regular teleport rocks - try (PreparedStatement psReg = con.prepareStatement("INSERT INTO trocklocations(characterid, mapid, vip) VALUES (?, ?, 1)")) { - for (int i = 0; i < getVipTrockSize(); i++) { - if (viptrockmaps.get(i) != MapId.NONE) { - psReg.setInt(1, getId()); - psReg.setInt(2, viptrockmaps.get(i)); - psReg.addBatch(); - } - } - psReg.executeBatch(); - } - - // Buddy - deleteWhereCharacterId(con, "DELETE FROM buddies WHERE characterid = ? AND pending = 0"); - try (PreparedStatement psBuddy = con.prepareStatement("INSERT INTO buddies (characterid, `buddyid`, `pending`, `group`) VALUES (?, ?, 0, ?)")) { - psBuddy.setInt(1, id); - - for (BuddylistEntry entry : buddylist.getBuddies()) { - if (entry.isVisible()) { - psBuddy.setInt(2, entry.getCharacterId()); - psBuddy.setString(3, entry.getGroup()); - psBuddy.addBatch(); - } - } - psBuddy.executeBatch(); - } - - // Area info - deleteWhereCharacterId(con, "DELETE FROM area_info WHERE charid = ?"); - try (PreparedStatement psArea = con.prepareStatement("INSERT INTO area_info (id, charid, area, info) VALUES (DEFAULT, ?, ?, ?)")) { - psArea.setInt(1, id); - - for (Entry area : area_info.entrySet()) { - psArea.setInt(2, area.getKey()); - psArea.setString(3, area.getValue()); - psArea.addBatch(); - } - psArea.executeBatch(); - } - - // Event stats - deleteWhereCharacterId(con, "DELETE FROM eventstats WHERE characterid = ?"); - try (PreparedStatement psEvent = con.prepareStatement("INSERT INTO eventstats (characterid, name, info) VALUES (?, ?, ?)")) { - psEvent.setInt(1, id); - - for (Map.Entry entry : events.entrySet()) { - psEvent.setString(2, entry.getKey()); - psEvent.setInt(3, entry.getValue().getInfo()); - psEvent.addBatch(); - } - - psEvent.executeBatch(); - } - - deleteQuestProgressWhereCharacterId(con, id); - - // Quests and medals - try (PreparedStatement psStatus = con.prepareStatement("INSERT INTO queststatus (`queststatusid`, `characterid`, `quest`, `status`, `time`, `expires`, `forfeited`, `completed`) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); - PreparedStatement psProgress = con.prepareStatement("INSERT INTO questprogress VALUES (DEFAULT, ?, ?, ?, ?)"); - PreparedStatement psMedal = con.prepareStatement("INSERT INTO medalmaps VALUES (DEFAULT, ?, ?, ?)")) { - psStatus.setInt(1, id); - - for (QuestStatus qs : getQuests()) { - psStatus.setInt(2, qs.getQuest().getId()); - psStatus.setInt(3, qs.getStatus().getId()); - psStatus.setInt(4, (int) (qs.getCompletionTime() / 1000)); - psStatus.setLong(5, qs.getExpirationTime()); - psStatus.setInt(6, qs.getForfeited()); - psStatus.setInt(7, qs.getCompleted()); - psStatus.executeUpdate(); - - try (ResultSet rs = psStatus.getGeneratedKeys()) { - rs.next(); - for (int mob : qs.getProgress().keySet()) { - psProgress.setInt(1, id); - psProgress.setInt(2, rs.getInt(1)); - psProgress.setInt(3, mob); - psProgress.setString(4, qs.getProgress(mob)); - psProgress.addBatch(); - } - psProgress.executeBatch(); - - for (int i = 0; i < qs.getMedalMaps().size(); i++) { - psMedal.setInt(1, id); - psMedal.setInt(2, rs.getInt(1)); - psMedal.setInt(3, qs.getMedalMaps().get(i)); - psMedal.addBatch(); - } - psMedal.executeBatch(); - } - } - } - - FamilyEntry familyEntry = getFamilyEntry(); //save family rep - if (familyEntry != null) { - if (familyEntry.saveReputation(con)) { - familyEntry.savedSuccessfully(); - } - FamilyEntry senior = familyEntry.getSenior(); - if (senior != null && senior.getChr() == null) { //only save for offline family members - if (senior.saveReputation(con)) { - senior.savedSuccessfully(); - } - senior = senior.getSenior(); //save one level up as well - if (senior != null && senior.getChr() == null) { - if (senior.saveReputation(con)) { - senior.savedSuccessfully(); - } - } - } - } - - saveCooldowns(con); - saveDiseases(con); - - if (cashshop != null) { - cashshop.save(con); - } - - if (storage != null && usedStorage) { - storage.saveToDB(con); - usedStorage = false; - } - - con.commit(); - } catch (Exception e) { - con.rollback(); - throw e; + ps.setInt(14, remainingAp); } finally { - con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); - con.setAutoCommit(true); + statWlock.unlock(); + effLock.unlock(); } - } catch (Exception e) { - log.error("Error saving chr {}, level: {}, job: {}", name, level, job.getId(), e); + + 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 + ")"); + } + } + + List petList = new LinkedList<>(); + petLock.lock(); + try { + for (int i = 0; i < 3; i++) { + if (pets[i] != null) { + petList.add(pets[i]); + } + } + } finally { + petLock.unlock(); + } + + for (Pet pet : petList) { + pet.saveToDb(); + } + + for (Entry> es : getExcluded().entrySet()) { // this set is already protected + try (PreparedStatement psIgnore = con.prepareStatement("DELETE FROM petignores WHERE petid=?")) { + psIgnore.setInt(1, es.getKey()); + psIgnore.executeUpdate(); + } + + try (PreparedStatement psIgnore = con.prepareStatement("INSERT INTO petignores (petid, itemid) VALUES (?, ?)")) { + psIgnore.setInt(1, es.getKey()); + for (Integer x : es.getValue()) { + psIgnore.setInt(2, x); + psIgnore.addBatch(); + } + psIgnore.executeBatch(); + } + } + + // Key config + deleteWhereCharacterId(con, "DELETE FROM keymap WHERE characterid = ?"); + try (PreparedStatement psKey = con.prepareStatement("INSERT INTO keymap (characterid, `key`, `type`, `action`) VALUES (?, ?, ?, ?)")) { + psKey.setInt(1, id); + + Set> keybindingItems = Collections.unmodifiableSet(keymap.entrySet()); + for (Entry keybinding : keybindingItems) { + psKey.setInt(2, keybinding.getKey()); + psKey.setInt(3, keybinding.getValue().getType()); + psKey.setInt(4, keybinding.getValue().getAction()); + psKey.addBatch(); + } + psKey.executeBatch(); + } + + // No quickslots, or no change. + boolean bQuickslotEquals = this.m_pQuickslotKeyMapped == null || (this.m_aQuickslotLoaded != null && Arrays.equals(this.m_pQuickslotKeyMapped.GetKeybindings(), this.m_aQuickslotLoaded)); + if (!bQuickslotEquals) { + long nQuickslotKeymapped = LongTool.BytesToLong(this.m_pQuickslotKeyMapped.GetKeybindings()); + + try (final PreparedStatement psQuick = con.prepareStatement("INSERT INTO quickslotkeymapped (accountid, keymap) VALUES (?, ?) ON DUPLICATE KEY UPDATE keymap = ?;")) { + psQuick.setInt(1, this.getAccountID()); + psQuick.setLong(2, nQuickslotKeymapped); + psQuick.setLong(3, nQuickslotKeymapped); + psQuick.executeUpdate(); + } + } + + // Skill macros + deleteWhereCharacterId(con, "DELETE FROM skillmacros WHERE characterid = ?"); + try (PreparedStatement psMacro = con.prepareStatement("INSERT INTO skillmacros (characterid, skill1, skill2, skill3, name, shout, position) VALUES (?, ?, ?, ?, ?, ?, ?)")) { + psMacro.setInt(1, getId()); + for (int i = 0; i < 5; i++) { + SkillMacro macro = skillMacros[i]; + if (macro != null) { + psMacro.setInt(2, macro.getSkill1()); + psMacro.setInt(3, macro.getSkill2()); + psMacro.setInt(4, macro.getSkill3()); + psMacro.setString(5, macro.getName()); + psMacro.setInt(6, macro.getShout()); + psMacro.setInt(7, i); + psMacro.addBatch(); + } + } + psMacro.executeBatch(); + } + + List> itemsWithType = new ArrayList<>(); + for (Inventory iv : inventory) { + for (Item item : iv.list()) { + itemsWithType.add(new Pair<>(item, iv.getType())); + } + } + + // Items + ItemFactory.INVENTORY.saveItems(itemsWithType, id, con); + + // Skills + try (PreparedStatement psSkill = con.prepareStatement("REPLACE INTO skills (characterid, skillid, skilllevel, masterlevel, expiration) VALUES (?, ?, ?, ?, ?)")) { + psSkill.setInt(1, id); + for (Entry skill : skills.entrySet()) { + psSkill.setInt(2, skill.getKey().getId()); + psSkill.setInt(3, skill.getValue().skillevel); + psSkill.setInt(4, skill.getValue().masterlevel); + psSkill.setLong(5, skill.getValue().expiration); + psSkill.addBatch(); + } + psSkill.executeBatch(); + } + + // Saved locations + deleteWhereCharacterId(con, "DELETE FROM savedlocations WHERE characterid = ?"); + try (PreparedStatement psLoc = con.prepareStatement("INSERT INTO savedlocations (characterid, `locationtype`, `map`, `portal`) VALUES (?, ?, ?, ?)")) { + psLoc.setInt(1, id); + for (SavedLocationType savedLocationType : SavedLocationType.values()) { + if (savedLocations[savedLocationType.ordinal()] != null) { + psLoc.setString(2, savedLocationType.name()); + psLoc.setInt(3, savedLocations[savedLocationType.ordinal()].getMapId()); + psLoc.setInt(4, savedLocations[savedLocationType.ordinal()].getPortal()); + psLoc.addBatch(); + } + } + psLoc.executeBatch(); + } + + deleteWhereCharacterId(con, "DELETE FROM trocklocations WHERE characterid = ?"); + + // Vip teleport rocks + try (PreparedStatement psVip = con.prepareStatement("INSERT INTO trocklocations(characterid, mapid, vip) VALUES (?, ?, 0)")) { + for (int i = 0; i < getTrockSize(); i++) { + if (trockmaps.get(i) != MapId.NONE) { + psVip.setInt(1, getId()); + psVip.setInt(2, trockmaps.get(i)); + psVip.addBatch(); + } + } + psVip.executeBatch(); + } + + // Regular teleport rocks + try (PreparedStatement psReg = con.prepareStatement("INSERT INTO trocklocations(characterid, mapid, vip) VALUES (?, ?, 1)")) { + for (int i = 0; i < getVipTrockSize(); i++) { + if (viptrockmaps.get(i) != MapId.NONE) { + psReg.setInt(1, getId()); + psReg.setInt(2, viptrockmaps.get(i)); + psReg.addBatch(); + } + } + psReg.executeBatch(); + } + + // Buddy + deleteWhereCharacterId(con, "DELETE FROM buddies WHERE characterid = ? AND pending = 0"); + try (PreparedStatement psBuddy = con.prepareStatement("INSERT INTO buddies (characterid, `buddyid`, `pending`, `group`) VALUES (?, ?, 0, ?)")) { + psBuddy.setInt(1, id); + + for (BuddylistEntry entry : buddylist.getBuddies()) { + if (entry.isVisible()) { + psBuddy.setInt(2, entry.getCharacterId()); + psBuddy.setString(3, entry.getGroup()); + psBuddy.addBatch(); + } + } + psBuddy.executeBatch(); + } + + // Area info + deleteWhereCharacterId(con, "DELETE FROM area_info WHERE charid = ?"); + try (PreparedStatement psArea = con.prepareStatement("INSERT INTO area_info (id, charid, area, info) VALUES (DEFAULT, ?, ?, ?)")) { + psArea.setInt(1, id); + + for (Entry area : area_info.entrySet()) { + psArea.setInt(2, area.getKey()); + psArea.setString(3, area.getValue()); + psArea.addBatch(); + } + psArea.executeBatch(); + } + + // Event stats + deleteWhereCharacterId(con, "DELETE FROM eventstats WHERE characterid = ?"); + try (PreparedStatement psEvent = con.prepareStatement("INSERT INTO eventstats (characterid, name, info) VALUES (?, ?, ?)")) { + psEvent.setInt(1, id); + + for (Map.Entry entry : events.entrySet()) { + psEvent.setString(2, entry.getKey()); + psEvent.setInt(3, entry.getValue().getInfo()); + psEvent.addBatch(); + } + + psEvent.executeBatch(); + } + + deleteQuestProgressWhereCharacterId(con, id); + + // Quests and medals + try (PreparedStatement psStatus = con.prepareStatement("INSERT INTO queststatus (`queststatusid`, `characterid`, `quest`, `status`, `time`, `expires`, `forfeited`, `completed`) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + PreparedStatement psProgress = con.prepareStatement("INSERT INTO questprogress VALUES (DEFAULT, ?, ?, ?, ?)"); + PreparedStatement psMedal = con.prepareStatement("INSERT INTO medalmaps VALUES (DEFAULT, ?, ?, ?)")) { + psStatus.setInt(1, id); + + for (QuestStatus qs : getQuests()) { + psStatus.setInt(2, qs.getQuest().getId()); + psStatus.setInt(3, qs.getStatus().getId()); + psStatus.setInt(4, (int) (qs.getCompletionTime() / 1000)); + psStatus.setLong(5, qs.getExpirationTime()); + psStatus.setInt(6, qs.getForfeited()); + psStatus.setInt(7, qs.getCompleted()); + psStatus.executeUpdate(); + + try (ResultSet rs = psStatus.getGeneratedKeys()) { + rs.next(); + for (int mob : qs.getProgress().keySet()) { + psProgress.setInt(1, id); + psProgress.setInt(2, rs.getInt(1)); + psProgress.setInt(3, mob); + psProgress.setString(4, qs.getProgress(mob)); + psProgress.addBatch(); + } + psProgress.executeBatch(); + + for (int i = 0; i < qs.getMedalMaps().size(); i++) { + psMedal.setInt(1, id); + psMedal.setInt(2, rs.getInt(1)); + psMedal.setInt(3, qs.getMedalMaps().get(i)); + psMedal.addBatch(); + } + psMedal.executeBatch(); + } + } + } + + FamilyEntry familyEntry = getFamilyEntry(); //save family rep + if (familyEntry != null) { + if (familyEntry.saveReputation(con)) { + familyEntry.savedSuccessfully(); + } + FamilyEntry senior = familyEntry.getSenior(); + if (senior != null && senior.getChr() == null) { //only save for offline family members + if (senior.saveReputation(con)) { + senior.savedSuccessfully(); + } + senior = senior.getSenior(); //save one level up as well + if (senior != null && senior.getChr() == null) { + if (senior.saveReputation(con)) { + senior.savedSuccessfully(); + } + } + } + } + + saveCooldowns(con); + saveDiseases(con); + + if (cashshop != null) { + cashshop.save(con); + } + + if (storage != null && usedStorage) { + storage.saveToDB(con); + usedStorage = false; } } diff --git a/src/main/java/database/character/CharacterSaver.java b/src/main/java/database/character/CharacterSaver.java index c6a2da115f..841bea8da7 100644 --- a/src/main/java/database/character/CharacterSaver.java +++ b/src/main/java/database/character/CharacterSaver.java @@ -2,8 +2,15 @@ package database.character; import client.Character; import database.monsterbook.MonsterCardDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.DatabaseConnection; + +import java.sql.Connection; +import java.sql.SQLException; public class CharacterSaver { + private static final Logger log = LoggerFactory.getLogger(CharacterSaver.class); private final MonsterCardDao monsterCardDao; public CharacterSaver(MonsterCardDao monsterCardDao) { @@ -11,7 +18,28 @@ public class CharacterSaver { } public void save(Character chr) { - chr.saveCharToDB(); + if (!chr.isLoggedin()) { + return; + } + + log.debug("Saving chr {}", chr.getName()); + try (Connection con = DatabaseConnection.getConnection()) { + con.setAutoCommit(false); + con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); + try { + chr.saveCharToDB(con); + con.commit(); + } catch (Exception e) { + con.rollback(); + throw e; + } finally { + con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); + con.setAutoCommit(true); + } + } catch (SQLException e) { + log.error("Error saving chr {}, level: {}, job: {}", chr.getName(), chr.getLevel(), chr.getJob().getId(), e); + return; + } // Saving monster cards to both MySQL and Postgres for now monsterCardDao.save(chr.getId(), chr.getMonsterBook().getCards());