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= 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). * Custom jail system (needs provided custom wz).
* Delete Character 100% (requires ENABLE_PIC activated). * Delete Character 100% (requires ENABLE_PIC activated).
* Boats, elevator and other travelling mechanics fully working. * Boats, elevator and other travelling mechanics fully working.
* Eanbled Hired Merchant can be used anywhere but FM Entrance.
* Vega's spell. * Vega's spell.
* Pet item ignore. * Pet item ignore.
* Autosaver (periodically saves on DB current state of every player in-game). * 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, 23 Setembro 2017,
Adicionado Water of Life. 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. 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"> <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="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"> <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> </open-files>
</project-private> </project-private>
+9
View File
@@ -12984,6 +12984,15 @@ CREATE TABLE IF NOT EXISTS `inventoryitems` (
KEY `CHARID` (`characterid`) KEY `CHARID` (`characterid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; ) 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` ( CREATE TABLE IF NOT EXISTS `ipbans` (
`ipbanid` int(10) unsigned NOT NULL AUTO_INCREMENT, `ipbanid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ip` varchar(40) NOT NULL DEFAULT '', `ip` varchar(40) NOT NULL DEFAULT '',
+14 -7
View File
@@ -2574,7 +2574,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
buffExpires.put(sourceid, expirationtime); 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); Map<MapleBuffStat, MapleBuffStatValueHolder> lbe = buffEffects.get(sourceid);
if(lbe.remove(buffStat) != null) { if(lbe.remove(buffStat) != null) {
@@ -2584,7 +2584,11 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
buffEffects.remove(sourceid); buffEffects.remove(sourceid);
buffExpires.remove(sourceid); buffExpires.remove(sourceid);
} }
return true;
} }
return false;
} }
private void removeItemEffectHolder(Integer sourceid) { private void removeItemEffectHolder(Integer sourceid) {
@@ -2634,9 +2638,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private void extractBuffValue(int sourceid, MapleBuffStat stat) { private void extractBuffValue(int sourceid, MapleBuffStat stat) {
chrLock.lock(); chrLock.lock();
try { try {
if(buffEffects.get(sourceid).remove(stat) != null) { removeEffectFromItemEffectHolder(sourceid, stat);
buffEffectsCount.put(stat, (byte)(buffEffectsCount.get(stat) - 1));
}
} finally { } finally {
chrLock.unlock(); chrLock.unlock();
} }
@@ -2755,7 +2757,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
for (Entry<MapleBuffStat, MapleBuffStatValueHolder> stat : stats.entrySet()) { for (Entry<MapleBuffStat, MapleBuffStatValueHolder> stat : stats.entrySet()) {
int sourceid = stat.getValue().effect.getBuffSourceId(); int sourceid = stat.getValue().effect.getBuffSourceId();
if(buffEffects.get(sourceid) == null) { if(!buffEffects.containsKey(sourceid)) {
buffExpires.remove(sourceid); buffExpires.remove(sourceid);
} }
@@ -6537,10 +6539,15 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
} }
public void addMerchantMesos(int add) { public void addMerchantMesos(int add) {
int newAmount;
try { try {
newAmount = (int)Math.min((long)merchantmeso + add, Integer.MAX_VALUE);
System.out.println("adding" + add + " now" + newAmount);
Connection con = DatabaseConnection.getConnection(); Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET MerchantMesos = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) { 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.setInt(2, id);
ps.executeUpdate(); ps.executeUpdate();
} }
@@ -6550,7 +6557,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
e.printStackTrace(); e.printStackTrace();
return; return;
} }
merchantmeso += add; merchantmeso = newAmount;
} }
public void setMerchantMeso(int set) { 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 { 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<>(); List<Pair<Item, MapleInventoryType>> items = new ArrayList<>();
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -135,7 +149,7 @@ public enum ItemFactory {
return items; 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 ps = null;
PreparedStatement pse = null; PreparedStatement pse = null;
ResultSet rs = null; ResultSet rs = null;
@@ -227,4 +241,212 @@ public enum ItemFactory {
lock.unlock(); 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 = true;
public static final boolean USE_ITEM_SORT_BY_NAME = false; //Item sorting based on name rather than id. 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_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_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_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_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_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_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_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. 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.
+13 -2
View File
@@ -39,6 +39,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
import net.MapleServerHandler; import net.MapleServerHandler;
import net.mina.MapleCodecFactory; import net.mina.MapleCodecFactory;
@@ -79,6 +81,7 @@ public class Server implements Runnable {
private static Server instance = null; private static Server instance = null;
private List<Pair<Integer, String>> worldRecommendedList = new LinkedList<>(); private List<Pair<Integer, String>> worldRecommendedList = new LinkedList<>();
private final Map<Integer, MapleGuild> guilds = new LinkedHashMap<>(); private final Map<Integer, MapleGuild> guilds = new LinkedHashMap<>();
private final Lock shutdownLock = new ReentrantLock();
private final PlayerBuffStorage buffStorage = new PlayerBuffStorage(); private final PlayerBuffStorage buffStorage = new PlayerBuffStorage();
private final Map<Integer, MapleAlliance> alliances = new LinkedHashMap<>(); private final Map<Integer, MapleAlliance> alliances = new LinkedHashMap<>();
private boolean online = false; private boolean online = false;
@@ -99,7 +102,8 @@ public class Server implements Runnable {
return worldRecommendedList; return worldRecommendedList;
} }
public void removeChannel(int worldid, int channel) { /*
public void removeChannel(int worldid, int channel) { //lol don't!
channels.remove(channel); channels.remove(channel);
World world = worlds.get(worldid); World world = worlds.get(worldid);
@@ -107,6 +111,7 @@ public class Server implements Runnable {
world.removeChannel(channel); world.removeChannel(channel);
} }
} }
*/
public Channel getChannel(int world, int channel) { public Channel getChannel(int world, int channel) {
return worlds.get(world).getChannel(channel); return worlds.get(world).getChannel(channel);
@@ -702,10 +707,13 @@ public class Server implements Runnable {
return worlds; 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() { return new Runnable() {
@Override @Override
public void run() { public void run() {
shutdownLock.lock();
try {
System.out.println((restart ? "Restarting" : "Shutting down") + " the server!\r\n"); System.out.println((restart ? "Restarting" : "Shutting down") + " the server!\r\n");
if (getWorlds() == null) return;//already shutdown if (getWorlds() == null) return;//already shutdown
for (World w : getWorlds()) { for (World w : getWorlds()) {
@@ -766,6 +774,9 @@ public class Server implements Runnable {
System.gc(); System.gc();
getInstance().run();//DID I DO EVERYTHING?! D: getInstance().run();//DID I DO EVERYTHING?! D:
} }
} finally {
shutdownLock.unlock();
}
} }
}; };
} }
@@ -26,6 +26,7 @@ import client.MapleCharacter;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import client.MapleClient; import client.MapleClient;
import constants.ServerConstants;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import server.maps.MapleMapObjectType; import server.maps.MapleMapObjectType;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
@@ -38,7 +39,7 @@ import tools.data.input.SeekableLittleEndianAccessor;
public final class HiredMerchantRequest extends AbstractMaplePacketHandler { public final class HiredMerchantRequest extends AbstractMaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
MapleCharacter chr = c.getPlayer(); 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()) { if (!chr.hasMerchant()) {
try { try {
if (ItemFactory.MERCHANT.loadItems(chr.getId(), false).isEmpty() && chr.getMerchantMeso() == 0) { 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.MapleInventory;
import client.inventory.MapleInventoryType; import client.inventory.MapleInventoryType;
import constants.ItemConstants; import constants.ItemConstants;
import constants.ServerConstants;
import java.util.Arrays; import java.util.Arrays;
@@ -234,7 +235,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
} else if (!merchant.isOpen()) { } else if (!merchant.isOpen()) {
chr.dropMessage(1, "This shop is in maintenance, please come by later."); chr.dropMessage(1, "This shop is in maintenance, please come by later.");
return; 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."); chr.dropMessage(1, "This shop has reached it's maximum capacity, please come by later.");
return; return;
} else { } else {
@@ -259,10 +260,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
game.chat(c, slea.readMapleAsciiString()); game.chat(c, slea.readMapleAsciiString());
} }
} else if (merchant != null) { } else if (merchant != null) {
String message = chr.getName() + " : " + slea.readMapleAsciiString(); merchant.sendMessage(c.getPlayer(), slea.readMapleAsciiString());
byte slot = (byte) (merchant.getVisitorSlot(c.getPlayer()) + 1);
merchant.getMessages().add(new Pair<>(message, slot));
merchant.broadcastToVisitors(MaplePacketCreator.hiredMerchantChat(message, slot));
} }
} else if (mode == Action.EXIT.getCode()) { } else if (mode == Action.EXIT.getCode()) {
if (chr.getTrade() != null) { if (chr.getTrade() != null) {
@@ -425,6 +423,9 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
Item sellItem = ivItem.copy(); Item sellItem = ivItem.copy();
if (chr.getItemQuantity(ivItem.getItemId(), false) < perBundle * bundles) { if (chr.getItemQuantity(ivItem.getItemId(), false) < perBundle * bundles) {
return; 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); sellItem.setQuantity(perBundle);
MaplePlayerShopItem item = new MaplePlayerShopItem(sellItem, bundles, price); MaplePlayerShopItem item = new MaplePlayerShopItem(sellItem, bundles, price);
@@ -521,7 +522,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
shop.broadcast(MaplePacketCreator.getPlayerShopItemUpdate(shop)); shop.broadcast(MaplePacketCreator.getPlayerShopItemUpdate(shop));
} else if (merchant != null) { } else if (merchant != null) {
merchant.buy(c, item, quantity); 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()) { } else if (mode == Action.TAKE_ITEM_BACK.getCode()) {
HiredMerchant merchant = chr.getHiredMerchant(); HiredMerchant merchant = chr.getHiredMerchant();
@@ -555,7 +556,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
chr.setHasMerchant(false); chr.setHasMerchant(false);
} }
if (merchant != null && merchant.isOwner(c.getPlayer())) { if (merchant != null && merchant.isOwner(c.getPlayer())) {
merchant.getMessages().clear(); merchant.clearMessages();
merchant.setOpen(true); merchant.setOpen(true);
} }
chr.setHiredMerchant(null); chr.setHiredMerchant(null);
@@ -117,10 +117,16 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
return; return;
} }
} catch(ClassCastException e) { } catch(ClassCastException e) {
//this happens due to mob on last map damaging player just before changing maps
if(ServerConstants.USE_DEBUG) {
e.printStackTrace(); e.printStackTrace();
FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Attacker is not a mob-type, rather is a " + map.getMapObject(oid).getClass().getName() + " entity."); FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, "Attacker is not a mob-type, rather is a " + map.getMapObject(oid).getClass().getName() + " entity.");
} }
return;
}
direction = slea.readByte(); direction = slea.readByte();
} }
if (damagefrom != -1 && damagefrom != -2 && attacker != null) { if (damagefrom != -1 && damagefrom != -2 && attacker != null) {
-1
View File
@@ -86,7 +86,6 @@ public class World {
private long mountUpdate; private long mountUpdate;
private Map<HiredMerchant, Byte> activeMerchants = new LinkedHashMap<>(); private Map<HiredMerchant, Byte> activeMerchants = new LinkedHashMap<>();
private ScheduledFuture<?> MerchantsSchedule;
private long merchantUpdate; private long merchantUpdate;
private ScheduledFuture<?> charactersSchedule; private ScheduledFuture<?> charactersSchedule;
+96 -16
View File
@@ -73,7 +73,13 @@ public class HiredMerchant extends AbstractMapleMapObject {
this.map = owner.getMap(); 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) { for (MapleCharacter visitor : visitors) {
if (visitor != null) { if (visitor != null) {
visitor.getClient().announce(packet); visitor.getClient().announce(packet);
@@ -82,14 +88,17 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
public void addVisitor(MapleCharacter visitor) { public void addVisitor(MapleCharacter visitor) {
synchronized(visitors) {
int i = this.getFreeSlot(); int i = this.getFreeSlot();
if (i > -1) { if (i > -1) {
visitors[i] = visitor; visitors[i] = visitor;
broadcastToVisitors(MaplePacketCreator.hiredMerchantVisitorAdd(visitor, i + 1)); broadcastToVisitors(MaplePacketCreator.hiredMerchantVisitorAdd(visitor, i + 1));
} }
} }
}
public void removeVisitor(MapleCharacter visitor) { public void removeVisitor(MapleCharacter visitor) {
synchronized(visitors) {
int slot = getVisitorSlot(visitor); int slot = getVisitorSlot(visitor);
if (slot < 0){ //Not found if (slot < 0){ //Not found
return; return;
@@ -101,8 +110,15 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
} }
} }
}
public int getVisitorSlot(MapleCharacter visitor) { public int getVisitorSlotThreadsafe(MapleCharacter visitor) {
synchronized(visitors) {
return getVisitorSlot(visitor);
}
}
private int getVisitorSlot(MapleCharacter visitor) {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (visitors[i] != null && visitors[i].getId() == visitor.getId()){ if (visitors[i] != null && visitors[i].getId() == visitor.getId()){
return i; return i;
@@ -112,6 +128,7 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
public void removeAllVisitors(String message) { public void removeAllVisitors(String message) {
synchronized(visitors) {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
if (visitors[i] != null) { if (visitors[i] != null) {
visitors[i].setHiredMerchant(null); visitors[i].setHiredMerchant(null);
@@ -123,10 +140,12 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
} }
} }
}
public void buy(MapleClient c, int item, short quantity) { public void buy(MapleClient c, int item, short quantity) {
MaplePlayerShopItem pItem = items.get(item);
synchronized (items) { synchronized (items) {
MaplePlayerShopItem pItem = items.get(item);
Item newItem = pItem.getItem().copy(); Item newItem = pItem.getItem().copy();
newItem.setQuantity((short) ((pItem.getItem().getQuantity() * quantity))); newItem.setQuantity((short) ((pItem.getItem().getQuantity() * quantity)));
if ((newItem.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) { if ((newItem.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) {
@@ -142,11 +161,15 @@ public class HiredMerchant extends AbstractMapleMapObject {
c.announce(MaplePacketCreator.enableActions()); c.announce(MaplePacketCreator.enableActions());
return; return;
} }
int price = pItem.getPrice() * quantity; int price = (int)Math.min((long)pItem.getPrice() * quantity, Integer.MAX_VALUE);
if (c.getPlayer().getMeso() >= price) { if (c.getPlayer().getMeso() >= price) {
if (MapleInventoryManipulator.addFromDrop(c, newItem, true)) { if (MapleInventoryManipulator.addFromDrop(c, newItem, true)) {
c.getPlayer().gainMeso(-price, false); c.getPlayer().gainMeso(-price, false);
synchronized (sold) {
sold.add(new SoldItem(c.getPlayer().getName(), pItem.getItem().getItemId(), quantity, price)); sold.add(new SoldItem(c.getPlayer().getName(), pItem.getItem().getItemId(), quantity, price));
}
pItem.setBundles((short) (pItem.getBundles() - quantity)); pItem.setBundles((short) (pItem.getBundles() - quantity));
if (pItem.getBundles() < 1) { if (pItem.getBundles() < 1) {
pItem.setDoesExist(false); pItem.setDoesExist(false);
@@ -187,7 +210,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
try { try {
saveItems(true); saveItems(true);
synchronized (items) {
items.clear(); items.clear();
}
} catch (SQLException ex) { } catch (SQLException ex) {
ex.printStackTrace(); ex.printStackTrace();
} }
@@ -234,16 +259,20 @@ public class HiredMerchant extends AbstractMapleMapObject {
con.close(); con.close();
} }
if (check(c.getPlayer(), getItems()) && !timeout) { List<MaplePlayerShopItem> copyItems = getItems();
for (MaplePlayerShopItem mpsi : getItems()) { if (check(c.getPlayer(), copyItems) && !timeout) {
for (MaplePlayerShopItem mpsi : copyItems) {
if (mpsi.isExist() && (mpsi.getItem().getType() == MapleInventoryType.EQUIP.getType())) { if (mpsi.isExist() && (mpsi.getItem().getType() == MapleInventoryType.EQUIP.getType())) {
MapleInventoryManipulator.addFromDrop(c, mpsi.getItem(), false); MapleInventoryManipulator.addFromDrop(c, mpsi.getItem(), false);
} else if (mpsi.isExist()) { } else if (mpsi.isExist()) {
MapleInventoryManipulator.addById(c, mpsi.getItem().getItemId(), (short) (mpsi.getBundles() * mpsi.getItem().getQuantity()), null, -1, mpsi.getItem().getFlag(), mpsi.getItem().getExpiration()); MapleInventoryManipulator.addById(c, mpsi.getItem().getItemId(), (short) (mpsi.getBundles() * mpsi.getItem().getQuantity()), null, -1, mpsi.getItem().getFlag(), mpsi.getItem().getExpiration());
} }
} }
synchronized (items) {
items.clear(); items.clear();
} }
}
try { try {
this.saveItems(timeout); this.saveItems(timeout);
@@ -251,7 +280,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
e.printStackTrace(); e.printStackTrace();
} }
synchronized (items) {
items.clear(); items.clear();
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -264,8 +295,10 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
public void clearItems() { public void clearItems() {
synchronized (items) {
items.clear(); items.clear();
} }
}
public int getOwnerId() { public int getOwnerId() {
return ownerId; return ownerId;
@@ -276,15 +309,25 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
public MapleCharacter[] getVisitors() { 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() { public List<MaplePlayerShopItem> getItems() {
synchronized (items) {
return Collections.unmodifiableList(items); return Collections.unmodifiableList(items);
} }
}
public void addItem(MaplePlayerShopItem item) { public void addItem(MaplePlayerShopItem item) {
synchronized (items) {
items.add(item); items.add(item);
}
try { try {
this.saveItems(false); this.saveItems(false);
} catch (SQLException ex) { } catch (SQLException ex) {
@@ -293,7 +336,10 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
public void removeFromSlot(int slot) { public void removeFromSlot(int slot) {
synchronized (items) {
items.remove(slot); items.remove(slot);
}
try { try {
this.saveItems(false); this.saveItems(false);
} catch (SQLException ex) { } 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++) { for (int i = 0; i < 3; i++) {
if (visitors[i] == null) { if (visitors[i] == null) {
return i; return i;
@@ -331,23 +382,37 @@ public class HiredMerchant extends AbstractMapleMapObject {
return chr.getId() == ownerId; 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 { public void saveItems(boolean shutdown) throws SQLException {
List<Pair<Item, MapleInventoryType>> itemsWithType = new ArrayList<>(); List<Pair<Item, MapleInventoryType>> itemsWithType = new ArrayList<>();
List<Short> bundles = new ArrayList<>();
for (MaplePlayerShopItem pItems : items) { for (MaplePlayerShopItem pItems : getItems()) {
Item newItem = pItems.getItem(); Item newItem = pItems.getItem();
if (shutdown) { short newBundle = pItems.getBundles();
newItem.setQuantity((short) (pItems.getItem().getQuantity() * pItems.getBundles()));
if (shutdown) { //is "shutdown" really necessary?
newItem.setQuantity((short) (pItems.getItem().getQuantity()));
} else { } 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()))); itemsWithType.add(new Pair<>(newItem, MapleInventoryType.getByType(newItem.getType())));
bundles.add(newBundle);
} }
} }
Connection con = DatabaseConnection.getConnection(); Connection con = DatabaseConnection.getConnection();
ItemFactory.MERCHANT.saveItems(itemsWithType, this.ownerId, con); ItemFactory.MERCHANT.saveItems(itemsWithType, bundles, this.ownerId, con);
con.close(); con.close();
} }
@@ -405,8 +470,21 @@ public class HiredMerchant extends AbstractMapleMapObject {
return (int) ((System.currentTimeMillis() - start) / 1000); return (int) ((System.currentTimeMillis() - start) / 1000);
} }
public void clearMessages() {
synchronized (messages) {
messages.clear();
}
}
public List<Pair<String, Byte>> getMessages() { 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() { public int getMapId() {
@@ -414,7 +492,9 @@ public class HiredMerchant extends AbstractMapleMapObject {
} }
public List<SoldItem> getSold() { public List<SoldItem> getSold() {
return sold; synchronized (sold) {
return Collections.unmodifiableList(sold);
}
} }
public int getMesos() { public int getMesos() {
+8 -6
View File
@@ -4959,7 +4959,7 @@ public class MaplePacketCreator {
mplew.writeInt(item.getBundles()); mplew.writeInt(item.getBundles());
mplew.writeInt(item.getPrice()); mplew.writeInt(item.getPrice());
mplew.writeInt(hm.getOwnerId()); 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()); MapleCharacter chr = c.getChannelServer().getPlayerStorage().getCharacterById(hm.getOwnerId());
if ((chr != null) && (c.getChannel() == hm.getChannel())) { if ((chr != null) && (c.getChannel() == hm.getChannel())) {
mplew.write(1); mplew.write(1);
@@ -5004,7 +5004,7 @@ public class MaplePacketCreator {
mplew.write(PlayerInteractionHandler.Action.ROOM.getCode()); mplew.write(PlayerInteractionHandler.Action.ROOM.getCode());
mplew.write(0x05); mplew.write(0x05);
mplew.write(0x04); mplew.write(0x04);
mplew.writeShort(hm.getVisitorSlot(chr) + 1); mplew.writeShort(hm.getVisitorSlotThreadsafe(chr) + 1);
mplew.writeInt(hm.getItemId()); mplew.writeInt(hm.getItemId());
mplew.writeMapleAsciiString("Hired Merchant"); mplew.writeMapleAsciiString("Hired Merchant");
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
@@ -5016,10 +5016,12 @@ public class MaplePacketCreator {
} }
mplew.write(-1); mplew.write(-1);
if (hm.isOwner(chr)) { if (hm.isOwner(chr)) {
mplew.writeShort(hm.getMessages().size()); List<Pair<String, Byte>> msgList = hm.getMessages();
for (int i = 0; i < hm.getMessages().size(); i++) {
mplew.writeMapleAsciiString(hm.getMessages().get(i).getLeft()); mplew.writeShort(msgList.size());
mplew.write(hm.getMessages().get(i).getRight()); for (int i = 0; i < msgList.size(); i++) {
mplew.writeMapleAsciiString(msgList.get(i).getLeft());
mplew.write(msgList.get(i).getRight());
} }
} else { } else {
mplew.writeShort(0); mplew.writeShort(0);