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:
ronancpl
2018-07-08 18:25:48 -03:00
parent 94425ba616
commit 5dc16d0cab
42 changed files with 1226 additions and 161 deletions
+6 -3
View File
@@ -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;
+16 -3
View File
@@ -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")));
+71 -10
View File
@@ -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);
}
+10 -8
View File
@@ -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) {
+1 -1
View File
@@ -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();
}
}