EXP system & Mob buffs/diseases optimization

Solved a problem within EXP distribution system that would hand out less overall EXP than the expected when the amount to be earned is low.
Optimized mob buffs and diseases, now using a dedicated thread to process all status expirations on a batch.
Refactored MonitoredLockTypes names to something more easily identificable.
Added a delay on mob effect applications, to be registered in after the cast animation time.
Fixed Flame Thrower acting passively when a attacking skill is used by the player.
This commit is contained in:
ronancpl
2018-06-30 22:48:02 -03:00
parent dac5c43635
commit 94425ba616
57 changed files with 926 additions and 608 deletions
+15 -4
View File
@@ -132,12 +132,23 @@ public class MapleLifeFactory {
stats.setRevives(revives);
}
decodeElementalString(stats, MapleDataTool.getString("elemAttr", monsterInfoData, ""));
MapleData monsterSkillData = monsterInfoData.getChildByPath("skill");
if (monsterSkillData != null) {
MapleData monsterSkillInfoData = monsterInfoData.getChildByPath("skill");
if (monsterSkillInfoData != null) {
int i = 0;
List<Pair<Integer, Integer>> skills = new ArrayList<>();
while (monsterSkillData.getChildByPath(Integer.toString(i)) != null) {
skills.add(new Pair<>(Integer.valueOf(MapleDataTool.getInt(i + "/skill", monsterSkillData, 0)), Integer.valueOf(MapleDataTool.getInt(i + "/level", monsterSkillData, 0))));
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) {
int animationTime = 0;
for(MapleData effectEntry : monsterSkillData.getChildren()) {
animationTime += MapleDataTool.getIntConvert("delay", effectEntry, 0);
}
MapleMonsterInformationProvider.getInstance().setMobSkillAnimationTime(mid, i, animationTime);
}
i++;
}
stats.setSkills(skills);
+54 -35
View File
@@ -56,6 +56,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import tools.locks.MonitoredReentrantLock;
import net.server.channel.Channel;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import server.TimerManager;
@@ -320,7 +321,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
return takenDamage.containsKey(chr.getId());
}
private void distributeExperienceToParty(int pid, int exp, int killer, Set<MapleCharacter> underleveled, int minThresholdLevel) {
private void distributeExperienceToParty(int pid, float exp, int killer, Set<MapleCharacter> underleveled, int minThresholdLevel) {
List<MapleCharacter> members = new LinkedList<>();
MapleCharacter pchar = getMap().getAnyCharacterFromParty(pid);
if(pchar != null) {
@@ -353,7 +354,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (level >= minThresholdLevel) {
boolean isKiller = killer == id;
boolean mostDamage = mostDamageCid == id;
int xp = (int) ((0.80f * exp * level) / partyLevel);
float xp = ((0.80f * exp * level) / partyLevel);
if (mostDamage) {
xp += (0.20f * exp);
}
@@ -378,14 +379,14 @@ public class MapleMonster extends AbstractLoadedMapleLife {
int minThresholdLevel = calcThresholdLevel(this.getMap().getEventInstance() != null);
int exp = getExp();
long totalHealth = maxHpPlusHeal.get();
Map<Integer, Integer> expDist = new HashMap<>();
Map<Integer, Integer> partyExp = new HashMap<>();
Map<Integer, Float> expDist = new HashMap<>();
Map<Integer, Float> partyExp = new HashMap<>();
float exp8perHp = (0.8f * exp) / totalHealth; // 80% of pool is split amongst all the damagers
float exp2 = (0.2f * exp); // 20% of pool goes to the killer or his/her party
for (Entry<Integer, AtomicInteger> damage : takenDamage.entrySet()) {
expDist.put(damage.getKey(), (int) (Math.min((exp8perHp * damage.getValue().get()), Integer.MAX_VALUE)));
expDist.put(damage.getKey(), exp8perHp * damage.getValue().get());
}
Collection<MapleCharacter> chrs = map.getCharacters();
@@ -393,15 +394,16 @@ public class MapleMonster extends AbstractLoadedMapleLife {
for (MapleCharacter mc : chrs) {
if (expDist.containsKey(mc.getId())) {
boolean isKiller = (mc.getId() == killerId);
int xp = expDist.get(mc.getId());
float xp = expDist.get(mc.getId());
if (isKiller) {
xp = (int)Math.min(exp2 + xp, Integer.MAX_VALUE);
xp += exp2;
}
MapleParty p = mc.getParty();
if (p != null) {
int pID = p.getId();
long pXP = (long) xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0);
partyExp.put(pID, (int)Math.min(pXP, Integer.MAX_VALUE));
float pXP = xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0);
partyExp.put(pID, pXP);
} else {
if(mc.getLevel() >= minThresholdLevel) {
//NO EXP WILL BE GIVEN for those who are underleveled!
@@ -413,7 +415,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
for (Entry<Integer, Integer> party : partyExp.entrySet()) {
for (Entry<Integer, Float> party : partyExp.entrySet()) {
distributeExperienceToParty(party.getKey(), party.getValue(), killerId, underleveled, minThresholdLevel);
}
@@ -422,7 +424,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
public void giveExpToCharacter(MapleCharacter attacker, int exp, boolean isKiller, int numExpSharers) {
public void giveExpToCharacter(MapleCharacter attacker, float exp, boolean isKiller, int numExpSharers) {
if (isKiller) {
if (getMap().getEventInstance() != null) {
getMap().getEventInstance().monsterKilled(attacker, this);
@@ -434,9 +436,10 @@ public class MapleMonster extends AbstractLoadedMapleLife {
int partyExp = 0;
if (attacker.getHp() > 0) {
int personalExp = exp * attacker.getExpRate();
exp *= attacker.getExpRate();
int personalExp = (int) exp;
if (exp > 0) {
if (exp <= Integer.MAX_VALUE) { // assuming no negative xp here
if (partyModifier > 0.0f) {
partyExp = (int) (personalExp * partyModifier * ServerConstants.PARTY_BONUS_EXP_RATE);
}
@@ -444,7 +447,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (holySymbol != null) {
personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0);
}
statiLock.lock();
try {
if (stati.containsKey(MonsterStatus.SHOWDOWN)) {
@@ -453,6 +456,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
} finally {
statiLock.unlock();
}
} else {
personalExp = Integer.MAX_VALUE;
}
attacker.gainExp(personalExp, partyExp, true, false, isKiller);
@@ -777,9 +782,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null);
map.broadcastMessage(packet, getPosition());
MapleCharacter controller = getController();
if (controller != null && !controller.isMapObjectVisible(this)) {
controller.getClient().announce(packet);
MapleCharacter chrController = getController();
if (chrController != null && !chrController.isMapObjectVisible(this)) {
chrController.getClient().announce(packet);
}
return animationTime;
@@ -832,6 +837,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
final Channel ch = map.getChannelServer();
final int mapid = map.getId();
if(statis.size() > 0) {
statiLock.lock();
try {
@@ -840,8 +847,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (oldEffect != null) {
oldEffect.removeActiveStatus(stat);
if (oldEffect.getStati().isEmpty()) {
oldEffect.cancelTask();
oldEffect.cancelDamageSchedule();
ch.interruptMobStatus(mapid, oldEffect);
}
}
}
@@ -850,7 +856,6 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
TimerManager timerManager = TimerManager.getInstance();
final Runnable cancelTask = new Runnable() {
@Override
@@ -875,17 +880,21 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
setVenomMulti(0);
status.cancelDamageSchedule();
}
};
Runnable overtimeAction = null;
int overtimeDelay = -1;
int animationTime;
if (poison) {
int poisonLevel = from.getSkillLevel(status.getSkill());
int poisonDamage = Math.min(Short.MAX_VALUE, (int) (getMaxHp() / (70.0 - poisonLevel) + 0.999));
status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
animationTime = broadcastStatusEffect(status);
status.setDamageSchedule(timerManager.register(new DamageTask(poisonDamage, from, status, cancelTask, 0), 1000, 1000));
overtimeAction = new DamageTask(poisonDamage, from, status, 0);
overtimeDelay = 1000;
} else if (venom) {
if (from.getJob() == MapleJob.NIGHTLORD || from.getJob() == MapleJob.SHADOWER || from.getJob().isA(MapleJob.NIGHTWALKER3)) {
int poisonLevel, matk, jobid = from.getJob().getId();
@@ -910,7 +919,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
status.setValue(MonsterStatus.VENOMOUS_WEAPON, Integer.valueOf(poisonDamage));
status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
animationTime = broadcastStatusEffect(status);
status.setDamageSchedule(timerManager.register(new DamageTask(poisonDamage, from, status, cancelTask, 0), 1000, 1000));
overtimeAction = new DamageTask(poisonDamage, from, status, 0);
overtimeDelay = 1000;
} else {
return false;
}
@@ -919,7 +930,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
int webDamage = (int) (getMaxHp() / 50.0 + 0.999);
status.setValue(MonsterStatus.SHADOW_WEB, Integer.valueOf(webDamage));
animationTime = broadcastStatusEffect(status);
status.setDamageSchedule(timerManager.schedule(new DamageTask(webDamage, from, status, cancelTask, 1), 3500));
overtimeAction = new DamageTask(webDamage, from, status, 1);
overtimeDelay = 3500;
*/
} else if (status.getSkill().getId() == 4121004 || status.getSkill().getId() == 4221004) { // Ninja Ambush
final Skill skill = SkillFactory.getSkill(status.getSkill().getId());
@@ -928,7 +941,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
status.setValue(MonsterStatus.NINJA_AMBUSH, Integer.valueOf(damage));
animationTime = broadcastStatusEffect(status);
status.setDamageSchedule(timerManager.register(new DamageTask(damage, from, status, cancelTask, 2), 1000, 1000));
overtimeAction = new DamageTask(damage, from, status, 2);
overtimeDelay = 1000;
} else {
animationTime = broadcastStatusEffect(status);
}
@@ -943,12 +958,11 @@ public class MapleMonster extends AbstractLoadedMapleLife {
statiLock.unlock();
}
status.setCancelTask(timerManager.schedule(cancelTask, duration + animationTime - 100));
ch.registerMobStatus(mapid, status, cancelTask, duration + animationTime - 100, overtimeAction, overtimeDelay);
return true;
}
public void applyMonsterBuff(final Map<MonsterStatus, Integer> stats, final int x, int skillId, long duration, MobSkill skill, final List<Integer> reflection) {
TimerManager timerManager = TimerManager.getInstance();
final Runnable cancelTask = new Runnable() {
@Override
@@ -991,7 +1005,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (controller != null && !controller.isMapObjectVisible(this)) {
controller.getClient().announce(packet);
}
effect.setCancelTask(timerManager.schedule(cancelTask, duration));
map.getChannelServer().registerMobStatus(map.getId(), effect, cancelTask, duration);
}
private void debuffMobStat(MonsterStatus stat) {
@@ -1191,15 +1206,13 @@ public class MapleMonster extends AbstractLoadedMapleLife {
private final int dealDamage;
private final MapleCharacter chr;
private final MonsterStatusEffect status;
private final Runnable cancelTask;
private final int type;
private final MapleMap map;
private DamageTask(int dealDamage, MapleCharacter chr, MonsterStatusEffect status, Runnable cancelTask, int type) {
private DamageTask(int dealDamage, MapleCharacter chr, MonsterStatusEffect status, int type) {
this.dealDamage = dealDamage;
this.chr = chr;
this.status = status;
this.cancelTask = cancelTask;
this.type = type;
this.map = chr.getMap();
}
@@ -1207,20 +1220,26 @@ public class MapleMonster extends AbstractLoadedMapleLife {
@Override
public void run() {
int curHp = hp.get();
if(curHp <= 1) return;
if(curHp <= 1) {
map.getChannelServer().interruptMobStatus(map.getId(), status);
return;
}
int damage = dealDamage;
if (damage >= curHp) {
damage = curHp - 1;
if (type == 1 || type == 2) {
cancelTask.run();
status.getCancelTask().cancel(false);
map.getChannelServer().interruptMobStatus(map.getId(), status);
}
}
if (damage > 0) {
damage(chr, damage, true);
if (type == 1) { // ninja ambush (type 2) is already displaying DOT
if (type == 1) {
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
} else if (type == 2) {
if(damage < dealDamage) { // ninja ambush (type 2) is already displaying DOT to the caster
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
}
}
}
}
@@ -53,6 +53,8 @@ public class MapleMonsterInformationProvider {
private final Map<Integer, List<Integer>> dropsChancePool = new HashMap<>(); // thanks to ronan
private final Set<Integer> hasNoMultiEquipDrops = new HashSet<>();
private final Map<Integer, List<MonsterDropEntry>> extraMultiEquipDrops = new HashMap<>();
private final Map<Pair<Integer, Integer>, Integer> mobSkillAnimationTime = new HashMap<>();
protected MapleMonsterInformationProvider() {
retrieveGlobal();
@@ -223,9 +225,17 @@ public class MapleMonsterInformationProvider {
dropsChancePool.put(monsterId, ret);
return ret;
}
public final void setMobSkillAnimationTime(int monsterId, int skillPos, int animationTime) {
mobSkillAnimationTime.put(new Pair<>(monsterId, skillPos), animationTime);
}
public final Integer getMobSkillAnimationTime(int monsterId, int skillPos) {
Integer time = mobSkillAnimationTime.get(new Pair<>(monsterId, skillPos));
return time == null ? 0 : time;
}
public static ArrayList<Pair<Integer, String>> getMobsIDsFromName(String search)
{
public static ArrayList<Pair<Integer, String>> getMobsIDsFromName(String search) {
MapleDataProvider dataProvider = MapleDataProviderFactory.getDataProvider(new File("wz/String.wz"));
ArrayList<Pair<Integer, String>> retMobs = new ArrayList<Pair<Integer, String>>();
MapleData data = dataProvider.getData("Mob.img");
@@ -243,8 +253,7 @@ public class MapleMonsterInformationProvider {
return retMobs;
}
public static String getMobNameFromId(int id)
{
public static String getMobNameFromId(int id) {
try
{
return MapleLifeFactory.getMonster(id).getName();
@@ -261,8 +270,7 @@ public class MapleMonsterInformationProvider {
}
}
public static String getMobNameFromID(int id)
{
public static String getMobNameFromID(int id) {
try
{
return MapleLifeFactory.getMonster(id).getName();
+21 -9
View File
@@ -32,6 +32,7 @@ import client.status.MonsterStatus;
import constants.GameConstants;
import java.util.LinkedList;
import java.util.Map;
import server.TimerManager;
import tools.Randomizer;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
@@ -103,6 +104,17 @@ public class MobSkill {
this.limit = limit;
}
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);
}
public void applyEffect(MapleCharacter player, MapleMonster monster, boolean skill, List<MapleCharacter> banishPlayers) {
MapleDisease disease = null;
Map<MonsterStatus, Integer> stats = new ArrayMap<MonsterStatus, Integer>();
@@ -282,15 +294,15 @@ public class MobSkill {
System.out.println("Unhandled Mob skill: " + skillId);
break;
}
if (stats.size() > 0) {
if (lt != null && rb != null && skill) {
for (MapleMapObject mons : getObjectsInRange(monster, MapleMapObjectType.MONSTER)) {
((MapleMonster) mons).applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
}
} else {
monster.applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
}
}
if (stats.size() > 0) {
if (lt != null && rb != null && skill) {
for (MapleMapObject mons : getObjectsInRange(monster, MapleMapObjectType.MONSTER)) {
((MapleMonster) mons).applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
}
} else {
monster.applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
}
}
if (disease != null) {
if (lt != null && rb != null && skill) {
int i = 0;
+29 -26
View File
@@ -59,7 +59,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.lang.ref.WeakReference;
import java.util.Comparator;
import net.server.Server;
import net.server.channel.Channel;
import scripting.map.MapScriptManager;
@@ -255,15 +254,43 @@ public class MapleMap {
public int getId() {
return mapid;
}
public Channel getChannelServer() {
return Server.getInstance().getWorld(world).getChannel(channel);
}
public MapleMap getReturnMap() {
if(returnMapId == 999999999) return this;
return Server.getInstance().getWorld(world).getChannel(channel).getMapFactory().getMap(returnMapId);
return getChannelServer().getMapFactory().getMap(returnMapId);
}
public int getReturnMapId() {
return returnMapId;
}
public MapleMap getForcedReturnMap() {
return getChannelServer().getMapFactory().getMap(forcedReturnMap);
}
public int getForcedReturnId() {
return forcedReturnMap;
}
public void setForcedReturnMap(int map) {
this.forcedReturnMap = map;
}
public long getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit;
}
public int getTimeLeft() {
return (int) ((timeLimit - System.currentTimeMillis()) / 1000);
}
public void setReactorState() {
chrRLock.lock();
@@ -322,30 +349,6 @@ public class MapleMap {
return true;
}
public int getForcedReturnId() {
return forcedReturnMap;
}
public MapleMap getForcedReturnMap() {
return Server.getInstance().getWorld(world).getChannel(channel).getMapFactory().getMap(forcedReturnMap);
}
public void setForcedReturnMap(int map) {
this.forcedReturnMap = map;
}
public long getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit;
}
public int getTimeLeft() {
return (int) ((timeLimit - System.currentTimeMillis()) / 1000);
}
public int getCurrentPartyId() {
for (MapleCharacter chr : this.getCharacters()) {
if (chr.getPartyId() != -1) {
+1 -1
View File
@@ -55,7 +55,7 @@ public class MapleReactor extends AbstractMapleMapObject {
private boolean attackHit;
private ScheduledFuture<?> timeoutTask = null;
private Lock reactorLock = new MonitoredReentrantLock(MonitoredLockType.REACTOR, true);
private Lock hitLock = new MonitoredReentrantLock(MonitoredLockType.REACTOR, true);
private Lock hitLock = new MonitoredReentrantLock(MonitoredLockType.REACTOR_HIT, true);
public MapleReactor(MapleReactorStats stats, int rid) {
this.evstate = (byte)0;