WK charges fix + Job level cap + MapleQuestItemFetcher

Fixed WK charges not overriding one another and some concurrency issues within MapleMap and EventInstanceManager.
New feature: job level cap, limits EXP gain until job advancement is done.
New tool: MapleQuestItemFetcher, searches through the server files for missing quest items and reports the results.
This commit is contained in:
ronancpl
2017-11-07 10:44:00 -02:00
parent 1fead59c57
commit 624aca375e
164 changed files with 26482 additions and 35341 deletions

View File

@@ -110,7 +110,7 @@ public enum MapleBuffStat {
// needs Soul Stone
//end incorrect buffstats
//WIND_WALK(0x400000000L, true),
WIND_WALK(0x400000000L, true),
ARAN_COMBO(0x1000000000L, true),
COMBO_DRAIN(0x2000000000L, true),
COMBO_BARRIER(0x4000000000L, true),

View File

@@ -2984,10 +2984,18 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
private List<Pair<MapleBuffStat, MapleBuffStatValueHolder>> cancelEffectInternal(MapleStatEffect effect, boolean overwrite, long startTime, Set<MapleBuffStat> removedStats) {
Map<MapleBuffStat, MapleBuffStatValueHolder> buffstats;
Map<MapleBuffStat, MapleBuffStatValueHolder> buffstats = null;
MapleBuffStat ombs;
if (!overwrite) { // is removing the source effect, meaning every effect from this srcid is being purged
buffstats = extractCurrentBuffStats(effect);
} else { // is dropping ALL current statups that uses same stats as the given effect
} else if ((ombs = getSingletonStatupFromEffect(effect)) != null) { // removing all effects of a buff having non-shareable buff stat.
MapleBuffStatValueHolder mbsvh = effects.get(ombs);
if(mbsvh != null) {
buffstats = extractCurrentBuffStats(mbsvh.effect);
}
}
if (buffstats == null) { // all else, is dropping ALL current statups that uses same stats as the given effect
buffstats = extractLeastRelevantStatEffectsIfFull(effect);
}
@@ -3178,7 +3186,17 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
return extractedStatBuffs;
}
private boolean isSingletonStatup(MapleBuffStat mbs) {
private static MapleBuffStat getSingletonStatupFromEffect(MapleStatEffect mse) {
for(Pair<MapleBuffStat, Integer> mbs : mse.getStatups()) {
if(isSingletonStatup(mbs.getLeft())) {
return mbs.getLeft();
}
}
return null;
}
private static boolean isSingletonStatup(MapleBuffStat mbs) {
switch(mbs) { //HPREC and MPREC are supposed to be singleton
case COUPON_EXP1:
case COUPON_EXP2:
@@ -3890,9 +3908,17 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
return maxhp;
}
public int getMaxLevel() {
public int getMaxClassLevel() {
return isCygnus() ? 120 : 200;
}
public int getMaxLevel() {
if(!ServerConstants.USE_ENFORCE_JOB_LEVEL_RANGE || isGmJob()) {
return getMaxClassLevel();
}
return GameConstants.getJobMaxLevel(job);
}
public int getMaxMp() {
return maxmp;
@@ -4691,12 +4717,12 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
public void increaseGuildCapacity() { //hopefully nothing is null
if (getMeso() < getGuild().getIncreaseGuildCost(getGuild().getCapacity())) {
if (getMeso() < MapleGuild.getIncreaseGuildCost(getGuild().getCapacity())) {
dropMessage(1, "You don't have enough mesos.");
return;
}
Server.getInstance().increaseGuildCapacity(guildid);
gainMeso(-getGuild().getIncreaseGuildCost(getGuild().getCapacity()), true, false, false);
gainMeso(-MapleGuild.getIncreaseGuildCost(getGuild().getCapacity()), true, false, false);
}
public boolean isActiveBuffedValue(int skillid) {
@@ -4741,13 +4767,18 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
public boolean isCygnus() {
return getJobType() == 1;
}
public boolean isGmJob() {
int jn = job.getJobNiche();
return jn >= 8 && jn <= 9;
}
public boolean isAran() {
return getJob().getId() >= 2000 && getJob().getId() <= 2112;
return job.getId() >= 2000 && job.getId() <= 2112;
}
public boolean isBeginnerJob() {
return (getJob().getId() == 0 || getJob().getId() == 1000 || getJob().getId() == 2000);
return (job.getId() == 0 || job.getId() == 1000 || job.getId() == 2000);
}
public boolean isGM() {
@@ -4852,9 +4883,9 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
}
level++;
if (level >= getMaxLevel()) {
if (level >= getMaxClassLevel()) {
exp.set(0);
level = getMaxLevel(); //To prevent levels past 200
level = getMaxClassLevel(); //To prevent levels past the maximum
}
maxhp = Math.min(30000, maxhp);

View File

@@ -57,7 +57,7 @@ public enum MapleJob {
THUNDERBREAKER1(1500), THUNDERBREAKER2(1510), THUNDERBREAKER3(1511), THUNDERBREAKER4(1512),
LEGEND(2000), EVAN(2001),
ARAN1(2100),ARAN2(2110), ARAN3(2111), ARAN4(2112),
ARAN1(2100), ARAN2(2110), ARAN3(2111), ARAN4(2112),
EVAN1(2200), EVAN2(2210), EVAN3(2211), EVAN4(2212), EVAN5(2213), EVAN6(2214),
EVAN7(2215), EVAN8(2216), EVAN9(2217), EVAN10(2218);

View File

@@ -1174,7 +1174,7 @@ public class Commands {
}
player.loseExp(player.getExp(), false, false);
player.setLevel(Math.min(Integer.parseInt(sub[1]), player.getMaxLevel()) - 1);
player.setLevel(Math.min(Integer.parseInt(sub[1]), player.getMaxClassLevel()) - 1);
player.resetPlayerRates();
if(ServerConstants.USE_ADD_RATES_BY_LEVEL == true) player.setPlayerRates();
@@ -1189,7 +1189,7 @@ public class Commands {
break;
}
while (player.getLevel() < Math.min(255, Integer.parseInt(sub[1]))) {
while (player.getLevel() < Math.min(player.getMaxClassLevel(), Integer.parseInt(sub[1]))) {
player.levelUp(false);
}
break;

View File

@@ -63,7 +63,7 @@ public class MonsterStatusEffect {
if (cancelTask != null) {
cancelTask.cancel(false);
}
cancelTask = null;
cancelTask = null;
}
public ScheduledFuture<?> getCancelTask() {

View File

@@ -62,7 +62,29 @@ public class GameConstants {
330000, 340000, 350000, 360000, 370000, 380000, 390000, 400000, 410000, 420000, 430000, 440000, 450000, 460000, 470000, 480000, 490000, 500000, 510000, 520000,
530000, 550000, 570000, 590000, 610000, 630000, 650000, 670000, 690000, 710000, 730000, 750000, 770000, 790000, 810000, 830000, 850000, 870000, 890000, 910000};
public static int getJobMaxLevel(MapleJob job) {
if(job.getId() % 1000 == 0) { // beginner
return 10;
} else if(job.getId() % 100 == 0) { // 1st job
return 30;
} else {
int jobBranch = job.getId() % 10;
switch(jobBranch) {
case 0:
return 70; // 2nd job
case 1:
return 120; // 3rd job
default:
return (job.getId() / 1000 == 1) ? 120 : 200; // 4th job: cygnus is 120, rest is 200
}
}
}
public static int getHiddenSkill(final int skill) {
switch (skill) {
case Aran.HIDDEN_FULL_DOUBLE:
@@ -82,7 +104,6 @@ public class GameConstants {
return 0;
}
public static boolean isAranSkills(final int skill) {
return Aran.FULL_SWING == skill || Aran.OVER_SWING == skill || Aran.COMBO_TEMPEST == skill || Aran.COMBO_FENRIR == skill || Aran.COMBO_DRAIN == skill
|| Aran.HIDDEN_FULL_DOUBLE == skill || Aran.HIDDEN_FULL_TRIPLE == skill || Aran.HIDDEN_OVER_DOUBLE == skill || Aran.HIDDEN_OVER_TRIPLE == skill

View File

@@ -17,9 +17,11 @@ public class ServerConstants {
//Login Configuration
public static final int CHANNEL_LOAD = 100; //Max players per channel.
public static final long RESPAWN_INTERVAL = 10 * 1000; //10 seconds, 10000.
public static final long PURGING_INTERVAL = 5 * 60 * 1000;
public static final long RANKING_INTERVAL = 60 * 60 * 1000; //60 minutes, 3600000.
public static final long COUPON_INTERVAL = 60 * 60 * 1000; //60 minutes, 3600000.
public static final boolean ENABLE_PIC = false; //Pick true/false to enable or disable Pic. Delete character needs this feature ENABLED.
public static final boolean ENABLE_PIN = false; //Pick true/false to enable or disable Pin.
@@ -50,6 +52,8 @@ public class ServerConstants {
public static final boolean USE_AUTOSAVE = true; //Enables server autosaving feature (saves characters to DB each 1 hour).
public static final boolean USE_SERVER_AUTOASSIGNER = true; //Server-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments.
public static final boolean USE_REFRESH_RANK_MOVE = true;
public static final boolean USE_ENFORCE_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.
public static final boolean USE_ENFORCE_OWL_SUGGESTIONS = false;//Forces the Owl of Minerva to always display the defined item array on GameConstants.OWL_DATA instead of those featured by the players.
public static final boolean USE_ENFORCE_UNMERCHABLE_PET = true; //Forces players to not sell pets via merchants. (since non-named pets gets dirty name and other possible DB-related issues)
public static final boolean USE_ENFORCE_MDOOR_POSITION = true; //Forces mystic door to be spawned near spawnpoints. (since things bugs out other way, and this helps players to locate the door faster)
@@ -57,7 +61,6 @@ public class ServerConstants {
public static final boolean USE_ERASE_UNTRADEABLE_DROP = true; //Forces flagged untradeable items to disappear when dropped.
public static final boolean USE_ERASE_PET_ON_EXPIRATION = false;//Forces pets to be removed from inventory when expire time comes, rather than converting it to a doll.
public static final boolean USE_BUFF_MOST_SIGNIFICANT = true; //When applying buffs, the player will stick with the highest stat boost among the listed, rather than overwriting stats.
public static final boolean USE_UNDERLEVELED_EXP_BLOCK = true; //Players N levels below the killed mob will gain no experience from defeating it.
//Server Rates And Experience
public static final int EXP_RATE = 10;
@@ -72,7 +75,7 @@ public class ServerConstants {
public static final int PARTY_EXPERIENCE_MOD = 1; //Change for event stuff.
//Miscellaneous Configuration
public static final byte MIN_UNDERLEVEL_TO_EXP_GAIN = 5; //Characters are unable to get EXP from a mob if their level are under this threshold, only if "USE_UNDERLEVELED_EXP_BLOCK" is enabled.
public static final byte MIN_UNDERLEVEL_TO_EXP_GAIN = 5; //Characters are unable to get EXP from a mob if their level are under this threshold, only if "USE_ENFORCE_MOB_LEVEL_RANGE" is enabled.
public static final byte MAX_MONITORED_BUFFSTATS = 5; //Limits accounting for "dormant" buff effects, that should take place when stronger stat buffs expires.
public static final int MAX_AP = 32767; //Max AP allotted on the auto-assigner.
public static final int MAX_EVENT_LEVELS = 8; //Event has different levels of rewarding system.
@@ -81,7 +84,7 @@ public class ServerConstants {
//Dangling Items Configuration
public static final int ITEM_EXPIRE_TIME = 3 * 60 * 1000; //Time before items start disappearing. Recommended to be set up to 3 minutes.
public static final int ITEM_MONITOR_TIME = 5 * 60 * 1000; //Interval between item monitoring tasks on maps, which checks for dangling item objects on the map item history.
public static final int ITEM_MONITOR_TIME = 5 * 60 * 1000; //Interval between item monitoring tasks on maps, which checks for dangling (null) item objects on the map item history.
public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map.
//Some Gameplay Enhancing Configurations

View File

@@ -108,7 +108,7 @@ public final class Channel {
IoBuffer.setUseDirectBuffer(false);
IoBuffer.setAllocator(new SimpleBufferAllocator());
acceptor = new NioSocketAcceptor();
TimerManager.getInstance().register(new respawnMaps(), 10000);
TimerManager.getInstance().register(new respawnMaps(), ServerConstants.RESPAWN_INTERVAL);
acceptor.setHandler(new MapleServerHandler(world, channel));
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory()));

View File

@@ -45,11 +45,6 @@ public final class CancelBuffHandler extends AbstractMaplePacketHandler implemen
int sourceid = slea.readInt();
switch (sourceid) {
case NightWalker.DARK_SIGHT: // wind walk as a dark sight...
c.getPlayer().cancelEffect(SkillFactory.getSkill(NightWalker.DARK_SIGHT).getEffect(1), false, -1);
c.getPlayer().cancelEffect(SkillFactory.getSkill(WindArcher.WIND_WALK).getEffect(1), false, -1);
break;
case FPArchMage.BIG_BANG:
case ILArchMage.BIG_BANG:
case Bishop.BIG_BANG:

View File

@@ -178,10 +178,14 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
}
}
}
if ((player.getSkillLevel(SkillFactory.getSkill(NightWalker.VANISH)) > 0 || player.getSkillLevel(SkillFactory.getSkill(WindArcher.WIND_WALK)) > 0 || player.getSkillLevel(SkillFactory.getSkill(Rogue.DARK_SIGHT)) > 0) && player.getBuffedValue(MapleBuffStat.DARKSIGHT) != null) {// && player.getBuffSource(MapleBuffStat.DARKSIGHT) != 9101004
if ((player.getSkillLevel(SkillFactory.getSkill(NightWalker.VANISH)) > 0 || player.getSkillLevel(SkillFactory.getSkill(Rogue.DARK_SIGHT)) > 0) && player.getBuffedValue(MapleBuffStat.DARKSIGHT) != null) {// && player.getBuffSource(MapleBuffStat.DARKSIGHT) != 9101004
player.cancelEffectFromBuffStat(MapleBuffStat.DARKSIGHT);
player.cancelBuffStats(MapleBuffStat.DARKSIGHT);
} else if(player.getSkillLevel(SkillFactory.getSkill(WindArcher.WIND_WALK)) > 0 && player.getBuffedValue(MapleBuffStat.WIND_WALK) != null) {
player.cancelEffectFromBuffStat(MapleBuffStat.WIND_WALK);
player.cancelBuffStats(MapleBuffStat.WIND_WALK);
}
applyAttack(attack, player, attackCount);
}
}

View File

@@ -211,10 +211,15 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
}
}
}
if ((player.getSkillLevel(SkillFactory.getSkill(NightWalker.VANISH)) > 0 || player.getSkillLevel(SkillFactory.getSkill(WindArcher.WIND_WALK)) > 0) && player.getBuffedValue(MapleBuffStat.DARKSIGHT) != null && attack.numAttacked > 0 && player.getBuffSource(MapleBuffStat.DARKSIGHT) != 9101004) {
if (player.getSkillLevel(SkillFactory.getSkill(NightWalker.VANISH)) > 0 && player.getBuffedValue(MapleBuffStat.DARKSIGHT) != null && attack.numAttacked > 0 && player.getBuffSource(MapleBuffStat.DARKSIGHT) != 9101004) {
player.cancelEffectFromBuffStat(MapleBuffStat.DARKSIGHT);
player.cancelBuffStats(MapleBuffStat.DARKSIGHT);
} else if(player.getSkillLevel(SkillFactory.getSkill(WindArcher.WIND_WALK)) > 0 && player.getBuffedValue(MapleBuffStat.WIND_WALK) != null && attack.numAttacked > 0) {
player.cancelEffectFromBuffStat(MapleBuffStat.WIND_WALK);
player.cancelBuffStats(MapleBuffStat.WIND_WALK);
}
applyAttack(attack, player, bulletCount);
}
}

View File

@@ -131,7 +131,12 @@ public class EventInstanceManager {
}
public EventManager getEm() {
return em;
sL.lock();
try {
return em;
} finally {
sL.unlock();
}
}
public int getEventPlayersJobs() {
@@ -623,7 +628,10 @@ public class EventInstanceManager {
}
}
public void dispose() {
public synchronized void dispose() {
if(disposed) return;
disposed = true;
try {
sL.lock();
try {
@@ -652,8 +660,14 @@ public class EventInstanceManager {
killCount.clear();
disposeExpedition();
if(!eventCleared) em.disposeInstance(name);
em = null;
sL.lock();
try {
if(!eventCleared) em.disposeInstance(name);
em = null;
} finally {
sL.unlock();
}
}
public MapleMapFactory getMapFactory() {
@@ -664,12 +678,11 @@ public class EventInstanceManager {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
if(em == null) return;
try {
sL.lock();
try {
em.getIv().invokeFunction(methodName, EventInstanceManager.this);
if(em == null) return;
em.getIv().invokeFunction(methodName, EventInstanceManager.this);
} finally {
sL.unlock();
}
@@ -685,10 +698,18 @@ public class EventInstanceManager {
}
public void saveWinner(MapleCharacter chr) {
String emName;
sL.lock();
try {
emName = em.getName();
} finally {
sL.unlock();
}
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("INSERT INTO eventstats (event, instance, characterid, channel) VALUES (?, ?, ?, ?)")) {
ps.setString(1, em.getName());
ps.setString(1, emName);
ps.setString(2, getName());
ps.setInt(3, chr.getId());
ps.setInt(4, chr.getClient().getChannel());
@@ -706,9 +727,14 @@ public class EventInstanceManager {
map.setEventInstance(this);
if (!mapFactory.isMapLoaded(mapId)) {
if (em.getProperty("shuffleReactors") != null && em.getProperty("shuffleReactors").equals("true")) {
map.shuffleReactors();
}
sL.lock();
try {
if (em.getProperty("shuffleReactors") != null && em.getProperty("shuffleReactors").equals("true")) {
map.shuffleReactors();
}
} finally {
sL.unlock();
}
}
return map;
}
@@ -1035,8 +1061,14 @@ public class EventInstanceManager {
private void disposeExpedition() {
if (expedition != null) {
expedition.dispose(eventCleared);
em.getChannelServer().getExpeditions().remove(expedition);
expedition.dispose(eventCleared);
sL.lock();
try {
em.getChannelServer().getExpeditions().remove(expedition);
} finally {
sL.unlock();
}
expedition = null;
}
@@ -1044,7 +1076,14 @@ public class EventInstanceManager {
public final void setEventCleared() {
eventCleared = true;
em.disposeInstance(name);
sL.lock();
try {
em.disposeInstance(name);
} finally {
sL.unlock();
}
disposeExpedition();
}

View File

@@ -1129,7 +1129,6 @@ public class MapleItemInformationProvider {
return isQuestItemCache.get(itemId);
}
MapleData data = getItemData(itemId);
System.out.println(data);
boolean questItem = MapleDataTool.getIntConvert("info/quest", data, 0) == 1;
isQuestItemCache.put(itemId, questItem);
return questItem;

View File

@@ -455,9 +455,10 @@ public class MapleStatEffect {
case Marksman.SHARP_EYES:
statups.add(new Pair<>(MapleBuffStat.SHARP_EYES, Integer.valueOf(ret.x << 8 | ret.y)));
break;
// THIEF
case Rogue.DARK_SIGHT:
case WindArcher.WIND_WALK:
statups.add(new Pair<>(MapleBuffStat.WIND_WALK, Integer.valueOf(x)));
break;
case Rogue.DARK_SIGHT:
case NightWalker.DARK_SIGHT:
statups.add(new Pair<>(MapleBuffStat.DARKSIGHT, Integer.valueOf(x)));
break;
@@ -1099,6 +1100,9 @@ public class MapleStatEffect {
} else if (isDs()) {
List<Pair<MapleBuffStat, Integer>> dsstat = Collections.singletonList(new Pair<>(MapleBuffStat.DARKSIGHT, 0));
mbuff = MaplePacketCreator.giveForeignBuff(applyto.getId(), dsstat);
} else if (isWw()) {
List<Pair<MapleBuffStat, Integer>> dsstat = Collections.singletonList(new Pair<>(MapleBuffStat.WIND_WALK, 0));
mbuff = MaplePacketCreator.giveForeignBuff(applyto.getId(), dsstat);
} else if (isCombo()) {
mbuff = MaplePacketCreator.giveForeignBuff(applyto.getId(), statups);
} else if (isMonsterRiding()) {
@@ -1353,7 +1357,11 @@ public class MapleStatEffect {
}
private boolean isDs() {
return skill && (sourceid == Rogue.DARK_SIGHT || sourceid == WindArcher.WIND_WALK || sourceid == NightWalker.DARK_SIGHT);
return skill && (sourceid == Rogue.DARK_SIGHT || sourceid == NightWalker.DARK_SIGHT);
}
private boolean isWw() {
return skill && (sourceid == WindArcher.WIND_WALK);
}
private boolean isCombo() {

View File

@@ -287,7 +287,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
int partyLevel = 0;
int leechMinLevel = (ServerConstants.USE_UNDERLEVELED_EXP_BLOCK) ? getLevel() - ServerConstants.MIN_UNDERLEVEL_TO_EXP_GAIN : 0; //NO EXP WILL BE GIVEN for those who are underleveled!
int leechMinLevel = (ServerConstants.USE_ENFORCE_MOB_LEVEL_RANGE) ? getLevel() - ServerConstants.MIN_UNDERLEVEL_TO_EXP_GAIN : 0; //NO EXP WILL BE GIVEN for those who are underleveled!
int leechCount = 0;
for (MapleCharacter mc : members) {
@@ -347,7 +347,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
long pXP = (long)xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0);
partyExp.put(pID, (int)Math.min(pXP, Integer.MAX_VALUE));
} else {
if(!ServerConstants.USE_UNDERLEVELED_EXP_BLOCK || mc.getLevel() >= getLevel() - ServerConstants.MIN_UNDERLEVEL_TO_EXP_GAIN) {
if(!ServerConstants.USE_ENFORCE_MOB_LEVEL_RANGE || mc.getLevel() >= getLevel() - ServerConstants.MIN_UNDERLEVEL_TO_EXP_GAIN) {
//NO EXP WILL BE GIVEN for those who are underleveled!
giveExpToCharacter(mc, xp, isKiller, 1);
} else {
@@ -1021,8 +1021,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
final MapleMonster mons = this;
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(
TimerManager.getInstance().schedule(
new Runnable() {
@Override

View File

@@ -602,6 +602,8 @@ public class MapleMap {
private void startItemMonitor() {
chrWLock.lock();
try {
if(itemMonitor != null) return;
itemMonitor = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
@@ -1093,14 +1095,13 @@ public class MapleMap {
public void destroyReactor(int oid) {
final MapleReactor reactor = getReactorByOid(oid);
TimerManager tMan = TimerManager.getInstance();
broadcastMessage(MaplePacketCreator.destroyReactor(reactor));
reactor.cancelReactorTimeout();
reactor.setAlive(false);
removeMapObject(reactor);
if (reactor.getDelay() > 0) {
tMan.schedule(new Runnable() {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
respawnReactor(reactor);
@@ -1879,9 +1880,12 @@ public class MapleMap {
}
public void addPlayer(final MapleCharacter chr) {
int chrSize;
chrWLock.lock();
try {
characters.add(chr);
chrSize = characters.size();
addPartyMemberInternal(chr);
} finally {
chrWLock.unlock();
@@ -1889,7 +1893,7 @@ public class MapleMap {
chr.setMapId(mapid);
itemMonitorTimeout = 1;
if (getCharacters().size() <= 1) {
if (chrSize == 1) {
if(!hasItemMonitor()) startItemMonitor();
if (onFirstUserEnter.length() != 0 && !chr.hasEntered(onFirstUserEnter, mapid) && MapScriptManager.getInstance().scriptExists(onFirstUserEnter, true)) {