Abstract channel schedulers + Mob animation track + More portal SFX
Implemented an improved scheduler system for channels, on where Runnables objects are "registered in" to run on a scheduled future time (effective run time will depend on the proc time of the worker acting under-the-hood). Implemented a channel scheduler for detecting "mobs currently on animation state". This allows the server to send info to the client about whether a mob should cast a skill or not at that moment. Improved concurrent protection on MapleMonster listeners registry. Improved resource deallocation when destroying a monster object. Added a server flag to allow clean slates to be used on equipments even on the "only successfully used scroll slots" case. Fixed a critical deadlock case with MapleServerHandler. Added the portal SFX for many scripted portals that still lacked the sound effect.
This commit is contained in:
@@ -929,14 +929,17 @@ public class MapleItemInformationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canUseCleanSlate(Equip nEquip) {
|
||||
Map<String, Integer> eqstats = this.getEquipStats(nEquip.getItemId());
|
||||
return ServerConstants.USE_ENHANCED_CLNSLATE || nEquip.getLevel() + nEquip.getUpgradeSlots() < eqstats.get("tuc");
|
||||
}
|
||||
|
||||
public Item scrollEquipWithId(Item equip, int scrollId, boolean usingWhiteScroll, int vegaItemId, boolean isGM) {
|
||||
boolean assertGM = (isGM && ServerConstants.USE_PERFECT_GM_SCROLL);
|
||||
|
||||
if (equip instanceof Equip) {
|
||||
Equip nEquip = (Equip) equip;
|
||||
|
||||
Map<String, Integer> stats = this.getEquipStats(scrollId);
|
||||
Map<String, Integer> eqstats = this.getEquipStats(equip.getItemId());
|
||||
|
||||
if (((nEquip.getUpgradeSlots() > 0 || ItemConstants.isCleanSlate(scrollId))) || assertGM) {
|
||||
double prop = (double)stats.get("success");
|
||||
@@ -963,7 +966,7 @@ public class MapleItemInformationProvider {
|
||||
case 2049001:
|
||||
case 2049002:
|
||||
case 2049003:
|
||||
if (nEquip.getLevel() + nEquip.getUpgradeSlots() < eqstats.get("tuc")) {
|
||||
if (canUseCleanSlate(nEquip)) {
|
||||
nEquip.setUpgradeSlots((byte) (nEquip.getUpgradeSlots() + 1));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -139,10 +139,10 @@ public class MapleLifeFactory {
|
||||
while (monsterSkillInfoData.getChildByPath(Integer.toString(i)) != null) {
|
||||
skills.add(new Pair<>(Integer.valueOf(MapleDataTool.getInt(i + "/skill", monsterSkillInfoData, 0)), Integer.valueOf(MapleDataTool.getInt(i + "/level", monsterSkillInfoData, 0))));
|
||||
|
||||
MapleData monsterSkillData = monsterData.getChildByPath("skill" + i);
|
||||
if(monsterSkillData != null) {
|
||||
MapleData monsterSkillData = monsterData.getChildByPath("skill" + (i + 1));
|
||||
if (monsterSkillData != null) {
|
||||
int animationTime = 0;
|
||||
for(MapleData effectEntry : monsterSkillData.getChildren()) {
|
||||
for (MapleData effectEntry : monsterSkillData.getChildren()) {
|
||||
animationTime += MapleDataTool.getIntConvert("delay", effectEntry, 0);
|
||||
}
|
||||
|
||||
@@ -153,6 +153,19 @@ public class MapleLifeFactory {
|
||||
}
|
||||
stats.setSkills(skills);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
MapleData monsterAttackData;
|
||||
while ((monsterAttackData = monsterData.getChildByPath("attack" + (i + 1))) != null) {
|
||||
int animationTime = 0;
|
||||
for (MapleData effectEntry : monsterAttackData.getChildren()) {
|
||||
animationTime += MapleDataTool.getIntConvert("delay", effectEntry, 0);
|
||||
}
|
||||
|
||||
MapleMonsterInformationProvider.getInstance().setMobAttackAnimationTime(mid, i, animationTime);
|
||||
i++;
|
||||
}
|
||||
|
||||
MapleData banishData = monsterInfoData.getChildByPath("ban");
|
||||
if (banishData != null) {
|
||||
stats.setBanishInfo(new BanishInfo(MapleDataTool.getString("banMsg", banishData), MapleDataTool.getInt("banMap/0/field", banishData, -1), MapleDataTool.getString("banMap/0/portal", banishData, "sp")));
|
||||
|
||||
@@ -95,6 +95,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private Lock externalLock = new MonitoredReentrantLock(MonitoredLockType.MOB_EXT);
|
||||
private Lock monsterLock = new MonitoredReentrantLock(MonitoredLockType.MOB, true);
|
||||
private Lock statiLock = new MonitoredReentrantLock(MonitoredLockType.MOB_STATI);
|
||||
private Lock animationLock = new MonitoredReentrantLock(MonitoredLockType.MOB_ANI);
|
||||
|
||||
public MapleMonster(int id, MapleMonsterStats stats) {
|
||||
super(id);
|
||||
@@ -230,6 +231,28 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
applyAndGetHpDamage(Integer.MAX_VALUE, false);
|
||||
}
|
||||
|
||||
public boolean applyAnimationIfRoaming(int attackPos, int skillPos) { // roam: not casting attack or skill animations
|
||||
if(!animationLock.tryLock()) return false;
|
||||
|
||||
try {
|
||||
long animationTime;
|
||||
|
||||
if(skillPos < 0) {
|
||||
animationTime = MapleMonsterInformationProvider.getInstance().getMobAttackAnimationTime(this.getId(), attackPos);
|
||||
} else {
|
||||
animationTime = MapleMonsterInformationProvider.getInstance().getMobSkillAnimationTime(this.getId(), skillPos);
|
||||
}
|
||||
|
||||
if(animationTime > 0) {
|
||||
return map.getChannelServer().registerMobOnAnimationEffect(map.getId(), this.hashCode(), animationTime);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
animationLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Integer applyAndGetHpDamage(int delta, boolean stayAlive) {
|
||||
int curHp = hp.get();
|
||||
if (curHp <= 0) { // this monster is already dead
|
||||
@@ -469,15 +492,15 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
public MapleCharacter killBy(final MapleCharacter killer) {
|
||||
distributeExperience(killer != null ? killer.getId() : 0);
|
||||
|
||||
MapleCharacter controller = getController();
|
||||
if (controller != null) { // this can/should only happen when a hidden gm attacks the monster
|
||||
controller.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
|
||||
controller.stopControllingMonster(this);
|
||||
MapleCharacter chrController = getController();
|
||||
if (chrController != null) { // this can/should only happen when a hidden gm attacks the monster
|
||||
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
|
||||
chrController.stopControllingMonster(this);
|
||||
}
|
||||
|
||||
final List<Integer> toSpawn = this.getRevives(); // this doesn't work (?)
|
||||
if (toSpawn != null) {
|
||||
final MapleMap reviveMap = killer.getMap();
|
||||
final MapleMap reviveMap = map;
|
||||
if (toSpawn.contains(9300216) && reviveMap.getId() > 925000000 && reviveMap.getId() < 926000000) {
|
||||
reviveMap.broadcastMessage(MaplePacketCreator.playSound("Dojang/clear"));
|
||||
reviveMap.broadcastMessage(MaplePacketCreator.showEffect("dojang/end/clear"));
|
||||
@@ -571,7 +594,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
public void dispatchMonsterKilled(boolean hasKiller) {
|
||||
public synchronized void dispatchMonsterKilled(boolean hasKiller) {
|
||||
if(!hasKiller) {
|
||||
dispatchUpdateQuestMobCount();
|
||||
}
|
||||
@@ -584,19 +607,52 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
for (MonsterListener listener : listeners.toArray(new MonsterListener[listeners.size()])) {
|
||||
MonsterListener[] listenersList;
|
||||
statiLock.lock();
|
||||
try {
|
||||
listenersList = listeners.toArray(new MonsterListener[listeners.size()]);
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
|
||||
for (MonsterListener listener : listenersList) {
|
||||
listener.monsterKilled(getAnimationTime("die1"));
|
||||
}
|
||||
|
||||
statiLock.lock();
|
||||
try {
|
||||
stati.clear();
|
||||
alreadyBuffed.clear();
|
||||
listeners.clear();
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchMonsterDamaged(MapleCharacter from, int trueDmg) {
|
||||
for (MonsterListener listener : listeners.toArray(new MonsterListener[listeners.size()])) {
|
||||
MonsterListener[] listenersList;
|
||||
statiLock.lock();
|
||||
try {
|
||||
listenersList = listeners.toArray(new MonsterListener[listeners.size()]);
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
|
||||
for (MonsterListener listener : listenersList) {
|
||||
listener.monsterDamaged(from, trueDmg);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchMonsterHealed(int trueHeal) {
|
||||
for (MonsterListener listener : listeners.toArray(new MonsterListener[listeners.size()])) {
|
||||
MonsterListener[] listenersList;
|
||||
statiLock.lock();
|
||||
try {
|
||||
listenersList = listeners.toArray(new MonsterListener[listeners.size()]);
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
|
||||
for (MonsterListener listener : listenersList) {
|
||||
listener.monsterHealed(trueHeal);
|
||||
}
|
||||
}
|
||||
@@ -653,7 +709,12 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
|
||||
public void addListener(MonsterListener listener) {
|
||||
listeners.add(listener);
|
||||
statiLock.lock();
|
||||
try {
|
||||
listeners.add(listener);
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isControllerHasAggro() {
|
||||
|
||||
@@ -54,6 +54,7 @@ public class MapleMonsterInformationProvider {
|
||||
private final Set<Integer> hasNoMultiEquipDrops = new HashSet<>();
|
||||
private final Map<Integer, List<MonsterDropEntry>> extraMultiEquipDrops = new HashMap<>();
|
||||
|
||||
private final Map<Pair<Integer, Integer>, Integer> mobAttackAnimationTime = new HashMap<>();
|
||||
private final Map<Pair<Integer, Integer>, Integer> mobSkillAnimationTime = new HashMap<>();
|
||||
|
||||
protected MapleMonsterInformationProvider() {
|
||||
@@ -226,6 +227,15 @@ public class MapleMonsterInformationProvider {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public final void setMobAttackAnimationTime(int monsterId, int attackPos, int animationTime) {
|
||||
mobAttackAnimationTime.put(new Pair<>(monsterId, attackPos), animationTime);
|
||||
}
|
||||
|
||||
public final Integer getMobAttackAnimationTime(int monsterId, int attackPos) {
|
||||
Integer time = mobAttackAnimationTime.get(new Pair<>(monsterId, attackPos));
|
||||
return time == null ? 0 : time;
|
||||
}
|
||||
|
||||
public final void setMobSkillAnimationTime(int monsterId, int skillPos, int animationTime) {
|
||||
mobSkillAnimationTime.put(new Pair<>(monsterId, skillPos), animationTime);
|
||||
}
|
||||
|
||||
@@ -105,14 +105,16 @@ public class MobSkill {
|
||||
}
|
||||
|
||||
public void applyDelayedEffect(final MapleCharacter player, final MapleMonster monster, final boolean skill, final List<MapleCharacter> banishPlayers, int animationTime) {
|
||||
TimerManager.getInstance().schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(monster.isAlive()) {
|
||||
applyEffect(player, monster, skill, banishPlayers);
|
||||
}
|
||||
}
|
||||
}, animationTime);
|
||||
Runnable toRun = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(monster.isAlive()) {
|
||||
applyEffect(player, monster, skill, banishPlayers);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
monster.getMap().getChannelServer().registerOverallAction(monster.getMap().getId(), toRun, animationTime);
|
||||
}
|
||||
|
||||
public void applyEffect(MapleCharacter player, MapleMonster monster, boolean skill, List<MapleCharacter> banishPlayers) {
|
||||
|
||||
@@ -193,7 +193,7 @@ public class MapleReactor extends AbstractMapleMapObject {
|
||||
this.resetReactorActions(newState);
|
||||
map.broadcastMessage(MaplePacketCreator.triggerReactor(this, (short) 0));
|
||||
} finally {
|
||||
this.unlockReactor();
|
||||
this.reactorLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user