Rewrite MonsterBook, touch up chr loading

Temporarily disabled loading monster cards from db
This commit is contained in:
P0nk
2023-07-25 21:27:10 +02:00
parent b3ec325e95
commit f6aa8ceba6
7 changed files with 165 additions and 259 deletions

View File

@@ -1901,8 +1901,8 @@ public class Character extends AbstractCharacterObject {
ii.getItemEffect(itemId).applyTo(this);
}
if (itemId / 10000 == 238) {
this.getMonsterBook().addCard(client, itemId);
if (ItemId.isMonsterCard(itemId)) {
monsterbook.addCard(itemId, client);
}
return true;
}
@@ -6743,7 +6743,7 @@ public class Character extends AbstractCharacterObject {
}
}
public static Character loadCharacterEntryFromDB(ResultSet rs, List<Item> equipped) {
public static Character loadCharacterViewFromDB(ResultSet rs, List<Item> equipped) {
Character ret = new Character();
try {
@@ -6794,7 +6794,7 @@ public class Character extends AbstractCharacterObject {
return ret;
}
public Character generateCharacterEntry() {
public Character createCharacterView() {
Character ret = new Character();
ret.accountid = this.getAccountID();
@@ -6854,10 +6854,16 @@ public class Character extends AbstractCharacterObject {
updateRemainingSp(remainingSp, GameConstants.getSkillBook(job.getId()));
}
public static Character loadCharFromDB(final int charid, Client client, boolean channelserver) throws SQLException {
@Deprecated
public static Character loadCharFromDB(int chrId, Client client, boolean channelServer) throws SQLException {
return loadCharFromDB(chrId, client, channelServer, new MonsterBook(Collections.emptyList()));
}
public static Character loadCharFromDB(final int chrId, Client client, boolean channelServer,
MonsterBook monsterBook) throws SQLException {
Character ret = new Character();
ret.client = client;
ret.id = charid;
ret.id = chrId;
try (Connection con = DatabaseConnection.getConnection()) {
final int mountexp;
@@ -6867,7 +6873,7 @@ public class Character extends AbstractCharacterObject {
// Character info
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM characters WHERE id = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) {
@@ -6925,8 +6931,7 @@ public class Character extends AbstractCharacterObject {
ret.allianceRank = rs.getInt("allianceRank");
ret.familyId = rs.getInt("familyId");
ret.bookCover = rs.getInt("monsterbookcover");
ret.monsterbook = new MonsterBook();
ret.monsterbook.loadCards(charid);
ret.monsterbook = monsterBook;
ret.vanquisherStage = rs.getInt("vanquisherStage");
ret.ariantPoints = rs.getInt("ariantPoints");
ret.dojoPoints = rs.getInt("dojoPoints");
@@ -6946,7 +6951,7 @@ public class Character extends AbstractCharacterObject {
ret.getInventory(InventoryType.ETC).setSlotLimit(rs.getByte("etcslots"));
short sandboxCheck = 0x0;
for (Pair<Item, InventoryType> item : ItemFactory.INVENTORY.loadItems(ret.id, !channelserver)) {
for (Pair<Item, InventoryType> item : ItemFactory.INVENTORY.loadItems(ret.id, !channelServer)) {
sandboxCheck |= item.getLeft().getFlag();
ret.getInventory(item.getRight()).addItemFromDB(item.getLeft());
@@ -6993,7 +6998,7 @@ public class Character extends AbstractCharacterObject {
// Items excluded from pet loot
try (PreparedStatement psPet = con.prepareStatement("SELECT petid FROM inventoryitems WHERE characterid = ? AND petid > -1")) {
psPet.setInt(1, charid);
psPet.setInt(1, chrId);
try (ResultSet rsPet = psPet.executeQuery()) {
while (rsPet.next()) {
@@ -7016,7 +7021,7 @@ public class Character extends AbstractCharacterObject {
ret.commitExcludedItems();
if (channelserver) {
if (channelServer) {
MapManager mapManager = client.getChannelServer().getMapFactory();
ret.map = mapManager.getMap(ret.mapid);
@@ -7054,7 +7059,7 @@ public class Character extends AbstractCharacterObject {
// Teleport rocks
try (PreparedStatement ps = con.prepareStatement("SELECT mapid,vip FROM trocklocations WHERE characterid = ? LIMIT 15")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
byte vip = 0;
@@ -7125,7 +7130,7 @@ public class Character extends AbstractCharacterObject {
// Blessing of the Fairy
try (PreparedStatement ps = con.prepareStatement("SELECT name, level FROM characters WHERE accountid = ? AND id != ? ORDER BY level DESC limit 1")) {
ps.setInt(1, ret.accountid);
ps.setInt(2, charid);
ps.setInt(2, chrId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -7135,12 +7140,12 @@ public class Character extends AbstractCharacterObject {
}
}
if (channelserver) {
if (channelServer) {
final Map<Integer, QuestStatus> loadedQuestStatus = new LinkedHashMap<>();
// Quest status
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM queststatus WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -7167,7 +7172,7 @@ public class Character extends AbstractCharacterObject {
// Quest progress
// opportunity for improvement on questprogress/medalmaps calls to DB
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM questprogress WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rsProgress = ps.executeQuery()) {
while (rsProgress.next()) {
QuestStatus status = loadedQuestStatus.get(rsProgress.getInt("queststatusid"));
@@ -7180,7 +7185,7 @@ public class Character extends AbstractCharacterObject {
// Medal map visit progress
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM medalmaps WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rsMedalMaps = ps.executeQuery()) {
while (rsMedalMaps.next()) {
QuestStatus status = loadedQuestStatus.get(rsMedalMaps.getInt("queststatusid"));
@@ -7195,7 +7200,7 @@ public class Character extends AbstractCharacterObject {
// Skills
try (PreparedStatement ps = con.prepareStatement("SELECT skillid,skilllevel,masterlevel,expiration FROM skills WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -7265,7 +7270,7 @@ public class Character extends AbstractCharacterObject {
// Skill macros
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM skillmacros WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -7278,7 +7283,7 @@ public class Character extends AbstractCharacterObject {
// Key config
try (PreparedStatement ps = con.prepareStatement("SELECT `key`,`type`,`action` FROM keymap WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -7292,7 +7297,7 @@ public class Character extends AbstractCharacterObject {
// Saved locations
try (PreparedStatement ps = con.prepareStatement("SELECT `locationtype`,`map`,`portal` FROM savedlocations WHERE characterid = ?")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -7303,7 +7308,7 @@ public class Character extends AbstractCharacterObject {
// Fame history
try (PreparedStatement ps = con.prepareStatement("SELECT `characterid_to`,`when` FROM famelog WHERE characterid = ? AND DATEDIFF(NOW(),`when`) < 30")) {
ps.setInt(1, charid);
ps.setInt(1, chrId);
try (ResultSet rs = ps.executeQuery()) {
ret.lastfametime = 0;
@@ -7315,7 +7320,7 @@ public class Character extends AbstractCharacterObject {
}
}
ret.buddylist.loadFromDb(charid);
ret.buddylist.loadFromDb(chrId);
ret.storage = wserv.getAccountStorage(ret.accountid);
/* Double-check storage incase player is first time on server

View File

@@ -1,190 +1,106 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package client;
import tools.DatabaseConnection;
import database.monsterbook.MonsterCard;
import net.jcip.annotations.ThreadSafe;
import tools.PacketCreator;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class MonsterBook {
private int specialCard = 0;
private int normalCard = 0;
private int bookLevel = 1;
private final Map<Integer, Integer> cards = new LinkedHashMap<>();
private final Lock lock = new ReentrantLock();
// TODO: add tests
@ThreadSafe
public class MonsterBook {
private final Map<Integer, MonsterCard> cards;
private int bookLevel;
public void addCard(final Client c, final int cardid) {
c.getPlayer().getMap().broadcastMessage(c.getPlayer(), PacketCreator.showForeignCardEffect(c.getPlayer().getId()), false);
public MonsterBook(List<MonsterCard> monsterCards) {
this.cards = monsterCards.stream()
.collect(Collectors.toMap(MonsterCard::cardId, Function.identity()));
}
Integer qty;
lock.lock();
try {
qty = cards.get(cardid);
public synchronized List<MonsterCard> getCards() {
return new ArrayList<>(cards.values());
}
if (qty != null) {
if (qty < 5) {
cards.put(cardid, qty + 1);
}
} else {
cards.put(cardid, 1);
qty = 0;
if (cardid / 1000 >= 2388) {
specialCard++;
} else {
normalCard++;
}
}
} finally {
lock.unlock();
public synchronized void addCard(int cardId, Client client) {
var monsterCard = cards.get(cardId);
if (monsterCard != null && monsterCard.isMaxLevel()) {
client.sendPacket(PacketCreator.addMonsterCardAlreadyFull());
return;
}
if (qty < 5) {
if (qty == 0) { // leveling system only accounts unique cards
calculateLevel();
}
c.sendPacket(PacketCreator.addCard(false, cardid, qty + 1));
c.sendPacket(PacketCreator.showGainCard());
boolean isNewCard = monsterCard == null;
final MonsterCard card;
if (isNewCard) {
card = new MonsterCard(cardId, (byte) 1);
cards.put(cardId, card);
calculateAndSetLevel();
} else {
c.sendPacket(PacketCreator.addCard(true, cardid, 5));
}
}
private void calculateLevel() {
lock.lock();
try {
int collectionExp = (normalCard + specialCard);
int level = 0, expToNextlevel = 1;
do {
level++;
expToNextlevel += level * 10;
} while (collectionExp >= expToNextlevel);
bookLevel = level; // thanks IxianMace for noticing book level differing between book UI and character info UI
} finally {
lock.unlock();
}
}
public int getBookLevel() {
lock.lock();
try {
return bookLevel;
} finally {
lock.unlock();
}
}
public Map<Integer, Integer> getCards() {
lock.lock();
try {
return Collections.unmodifiableMap(cards);
} finally {
lock.unlock();
}
}
public int getTotalCards() {
lock.lock();
try {
return specialCard + normalCard;
} finally {
lock.unlock();
}
}
public int getNormalCard() {
lock.lock();
try {
return normalCard;
} finally {
lock.unlock();
}
}
public int getSpecialCard() {
lock.lock();
try {
return specialCard;
} finally {
lock.unlock();
}
}
public void loadCards(final int charid) throws SQLException {
lock.lock();
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT cardid, level FROM monsterbook WHERE charid = ? ORDER BY cardid ASC")) {
ps.setInt(1, charid);
try (ResultSet rs = ps.executeQuery()) {
int cardid;
int level;
while (rs.next()) {
cardid = rs.getInt("cardid");
level = rs.getInt("level");
if (cardid / 1000 >= 2388) {
specialCard++;
} else {
normalCard++;
}
cards.put(cardid, level);
}
}
} finally {
lock.unlock();
card = new MonsterCard(cardId, (byte) (monsterCard.level() + 1));
cards.put(cardId, card);
}
calculateLevel();
var chr = client.getPlayer();
chr.sendPacket(PacketCreator.addMonsterCard(card));
chr.sendPacket(PacketCreator.showMonsterCardEffect());
chr.getMap().broadcastMessage(chr, PacketCreator.showForeignMonsterCardEffect(chr.getId()), false);
}
public void saveCards(Connection con, int chrId) throws SQLException {
private synchronized void calculateAndSetLevel() {
int collectionExp = getTotalCards();
int level = 0;
int expToNextLevel = 1;
do {
level++;
expToNextLevel += level * 10;
} while (collectionExp >= expToNextLevel);
this.bookLevel = level;
}
public synchronized int getBookLevel() {
return bookLevel;
}
public synchronized int getNormalCards() {
return (int) cards.values().stream()
.filter(Predicate.not(MonsterCard::isSpecial))
.count();
}
public synchronized int getSpecialCards() {
return (int) cards.values().stream()
.filter(MonsterCard::isSpecial)
.count();
}
public synchronized int getTotalCards() {
return cards.size();
}
public synchronized void saveCards(Connection con, int chrId) throws SQLException {
final String query = """
INSERT INTO monsterbook (charid, cardid, level)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE level = ?;
""";
try (final PreparedStatement ps = con.prepareStatement(query)) {
for (Map.Entry<Integer, Integer> cardAndLevel : cards.entrySet()) {
final int card = cardAndLevel.getKey();
final int level = cardAndLevel.getValue();
for (MonsterCard card : cards.values()) {
// insert
ps.setInt(1, chrId);
ps.setInt(2, card);
ps.setInt(3, level);
ps.setInt(2, card.cardId());
ps.setInt(3, card.level());
// update
ps.setInt(4, level);
ps.setInt(4, card.level());
ps.addBatch();
}