package server.life; import clientside.MapleBuffStat; import clientside.MapleCharacter; import clientside.MapleClient; import clientside.MapleDisease; import clientside.MapleTrait; import clientside.Skill; import clientside.SkillFactory; import client.inventory.Equip; import client.inventory.Item; import client.inventory.MapleInventoryType; import client.status.MonsterStatus; import client.status.MonsterStatusEffect; import constants.GameConstants; import constants.ServerConstants; import handling.channel.ChannelServer; import handling.world.MapleParty; import handling.world.MaplePartyCharacter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantReadWriteLock; import scripting.EventInstanceManager; import server.MapleItemInformationProvider; import server.MapleStatEffect; import server.Randomizer; import server.Timer; import server.maps.MapleMap; import server.maps.MapleMapObject; import server.maps.MapleMapObjectType; import tools.ConcurrentEnumMap; import tools.Pair; import tools.packet.CField; import tools.packet.MobPacket; public class MapleMonster extends AbstractLoadedMapleLife { private MapleMonsterStats stats; private ChangeableStats ostats = null; private long hp; private long nextKill = 0L; private long lastDropTime = 0L; private int mp; private byte carnivalTeam = -1; private MapleMap map; private WeakReference<MapleMonster> sponge = new WeakReference(null); private int linkoid = 0; private int lastNode = -1; private int highestDamageChar = 0; private int linkCID = 0; private WeakReference<MapleCharacter> controller = new WeakReference(null); private boolean fake = false; private boolean dropsDisabled = false; private boolean controllerHasAggro = false; private final Collection<AttackerEntry> attackers = new LinkedList(); private EventInstanceManager eventInstance; private MonsterListener listener = null; private byte[] reflectpack = null; private byte[] nodepack = null; private final ConcurrentEnumMap<MonsterStatus, MonsterStatusEffect> stati = new ConcurrentEnumMap(MonsterStatus.class); private final LinkedList<MonsterStatusEffect> poisons = new LinkedList(); private final ReentrantReadWriteLock poisonsLock = new ReentrantReadWriteLock(); private Map<Integer, Long> usedSkills; private int stolen = -1; private boolean shouldDropItem = false; private boolean killed = false; public MapleMonster(int id, MapleMonsterStats stats) { super(id); initWithStats(stats); } public MapleMonster(MapleMonster monster) { super(monster); initWithStats(monster.stats); } private final void initWithStats(MapleMonsterStats stats) { setStance(5); this.stats = stats; this.hp = stats.getHp(); this.mp = stats.getMp(); if (stats.getNoSkills() > 0) { this.usedSkills = new HashMap(); } } public final ArrayList<AttackerEntry> getAttackers() { if ((this.attackers == null) || (this.attackers.size() <= 0)) { return new ArrayList(); } ArrayList ret = new ArrayList(); for (AttackerEntry e : this.attackers) { if (e != null) { ret.add(e); } } return ret; } public final MapleMonsterStats getStats() { return this.stats; } public final void disableDrops() { this.dropsDisabled = true; } public final boolean dropsDisabled() { return this.dropsDisabled; } public final void setSponge(MapleMonster mob) { this.sponge = new WeakReference(mob); if (this.linkoid <= 0) { this.linkoid = mob.getObjectId(); } } public final void setMap(MapleMap map) { this.map = map; startDropItemSchedule(); } public final long getHp() { return this.hp; } public final void setHp(long hp) { this.hp = hp; } public final ChangeableStats getChangedStats() { return this.ostats; } public final long getMobMaxHp() { if (this.ostats != null) { return this.ostats.hp; } return this.stats.getHp(); } public final int getMp() { return this.mp; } public final void setMp(int mp) { if (mp < 0) { mp = 0; } this.mp = mp; } public final int getMobMaxMp() { if (this.ostats != null) { return this.ostats.mp; } return this.stats.getMp(); } public final int getMobExp() { if (this.ostats != null) { return this.ostats.exp; } return this.stats.getExp(); } public final void setOverrideStats(OverrideMonsterStats ostats) { this.ostats = new ChangeableStats(this.stats, ostats); this.hp = ostats.getHp(); this.mp = ostats.getMp(); } public final void changeLevel(int newLevel) { changeLevel(newLevel, true); } public final void changeLevel(int newLevel, boolean pqMob) { if (!this.stats.isChangeable()) { return; } this.ostats = new ChangeableStats(this.stats, newLevel, pqMob); this.hp = this.ostats.getHp(); this.mp = this.ostats.getMp(); } public final void hellChangeLevel(final double newLevel, final int hpBuff, final int bossHpBuff, final double expMulti) { //Custom hell if (!stats.isChangeable()) { return; } this.ostats = new ChangeableStats(stats, newLevel, hpBuff, bossHpBuff, expMulti); this.hp = ostats.getHp(); this.mp = ostats.getMp(); } public final MapleMonster getSponge() { return (MapleMonster) this.sponge.get(); } public final void damage(MapleCharacter from, long damage, boolean updateAttackTime) { damage(from, damage, updateAttackTime, 0); } public final void damage(MapleCharacter from, long damage, boolean updateAttackTime, int lastSkill) { if ((from == null) || (damage <= 0L) || (!isAlive())) { return; } AttackerEntry attacker = null; if (from.getParty() != null) { attacker = new PartyAttackerEntry(from.getParty().getId()); } else { attacker = new SingleAttackerEntry(from); } boolean replaced = false; for (AttackerEntry aentry : getAttackers()) { if ((aentry != null) && (aentry.equals(attacker))) { attacker = aentry; replaced = true; break; } } if (!replaced) { this.attackers.add(attacker); } long rDamage = Math.max(0L, Math.min(damage, this.hp)); attacker.addDamage(from, rDamage, updateAttackTime); if (this.stats.getSelfD() != -1) { this.hp -= rDamage; if (this.hp > 0L) { if (this.hp < this.stats.getSelfDHp()) { this.map.killMonster(this, from, false, false, this.stats.getSelfD(), lastSkill); } else { for (AttackerEntry mattacker : getAttackers()) { for (AttackingMapleCharacter cattacker : mattacker.getAttackers()) { if ((cattacker.getAttacker().getMap() == from.getMap()) && (cattacker.getLastAttackTime() >= System.currentTimeMillis() - 4000L)) { cattacker.getAttacker().getClient().getSession().write(MobPacket.showMonsterHP(getObjectId(), getHPPercent())); } } } } } else { this.map.killMonster(this, from, true, false, (byte) 1, lastSkill); } } else { if ((this.sponge.get() != null) && (((MapleMonster) this.sponge.get()).hp > 0L)) { ((MapleMonster) this.sponge.get()).hp -= rDamage; if (((MapleMonster) this.sponge.get()).hp <= 0L) { this.map.broadcastMessage(MobPacket.showBossHP(this)); this.map.killMonster((MapleMonster) this.sponge.get(), from, true, false, (byte) 1, lastSkill); } else { this.map.broadcastMessage(MobPacket.showBossHP(this)); } } if (this.hp > 0L) { this.hp -= rDamage; if (this.eventInstance != null) { this.eventInstance.monsterDamaged(from, this, (int) rDamage); } else { EventInstanceManager em = from.getEventInstance(); if (em != null) { em.monsterDamaged(from, this, (int) rDamage); } } if ((this.sponge.get() == null) && (this.hp > 0L)) { switch (this.stats.getHPDisplayType()) { case 0: this.map.broadcastMessage(MobPacket.showBossHP(this), getTruePosition()); break; case 1: this.map.broadcastMessage(from, MobPacket.damageFriendlyMob(this, damage, true), false); break; case 2: this.map.broadcastMessage(MobPacket.showMonsterHP(getObjectId(), getHPPercent())); from.mulung_EnergyModify(true); break; case 3: for (AttackerEntry mattacker : getAttackers()) { if (mattacker != null) { for (AttackingMapleCharacter cattacker : mattacker.getAttackers()) { if ((cattacker != null) && (cattacker.getAttacker().getMap() == from.getMap()) && (cattacker.getLastAttackTime() >= System.currentTimeMillis() - 4000L)) { cattacker.getAttacker().getClient().getSession().write(MobPacket.showMonsterHP(getObjectId(), getHPPercent())); } } } } } } if (this.hp <= 0L) { this.map.killMonster(this, from, true, false, (byte) 1, lastSkill); } } } startDropItemSchedule(); } public int getHPPercent() { return (int) Math.ceil(this.hp * 100.0D / getMobMaxHp()); } public final void heal(int hp, int mp, boolean broadcast) { long TotalHP = getHp() + hp; int TotalMP = getMp() + mp; if (TotalHP >= getMobMaxHp()) { setHp(getMobMaxHp()); } else { setHp(TotalHP); } if (TotalMP >= getMp()) { setMp(getMp()); } else { setMp(TotalMP); } if (broadcast) { this.map.broadcastMessage(MobPacket.healMonster(getObjectId(), hp)); } else if (this.sponge.get() != null) { ((MapleMonster) this.sponge.get()).hp += hp; } } public final void killed() { if (this.listener != null) { this.listener.monsterKilled(); } this.listener = null; } private final void giveExpToCharacter(MapleCharacter attacker, int exp, boolean highestDamage, int numExpSharers, byte pty, byte Class_Bonus_EXP_PERCENT, byte Premium_Bonus_EXP_PERCENT, int lastskillID) { if (highestDamage) { if (this.eventInstance != null) { this.eventInstance.monsterKilled(attacker, this); } else { EventInstanceManager em = attacker.getEventInstance(); if (em != null) { em.monsterKilled(attacker, this); } } this.highestDamageChar = attacker.getId(); } if (exp > 0) { MonsterStatusEffect ms = (MonsterStatusEffect) this.stati.get(MonsterStatus.SHOWDOWN); if (ms != null) { exp += (int) (exp * (ms.getX().intValue() / 100.0D)); } Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL); if (holySymbol != null) { exp = (int) (exp * (1.0D + holySymbol.doubleValue() / 100.0D)); } if (attacker.hasDisease(MapleDisease.CURSE)) { exp /= 2; } exp = (int) Math.min(2147483647.0D, exp * attacker.getEXPMod() * attacker.getStat().expBuff / 100.0D * (GameConstants.getExpRate(attacker.getJob(), ChannelServer.getInstance(this.map.getChannel()).getExpRate()))); int Class_Bonus_EXP = 0; if (Class_Bonus_EXP_PERCENT > 0) { Class_Bonus_EXP = (int) (exp / 100.0D * Class_Bonus_EXP_PERCENT); } int Premium_Bonus_EXP = 0; if (Premium_Bonus_EXP_PERCENT > 0) { Premium_Bonus_EXP = (int) (exp / 100.0D * Premium_Bonus_EXP_PERCENT); } int Equipment_Bonus_EXP = (int) (exp / 100.0D * attacker.getStat().equipmentBonusExp); if ((attacker.getStat().equippedFairy > 0) && (attacker.getFairyExp() > 0)) { Equipment_Bonus_EXP += (int) (exp / 100.0D * attacker.getFairyExp()); } attacker.getTrait(MapleTrait.MapleTraitType.charisma).addExp(this.stats.getCharismaEXP(), attacker); attacker.gainExpMonster(exp, true, highestDamage, pty, Class_Bonus_EXP, Equipment_Bonus_EXP, Premium_Bonus_EXP, this.stats.isPartyBonus(), this.stats.getPartyBonusRate()); } attacker.mobKilled(getId(), lastskillID); } public final int killBy(final MapleCharacter killer, final int lastSkill) { if (killed) { return 1; } killed = true; int totalBaseExp = getMobExp(); AttackerEntry highest = null; long highdamage = 0; final List<AttackerEntry> list = getAttackers(); for (final AttackerEntry attackEntry : list) { if (attackEntry != null && attackEntry.getDamage() > highdamage) { highest = attackEntry; highdamage = attackEntry.getDamage(); } } int baseExp; for (final AttackerEntry attackEntry : list) { if (attackEntry != null) { baseExp = (int) Math.ceil(totalBaseExp * ((double) attackEntry.getDamage() / getMobMaxHp())); attackEntry.killedMob(getMap(), baseExp, attackEntry == highest, lastSkill); } } MapleCharacter controll = (MapleCharacter) this.controller.get(); if (controll != null) { controll.getClient().getSession().write(MobPacket.stopControllingMonster(getObjectId())); controll.stopControllingMonster(this); } int achievement = 0; switch (getId()) { case 9400121: achievement = 12; break; case 8500002: achievement = 13; break; case 8510000: case 8520000: achievement = 14; break; default: break; } if (achievement != 0) { if (killer != null && killer.getParty() != null) { for (MaplePartyCharacter mp : killer.getParty().getMembers()) { final MapleCharacter mpc = killer.getMap().getCharacterById(mp.getId()); if (mpc != null) { // mpc.finishAchievement(achievement); } } } else if (killer != null) { // killer.finishAchievement(achievement); } // I can't fix any skills cuz i don't get recv :( lol gm }//you cant fix the skills ;o? not until someone sniffs forme wanna do it on my cpu?;o hmm its hard because maple lags on tv if (killer != null && stats.isBoss()) { ///killer.finishAchievement(18); } spawnRevives(getMap()); if (eventInstance != null) { eventInstance.unregisterMonster(this); eventInstance = null; } if (killer != null && killer.getPyramidSubway() != null) { killer.getPyramidSubway().onKill(killer); } hp = 0; MapleMonster oldSponge = getSponge(); sponge = new WeakReference<>(null); if (oldSponge != null && oldSponge.isAlive()) { boolean set = true; for (MapleMapObject mon : map.getAllMonstersThreadsafe()) { MapleMonster mons = (MapleMonster) mon; if (mons.isAlive() && mons.getObjectId() != oldSponge.getObjectId() && mons.getStats().getLevel() > 1 && mons.getObjectId() != this.getObjectId() && (mons.getSponge() == oldSponge || mons.getLinkOid() == oldSponge.getObjectId())) { //sponge was this, please update set = false; break; } } if (set) { //all sponge monsters are dead, please kill off the sponge map.killMonster(oldSponge, killer, true, false, (byte) 1); } } reflectpack = null; nodepack = null; if (stati.size() > 0) { List<MonsterStatus> statuses = new LinkedList<>(stati.keySet()); for (MonsterStatus ms : statuses) { cancelStatus(ms); } statuses.clear(); } if (poisons.size() > 0) { List<MonsterStatusEffect> ps = new LinkedList<>(); poisonsLock.readLock().lock(); try { ps.addAll(poisons); } finally { poisonsLock.readLock().unlock(); } for (MonsterStatusEffect p : ps) { cancelSingleStatus(p); } ps.clear(); } //attackers.clear(); cancelDropItem(); int v1 = highestDamageChar; this.highestDamageChar = 0; //reset so we dont kill twice return v1; } public final void spawnRevives(MapleMap map) { List<Integer> toSpawn = this.stats.getRevives(); if ((toSpawn == null) || (getLinkCID() > 0)) { return; } MapleMonster spongy = null; Iterator i$; switch (getId()) { case 6160003: case 8820002: case 8820003: case 8820004: case 8820005: case 8820006: case 8840000: case 8850011: break; case 8810118: case 8810119: case 8810120: case 8810121: for (int i : toSpawn) { MapleMonster mob = MapleLifeFactory.getMonster(i); mob.setPosition(getTruePosition()); if (this.eventInstance != null) { this.eventInstance.registerMonster(mob); } if (dropsDisabled()) { mob.disableDrops(); } switch (mob.getId()) { case 8810119: case 8810120: case 8810121: case 8810122: spongy = mob; } } if ((spongy != null) && (map.getMonsterById(spongy.getId()) == null)) { map.spawnMonster(spongy, -2); for (MapleMapObject mon : map.getAllMonstersThreadsafe()) { MapleMonster mons = (MapleMonster) mon; if ((mons.getObjectId() != spongy.getObjectId()) && ((mons.getSponge() == this) || (mons.getLinkOid() == getObjectId()))) { mons.setSponge(spongy); } } } break; case 8810026: case 8810130: case 8820008: case 8820009: case 8820010: case 8820011: case 8820012: case 8820013: List<MapleMonster> mobs = new ArrayList(); for (int i : toSpawn) { MapleMonster mob = MapleLifeFactory.getMonster(i); mob.setPosition(getTruePosition()); if (this.eventInstance != null) { this.eventInstance.registerMonster(mob); } if (dropsDisabled()) { mob.disableDrops(); } switch (mob.getId()) { case 8810018: case 8810118: case 8820009: case 8820010: case 8820011: case 8820012: case 8820013: case 8820014: spongy = mob; break; default: mobs.add(mob); } } if ((spongy != null) && (map.getMonsterById(spongy.getId()) == null)) { map.spawnMonster(spongy, -2); for (MapleMonster i : mobs) { map.spawnMonster(i, -2); i.setSponge(spongy); } } break; case 8820014: for (i$ = toSpawn.iterator(); i$.hasNext();) { int i = ((Integer) i$.next()).intValue(); MapleMonster mob = MapleLifeFactory.getMonster(i); if (this.eventInstance != null) { this.eventInstance.registerMonster(mob); } mob.setPosition(getTruePosition()); if (dropsDisabled()) { mob.disableDrops(); } map.spawnMonster(mob, -2); } break; default: for (i$ = toSpawn.iterator(); i$.hasNext();) { int i = ((Integer) i$.next()).intValue(); MapleMonster mob = MapleLifeFactory.getMonster(i); if (this.eventInstance != null) { this.eventInstance.registerMonster(mob); } mob.setPosition(getTruePosition()); if (dropsDisabled()) { mob.disableDrops(); } map.spawnRevives(mob, getObjectId()); if (mob.getId() == 9300216) { map.broadcastMessage(CField.environmentChange("Dojang/clear", 4)); map.broadcastMessage(CField.environmentChange("dojang/end/clear", 4)); } } } } public final boolean isAlive() { return this.hp > 0L; } public final void setCarnivalTeam(byte team) { this.carnivalTeam = team; } public final byte getCarnivalTeam() { return this.carnivalTeam; } public final MapleCharacter getController() { return (MapleCharacter) this.controller.get(); } public final void setController(MapleCharacter controller) { this.controller = new WeakReference(controller); } public final void switchController(MapleCharacter newController, boolean immediateAggro) { MapleCharacter controllers = getController(); if (controllers == newController) { return; } if (controllers != null) { controllers.stopControllingMonster(this); controllers.getClient().getSession().write(MobPacket.stopControllingMonster(getObjectId())); sendStatus(controllers.getClient()); } newController.controlMonster(this, immediateAggro); setController(newController); if (immediateAggro) { setControllerHasAggro(true); } } public final void addListener(MonsterListener listener) { this.listener = listener; } public final boolean isControllerHasAggro() { return this.controllerHasAggro; } public final void setControllerHasAggro(boolean controllerHasAggro) { this.controllerHasAggro = controllerHasAggro; } public final void sendStatus(MapleClient client) { if (this.reflectpack != null) { client.getSession().write(this.reflectpack); } if (this.poisons.size() > 0) { this.poisonsLock.readLock().lock(); try { client.getSession().write(MobPacket.applyMonsterStatus(this, this.poisons)); } finally { this.poisonsLock.readLock().unlock(); } } } @Override public final void sendSpawnData(MapleClient client) { if (!isAlive()) { return; } client.getSession().write(MobPacket.spawnMonster(this, (this.fake) && (this.linkCID <= 0) ? -4 : -1, 0)); sendStatus(client); if ((this.map != null) && (!this.stats.isEscort()) && (client.getPlayer() != null) && (client.getPlayer().getTruePosition().distanceSq(getTruePosition()) <= GameConstants.maxViewRangeSq_Half())) { this.map.updateMonsterController(this); } } @Override public final void sendDestroyData(MapleClient client) { if ((this.stats.isEscort()) && (getEventInstance() != null) && (this.lastNode >= 0)) { this.map.resetShammos(client); } else { client.getSession().write(MobPacket.killMonster(getObjectId(), 0)); if ((getController() != null) && (client.getPlayer() != null) && (client.getPlayer().getId() == getController().getId())) { client.getPlayer().stopControllingMonster(this); } } } @Override public final String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.stats.getName()); sb.append("("); sb.append(getId()); sb.append(") (Level "); sb.append(this.stats.getLevel()); sb.append(") at (X"); sb.append(getTruePosition().x); sb.append("/ Y"); sb.append(getTruePosition().y); sb.append(") with "); sb.append(getHp()); sb.append("/ "); sb.append(getMobMaxHp()); sb.append("hp, "); sb.append(getMp()); sb.append("/ "); sb.append(getMobMaxMp()); sb.append(" mp, oid: "); sb.append(getObjectId()); sb.append(" || Controller : "); MapleCharacter chr = (MapleCharacter) this.controller.get(); sb.append(chr != null ? chr.getName() : "none"); return sb.toString(); } @Override public final MapleMapObjectType getType() { return MapleMapObjectType.MONSTER; } public final EventInstanceManager getEventInstance() { return this.eventInstance; } public final void setEventInstance(EventInstanceManager eventInstance) { this.eventInstance = eventInstance; } public final int getStatusSourceID(MonsterStatus status) { if ((status == MonsterStatus.POISON) || (status == MonsterStatus.BURN)) { this.poisonsLock.readLock().lock(); try { for (MonsterStatusEffect ps : this.poisons) { if (ps != null) { return ps.getSkill(); } } return -1; } finally { this.poisonsLock.readLock().unlock(); } } MonsterStatusEffect effect = (MonsterStatusEffect) this.stati.get(status); if (effect != null) { return effect.getSkill(); } return -1; } public final ElementalEffectiveness getEffectiveness(Element e) { if ((this.stati.size() > 0) && (this.stati.containsKey(MonsterStatus.DOOM))) { return ElementalEffectiveness.NORMAL; } return this.stats.getEffectiveness(e); } public final void applyStatus(MapleCharacter from, MonsterStatusEffect status, boolean poison, long duration, boolean checkboss, MapleStatEffect eff) { if ((!isAlive()) || (getLinkCID() > 0)) { return; } Skill skilz = SkillFactory.getSkill(status.getSkill()); if (skilz != null) { switch (stats.getEffectiveness(skilz.getElement())) { case IMMUNE: case STRONG: return; case NORMAL: case WEAK: break; default: return; } } int statusSkill = status.getSkill(); switch (statusSkill) { case 2111006: switch (stats.getEffectiveness(Element.POISON)) { case IMMUNE: case STRONG: return; } break; case 2211006: switch (stats.getEffectiveness(Element.ICE)) { case IMMUNE: case STRONG: return; } break; case 4120005: case 4220005: case 14110004: switch (stats.getEffectiveness(Element.POISON)) { case IMMUNE: case STRONG: return; } break; } if (duration >= 2000000000L) { duration = 5000L; } MonsterStatus stat = status.getStati(); if ((this.stats.isNoDoom()) && (stat == MonsterStatus.DOOM)) { return; } if (this.stats.isBoss()) { if (stat == MonsterStatus.STUN) { return; } if ((checkboss) && (stat != MonsterStatus.SPEED) && (stat != MonsterStatus.NINJA_AMBUSH) && (stat != MonsterStatus.WATK) && (stat != MonsterStatus.POISON) && (stat != MonsterStatus.BURN) && (stat != MonsterStatus.DARKNESS) && (stat != MonsterStatus.MAGIC_CRASH)) { return; } if ((getId() == 8850011) && (stat == MonsterStatus.MAGIC_CRASH)) { return; } } if (((this.stats.isFriendly()) || (isFake())) && ((stat == MonsterStatus.STUN) || (stat == MonsterStatus.SPEED) || (stat == MonsterStatus.POISON) || (stat == MonsterStatus.BURN))) { return; } if (((stat == MonsterStatus.BURN) || (stat == MonsterStatus.POISON)) && (eff == null)) { return; } if (this.stati.containsKey(stat)) { cancelStatus(stat); } if ((stat == MonsterStatus.POISON) || (stat == MonsterStatus.BURN)) { int count = 0; this.poisonsLock.readLock().lock(); try { for (MonsterStatusEffect mse : this.poisons) { if ((mse != null) && ((mse.getSkill() == eff.getSourceId()) || (mse.getSkill() == GameConstants.getLinkedAranSkill(eff.getSourceId())) || (GameConstants.getLinkedAranSkill(mse.getSkill()) == eff.getSourceId()))) { count++; } } } finally { this.poisonsLock.readLock().unlock(); } if (count >= eff.getDOTStack()) { return; } } if ((poison) && (getHp() > 1L) && (eff != null)) { duration = Math.max(duration, eff.getDOTTime() * 1000); } duration += from.getStat().dotTime * 1000; long aniTime = duration; if (skilz != null) { aniTime += skilz.getAnimationTime(); } status.setCancelTask(aniTime); if ((poison) && (getHp() > 1L)) { status.setValue(status.getStati(), Integer.valueOf((int) ((eff.getDOT() + from.getStat().dot + from.getStat().getDamageIncrease(eff.getSourceId())) * from.getStat().getCurrentMaxBaseDamage() / 100.0D))); int dam = Integer.valueOf((int) (aniTime / 1000L * status.getX().intValue() / 2L)).intValue(); status.setPoisonSchedule(dam, from); if (dam > 0) { if (dam >= this.hp) { dam = (int) (this.hp - 1L); } damage(from, dam, false); } } else if ((statusSkill == 4111003) || (statusSkill == 14111001)) { status.setValue(status.getStati(), Integer.valueOf((int) (getMobMaxHp() / 50.0D + 0.999D))); status.setPoisonSchedule(Integer.valueOf(status.getX().intValue()).intValue(), from); } else if (statusSkill == 4341003) { status.setPoisonSchedule(Integer.valueOf((int) (eff.getDamage() * from.getStat().getCurrentMaxBaseDamage() / 100.0D)).intValue(), from); } else if ((statusSkill == 4121004) || (statusSkill == 4221004)) { status.setValue(status.getStati(), Integer.valueOf(Math.min(32767, Integer.valueOf((int) (eff.getDamage() * from.getStat().getCurrentMaxBaseDamage() / 100.0D)).intValue()))); int dam = Integer.valueOf((int) (aniTime / 1000L * status.getX().intValue() / 2L)).intValue(); status.setPoisonSchedule(dam, from); if (dam > 0) { if (dam >= this.hp) { dam = (int) (this.hp - 1L); } damage(from, dam, false); } } MapleCharacter con = getController(); if ((stat == MonsterStatus.POISON) || (stat == MonsterStatus.BURN)) { this.poisonsLock.writeLock().lock(); try { this.poisons.add(status); if (con != null) { this.map.broadcastMessage(con, MobPacket.applyMonsterStatus(this, this.poisons), getTruePosition()); con.getClient().getSession().write(MobPacket.applyMonsterStatus(this, this.poisons)); } else { this.map.broadcastMessage(MobPacket.applyMonsterStatus(this, this.poisons), getTruePosition()); } } finally { this.poisonsLock.writeLock().unlock(); } } else { this.stati.put(stat, status); if (con != null) { this.map.broadcastMessage(con, MobPacket.applyMonsterStatus(this, status), getTruePosition()); con.getClient().getSession().write(MobPacket.applyMonsterStatus(this, status)); } else { this.map.broadcastMessage(MobPacket.applyMonsterStatus(this, status), getTruePosition()); } } } public void applyStatus(MonsterStatusEffect status) { if (this.stati.containsKey(status.getStati())) { cancelStatus(status.getStati()); } this.stati.put(status.getStati(), status); this.map.broadcastMessage(MobPacket.applyMonsterStatus(this, status), getTruePosition()); } public final void dispelSkill(final MobSkill skillId) { List<MonsterStatus> toCancel = new ArrayList<MonsterStatus>(); for (Entry<MonsterStatus, MonsterStatusEffect> effects : stati.entrySet()) { MonsterStatusEffect mse = effects.getValue(); if (mse.getMobSkill() != null && mse.getMobSkill().getSkillId() == skillId.getSkillId()) { //not checking for level. toCancel.add(effects.getKey()); } } for (MonsterStatus stat : toCancel) { cancelStatus(stat); } } public final void applyMonsterBuff(final Map<MonsterStatus, Integer> effect, final int skillId, final long duration, final MobSkill skill, final List<Integer> reflection) { for (Entry<MonsterStatus, Integer> z : effect.entrySet()) { if (stati.containsKey(z.getKey())) { cancelStatus(z.getKey()); } final MonsterStatusEffect effectz = new MonsterStatusEffect(z.getKey(), z.getValue(), 0, skill, true, reflection.size() > 0); effectz.setCancelTask(duration); stati.put(z.getKey(), effectz); } final MapleCharacter con = getController(); if (reflection.size() > 0) { this.reflectpack = MobPacket.applyMonsterStatus(getObjectId(), effect, reflection, skill); if (con != null) { map.broadcastMessage(con, reflectpack, getTruePosition()); con.getClient().getSession().write(this.reflectpack); } else { map.broadcastMessage(reflectpack, getTruePosition()); } } else { for (Entry<MonsterStatus, Integer> z : effect.entrySet()) { if (con != null) { map.broadcastMessage(con, MobPacket.applyMonsterStatus(getObjectId(), z.getKey(), z.getValue(), skill), getTruePosition()); con.getClient().getSession().write(MobPacket.applyMonsterStatus(getObjectId(), z.getKey(), z.getValue(), skill)); } else { map.broadcastMessage(MobPacket.applyMonsterStatus(getObjectId(), z.getKey(), z.getValue(), skill), getTruePosition()); } } } } public final void setTempEffectiveness(final Element e, long milli) { this.stats.setEffectiveness(e, ElementalEffectiveness.WEAK); Timer.EtcTimer.getInstance().schedule(new Runnable() { public void run() { MapleMonster.this.stats.removeEffectiveness(e); } }, milli); } public final boolean isBuffed(MonsterStatus status) { if ((status == MonsterStatus.POISON) || (status == MonsterStatus.BURN)) { return (this.poisons.size() > 0) || (this.stati.containsKey(status)); } return this.stati.containsKey(status); } public final MonsterStatusEffect getBuff(MonsterStatus status) { return (MonsterStatusEffect) this.stati.get(status); } public final int getStatiSize() { return this.stati.size() + (this.poisons.size() > 0 ? 1 : 0); } public final ArrayList<MonsterStatusEffect> getAllBuffs() { ArrayList ret = new ArrayList(); for (MonsterStatusEffect e : this.stati.values()) { ret.add(e); } this.poisonsLock.readLock().lock(); try { for (MonsterStatusEffect e : this.poisons) { ret.add(e); } } finally { this.poisonsLock.readLock().unlock(); } return ret; } public final void setFake(boolean fake) { this.fake = fake; } public final boolean isFake() { return this.fake; } public final MapleMap getMap() { return this.map; } public final List<Pair<Integer, Integer>> getSkills() { return this.stats.getSkills(); } public final boolean hasSkill(int skillId, int level) { return this.stats.hasSkill(skillId, level); } public final long getLastSkillUsed(int skillId) { if (this.usedSkills.containsKey(Integer.valueOf(skillId))) { return ((Long) this.usedSkills.get(Integer.valueOf(skillId))).longValue(); } return 0L; } public final void setLastSkillUsed(int skillId, long now, long cooltime) { switch (skillId) { case 140: this.usedSkills.put(Integer.valueOf(skillId), Long.valueOf(now + cooltime * 2L)); this.usedSkills.put(Integer.valueOf(141), Long.valueOf(now)); break; case 141: this.usedSkills.put(Integer.valueOf(skillId), Long.valueOf(now + cooltime * 2L)); this.usedSkills.put(Integer.valueOf(140), Long.valueOf(now + cooltime)); break; default: this.usedSkills.put(Integer.valueOf(skillId), Long.valueOf(now + cooltime)); } } public final byte getNoSkills() { return this.stats.getNoSkills(); } public final boolean isFirstAttack() { return this.stats.isFirstAttack(); } public final int getBuffToGive() { return this.stats.getBuffToGive(); } public final void doPoison(MonsterStatusEffect status, WeakReference<MapleCharacter> weakChr) { if (((status.getStati() == MonsterStatus.BURN) || (status.getStati() == MonsterStatus.POISON)) && (this.poisons.size() <= 0)) { return; } if ((status.getStati() != MonsterStatus.BURN) && (status.getStati() != MonsterStatus.POISON) && (!this.stati.containsKey(status.getStati()))) { return; } if (weakChr == null) { return; } long damage = status.getPoisonSchedule(); boolean shadowWeb = (status.getSkill() == 4111003) || (status.getSkill() == 14111001); MapleCharacter chr = (MapleCharacter) weakChr.get(); boolean cancel = (damage <= 0L) || (chr == null) || (chr.getMapId() != this.map.getId()); if (damage >= this.hp) { damage = this.hp - 1L; cancel = (!shadowWeb) || (cancel); } if (!cancel) { damage(chr, damage, false); if (shadowWeb) { this.map.broadcastMessage(MobPacket.damageMonster(getObjectId(), damage), getTruePosition()); } } } public String getName() { return this.stats.getName(); } public int getLinkOid() { return this.linkoid; } public void setLinkOid(int lo) { this.linkoid = lo; } public final ConcurrentEnumMap<MonsterStatus, MonsterStatusEffect> getStati() { return this.stati; } public void addEmpty() { for (MonsterStatus stat : MonsterStatus.values()) { if (stat.isEmpty()) { this.stati.put(stat, new MonsterStatusEffect(stat, Integer.valueOf(0), 0, null, false)); } } } public final int getStolen() { return this.stolen; } public final void setStolen(int s) { this.stolen = s; } public final void handleSteal(MapleCharacter chr) { double showdown = 100.0D; MonsterStatusEffect mse = getBuff(MonsterStatus.SHOWDOWN); if (mse != null) { showdown += mse.getX().intValue(); } Skill steal = SkillFactory.getSkill(4201004); int level = chr.getTotalSkillLevel(steal); int chServerrate = ChannelServer.getInstance(chr.getClient().getChannel()).getDropRate(); if ((level > 0) && (!getStats().isBoss()) && (this.stolen == -1) && (steal.getEffect(level).makeChanceResult())) { MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance(); List de = mi.retrieveDrop(getId()); if (de == null) { this.stolen = 0; return; } List<MonsterDropEntry> dropEntry = new ArrayList(de); Collections.shuffle(dropEntry); for (MonsterDropEntry d : dropEntry) { if ((d.itemId > 0) && (d.questid == 0) && (d.itemId / 10000 != 238) && (Randomizer.nextInt(999999) < (int) (10 * d.chance * chServerrate * chr.getDropMod() * (chr.getStat().dropBuff / 100.0D) * (showdown / 100.0D)))) { Item idrop; if (GameConstants.getInventoryType(d.itemId) == MapleInventoryType.EQUIP) { Equip eq = (Equip) MapleItemInformationProvider.getInstance().getEquipById(d.itemId); idrop = MapleItemInformationProvider.getInstance().randomizeStats(eq); } else { idrop = new Item(d.itemId, (short) 0, (short) (d.Maximum != 1 ? Randomizer.nextInt(d.Maximum - d.Minimum) + d.Minimum : 1), (short) 0); } this.stolen = d.itemId; this.map.spawnMobDrop(idrop, this.map.calcDropPos(getPosition(), getTruePosition()), this, chr, (byte) 0, 0); break; } } } else { this.stolen = 0; } } public final void setLastNode(int lastNode) { this.lastNode = lastNode; } public final int getLastNode() { return this.lastNode; } public final void cancelStatus(MonsterStatus stat) { if ((stat == MonsterStatus.EMPTY) || (stat == MonsterStatus.SUMMON)) { return; } MonsterStatusEffect mse = (MonsterStatusEffect) this.stati.get(stat); if ((mse == null) || (!isAlive())) { return; } if (mse.isReflect()) { this.reflectpack = null; } mse.cancelPoisonSchedule(this); MapleCharacter con = getController(); if (con != null) { this.map.broadcastMessage(con, MobPacket.cancelMonsterStatus(getObjectId(), stat), getTruePosition()); con.getClient().getSession().write(MobPacket.cancelMonsterStatus(getObjectId(), stat)); } else { this.map.broadcastMessage(MobPacket.cancelMonsterStatus(getObjectId(), stat), getTruePosition()); } this.stati.remove(stat); } public final void cancelSingleStatus(MonsterStatusEffect stat) { if ((stat == null) || (stat.getStati() == MonsterStatus.EMPTY) || (stat.getStati() == MonsterStatus.SUMMON) || (!isAlive())) { return; } if ((stat.getStati() != MonsterStatus.POISON) && (stat.getStati() != MonsterStatus.BURN)) { cancelStatus(stat.getStati()); return; } this.poisonsLock.writeLock().lock(); try { if (!this.poisons.contains(stat)) { return; } this.poisons.remove(stat); if (stat.isReflect()) { this.reflectpack = null; } stat.cancelPoisonSchedule(this); MapleCharacter con = getController(); if (con != null) { this.map.broadcastMessage(con, MobPacket.cancelPoison(getObjectId(), stat), getTruePosition()); con.getClient().getSession().write(MobPacket.cancelPoison(getObjectId(), stat)); } else { this.map.broadcastMessage(MobPacket.cancelPoison(getObjectId(), stat), getTruePosition()); } } finally { this.poisonsLock.writeLock().unlock(); } } public final void cancelDropItem() { this.lastDropTime = 0L; } public final void startDropItemSchedule() { cancelDropItem(); if ((this.stats.getDropItemPeriod() <= 0) || (!isAlive())) { return; } this.shouldDropItem = false; this.lastDropTime = System.currentTimeMillis(); } public boolean shouldDrop(long now) { return (this.lastDropTime > 0L) && (this.lastDropTime + this.stats.getDropItemPeriod() * 1000 < now); } public void doDropItem(long now) { int itemId; switch (getId()) { case 9300061: itemId = 4001101; break; default: cancelDropItem(); return; } if ((isAlive()) && (this.map != null)) { if (this.shouldDropItem) { this.map.spawnAutoDrop(itemId, getTruePosition()); } else { this.shouldDropItem = true; } } this.lastDropTime = now; } public byte[] getNodePacket() { return this.nodepack; } public void setNodePacket(byte[] np) { this.nodepack = np; } public void registerKill(long next) { this.nextKill = (System.currentTimeMillis() + next); } public boolean shouldKill(long now) { return (this.nextKill > 0L) && (now > this.nextKill); } public int getLinkCID() { return this.linkCID; } public void setLinkCID(int lc) { this.linkCID = lc; if (lc > 0) { this.stati.put(MonsterStatus.HYPNOTIZE, new MonsterStatusEffect(MonsterStatus.HYPNOTIZE, Integer.valueOf(60000), 30001062, null, false)); } } private class PartyAttackerEntry implements MapleMonster.AttackerEntry { private long totDamage = 0L; private final Map<Integer, MapleMonster.OnePartyAttacker> attackers = new HashMap(6); private int partyid; public PartyAttackerEntry(int partyid) { this.partyid = partyid; } public List<MapleMonster.AttackingMapleCharacter> getAttackers() { List ret = new ArrayList(this.attackers.size()); for (Map.Entry entry : this.attackers.entrySet()) { MapleCharacter chr = MapleMonster.this.map.getCharacterById(((Integer) entry.getKey()).intValue()); if (chr != null) { ret.add(new MapleMonster.AttackingMapleCharacter(chr, ((MapleMonster.OnePartyAttacker) entry.getValue()).lastAttackTime)); } } return ret; } private final Map<MapleCharacter, MapleMonster.OnePartyAttacker> resolveAttackers() { Map ret = new HashMap(this.attackers.size()); for (Map.Entry aentry : this.attackers.entrySet()) { MapleCharacter chr = MapleMonster.this.map.getCharacterById(((Integer) aentry.getKey()).intValue()); if (chr != null) { ret.put(chr, aentry.getValue()); } } return ret; } @Override public final boolean contains(MapleCharacter chr) { return this.attackers.containsKey(Integer.valueOf(chr.getId())); } @Override public final long getDamage() { return this.totDamage; } @Override public void addDamage(MapleCharacter from, long damage, boolean updateAttackTime) { MapleMonster.OnePartyAttacker oldPartyAttacker = (MapleMonster.OnePartyAttacker) this.attackers.get(Integer.valueOf(from.getId())); if (oldPartyAttacker != null) { oldPartyAttacker.damage += damage; oldPartyAttacker.lastKnownParty = from.getParty(); if (updateAttackTime) { oldPartyAttacker.lastAttackTime = System.currentTimeMillis(); } } else { MapleMonster.OnePartyAttacker onePartyAttacker = new MapleMonster.OnePartyAttacker(from.getParty(), damage); this.attackers.put(Integer.valueOf(from.getId()), onePartyAttacker); if (!updateAttackTime) { onePartyAttacker.lastAttackTime = 0L; } } this.totDamage += damage; } @Override public final void killedMob(final MapleMap map, final int baseExp, final boolean mostDamage, final int lastSkill) { MapleCharacter pchr, highest = null; long iDamage, highestDamage = 0; int iexp = 0; MapleParty party; double addedPartyLevel, levelMod, innerBaseExp; List<MapleCharacter> expApplicable; final Map<MapleCharacter, ExpMap> expMap = new HashMap<MapleCharacter, ExpMap>(6); byte Class_Bonus_EXP; byte Premium_Bonus_EXP; for (final Entry<MapleCharacter, OnePartyAttacker> attacker : resolveAttackers().entrySet()) { party = attacker.getValue().lastKnownParty; addedPartyLevel = 0; Class_Bonus_EXP = 0; Premium_Bonus_EXP = 0; expApplicable = new ArrayList<MapleCharacter>(); for (final MaplePartyCharacter partychar : party.getMembers()) { if (attacker.getKey().getLevel() - partychar.getLevel() <= 5 || stats.getLevel() - partychar.getLevel() <= 5) { pchr = map.getCharacterById(partychar.getId()); if (pchr != null && pchr.isAlive()) { expApplicable.add(pchr); addedPartyLevel += pchr.getLevel(); Class_Bonus_EXP += ServerConstants.Class_Bonus_EXP(pchr.getJob()); if (pchr.getStat().equippedWelcomeBackRing && Premium_Bonus_EXP == 0) { Premium_Bonus_EXP = 80; } } } } iDamage = attacker.getValue().damage; if (iDamage > highestDamage) { highest = attacker.getKey(); highestDamage = iDamage; } innerBaseExp = baseExp * ((double) iDamage / totDamage); if (expApplicable.size() <= 1) { Class_Bonus_EXP = 0; //no class bonus if not in a party. } for (final MapleCharacter expReceiver : expApplicable) { iexp = expMap.get(expReceiver) == null ? 0 : expMap.get(expReceiver).exp; levelMod = expReceiver.getLevel() / addedPartyLevel * (GameConstants.GMS ? 0.8 : 0.4); iexp += (int) Math.round(((attacker.getKey().getId() == expReceiver.getId() ? (GameConstants.GMS ? 0.2 : 0.6) : 0.0) + levelMod) * innerBaseExp); expMap.put(expReceiver, new ExpMap(iexp, (byte) expApplicable.size(), Class_Bonus_EXP, Premium_Bonus_EXP)); } } ExpMap expmap; for (final Entry<MapleCharacter, ExpMap> expReceiver : expMap.entrySet()) { expmap = expReceiver.getValue(); giveExpToCharacter(expReceiver.getKey(), expmap.exp, mostDamage ? expReceiver.getKey() == highest : false, expMap.size(), expmap.ptysize, expmap.Class_Bonus_EXP, expmap.Premium_Bonus_EXP, lastSkill); } } @Override public final int hashCode() { int prime = 31; int result = 1; result = 31 * result + this.partyid; return result; } @Override public final boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } PartyAttackerEntry other = (PartyAttackerEntry) obj; if (this.partyid != other.partyid) { return false; } return true; } } private static final class OnePartyAttacker { public MapleParty lastKnownParty; public long damage; public long lastAttackTime; public OnePartyAttacker(MapleParty lastKnownParty, long damage) { this.lastKnownParty = lastKnownParty; this.damage = damage; this.lastAttackTime = System.currentTimeMillis(); } } private static final class ExpMap { public final int exp; public final byte ptysize; public final byte Class_Bonus_EXP; public final byte Premium_Bonus_EXP; public ExpMap(int exp, byte ptysize, byte Class_Bonus_EXP, byte Premium_Bonus_EXP) { this.exp = exp; this.ptysize = ptysize; this.Class_Bonus_EXP = Class_Bonus_EXP; this.Premium_Bonus_EXP = Premium_Bonus_EXP; } } private final class SingleAttackerEntry implements MapleMonster.AttackerEntry { private long damage = 0L; private int chrid; private long lastAttackTime; public SingleAttackerEntry(MapleCharacter from) { this.chrid = from.getId(); } @Override public void addDamage(MapleCharacter from, long damage, boolean updateAttackTime) { if (this.chrid == from.getId()) { this.damage += damage; if (updateAttackTime) { this.lastAttackTime = System.currentTimeMillis(); } } } @Override public final List<MapleMonster.AttackingMapleCharacter> getAttackers() { MapleCharacter chr = MapleMonster.this.map.getCharacterById(this.chrid); if (chr != null) { return Collections.singletonList(new MapleMonster.AttackingMapleCharacter(chr, this.lastAttackTime)); } return Collections.emptyList(); } @Override public boolean contains(MapleCharacter chr) { return this.chrid == chr.getId(); } @Override public long getDamage() { return this.damage; } @Override public void killedMob(MapleMap map, int baseExp, boolean mostDamage, int lastSkill) { MapleCharacter chr = map.getCharacterById(this.chrid); if ((chr != null) && (chr.isAlive())) { MapleMonster.this.giveExpToCharacter(chr, baseExp, mostDamage, 1, (byte) 0, (byte) 0, (byte) 0, lastSkill); } } @Override public int hashCode() { return this.chrid; } @Override public final boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } SingleAttackerEntry other = (SingleAttackerEntry) obj; return this.chrid == other.chrid; } } private static abstract interface AttackerEntry { public abstract List<MapleMonster.AttackingMapleCharacter> getAttackers(); public abstract void addDamage(MapleCharacter paramMapleCharacter, long paramLong, boolean paramBoolean); public abstract long getDamage(); public abstract boolean contains(MapleCharacter paramMapleCharacter); public abstract void killedMob(MapleMap paramMapleMap, int paramInt1, boolean paramBoolean, int paramInt2); } private static class AttackingMapleCharacter { private MapleCharacter attacker; private long lastAttackTime; public AttackingMapleCharacter(MapleCharacter attacker, long lastAttackTime) { this.attacker = attacker; this.lastAttackTime = lastAttackTime; } public final long getLastAttackTime() { return this.lastAttackTime; } public final void setLastAttackTime(long lastAttackTime) { this.lastAttackTime = lastAttackTime; } public final MapleCharacter getAttacker() { return this.attacker; } } }