Protected Hired Merchant + Buff system patch

Fixed some issues with Fredrick not retrieving the right amount of items
from Hired Merchants. Added concurrency protection for HM. Patched a
minor issue on buff system.
This commit is contained in:
ronancpl
2017-09-26 00:18:14 -03:00
parent 28258530e4
commit de7e686a92
42 changed files with 500 additions and 131 deletions

View File

@@ -2574,7 +2574,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
buffExpires.put(sourceid, expirationtime);
}
private void removeEffectFromItemEffectHolder(Integer sourceid, MapleBuffStat buffStat) {
private boolean removeEffectFromItemEffectHolder(Integer sourceid, MapleBuffStat buffStat) {
Map<MapleBuffStat, MapleBuffStatValueHolder> lbe = buffEffects.get(sourceid);
if(lbe.remove(buffStat) != null) {
@@ -2584,7 +2584,11 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
buffEffects.remove(sourceid);
buffExpires.remove(sourceid);
}
return true;
}
return false;
}
private void removeItemEffectHolder(Integer sourceid) {
@@ -2634,9 +2638,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private void extractBuffValue(int sourceid, MapleBuffStat stat) {
chrLock.lock();
try {
if(buffEffects.get(sourceid).remove(stat) != null) {
buffEffectsCount.put(stat, (byte)(buffEffectsCount.get(stat) - 1));
}
removeEffectFromItemEffectHolder(sourceid, stat);
} finally {
chrLock.unlock();
}
@@ -2755,7 +2757,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
for (Entry<MapleBuffStat, MapleBuffStatValueHolder> stat : stats.entrySet()) {
int sourceid = stat.getValue().effect.getBuffSourceId();
if(buffEffects.get(sourceid) == null) {
if(!buffEffects.containsKey(sourceid)) {
buffExpires.remove(sourceid);
}
@@ -6537,10 +6539,15 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
public void addMerchantMesos(int add) {
int newAmount;
try {
newAmount = (int)Math.min((long)merchantmeso + add, Integer.MAX_VALUE);
System.out.println("adding" + add + " now" + newAmount);
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET MerchantMesos = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, merchantmeso + add);
ps.setInt(1, newAmount);
ps.setInt(2, id);
ps.executeUpdate();
}
@@ -6550,7 +6557,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
e.printStackTrace();
return;
}
merchantmeso += add;
merchantmeso = newAmount;
}
public void setMerchantMeso(int set) {

View File

@@ -57,6 +57,20 @@ public enum ItemFactory {
}
public List<Pair<Item, MapleInventoryType>> loadItems(int id, boolean login) throws SQLException {
if(value != 6) return loadItemsCommon(id, login);
else return loadItemsMerchant(id, login);
}
public void saveItems(List<Pair<Item, MapleInventoryType>> items, int id, Connection con) throws SQLException {
saveItems(items, null, id, con);
}
public synchronized void saveItems(List<Pair<Item, MapleInventoryType>> items, List<Short> bundlesList, int id, Connection con) throws SQLException {
if(value != 6) saveItemsCommon(items, id, con);
else saveItemsMerchant(items, bundlesList, id, con);
}
private List<Pair<Item, MapleInventoryType>> loadItemsCommon(int id, boolean login) throws SQLException {
List<Pair<Item, MapleInventoryType>> items = new ArrayList<>();
PreparedStatement ps = null;
@@ -135,7 +149,7 @@ public enum ItemFactory {
return items;
}
public synchronized void saveItems(List<Pair<Item, MapleInventoryType>> items, int id, Connection con) throws SQLException {
private void saveItemsCommon(List<Pair<Item, MapleInventoryType>> items, int id, Connection con) throws SQLException {
PreparedStatement ps = null;
PreparedStatement pse = null;
ResultSet rs = null;
@@ -227,4 +241,212 @@ public enum ItemFactory {
lock.unlock();
}
}
private List<Pair<Item, MapleInventoryType>> loadItemsMerchant(int id, boolean login) throws SQLException {
List<Pair<Item, MapleInventoryType>> items = new ArrayList<>();
PreparedStatement ps = null, ps2 = null;
ResultSet rs = null, rs2 = null;
Connection con = DatabaseConnection.getConnection();
try {
StringBuilder query = new StringBuilder();
query.append("SELECT * FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");
query.append(account ? "accountid" : "characterid").append("` = ?");
if (login) {
query.append(" AND `inventorytype` = ").append(MapleInventoryType.EQUIPPED.getType());
}
ps = con.prepareStatement(query.toString());
ps.setInt(1, value);
ps.setInt(2, id);
rs = ps.executeQuery();
while (rs.next()) {
ps2 = con.prepareStatement("SELECT `bundles` FROM `inventorymerchant` WHERE `inventoryitemid` = ?");
ps2.setInt(1, rs.getInt("inventoryitemid"));
rs2 = ps2.executeQuery();
short bundles = 0;
if(rs2.next()) {
bundles = rs2.getShort("bundles");
}
MapleInventoryType mit = MapleInventoryType.getByType(rs.getByte("inventorytype"));
if (mit.equals(MapleInventoryType.EQUIP) || mit.equals(MapleInventoryType.EQUIPPED)) {
Equip equip = new Equip(rs.getInt("itemid"), (short) rs.getInt("position"));
equip.setOwner(rs.getString("owner"));
equip.setQuantity((short) rs.getInt("quantity"));
equip.setAcc((short) rs.getInt("acc"));
equip.setAvoid((short) rs.getInt("avoid"));
equip.setDex((short) rs.getInt("dex"));
equip.setHands((short) rs.getInt("hands"));
equip.setHp((short) rs.getInt("hp"));
equip.setInt((short) rs.getInt("int"));
equip.setJump((short) rs.getInt("jump"));
equip.setVicious((short) rs.getInt("vicious"));
equip.setFlag((byte) rs.getInt("flag"));
equip.setLuk((short) rs.getInt("luk"));
equip.setMatk((short) rs.getInt("matk"));
equip.setMdef((short) rs.getInt("mdef"));
equip.setMp((short) rs.getInt("mp"));
equip.setSpeed((short) rs.getInt("speed"));
equip.setStr((short) rs.getInt("str"));
equip.setWatk((short) rs.getInt("watk"));
equip.setWdef((short) rs.getInt("wdef"));
equip.setUpgradeSlots((byte) rs.getInt("upgradeslots"));
equip.setLevel((byte) rs.getByte("level"));
equip.setItemExp(rs.getInt("itemexp"));
equip.setItemLevel(rs.getByte("itemlevel"));
equip.setExpiration(rs.getLong("expiration"));
equip.setGiftFrom(rs.getString("giftFrom"));
equip.setRingId(rs.getInt("ringid"));
items.add(new Pair<Item, MapleInventoryType>(equip, mit));
} else {
if(bundles > 0) {
Item item = new Item(rs.getInt("itemid"), (byte) rs.getInt("position"), (short)(bundles * rs.getInt("quantity")), rs.getInt("petid"));
item.setOwner(rs.getString("owner"));
item.setExpiration(rs.getLong("expiration"));
item.setGiftFrom(rs.getString("giftFrom"));
item.setFlag((byte) rs.getInt("flag"));
items.add(new Pair<>(item, mit));
}
}
rs2.close();
ps2.close();
}
rs.close();
ps.close();
con.close();
} finally {
if (rs2 != null && !rs2.isClosed()) {
rs2.close();
}
if (ps2 != null && !ps2.isClosed()) {
ps2.close();
}
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (con != null && !con.isClosed()) {
con.close();
}
}
return items;
}
private void saveItemsMerchant(List<Pair<Item, MapleInventoryType>> items, List<Short> bundlesList, int id, Connection con) throws SQLException {
PreparedStatement ps = null;
PreparedStatement pse = null;
ResultSet rs = null;
lock.lock();
try {
ps = con.prepareStatement("DELETE FROM `inventorymerchant` WHERE `characterid` = ?");
ps.setInt(1, id);
ps.executeUpdate();
ps.close();
StringBuilder query = new StringBuilder();
query.append("DELETE `inventoryitems`, `inventoryequipment` FROM `inventoryitems` LEFT JOIN `inventoryequipment` USING(`inventoryitemid`) WHERE `type` = ? AND `");
query.append(account ? "accountid" : "characterid").append("` = ?");
ps = con.prepareStatement(query.toString());
ps.setInt(1, value);
ps.setInt(2, id);
ps.executeUpdate();
ps.close();
ps = con.prepareStatement("INSERT INTO `inventoryitems` VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
if (!items.isEmpty()) {
int i = 0;
for (Pair<Item, MapleInventoryType> pair : items) {
Item item = pair.getLeft();
Short bundles = bundlesList.get(i);
MapleInventoryType mit = pair.getRight();
i++;
ps.setInt(1, value);
ps.setString(2, account ? null : String.valueOf(id));
ps.setString(3, account ? String.valueOf(id) : null);
ps.setInt(4, item.getItemId());
ps.setInt(5, mit.getType());
ps.setInt(6, item.getPosition());
ps.setInt(7, item.getQuantity());
ps.setString(8, item.getOwner());
ps.setInt(9, item.getPetId());
ps.setInt(10, item.getFlag());
ps.setLong(11, item.getExpiration());
ps.setString(12, item.getGiftFrom());
ps.executeUpdate();
rs = ps.getGeneratedKeys();
if (!rs.next()) {
throw new RuntimeException("Inserting item failed.");
}
int genKey = rs.getInt(1);
rs.close();
pse = con.prepareStatement("INSERT INTO `inventorymerchant` VALUES (DEFAULT, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
pse.setInt(1, genKey);
pse.setInt(2, id);
pse.setInt(3, bundles);
pse.executeUpdate();
pse.close();
if (mit.equals(MapleInventoryType.EQUIP) || mit.equals(MapleInventoryType.EQUIPPED)) {
pse = con.prepareStatement("INSERT INTO `inventoryequipment` VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
pse.setInt(1, genKey);
Equip equip = (Equip) item;
pse.setInt(2, equip.getUpgradeSlots());
pse.setInt(3, equip.getLevel());
pse.setInt(4, equip.getStr());
pse.setInt(5, equip.getDex());
pse.setInt(6, equip.getInt());
pse.setInt(7, equip.getLuk());
pse.setInt(8, equip.getHp());
pse.setInt(9, equip.getMp());
pse.setInt(10, equip.getWatk());
pse.setInt(11, equip.getMatk());
pse.setInt(12, equip.getWdef());
pse.setInt(13, equip.getMdef());
pse.setInt(14, equip.getAcc());
pse.setInt(15, equip.getAvoid());
pse.setInt(16, equip.getHands());
pse.setInt(17, equip.getSpeed());
pse.setInt(18, equip.getJump());
pse.setInt(19, 0);
pse.setInt(20, equip.getVicious());
pse.setInt(21, equip.getItemLevel());
pse.setInt(22, equip.getItemExp());
pse.setInt(23, equip.getRingId());
pse.executeUpdate();
pse.close();
}
}
}
ps.close();
} finally {
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (pse != null && !pse.isClosed()) {
pse.close();
}
if(rs != null && !rs.isClosed()) {
rs.close();
}
lock.unlock();
}
}
}

View File

@@ -43,10 +43,12 @@ public class ServerConstants {
public static final boolean USE_ITEM_SORT = true;
public static final boolean USE_ITEM_SORT_BY_NAME = false; //Item sorting based on name rather than id.
public static final boolean USE_PARTY_SEARCH = false;
public static final boolean USE_MERCHANT_ANYWHERE = true; //Enables player shops and hired merchants outside FM rooms (except FM entrance).
public static final boolean USE_AUTOBAN = false; //Commands the server to detect infractors automatically.
public static final boolean USE_AUTOSAVE = true; //Enables server autosaving feature (saves characters to DB each 1 hour).
public static final boolean USE_SERVER_AUTOASSIGNER = true; //Server-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments.
public static final boolean USE_REFRESH_RANK_MOVE = true;
public static final boolean USE_ENFORCE_UNMERCHABLE_PET = true; //Forces players to not sell pets via merchants. (since non-named pets gets dirty name and other possible DB-related issues)
public static final boolean USE_ENFORCE_MDOOR_POSITION = true; //Forces mystic door to be spawned near spawnpoints. (since things bugs out other way, and this helps players to locate the door faster)
public static final boolean USE_ERASE_UNTRADEABLE_DROP = true; //Forces flagged untradeable items to disappear when dropped.
public static final boolean USE_ERASE_PET_ON_EXPIRATION = false;//Forces pets to be removed from inventory when expire time comes, rather than converting it to a doll.

View File

@@ -39,6 +39,8 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
import net.MapleServerHandler;
import net.mina.MapleCodecFactory;
@@ -79,6 +81,7 @@ public class Server implements Runnable {
private static Server instance = null;
private List<Pair<Integer, String>> worldRecommendedList = new LinkedList<>();
private final Map<Integer, MapleGuild> guilds = new LinkedHashMap<>();
private final Lock shutdownLock = new ReentrantLock();
private final PlayerBuffStorage buffStorage = new PlayerBuffStorage();
private final Map<Integer, MapleAlliance> alliances = new LinkedHashMap<>();
private boolean online = false;
@@ -99,7 +102,8 @@ public class Server implements Runnable {
return worldRecommendedList;
}
public void removeChannel(int worldid, int channel) {
/*
public void removeChannel(int worldid, int channel) { //lol don't!
channels.remove(channel);
World world = worlds.get(worldid);
@@ -107,6 +111,7 @@ public class Server implements Runnable {
world.removeChannel(channel);
}
}
*/
public Channel getChannel(int world, int channel) {
return worlds.get(world).getChannel(channel);
@@ -702,69 +707,75 @@ public class Server implements Runnable {
return worlds;
}
public final Runnable shutdown(final boolean restart) {//only once :D
public final Runnable shutdown(final boolean restart) {//no player should be online when trying to shutdown!
return new Runnable() {
@Override
public void run() {
System.out.println((restart ? "Restarting" : "Shutting down") + " the server!\r\n");
if (getWorlds() == null) return;//already shutdown
for (World w : getWorlds()) {
w.shutdown();
}
/*for (World w : getWorlds()) {
while (w.getPlayerStorage().getAllCharacters().size() > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
System.err.println("FUCK MY LIFE");
shutdownLock.lock();
try {
System.out.println((restart ? "Restarting" : "Shutting down") + " the server!\r\n");
if (getWorlds() == null) return;//already shutdown
for (World w : getWorlds()) {
w.shutdown();
}
/*for (World w : getWorlds()) {
while (w.getPlayerStorage().getAllCharacters().size() > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
System.err.println("FUCK MY LIFE");
}
}
}
}
for (Channel ch : getAllChannels()) {
while (ch.getConnectedClients() > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
System.err.println("FUCK MY LIFE");
for (Channel ch : getAllChannels()) {
while (ch.getConnectedClients() > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
System.err.println("FUCK MY LIFE");
}
}
}*/
TimerManager.getInstance().purge();
TimerManager.getInstance().stop();
for (Channel ch : getAllChannels()) {
while (!ch.finishedShutdown()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
System.err.println("FUCK MY LIFE");
}
}
}
}*/
worlds.clear();
worlds = null;
channels.clear();
channels = null;
worldRecommendedList.clear();
worldRecommendedList = null;
TimerManager.getInstance().purge();
TimerManager.getInstance().stop();
for (Channel ch : getAllChannels()) {
while (!ch.finishedShutdown()) {
System.out.println("Worlds + Channels are offline.");
acceptor.unbind();
acceptor = null;
if (!restart) {
System.exit(0);
} else {
System.out.println("\r\nRestarting the server....\r\n");
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
System.err.println("FUCK MY LIFE");
instance.finalize();//FUU I CAN AND IT'S FREE
} catch (Throwable ex) {
ex.printStackTrace();
}
instance = null;
System.gc();
getInstance().run();//DID I DO EVERYTHING?! D:
}
}
worlds.clear();
worlds = null;
channels.clear();
channels = null;
worldRecommendedList.clear();
worldRecommendedList = null;
System.out.println("Worlds + Channels are offline.");
acceptor.unbind();
acceptor = null;
if (!restart) {
System.exit(0);
} else {
System.out.println("\r\nRestarting the server....\r\n");
try {
instance.finalize();//FUU I CAN AND IT'S FREE
} catch (Throwable ex) {
ex.printStackTrace();
}
instance = null;
System.gc();
getInstance().run();//DID I DO EVERYTHING?! D:
} finally {
shutdownLock.unlock();
}
}
};

View File

@@ -72,10 +72,10 @@ public class FredrickHandler extends AbstractMaplePacketHandler {
chr.getHiredMerchant().clearItems();
for (int i = 0; i < items.size(); i++) {
Item item = items.get(i).getLeft();
Item item = items.get(i).getLeft();
MapleInventoryManipulator.addFromDrop(c, item, false);
String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId());
FilePrinter.printError(FilePrinter.FREDRICK + chr.getName() + ".txt", chr.getName() + " gained " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")\r\n");
FilePrinter.printError(FilePrinter.FREDRICK + chr.getName() + ".txt", chr.getName() + " gained " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")\r\n");
}
c.announce(MaplePacketCreator.fredrickMessage((byte) 0x1E));

View File

@@ -26,6 +26,7 @@ import client.MapleCharacter;
import java.sql.SQLException;
import java.util.Arrays;
import client.MapleClient;
import constants.ServerConstants;
import net.AbstractMaplePacketHandler;
import server.maps.MapleMapObjectType;
import tools.MaplePacketCreator;
@@ -38,7 +39,7 @@ import tools.data.input.SeekableLittleEndianAccessor;
public final class HiredMerchantRequest extends AbstractMaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
MapleCharacter chr = c.getPlayer();
if (chr.getMap().getMapObjectsInRange(chr.getPosition(), 23000, Arrays.asList(MapleMapObjectType.HIRED_MERCHANT)).isEmpty() && chr.getMapId() > 910000000 && chr.getMapId() < 910000023) {
if (chr.getMap().getMapObjectsInRange(chr.getPosition(), 23000, Arrays.asList(MapleMapObjectType.HIRED_MERCHANT)).isEmpty() && ((ServerConstants.USE_MERCHANT_ANYWHERE && chr.getMapId() != 910000000) || (chr.getMapId() > 910000000 && chr.getMapId() < 910000023))) {
if (!chr.hasMerchant()) {
try {
if (ItemFactory.MERCHANT.loadItems(chr.getId(), false).isEmpty() && chr.getMerchantMeso() == 0) {

View File

@@ -28,6 +28,7 @@ import client.inventory.Item;
import client.inventory.MapleInventory;
import client.inventory.MapleInventoryType;
import constants.ItemConstants;
import constants.ServerConstants;
import java.util.Arrays;
@@ -234,7 +235,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
} else if (!merchant.isOpen()) {
chr.dropMessage(1, "This shop is in maintenance, please come by later.");
return;
} else if (merchant.getFreeSlot() == -1) {
} else if (merchant.getFreeSlotThreadsafe() == -1) {
chr.dropMessage(1, "This shop has reached it's maximum capacity, please come by later.");
return;
} else {
@@ -259,10 +260,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
game.chat(c, slea.readMapleAsciiString());
}
} else if (merchant != null) {
String message = chr.getName() + " : " + slea.readMapleAsciiString();
byte slot = (byte) (merchant.getVisitorSlot(c.getPlayer()) + 1);
merchant.getMessages().add(new Pair<>(message, slot));
merchant.broadcastToVisitors(MaplePacketCreator.hiredMerchantChat(message, slot));
merchant.sendMessage(c.getPlayer(), slea.readMapleAsciiString());
}
} else if (mode == Action.EXIT.getCode()) {
if (chr.getTrade() != null) {
@@ -408,7 +406,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
} else if (mode == Action.CONFIRM.getCode()) {
MapleTrade.completeTrade(c.getPlayer());
} else if (mode == Action.ADD_ITEM.getCode() || mode == Action.PUT_ITEM.getCode()) {
MapleInventoryType type = MapleInventoryType.getByType(slea.readByte());
MapleInventoryType type = MapleInventoryType.getByType(slea.readByte());
short slot = slea.readShort();
short bundles = slea.readShort();
if (chr.getInventory(type).getItem(slot) == null || chr.getItemQuantity(chr.getInventory(type).getItem(slot).getItemId(), false) < bundles || chr.getInventory(type).getItem(slot).getFlag() == ItemConstants.UNTRADEABLE) {
@@ -425,6 +423,9 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
Item sellItem = ivItem.copy();
if (chr.getItemQuantity(ivItem.getItemId(), false) < perBundle * bundles) {
return;
} else if (ServerConstants.USE_ENFORCE_UNMERCHABLE_PET && ItemConstants.isPet(ivItem.getItemId())) {
c.announce(MaplePacketCreator.serverNotice(1, "Pets are not allowed to be sold on the Player Shop."));
return;
}
sellItem.setQuantity(perBundle);
MaplePlayerShopItem item = new MaplePlayerShopItem(sellItem, bundles, price);
@@ -521,7 +522,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
shop.broadcast(MaplePacketCreator.getPlayerShopItemUpdate(shop));
} else if (merchant != null) {
merchant.buy(c, item, quantity);
merchant.broadcastToVisitors(MaplePacketCreator.updateHiredMerchant(merchant, c.getPlayer()));
merchant.broadcastToVisitorsThreadsafe(MaplePacketCreator.updateHiredMerchant(merchant, c.getPlayer()));
}
} else if (mode == Action.TAKE_ITEM_BACK.getCode()) {
HiredMerchant merchant = chr.getHiredMerchant();
@@ -555,7 +556,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
chr.setHasMerchant(false);
}
if (merchant != null && merchant.isOwner(c.getPlayer())) {
merchant.getMessages().clear();
merchant.clearMessages();
merchant.setOpen(true);
}
chr.setHiredMerchant(null);

View File

@@ -117,8 +117,14 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
return;
}
} catch(ClassCastException e) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Attacker is not a mob-type, rather is a " + map.getMapObject(oid).getClass().getName() + " entity.");
//this happens due to mob on last map damaging player just before changing maps
if(ServerConstants.USE_DEBUG) {
e.printStackTrace();
FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Attacker is not a mob-type, rather is a " + map.getMapObject(oid).getClass().getName() + " entity.");
}
return;
}
direction = slea.readByte();

View File

@@ -86,7 +86,6 @@ public class World {
private long mountUpdate;
private Map<HiredMerchant, Byte> activeMerchants = new LinkedHashMap<>();
private ScheduledFuture<?> MerchantsSchedule;
private long merchantUpdate;
private ScheduledFuture<?> charactersSchedule;

View File

@@ -73,7 +73,13 @@ public class HiredMerchant extends AbstractMapleMapObject {
this.map = owner.getMap();
}
public void broadcastToVisitors(final byte[] packet) {
public void broadcastToVisitorsThreadsafe(final byte[] packet) {
synchronized(visitors) {
broadcastToVisitors(packet);
}
}
private void broadcastToVisitors(final byte[] packet) {
for (MapleCharacter visitor : visitors) {
if (visitor != null) {
visitor.getClient().announce(packet);
@@ -82,27 +88,37 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public void addVisitor(MapleCharacter visitor) {
int i = this.getFreeSlot();
if (i > -1) {
visitors[i] = visitor;
broadcastToVisitors(MaplePacketCreator.hiredMerchantVisitorAdd(visitor, i + 1));
}
}
public void removeVisitor(MapleCharacter visitor) {
int slot = getVisitorSlot(visitor);
if (slot < 0){ //Not found
return;
}
if (visitors[slot] != null && visitors[slot].getId() == visitor.getId()) {
visitors[slot] = null;
if (slot != -1) {
broadcastToVisitors(MaplePacketCreator.hiredMerchantVisitorLeave(slot + 1));
synchronized(visitors) {
int i = this.getFreeSlot();
if (i > -1) {
visitors[i] = visitor;
broadcastToVisitors(MaplePacketCreator.hiredMerchantVisitorAdd(visitor, i + 1));
}
}
}
public int getVisitorSlot(MapleCharacter visitor) {
public void removeVisitor(MapleCharacter visitor) {
synchronized(visitors) {
int slot = getVisitorSlot(visitor);
if (slot < 0){ //Not found
return;
}
if (visitors[slot] != null && visitors[slot].getId() == visitor.getId()) {
visitors[slot] = null;
if (slot != -1) {
broadcastToVisitors(MaplePacketCreator.hiredMerchantVisitorLeave(slot + 1));
}
}
}
}
public int getVisitorSlotThreadsafe(MapleCharacter visitor) {
synchronized(visitors) {
return getVisitorSlot(visitor);
}
}
private int getVisitorSlot(MapleCharacter visitor) {
for (int i = 0; i < 3; i++) {
if (visitors[i] != null && visitors[i].getId() == visitor.getId()){
return i;
@@ -112,21 +128,24 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public void removeAllVisitors(String message) {
for (int i = 0; i < 3; i++) {
if (visitors[i] != null) {
visitors[i].setHiredMerchant(null);
visitors[i].getClient().announce(MaplePacketCreator.leaveHiredMerchant(i + 1, 0x11));
if (message.length() > 0) {
visitors[i].dropMessage(1, message);
synchronized(visitors) {
for (int i = 0; i < 3; i++) {
if (visitors[i] != null) {
visitors[i].setHiredMerchant(null);
visitors[i].getClient().announce(MaplePacketCreator.leaveHiredMerchant(i + 1, 0x11));
if (message.length() > 0) {
visitors[i].dropMessage(1, message);
}
visitors[i] = null;
}
visitors[i] = null;
}
}
}
public void buy(MapleClient c, int item, short quantity) {
MaplePlayerShopItem pItem = items.get(item);
synchronized (items) {
MaplePlayerShopItem pItem = items.get(item);
Item newItem = pItem.getItem().copy();
newItem.setQuantity((short) ((pItem.getItem().getQuantity() * quantity)));
if ((newItem.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) {
@@ -142,11 +161,15 @@ public class HiredMerchant extends AbstractMapleMapObject {
c.announce(MaplePacketCreator.enableActions());
return;
}
int price = pItem.getPrice() * quantity;
int price = (int)Math.min((long)pItem.getPrice() * quantity, Integer.MAX_VALUE);
if (c.getPlayer().getMeso() >= price) {
if (MapleInventoryManipulator.addFromDrop(c, newItem, true)) {
c.getPlayer().gainMeso(-price, false);
sold.add(new SoldItem(c.getPlayer().getName(), pItem.getItem().getItemId(), quantity, price));
synchronized (sold) {
sold.add(new SoldItem(c.getPlayer().getName(), pItem.getItem().getItemId(), quantity, price));
}
pItem.setBundles((short) (pItem.getBundles() - quantity));
if (pItem.getBundles() < 1) {
pItem.setDoesExist(false);
@@ -187,7 +210,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
try {
saveItems(true);
items.clear();
synchronized (items) {
items.clear();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
@@ -234,15 +259,19 @@ public class HiredMerchant extends AbstractMapleMapObject {
con.close();
}
if (check(c.getPlayer(), getItems()) && !timeout) {
for (MaplePlayerShopItem mpsi : getItems()) {
List<MaplePlayerShopItem> copyItems = getItems();
if (check(c.getPlayer(), copyItems) && !timeout) {
for (MaplePlayerShopItem mpsi : copyItems) {
if (mpsi.isExist() && (mpsi.getItem().getType() == MapleInventoryType.EQUIP.getType())) {
MapleInventoryManipulator.addFromDrop(c, mpsi.getItem(), false);
} else if (mpsi.isExist()) {
MapleInventoryManipulator.addById(c, mpsi.getItem().getItemId(), (short) (mpsi.getBundles() * mpsi.getItem().getQuantity()), null, -1, mpsi.getItem().getFlag(), mpsi.getItem().getExpiration());
}
}
items.clear();
synchronized (items) {
items.clear();
}
}
try {
@@ -251,7 +280,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
e.printStackTrace();
}
items.clear();
synchronized (items) {
items.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
@@ -264,7 +295,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public void clearItems() {
items.clear();
synchronized (items) {
items.clear();
}
}
public int getOwnerId() {
@@ -276,15 +309,25 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public MapleCharacter[] getVisitors() {
return visitors;
synchronized(visitors) {
MapleCharacter[] copy = new MapleCharacter[3];
for(int i = 0; i < visitors.length; i++) copy[i] = visitors[i];
return copy;
}
}
public List<MaplePlayerShopItem> getItems() {
return Collections.unmodifiableList(items);
synchronized (items) {
return Collections.unmodifiableList(items);
}
}
public void addItem(MaplePlayerShopItem item) {
items.add(item);
synchronized (items) {
items.add(item);
}
try {
this.saveItems(false);
} catch (SQLException ex) {
@@ -293,7 +336,10 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public void removeFromSlot(int slot) {
items.remove(slot);
synchronized (items) {
items.remove(slot);
}
try {
this.saveItems(false);
} catch (SQLException ex) {
@@ -301,8 +347,13 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
}
public int getFreeSlotThreadsafe() {
synchronized(visitors) {
return getFreeSlot();
}
}
public int getFreeSlot() {
private int getFreeSlot() {
for (int i = 0; i < 3; i++) {
if (visitors[i] == null) {
return i;
@@ -330,24 +381,38 @@ public class HiredMerchant extends AbstractMapleMapObject {
public boolean isOwner(MapleCharacter chr) {
return chr.getId() == ownerId;
}
public void sendMessage(MapleCharacter chr, String msg) {
String message = chr.getName() + " : " + msg;
byte slot = (byte) (getVisitorSlot(chr) + 1);
synchronized (messages) {
messages.add(new Pair<>(message, slot));
}
broadcastToVisitors(MaplePacketCreator.hiredMerchantChat(message, slot));
}
public void saveItems(boolean shutdown) throws SQLException {
List<Pair<Item, MapleInventoryType>> itemsWithType = new ArrayList<>();
List<Short> bundles = new ArrayList<>();
for (MaplePlayerShopItem pItems : items) {
for (MaplePlayerShopItem pItems : getItems()) {
Item newItem = pItems.getItem();
if (shutdown) {
newItem.setQuantity((short) (pItems.getItem().getQuantity() * pItems.getBundles()));
short newBundle = pItems.getBundles();
if (shutdown) { //is "shutdown" really necessary?
newItem.setQuantity((short) (pItems.getItem().getQuantity()));
} else {
newItem.setQuantity(pItems.getItem().getQuantity());
newItem.setQuantity((short) (pItems.getItem().getQuantity()));
}
if (pItems.getBundles() > 0) {
if (newBundle > 0) {
itemsWithType.add(new Pair<>(newItem, MapleInventoryType.getByType(newItem.getType())));
bundles.add(newBundle);
}
}
Connection con = DatabaseConnection.getConnection();
ItemFactory.MERCHANT.saveItems(itemsWithType, this.ownerId, con);
ItemFactory.MERCHANT.saveItems(itemsWithType, bundles, this.ownerId, con);
con.close();
}
@@ -405,8 +470,21 @@ public class HiredMerchant extends AbstractMapleMapObject {
return (int) ((System.currentTimeMillis() - start) / 1000);
}
public void clearMessages() {
synchronized (messages) {
messages.clear();
}
}
public List<Pair<String, Byte>> getMessages() {
return messages;
synchronized (messages) {
List<Pair<String, Byte>> msgList = new LinkedList<>();
for(Pair<String, Byte> m : messages) {
msgList.add(m);
}
return msgList;
}
}
public int getMapId() {
@@ -414,7 +492,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public List<SoldItem> getSold() {
return sold;
synchronized (sold) {
return Collections.unmodifiableList(sold);
}
}
public int getMesos() {

View File

@@ -4959,7 +4959,7 @@ public class MaplePacketCreator {
mplew.writeInt(item.getBundles());
mplew.writeInt(item.getPrice());
mplew.writeInt(hm.getOwnerId());
mplew.write(hm.getFreeSlot() == -1 ? 1 : 0);
mplew.write(hm.getFreeSlotThreadsafe() == -1 ? 1 : 0);
MapleCharacter chr = c.getChannelServer().getPlayerStorage().getCharacterById(hm.getOwnerId());
if ((chr != null) && (c.getChannel() == hm.getChannel())) {
mplew.write(1);
@@ -5004,7 +5004,7 @@ public class MaplePacketCreator {
mplew.write(PlayerInteractionHandler.Action.ROOM.getCode());
mplew.write(0x05);
mplew.write(0x04);
mplew.writeShort(hm.getVisitorSlot(chr) + 1);
mplew.writeShort(hm.getVisitorSlotThreadsafe(chr) + 1);
mplew.writeInt(hm.getItemId());
mplew.writeMapleAsciiString("Hired Merchant");
for (int i = 0; i < 3; i++) {
@@ -5016,10 +5016,12 @@ public class MaplePacketCreator {
}
mplew.write(-1);
if (hm.isOwner(chr)) {
mplew.writeShort(hm.getMessages().size());
for (int i = 0; i < hm.getMessages().size(); i++) {
mplew.writeMapleAsciiString(hm.getMessages().get(i).getLeft());
mplew.write(hm.getMessages().get(i).getRight());
List<Pair<String, Byte>> msgList = hm.getMessages();
mplew.writeShort(msgList.size());
for (int i = 0; i < msgList.size(); i++) {
mplew.writeMapleAsciiString(msgList.get(i).getLeft());
mplew.write(msgList.get(i).getRight());
}
} else {
mplew.writeShort(0);