Mob aggro overhaul + STR-DEX autoassign patch + Guild & Pl. Store fix

Reviewed Monster Magnet skill effect on mobs, now properly showing up to other players.
Reworked concurrency protection on login handler, now properly synchronizing requests for the same accountId.
Reviewed an expedition issue with attempting to send packets on loggedoff players. Expeditions no longer holds a character object list of its own, rather finding players through their id on the world storage.
Added stat requirement definition as first message on 1st job NPCs.
Fixed an issue with area triggered NPC conversations not getting properly disposed.
Reviewed "launch.bat", internally referencing Java7 engine. Assuming it has been installed in the default directory on the system, it's no longer necessary to set precedence on PATH for it.
Fixed need for "reapproval from the 3rd job instructors" to attempt Zakum once more.
Reviewed goto command. Non-GM's no longer has access to area maps, only towns.
Implemented a new server flag for enforcing base rates (server rate: 1x) on players level 10 or lower.
Fixed player guild tooltips not being properly marshalled to others on the map, at the event of several guild actions.
Reviewed event system allowing creation of same-name events, potentially leading to null EIM problems.
Refactored some locks on MapleCharacter, potentially solving a deadlock case within expiring/forfeiting quest methods.
Implemented a major overhaul on mob aggro system, now updating player aggro in real-time based on their latest DPS. Properly refactored and encapsulated aggro mechanics.
Fixed NPE on disposing events with alive mobs.
Added server-side birthday check handling when opening player shops or merchants having cash items in store.
Fixed player shop tooltip not finishing properly when shop owner closes store with visitors still in there.
Fixed forceChangeMap method not warping players to the designated event map (rather sending them to the starting event map).
Reviewed "summon" command, now using forceChangeMap, also changing channels if needed.
Reworked HeavenMS autoassigner: STR-based classes now ups a bit more DEX than before, since its a vital attribute for accuracy on their actions.
Added meso ceil check on player transactions. Trades now gets suspended if the max amount is reached.
Implemented server-side item limit check on player shops and merchants.
Fixed an exploit with merchants, on where players would be able to save the selling item on DB before taking from inventory.
This commit is contained in:
ronancpl
2019-01-31 00:49:15 -02:00
parent 98be29b088
commit efc8884e19
101 changed files with 17229 additions and 16455 deletions
+87 -61
View File
@@ -273,7 +273,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
private ScheduledFuture<?> pendantOfSpirit = null; //1122017
private Lock chrLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_CHR, true);
private Lock evtLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_EVT, true);
private Lock petLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_PET, true); // for meso & quest tasks as well
private Lock petLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_PET, true);
private Lock prtLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_PRT);
private Lock cpnLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_CPN);
private Map<Integer, Set<Integer>> excluded = new LinkedHashMap<>();
@@ -852,17 +852,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
List<Pair<MapleBuffStat, Integer>> ldsstat = Collections.singletonList(new Pair<MapleBuffStat, Integer>(MapleBuffStat.DARKSIGHT, 0));
getMap().broadcastGMMessage(this, MaplePacketCreator.giveForeignBuff(id, ldsstat), false);
for (MapleMonster mon : this.getControlledMonsters()) {
mon.lockMonster();
try {
mon.setController(null);
mon.setControllerHasAggro(false);
mon.setControllerKnowsAboutAggro(false);
mon.getMap().updateMonsterController(mon);
} finally {
mon.unlockMonster();
}
}
this.releaseControlledMonsters();
}
announce(MaplePacketCreator.enableActions());
}
@@ -1356,7 +1346,8 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
mapEim.registerPlayer(this);
// thanks Thora for finding an issue with players not being actually warped into the target event map (rather sent to the event starting map)
mapEim.registerPlayer(this, false);
}
MapleMap to = target; // warps directly to the target intead of the target's map id, this allows GMs to patrol players inside instances.
@@ -1708,29 +1699,48 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
public void checkMonsterAggro(MapleMonster monster) {
monster.lockMonster();
try {
if (!monster.isControllerHasAggro()) {
if (monster.getController() == this) {
monster.setControllerHasAggro(true);
} else {
monster.switchController(this, true);
}
public void controlMonster(MapleMonster monster) {
if (cpnLock.tryLock()) {
try {
controlled.add(monster);
} finally {
cpnLock.unlock();
}
} finally {
monster.unlockMonster();
}
}
public void controlMonster(MapleMonster monster, boolean aggro) {
monster.lockMonster();
public void stopControllingMonster(MapleMonster monster) {
if (cpnLock.tryLock()) {
try {
controlled.remove(monster);
} finally {
cpnLock.unlock();
}
}
}
public int getNumControlledMonsters() {
cpnLock.lock();
try {
monster.setController(this);
controlled.add(monster);
client.announce(MaplePacketCreator.controlMonster(monster, false, aggro));
return controlled.size();
} finally {
monster.unlockMonster();
cpnLock.unlock();
}
}
public void releaseControlledMonsters() {
Collection<MapleMonster> controlledMonsters;
cpnLock.lock();
try {
controlledMonsters = new ArrayList<>(controlled);
controlled.clear();
} finally {
cpnLock.unlock();
}
for (MapleMonster monster : controlledMonsters) {
monster.aggroRedirectController();
}
}
@@ -2967,6 +2977,11 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
public boolean canHoldMeso(int gain) { // thanks lucasziron found pointing out a need to check space availability for mesos on player transactions
long nextMeso = (long) meso.get() + gain;
return nextMeso <= Integer.MAX_VALUE;
}
public void gainMeso(int gain) {
gainMeso(gain, true, false, true);
}
@@ -4124,11 +4139,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
return Collections.unmodifiableList(ret);
}
}
public Collection<MapleMonster> getControlledMonsters() {
return Collections.unmodifiableCollection(controlled);
}
public List<MapleRing> getCrushRings() {
Collections.sort(crushRings);
return crushRings;
@@ -4343,7 +4354,15 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
return gachaexp.get();
}
public boolean hasNoviceExpRate() {
return ServerConstants.USE_ENFORCE_NOVICE_EXPRATE && isBeginnerJob() && level < 11;
}
public int getExpRate() {
if (hasNoviceExpRate()) { // base exp rate 1x for early levels idea thanks to Vcoc
return 1;
}
return expRate;
}
@@ -4383,7 +4402,17 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
public int getRawMesoRate() {
return mesoRate / (mesoCoupon * getWorldServer().getMesoRate());
}
public int getQuestExpRate() {
World w = getWorldServer();
return w.getExpRate() * w.getQuestRate();
}
public int getQuestMesoRate() {
World w = getWorldServer();
return w.getMesoRate() * w.getQuestRate();
}
public int getFace() {
return face;
}
@@ -4829,10 +4858,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
public int getNumControlledMonsters() {
return controlled.size();
}
public MapleParty getParty() {
prtLock.lock();
try {
@@ -5635,9 +5660,9 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
public boolean isGuildLeader() { // true on guild master or jr. master
return guildid > 0 && guildRank < 3;
}
public void leaveMap() {
controlled.clear();
releaseControlledMonsters();
visibleMapObjects.clear();
setChair(0);
if (hpDecreaseTask != null) {
@@ -7772,17 +7797,22 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
throw new RuntimeException("Character not in database (" + id + ")");
}
List<MaplePet> petList = new LinkedList<>();
petLock.lock();
try {
for (int i = 0; i < 3; i++) {
if (pets[i] != null) {
pets[i].saveToDb();
petList.add(pets[i]);
}
}
} finally {
petLock.unlock();
}
for (MaplePet pet : petList) {
pet.saveToDb();
}
for(Entry<Integer, Set<Integer>> es: getExcluded().entrySet()) { // this set is already protected
try (PreparedStatement ps2 = con.prepareStatement("DELETE FROM petignores WHERE petid=?")) {
ps2.setInt(1, es.getKey());
@@ -8912,11 +8942,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}, duration);
}
public void stopControllingMonster(MapleMonster monster) {
controlled.remove(monster);
}
public void unequipAllPets() {
for (int i = 0; i < 3; i++) {
MaplePet pet = getPet(i);
@@ -9046,19 +9072,19 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
public void cancelQuestExpirationTask() {
petLock.lock();
evtLock.lock();
try {
if (questExpireTask != null) {
questExpireTask.cancel(false);
questExpireTask = null;
}
} finally {
petLock.unlock();
evtLock.unlock();
}
}
public void forfeitExpirableQuests() {
petLock.lock();
evtLock.lock();
try {
for(MapleQuest quest : questExpirations.keySet()) {
quest.forfeit(this);
@@ -9066,12 +9092,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
questExpirations.clear();
} finally {
petLock.unlock();
evtLock.unlock();
}
}
public void questExpirationTask() {
petLock.lock();
evtLock.lock();
try {
if(!questExpirations.isEmpty()) {
if(questExpireTask == null) {
@@ -9084,12 +9110,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
} finally {
petLock.unlock();
evtLock.unlock();
}
}
private void runQuestExpireTask() {
petLock.lock();
evtLock.lock();
try {
long timeNow = Server.getInstance().getCurrentTime();
List<MapleQuest> expireList = new LinkedList<>();
@@ -9110,12 +9136,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
}
}
} finally {
petLock.unlock();
evtLock.unlock();
}
}
private void registerQuestExpire(MapleQuest quest, long time) {
petLock.lock();
evtLock.lock();
try {
if(questExpireTask == null) {
questExpireTask = TimerManager.getInstance().register(new Runnable() {
@@ -9128,7 +9154,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
questExpirations.put(quest, Server.getInstance().getCurrentTime() + time);
} finally {
petLock.unlock();
evtLock.unlock();
}
}
@@ -9671,7 +9697,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
if (pendantOfSpirit != null) { pendantOfSpirit.cancel(true); }
pendantOfSpirit = null;
petLock.lock();
evtLock.lock();
try {
if (questExpireTask != null) {
questExpireTask.cancel(false);
@@ -9681,7 +9707,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
questExpirations = null;
}
} finally {
petLock.unlock();
evtLock.unlock();
}
if (maplemount != null) {
+7 -7
View File
@@ -175,22 +175,22 @@ public class MapleClient {
return chars;
}
public List<String> loadCharacterNames(int serverId) {
public List<String> loadCharacterNames(int worldId) {
List<String> chars = new ArrayList<>(15);
for (CharNameAndId cni : loadCharactersInternal(serverId)) {
for (CharNameAndId cni : loadCharactersInternal(worldId)) {
chars.add(cni.name);
}
return chars;
}
private List<CharNameAndId> loadCharactersInternal(int serverId) {
private List<CharNameAndId> loadCharactersInternal(int worldId) {
PreparedStatement ps;
List<CharNameAndId> chars = new ArrayList<>(15);
try {
Connection con = DatabaseConnection.getConnection();
ps = con.prepareStatement("SELECT id, name FROM characters WHERE accountid = ? AND world = ?");
ps.setInt(1, this.getAccID());
ps.setInt(2, serverId);
ps.setInt(2, worldId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
chars.add(new CharNameAndId(rs.getString("name"), rs.getInt("id")));
@@ -882,7 +882,7 @@ public class MapleClient {
}
public final void disconnect(final boolean shutdown, final boolean cashshop) {
if (isDisconnecting()) {
if (canDisconnect()) {
ThreadManager.getInstance().newTask(new Runnable() {
@Override
public void run() {
@@ -893,12 +893,12 @@ public class MapleClient {
}
public final void forceDisconnect() {
if (isDisconnecting()) {
if (canDisconnect()) {
disconnectInternal(true, false);
}
}
private synchronized boolean isDisconnecting() {
private synchronized boolean canDisconnect() {
if (disconnecting) {
return false;
}
@@ -39,7 +39,7 @@ public class RatesCommand extends Command {
// travel rates not applicable since it's intrinsically a server/environment rate rather than a character rate
String showMsg_ = "#eCHARACTER RATES#n" + "\r\n\r\n";
showMsg_ += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + "\r\n";
showMsg_ += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + (player.hasNoviceExpRate() ? " - novice rate" : "") + "\r\n";
showMsg_ += "MESO Rate: #e#b" + player.getMesoRate() + "x#k#n" + "\r\n";
showMsg_ += "DROP Rate: #e#b" + player.getDropRate() + "x#k#n" + "\r\n";
showMsg_ += "BOSS DROP Rate: #e#b" + player.getBossDropRate() + "x#k#n" + "\r\n";
@@ -40,7 +40,7 @@ public class ShowRatesCommand extends Command {
showMsg += "World EXP Rate: #k" + c.getWorldServer().getExpRate() + "x#k" + "\r\n";
showMsg += "Player EXP Rate: #k" + player.getRawExpRate() + "x#k" + "\r\n";
if(player.getCouponExpRate() != 1) showMsg += "Coupon EXP Rate: #k" + player.getCouponExpRate() + "x#k" + "\r\n";
showMsg += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + "\r\n";
showMsg += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + (player.hasNoviceExpRate() ? " - novice rate" : "") + "\r\n";
showMsg += "\r\n" + "#eMESO RATE#n" + "\r\n";
showMsg += "World MESO Rate: #k" + c.getWorldServer().getMesoRate() + "x#k" + "\r\n";
@@ -66,7 +66,7 @@ public class ShowRatesCommand extends Command {
}
showMsg += "\r\n";
showMsg += "World TRAVEL Rate: #e#b" + c.getWorldServer().getTravelRate() + "x#k#n" + "\r\nServer\r\nPlayer";
showMsg += "World TRAVEL Rate: #e#b" + c.getWorldServer().getTravelRate() + "x#k#n" + "\r\n";
player.showHint(showMsg, 300);
}
@@ -41,8 +41,6 @@ public class GotoCommand extends Command {
@Override
public void execute(MapleClient c, String[] params) {
final HashMap<String, Integer> gotomaps = GameConstants.GOTO_MAPS;
MapleCharacter player = c.getPlayer();
if (params.length < 1){
player.yellowMessage("Syntax: @goto <map name>");
@@ -54,6 +52,13 @@ public class GotoCommand extends Command {
return;
}
HashMap<String, Integer> gotomaps;
if (player.isGM()) {
gotomaps = new HashMap<>(GameConstants.GOTO_AREAS); // distinct map registry for GM/users suggested thanks to Vcoc
} else {
gotomaps = new HashMap<>(GameConstants.GOTO_TOWNS);
}
if (gotomaps.containsKey(params[0])) {
MapleMap target = c.getChannelServer().getMapFactory().getMap(gotomaps.get(params[0]));
@@ -26,6 +26,7 @@ package client.command.commands.gm2;
import client.command.Command;
import client.MapleClient;
import client.MapleCharacter;
import server.maps.MapleMap;
import net.server.Server;
import net.server.channel.Channel;
@@ -54,33 +55,26 @@ public class SummonCommand extends Command {
}
}
if (victim != null) {
boolean changingEvent = true;
if (victim.getEventInstance() != null) {
if (player.getEventInstance() != null && victim.getEventInstance().getLeaderId() == player.getEventInstance().getLeaderId()) {
changingEvent = false;
} else {
victim.getEventInstance().unregisterPlayer(victim);
}
if (!victim.isLoggedinWorld()) {
player.dropMessage(6, "Player currently not logged in or unreachable.");
return;
}
//Attempt to join the warpers instance.
if (player.getEventInstance() != null && changingEvent) {
if (player.getClient().getChannel() == victim.getClient().getChannel()) {
player.getEventInstance().registerPlayer(victim);
victim.saveLocationOnWarp();
victim.changeMap(player.getEventInstance().getMapInstance(player.getMapId()), player.getMap().findClosestPortal(player.getPosition()));
} else {
player.dropMessage("Target isn't on your channel, not able to warp into event instance.");
}
} else {//If victim isn't in an event instance or is in the same event instance as the one the caller is, just warp them.
victim.saveLocationOnWarp();
victim.changeMap(player.getMapId(), player.getMap().findClosestPortal(player.getPosition()));
}
if (player.getClient().getChannel() != victim.getClient().getChannel()) {//And then change channel if needed.
victim.dropMessage("Changing channel, please wait a moment.");
victim.getClient().changeChannel(player.getClient().getChannel());
}
try {
for (int i = 0; i < 7; i++) { // poll for a while until the player reconnects
if (victim.isLoggedinWorld()) break;
Thread.sleep(1777);
}
} catch (InterruptedException e) {}
MapleMap map = player.getMap();
victim.saveLocationOnWarp();
victim.forceChangeMap(map, map.findClosestPortal(player.getPosition()));
} else {
player.dropMessage(6, "Unknown player.");
}
@@ -29,7 +29,6 @@ import client.command.Command;
import server.maps.MapleMap;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
public class WarpAreaCommand extends Command {
@@ -54,7 +53,7 @@ public class WarpAreaCommand extends Command {
Point pos = player.getPosition();
Collection<MapleCharacter> characters = new ArrayList<>(player.getMap().getCharacters());
Collection<MapleCharacter> characters = player.getMap().getAllPlayers();
for (MapleCharacter victim : characters) {
if (victim.getPosition().distanceSq(pos) <= 50000) {
@@ -28,7 +28,6 @@ import client.MapleClient;
import client.command.Command;
import server.maps.MapleMap;
import java.util.ArrayList;
import java.util.Collection;
public class WarpMapCommand extends Command {
@@ -51,7 +50,7 @@ public class WarpMapCommand extends Command {
return;
}
Collection<MapleCharacter> characters = new ArrayList<>(player.getMap().getCharacters());
Collection<MapleCharacter> characters = player.getMap().getAllPlayers();
for (MapleCharacter victim : characters) {
victim.saveLocationOnWarp();
@@ -30,6 +30,8 @@ import net.server.Server;
import net.server.channel.Channel;
import server.expeditions.MapleExpedition;
import java.util.Map.Entry;
public class ExpedsCommand extends Command {
{
setDescription("");
@@ -53,11 +55,11 @@ public class ExpedsCommand extends Command {
player.yellowMessage(">> Size: " + exped.getMembers().size());
player.yellowMessage(">> Leader: " + exped.getLeader().getName());
int memId = 2;
for (MapleCharacter member : exped.getMembers()) {
if (exped.isLeader(member)) {
for (Entry<Integer, String> e : exped.getMembers().entrySet()) {
if (exped.isLeader(e.getKey())) {
continue;
}
player.yellowMessage(">>> Member " + memId + ": " + member.getName());
player.yellowMessage(">>> Member " + memId + ": " + e.getValue());
memId++;
}
}
@@ -28,7 +28,6 @@ import client.MapleClient;
import client.MapleCharacter;
import server.maps.MapleMap;
import java.util.ArrayList;
import java.util.Collection;
public class ReloadMapCommand extends Command {
@@ -42,7 +41,7 @@ public class ReloadMapCommand extends Command {
MapleMap newMap = c.getChannelServer().getMapFactory().resetMap(player.getMapId());
int callerid = c.getPlayer().getId();
Collection<MapleCharacter> characters = new ArrayList<>(player.getMap().getCharacters());
Collection<MapleCharacter> characters = player.getMap().getAllPlayers();
for (MapleCharacter chr : characters) {
chr.saveLocationOnWarp();
@@ -23,12 +23,11 @@
*/
package client.command.commands.gm3;
import client.command.Command;
import client.MapleClient;
import client.MapleCharacter;
import client.command.Command;
import client.MapleClient;
import client.MapleCharacter;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
public class WarpSnowBallCommand extends Command {
{
@@ -38,7 +37,7 @@ public class WarpSnowBallCommand extends Command {
@Override
public void execute(MapleClient c, String[] params) {
MapleCharacter player = c.getPlayer();
List<MapleCharacter> chars = new ArrayList<>(player.getMap().getCharacters());
List<MapleCharacter> chars = player.getMap().getAllPlayers();
for (MapleCharacter chr : chars) {
chr.saveLocationOnWarp();
chr.changeMap(109060000, chr.getTeam());
@@ -72,7 +72,7 @@ public class DebugCommand extends Command {
for (MapleMapObject monstermo : monsters) {
MapleMonster monster = (MapleMonster) monstermo;
MapleCharacter controller = monster.getController();
player.message("Monster ID: " + monster.getId() + " Aggro target: " + ((controller != null) ? controller.getName() : "<none>"));
player.message("Monster ID: " + monster.getId() + " Aggro target: " + ((controller != null) ? controller.getName() + " Has aggro: " + monster.isControllerHasAggro() + " Knowns aggro: " + monster.isControllerKnowsAboutAggro() : "<none>"));
}
break;
+6 -5
View File
@@ -56,8 +56,13 @@ public class Item implements Comparable<Item> {
this.id = id;
this.position = position;
this.quantity = quantity;
if (petid > -1) { // issue with null "pet" having petid > -1 found thanks to MedicOP
this.pet = MaplePet.loadFromDb(id, position, petid);
if (this.pet == null) {
petid = -1;
}
}
this.petid = petid;
if (petid > -1) this.pet = MaplePet.loadFromDb(id, position, petid);
this.flag = 0;
this.log = new LinkedList<>();
}
@@ -121,10 +126,6 @@ public class Item implements Comparable<Item> {
public int getPetId() {
return petid;
}
public void setPetId(int id) {
this.petid = id;
}
@Override
public int compareTo(Item other) {
+63 -49
View File
@@ -134,10 +134,10 @@ public class AssignAPProcessor {
luk = scStat;
str = 0; dex = 0;
if(luk + chr.getLuk() > CAP) {
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && luk + chr.getLuk() > CAP) {
temp = luk + chr.getLuk() - CAP;
luk -= temp;
int_ += temp;
scStat -= temp;
prStat += temp;
}
primary = MapleStat.INT;
@@ -160,10 +160,10 @@ public class AssignAPProcessor {
str = scStat;
int_ = 0; luk = 0;
if(str + chr.getStr() > CAP) {
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && str + chr.getStr() > CAP) {
temp = str + chr.getStr() - CAP;
str -= temp;
dex += temp;
scStat -= temp;
prStat += temp;
}
primary = MapleStat.DEX;
@@ -186,10 +186,10 @@ public class AssignAPProcessor {
str = scStat;
int_ = 0; luk = 0;
if(str + chr.getStr() > CAP) {
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && str + chr.getStr() > CAP) {
temp = str + chr.getStr() - CAP;
str -= temp;
dex += temp;
scStat -= temp;
prStat += temp;
}
primary = MapleStat.DEX;
@@ -240,15 +240,15 @@ public class AssignAPProcessor {
str = trStat;
int_ = 0;
if(dex + chr.getDex() > CAP) {
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && dex + chr.getDex() > CAP) {
temp = dex + chr.getDex() - CAP;
dex -= temp;
luk += temp;
scStat -= temp;
prStat += temp;
}
if(str + chr.getStr() > CAP) {
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && str + chr.getStr() > CAP) {
temp = str + chr.getStr() - CAP;
str -= temp;
luk += temp;
trStat -= temp;
prStat += temp;
}
primary = MapleStat.LUK;
@@ -258,50 +258,64 @@ public class AssignAPProcessor {
break;
case BRAWLER:
CAP = 120;
scStat = chr.getLevel() - (chr.getDex() + dex - eqpDex);
if(scStat < 0) scStat = 0;
scStat = Math.min(scStat, tempAp);
if(tempAp > scStat) tempAp -= scStat;
else tempAp = 0;
prStat = tempAp;
str = prStat;
dex = scStat;
int_ = 0; luk = 0;
if(dex + chr.getDex() > CAP) {
temp = dex + chr.getDex() - CAP;
dex -= temp;
str += temp;
}
primary = MapleStat.STR;
secondary = MapleStat.DEX;
break;
default: //warrior, beginner, ...
CAP = 80;
CAP = 300;
boolean highDex = false; // thanks lucasziron & Vcoc for finding out DEX autoassigning poorly for STR-based characters
if (chr.getLevel() < 40) {
if (chr.getDex() >= (2 * chr.getLevel()) + 2) {
highDex = true;
}
} else {
if (chr.getDex() >= chr.getLevel() + 42) {
highDex = true;
}
}
// other classes will start favoring more DEX only if a level-based threshold is reached.
if(!highDex) {
scStat = 0;
if(chr.getDex() < 80) {
scStat = (2 * chr.getLevel()) - (chr.getDex() + dex - eqpDex);
if(scStat < 0) scStat = 0;
scStat = ((2 * chr.getLevel()) / 3) - (chr.getDex() + dex - eqpDex);
if(scStat < 0) scStat = 0;
scStat = Math.min(scStat, tempAp);
scStat = Math.min(80 - chr.getDex(), scStat);
scStat = Math.min(tempAp, scStat);
tempAp -= scStat;
}
if(tempAp > scStat) tempAp -= scStat;
else tempAp = 0;
temp = (chr.getLevel() + 40) - Math.max(80, scStat + chr.getDex() + dex - eqpDex);
if(temp < 0) temp = 0;
temp = Math.min(tempAp, temp);
scStat += temp;
tempAp -= temp;
} else {
scStat = 0;
if(chr.getDex() < 96) {
scStat = (int)(2.4 * chr.getLevel()) - (chr.getDex() + dex - eqpDex);
if(scStat < 0) scStat = 0;
scStat = Math.min(96 - chr.getDex(), scStat);
scStat = Math.min(tempAp, scStat);
tempAp -= scStat;
}
temp = 96 + (int)(1.2 * (chr.getLevel() - 40)) - Math.max(96, scStat + chr.getDex() + dex - eqpDex);
if(temp < 0) temp = 0;
temp = Math.min(tempAp, temp);
scStat += temp;
tempAp -= temp;
}
prStat = tempAp;
str = prStat;
dex = scStat;
int_ = 0; luk = 0;
if(dex + chr.getDex() > CAP) {
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && dex + chr.getDex() > CAP) {
temp = dex + chr.getDex() - CAP;
dex -= temp;
str += temp;
scStat -= temp;
prStat += temp;
}
primary = MapleStat.STR;
+6 -11
View File
@@ -469,6 +469,11 @@ public class DueyProcessor {
}
if (dp.getItem() != null) {
if (!c.getPlayer().canHoldMeso(dp.getMesos())) {
c.announce(MaplePacketCreator.sendDueyMSG(Actions.TOCLIENT_RECV_UNKNOWN_ERROR.getCode()));
return;
}
if (!MapleInventoryManipulator.checkSpace(c, dp.getItem().getItemId(), dp.getItem().getQuantity(), dp.getItem().getOwner())) {
int itemid = dp.getItem().getItemId();
if(MapleItemInformationProvider.getInstance().isPickupRestricted(itemid) && c.getPlayer().getInventory(ItemConstants.getInventoryType(itemid)).findById(itemid) != null) {
@@ -483,17 +488,7 @@ public class DueyProcessor {
}
}
long gainmesos;
long totalmesos = (long) dp.getMesos() + c.getPlayer().getMeso();
if (totalmesos < 0 || dp.getMesos() < 0) {
gainmesos = 0;
} else {
totalmesos = Math.min(totalmesos, Integer.MAX_VALUE);
gainmesos = totalmesos - c.getPlayer().getMeso();
}
c.getPlayer().gainMeso((int)gainmesos, false);
c.getPlayer().gainMeso(dp.getMesos(), false);
removeItemFromDB(packageid);
c.announce(MaplePacketCreator.removeItemFromDuey(false, packageid));
+1 -1
View File
@@ -47,7 +47,7 @@ import tools.Pair;
*/
public class FredrickProcessor {
private static boolean canRetrieveFromFredrick(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items) {
if (chr.getMeso() + chr.getMerchantMeso() < 0) {
if (!chr.canHoldMeso(chr.getMerchantMeso())) {
return false;
}
return MapleInventory.checkSpotsAndOwnership(chr, items);
+21 -15
View File
@@ -45,9 +45,8 @@ public class GameConstants {
return(EXP_RATE_GAIN[slot]);
}
// used by the "goto" command
public static final HashMap<String, Integer> GOTO_MAPS = new HashMap<String, Integer>() {{
put("gmmap", 180000000);
// used by the "goto" command for players
public static final HashMap<String, Integer> GOTO_TOWNS = new HashMap<String, Integer>() {{
put("southperry", 60000);
put("amherst", 1000000);
put("henesys", 100000000);
@@ -72,6 +71,25 @@ public class GameConstants {
put("korean", 222000000);
put("ellin", 300000000);
put("nlc", 600000000);
put("showa", 801000000);
put("shrine", 800000000);
put("ariant", 260000000);
put("magatia", 261000000);
put("singapore", 540000000);
put("quay", 541000000);
put("kampung", 551000000);
put("amoria", 680000000);
put("temple", 270000100);
put("square", 103040000);
put("neo", 240070000);
put("mushking", 106020000);
}};
// used by the "goto" command for only-GMs
public static final HashMap<String, Integer> GOTO_AREAS = new HashMap<String, Integer>() {{
putAll(GOTO_TOWNS);
put("gmmap", 180000000);
put("excavation", 990000000);
put("mushmom", 100000005);
put("griffey", 240020101);
@@ -80,25 +98,13 @@ public class GameConstants {
put("balrog", 105090900);
put("zakum", 211042300);
put("papu", 220080001);
put("showa", 801000000);
put("guild", 200000301);
put("shrine", 800000000);
put("skelegon", 240040511);
put("hpq", 100000200);
put("pianus", 230040420);
put("horntail", 240050400);
put("pinkbean", 270050000);
put("ariant", 260000000);
put("magatia", 261000000);
put("singapore", 540000000);
put("quay", 541000000);
put("kampung", 551000000);
put("keep", 610020006);
put("amoria", 680000000);
put("temple", 270000100);
put("square", 103040000);
put("neo", 240070000);
put("mushking", 106020000);
put("dojo", 925020001);
put("bosspq", 970030000);
put("fm", 910000000);
+8 -4
View File
@@ -76,13 +76,15 @@ public class ServerConstants {
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_FOR_STARTERS = true; //Players level 10 or below can create/invite other players on the given level range.
public static final boolean USE_AUTOASSIGN_STARTERS_AP = false; //Beginners level 10 or below have their AP autoassigned (they can't choose to levelup a stat). Set true if the localhost doesn't support AP assigning for beginners level 10 or below.
public static final boolean USE_AUTOASSIGN_STARTERS_AP = false; //Beginners level 10 or below have their AP autoassigned (they can't choose to levelup a stat). Set true ONLY if the localhost doesn't support AP assigning for beginners level 10 or below.
public static final boolean USE_AUTOASSIGN_SECONDARY_CAP = true;//Prevents AP autoassign from spending on secondary stats after the player class' cap (defined on the autoassign handler) has been reached.
public static final boolean USE_AUTOBAN = false; //Commands the server to detect infractors automatically.
public static final boolean USE_AUTOBAN_LOG = true; //Log autoban related messages. Still logs even with USE_AUTOBAN disabled.
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; //HeavenMS-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_ADMIN_ACCOUNT = false; //Forces accounts having GM characters to be treated as a "GM account" by the client (localhost). Some of the GM account perks is the ability to FLY, but unable to TRADE.
public static final boolean USE_ENFORCE_NOVICE_EXPRATE = false; //Hardsets experience rate 1x for beginners level 10 or under. Ideal for roaming on novice areas without caring too much about losing some stats.
public static final boolean USE_ENFORCE_HPMP_SWAP = false; //Forces players to reuse stats (via AP Resetting) located on HP/MP pool only inside the HP/MP stats.
public static final boolean USE_ENFORCE_MOB_LEVEL_RANGE = true; //Players N levels below the killed mob will gain no experience from defeating it.
public static final boolean USE_ENFORCE_JOB_LEVEL_RANGE = false;//Caps the player level on the minimum required to advance their current jobs.
@@ -166,11 +168,13 @@ public class ServerConstants {
public static final int ITEM_EXPIRE_CHECK = 10 * 1000; //Interval between item expiring tasks on maps, which checks and makes disappear expired items.
public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map.
public static final int MAP_VISITED_SIZE = 5; //Max length for last mapids visited by a player. This is used to recover and update drops on these maps accordingly with player actions.
public static final int MAP_DAMAGE_OVERTIME_INTERVAL = 5000;//Interval in seconds between map environment damage (e.g. El Nath and Aqua Road surrondings).
public static final int MAP_DAMAGE_OVERTIME_INTERVAL = 5000;//Interval in milliseconds between map environment damage (e.g. El Nath and Aqua Road surrondings).
//Channel Mob Disease Monitor Configuration
public static final int MOB_STATUS_MONITOR_PROC = 200; //Frequency in milliseconds between each proc on the mob disease monitor schedule.
public static final int MOB_STATUS_MONITOR_LIFE = 84; //Idle proc count the mob disease monitor is allowed to be there before closing it due to inactivity.
public static final int MOB_STATUS_MONITOR_PROC = 200; //Frequency in milliseconds between each proc on the mob disease monitor schedule.
public static final int MOB_STATUS_MONITOR_LIFE = 84; //Idle proc count the mob disease monitor is allowed to be there before closing it due to inactivity.
public static final int MOB_STATUS_AGGRO_PERSISTENCE = 2; //Idle proc count on aggro update for a mob to keep following the current controller, given him/her is the leading damage dealer.
public static final int MOB_STATUS_AGGRO_INTERVAL = 5000; //Interval in milliseconds between aggro logistics update.
//Some Gameplay Enhancing Configurations
//Scroll Configuration
+4 -2
View File
@@ -228,6 +228,8 @@ public enum SendOpcode {
GIVE_FOREIGN_BUFF(0xC7),
CANCEL_FOREIGN_BUFF(0xC8),
UPDATE_PARTYMEMBER_HP(0xC9),
GUILD_NAME_CHANGED(0xCA),
GUILD_MARK_CHANGED(0xCB),
THROW_GRENADE(0xCC),
CANCEL_CHAIR(0xCD),
SHOW_ITEM_GAIN_INCHAT(0xCE),
@@ -257,8 +259,8 @@ public enum SendOpcode {
DAMAGE_MONSTER(0xF6),
ARIANT_THING(0xF9),
SHOW_MONSTER_HP(0xFA),
SHOW_DRAGGED(0xFB),//CATCH
CATCH_MONSTER(0xFC),
CATCH_MONSTER(0xFB),
CATCH_MONSTER_WITH_ITEM(0xFC),
SHOW_MAGNET(0xFD),
SPAWN_NPC(0x101),
REMOVE_NPC(0x102),
-1
View File
@@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import constants.ServerConstants;
import net.server.audit.locks.MonitoredLockType;
import server.TimerManager;
import tools.FilePrinter;
@@ -84,6 +84,7 @@ public enum MonitoredLockType {
VISITOR_PSHOP,
STORAGE,
MOB,
MOB_AGGRO,
MOB_ANI,
MOB_EXT,
MOB_STATI,
@@ -96,6 +97,8 @@ public enum MonitoredLockType {
MAP_ITEM,
MAP_LOOT,
MAP_BOUNDS,
MAP_AGGRO,
MAP_AGGRO_IDLE,
MINIDUNGEON,
REACTOR,
REACTOR_HIT;
@@ -274,7 +274,7 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
totDamageToOneMonster += eachd;
}
totDamage += totDamageToOneMonster;
player.checkMonsterAggro(monster);
monster.aggroMonsterDamage(player, totDamageToOneMonster);
if (player.getBuffedValue(MapleBuffStat.PICKPOCKET) != null && (attack.skill == 0 || attack.skill == Rogue.DOUBLE_STAB || attack.skill == Bandit.SAVAGE_BLOW || attack.skill == ChiefBandit.ASSAULTER || attack.skill == ChiefBandit.BAND_OF_THIEVES || attack.skill == Shadower.ASSASSINATE || attack.skill == Shadower.TAUNT || attack.skill == Shadower.BOOMERANG_STEP)) {
Skill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET);
int picklv = (player.isGM()) ? pickpocket.getMaxLevel() : player.getSkillLevel(pickpocket);
@@ -51,11 +51,11 @@ public abstract class AbstractMovementPacketHandler extends AbstractMaplePacketH
short ypos = lea.readShort();
short xwobble = lea.readShort();
short ywobble = lea.readShort();
short unk = lea.readShort();
short fh = lea.readShort();
byte newstate = lea.readByte();
short duration = lea.readShort();
AbsoluteLifeMovement alm = new AbsoluteLifeMovement(command, new Point(xpos, ypos), duration, newstate);
alm.setUnk(unk);
alm.setFh(fh);
alm.setPixelsPerSecond(new Point(xwobble, ywobble));
res.add(alm);
break;
@@ -105,11 +105,11 @@ public abstract class AbstractMovementPacketHandler extends AbstractMaplePacketH
/*case 11: { // Chair
short xpos = lea.readShort();
short ypos = lea.readShort();
short unk = lea.readShort();
short fh = lea.readShort();
byte newstate = lea.readByte();
short duration = lea.readShort();
ChairMovement cm = new ChairMovement(command, new Point(xpos, ypos), duration, newstate);
cm.setUnk(unk);
cm.setFh(fh);
res.add(cm);
break;
}*/
@@ -118,14 +118,14 @@ public abstract class AbstractMovementPacketHandler extends AbstractMaplePacketH
short ypos = lea.readShort();
short xwobble = lea.readShort();
short ywobble = lea.readShort();
short unk = lea.readShort();
short fh = lea.readShort();
short ofh = lea.readShort();
byte newstate = lea.readByte();
short duration = lea.readShort();
JumpDownMovement jdm = new JumpDownMovement(command, new Point(xpos, ypos), duration, newstate);
jdm.setUnk(unk);
jdm.setFh(fh);
jdm.setPixelsPerSecond(new Point(xwobble, ywobble));
jdm.setFH(fh);
jdm.setOriginFh(ofh);
res.add(jdm);
break;
}
@@ -32,32 +32,15 @@ public final class AutoAggroHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
if (c.getPlayer().isHidden()) return; // Don't auto aggro GM's in hide...
MapleCharacter player = c.getPlayer();
if (player.isHidden()) return; // Don't auto aggro GM's in hide...
MapleMap map = c.getPlayer().getMap();
MapleMap map = player.getMap();
int oid = slea.readInt();
MapleMonster monster = map.getMonsterByOid(oid);
if (monster != null) {
MapleCharacter currentController = monster.getController();
monster.lockMonster();
try {
if (currentController != null) {
if (!monster.isControllerHasAggro()) {
if (map.getCharacterById(currentController.getId()) == null) {
monster.switchController(c.getPlayer(), true);
} else {
monster.switchController(currentController, true);
}
} else if (map.getCharacterById(currentController.getId()) == null) {
monster.switchController(c.getPlayer(), true);
}
} else {
monster.switchController(c.getPlayer(), true);
}
} finally {
monster.unlockMonster();
}
monster.aggroAutoAggroUpdate(player);
}
}
}
@@ -47,10 +47,6 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
return true;
}
private void restancePlayer(MapleCharacter mc) {
mc.broadcastStance();
}
private class Invited {
public String name;
public int gid;
@@ -131,7 +127,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.showGuildInfo(mc));
c.getPlayer().dropMessage(1, "You have successfully created a Guild.");
restancePlayer(mc);
mc.getGuild().broadcastNameChanged();
mc.getGuild().broadcastEmblemChanged();
break;
case 0x05:
if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) {
@@ -191,7 +188,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
if(allianceId > 0) Server.getInstance().getAlliance(allianceId).updateAlliancePackets(mc);
mc.saveGuildStatus(); // update database
restancePlayer(mc);
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(mc.getId(), mc.getGuild().getName())); // thanks Vcoc for pointing out an issue with updating guild tooltip to players in the map
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildMarkChanged(mc.getId(), mc.getGuild()));
break;
case 0x07:
cid = slea.readInt();
@@ -212,7 +210,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
mc.getMGC().setGuildId(0);
mc.getMGC().setGuildRank(5);
mc.saveGuildStatus();
restancePlayer(mc);
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(mc.getId(), ""));
break;
case 0x08:
allianceId = mc.getGuild().getAllianceId();
@@ -272,7 +270,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
}
mc.gainMeso(-ServerConstants.CHANGE_EMBLEM_COST, true, false, true);
restancePlayer(mc);
mc.getGuild().broadcastNameChanged();
mc.getGuild().broadcastEmblemChanged();
break;
case 0x10:
if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) {
@@ -77,8 +77,6 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
boolean isAttack = inRangeInclusive(rawActivity, 24, 41);
boolean isSkill = inRangeInclusive(rawActivity, 42, 59);
boolean currentController = (monster.getController() == player);
MobSkill toUse = null;
int useSkillId = 0, useSkillLevel = 0;
@@ -112,10 +110,6 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
int atkStatus = monster.canUseAttack(castPos, isSkill);
if (atkStatus < 1) {
if (!currentController) {
return;
}
rawActivity = -1;
pOption = 0;
}
@@ -147,24 +141,8 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
Point startPos = new Point(start_x, start_y - 2);
List<LifeMovementFragment> res = parseMovement(slea);
boolean aggro;
monster.lockMonster();
try {
if (!currentController) {
if (monster.isAttackedBy(player)) {
monster.switchController(player, true);
} else {
return;
}
}
aggro = monster.isControllerHasAggro();
if (aggro) {
monster.setControllerKnowsAboutAggro(true);
}
} finally {
monster.unlockMonster();
}
Boolean aggro = monster.aggroMoveLifeUpdate(player);
if (aggro == null) return;
if (nextUse != null) {
c.announce(MaplePacketCreator.moveMonsterResponse(objectid, moveid, mobMp, aggro, nextSkillId, nextSkillLevel));
@@ -174,7 +152,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
if (res != null) {
if (ServerConstants.USE_DEBUG_SHOW_RCVD_MVLIFE) {
System.out.println((isSkill ? "SKILL " : (isAttack ? "ATTCK " : " ")) + "castPos: " + castPos + " rawAct: " + rawActivity + " opt: " + pOption + " skillID: " + useSkillId + " skillLV: " + useSkillLevel + " " + "allowSkill: " + nextMovementCouldBeSkill);
System.out.println((isSkill ? "SKILL " : (isAttack ? "ATTCK " : " ")) + "castPos: " + castPos + " rawAct: " + rawActivity + " opt: " + pOption + " skillID: " + useSkillId + " skillLV: " + useSkillLevel + " " + "allowSkill: " + nextMovementCouldBeSkill + " mobMp: " + mobMp);
}
map.broadcastMessage(player, MaplePacketCreator.moveMonster(objectid, nextMovementCouldBeSkill, rawActivity, useSkillId, useSkillLevel, pOption, startPos, res), monster.getPosition());
@@ -35,6 +35,7 @@ import net.AbstractMaplePacketHandler;
import server.MapleItemInformationProvider;
import server.MapleTrade;
import constants.GameConstants;
import java.sql.SQLException;
import server.maps.FieldLimit;
import server.maps.MapleHiredMerchant;
import server.maps.MapleMapObject;
@@ -61,8 +62,8 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
CHAT(6),
CHAT_THING(8),
EXIT(0xA),
OPEN(0xB),
TRADE_BIRTHDAY(0x0E),
OPEN_STORE(0xB),
OPEN_CASH(0xE),
SET_ITEMS(0xF),
SET_MESO(0x10),
CONFIRM(0x11),
@@ -74,7 +75,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
REMOVE_ITEM(0x1B),
BAN_PLAYER(0x1C),
MERCHANT_THING(0x1D),
OPEN_STORE(0x1E),
OPEN_THING(0x1E),
PUT_ITEM(0x21),
MERCHANT_BUY(0x22),
TAKE_ITEM_BACK(0x26),
@@ -136,7 +137,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
try {
byte mode = slea.readByte();
final MapleCharacter chr = c.getPlayer();
if (mode == Action.CREATE.getCode()) {
if(!chr.isAlive()) { // thanks GabrielSin for pointing this
chr.getClient().announce(MaplePacketCreator.getMiniRoomError(4));
@@ -333,14 +334,25 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
chr.closeMiniGame();
chr.closeHiredMerchant(true);
}
} else if (mode == Action.OPEN.getCode()) {
} else if (mode == Action.OPEN_STORE.getCode() || mode == Action.OPEN_CASH.getCode()) {
if (isTradeOpen(chr)) return;
if (mode == Action.OPEN_STORE.getCode()) {
slea.readByte(); //01
} else {
slea.readShort();
int birthday = slea.readInt();
if (!CashOperationHandler.checkBirthday(c, birthday)) { // birthday check here found thanks to lucasziron
c.announce(MaplePacketCreator.serverNotice(1, "Please check again the birthday date."));
return;
}
c.announce(MaplePacketCreator.hiredMerchantOwnerMaintenanceLeave());
}
MaplePlayerShop shop = chr.getPlayerShop();
MapleHiredMerchant merchant = chr.getHiredMerchant();
if (shop != null && shop.isOwner(chr)) {
slea.readByte();//01
if(ServerConstants.USE_ERASE_PERMIT_ON_OPENSHOP) {
try {
MapleInventoryManipulator.removeById(c, MapleInventoryType.CASH, shop.getItemId(), 1, true, false);
@@ -355,7 +367,6 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
chr.getMap().addMapObject(merchant);
chr.setHiredMerchant(null);
chr.getMap().broadcastMessage(MaplePacketCreator.spawnHiredMerchantBox(merchant));
slea.readByte();
}
} else if (mode == Action.READY.getCode()) {
MapleMiniGame game = chr.getMiniGame();
@@ -528,7 +539,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.serverNotice(1, "Cash items are not allowed to be sold on the Player Store."));
return;
}
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 Store."));
return;
@@ -543,26 +554,44 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
MaplePlayerShop shop = chr.getPlayerShop();
MapleHiredMerchant merchant = chr.getHiredMerchant();
if (shop != null && shop.isOwner(chr)) {
if (shop.isOpen()) {
if (shop.isOpen() || !shop.addItem(shopItem)) { // thanks Vcoc for pointing an exploit with unlimited shop slots
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell it anymore."));
return;
}
shop.addItem(shopItem);
if (ItemConstants.isRechargeable(ivItem.getItemId())) {
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, ivItem.getQuantity(), true);
} else {
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, (short) (bundles * perBundle), true);
}
c.announce(MaplePacketCreator.getPlayerShopItemUpdate(shop));
} else if (merchant != null && merchant.isOwner(chr)) {
if (merchant.isOpen()) {
if (ivType.equals(MapleInventoryType.CASH) && merchant.isPublished()) {
c.announce(MaplePacketCreator.serverNotice(1, "Cash items are only allowed to be sold when first opening the store."));
return;
}
if (merchant.isOpen() || !merchant.addItem(shopItem)) { // thanks Vcoc for pointing an exploit with unlimited shop slots
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell it anymore."));
return;
}
merchant.addItem(shopItem);
if (ItemConstants.isRechargeable(ivItem.getItemId())) {
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, ivItem.getQuantity(), true);
} else {
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, (short) (bundles * perBundle), true);
}
c.announce(MaplePacketCreator.updateHiredMerchant(merchant, chr));
}
if (ItemConstants.isRechargeable(ivItem.getItemId())) {
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, ivItem.getQuantity(), true);
try {
merchant.saveItems(false); // thanks Masterrulax for realizing yet another dupe with merchants/Fredrick
} catch (SQLException ex) {
ex.printStackTrace();
}
} else {
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, (short) (bundles * perBundle), true);
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell without owning a shop."));
}
} else if (mode == Action.REMOVE_ITEM.getCode()) {
if (isTradeOpen(chr)) return;
@@ -616,8 +645,9 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
MaplePlayerShop shop = chr.getPlayerShop();
MapleHiredMerchant merchant = chr.getHiredMerchant();
if (shop != null && shop.isVisitor(chr)) {
shop.buy(c, itemid, quantity);
shop.broadcast(MaplePacketCreator.getPlayerShopItemUpdate(shop));
if (shop.buy(c, itemid, quantity)) {
shop.broadcast(MaplePacketCreator.getPlayerShopItemUpdate(shop));
}
} else if (merchant != null && !merchant.isOwner(chr)) {
merchant.buy(c, itemid, quantity);
merchant.broadcastToVisitorsThreadsafe(MaplePacketCreator.updateHiredMerchant(merchant, chr));
@@ -661,6 +691,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
if (merchant.isOwner(chr)) {
merchant.clearMessages();
merchant.setOpen(true);
merchant.getMap().broadcastMessage(MaplePacketCreator.updateHiredMerchantBox(merchant));
}
}
@@ -62,7 +62,9 @@ import constants.ServerConstants;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.server.coordinator.MapleEventRecallCoordinator;
import net.server.coordinator.MapleSessionCoordinator;
import org.apache.mina.core.session.IoSession;
@@ -72,6 +74,25 @@ import tools.packets.Wedding;
public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
private static Set<Integer> attemptingLoginAccounts = new HashSet<>();
private boolean tryAcquireAccount(int accId) {
synchronized (attemptingLoginAccounts) {
if (attemptingLoginAccounts.contains(accId)) {
return false;
}
attemptingLoginAccounts.add(accId);
return true;
}
}
private void releaseAccount(int accId) {
synchronized (attemptingLoginAccounts) {
attemptingLoginAccounts.remove(accId);
}
}
@Override
public final boolean validateState(MapleClient c) {
return !c.isLoggedIn();
@@ -82,298 +103,313 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
final int cid = slea.readInt();
final Server server = Server.getInstance();
World wserv = server.getWorld(c.getWorld());
if(wserv == null) {
c.disconnect(true, false);
return;
}
Channel cserv = wserv.getChannel(c.getChannel());
if(cserv == null) {
c.setChannel(1);
cserv = wserv.getChannel(c.getChannel());
if(cserv == null) {
c.disconnect(true, false);
return;
}
}
MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid);
boolean newcomer = false;
IoSession session = c.getSession();
String remoteHwid;
if (player == null) {
if (!server.validateCharacteridInTransition((InetSocketAddress) session.getRemoteAddress(), cid)) {
c.disconnect(true, false);
return;
}
remoteHwid = MapleSessionCoordinator.getInstance().getGameSessionHwid(session);
if (remoteHwid == null) {
c.disconnect(true, false);
return;
}
if (c.tryacquireClient()) { // thanks MedicOP for assisting on concurrency protection here
try {
player = MapleCharacter.loadCharFromDB(cid, c, true);
newcomer = true;
} catch (SQLException e) {
e.printStackTrace();
}
} else {
remoteHwid = player.getClient().getHWID();
}
if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs
c.disconnect(true, false);
return;
}
c.setPlayer(player);
c.setAccID(player.getAccountID());
boolean allowLogin = true;
/* is this check really necessary?
if (state == MapleClient.LOGIN_SERVER_TRANSITION || state == MapleClient.LOGIN_NOTLOGGEDIN) {
List<String> charNames = c.loadCharacterNames(c.getWorld());
if(!newcomer) {
charNames.remove(player.getName());
}
for (String charName : charNames) {
if(wserv.getPlayerStorage().getCharacterByName(charName) != null) {
allowLogin = false;
break;
}
}
}
*/
c.lockClient(); // Sync this to prevent wrong login state for double channel changes
try {
int state = c.getLoginState();
if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) {
c.setPlayer(null);
c.setAccID(0);
if (state == MapleClient.LOGIN_LOGGEDIN) {
World wserv = server.getWorld(c.getWorld());
if(wserv == null) {
c.disconnect(true, false);
} else {
c.announce(MaplePacketCreator.getAfterLoginError(7));
return;
}
return;
}
c.updateLoginState(MapleClient.LOGIN_LOGGEDIN);
} finally {
c.unlockClient();
}
if (!newcomer) {
c.setCharacterSlots((byte) player.getClient().getCharacterSlots());
player.newClient(c);
}
int hwidLen = remoteHwid.length();
session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid.substring(hwidLen - 8, hwidLen));
c.setHWID(remoteHwid);
cserv.addPlayer(player);
wserv.addPlayer(player);
player.setEnteredChannelWorld();
List<PlayerBuffValueHolder> buffs = server.getPlayerBuffStorage().getBuffsFromStorage(cid);
if (buffs != null) {
List<Pair<Long, PlayerBuffValueHolder>> timedBuffs = getLocalStartTimes(buffs);
player.silentGiveBuffs(timedBuffs);
}
Map<MapleDisease, Pair<Long, MobSkill>> diseases = server.getPlayerBuffStorage().getDiseasesFromStorage(cid);
if (diseases != null) {
player.silentApplyDiseases(diseases);
}
c.announce(MaplePacketCreator.getCharInfo(player));
if (!player.isHidden()) {
if(player.isGM() && ServerConstants.USE_AUTOHIDE_GM) {
player.toggleHide(true);
}
}
player.sendKeymap();
player.sendMacros();
// pot bindings being passed through other characters on the account detected thanks to Croosade dev team
MapleKeyBinding autohpPot = player.getKeymap().get(91);
player.announce(MaplePacketCreator.sendAutoHpPot(autohpPot != null ? autohpPot.getAction() : 0));
MapleKeyBinding autompPot = player.getKeymap().get(92);
player.announce(MaplePacketCreator.sendAutoMpPot(autompPot != null ? autompPot.getAction() : 0));
player.getMap().addPlayer(player);
player.visitMap(player.getMap());
BuddyList bl = player.getBuddylist();
int buddyIds[] = bl.getBuddyIds();
wserv.loggedOn(player.getName(), player.getId(), c.getChannel(), buddyIds);
for (CharacterIdChannelPair onlineBuddy : wserv.multiBuddyFind(player.getId(), buddyIds)) {
BuddylistEntry ble = bl.get(onlineBuddy.getCharacterId());
ble.setChannel(onlineBuddy.getChannel());
bl.put(ble);
}
c.announce(MaplePacketCreator.updateBuddylist(bl.getBuddies()));
c.announce(MaplePacketCreator.loadFamily(player));
if (player.getFamilyId() > 0) {
MapleFamily f = wserv.getFamily(player.getFamilyId());
if (f == null) {
f = new MapleFamily(player.getId());
wserv.addFamily(player.getFamilyId(), f);
}
player.setFamily(f);
c.announce(MaplePacketCreator.getFamilyInfo(f.getMember(player.getId())));
}
if (player.getGuildId() > 0) {
MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player);
if (playerGuild == null) {
player.deleteGuild(player.getGuildId());
player.getMGC().setGuildId(0);
player.getMGC().setGuildRank(5);
} else {
playerGuild.getMGC(player.getId()).setCharacter(player);
player.setMGC(playerGuild.getMGC(player.getId()));
server.setGuildMemberOnline(player, true, c.getChannel());
c.announce(MaplePacketCreator.showGuildInfo(player));
int allianceId = player.getGuild().getAllianceId();
if (allianceId > 0) {
MapleAlliance newAlliance = server.getAlliance(allianceId);
if (newAlliance == null) {
newAlliance = MapleAlliance.loadAlliance(allianceId);
if (newAlliance != null) {
server.addAlliance(allianceId, newAlliance);
} else {
player.getGuild().setAllianceId(0);
}
}
if (newAlliance != null) {
c.announce(MaplePacketCreator.updateAllianceInfo(newAlliance, c));
c.announce(MaplePacketCreator.allianceNotice(newAlliance.getId(), newAlliance.getNotice()));
if (newcomer) {
server.allianceMessage(allianceId, MaplePacketCreator.allianceMemberOnline(player, true), player.getId(), -1);
}
}
}
}
}
player.showNote();
if (player.getParty() != null) {
MaplePartyCharacter pchar = player.getMPC();
//Use this in case of enabling party HPbar HUD when logging in, however "you created a party" will appear on chat.
//c.announce(MaplePacketCreator.partyCreated(pchar));
pchar.setChannel(c.getChannel());
pchar.setMapId(player.getMapId());
pchar.setOnline(true);
wserv.updateParty(player.getParty().getId(), PartyOperation.LOG_ONOFF, pchar);
player.updatePartyMemberHP();
}
MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED);
eqpInv.lockInventory();
try {
for(Item it : eqpInv.list()) {
player.equippedItem((Equip) it);
}
} finally {
eqpInv.unlockInventory();
}
c.announce(MaplePacketCreator.updateBuddylist(player.getBuddylist().getBuddies()));
CharacterNameAndId pendingBuddyRequest = c.getPlayer().getBuddylist().pollPendingRequest();
if (pendingBuddyRequest != null) {
c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName()));
}
c.announce(MaplePacketCreator.updateGender(player));
player.checkMessenger();
c.announce(MaplePacketCreator.enableReport());
player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1);
player.checkBerserk(player.isHidden());
if (newcomer) {
for(MaplePet pet : player.getPets()) {
if(pet != null)
wserv.registerPetHunger(player, player.getPetIndex(pet));
}
player.reloadQuestExpirations();
/*
if (!c.hasVotedAlready()){
player.announce(MaplePacketCreator.earnTitleMessage("You can vote now! Vote and earn a vote point!"));
}
*/
if (player.isGM()){
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in"));
}
if(diseases != null) {
for(Entry<MapleDisease, Pair<Long, MobSkill>> e : diseases.entrySet()) {
final List<Pair<MapleDisease, Integer>> debuff = Collections.singletonList(new Pair<>(e.getKey(), Integer.valueOf(e.getValue().getRight().getX())));
c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight()));
Channel cserv = wserv.getChannel(c.getChannel());
if(cserv == null) {
c.setChannel(1);
cserv = wserv.getChannel(c.getChannel());
if(cserv == null) {
c.disconnect(true, false);
return;
}
}
MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid);
boolean newcomer = false;
IoSession session = c.getSession();
String remoteHwid;
if (player == null) {
if (!server.validateCharacteridInTransition((InetSocketAddress) session.getRemoteAddress(), cid)) {
c.disconnect(true, false);
return;
}
remoteHwid = MapleSessionCoordinator.getInstance().getGameSessionHwid(session);
if (remoteHwid == null) {
c.disconnect(true, false);
return;
}
try {
player = MapleCharacter.loadCharFromDB(cid, c, true);
newcomer = true;
} catch (SQLException e) {
e.printStackTrace();
}
} else {
remoteHwid = player.getClient().getHWID();
}
if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs
c.disconnect(true, false);
return;
}
c.setPlayer(player);
c.setAccID(player.getAccountID());
boolean allowLogin = true;
/* is this check really necessary?
if (state == MapleClient.LOGIN_SERVER_TRANSITION || state == MapleClient.LOGIN_NOTLOGGEDIN) {
List<String> charNames = c.loadCharacterNames(c.getWorld());
if(!newcomer) {
charNames.remove(player.getName());
}
for (String charName : charNames) {
if(wserv.getPlayerStorage().getCharacterByName(charName) != null) {
allowLogin = false;
break;
}
}
}
*/
int accId = c.getAccID();
if (tryAcquireAccount(accId)) { // Sync this to prevent wrong login state for double loggedin handling
try {
int state = c.getLoginState();
if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) {
c.setPlayer(null);
c.setAccID(0);
if (state == MapleClient.LOGIN_LOGGEDIN) {
c.disconnect(true, false);
} else {
c.announce(MaplePacketCreator.getAfterLoginError(7));
}
return;
}
c.updateLoginState(MapleClient.LOGIN_LOGGEDIN);
} finally {
releaseAccount(accId);
}
} else {
c.setPlayer(null);
c.setAccID(0);
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
player.announceDiseases();
if (!newcomer) {
c.setCharacterSlots((byte) player.getClient().getCharacterSlots());
player.newClient(c);
}
int hwidLen = remoteHwid.length();
session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid.substring(hwidLen - 8, hwidLen));
c.setHWID(remoteHwid);
cserv.addPlayer(player);
wserv.addPlayer(player);
player.setEnteredChannelWorld();
List<PlayerBuffValueHolder> buffs = server.getPlayerBuffStorage().getBuffsFromStorage(cid);
if (buffs != null) {
List<Pair<Long, PlayerBuffValueHolder>> timedBuffs = getLocalStartTimes(buffs);
player.silentGiveBuffs(timedBuffs);
}
Map<MapleDisease, Pair<Long, MobSkill>> diseases = server.getPlayerBuffStorage().getDiseasesFromStorage(cid);
if (diseases != null) {
player.silentApplyDiseases(diseases);
}
c.announce(MaplePacketCreator.getCharInfo(player));
if (!player.isHidden()) {
if(player.isGM() && ServerConstants.USE_AUTOHIDE_GM) {
player.toggleHide(true);
}
}
player.sendKeymap();
player.sendMacros();
// pot bindings being passed through other characters on the account detected thanks to Croosade dev team
MapleKeyBinding autohpPot = player.getKeymap().get(91);
player.announce(MaplePacketCreator.sendAutoHpPot(autohpPot != null ? autohpPot.getAction() : 0));
MapleKeyBinding autompPot = player.getKeymap().get(92);
player.announce(MaplePacketCreator.sendAutoMpPot(autompPot != null ? autompPot.getAction() : 0));
player.getMap().addPlayer(player);
player.visitMap(player.getMap());
BuddyList bl = player.getBuddylist();
int buddyIds[] = bl.getBuddyIds();
wserv.loggedOn(player.getName(), player.getId(), c.getChannel(), buddyIds);
for (CharacterIdChannelPair onlineBuddy : wserv.multiBuddyFind(player.getId(), buddyIds)) {
BuddylistEntry ble = bl.get(onlineBuddy.getCharacterId());
ble.setChannel(onlineBuddy.getChannel());
bl.put(ble);
}
c.announce(MaplePacketCreator.updateBuddylist(bl.getBuddies()));
c.announce(MaplePacketCreator.loadFamily(player));
if (player.getFamilyId() > 0) {
MapleFamily f = wserv.getFamily(player.getFamilyId());
if (f == null) {
f = new MapleFamily(player.getId());
wserv.addFamily(player.getFamilyId(), f);
}
player.setFamily(f);
c.announce(MaplePacketCreator.getFamilyInfo(f.getMember(player.getId())));
}
if (player.getGuildId() > 0) {
MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player);
if (playerGuild == null) {
player.deleteGuild(player.getGuildId());
player.getMGC().setGuildId(0);
player.getMGC().setGuildRank(5);
} else {
playerGuild.getMGC(player.getId()).setCharacter(player);
player.setMGC(playerGuild.getMGC(player.getId()));
server.setGuildMemberOnline(player, true, c.getChannel());
c.announce(MaplePacketCreator.showGuildInfo(player));
int allianceId = player.getGuild().getAllianceId();
if (allianceId > 0) {
MapleAlliance newAlliance = server.getAlliance(allianceId);
if (newAlliance == null) {
newAlliance = MapleAlliance.loadAlliance(allianceId);
if (newAlliance != null) {
server.addAlliance(allianceId, newAlliance);
} else {
player.getGuild().setAllianceId(0);
}
}
if (newAlliance != null) {
c.announce(MaplePacketCreator.updateAllianceInfo(newAlliance, c));
c.announce(MaplePacketCreator.allianceNotice(newAlliance.getId(), newAlliance.getNotice()));
if (newcomer) {
server.allianceMessage(allianceId, MaplePacketCreator.allianceMemberOnline(player, true), player.getId(), -1);
}
}
}
}
}
player.showNote();
if (player.getParty() != null) {
MaplePartyCharacter pchar = player.getMPC();
//Use this in case of enabling party HPbar HUD when logging in, however "you created a party" will appear on chat.
//c.announce(MaplePacketCreator.partyCreated(pchar));
pchar.setChannel(c.getChannel());
pchar.setMapId(player.getMapId());
pchar.setOnline(true);
wserv.updateParty(player.getParty().getId(), PartyOperation.LOG_ONOFF, pchar);
player.updatePartyMemberHP();
}
MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED);
eqpInv.lockInventory();
try {
for(Item it : eqpInv.list()) {
player.equippedItem((Equip) it);
}
} finally {
eqpInv.unlockInventory();
}
c.announce(MaplePacketCreator.updateBuddylist(player.getBuddylist().getBuddies()));
CharacterNameAndId pendingBuddyRequest = c.getPlayer().getBuddylist().pollPendingRequest();
if (pendingBuddyRequest != null) {
c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName()));
}
c.announce(MaplePacketCreator.updateGender(player));
player.checkMessenger();
c.announce(MaplePacketCreator.enableReport());
player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1);
player.checkBerserk(player.isHidden());
if (newcomer) {
for(MaplePet pet : player.getPets()) {
if(pet != null)
wserv.registerPetHunger(player, player.getPetIndex(pet));
}
player.reloadQuestExpirations();
/*
if (!c.hasVotedAlready()){
player.announce(MaplePacketCreator.earnTitleMessage("You can vote now! Vote and earn a vote point!"));
}
*/
if (player.isGM()){
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in"));
}
if(diseases != null) {
for(Entry<MapleDisease, Pair<Long, MobSkill>> e : diseases.entrySet()) {
final List<Pair<MapleDisease, Integer>> debuff = Collections.singletonList(new Pair<>(e.getKey(), Integer.valueOf(e.getValue().getRight().getX())));
c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight()));
}
player.announceDiseases();
}
} else {
if(player.isRidingBattleship()) {
player.announceBattleshipHp();
}
}
player.buffExpireTask();
player.diseaseExpireTask();
player.skillCooldownTask();
player.expirationTask();
player.questExpirationTask();
if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) {
player.createDragon();
}
player.commitExcludedItems();
showDueyNotification(c, player);
if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask();
player.resetPlayerRates();
if(ServerConstants.USE_ADD_RATES_BY_LEVEL == true) player.setPlayerRates();
player.setWorldRates();
player.updateCouponRates();
player.receivePartyMemberHP();
if(player.getPartnerId() > 0) {
int partnerId = player.getPartnerId();
final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId);
if(partner != null && !partner.isAwayFromWorld()) {
player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId()));
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
}
}
if (newcomer) {
EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid);
if (eim != null) {
eim.registerPlayer(player);
}
}
} finally {
c.releaseClient();
}
} else {
if(player.isRidingBattleship()) {
player.announceBattleshipHp();
}
}
player.buffExpireTask();
player.diseaseExpireTask();
player.skillCooldownTask();
player.expirationTask();
player.questExpirationTask();
if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) {
player.createDragon();
}
player.commitExcludedItems();
showDueyNotification(c, player);
if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask();
player.resetPlayerRates();
if(ServerConstants.USE_ADD_RATES_BY_LEVEL == true) player.setPlayerRates();
player.setWorldRates();
player.updateCouponRates();
player.receivePartyMemberHP();
if(player.getPartnerId() > 0) {
int partnerId = player.getPartnerId();
final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId);
if(partner != null && !partner.isAwayFromWorld()) {
player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId()));
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
}
}
if (newcomer) {
EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid);
if (eim != null) {
eim.registerPlayer(player);
}
c.announce(MaplePacketCreator.getAfterLoginError(10));
}
}
@@ -91,26 +91,22 @@ public final class SpecialMoveHandler extends AbstractMaplePacketHandler {
}
if (skillid == Hero.MONSTER_MAGNET || skillid == Paladin.MONSTER_MAGNET || skillid == DarkKnight.MONSTER_MAGNET) { // Monster Magnet
int num = slea.readInt();
int mobId;
byte success;
for (int i = 0; i < num; i++) {
mobId = slea.readInt();
success = slea.readByte();
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showMagnet(mobId, success), false);
MapleMonster monster = chr.getMap().getMonsterByOid(mobId);
int mobOid = slea.readInt();
byte success = slea.readByte();
chr.getMap().broadcastMessage(chr, MaplePacketCreator.catchMonster(mobOid, success), false);
MapleMonster monster = chr.getMap().getMonsterByOid(mobOid);
if (monster != null) {
if (!monster.isBoss()) {
monster.lockMonster();
try {
monster.switchController(chr, monster.isControllerHasAggro());
} finally {
monster.unlockMonster();
}
monster.aggroClearDamages();
monster.aggroMonsterDamage(chr, 1);
monster.aggroSwitchController(chr, true);
}
}
}
byte direction = slea.readByte();
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showBuffeffect(chr.getId(), skillid, chr.getSkillLevel(skillid), direction), false);
byte direction = slea.readByte(); // thanks MedicOP for pointing some 3rd-party related issues with Magnet
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showBuffeffect(chr.getId(), skillid, chr.getSkillLevel(skillid), 1, direction), false);
c.announce(MaplePacketCreator.enableActions());
return;
} else if (skillid == Brawler.MP_RECOVERY) {// MP Recovery
@@ -191,7 +191,7 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
damage -= bouncedamage;
map.damageMonster(chr, attacker, bouncedamage);
map.broadcastMessage(chr, MaplePacketCreator.damageMonster(oid, bouncedamage), false, true);
chr.checkMonsterAggro(attacker);
attacker.aggroMonsterDamage(chr, bouncedamage);
}
if (attacker != null && damagefrom == -1 && chr.getBuffedValue(MapleBuffStat.BODY_PRESSURE) != null) {
Skill skill = SkillFactory.getSkill(Aran.BODY_PRESSURE);
@@ -0,0 +1,371 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.coordinator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import constants.ServerConstants;
import client.MapleCharacter;
import java.util.concurrent.ScheduledFuture;
import net.server.Server;
import net.server.audit.LockCollector;
import server.life.MapleMonster;
import server.maps.MapleMap;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import server.TimerManager;
import tools.Pair;
/**
*
* @author Ronan
*/
public class MapleMonsterAggroCoordinator {
private MonitoredReentrantLock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MAP_AGGRO);
private MonitoredReentrantLock idleLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MAP_AGGRO_IDLE, true);
private long lastStopTime = Server.getInstance().getCurrentTime();
private ScheduledFuture<?> aggroMonitor = null;
private Map<MapleMonster, Map<Integer, PlayerAggroEntry>> mobAggroEntries = new HashMap<>();
private Map<MapleMonster, List<PlayerAggroEntry>> mobSortedAggros = new HashMap<>();
private class PlayerAggroEntry {
protected int cid;
protected int averageDamage = 0;
protected int currentDamageInstances = 0;
protected long accumulatedDamage = 0;
protected int expireStreak = 0;
protected int updateStreak = 0;
protected int toNextUpdate = 0;
protected int entryRank = -1;
protected PlayerAggroEntry(int cid) {
this.cid = cid;
}
}
public void stopAggroCoordinator() {
idleLock.lock();
try {
if (aggroMonitor == null) return;
aggroMonitor.cancel(false);
aggroMonitor = null;
} finally {
idleLock.unlock();
}
lastStopTime = Server.getInstance().getCurrentTime();
}
public void startAggroCoordinator() {
idleLock.lock();
try {
if (aggroMonitor != null) return;
aggroMonitor = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
runAggroUpdate(1);
runSortLeadingCharactersAggro();
}
}, ServerConstants.MOB_STATUS_AGGRO_INTERVAL, ServerConstants.MOB_STATUS_AGGRO_INTERVAL);
} finally {
idleLock.unlock();
}
int timeDelta = (int) Math.ceil((Server.getInstance().getCurrentTime() - lastStopTime) / ServerConstants.MOB_STATUS_AGGRO_INTERVAL);
if (timeDelta > 0) {
runAggroUpdate(timeDelta);
}
}
private static void updateEntryExpiration(PlayerAggroEntry pae) {
pae.toNextUpdate = (int) Math.ceil((120000L / ServerConstants.MOB_STATUS_AGGRO_INTERVAL) / Math.pow(2, pae.expireStreak + pae.currentDamageInstances));
}
private static void insertEntryDamage(PlayerAggroEntry pae, int damage) {
synchronized (pae) {
long totalDamage = pae.averageDamage;
totalDamage *= pae.currentDamageInstances;
totalDamage += damage;
pae.expireStreak = 0;
pae.updateStreak = 0;
updateEntryExpiration(pae);
pae.currentDamageInstances += 1;
pae.averageDamage = (int)(totalDamage / pae.currentDamageInstances);
pae.accumulatedDamage = totalDamage;
}
}
private static boolean expiredAfterUpdateEntryDamage(PlayerAggroEntry pae, int deltaTime) {
synchronized (pae) {
pae.updateStreak += 1;
pae.toNextUpdate -= deltaTime;
if (pae.toNextUpdate <= 0) { // reached dmg instance expire time
pae.expireStreak += 1;
updateEntryExpiration(pae);
pae.currentDamageInstances -= 1;
if (pae.currentDamageInstances < 1) { // expired aggro for this player
return true;
}
pae.accumulatedDamage = pae.averageDamage * pae.currentDamageInstances;
}
return false;
}
}
public void addAggroDamage(MapleMonster mob, int cid, int damage) { // assumption: should not trigger after dispose()
if (!mob.isAlive()) return;
List<PlayerAggroEntry> sortedAggro = mobSortedAggros.get(mob);
Map<Integer, PlayerAggroEntry> mobAggro = mobAggroEntries.get(mob);
if (mobAggro == null) {
if (lock.tryLock()) { // can run unreliably, as fast as possible... try lock that is!
try {
mobAggro = mobAggroEntries.get(mob);
if (mobAggro == null) {
mobAggro = new HashMap<>();
mobAggroEntries.put(mob, mobAggro);
sortedAggro = new LinkedList<>();
mobSortedAggros.put(mob, sortedAggro);
} else {
sortedAggro = mobSortedAggros.get(mob);
}
} finally {
lock.unlock();
}
} else {
return;
}
}
PlayerAggroEntry aggroEntry = mobAggro.get(cid);
if (aggroEntry == null) {
aggroEntry = new PlayerAggroEntry(cid);
synchronized (mobAggro) {
synchronized (sortedAggro) {
PlayerAggroEntry mappedEntry = mobAggro.get(cid);
if (mappedEntry == null) {
mobAggro.put(aggroEntry.cid, aggroEntry);
sortedAggro.add(aggroEntry);
} else {
aggroEntry = mappedEntry;
}
}
}
} else if (damage < 1) {
return;
}
insertEntryDamage(aggroEntry, damage);
}
private void runAggroUpdate(int deltaTime) {
List<Pair<MapleMonster, Map<Integer, PlayerAggroEntry>>> aggroMobs = new LinkedList<>();
lock.lock();
try {
for (Entry<MapleMonster, Map<Integer, PlayerAggroEntry>> e : mobAggroEntries.entrySet()) {
aggroMobs.add(new Pair<>(e.getKey(), e.getValue()));
}
} finally {
lock.unlock();
}
for (Pair<MapleMonster, Map<Integer, PlayerAggroEntry>> am : aggroMobs) {
Map<Integer, PlayerAggroEntry> mobAggro = am.getRight();
List<PlayerAggroEntry> sortedAggro = mobSortedAggros.get(am.getLeft());
if (sortedAggro != null) {
List<Integer> toRemove = new LinkedList<>();
List<Integer> toRemoveIdx = new ArrayList<>(mobAggro.size());
List<Integer> toRemoveByFetch = new LinkedList<>();
synchronized (mobAggro) {
synchronized (sortedAggro) {
for (PlayerAggroEntry pae : mobAggro.values()) {
if (expiredAfterUpdateEntryDamage(pae, deltaTime)) {
toRemove.add(pae.cid);
if (pae.entryRank > -1) toRemoveIdx.add(pae.entryRank);
else toRemoveByFetch.add(pae.cid);
}
}
if (!toRemove.isEmpty()) {
for (Integer cid : toRemove) {
mobAggro.remove(cid);
}
if (mobAggro.isEmpty()) { // all aggro on this mob expired
am.getLeft().aggroResetAggro();
}
}
if (!toRemoveIdx.isEmpty()) {
Collections.sort(toRemoveIdx, new Comparator<Integer>() { // last to first indexes
@Override
public int compare(Integer p1, Integer p2) {
return p1 < p2 ? 1 : p1.equals(p2) ? 0 : -1;
}
});
for (int idx : toRemoveIdx) {
sortedAggro.remove(idx);
}
}
if (!toRemoveByFetch.isEmpty()) {
for (Integer cid : toRemoveByFetch) {
for (int i = 0; i < sortedAggro.size(); i++) {
if (cid.equals(sortedAggro.get(i).cid)) {
sortedAggro.remove(i);
break;
}
}
}
}
}
}
}
}
}
private static void insertionSortAggroList(List<PlayerAggroEntry> paeList) {
for (int i = 1; i < paeList.size(); i++) {
PlayerAggroEntry pae = paeList.get(i);
long curAccDmg = pae.accumulatedDamage;
int j = i - 1;
while (j >= 0 && curAccDmg > paeList.get(j).accumulatedDamage) {
j -= 1;
}
j += 1;
if (j != i) {
paeList.remove(i);
paeList.add(j, pae);
}
}
int i = 0;
for (PlayerAggroEntry pae : paeList) {
pae.entryRank = i;
i += 1;
}
}
public boolean isLeadingCharacterAggro(MapleMonster mob, MapleCharacter player) {
// by assuming the quasi-sorted nature of "mobAggroList", this method
// returns whether the player given as parameter can be elected as next aggro leader
List<PlayerAggroEntry> mobAggroList = mobSortedAggros.get(mob);
if (mobAggroList != null) {
synchronized (mobAggroList) {
mobAggroList = new ArrayList<>(mobAggroList.subList(0, Math.min(mobAggroList.size(), 5)));
}
MapleMap map = mob.getMap();
for (PlayerAggroEntry pae : mobAggroList) {
MapleCharacter chr = map.getCharacterById(pae.cid);
if (chr != null) {
if (player.getId() == pae.cid) {
return true;
} else if (pae.updateStreak < ServerConstants.MOB_STATUS_AGGRO_PERSISTENCE && chr.isAlive()) { // verifies currently leading players activity
return false;
}
}
}
}
return false;
}
public void runSortLeadingCharactersAggro() {
List<List<PlayerAggroEntry>> aggroList;
lock.lock();
try {
aggroList = new ArrayList<>(mobSortedAggros.values());
} finally {
lock.unlock();
}
for (List<PlayerAggroEntry> mobAggroList : aggroList) {
synchronized (mobAggroList) {
insertionSortAggroList(mobAggroList);
}
}
}
public void removeAggroEntries(MapleMonster mob) {
lock.lock();
try {
mobAggroEntries.remove(mob);
mobSortedAggros.remove(mob);
} finally {
lock.unlock();
}
}
public void dispose() {
stopAggroCoordinator();
lock.lock();
try {
mobAggroEntries.clear();
mobSortedAggros.clear();
} finally {
lock.unlock();
}
disposeLocks();
}
private void disposeLocks() {
LockCollector.getInstance().registerDisposeAction(new Runnable() {
@Override
public void run() {
emptyLocks();
}
});
}
private void emptyLocks() {
lock = lock.dispose();
}
}
+29 -4
View File
@@ -39,6 +39,7 @@ import java.util.Set;
import java.util.concurrent.locks.Lock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.PlayerStorage;
import net.server.Server;
import net.server.channel.Channel;
import tools.DatabaseConnection;
@@ -109,11 +110,11 @@ public class MapleGuild {
con.close();
} catch (SQLException se) {
se.printStackTrace();
System.out.println("Unable to read guild information from sql" + se);
System.out.println("Unable to read guild information from sql: " + se);
}
}
public void buildNotifications() {
private void buildNotifications() {
if (!bDirty) {
return;
}
@@ -260,10 +261,10 @@ public class MapleGuild {
return name;
}
public java.util.Collection<MapleGuildCharacter> getMembers() {
public List<MapleGuildCharacter> getMembers() {
membersLock.lock();
try {
return java.util.Collections.unmodifiableCollection(members);
return new ArrayList<>(members);
} finally {
membersLock.unlock();
}
@@ -277,6 +278,30 @@ public class MapleGuild {
return signature;
}
public void broadcastNameChanged() {
PlayerStorage ps = Server.getInstance().getWorld(world).getPlayerStorage();
for (MapleGuildCharacter mgc : getMembers()) {
MapleCharacter chr = ps.getCharacterById(mgc.getId());
if (chr == null || !chr.isLoggedinWorld()) continue;
byte[] packet = MaplePacketCreator.guildNameChanged(chr.getId(), this.getName());
chr.getMap().broadcastMessage(chr, packet);
}
}
public void broadcastEmblemChanged() {
PlayerStorage ps = Server.getInstance().getWorld(world).getPlayerStorage();
for (MapleGuildCharacter mgc : getMembers()) {
MapleCharacter chr = ps.getCharacterById(mgc.getId());
if (chr == null || !chr.isLoggedinWorld()) continue;
byte[] packet = MaplePacketCreator.guildMarkChanged(chr.getId(), this);
chr.getMap().broadcastMessage(chr, packet);
}
}
public void broadcast(final byte[] packet) {
broadcast(packet, -1, BCOp.NONE);
}
+9 -1
View File
@@ -612,7 +612,15 @@ public class World {
mc.saveGuildStatus();
}
if (bDifferentGuild) {
mc.broadcastStance();
if (mc.isLoggedinWorld()) {
MapleGuild guild = Server.getInstance().getGuild(guildid);
if (guild != null) {
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(cid, guild.getName()));
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildMarkChanged(cid, guild));
} else {
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(cid, ""));
}
}
}
}
+9 -2
View File
@@ -237,6 +237,15 @@ public class AbstractPlayerInteraction {
return intList;
}
public boolean canHoldAll(List<Double> itemids) {
List<Double> quantity = new LinkedList<>();
for (int i = 0; i < itemids.size(); i++) {
quantity.add(1.0);
}
return canHoldAll(itemids, quantity);
}
public boolean canHoldAll(List<Double> itemids, List<Double> quantity) {
return canHoldAll(convertToIntegerArray(itemids), convertToIntegerArray(quantity), true);
}
@@ -554,8 +563,6 @@ public class AbstractPlayerInteraction {
if(expires >= 0)
item.setExpiration(System.currentTimeMillis() + expires);
item.setPetId(petId);
if (!MapleInventoryManipulator.checkSpace(c, id, quantity, "")) {
c.getPlayer().dropMessage(1, "Your inventory is full. Please remove an item from your " + ItemConstants.getInventoryType(id).name() + " inventory.");
+14 -9
View File
@@ -229,7 +229,11 @@ public class EventInstanceManager {
}
public synchronized void registerPlayer(final MapleCharacter chr) {
public synchronized void registerPlayer(final MapleCharacter chr) {
registerPlayer(chr, true);
}
public synchronized void registerPlayer(final MapleCharacter chr, boolean runEntryScript) {
if (chr == null || !chr.isLoggedinWorld() || disposed) {
return;
}
@@ -246,10 +250,12 @@ public class EventInstanceManager {
wL.unlock();
}
try {
em.getIv().invokeFunction("playerEntry", EventInstanceManager.this, chr);
} catch (ScriptException | NoSuchMethodException ex) {
ex.printStackTrace();
if (runEntryScript) {
try {
em.getIv().invokeFunction("playerEntry", EventInstanceManager.this, chr);
} catch (ScriptException | NoSuchMethodException ex) {
ex.printStackTrace();
}
}
}
@@ -372,7 +378,7 @@ public class EventInstanceManager {
private void registerExpeditionTeam(MapleExpedition exped, int recruitMap) {
expedition = exped;
for (MapleCharacter chr: exped.getMembers()) {
for (MapleCharacter chr: exped.getActiveMembers()) {
if (chr.getMapId() == recruitMap) {
registerPlayer(chr);
}
@@ -628,7 +634,6 @@ public class EventInstanceManager {
sL.lock();
try {
if(!eventCleared) em.disposeInstance(name);
em = null;
} finally {
sL.unlock();
}
@@ -636,10 +641,11 @@ public class EventInstanceManager {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
mapFactory.dispose(); // reactors issue on dispose event maps found thanks to MedicOP
mapFactory.dispose(); // issues from instantly disposing some event objects found thanks to MedicOP
wL.lock();
try {
mapFactory = null;
em = null;
} finally {
wL.unlock();
}
@@ -675,7 +681,6 @@ public class EventInstanceManager {
@Override
public void run() {
try {
if(em == null) return;
em.getIv().invokeFunction(methodName, EventInstanceManager.this);
} catch (ScriptException | NoSuchMethodException ex) {
ex.printStackTrace();
+43 -19
View File
@@ -22,7 +22,6 @@
package scripting.event;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@@ -116,13 +115,16 @@ public class EventManager {
ex.printStackTrace();
}
Collection<EventInstanceManager> eimList;
synchronized(instances) {
for(EventInstanceManager eim : instances.values()) {
eim.dispose(true);
}
eimList = getInstances();
instances.clear();
}
for(EventInstanceManager eim : eimList) {
eim.dispose(true);
}
List<EventInstanceManager> readyEims;
queueLock.lock();
try {
@@ -253,7 +255,9 @@ public class EventManager {
}
public Collection<EventInstanceManager> getInstances() {
return Collections.unmodifiableCollection(instances.values());
synchronized (instances) {
return new LinkedList<>(instances.values());
}
}
public EventInstanceManager newInstance(String name) {
@@ -265,7 +269,13 @@ public class EventManager {
ret.setName(name);
}
instances.put(name, ret);
synchronized (instances) {
if (instances.containsKey(name)) {
return null;
}
instances.put(name, ret);
}
return ret;
}
@@ -274,7 +284,10 @@ public class EventManager {
@Override
public void run() {
freeLobbyInstance(name);
instances.remove(name);
synchronized (instances) {
instances.remove(name);
}
}
}, ServerConstants.EVENT_LOBBY_DELAY * 1000);
}
@@ -379,14 +392,17 @@ public class EventManager {
if(!startLobbyInstance(lobbyId)) return false;
}
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", leader.getClient().getChannel()));
if(eim == null) {
EventInstanceManager eim;
try {
eim = (EventInstanceManager) (iv.invokeFunction("setup", leader.getClient().getChannel()));
instanceLocks.put(eim.getName(), lobbyId);
} catch (NullPointerException npe) {
if(lobbyId > -1) {
setLockLobby(lobbyId, false);
}
return false;
}
instanceLocks.put(eim.getName(), lobbyId);
eim.setLeader(leader);
exped.start();
@@ -442,14 +458,16 @@ public class EventManager {
}
}
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : leader.getId()));
if(eim == null) {
EventInstanceManager eim;
try {
eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : leader.getId()));
instanceLocks.put(eim.getName(), lobbyId);
} catch (NullPointerException npe) {
if(lobbyId > -1) {
setLockLobby(lobbyId, false);
}
return false;
}
instanceLocks.put(eim.getName(), lobbyId);
eim.setLeader(leader);
if(chr != null) eim.registerPlayer(chr);
@@ -500,14 +518,17 @@ public class EventManager {
if(!startLobbyInstance(lobbyId)) return false;
}
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", (Object) null));
if(eim == null) {
EventInstanceManager eim;
try {
eim = (EventInstanceManager) (iv.invokeFunction("setup", (Object) null));
instanceLocks.put(eim.getName(), lobbyId);
} catch (NullPointerException npe) {
if(lobbyId > -1) {
setLockLobby(lobbyId, false);
}
return false;
}
instanceLocks.put(eim.getName(), lobbyId);
eim.setLeader(leader);
eim.registerParty(party, map);
@@ -559,14 +580,17 @@ public class EventManager {
if(!startLobbyInstance(lobbyId)) return false;
}
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : party.getLeaderId()));
if(eim == null) {
EventInstanceManager eim;
try {
eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : party.getLeaderId()));
instanceLocks.put(eim.getName(), lobbyId);
} catch (NullPointerException npe) {
if(lobbyId > -1) {
setLockLobby(lobbyId, false);
}
return false;
}
instanceLocks.put(eim.getName(), lobbyId);
eim.setLeader(leader);
eim.registerParty(party, map);
@@ -100,6 +100,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction {
public void dispose() {
NPCScriptManager.getInstance().dispose(this);
getClient().announce(MaplePacketCreator.enableActions());
}
public void sendNext(String text) {
+16
View File
@@ -202,6 +202,10 @@ public class MapleTrade {
return exchangeMeso;
}
private boolean fitsMeso() {
return chr.canHoldMeso(exchangeMeso - getFee(exchangeMeso));
}
private boolean fitsInInventory() {
List<Pair<Item, MapleInventoryType>> tradeItems = new LinkedList<>();
for (Item item : exchangeItems) {
@@ -218,6 +222,18 @@ public class MapleTrade {
if (partner.isLocked()) {
local.complete1();
partner.complete1();
if (!local.fitsMeso()) {
cancelTrade(c);
c.message("There is not enough meso inventory space to complete the trade.");
partner.getChr().message("Partner does not have enough meso inventory space to complete the trade.");
return;
}
else if (!partner.fitsMeso()) {
cancelTrade(c);
c.message("Partner does not have enough meso inventory space to complete the trade.");
partner.getChr().message("There is not enough meso inventory space to complete the trade.");
return;
}
if (!local.fitsInInventory()) {
cancelTrade(c);
c.message("There is not enough inventory space to complete the trade.");
+78 -36
View File
@@ -23,11 +23,17 @@
package server.expeditions;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import net.server.PlayerStorage;
import net.server.Server;
import server.TimerManager;
import server.life.MapleMonster;
@@ -78,18 +84,18 @@ public class MapleExpedition {
private MapleExpeditionType type;
private boolean registering;
private MapleMap startMap;
private ArrayList<String> bossLogs;
private List<String> bossLogs;
private ScheduledFuture<?> schedule;
private List<MapleCharacter> members = new ArrayList<>();
private List<Integer> banned = new ArrayList<>();
private Map<Integer, String> members = new ConcurrentHashMap<>();
private List<Integer> banned = new CopyOnWriteArrayList<>();
private long startTime;
public MapleExpedition(MapleCharacter player, MapleExpeditionType met) {
leader = player;
members.add(leader);
members.put(player.getId(), player.getName());
startMap = player.getMap();
type = met;
bossLogs = new ArrayList<String>();
bossLogs = new CopyOnWriteArrayList<>();
beginRegistration();
}
@@ -109,7 +115,7 @@ public class MapleExpedition {
@Override
public void run() {
if (registering){
leader.getClient().getChannelServer().getExpeditions().remove(exped);
startMap.getChannelServer().getExpeditions().remove(exped);
startMap.broadcastMessage(MaplePacketCreator.serverNotice(6, "[Expedition] The time limit has been reached. Expedition has been disbanded."));
dispose(false);
@@ -134,7 +140,7 @@ public class MapleExpedition {
broadcastExped(MaplePacketCreator.removeClock());
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] The expedition has started! Good luck, brave heroes!"));
startTime = System.currentTimeMillis();
Server.getInstance().broadcastGMMessage(leader.getWorld(), MaplePacketCreator.serverNotice(6, "[Expedition] " + type.toString() + " Expedition started with leader: " + leader.getName()));
Server.getInstance().broadcastGMMessage(startMap.getWorld(), MaplePacketCreator.serverNotice(6, "[Expedition] " + type.toString() + " Expedition started with leader: " + leader.getName()));
}
public String addMember(MapleCharacter player) {
@@ -147,22 +153,21 @@ public class MapleExpedition {
if (members.size() >= type.getMaxSize()){ //Would be a miracle if anybody ever saw this
return "Sorry, this expedition is full!";
}
if (members.add(player)){
player.announce(MaplePacketCreator.getClock((int)(startTime - System.currentTimeMillis()) / 1000));
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + player.getName() + " has joined the expedition!"));
return "You have registered for the expedition successfully!";
}
return "Sorry, something went really wrong. Report this on the forum with a screenshot!";
members.put(player.getId(), player.getName());
player.announce(MaplePacketCreator.getClock((int)(startTime - System.currentTimeMillis()) / 1000));
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + player.getName() + " has joined the expedition!"));
return "You have registered for the expedition successfully!";
}
private void broadcastExped(byte [] data){
for (MapleCharacter member : members){
member.getClient().announce(data);
private void broadcastExped(byte[] packet){
for (MapleCharacter chr : getActiveMembers()){
chr.announce(packet);
}
}
public boolean removeMember(MapleCharacter chr) {
if(members.remove(chr)) {
if(members.remove(chr.getId()) != null) {
chr.announce(MaplePacketCreator.removeClock());
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + chr.getName() + " has left the expedition."));
chr.dropMessage(6, "[Expedition] You have left this expedition.");
@@ -175,9 +180,42 @@ public class MapleExpedition {
public MapleExpeditionType getType() {
return type;
}
public List<MapleCharacter> getMembers() {
return members;
public List<MapleCharacter> getActiveMembers() { // thanks MedicOP for figuring out an issue with broadcasting packets to offline members
PlayerStorage ps = startMap.getWorldServer().getPlayerStorage();
List<MapleCharacter> activeMembers = new LinkedList<>();
for (Integer chrid : getMembers().keySet()){
MapleCharacter chr = ps.getCharacterById(chrid);
if (chr != null && chr.isLoggedinWorld()) {
activeMembers.add(chr);
}
}
return activeMembers;
}
public Map<Integer, String> getMembers() {
return new HashMap<>(members);
}
public List<Entry<Integer, String>> getMemberList() {
List<Entry<Integer, String>> memberList = new LinkedList<>();
Entry<Integer, String> leaderEntry = null;
for (Entry<Integer, String> e : getMembers().entrySet()) {
if (!isLeader(e.getKey())) {
memberList.add(e);
} else {
leaderEntry = e;
}
}
if (leaderEntry != null) {
memberList.add(0, leaderEntry);
}
return memberList;
}
public MapleCharacter getLeader(){
@@ -189,16 +227,15 @@ public class MapleExpedition {
}
public boolean contains(MapleCharacter player) {
for (MapleCharacter member : members){
if (member.getId() == player.getId()){
return true;
}
}
return false;
return members.containsKey(player.getId());
}
public boolean isLeader(MapleCharacter player) {
return leader.equals(player);
return isLeader(player.getId());
}
public boolean isLeader(int playerid) {
return leader.getId() == playerid;
}
public boolean isRegistering(){
@@ -209,15 +246,20 @@ public class MapleExpedition {
return !registering;
}
public void ban(MapleCharacter player) {
if (!banned.contains(player.getId())) {
banned.add(player.getId());
members.remove(player);
public void ban(Entry<Integer, String> chr) {
int cid = chr.getKey();
if (!banned.contains(cid)) {
banned.add(cid);
members.remove(cid);
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + player.getName() + " has been banned from the expedition."));
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + chr.getValue() + " has been banned from the expedition."));
player.announce(MaplePacketCreator.removeClock());
player.dropMessage(6, "[Expedition] You have been banned from this expedition.");
MapleCharacter player = startMap.getWorldServer().getPlayerStorage().getCharacterById(cid);
if (player != null && player.isLoggedinWorld()) {
player.announce(MaplePacketCreator.removeClock());
player.dropMessage(6, "[Expedition] You have been banned from this expedition.");
}
}
}
@@ -225,7 +267,7 @@ public class MapleExpedition {
return startTime;
}
public ArrayList<String> getBossLogs(){
public List<String> getBossLogs(){
return bossLogs;
}
+262 -136
View File
@@ -55,7 +55,6 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import net.server.audit.locks.MonitoredReentrantLock;
import net.server.Server;
import net.server.channel.Channel;
import net.server.world.World;
import net.server.world.MapleParty;
@@ -72,6 +71,7 @@ import tools.Randomizer;
import net.server.audit.LockCollector;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.coordinator.MapleMonsterAggroCoordinator;
public class MapleMonster extends AbstractLoadedMapleLife {
private ChangeableStats ostats = null; //unused, v83 WZs offers no support for changeable stats.
@@ -100,6 +100,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
private MonitoredReentrantLock monsterLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB, true);
private MonitoredReentrantLock statiLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_STATI);
private MonitoredReentrantLock animationLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_ANI);
private MonitoredReentrantLock aggroUpdateLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_AGGRO);
public MapleMonster(int id, MapleMonsterStats stats) {
super(id);
@@ -578,31 +579,10 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
private Pair<MapleCharacter, Boolean> removeController() {
this.lockMonster();
try {
MapleCharacter chrController = getController();
boolean hadAggro = isControllerHasAggro();
if (chrController != null) { // this can/should only happen when a hidden gm attacks the monster
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
chrController.stopControllingMonster(this);
}
setController(null);
setControllerHasAggro(false);
setControllerKnowsAboutAggro(false);
return new Pair<>(chrController, hadAggro);
} finally {
this.unlockMonster();
}
}
public MapleCharacter killBy(final MapleCharacter killer) {
distributeExperience(killer != null ? killer.getId() : 0);
final Pair<MapleCharacter, Boolean> lastController = removeController();
final Pair<MapleCharacter, Boolean> lastController = aggroRemoveController();
final List<Integer> toSpawn = this.getRevives();
if (toSpawn != null) {
final MapleMap reviveMap = map;
@@ -675,7 +655,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true);
}
} else if (controller != null) {
mob.switchController(controller, aggro);
mob.aggroSwitchController(controller, aggro);
}
if(eim != null) {
@@ -725,10 +705,12 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
private synchronized void processMonsterKilled(boolean hasKiller) {
if(!hasKiller) {
if(!hasKiller) { // players won't gain EXP from a mob that has no killer, but a quest count they should
dispatchUpdateQuestMobCount();
}
this.aggroClearDamages();
MonsterListener[] listenersList;
statiLock.lock();
try {
@@ -794,46 +776,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
public boolean isAlive() {
return this.hp.get() > 0;
}
public MapleCharacter getController() {
monsterLock.lock();
try {
return controller.get();
} finally {
monsterLock.unlock();
}
}
public void setController(MapleCharacter controller) {
monsterLock.lock();
try {
this.controller = new WeakReference<>(controller);
} finally {
monsterLock.unlock();
}
}
public void switchController(MapleCharacter newController, boolean immediateAggro) {
this.lockMonster();
try {
MapleCharacter controllers = getController();
if (controllers == newController) {
return;
}
removeController();
newController.controlMonster(this, immediateAggro);
setController(newController);
if (immediateAggro) {
setControllerHasAggro(true);
}
setControllerKnowsAboutAggro(false);
} finally {
this.unlockMonster();
}
}
public void addListener(MonsterListener listener) {
statiLock.lock();
try {
@@ -843,46 +786,28 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
public boolean isControllerHasAggro() {
monsterLock.lock();
try {
return fake ? false : controllerHasAggro;
} finally {
monsterLock.unlock();
}
public MapleCharacter getController() {
return controller.get();
}
public void setControllerHasAggro(boolean controllerHasAggro) {
monsterLock.lock();
try {
if (fake) {
return;
}
this.controllerHasAggro = controllerHasAggro;
} finally {
monsterLock.unlock();
}
private void setController(MapleCharacter controller) {
this.controller = new WeakReference<>(controller);
}
public boolean isControllerHasAggro() {
return fake ? false : controllerHasAggro;
}
private void setControllerHasAggro(boolean controllerHasAggro) {
if (!fake) this.controllerHasAggro = controllerHasAggro;
}
public boolean isControllerKnowsAboutAggro() {
monsterLock.lock();
try {
return fake ? false : controllerKnowsAboutAggro;
} finally {
monsterLock.unlock();
}
return fake ? false : controllerKnowsAboutAggro;
}
public void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
monsterLock.lock();
try {
if (fake) {
return;
}
this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
} finally {
monsterLock.unlock();
}
private void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
if (!fake) this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
}
public byte[] makeBossHPBarPacket() {
@@ -956,15 +881,29 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
private MapleCharacter getActiveController() {
MapleCharacter chr = getController();
if (chr != null && chr.isLoggedinWorld() && chr.getMap() == this.getMap()) {
return chr;
} else {
return null;
}
}
private void broadcastMonsterStatusMessage(byte[] packet) {
map.broadcastMessage(packet, getPosition());
MapleCharacter chrController = getActiveController();
if (chrController != null && !chrController.isMapObjectVisible(MapleMonster.this)) {
chrController.announce(packet);
}
}
private int broadcastStatusEffect(final MonsterStatusEffect status) {
int animationTime = status.getSkill().getAnimationTime();
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null);
map.broadcastMessage(packet, getPosition());
MapleCharacter chrController = getController();
if (chrController != null && !chrController.isMapObjectVisible(this)) {
chrController.getClient().announce(packet);
}
broadcastMonsterStatusMessage(packet);
return animationTime;
}
@@ -1041,12 +980,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
public void run() {
if (isAlive()) {
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), status.getStati());
map.broadcastMessage(packet, getPosition());
MapleCharacter controller = getController();
if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) {
controller.getClient().announce(packet);
}
broadcastMonsterStatusMessage(packet);
}
statiLock.lock();
@@ -1148,12 +1082,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
public void run() {
if (isAlive()) {
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), stats);
map.broadcastMessage(packet, getPosition());
MapleCharacter controller = getController();
if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) {
controller.getClient().announce(packet);
}
broadcastMonsterStatusMessage(packet);
statiLock.lock();
try {
@@ -1168,7 +1097,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
};
final MonsterStatusEffect effect = new MonsterStatusEffect(stats, null, skill, true);
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), effect, reflection);
map.broadcastMessage(packet, getPosition());
broadcastMonsterStatusMessage(packet);
statiLock.lock();
try {
@@ -1180,11 +1109,6 @@ public class MapleMonster extends AbstractLoadedMapleLife {
statiLock.unlock();
}
MapleCharacter controller = getController();
if (controller != null && !controller.isMapObjectVisible(this)) {
controller.getClient().announce(packet);
}
map.getChannelServer().registerMobStatus(map.getId(), effect, cancelTask, duration);
}
@@ -1193,30 +1117,28 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
public void resetMobPosition(Point newPoint) {
removeController();
aggroRemoveController();
setPosition(newPoint);
map.broadcastMessage(MaplePacketCreator.moveMonster(this.getObjectId(), false, -1, 0, 0, 0, this.getPosition(), this.getIdleMovement()));
map.moveMonster(this, this.getPosition());
map.updateMonsterController(this);
aggroUpdateController();
}
private void debuffMobStat(MonsterStatus stat) {
MonsterStatusEffect oldEffect;
statiLock.lock();
try {
if (isBuffed(stat)) {
final MonsterStatusEffect oldEffect = stati.get(stat);
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati());
map.broadcastMessage(packet, getPosition());
MapleCharacter chrController = getController();
if (chrController != null && !chrController.isMapObjectVisible(MapleMonster.this)) {
chrController.getClient().announce(packet);
}
stati.remove(stat);
}
oldEffect = stati.remove(stat);
} finally {
statiLock.unlock();
}
if (oldEffect != null) {
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati());
broadcastMonsterStatusMessage(packet);
}
}
public void debuffMob(int skillid) {
@@ -1280,7 +1202,11 @@ public class MapleMonster extends AbstractLoadedMapleLife {
public MapleMap getMap() {
return map;
}
public MapleMonsterAggroCoordinator getMapAggroCoordinator() {
return map.getAggroCoordinator();
}
public List<Pair<Integer, Integer>> getSkills() {
return stats.getSkills();
}
@@ -1679,6 +1605,206 @@ public class MapleMonster extends AbstractLoadedMapleLife {
changeLevelByDifficulty(difficulty, pqMob);
}
private MapleCharacter getNextControllerCandidate() {
int mincontrolled = Integer.MAX_VALUE;
MapleCharacter newController = null;
int mincontrolleddead = Integer.MAX_VALUE;
MapleCharacter newControllerDead = null;
for (MapleCharacter chr : getMap().getAllPlayers()) {
if (!chr.isHidden()) {
int ctrlMonsSize = chr.getNumControlledMonsters();
if (chr.isAlive()) {
if (ctrlMonsSize < mincontrolled) {
mincontrolled = ctrlMonsSize;
newController = chr;
}
} else {
if (ctrlMonsSize < mincontrolleddead) {
mincontrolleddead = ctrlMonsSize;
newControllerDead = chr;
}
}
}
}
return newController != null ? newController : newControllerDead;
}
/**
* Removes controllability status from the current controller of this mob.
*
*/
private Pair<MapleCharacter, Boolean> aggroRemoveController() {
MapleCharacter chrController;
boolean hadAggro;
aggroUpdateLock.lock();
try {
chrController = getActiveController();
hadAggro = isControllerHasAggro();
this.setController(null);
this.setControllerHasAggro(false);
this.setControllerKnowsAboutAggro(false);
} finally {
aggroUpdateLock.unlock();
}
if (chrController != null) { // this can/should only happen when a hidden gm attacks the monster
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
chrController.stopControllingMonster(this);
}
return new Pair<>(chrController, hadAggro);
}
/**
* Pass over the mob controllability and updates aggro status on the new
* player controller.
*
*/
public void aggroSwitchController(MapleCharacter newController, boolean immediateAggro) {
if (aggroUpdateLock.tryLock()) {
try {
MapleCharacter prevController = getController();
if (prevController == newController) {
return;
}
aggroRemoveController();
if (!(newController != null && newController.isLoggedinWorld() && newController.getMap() == this.getMap())) {
return;
}
this.setController(newController);
this.setControllerHasAggro(immediateAggro);
this.setControllerKnowsAboutAggro(false);
} finally {
aggroUpdateLock.unlock();
}
newController.announce(MaplePacketCreator.controlMonster(this, false, immediateAggro));
newController.controlMonster(this);
}
}
/**
* Automagically finds a new controller for the given monster from the chars
* on the map it is from...
*
*/
public void aggroUpdateController() {
MapleCharacter chrController = this.getActiveController();
if (chrController != null && chrController.isAlive()) {
return;
}
MapleCharacter newController = getNextControllerCandidate();
if (newController == null) { // was a new controller found? (if not no one is on the map)
return;
}
this.aggroSwitchController(newController, false);
}
/**
* Ensures controllability removal of the current player controller, and
* fetches for any player on the map to start controlling in place.
*
*/
public void aggroRedirectController() {
this.aggroRemoveController(); // don't care if new controller not found, at least remove current controller
this.aggroUpdateController();
}
/**
* Returns the current aggro status on the specified player, or null if the
* specified player is currently not this mob's controller.
*
*/
public Boolean aggroMoveLifeUpdate(MapleCharacter player) {
MapleCharacter chrController = getController();
if (chrController != null && player.getId() == chrController.getId()) {
boolean aggro = this.isControllerHasAggro();
if (aggro) {
this.setControllerKnowsAboutAggro(true);
}
return aggro;
} else {
return null;
}
}
/**
* Refreshes auto aggro for the player passed as parameter, does nothing if
* there is already an active controller for this mob.
*
*/
public void aggroAutoAggroUpdate(MapleCharacter player) {
MapleCharacter chrController = this.getActiveController();
if (chrController == null) {
this.aggroSwitchController(player, true);
} else if (chrController.getId() == player.getId()) {
this.setControllerHasAggro(true);
}
}
/**
* Applied damage input for this mob, enough damage taken implies
* an aggro target update for the attacker shortly.
*
*/
public void aggroMonsterDamage(MapleCharacter attacker, int damage) {
MapleMonsterAggroCoordinator mmac = this.getMapAggroCoordinator();
mmac.addAggroDamage(this, attacker.getId(), damage);
MapleCharacter chrController = this.getController(); // aggro based on DPS rather than first-come-first-served, now live after suggestions thanks to MedicOP, Thora, Vcoc
if (chrController != attacker) {
if (this.getMapAggroCoordinator().isLeadingCharacterAggro(this, attacker)) {
this.aggroSwitchController(attacker, true);
}
/*
For some reason, some mobs loses aggro on controllers if other players also attacks them.
Maybe it was intended by Nexon to interchange controllers at every attack...
else if (chrController != null) {
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
chrController.announce(MaplePacketCreator.controlMonster(this, false, true));
}
*/
} else {
this.setControllerHasAggro(true);
}
}
/**
* Clears all applied damage input for this mob, doesn't refresh target aggro.
*
*/
public void aggroClearDamages() {
this.getMapAggroCoordinator().removeAggroEntries(this);
}
/**
* Clears this mob aggro on the current controller.
*
*/
public void aggroResetAggro() {
aggroUpdateLock.lock();
try {
this.setControllerHasAggro(false);
this.setControllerKnowsAboutAggro(false);
} finally {
aggroUpdateLock.unlock();
}
}
public final void disposeLocks() {
LockCollector.getInstance().registerDisposeAction(new Runnable() {
@Override
@@ -52,8 +52,9 @@ public class MapleMonsterInformationProvider {
return instance;
}
private final Map<Integer, List<MonsterDropEntry>> drops = new HashMap<>();
private final Map<Integer, List<MonsterDropEntry>> drops = new HashMap<>();
private final List<MonsterGlobalDropEntry> globaldrops = new ArrayList<>();
private final Map<Integer, List<MonsterGlobalDropEntry>> continentdrops = new HashMap<>();
private final Map<Integer, List<Integer>> dropsChancePool = new HashMap<>(); // thanks to ronan
private final Set<Integer> hasNoMultiEquipDrops = new HashSet<>();
@@ -70,9 +71,24 @@ public class MapleMonsterInformationProvider {
protected MapleMonsterInformationProvider() {
retrieveGlobal();
}
public final List<MonsterGlobalDropEntry> getGlobalDrop() {
return globaldrops;
public final List<MonsterGlobalDropEntry> getRelevantGlobalDrops(int mapid) {
int continentid = mapid / 100000000;
List<MonsterGlobalDropEntry> contiItems = continentdrops.get(continentid);
if (contiItems == null) { // continent separated global drops found thanks to marcuswoon
contiItems = new ArrayList<>();
for (MonsterGlobalDropEntry e : globaldrops) {
if (e.continentid < 0 || e.continentid == continentid) {
contiItems.add(e);
}
}
continentdrops.put(continentid, contiItems);
}
return contiItems;
}
private void retrieveGlobal() {
@@ -90,8 +106,7 @@ public class MapleMonsterInformationProvider {
new MonsterGlobalDropEntry(
rs.getInt("itemid"),
rs.getInt("chance"),
rs.getInt("continent"),
rs.getByte("dropType"),
rs.getByte("continent"),
rs.getInt("minimum_quantity"),
rs.getInt("maximum_quantity"),
rs.getShort("questid")));
+3 -4
View File
@@ -25,15 +25,14 @@ package server.life;
* @author LightPepsi
*/
public class MonsterGlobalDropEntry {
public MonsterGlobalDropEntry(int itemId, int chance, int continent, byte dropType, int Minimum, int Maximum, short questid) {
public MonsterGlobalDropEntry(int itemId, int chance, int continent, int Minimum, int Maximum, short questid) {
this.itemId = itemId;
this.chance = chance;
this.dropType = dropType;
this.questid = questid;
this.continentid = continent;
this.Minimum = Minimum;
this.Maximum = Maximum;
}
public byte dropType;
public int itemId, chance, Minimum, Maximum;
public int itemId, chance, Minimum, Maximum, continentid;
public short questid;
}
+18 -11
View File
@@ -64,6 +64,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
private List<Pair<String, Byte>> messages = new LinkedList<>();
private List<SoldItem> sold = new LinkedList<>();
private AtomicBoolean open = new AtomicBoolean();
private boolean published = false;
private MapleMap map;
private Lock visitorLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.VISITOR_MERCH, true);
@@ -100,10 +101,14 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
visitorLock.lock();
try {
byte count = 0;
for (MapleCharacter visitor : visitors) {
if (visitor != null) {
count++;
if (this.isOpen()) {
for (MapleCharacter visitor : visitors) {
if (visitor != null) {
count++;
}
}
} else {
count = (byte) (visitors.length + 1);
}
return new byte[]{count, (byte) (visitors.length + 1)};
@@ -471,15 +476,12 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
return false;
}
public void addItem(MaplePlayerShopItem item) {
public boolean addItem(MaplePlayerShopItem item) {
synchronized (items) {
if (items.size() >= 16) return false;
items.add(item);
}
try {
this.saveItems(false);
} catch (SQLException ex) {
ex.printStackTrace();
return true;
}
}
@@ -521,13 +523,18 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
public void setDescription(String description) {
this.description = description;
}
public boolean isPublished() {
return published;
}
public boolean isOpen() {
return open.get();
}
public void setOpen(boolean set) {
open.getAndSet(set);
published = true;
}
public int getItemId() {
+28 -106
View File
@@ -62,6 +62,7 @@ import net.server.audit.locks.MonitoredReentrantReadWriteLock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import java.lang.ref.WeakReference;
import net.server.Server;
import net.server.coordinator.MapleMonsterAggroCoordinator;
import net.server.channel.Channel;
import net.server.world.World;
import scripting.map.MapScriptManager;
@@ -140,6 +141,7 @@ public class MapleMap {
private int fieldType;
private int fieldLimit = 0;
private int mobCapacity = -1;
private MapleMonsterAggroCoordinator aggroMonitor = null; // aggroMonitor activity in sync with itemMonitor
private ScheduledFuture<?> mapMonitor = null;
private ScheduledFuture<?> itemMonitor = null;
private ScheduledFuture<?> expireItemsTask = null;
@@ -185,6 +187,8 @@ public class MapleMap {
final ReentrantReadWriteLock objectLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.MAP_OBJS, true);
objectRLock = objectLock.readLock();
objectWLock = objectLock.writeLock();
aggroMonitor = new MapleMonsterAggroCoordinator();
}
public void setEventInstance(EventInstanceManager eim) {
@@ -709,7 +713,7 @@ public class MapleMap {
if(useBaseRate) chRate = 1;
final MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance();
final List<MonsterGlobalDropEntry> globalEntry = mi.getGlobalDrop();
final List<MonsterGlobalDropEntry> globalEntry = mi.getRelevantGlobalDrops(this.getId());
final List<MonsterDropEntry> dropEntry = new ArrayList<>();
final List<MonsterDropEntry> visibleQuestEntry = new ArrayList<>();
@@ -777,6 +781,7 @@ public class MapleMap {
if(itemMonitorTimeout == 0) {
if(itemMonitor != null) {
stopItemMonitor();
aggroMonitor.stopAggroCoordinator();
}
return;
@@ -1144,12 +1149,10 @@ public class MapleMap {
}
public List<MapleCharacter> getAllPlayers() {
List<MapleCharacter> character = new LinkedList<>();
List<MapleCharacter> character;
chrRLock.lock();
try {
for (MapleCharacter a : characters) {
character.add(a);
}
character = new ArrayList<>(characters);
} finally {
chrRLock.unlock();
}
@@ -1316,7 +1319,7 @@ public class MapleMap {
if (mons != null) {
if (mons.getId() == 8800000) {
makeMonsterReal(mons);
updateMonsterController(mons);
mons.aggroUpdateController();
break;
}
}
@@ -1552,88 +1555,6 @@ public class MapleMap {
}
}
private MapleCharacter getNextControllerCandidate() {
int mincontrolled = Integer.MAX_VALUE;
MapleCharacter newController = null;
int mincontrolleddead = Integer.MAX_VALUE;
MapleCharacter newControllerDead = null;
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (!chr.isHidden()) {
int ctrlMonsSize = chr.getControlledMonsters().size();
if (chr.isAlive()) {
if (ctrlMonsSize < mincontrolled) {
mincontrolled = ctrlMonsSize;
newController = chr;
}
} else {
if (ctrlMonsSize < mincontrolleddead) {
mincontrolleddead = ctrlMonsSize;
newControllerDead = chr;
}
}
}
}
} finally {
chrRLock.unlock();
}
return newController != null ? newController : newControllerDead;
}
/**
* Automagically finds a new controller for the given monster from the chars
* on the map...
*
* @param monster
*/
public void updateMonsterController(MapleMonster monster) {
monster.lockMonster();
try {
if (!monster.isAlive()) {
return;
}
MapleCharacter newController;
MapleCharacter chrController = monster.getController();
if (chrController != null) {
if (chrController.getMap() != this) {
chrController.stopControllingMonster(monster);
newController = getNextControllerCandidate();
} else {
if (chrController.isAlive()) {
return;
}
newController = getNextControllerCandidate();
if (newController == null || !newController.isAlive()) {
return;
}
chrController.stopControllingMonster(monster);
}
} else {
newController = getNextControllerCandidate();
}
if (newController != null) { // was a new controller found? (if not no one is on the map)
if (monster.isFirstAttack()) {
newController.controlMonster(monster, true);
monster.setControllerHasAggro(true);
monster.setControllerKnowsAboutAggro(true);
} else {
newController.controlMonster(monster, false);
}
}
} finally {
monster.unlockMonster();
}
}
private Map<Integer, MapleMapObject> getCopyMapObjects() {
objectRLock.lock();
try {
@@ -1862,7 +1783,7 @@ public class MapleMap {
}
});
updateMonsterController(monster);
monster.aggroUpdateController();
if (monster.hasBossHPBar()) {
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
@@ -1948,7 +1869,7 @@ public class MapleMap {
}
}, null);
updateMonsterController(monster);
monster.aggroUpdateController();
if (monster.hasBossHPBar()) {
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
@@ -1993,7 +1914,8 @@ public class MapleMap {
c.announce(MaplePacketCreator.spawnMonster(monster, true, effect));
}
});
updateMonsterController(monster);
monster.aggroUpdateController();
if (monster.hasBossHPBar()) {
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
@@ -2019,7 +1941,8 @@ public class MapleMap {
public void makeMonsterReal(final MapleMonster monster) {
monster.setFake(false);
broadcastMessage(MaplePacketCreator.makeMonsterReal(monster));
updateMonsterController(monster);
monster.aggroUpdateController();
}
public void spawnReactor(final MapleReactor reactor) {
@@ -2403,7 +2326,10 @@ public class MapleMap {
chr.setMapId(mapid);
if (chrSize == 1) {
if(!hasItemMonitor()) startItemMonitor();
if(!hasItemMonitor()) {
startItemMonitor();
aggroMonitor.startAggroCoordinator();
}
if (onFirstUserEnter.length() != 0 && !chr.hasEntered(onFirstUserEnter, mapid) && MapScriptManager.getInstance().scriptExists(onFirstUserEnter, true)) {
chr.enteredScript(onFirstUserEnter, mapid);
@@ -2688,19 +2614,8 @@ public class MapleMap {
broadcastGMMessage(MaplePacketCreator.removePlayerFromMap(chr.getId()));
}
for (MapleMonster monster : chr.getControlledMonsters()) {
monster.lockMonster();
try {
monster.setController(null);
monster.setControllerHasAggro(false);
monster.setControllerKnowsAboutAggro(false);
updateMonsterController(monster);
} finally {
monster.unlockMonster();
}
}
chr.leaveMap();
for (MapleSummon summon : new ArrayList<>(chr.getSummonsValues())) {
if (summon.isStationary()) {
chr.cancelEffectFromBuffStat(MapleBuffStat.PUPPET);
@@ -2926,7 +2841,7 @@ public class MapleMap {
if (isNonRangedType(o.getType())) {
o.sendSpawnData(mapleClient);
} else if (o.getType() == MapleMapObjectType.MONSTER) {
updateMonsterController((MapleMonster) o);
((MapleMonster) o).aggroUpdateController();
} else if (o.getType() == MapleMapObjectType.SUMMON) {
MapleSummon summon = (MapleSummon) o;
if (summon.getOwner() == chr) {
@@ -3038,6 +2953,10 @@ public class MapleMap {
mapArea.setBounds(vrLeft, vrTop, vrRight - vrLeft, vrBottom - vrTop);
}
public MapleMonsterAggroCoordinator getAggroCoordinator() {
return aggroMonitor;
}
/**
* it's threadsafe, gtfo :D
*
@@ -4091,6 +4010,9 @@ public class MapleMap {
chrWLock.lock();
try {
aggroMonitor.dispose();
aggroMonitor = null;
if(itemMonitor != null) {
itemMonitor.cancel(false);
itemMonitor = null;
+30 -9
View File
@@ -103,11 +103,15 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
visitorLock.lock();
try {
byte count = 0;
for (MapleCharacter visitor : visitors) {
if (visitor != null) {
count++;
//if (this.isOpen()) {
for (MapleCharacter visitor : visitors) {
if (visitor != null) {
count++;
}
}
}
//} else { shouldn't happen since there isn't a "closed" state for player shops.
// count = (byte) (visitors.length + 1);
//}
return new byte[]{count, (byte) visitors.length};
} finally {
@@ -198,9 +202,12 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
}
}
public void addItem(MaplePlayerShopItem item) {
public boolean addItem(MaplePlayerShopItem item) {
synchronized (items) {
if (items.size() >= 16) return false;
items.add(item);
return true;
}
}
@@ -241,7 +248,7 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
* @param item
* @param quantity
*/
public void buy(MapleClient c, int item, short quantity) {
public boolean buy(MapleClient c, int item, short quantity) {
synchronized (items) {
if (isVisitor(c.getPlayer())) {
MaplePlayerShopItem pItem = items.get(item);
@@ -250,10 +257,10 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
newItem.setQuantity((short) ((pItem.getItem().getQuantity() * quantity)));
if (quantity < 1 || !pItem.isExist() || pItem.getBundles() < quantity) {
c.announce(MaplePacketCreator.enableActions());
return;
return false;
} else if (newItem.getInventoryType().equals(MapleInventoryType.EQUIP) && newItem.getQuantity() > 1) {
c.announce(MaplePacketCreator.enableActions());
return;
return false;
}
MapleKarmaManipulator.toggleKarmaFlagToUntradeable(newItem);
@@ -264,6 +271,12 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
if (c.getPlayer().getMeso() >= price) {
if (canBuy(c, newItem)) {
if (!owner.canHoldMeso(price)) {
owner.dropMessage(1, "Transaction failed since the shop owner can't hold any more mesos.");
c.announce(MaplePacketCreator.enableActions());
return false;
}
c.getPlayer().gainMeso(-price, false);
owner.gainMeso(price, true);
@@ -286,13 +299,21 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
}
} else {
c.getPlayer().dropMessage(1, "Your inventory is full. Please clean a slot before buying this item.");
c.announce(MaplePacketCreator.enableActions());
return false;
}
} else {
c.getPlayer().dropMessage(1, "You don't have enough mesos to purchase this item.");
c.announce(MaplePacketCreator.enableActions());
return false;
}
return true;
} finally {
visitorLock.unlock();
}
} else {
return false;
}
}
}
@@ -418,9 +439,9 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
}
public void closeShop() {
owner.getMap().broadcastMessage(MaplePacketCreator.removePlayerShopBox(this));
clearChatLog();
removeVisitors();
owner.getMap().broadcastMessage(MaplePacketCreator.removePlayerShopBox(this));
}
public void sendShop(MapleClient c) {
@@ -26,7 +26,7 @@ import tools.data.output.LittleEndianWriter;
public class AbsoluteLifeMovement extends AbstractLifeMovement {
private Point pixelsPerSecond;
private int unk;
private int fh;
public AbsoluteLifeMovement(int type, Point position, int duration, int newstate) {
super(type, position, duration, newstate);
@@ -40,12 +40,12 @@ public class AbsoluteLifeMovement extends AbstractLifeMovement {
this.pixelsPerSecond = wobble;
}
public int getUnk() {
return unk;
public int getFh() { // unk -> fh, thanks Spoon for pointing this out
return fh;
}
public void setUnk(int unk) {
this.unk = unk;
public void setFh(int fh) {
this.fh = fh;
}
@Override
@@ -55,7 +55,7 @@ public class AbsoluteLifeMovement extends AbstractLifeMovement {
lew.writeShort(getPosition().y);
lew.writeShort(pixelsPerSecond.x);
lew.writeShort(pixelsPerSecond.y);
lew.writeShort(unk);
lew.writeShort(fh);
lew.write(getNewstate());
lew.writeShort(getDuration());
}
+6 -6
View File
@@ -25,18 +25,18 @@ import java.awt.Point;
import tools.data.output.LittleEndianWriter;
public class ChairMovement extends AbstractLifeMovement {
private int unk;
private int fh;
public ChairMovement(int type, Point position, int duration, int newstate) {
super(type, position, duration, newstate);
}
public int getUnk() {
return unk;
public int getFh() {
return fh;
}
public void setUnk(int unk) {
this.unk = unk;
public void setFh(int fh) {
this.fh = fh;
}
@Override
@@ -44,7 +44,7 @@ public class ChairMovement extends AbstractLifeMovement {
lew.write(getType());
lew.writeShort(getPosition().x);
lew.writeShort(getPosition().y);
lew.writeShort(unk);
lew.writeShort(fh);
lew.write(getNewstate());
lew.writeShort(getDuration());
}
+12 -12
View File
@@ -26,8 +26,8 @@ import tools.data.output.LittleEndianWriter;
public class JumpDownMovement extends AbstractLifeMovement {
private Point pixelsPerSecond;
private int unk;
private int fh;
private int originFh;
public JumpDownMovement(int type, Point position, int duration, int newstate) {
super(type, position, duration, newstate);
@@ -41,22 +41,22 @@ public class JumpDownMovement extends AbstractLifeMovement {
this.pixelsPerSecond = wobble;
}
public int getUnk() {
return unk;
}
public void setUnk(int unk) {
this.unk = unk;
}
public int getFH() {
public int getFh() {
return fh;
}
public void setFH(int fh) {
public void setFh(int fh) {
this.fh = fh;
}
public int getOriginFh() {
return originFh;
}
public void setOriginFh(int fh) { // fh actually originFh, thanks Spoon for pointing this out
this.originFh = fh;
}
@Override
public void serialize(LittleEndianWriter lew) {
lew.write(getType());
@@ -64,8 +64,8 @@ public class JumpDownMovement extends AbstractLifeMovement {
lew.writeShort(getPosition().y);
lew.writeShort(pixelsPerSecond.x);
lew.writeShort(pixelsPerSecond.y);
lew.writeShort(unk);
lew.writeShort(fh);
lew.writeShort(originFh);
lew.write(getNewstate());
lew.writeShort(getDuration());
}
+5 -11
View File
@@ -23,7 +23,6 @@ package server.quest.actions;
import client.MapleCharacter;
import constants.ServerConstants;
import net.server.world.World;
import provider.MapleData;
import provider.MapleDataTool;
import server.quest.MapleQuest;
@@ -53,15 +52,10 @@ public class ExpAction extends MapleQuestAction {
}
public static void runAction(MapleCharacter chr, int gain) {
if (chr.isBeginnerJob() && chr.getLevel() < 10) {
chr.gainExp(gain, true, true);
} else {
if(!ServerConstants.USE_QUEST_RATE) {
chr.gainExp(gain * chr.getExpRate(), true, true);
} else {
World w = chr.getClient().getWorldServer();
chr.gainExp(gain * w.getExpRate() * w.getQuestRate(), true, true);
}
}
if (!ServerConstants.USE_QUEST_RATE) {
chr.gainExp(gain * chr.getExpRate(), true, true);
} else {
chr.gainExp(gain * chr.getQuestExpRate(), true, true);
}
}
}
+3 -5
View File
@@ -23,7 +23,6 @@ package server.quest.actions;
import client.MapleCharacter;
import constants.ServerConstants;
import net.server.world.World;
import provider.MapleData;
import provider.MapleDataTool;
import server.quest.MapleQuest;
@@ -54,14 +53,13 @@ public class MesoAction extends MapleQuestAction {
}
public static void runAction(MapleCharacter chr, int gain) {
if(gain < 0) {
if (gain < 0) {
chr.gainMeso(gain, true, false, true);
} else {
if(!ServerConstants.USE_QUEST_RATE) {
if (!ServerConstants.USE_QUEST_RATE) {
chr.gainMeso(gain * chr.getMesoRate(), true, false, true);
} else {
World w = chr.getClient().getWorldServer();
chr.gainMeso(gain * w.getMesoRate() * w.getQuestRate(), true, false, true);
chr.gainMeso(gain * chr.getQuestMesoRate(), true, false, true);
}
}
}
+2 -2
View File
@@ -40,8 +40,8 @@ public class LogHelper {
String log = expedition.getType().toString() + " EXPEDITION\r\n";
log += getTimeString(expedition.getStartTime()) + "\r\n";
for (MapleCharacter member : expedition.getMembers()){
log += ">>" + member.getName() + "\r\n";
for (String memberName : expedition.getMembers().values()){
log += ">>" + memberName + "\r\n";
}
log += "BOSS KILLS\r\n";
for (String message: expedition.getBossLogs()){
+60 -16
View File
@@ -1841,6 +1841,30 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
/**
* Guild Name & Mark update packet, thanks to Arnah (Vertisy)
*
* @param guildName The Guild name, blank for nothing.
*/
public static byte[] guildNameChanged(int chrid, String guildName){
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.GUILD_NAME_CHANGED.getValue());
mplew.writeInt(chrid);
mplew.writeMapleAsciiString(guildName);
return mplew.getPacket();
}
public static byte[] guildMarkChanged(int chrid, MapleGuild guild){
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.GUILD_MARK_CHANGED.getValue());
mplew.writeInt(chrid);
mplew.writeShort(guild.getLogoBG());
mplew.write(guild.getLogoBGColor());
mplew.writeShort(guild.getLogo());
mplew.write(guild.getLogoColor());
return mplew.getPacket();
}
/**
* Gets a packet spawning a player as a mapobject to other clients.
*
@@ -3384,7 +3408,7 @@ public class MaplePacketCreator {
public static byte[] showBuffeffect(int cid, int skillid, int effectid) {
return showBuffeffect(cid, skillid, effectid, (byte) 3);
}
public static byte[] showBuffeffect(int cid, int skillid, int effectid, byte direction) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SHOW_FOREIGN_EFFECT.getValue());
@@ -3396,6 +3420,19 @@ public class MaplePacketCreator {
mplew.writeLong(0);
return mplew.getPacket();
}
public static byte[] showBuffeffect(int cid, int skillid, int skilllv, int effectid, byte direction) { // updated packet structure found thanks to Rien dev team
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SHOW_FOREIGN_EFFECT.getValue());
mplew.writeInt(cid);
mplew.write(effectid);
mplew.writeInt(skillid);
mplew.write(0);
mplew.write(skilllv);
mplew.write(direction);
return mplew.getPacket();
}
public static byte[] showOwnBuffEffect(int skillid, int effectid) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
@@ -4597,13 +4634,21 @@ public class MaplePacketCreator {
mplew.writeInt(skillId);
return mplew.getPacket();
}
public static byte[] showMagnet(int mobid, byte success) { // Monster Magnet
public static byte[] catchMonster(int mobOid, byte success) { // updated packet structure found thanks to Rien dev team
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SHOW_MAGNET.getValue());
mplew.writeInt(mobid);
mplew.writeShort(SendOpcode.CATCH_MONSTER.getValue());
mplew.writeInt(mobOid);
mplew.write(success);
return mplew.getPacket();
}
public static byte[] catchMonster(int mobOid, int itemid, byte success) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CATCH_MONSTER_WITH_ITEM.getValue());
mplew.writeInt(mobOid);
mplew.writeInt(itemid);
mplew.write(success);
mplew.skip(10); //Mmmk
return mplew.getPacket();
}
@@ -4932,15 +4977,6 @@ public class MaplePacketCreator {
return mplew.getPacket();
}
public static byte[] catchMonster(int monsobid, int itemid, byte success) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.CATCH_MONSTER.getValue());
mplew.writeInt(monsobid);
mplew.writeInt(itemid);
mplew.write(success);
return mplew.getPacket();
}
public static byte[] catchMessage(int message) { // not done, I guess
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.BRIDLE_MOB_CATCH_FAIL.getValue());
@@ -5599,7 +5635,7 @@ public class MaplePacketCreator {
}
return mplew.getPacket();
}
public static byte[] hiredMerchantOwnerLeave() {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
@@ -5607,6 +5643,14 @@ public class MaplePacketCreator {
mplew.write(0);
return mplew.getPacket();
}
public static byte[] hiredMerchantOwnerMaintenanceLeave() {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
mplew.write(PlayerInteractionHandler.Action.REAL_CLOSE_MERCHANT.getCode());
mplew.write(5);
return mplew.getPacket();
}
public static byte[] hiredMerchantMaintenanceMessage() {
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(5);