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
+1 -1
View File
@@ -1,4 +1,4 @@
#Mon, 25 Sep 2017 01:46:09 -0300
#Tue, 26 Sep 2017 00:15:28 -0300
C\:\\Nexon\\MapleSolaxia\\MapleSolaxiaV2=
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -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).
+4
View File
@@ -550,3 +550,7 @@ 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.
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.
+25 -1
View File
@@ -2,6 +2,30 @@
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="1"/>
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
<group/>
<group>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/PlayerLoggedinHandler.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/TakeDamageHandler.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/command/Commands.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/maps/HiredMerchant.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/MapleStatEffect.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/inventory/Item.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/scripts/npc/9030000.js</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/maps/MapleMapObject.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/constants/ExpTable.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/constants/ServerConstants.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/world/World.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/worker/HiredMerchantWorker.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/FredrickHandler.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/PlayerInteractionHandler.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/tools/MaplePacketCreator.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/HiredMerchantRequest.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/AutoAssignHandler.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/inventory/ItemFactory.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/Server.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/scripting/npc/NPCConversationManager.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/MapleCharacter.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/maps/MapleMap.java</file>
<file>file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/Channel.java</file>
</group>
</open-files>
</project-private>
+9
View File
@@ -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 '',
+14 -7
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) {
+223 -1
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();
}
}
}
+2
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.
+64 -53
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();
}
}
};
@@ -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));
@@ -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) {
@@ -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);
@@ -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();
-1
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;
+127 -47
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;
@@ -331,23 +382,37 @@ public class HiredMerchant extends AbstractMapleMapObject {
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() {
+8 -6
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);