Autosave feature + Pet Ignore fix

Added experimental autosaver feature. Fixed pet ignore feature not
saving/loading data in some cases. Added concurrency protection for
inventory classes and monster book.
This commit is contained in:
ronancpl
2017-09-11 16:53:40 -03:00
parent e064d5cbfa
commit f387d589b2
55 changed files with 568 additions and 248 deletions

View File

@@ -4529,26 +4529,31 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
}
PreparedStatement ps2;
ResultSet rs2;
for(byte i = 0; i < 3; i++) {
MaplePet pet = ret.getPet(i);
if(pet == null) continue;
int petId = pet.getUniqueId();
ps2 = con.prepareStatement("SELECT itemid FROM petignores WHERE petid = ?"); // Get pet details..
PreparedStatement ps2, ps3;
ResultSet rs2, rs3;
ps3 = con.prepareStatement("SELECT petid FROM inventoryitems WHERE characterid = ? AND petid > -1");
ps3.setInt(1, charid);
rs3 = ps3.executeQuery();
while(rs3.next()) {
int petId = rs3.getInt("petid");
ps2 = con.prepareStatement("SELECT itemid FROM petignores WHERE petid = ?");
ps2.setInt(1, petId);
ret.resetExcluded(petId);
rs2 = ps2.executeQuery();
while(rs2.next()) {
ret.addExcluded(petId, rs2.getInt("itemid"));
}
ps2.close();
rs2.close();
}
ps3.close();
rs3.close();
ret.commitExcludedItems();
if (channelserver) {
@@ -5525,10 +5530,28 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
}
// synchronize this call instead of trying to give access all at once (?)
public synchronized void saveToDB() {
public void saveToDB() {
if(ServerConstants.USE_AUTOSAVE) {
Runnable r = new Runnable() {
@Override
public void run() {
saveToDB(true);
}
};
Thread t = new Thread(r); //spawns a new thread to deal with this
t.start();
} else {
saveToDB(true);
}
}
public synchronized void saveToDB(boolean notAutosave) {
Calendar c = Calendar.getInstance();
FilePrinter.print(FilePrinter.SAVING_CHARACTER, "Attempting to save " + name + " at " + c.getTime().toString());
if(notAutosave) FilePrinter.print(FilePrinter.SAVING_CHARACTER, "Attempting to save " + name + " at " + c.getTime().toString());
else FilePrinter.print(FilePrinter.AUTOSAVING_CHARACTER, "Attempting to autosave " + name + " at " + c.getTime().toString());
Connection con = null;
try {
con = DatabaseConnection.getConnection();
@@ -5577,7 +5600,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
ps.setInt(22, meso.get());
ps.setInt(23, hpMpApUsed);
if (map == null || map.getId() == 610020000 || map.getId() == 610020001) {
if (map == null || map.getId() == 610020000 || map.getId() == 610020001) { // reset to first spawnpoint on those maps
ps.setInt(24, 0);
} else {
MaplePortal closest = map.findClosestPlayerSpawnpoint(getPosition());
@@ -5648,7 +5671,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
petLock.unlock();
}
for(Entry<Integer, Set<Integer>> es: getExcluded().entrySet()) {
for(Entry<Integer, Set<Integer>> es: getExcluded().entrySet()) { // this set is already protected
try (PreparedStatement ps2 = con.prepareStatement("DELETE FROM petignores WHERE petid=?")) {
ps2.setInt(1, es.getKey());
ps2.executeUpdate();
@@ -5667,8 +5690,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
deleteWhereCharacterId(con, "DELETE FROM keymap WHERE characterid = ?");
ps = con.prepareStatement("INSERT INTO keymap (characterid, `key`, `type`, `action`) VALUES (?, ?, ?, ?)");
ps.setInt(1, id);
for (Entry<Integer, MapleKeyBinding> keybinding : keymap.entrySet()) {
ps.setInt(2, keybinding.getKey().intValue());
Set<Entry<Integer, MapleKeyBinding>> keybindingItems = Collections.unmodifiableSet(keymap.entrySet());
for (Entry<Integer, MapleKeyBinding> keybinding : keybindingItems) {
ps.setInt(2, keybinding.getKey());
ps.setInt(3, keybinding.getValue().getType());
ps.setInt(4, keybinding.getValue().getAction());
ps.addBatch();
@@ -5691,14 +5716,13 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
}
ps.executeBatch();
List<Pair<Item, MapleInventoryType>> itemsWithType = new ArrayList<>();
for (MapleInventory iv : inventory) {
for (Item item : iv.list()) {
itemsWithType.add(new Pair<>(item, iv.getType()));
}
}
ItemFactory.INVENTORY.saveItems(itemsWithType, id, con);
deleteWhereCharacterId(con, "DELETE FROM skills WHERE characterid = ?");
@@ -6283,7 +6307,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
if (slots <= 96) {
inventory[type].setSlotLimit(slots);
saveToDB();
this.saveToDB();
if (update) {
client.announce(MaplePacketCreator.updateInventorySlotLimit(type, slots));
}
@@ -6552,6 +6576,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
getMap().broadcastMessage(this, MaplePacketCreator.showPet(this, pet, true, hunger), true);
removePet(pet, shift_left);
commitExcludedItems();
client.announce(MaplePacketCreator.petStatUpdate(this));
client.announce(MaplePacketCreator.enableActions());
}

View File

@@ -25,9 +25,13 @@ 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.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import tools.DatabaseConnection;
import tools.MaplePacketCreator;
@@ -36,85 +40,140 @@ public final class MonsterBook {
private int normalCard = 0;
private int bookLevel = 1;
private Map<Integer, Integer> cards = new LinkedHashMap<>();
private Lock lock = new ReentrantLock();
private Set<Entry<Integer, Integer>> getCardSet() {
lock.lock();
try {
return Collections.unmodifiableSet(cards.entrySet());
} finally {
lock.unlock();
}
}
public void addCard(final MapleClient c, final int cardid) {
c.getPlayer().getMap().broadcastMessage(c.getPlayer(), MaplePacketCreator.showForeignCardEffect(c.getPlayer().getId()), false);
for (Entry<Integer, Integer> all : cards.entrySet()) {
if (all.getKey() == cardid) {
if (all.getValue() > 4) {
c.announce(MaplePacketCreator.addCard(true, cardid, all.getValue()));
} else {
all.setValue(all.getValue() + 1);
c.announce(MaplePacketCreator.addCard(false, cardid, all.getValue()));
c.announce(MaplePacketCreator.showGainCard());
calculateLevel();
Integer qty;
lock.lock();
try {
qty = cards.get(cardid);
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++;
}
return;
}
} finally {
lock.unlock();
}
cards.put(cardid, 1);
c.announce(MaplePacketCreator.addCard(false, cardid, 1));
c.announce(MaplePacketCreator.showGainCard());
calculateLevel();
if (cardid / 1000 >= 2388) {
specialCard++;
if(qty < 5) {
calculateLevel(); // current leveling system only accounts unique cards...
c.announce(MaplePacketCreator.addCard(false, cardid, qty + 1));
c.announce(MaplePacketCreator.showGainCard());
} else {
normalCard++;
c.announce(MaplePacketCreator.addCard(true, cardid, 5));
}
//c.getPlayer().saveToDB(); //is it REALLY needed to save to DB every new entry?
}
private void calculateLevel() {
bookLevel = (int) Math.max(1, Math.sqrt((normalCard + specialCard) / 5));
lock.lock();
try {
bookLevel = (int) Math.max(1, Math.sqrt((normalCard + specialCard) / 5));
} finally {
lock.unlock();
}
}
public int getBookLevel() {
return bookLevel;
lock.lock();
try {
return bookLevel;
} finally {
lock.unlock();
}
}
public Map<Integer, Integer> getCards() {
return cards;
lock.lock();
try {
return Collections.unmodifiableMap(cards);
} finally {
lock.unlock();
}
}
public int getTotalCards() {
return specialCard + normalCard;
lock.lock();
try {
return specialCard + normalCard;
} finally {
lock.unlock();
}
}
public int getNormalCard() {
return normalCard;
lock.lock();
try {
return normalCard;
} finally {
lock.unlock();
}
}
public int getSpecialCard() {
return specialCard;
lock.lock();
try {
return specialCard;
} finally {
lock.unlock();
}
}
public void loadCards(final int charid) throws SQLException {
Connection con = DatabaseConnection.getConnection();
try (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, level;
while (rs.next()) {
cardid = rs.getInt("cardid");
level = rs.getInt("level");
if (cardid / 1000 >= 2388) {
specialCard++;
} else {
normalCard++;
lock.lock();
try {
Connection con = DatabaseConnection.getConnection();
try (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, level;
while (rs.next()) {
cardid = rs.getInt("cardid");
level = rs.getInt("level");
if (cardid / 1000 >= 2388) {
specialCard++;
} else {
normalCard++;
}
cards.put(cardid, level);
}
cards.put(cardid, level);
}
}
con.close();
} finally {
lock.unlock();
}
con.close();
calculateLevel();
}
public void saveCards(final int charid) {
if (cards.isEmpty()) {
Set<Entry<Integer, Integer>> cardSet = getCardSet();
if (cardSet.isEmpty()) {
return;
}
try {
@@ -125,7 +184,7 @@ public final class MonsterBook {
ps.close();
boolean first = true;
StringBuilder query = new StringBuilder();
for (Entry<Integer, Integer> all : cards.entrySet()) {
for (Entry<Integer, Integer> all : cardSet) {
if (first) {
query.append("INSERT INTO monsterbook VALUES (");
first = false;

View File

@@ -141,7 +141,6 @@ public enum ItemFactory {
ResultSet rs = null;
lock.lock();
try {
StringBuilder query = new StringBuilder();
query.append("DELETE `inventoryitems`, `inventoryequipment` FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");

View File

@@ -29,6 +29,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import tools.Pair;
import client.MapleCharacter;
@@ -36,6 +38,7 @@ import client.MapleClient;
import constants.ItemConstants;
import server.MapleItemInformationProvider;
import server.MapleInventoryManipulator;
import tools.FilePrinter;
/**
*
@@ -47,6 +50,7 @@ public class MapleInventory implements Iterable<Item> {
private byte slotLimit;
private MapleInventoryType type;
private boolean checked = false;
private Lock lock = new ReentrantLock();
public MapleInventory(MapleCharacter mc, MapleInventoryType type, byte slotLimit) {
this.owner = mc;
@@ -64,15 +68,34 @@ public class MapleInventory implements Iterable<Item> {
}
public byte getSlotLimit() {
return slotLimit;
lock.lock();
try {
return slotLimit;
} finally {
lock.unlock();
}
}
public void setSlotLimit(int newLimit) {
slotLimit = (byte) newLimit;
lock.lock();
try {
slotLimit = (byte) newLimit;
} finally {
lock.unlock();
}
}
public Collection<Item> list() {
lock.lock();
try {
return Collections.unmodifiableCollection(inventory.values());
} finally {
lock.unlock();
}
}
public Item findById(int itemId) {
for (Item item : inventory.values()) {
for (Item item : list()) {
if (item.getItemId() == itemId) {
return item;
}
@@ -81,10 +104,10 @@ public class MapleInventory implements Iterable<Item> {
}
public Item findByName(String name) {
for (Item item : inventory.values()) {
for (Item item : list()) {
String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId());
if(itemName == null) {
System.out.println("[CRITICAL] Item " + item.getItemId() + " has no name.");
FilePrinter.printError(FilePrinter.EXCEPTION, "[CRITICAL] Item " + item.getItemId() + " has no name.");
continue;
}
@@ -97,7 +120,7 @@ public class MapleInventory implements Iterable<Item> {
public int countById(int itemId) {
int qty = 0;
for (Item item : inventory.values()) {
for (Item item : list()) {
if (item.getItemId() == itemId) {
qty += item.getQuantity();
}
@@ -138,7 +161,7 @@ public class MapleInventory implements Iterable<Item> {
public List<Item> listById(int itemId) {
List<Item> ret = new ArrayList<>();
for (Item item : inventory.values()) {
for (Item item : list()) {
if (item.getItemId() == itemId) {
ret.add(item);
}
@@ -149,10 +172,6 @@ public class MapleInventory implements Iterable<Item> {
return ret;
}
public Collection<Item> list() {
return inventory.values();
}
public short addItem(Item item) {
short slotId = getNextFreeSlot();
if (slotId < 0 || item == null) {
@@ -171,29 +190,34 @@ public class MapleInventory implements Iterable<Item> {
}
public void move(short sSlot, short dSlot, short slotMax) {
Item source = (Item) inventory.get(sSlot);
Item target = (Item) inventory.get(dSlot);
if (source == null) {
return;
}
if (target == null) {
source.setPosition(dSlot);
inventory.put(dSlot, source);
inventory.remove(sSlot);
} else if (target.getItemId() == source.getItemId() && !ItemConstants.isRechargable(source.getItemId())) {
if (type.getType() == MapleInventoryType.EQUIP.getType()) {
lock.lock();
try {
Item source = (Item) inventory.get(sSlot);
Item target = (Item) inventory.get(dSlot);
if (source == null) {
return;
}
if (target == null) {
source.setPosition(dSlot);
inventory.put(dSlot, source);
inventory.remove(sSlot);
} else if (target.getItemId() == source.getItemId() && !ItemConstants.isRechargable(source.getItemId())) {
if (type.getType() == MapleInventoryType.EQUIP.getType()) {
swap(target, source);
}
if (source.getQuantity() + target.getQuantity() > slotMax) {
short rest = (short) ((source.getQuantity() + target.getQuantity()) - slotMax);
source.setQuantity(rest);
target.setQuantity(slotMax);
} else {
target.setQuantity((short) (source.getQuantity() + target.getQuantity()));
inventory.remove(sSlot);
}
} else {
swap(target, source);
}
if (source.getQuantity() + target.getQuantity() > slotMax) {
short rest = (short) ((source.getQuantity() + target.getQuantity()) - slotMax);
source.setQuantity(rest);
target.setQuantity(slotMax);
} else {
target.setQuantity((short) (source.getQuantity() + target.getQuantity()));
inventory.remove(sSlot);
}
} else {
swap(target, source);
} finally {
lock.unlock();
}
}
@@ -208,7 +232,12 @@ public class MapleInventory implements Iterable<Item> {
}
public Item getItem(short slot) {
return inventory.get(slot);
lock.lock();
try {
return inventory.get(slot);
} finally {
lock.unlock();
}
}
public void removeItem(short slot) {
@@ -216,7 +245,7 @@ public class MapleInventory implements Iterable<Item> {
}
public void removeItem(short slot, short quantity, boolean allowZero) {
Item item = inventory.get(slot);
Item item = getItem(slot);
if (item == null) {// TODO is it ok not to throw an exception here?
return;
}
@@ -230,7 +259,12 @@ public class MapleInventory implements Iterable<Item> {
}
public void addSlot(short slot, Item item) {
inventory.put(slot, item);
lock.lock();
try {
inventory.put(slot, item);
} finally {
lock.unlock();
}
if(ItemConstants.isRateCoupon(item.getItemId())) {
owner.updateCouponRates();
@@ -238,7 +272,13 @@ public class MapleInventory implements Iterable<Item> {
}
public void removeSlot(short slot) {
Item item = inventory.remove(slot);
Item item;
lock.lock();
try {
item = inventory.remove(slot);
} finally {
lock.unlock();
}
if(item != null && ItemConstants.isRateCoupon(item.getItemId())) {
owner.updateCouponRates();
@@ -246,40 +286,67 @@ public class MapleInventory implements Iterable<Item> {
}
public boolean isFull() {
return inventory.size() >= slotLimit;
lock.lock();
try {
return inventory.size() >= slotLimit;
} finally {
lock.unlock();
}
}
public boolean isFull(int margin) {
return inventory.size() + margin >= slotLimit;
lock.lock();
try {
return inventory.size() + margin >= slotLimit;
} finally {
lock.unlock();
}
}
public boolean isFullAfterSomeItems(int margin, int used) {
return inventory.size() + margin >= slotLimit - used;
lock.lock();
try {
return inventory.size() + margin >= slotLimit - used;
} finally {
lock.unlock();
}
}
public short getNextFreeSlot() {
if (isFull()) {
return -1;
}
for (short i = 1; i <= slotLimit; i++) {
if (!inventory.keySet().contains(i)) {
return i;
lock.lock();
try {
for (short i = 1; i <= slotLimit; i++) {
if (!inventory.containsKey(i)) {
return i;
}
}
return -1;
} finally {
lock.unlock();
}
return -1;
}
public short getNumFreeSlot() {
if (isFull()) {
return 0;
}
short free = 0;
for (short i = 1; i <= slotLimit; i++) {
if (!inventory.keySet().contains(i)) {
free++;
}
}
return free;
lock.lock();
try {
short free = 0;
for (short i = 1; i <= slotLimit; i++) {
if (!inventory.containsKey(i)) {
free++;
}
}
return free;
} finally {
lock.unlock();
}
}
public static boolean checkSpot(MapleCharacter chr, Item item) {
@@ -394,7 +461,7 @@ public class MapleInventory implements Iterable<Item> {
@Override
public Iterator<Item> iterator() {
return Collections.unmodifiableCollection(inventory.values()).iterator();
return Collections.unmodifiableCollection(list()).iterator();
}
public Collection<MapleInventory> allInventories() {
@@ -404,7 +471,7 @@ public class MapleInventory implements Iterable<Item> {
public Item findByCashId(int cashId) {
boolean isRing = false;
Equip equip = null;
for (Item item : inventory.values()) {
for (Item item : list()) {
if (item.getType() == MapleInventoryType.EQUIP.getType()) {
equip = (Equip) item;
isRing = equip.getRingId() > -1;
@@ -417,10 +484,20 @@ public class MapleInventory implements Iterable<Item> {
}
public boolean checked() {
return checked;
lock.lock();
try {
return checked;
} finally {
lock.unlock();
}
}
public void checked(boolean yes) {
checked = yes;
lock.lock();
try {
checked = yes;
} finally {
lock.unlock();
}
}
}