diff --git a/build/built-jar.properties b/build/built-jar.properties
index 1dddac2c40..61c2adeeb4 100644
--- a/build/built-jar.properties
+++ b/build/built-jar.properties
@@ -1,4 +1,4 @@
-#Mon, 25 Sep 2017 01:46:09 -0300
+#Tue, 26 Sep 2017 00:15:28 -0300
C\:\\Nexon\\MapleSolaxia\\MapleSolaxiaV2=
diff --git a/build/classes/client/MapleCharacter$12.class b/build/classes/client/MapleCharacter$12.class
index e736acf9ee..6a62838746 100644
Binary files a/build/classes/client/MapleCharacter$12.class and b/build/classes/client/MapleCharacter$12.class differ
diff --git a/build/classes/client/MapleCharacter$13.class b/build/classes/client/MapleCharacter$13.class
index 34bde9a6aa..d3f52fd50a 100644
Binary files a/build/classes/client/MapleCharacter$13.class and b/build/classes/client/MapleCharacter$13.class differ
diff --git a/build/classes/client/MapleCharacter$14.class b/build/classes/client/MapleCharacter$14.class
index 6037fe54ff..255c64f4c7 100644
Binary files a/build/classes/client/MapleCharacter$14.class and b/build/classes/client/MapleCharacter$14.class differ
diff --git a/build/classes/client/MapleCharacter$15.class b/build/classes/client/MapleCharacter$15.class
index db05f3875a..fa7d606867 100644
Binary files a/build/classes/client/MapleCharacter$15.class and b/build/classes/client/MapleCharacter$15.class differ
diff --git a/build/classes/client/MapleCharacter$16.class b/build/classes/client/MapleCharacter$16.class
index 62129b955a..1ab3d761bd 100644
Binary files a/build/classes/client/MapleCharacter$16.class and b/build/classes/client/MapleCharacter$16.class differ
diff --git a/build/classes/client/MapleCharacter$17.class b/build/classes/client/MapleCharacter$17.class
index 0430f5e907..a4e3b6e033 100644
Binary files a/build/classes/client/MapleCharacter$17.class and b/build/classes/client/MapleCharacter$17.class differ
diff --git a/build/classes/client/MapleCharacter$18.class b/build/classes/client/MapleCharacter$18.class
index 28b558a69e..b6e98f3a01 100644
Binary files a/build/classes/client/MapleCharacter$18.class and b/build/classes/client/MapleCharacter$18.class differ
diff --git a/build/classes/client/MapleCharacter$19.class b/build/classes/client/MapleCharacter$19.class
index b66acd2e28..167759f2c9 100644
Binary files a/build/classes/client/MapleCharacter$19.class and b/build/classes/client/MapleCharacter$19.class differ
diff --git a/build/classes/client/MapleCharacter$MapleBuffStatValueHolder.class b/build/classes/client/MapleCharacter$MapleBuffStatValueHolder.class
index 8b4b8c77f7..9afef49517 100644
Binary files a/build/classes/client/MapleCharacter$MapleBuffStatValueHolder.class and b/build/classes/client/MapleCharacter$MapleBuffStatValueHolder.class differ
diff --git a/build/classes/client/MapleCharacter$MapleCoolDownValueHolder.class b/build/classes/client/MapleCharacter$MapleCoolDownValueHolder.class
index 65e9a34750..5ad57c5686 100644
Binary files a/build/classes/client/MapleCharacter$MapleCoolDownValueHolder.class and b/build/classes/client/MapleCharacter$MapleCoolDownValueHolder.class differ
diff --git a/build/classes/client/MapleCharacter$SkillEntry.class b/build/classes/client/MapleCharacter$SkillEntry.class
index 40d7d03d26..bcc52c1fc7 100644
Binary files a/build/classes/client/MapleCharacter$SkillEntry.class and b/build/classes/client/MapleCharacter$SkillEntry.class differ
diff --git a/build/classes/client/MapleCharacter.class b/build/classes/client/MapleCharacter.class
index d5e8d6a729..bc92c84f44 100644
Binary files a/build/classes/client/MapleCharacter.class and b/build/classes/client/MapleCharacter.class differ
diff --git a/build/classes/client/inventory/ItemFactory.class b/build/classes/client/inventory/ItemFactory.class
index f947434d16..de06d0915b 100644
Binary files a/build/classes/client/inventory/ItemFactory.class and b/build/classes/client/inventory/ItemFactory.class differ
diff --git a/build/classes/constants/ServerConstants.class b/build/classes/constants/ServerConstants.class
index 458e585da8..0f084b32ab 100644
Binary files a/build/classes/constants/ServerConstants.class and b/build/classes/constants/ServerConstants.class differ
diff --git a/build/classes/net/server/Server$1.class b/build/classes/net/server/Server$1.class
index 06eb077f6a..03815bf333 100644
Binary files a/build/classes/net/server/Server$1.class and b/build/classes/net/server/Server$1.class differ
diff --git a/build/classes/net/server/Server.class b/build/classes/net/server/Server.class
index 4bef61ba52..22d554a6ab 100644
Binary files a/build/classes/net/server/Server.class and b/build/classes/net/server/Server.class differ
diff --git a/build/classes/net/server/channel/handlers/HiredMerchantRequest.class b/build/classes/net/server/channel/handlers/HiredMerchantRequest.class
index f1273bf4fb..d2fb39fd73 100644
Binary files a/build/classes/net/server/channel/handlers/HiredMerchantRequest.class and b/build/classes/net/server/channel/handlers/HiredMerchantRequest.class differ
diff --git a/build/classes/net/server/channel/handlers/PlayerInteractionHandler$Action.class b/build/classes/net/server/channel/handlers/PlayerInteractionHandler$Action.class
index 651f198286..463158ce7a 100644
Binary files a/build/classes/net/server/channel/handlers/PlayerInteractionHandler$Action.class and b/build/classes/net/server/channel/handlers/PlayerInteractionHandler$Action.class differ
diff --git a/build/classes/net/server/channel/handlers/PlayerInteractionHandler.class b/build/classes/net/server/channel/handlers/PlayerInteractionHandler.class
index a043aa9590..92dd331d9e 100644
Binary files a/build/classes/net/server/channel/handlers/PlayerInteractionHandler.class and b/build/classes/net/server/channel/handlers/PlayerInteractionHandler.class differ
diff --git a/build/classes/net/server/channel/handlers/TakeDamageHandler.class b/build/classes/net/server/channel/handlers/TakeDamageHandler.class
index 204bf8b9cd..35a4bde059 100644
Binary files a/build/classes/net/server/channel/handlers/TakeDamageHandler.class and b/build/classes/net/server/channel/handlers/TakeDamageHandler.class differ
diff --git a/build/classes/net/server/world/World$1.class b/build/classes/net/server/world/World$1.class
index af99445edf..d522d36d64 100644
Binary files a/build/classes/net/server/world/World$1.class and b/build/classes/net/server/world/World$1.class differ
diff --git a/build/classes/net/server/world/World.class b/build/classes/net/server/world/World.class
index e2e7aa6861..22bd854aed 100644
Binary files a/build/classes/net/server/world/World.class and b/build/classes/net/server/world/World.class differ
diff --git a/build/classes/server/maps/HiredMerchant$SoldItem.class b/build/classes/server/maps/HiredMerchant$SoldItem.class
index ec0540adc6..0f41d3e8e1 100644
Binary files a/build/classes/server/maps/HiredMerchant$SoldItem.class and b/build/classes/server/maps/HiredMerchant$SoldItem.class differ
diff --git a/build/classes/server/maps/HiredMerchant.class b/build/classes/server/maps/HiredMerchant.class
index d515db0951..177d1481fb 100644
Binary files a/build/classes/server/maps/HiredMerchant.class and b/build/classes/server/maps/HiredMerchant.class differ
diff --git a/build/classes/tools/MaplePacketCreator.class b/build/classes/tools/MaplePacketCreator.class
index 793fdd2e25..f9657fa238 100644
Binary files a/build/classes/tools/MaplePacketCreator.class and b/build/classes/tools/MaplePacketCreator.class differ
diff --git a/dist/MapleSolaxia.jar b/dist/MapleSolaxia.jar
index a0c8db00ee..b0954fde12 100644
Binary files a/dist/MapleSolaxia.jar and b/dist/MapleSolaxia.jar differ
diff --git a/docs/feature_list.txt b/docs/feature_list.txt
index 145f6b6f7c..b4d5d2856e 100644
--- a/docs/feature_list.txt
+++ b/docs/feature_list.txt
@@ -67,6 +67,7 @@ Server potentials:
* Custom jail system (needs provided custom wz).
* Delete Character 100% (requires ENABLE_PIC activated).
* Boats, elevator and other travelling mechanics fully working.
+* Eanbled Hired Merchant can be used anywhere but FM Entrance.
* Vega's spell.
* Pet item ignore.
* Autosaver (periodically saves on DB current state of every player in-game).
diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt
index 52b8e10045..453e227721 100644
--- a/docs/mychanges_ptbr.txt
+++ b/docs/mychanges_ptbr.txt
@@ -549,4 +549,8 @@ Consertado GPQ n
23 Setembro 2017,
Adicionado Water of Life.
-Consertado bug com sistema novo de buffs ao entrar no cash shop e em outros cenários onde não se detectava o melhor buff corretamente.
\ No newline at end of file
+Consertado bug com sistema novo de buffs ao entrar no cash shop e em outros cenários onde não se detectava o melhor buff corretamente.
+
+25 Setembro 2017,
+Adicionado proteção de acesso concorrente a ações de Hired Merchant.
+Corrigido alguns problemas com Hired Merchant não retornando a quantidade correta de itens.
\ No newline at end of file
diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml
index 5af0ed1acc..d11e7eb3df 100644
--- a/nbproject/private/private.xml
+++ b/nbproject/private/private.xml
@@ -2,6 +2,30 @@
-
+
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/PlayerLoggedinHandler.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/TakeDamageHandler.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/command/Commands.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/maps/HiredMerchant.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/MapleStatEffect.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/inventory/Item.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/scripts/npc/9030000.js
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/maps/MapleMapObject.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/constants/ExpTable.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/constants/ServerConstants.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/world/World.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/worker/HiredMerchantWorker.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/FredrickHandler.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/PlayerInteractionHandler.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/tools/MaplePacketCreator.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/HiredMerchantRequest.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/AutoAssignHandler.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/inventory/ItemFactory.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/Server.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/scripting/npc/NPCConversationManager.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/MapleCharacter.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/maps/MapleMap.java
+ file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/Channel.java
+
diff --git a/sql/db_database.sql b/sql/db_database.sql
index 855063d158..36024833ee 100644
--- a/sql/db_database.sql
+++ b/sql/db_database.sql
@@ -12984,6 +12984,15 @@ CREATE TABLE IF NOT EXISTS `inventoryitems` (
KEY `CHARID` (`characterid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+CREATE TABLE IF NOT EXISTS `inventorymerchant` (
+ `inventorymerchantid` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `inventoryitemid` int(10) unsigned NOT NULL DEFAULT '0',
+ `characterid` int(11) DEFAULT NULL,
+ `bundles` int(10) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`inventorymerchantid`),
+ KEY `INVENTORYITEMID` (`inventoryitemid`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
CREATE TABLE IF NOT EXISTS `ipbans` (
`ipbanid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ip` varchar(40) NOT NULL DEFAULT '',
diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java
index 909e01a0d1..8578d06424 100644
--- a/src/client/MapleCharacter.java
+++ b/src/client/MapleCharacter.java
@@ -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 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 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) {
diff --git a/src/client/inventory/ItemFactory.java b/src/client/inventory/ItemFactory.java
index 5bd1d98401..6812e63554 100644
--- a/src/client/inventory/ItemFactory.java
+++ b/src/client/inventory/ItemFactory.java
@@ -57,6 +57,20 @@ public enum ItemFactory {
}
public List> loadItems(int id, boolean login) throws SQLException {
+ if(value != 6) return loadItemsCommon(id, login);
+ else return loadItemsMerchant(id, login);
+ }
+
+ public void saveItems(List> items, int id, Connection con) throws SQLException {
+ saveItems(items, null, id, con);
+ }
+
+ public synchronized void saveItems(List> items, List bundlesList, int id, Connection con) throws SQLException {
+ if(value != 6) saveItemsCommon(items, id, con);
+ else saveItemsMerchant(items, bundlesList, id, con);
+ }
+
+ private List> loadItemsCommon(int id, boolean login) throws SQLException {
List> items = new ArrayList<>();
PreparedStatement ps = null;
@@ -135,7 +149,7 @@ public enum ItemFactory {
return items;
}
- public synchronized void saveItems(List> items, int id, Connection con) throws SQLException {
+ private void saveItemsCommon(List> 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> loadItemsMerchant(int id, boolean login) throws SQLException {
+ List> 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- (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> items, List 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
- 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();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java
index 2798aaaa0d..3c64e28177 100644
--- a/src/constants/ServerConstants.java
+++ b/src/constants/ServerConstants.java
@@ -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.
diff --git a/src/net/server/Server.java b/src/net/server/Server.java
index 2653260ef0..3005d29030 100644
--- a/src/net/server/Server.java
+++ b/src/net/server/Server.java
@@ -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> worldRecommendedList = new LinkedList<>();
private final Map guilds = new LinkedHashMap<>();
+ private final Lock shutdownLock = new ReentrantLock();
private final PlayerBuffStorage buffStorage = new PlayerBuffStorage();
private final Map 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();
}
}
};
diff --git a/src/net/server/channel/handlers/FredrickHandler.java b/src/net/server/channel/handlers/FredrickHandler.java
index b1a931f1b3..0af9a78353 100644
--- a/src/net/server/channel/handlers/FredrickHandler.java
+++ b/src/net/server/channel/handlers/FredrickHandler.java
@@ -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));
diff --git a/src/net/server/channel/handlers/HiredMerchantRequest.java b/src/net/server/channel/handlers/HiredMerchantRequest.java
index 73c76cf97f..58b21a7c56 100644
--- a/src/net/server/channel/handlers/HiredMerchantRequest.java
+++ b/src/net/server/channel/handlers/HiredMerchantRequest.java
@@ -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) {
diff --git a/src/net/server/channel/handlers/PlayerInteractionHandler.java b/src/net/server/channel/handlers/PlayerInteractionHandler.java
index 8d60569f9b..20a91415a6 100644
--- a/src/net/server/channel/handlers/PlayerInteractionHandler.java
+++ b/src/net/server/channel/handlers/PlayerInteractionHandler.java
@@ -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);
diff --git a/src/net/server/channel/handlers/TakeDamageHandler.java b/src/net/server/channel/handlers/TakeDamageHandler.java
index a9c082233c..9f1e5e9759 100644
--- a/src/net/server/channel/handlers/TakeDamageHandler.java
+++ b/src/net/server/channel/handlers/TakeDamageHandler.java
@@ -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();
diff --git a/src/net/server/world/World.java b/src/net/server/world/World.java
index 565bd9e69b..17683c65a1 100644
--- a/src/net/server/world/World.java
+++ b/src/net/server/world/World.java
@@ -86,7 +86,6 @@ public class World {
private long mountUpdate;
private Map activeMerchants = new LinkedHashMap<>();
- private ScheduledFuture> MerchantsSchedule;
private long merchantUpdate;
private ScheduledFuture> charactersSchedule;
diff --git a/src/server/maps/HiredMerchant.java b/src/server/maps/HiredMerchant.java
index c5a1fc527e..4761df7a3c 100644
--- a/src/server/maps/HiredMerchant.java
+++ b/src/server/maps/HiredMerchant.java
@@ -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 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 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> itemsWithType = new ArrayList<>();
+ List 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> getMessages() {
- return messages;
+ synchronized (messages) {
+ List> msgList = new LinkedList<>();
+ for(Pair m : messages) {
+ msgList.add(m);
+ }
+
+ return msgList;
+ }
}
public int getMapId() {
@@ -414,7 +492,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
}
public List getSold() {
- return sold;
+ synchronized (sold) {
+ return Collections.unmodifiableList(sold);
+ }
}
public int getMesos() {
diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java
index 0baa484756..528b9955e4 100644
--- a/src/tools/MaplePacketCreator.java
+++ b/src/tools/MaplePacketCreator.java
@@ -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> 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);