/*
OrpheusMS: MapleStory Private Server based on OdinMS
Copyright (C) 2012 Aaron Weiss <aaron@deviant-core.net>
Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
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, either version 3 of the
License, or (at your option) any later version.
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 server.life;
import client.ISkill;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import client.MapleBuffStat;
import client.MapleCharacter;
import client.MapleClient;
import client.MapleJob;
import client.SkillFactory;
import client.status.MonsterStatus;
import client.status.MonsterStatusEffect;
import java.awt.Point;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.concurrent.locks.ReentrantLock;
import tools.Randomizer;
import net.MaplePacket;
import net.server.Channel;
import net.server.MapleParty;
import net.server.MaplePartyCharacter;
import scripting.event.EventInstanceManager;
import server.TimerManager;
import server.life.MapleLifeFactory.BanishInfo;
import server.maps.MapleMap;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
import server.maps.TimeMobEntry;
import tools.ArrayMap;
import tools.MaplePacketCreator;
import tools.Output;
public class MapleMonster extends AbstractLoadedMapleLife {
private MapleMonsterStats stats;
private int hp, mp;
private WeakReference<MapleCharacter> controller = new WeakReference<MapleCharacter>(null);
private boolean controllerHasAggro, controllerKnowsAboutAggro;
private Collection<AttackerEntry> attackers = new LinkedList<AttackerEntry>();
private EventInstanceManager eventInstance = null;
private Collection<MonsterListener> listeners = new LinkedList<MonsterListener>();
private MapleCharacter highestDamageChar;
private EnumMap<MonsterStatus, MonsterStatusEffect> stati = new EnumMap<MonsterStatus, MonsterStatusEffect>(MonsterStatus.class);
private MapleMap map;
private int VenomMultiplier = 0;
private boolean fake = false;
private boolean dropsDisabled = false;
private List<MobSkillEntry> usedSkills = new ArrayList<MobSkillEntry>();
private Map<MobSkillEntry, Integer> skillsUsed = new HashMap<MobSkillEntry, Integer>();
private List<Integer> stolenItems = new ArrayList<Integer>();
private int team;
public ReentrantLock monsterLock = new ReentrantLock();
public MapleMonster(int id, MapleMonsterStats stats) {
super(id);
initWithStats(stats);
}
public MapleMonster(MapleMonster monster) {
super(monster);
initWithStats(monster.stats);
}
private void initWithStats(MapleMonsterStats stats) {
setStance(5);
this.stats = stats;
hp = stats.getHp();
mp = stats.getMp();
}
public void disableDrops() {
this.dropsDisabled = true;
}
public boolean dropsDisabled() {
return dropsDisabled;
}
public void setMap(MapleMap map) {
this.map = map;
}
public int getHp() {
return hp;
}
public void setHp(int hp) {
this.hp = hp;
}
public int getMaxHp() {
return stats.getHp();
}
public int getMp() {
return mp;
}
public void setMp(int mp) {
if (mp < 0) {
mp = 0;
}
this.mp = mp;
}
public int getMaxMp() {
return stats.getMp();
}
public int getExp() {
return stats.getExp();
}
int getLevel() {
return stats.getLevel();
}
public int getCP() {
return stats.getCP();
}
public int getTeam() {
return team;
}
public void setTeam(int team) {
this.team = team;
}
public int getVenomMulti() {
return this.VenomMultiplier;
}
public void setVenomMulti(int multiplier) {
this.VenomMultiplier = multiplier;
}
public MapleMonsterStats getStats() {
return stats;
}
public boolean isBoss() {
return stats.isBoss() || isHT();
}
public int getAnimationTime(String name) {
return stats.getAnimationTime(name);
}
private List<Integer> getRevives() {
return stats.getRevives();
}
private byte getTagColor() {
return stats.getTagColor();
}
private byte getTagBgColor() {
return stats.getTagBgColor();
}
/**
*
* @param from
* the player that dealt the damage
* @param damage
* @param updateAttackTime
*/
public void damage(MapleCharacter from, int damage, boolean updateAttackTime) {
AttackerEntry attacker = null;
if (from.getParty() != null) {
attacker = new PartyAttackerEntry(from.getParty().getId(), from.getClient().getChannelServer());
} else {
attacker = new SingleAttackerEntry(from, from.getClient().getChannelServer());
}
boolean replaced = false;
for (AttackerEntry aentry : attackers) {
if (aentry.equals(attacker)) {
attacker = aentry;
replaced = true;
break;
}
}
if (!replaced) {
attackers.add(attacker);
}
int rDamage = Math.max(0, Math.min(damage, this.hp));
attacker.addDamage(from, rDamage, updateAttackTime);
this.hp -= rDamage;
int remhppercentage = (int) Math.ceil((this.hp * 100.0) / getMaxHp());
if (remhppercentage < 1) {
remhppercentage = 1;
}
long okTime = System.currentTimeMillis() - 4000;
if (hasBossHPBar()) {
from.getMap().broadcastMessage(makeBossHPBarPacket(), getPosition());
} else if (!isBoss()) {
for (AttackerEntry mattacker : attackers) {
for (AttackingMapleCharacter cattacker : mattacker.getAttackers()) {
if (cattacker.getAttacker().getMap() == from.getMap()) {
if (cattacker.getLastAttackTime() >= okTime) {
cattacker.getAttacker().getClient().getSession().write(MaplePacketCreator.showMonsterHP(getObjectId(), remhppercentage));
}
}
}
}
}
}
public void heal(int hp, int mp) {
int hp2Heal = getHp() + hp;
int mp2Heal = getMp() + mp;
if (hp2Heal >= getMaxHp()) {
hp2Heal = getMaxHp();
}
if (mp2Heal >= getMaxMp()) {
mp2Heal = getMaxMp();
}
setHp(hp2Heal);
setMp(mp2Heal);
getMap().broadcastMessage(MaplePacketCreator.healMonster(getObjectId(), hp));
}
public boolean isAttackedBy(MapleCharacter chr) {
for (AttackerEntry aentry : attackers) {
if (aentry.contains(chr)) {
return true;
}
}
return false;
}
public void giveExpToCharacter(MapleCharacter attacker, int exp, boolean highestDamage, int numExpSharers) {
if (highestDamage) {
if (eventInstance != null) {
eventInstance.monsterKilled(attacker, this);
}
highestDamageChar = attacker;
}
if (attacker.getHp() > 0) {
int personalExp = exp;
if (exp > 0) {
Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL);
if (holySymbol != null) {
if (numExpSharers == 1) {
personalExp *= 1.0 + (holySymbol.doubleValue() / 500.0);
} else {
personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0);
}
}
if (stati.containsKey(MonsterStatus.SHOWDOWN)) {
personalExp *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0);
}
}
if (exp < 0) {// O.O ><
personalExp = Integer.MAX_VALUE;
}
attacker.gainExp(personalExp, true, false, highestDamage);
attacker.increaseEquipExp(personalExp);// better place
attacker.mobKilled(this.getId());
}
}
public MapleCharacter killBy(MapleCharacter killer) {
long totalBaseExpL = (this.getExp() * killer.getClient().getPlayer().getExpRate());
int totalBaseExp = (int) (Math.min(Integer.MAX_VALUE, totalBaseExpL));
AttackerEntry highest = null;
int highdamage = 0;
for (AttackerEntry attackEntry : attackers) {
if (attackEntry.getDamage() > highdamage) {
highest = attackEntry;
highdamage = attackEntry.getDamage();
}
}
for (AttackerEntry attackEntry : attackers) {
attackEntry.killedMob(killer.getMap(), (int) Math.ceil(totalBaseExp * ((double) attackEntry.getDamage() / getMaxHp())), attackEntry == highest);
}
if (this.getController() != null) { // this can/should only happen when
// a hidden gm attacks the monster
getController().getClient().getSession().write(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
getController().stopControllingMonster(this);
}
final List<Integer> toSpawn = this.getRevives();
if (toSpawn != null) {
final MapleMap reviveMap = killer.getMap();
if (toSpawn.contains(9300216) && reviveMap.getId() > 925000000 && reviveMap.getId() < 926000000) {
reviveMap.broadcastMessage(MaplePacketCreator.playSound("Dojang/clear"));
reviveMap.broadcastMessage(MaplePacketCreator.showEffect("dojang/end/clear"));
}
TimeMobEntry timeMob = reviveMap.getTimeMob();
if (timeMob != null) {
if (toSpawn.contains(timeMob.id)) {
reviveMap.broadcastMessage(MaplePacketCreator.serverNotice(6, timeMob.message));
}
if (timeMob.id == 9300338 && (reviveMap.getId() >= 922240100 && reviveMap.getId() <= 922240119)) {
if (!reviveMap.containsNPC(9001108)) {
MapleNPC npc = MapleLifeFactory.getNPC(9001108);
npc.setPosition(new Point(172, 9));
npc.setCy(9);
npc.setRx0(172 + 50);
npc.setRx1(172 - 50);
npc.setFh(27);
reviveMap.addMapObject(npc);
reviveMap.broadcastMessage(MaplePacketCreator.spawnNPC(npc));
} else {
reviveMap.toggleHiddenNPC(9001108);
}
}
}
for (Integer mid : toSpawn) {
final MapleMonster mob = MapleLifeFactory.getMonster(mid);
if (eventInstance != null) {
eventInstance.registerMonster(mob);
}
mob.setPosition(getPosition());
if (dropsDisabled()) {
mob.disableDrops();
}
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
reviveMap.spawnMonster(mob);
}
}, getAnimationTime("die1"));
}
}
if (eventInstance != null) {
eventInstance.unregisterMonster(this);
}
for (MonsterListener listener : listeners.toArray(new MonsterListener[listeners.size()])) {
listener.monsterKilled(this, highestDamageChar);
}
MapleCharacter ret = highestDamageChar;
highestDamageChar = null; // may not keep hard references to chars
// outside of PlayerStorage or MapleMap
return ret;
}
public boolean isAlive() {
return this.hp > 0;
}
public MapleCharacter getController() {
return controller.get();
}
public void setController(MapleCharacter controller) {
this.controller = new WeakReference<MapleCharacter>(controller);
}
public void switchController(MapleCharacter newController, boolean immediateAggro) {
MapleCharacter controllers = getController();
if (controllers == newController) {
return;
}
if (controllers != null) {
controllers.stopControllingMonster(this);
controllers.getClient().getSession().write(MaplePacketCreator.stopControllingMonster(getObjectId()));
}
newController.controlMonster(this, immediateAggro);
setController(newController);
if (immediateAggro) {
setControllerHasAggro(true);
}
setControllerKnowsAboutAggro(false);
}
public void addListener(MonsterListener listener) {
listeners.add(listener);
}
public boolean isControllerHasAggro() {
return fake ? false : controllerHasAggro;
}
public void setControllerHasAggro(boolean controllerHasAggro) {
if (fake) {
return;
}
this.controllerHasAggro = controllerHasAggro;
}
public boolean isControllerKnowsAboutAggro() {
return fake ? false : controllerKnowsAboutAggro;
}
public void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
if (fake) {
return;
}
this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
}
public MaplePacket makeBossHPBarPacket() {
return MaplePacketCreator.showBossHP(getId(), getHp(), getMaxHp(), getTagColor(), getTagBgColor());
}
public boolean hasBossHPBar() {
return (isBoss() && getTagColor() > 0) || isHT();
}
private boolean isHT() {
return getId() == 8810018;
}
@Override
public void sendSpawnData(MapleClient c) {
if (!isAlive()) {
return;
}
if (isFake()) {
c.getSession().write(MaplePacketCreator.spawnFakeMonster(this, 0));
} else {
c.getSession().write(MaplePacketCreator.spawnMonster(this, false));
}
if (stati.size() > 0) {
for (final MonsterStatusEffect mse : this.stati.values()) {
c.getSession().write(MaplePacketCreator.applyMonsterStatus(getObjectId(), mse));
}
}
if (hasBossHPBar()) {
if (this.getMap().countMonster(8810026) > 2 && this.getMap().getId() == 240060200) {
this.getMap().killAllMonsters();
return;
}
c.getSession().write(makeBossHPBarPacket());
}
}
@Override
public void sendDestroyData(MapleClient client) {
client.getSession().write(MaplePacketCreator.killMonster(getObjectId(), false));
}
@Override
public MapleMapObjectType getType() {
return MapleMapObjectType.MONSTER;
}
public void setEventInstance(EventInstanceManager eventInstance) {
this.eventInstance = eventInstance;
}
public boolean isMobile() {
return stats.isMobile();
}
public ElementalEffectiveness getEffectiveness(Element e) {
if (stati.size() > 0 && stati.get(MonsterStatus.DOOM) != null) {
return ElementalEffectiveness.NORMAL; // like blue snails
}
return stats.getEffectiveness(e);
}
public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration) {
return applyStatus(from, status, poison, duration, false);
}
public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration, boolean venom) {
switch (stats.getEffectiveness(status.getSkill().getElement())) {
case IMMUNE:
case STRONG:
case NEUTRAL:
return false;
case NORMAL:
case WEAK:
break;
default: {
Output.print("Unknown elemental effectiveness: " + stats.getEffectiveness(status.getSkill().getElement()));
return false;
}
}
if (status.getSkill().getId() == 2111006) { // fp compo
ElementalEffectiveness effectiveness = stats.getEffectiveness(Element.POISON);
if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) {
return false;
}
} else if (status.getSkill().getId() == 2211006) { // il compo
ElementalEffectiveness effectiveness = stats.getEffectiveness(Element.ICE);
if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) {
return false;
}
} else if (status.getSkill().getId() == 4120005 || status.getSkill().getId() == 4220005 || status.getSkill().getId() == 14110004) {// venom
if (stats.getEffectiveness(Element.POISON) == ElementalEffectiveness.WEAK) {
return false;
}
}
if (poison && getHp() <= 1) {
return false;
}
final Map<MonsterStatus, Integer> statis = status.getStati();
if (stats.isBoss()) {
if (!(statis.containsKey(MonsterStatus.SPEED) && statis.containsKey(MonsterStatus.NINJA_AMBUSH) && statis.containsKey(MonsterStatus.WATK))) {
return false;
}
}
for (MonsterStatus stat : statis.keySet()) {
final MonsterStatusEffect oldEffect = stati.get(stat);
if (oldEffect != null) {
oldEffect.removeActiveStatus(stat);
if (oldEffect.getStati().isEmpty()) {
oldEffect.cancelTask();
oldEffect.cancelDamageSchedule();
}
}
}
TimerManager timerManager = TimerManager.getInstance();
final Runnable cancelTask = new Runnable() {
@Override
public void run() {
if (isAlive()) {
MaplePacket packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), status.getStati());
map.broadcastMessage(packet, getPosition());
if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) {
getController().getClient().getSession().write(packet);
}
}
for (MonsterStatus stat : status.getStati().keySet()) {
stati.remove(stat);
}
setVenomMulti(0);
status.cancelDamageSchedule();
}
};
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));
status.setDamageSchedule(timerManager.register(new DamageTask(poisonDamage, from, status, cancelTask, 0), 1000, 1000));
} else if (venom) {
if (from.getJob() == MapleJob.NIGHTLORD || from.getJob() == MapleJob.SHADOWER || from.getJob().isA(MapleJob.NIGHTWALKER3)) {
int poisonLevel = 0, matk = 0, id = from.getJob().getId();
int skill = (id == 412 ? 4120005 : (id == 422 ? 4220005 : 14110004));
poisonLevel = from.getSkillLevel(SkillFactory.getSkill(skill));
if (poisonLevel <= 0) {
return false;
}
matk = SkillFactory.getSkill(skill).getEffect(poisonLevel).getMatk();
int luk = from.getLuk();
int maxDmg = (int) Math.ceil(Math.min(Short.MAX_VALUE, 0.2 * luk * matk));
int minDmg = (int) Math.ceil(Math.min(Short.MAX_VALUE, 0.1 * luk * matk));
int gap = maxDmg - minDmg;
if (gap == 0) {
gap = 1;
}
int poisonDamage = 0;
for (int i = 0; i < getVenomMulti(); i++) {
poisonDamage += (Randomizer.nextInt(gap) + minDmg);
}
poisonDamage = Math.min(Short.MAX_VALUE, poisonDamage);
status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
status.setDamageSchedule(timerManager.register(new DamageTask(poisonDamage, from, status, cancelTask, 0), 1000, 1000));
} else {
return false;
}
} else if (status.getSkill().getId() == 4111003 || status.getSkill().getId() == 14111001) { // Shadow
// Web
status.setDamageSchedule(timerManager.schedule(new DamageTask((int) (getMaxHp() / 50.0 + 0.999), from, status, cancelTask, 1), 3500));
} else if (status.getSkill().getId() == 4121004 || status.getSkill().getId() == 4221004) { // Ninja
// Ambush
final ISkill skill = SkillFactory.getSkill(status.getSkill().getId());
final byte level = from.getSkillLevel(skill);
final int damage = (int) ((from.getStr() + from.getLuk()) * (1.5 + (level * 0.05)) * skill.getEffect(level).getDamage());
/*
* if (getHp() - damage <= 1) { make hp 1 betch damage = getHp() -
* (getHp() - 1); }
*/
status.setValue(MonsterStatus.NINJA_AMBUSH, Integer.valueOf(damage));
status.setDamageSchedule(timerManager.register(new DamageTask(damage, from, status, cancelTask, 2), 1000, 1000));
}
for (MonsterStatus stat : status.getStati().keySet()) {
stati.put(stat, status);
}
int animationTime = status.getSkill().getAnimationTime();
MaplePacket packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status);
map.broadcastMessage(packet, getPosition());
if (getController() != null && !getController().isMapObjectVisible(this)) {
getController().getClient().getSession().write(packet);
}
status.setCancelTask(timerManager.schedule(cancelTask, duration + animationTime));
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
public void run() {
if (isAlive()) {
MaplePacket packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), stats);
map.broadcastMessage(packet, getPosition());
if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) {
getController().getClient().getSession().write(packet);
}
for (final MonsterStatus stat : stats.keySet()) {
stati.remove(stat);
}
}
}
};
final MonsterStatusEffect effect = new MonsterStatusEffect(stats, null, skill, true);
MaplePacket packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), effect);
map.broadcastMessage(packet, getPosition());
if (getController() != null && !getController().isMapObjectVisible(this)) {
getController().getClient().getSession().write(packet);
}
timerManager.schedule(cancelTask, duration);
}
public boolean isBuffed(MonsterStatus status) {
return stati.containsKey(status);
}
public void setFake(boolean fake) {
this.fake = fake;
}
public boolean isFake() {
return fake;
}
public MapleMap getMap() {
return map;
}
public List<MobSkillEntry> getSkills() {
return stats.getSkills();
}
public boolean hasSkill(int skillId, int level) {
return stats.hasSkill(skillId, level);
}
public boolean canUseSkill(MobSkill toUse) {
if (toUse == null) {
return false;
}
for (MobSkillEntry skill : usedSkills) {
if (skill.skillId == toUse.getSkillId() && skill.level == toUse.getSkillLevel()) {
return false;
}
}
if (toUse.getLimit() > 0) {
if (this.skillsUsed.containsKey(new MobSkillEntry(toUse.getSkillId(), toUse.getSkillLevel()))) {
int times = this.skillsUsed.get(new MobSkillEntry(toUse.getSkillId(), toUse.getSkillLevel()));
if (times >= toUse.getLimit()) {
return false;
}
}
}
if (toUse.getSkillId() == 200) {
Collection<MapleMapObject> mmo = getMap().getMapObjects();
int i = 0;
for (MapleMapObject mo : mmo) {
if (mo.getType() == MapleMapObjectType.MONSTER) {
i++;
}
}
if (i > 100) {
return false;
}
}
return true;
}
public void usedSkill(final int skillId, final int level, long cooltime) {
this.usedSkills.add(new MobSkillEntry(skillId, level));
if (this.skillsUsed.containsKey(new MobSkillEntry(skillId, level))) {
int times = this.skillsUsed.get(new MobSkillEntry(skillId, level)) + 1;
this.skillsUsed.remove(new MobSkillEntry(skillId, level));
this.skillsUsed.put(new MobSkillEntry(skillId, level), times);
} else {
this.skillsUsed.put(new MobSkillEntry(skillId, level), 1);
}
final MapleMonster mons = this;
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
mons.clearSkill(skillId, level);
}
}, cooltime);
}
public void clearSkill(int skillId, int level) {
int index = -1;
for (MobSkillEntry skill : usedSkills) {
if (skill.skillId == skillId && skill.level == level) {
index = usedSkills.indexOf(skill);
break;
}
}
if (index != -1) {
usedSkills.remove(index);
}
}
public int getNoSkills() {
return this.stats.getNoSkills();
}
public boolean isFirstAttack() {
return this.stats.isFirstAttack();
}
public int getBuffToGive() {
return this.stats.getBuffToGive();
}
private final class DamageTask implements Runnable {
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) {
this.dealDamage = dealDamage;
this.chr = chr;
this.status = status;
this.cancelTask = cancelTask;
this.type = type;
this.map = chr.getMap();
}
@Override
public void run() {
int damage = dealDamage;
if (damage >= hp) {
damage = hp - 1;
if (type == 1 || type == 2) {
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
cancelTask.run();
status.getCancelTask().cancel(false);
}
}
if (hp > 1 && damage > 0) {
damage(chr, damage, false);
if (type == 1) {
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
}
}
}
}
public String getName() {
return stats.getName();
}
private class AttackingMapleCharacter {
private MapleCharacter attacker;
private long lastAttackTime;
public AttackingMapleCharacter(MapleCharacter attacker, long lastAttackTime) {
super();
this.attacker = attacker;
this.lastAttackTime = lastAttackTime;
}
public long getLastAttackTime() {
return lastAttackTime;
}
public MapleCharacter getAttacker() {
return attacker;
}
}
private interface AttackerEntry {
List<AttackingMapleCharacter> getAttackers();
public void addDamage(MapleCharacter from, int damage, boolean updateAttackTime);
public int getDamage();
public boolean contains(MapleCharacter chr);
public void killedMob(MapleMap map, int baseExp, boolean mostDamage);
}
private class SingleAttackerEntry implements AttackerEntry {
private int damage;
private int chrid;
private long lastAttackTime;
private Channel cserv;
public SingleAttackerEntry(MapleCharacter from, Channel cserv) {
this.chrid = from.getId();
this.cserv = cserv;
}
@Override
public void addDamage(MapleCharacter from, int damage, boolean updateAttackTime) {
if (chrid == from.getId()) {
this.damage += damage;
} else {
throw new IllegalArgumentException("Not the attacker of this entry");
}
if (updateAttackTime) {
lastAttackTime = System.currentTimeMillis();
}
}
@Override
public List<AttackingMapleCharacter> getAttackers() {
MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(chrid);
if (chr != null) {
return Collections.singletonList(new AttackingMapleCharacter(chr, lastAttackTime));
} else {
return Collections.emptyList();
}
}
@Override
public boolean contains(MapleCharacter chr) {
return chrid == chr.getId();
}
@Override
public int getDamage() {
return damage;
}
@Override
public void killedMob(MapleMap map, int baseExp, boolean mostDamage) {
MapleCharacter chr = map.getCharacterById(chrid);
if (chr != null) {
giveExpToCharacter(chr, baseExp, mostDamage, 1);
}
}
@Override
public int hashCode() {
return chrid;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SingleAttackerEntry other = (SingleAttackerEntry) obj;
return chrid == other.chrid;
}
}
private static class OnePartyAttacker {
public MapleParty lastKnownParty;
public int damage;
public long lastAttackTime;
public OnePartyAttacker(MapleParty lastKnownParty, int damage) {
this.lastKnownParty = lastKnownParty;
this.damage = damage;
this.lastAttackTime = System.currentTimeMillis();
}
}
private class PartyAttackerEntry implements AttackerEntry {
private int totDamage;
private Map<Integer, OnePartyAttacker> attackers;
private Channel cserv;
private int partyid;
public PartyAttackerEntry(int partyid, Channel cserv) {
this.partyid = partyid;
this.cserv = cserv;
attackers = new HashMap<Integer, OnePartyAttacker>(6);
}
@Override
public List<AttackingMapleCharacter> getAttackers() {
List<AttackingMapleCharacter> ret = new ArrayList<AttackingMapleCharacter>(attackers.size());
for (Entry<Integer, OnePartyAttacker> entry : attackers.entrySet()) {
MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(entry.getKey());
if (chr != null) {
ret.add(new AttackingMapleCharacter(chr, entry.getValue().lastAttackTime));
}
}
return ret;
}
private Map<MapleCharacter, OnePartyAttacker> resolveAttackers() {
Map<MapleCharacter, OnePartyAttacker> ret = new HashMap<MapleCharacter, OnePartyAttacker>(attackers.size());
for (Entry<Integer, OnePartyAttacker> aentry : attackers.entrySet()) {
MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(aentry.getKey());
if (chr != null) {
ret.put(chr, aentry.getValue());
}
}
return ret;
}
@Override
public boolean contains(MapleCharacter chr) {
return attackers.containsKey(chr.getId());
}
@Override
public int getDamage() {
return totDamage;
}
@Override
public void addDamage(MapleCharacter from, int damage, boolean updateAttackTime) {
OnePartyAttacker oldPartyAttacker = attackers.get(from.getId());
if (oldPartyAttacker != null) {
oldPartyAttacker.damage += damage;
oldPartyAttacker.lastKnownParty = from.getParty();
if (updateAttackTime) {
oldPartyAttacker.lastAttackTime = System.currentTimeMillis();
}
} else {
// TODO actually this causes wrong behaviour when the party
// changes between attacks
// only the last setup will get exp - but otherwise we'd have to
// store the full party
// constellation for every attack/everytime it changes, might be
// wanted/needed in the
// future but not now
OnePartyAttacker onePartyAttacker = new OnePartyAttacker(from.getParty(), damage);
attackers.put(from.getId(), onePartyAttacker);
if (!updateAttackTime) {
onePartyAttacker.lastAttackTime = 0;
}
}
totDamage += damage;
}
@Override
public void killedMob(MapleMap map, int baseExp, boolean mostDamage) {
Map<MapleCharacter, OnePartyAttacker> attackers_ = resolveAttackers();
MapleCharacter highest = null;
int highestDamage = 0;
Map<MapleCharacter, Integer> expMap = new ArrayMap<MapleCharacter, Integer>(6);
for (Entry<MapleCharacter, OnePartyAttacker> attacker : attackers_.entrySet()) {
MapleParty party = attacker.getValue().lastKnownParty;
double averagePartyLevel = 0;
List<MapleCharacter> expApplicable = new ArrayList<MapleCharacter>();
for (MaplePartyCharacter partychar : party.getMembers()) {
if (attacker.getKey().getLevel() - partychar.getLevel() <= 5 || getLevel() - partychar.getLevel() <= 5) {
MapleCharacter pchr = cserv.getPlayerStorage().getCharacterByName(partychar.getName());
if (pchr != null) {
if (pchr.isAlive() && pchr.getMap() == map) {
expApplicable.add(pchr);
averagePartyLevel += pchr.getLevel();
}
}
}
}
double expBonus = 1.0;
if (expApplicable.size() > 1) {
expBonus = 1.10 + 0.05 * expApplicable.size();
averagePartyLevel /= expApplicable.size();
}
int iDamage = attacker.getValue().damage;
if (iDamage > highestDamage) {
highest = attacker.getKey();
highestDamage = iDamage;
}
double innerBaseExp = baseExp * ((double) iDamage / totDamage);
double expFraction = (innerBaseExp * expBonus) / (expApplicable.size() + 1);
for (MapleCharacter expReceiver : expApplicable) {
Integer oexp = expMap.get(expReceiver);
int iexp;
if (oexp == null) {
iexp = 0;
} else {
iexp = oexp.intValue();
}
double expWeight = (expReceiver == attacker.getKey() ? 2.0 : 1.0);
double levelMod = expReceiver.getLevel() / averagePartyLevel;
if (levelMod > 1.0 || this.attackers.containsKey(expReceiver.getId())) {
levelMod = 1.0;
}
iexp += (int) Math.round(expFraction * expWeight * levelMod);
expMap.put(expReceiver, Integer.valueOf(iexp));
}
}
for (Entry<MapleCharacter, Integer> expReceiver : expMap.entrySet()) {
giveExpToCharacter(expReceiver.getKey(), expReceiver.getValue(), mostDamage ? expReceiver.getKey() == highest : false, expMap.size());
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + partyid;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PartyAttackerEntry other = (PartyAttackerEntry) obj;
if (partyid != other.partyid) {
return false;
}
return true;
}
}
public void addStolen(int itemId) {
stolenItems.add(itemId);
}
public void setTempEffectiveness(Element e, ElementalEffectiveness ee, long milli) {
final Element fE = e;
final ElementalEffectiveness fEE = stats.getEffectiveness(e);
if (!stats.getEffectiveness(e).equals(ElementalEffectiveness.WEAK)) {
stats.setEffectiveness(e, ee);
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
stats.removeEffectiveness(fE);
stats.setEffectiveness(fE, fEE);
}
}, milli);
}
}
public BanishInfo getBanish() {
return stats.getBanishInfo();
}
public void setBoss(boolean boss) {
this.stats.setBoss(boss);
}
public int getDropPeriodTime() {
return stats.getDropPeriod();
}
public int getPADamage() {
return stats.getPADamage();
}
public Map<MonsterStatus, MonsterStatusEffect> getStati() {
return stati;
}
public final void empty() {
try {
this.monsterLock.unlock();
} catch (Exception e) {
} // who cares
this.monsterLock = null;
final Iterator<MonsterStatusEffect> mseIt = stati.values().iterator();
MonsterStatusEffect mse;
while (mseIt.hasNext()) {
mse = mseIt.next();
mse.cancelDamageSchedule();
mse.cancelTask();
mseIt.remove();
mse = null;
}
this.stati = null;
this.usedSkills = null;
this.listeners = null;
this.skillsUsed = null;
this.stats = null;
this.highestDamageChar = null;
this.map = null;
this.eventInstance = null;
this.attackers = null;
this.stolenItems = null;
if (controller.get() != null) {
controller.get().stopControllingMonster(this);
}
controller.clear();
this.controller = null;
}
}