MapleMonster.java
上传用户:gwt600
上传日期:2021-06-03
资源大小:704k
文件大小:42k
源码类别:

游戏

开发平台:

Java

  1. /*
  2. This file is part of the OdinMS Maple Story Server
  3. Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
  4. Matthias Butz <matze@odinms.de>
  5. Jan Christian Meyer <vimes@odinms.de>
  6. This program is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU Affero General Public License version 3
  8. as published by the Free Software Foundation. You may not use, modify
  9. or distribute this program under any other version of the
  10. GNU Affero General Public License.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU Affero General Public License for more details.
  15. You should have received a copy of the GNU Affero General Public License
  16. along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  */
  18. package net.sf.odinms.server.life;
  19. import java.lang.ref.WeakReference;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.LinkedHashMap;
  25. import java.util.LinkedList;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Random;
  29. import java.util.Map.Entry;
  30. import java.util.concurrent.ScheduledFuture;
  31. import net.sf.odinms.client.MapleBuffStat;
  32. import net.sf.odinms.client.MapleCharacter;
  33. import net.sf.odinms.client.MapleClient;
  34. import net.sf.odinms.client.MapleInventory;
  35. import net.sf.odinms.client.MapleInventoryType;
  36. import net.sf.odinms.client.MapleJob;
  37. import net.sf.odinms.client.SkillFactory;
  38. import net.sf.odinms.client.status.MonsterStatus;
  39. import net.sf.odinms.client.status.MonsterStatusEffect;
  40. import net.sf.odinms.net.MaplePacket;
  41. import net.sf.odinms.net.channel.ChannelServer;
  42. import net.sf.odinms.net.world.MapleParty;
  43. import net.sf.odinms.net.world.MaplePartyCharacter;
  44. import net.sf.odinms.scripting.event.EventInstanceManager;
  45. import net.sf.odinms.server.TimerManager;
  46. import net.sf.odinms.server.life.MapleMonsterInformationProvider.DropEntry;
  47. import net.sf.odinms.server.maps.MapleMap;
  48. import net.sf.odinms.server.maps.MapleMapObject;
  49. import net.sf.odinms.server.maps.MapleMapObjectType;
  50. import net.sf.odinms.tools.ArrayMap;
  51. import net.sf.odinms.tools.MaplePacketCreator;
  52. import net.sf.odinms.tools.Pair;
  53. public class MapleMonster extends AbstractLoadedMapleLife {
  54.     private MapleMonsterStats stats;
  55.     private MapleMonsterStats overrideStats;
  56.     private int hp;
  57.     private int team = -1;
  58.     private int mp;
  59.     private WeakReference<MapleCharacter> controller = new WeakReference<MapleCharacter>(null);
  60.     private boolean controllerHasAggro,  controllerKnowsAboutAggro;
  61.     private Collection<AttackerEntry> attackers = new LinkedList<AttackerEntry>();
  62.     private EventInstanceManager eventInstance = null;
  63.     private Collection<MonsterListener> listeners = new LinkedList<MonsterListener>();
  64.     private MapleCharacter highestDamageChar;
  65.     private Map<MonsterStatus, MonsterStatusEffect> stati = new LinkedHashMap<MonsterStatus, MonsterStatusEffect>();
  66.     private List<MonsterStatusEffect> activeEffects = new ArrayList<MonsterStatusEffect>();
  67.     private MapleMap map;
  68.     private int VenomMultiplier = 0;
  69.     private boolean fake = false;
  70.     private boolean dropsDisabled = false;
  71.     private List<Pair<Integer, Integer>> usedSkills = new ArrayList<Pair<Integer, Integer>>();
  72.     private Map<Pair<Integer, Integer>, Integer> skillsUsed = new HashMap<Pair<Integer, Integer>, Integer>();
  73.     private List<MonsterStatus> monsterBuffs = new ArrayList<MonsterStatus>();
  74.     public MapleMonster(int id, MapleMonsterStats stats) {
  75.         super(id);
  76.         initWithStats(stats);
  77.     }
  78.     public MapleMonster(MapleMonster monster) {
  79.         super(monster);
  80.         initWithStats(monster.stats);
  81.     }
  82.     private void initWithStats(MapleMonsterStats stats) {
  83.         setStance(5);
  84.         this.stats = stats;
  85.         hp = stats.getHp();
  86.         mp = stats.getMp();
  87.     }
  88.     public void disableDrops() {
  89.         this.dropsDisabled = true;
  90.     }
  91.     public boolean dropsDisabled() {
  92.         return dropsDisabled;
  93.     }
  94.     public void setMap(MapleMap map) {
  95.         this.map = map;
  96.     }
  97.     public int getDrop() {
  98.         MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance();
  99.         int lastAssigned = -1;
  100.         int minChance = 1;
  101.         List<DropEntry> dl = mi.retrieveDropChances(getId());
  102.         for (DropEntry d : dl) {
  103.             if (d.chance > minChance) {
  104.                 minChance = d.chance;
  105.             }
  106.         }
  107.         for (DropEntry d : dl) {
  108.             d.assignedRangeStart = lastAssigned + 1;
  109.             d.assignedRangeLength = (int) Math.ceil(((double) 1 / (double) d.chance) * minChance);
  110.             lastAssigned += d.assignedRangeLength;
  111.         }
  112.         // now produce the randomness o.o
  113.         Random r = new Random();
  114.         int c = r.nextInt(minChance);
  115.         for (DropEntry d : dl) {
  116.             if (c >= d.assignedRangeStart && c < (d.assignedRangeStart + d.assignedRangeLength)) {
  117.                 return d.itemId;
  118.             }
  119.         }
  120.         return -1;
  121.     }
  122.     public int getHp() {
  123.         return hp;
  124.     }
  125.     public void setHp(int hp) {
  126.         this.hp = hp;
  127.     }
  128.     public int getMaxHp() {
  129.         if (overrideStats != null) {
  130.             return overrideStats.getHp();
  131.         }
  132.         return stats.getHp();
  133.     }
  134.     public int getMp() {
  135.         return mp;
  136.     }
  137.     public void setMp(int mp) {
  138.         if (mp < 0) {
  139.             mp = 0;
  140.         }
  141.         this.mp = mp;
  142.     }
  143.     public int getMaxMp() {
  144.         if (overrideStats != null) {
  145.             return overrideStats.getMp();
  146.         }
  147.         return stats.getMp();
  148.     }
  149.     public int getExp() {
  150.         if (overrideStats != null) {
  151.             return overrideStats.getExp();
  152.         }
  153.         return stats.getExp();
  154.     }
  155.     public int getLevel() {
  156.         return stats.getLevel();
  157.     }
  158.     public int getRemoveAfter() {
  159.         return stats.getRemoveAfter();
  160.     }
  161.     public int getVenomMulti() {
  162.         return this.VenomMultiplier;
  163.     }
  164.     public void setVenomMulti(int multiplier) {
  165.         this.VenomMultiplier = multiplier;
  166.     }
  167.     public boolean isBoss() {
  168.         return stats.isBoss() || getId() == 8810018;
  169.     }
  170.     public boolean isFfaLoot() {
  171.         return stats.isFfaLoot();
  172.     }
  173.     public int getAnimationTime(String name) {
  174.         return stats.getAnimationTime(name);
  175.     }
  176.     public List<Integer> getRevives() {
  177.         return stats.getRevives();
  178.     }
  179.     public void setOverrideStats(MapleMonsterStats overrideStats) {
  180.         this.overrideStats = overrideStats;
  181.     }
  182.     public byte getTagColor() {
  183.         return stats.getTagColor();
  184.     }
  185.     public byte getTagBgColor() {
  186.         return stats.getTagBgColor();
  187.     }
  188.     public boolean getUndead() {
  189.         return stats.getUndead();
  190.     }
  191.     /**
  192.      *
  193.      * @param from the player that dealt the damage
  194.      * @param damage
  195.      */
  196.     public void damage(MapleCharacter from, int damage, boolean updateAttackTime) {
  197.         AttackerEntry attacker = null;
  198.         if (from.getParty() != null) {
  199.             attacker = new PartyAttackerEntry(from.getParty().getId(), from.getClient().getChannelServer());
  200.         } else {
  201.             attacker = new SingleAttackerEntry(from, from.getClient().getChannelServer());
  202.         }
  203.         boolean replaced = false;
  204.         for (AttackerEntry aentry : attackers) {
  205.             if (aentry.equals(attacker)) {
  206.                 attacker = aentry;
  207.                 replaced = true;
  208.                 break;
  209.             }
  210.         }
  211.         if (!replaced) {
  212.             attackers.add(attacker);
  213.         }
  214.         int rDamage = Math.max(0, Math.min(damage, this.hp));
  215.         attacker.addDamage(from, rDamage, updateAttackTime);
  216.         this.hp -= rDamage;
  217.         int remhppercentage = (int) Math.ceil((this.hp * 100.0) / getMaxHp());
  218.         if (remhppercentage < 1) {
  219.             remhppercentage = 1;
  220.         }
  221.         long okTime = System.currentTimeMillis() - 4000;
  222.         if (hasBossHPBar()) {
  223.             from.getMap().broadcastMessage(makeBossHPBarPacket(), getPosition());
  224.         } else if (!isBoss()) {
  225.             for (AttackerEntry mattacker : attackers) {
  226.                 for (AttackingMapleCharacter cattacker : mattacker.getAttackers()) {
  227.                     // current attacker is on the map of the monster
  228.                     if (cattacker.getAttacker().getMap() == from.getMap()) {
  229.                         if (cattacker.getLastAttackTime() >= okTime) {
  230.                             cattacker.getAttacker().getClient().getSession().write(MaplePacketCreator.showMonsterHP(getObjectId(), remhppercentage));
  231.                         }
  232.                     }
  233.                 }
  234.             }
  235.         }
  236.     }
  237.     public int getTeam() {
  238. return team;
  239. }
  240.     public void heal(int hp, int mp) {
  241. // int finalHP = hp / 10000 * ((int) (8000 + 10000 * Math.random()));
  242.         int hp2Heal = getHp() + hp;
  243.         int mp2Heal = getMp() + mp;
  244.         if (hp2Heal >= getMaxHp()) {
  245.             hp2Heal = getMaxHp();
  246.         }
  247.         if (mp2Heal >= getMaxMp()) {
  248.             mp2Heal = getMaxMp();
  249.         }
  250.         setHp(hp2Heal);
  251.         setMp(mp2Heal);
  252.         getMap().broadcastMessage(MaplePacketCreator.healMonster(getObjectId(), hp));
  253.     }
  254.     public boolean isAttackedBy(MapleCharacter chr) {
  255.         for (AttackerEntry aentry : attackers) {
  256.             if (aentry.contains(chr)) {
  257.                 return true;
  258.             }
  259.         }
  260.         return false;
  261.     }
  262.     public void giveExpToCharacter(MapleCharacter attacker, int exp, boolean highestDamage, int numExpSharers) {
  263.         MapleInventory r =attacker.getInventory(MapleInventoryType.CASH);
  264.         if (highestDamage) {
  265.             if (eventInstance != null) {
  266.                 eventInstance.monsterKilled(attacker, this);
  267.             }
  268.             highestDamageChar = attacker;
  269.         }
  270.         if (attacker.getHp() > 0) {
  271.             int personalExp = exp;
  272.             if (exp > 0) {
  273.                 Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL);
  274.                 if (holySymbol != null) {
  275.                     if (numExpSharers == 1) {
  276.                         personalExp *= 1.0 + (holySymbol.doubleValue() / 500.0);
  277.                     } else {
  278.                         personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0);
  279.                     }
  280.                 }
  281.                 
  282.             }
  283.             while (personalExp > Integer.MAX_VALUE) {
  284.                 attacker.gainExp(Integer.MAX_VALUE, true, false, highestDamage);
  285.                 personalExp -= Integer.MAX_VALUE;
  286.             }
  287.             /*双倍卡5211018 || 5211003*/ 
  288.             if (r.findById(5211003) != null) {
  289.                 attacker.gainExp(personalExp * 3, true, false, highestDamage);
  290.                 if (r.findById(5211017) != null) {
  291.                 attacker.gainExp(personalExp * 100, true, false, highestDamage);
  292.             } else if (r.findById(5211018) != null){
  293.                 attacker.gainExp(personalExp * 2, true, false, highestDamage);
  294.             } else {
  295.                if (attacker.getClient().getChannel() == ChannelServer.getInstance(attacker.getClient().getChannel()).getblackmsVipCh()) {
  296. attacker.gainExp((int) (personalExp * ChannelServer.getInstance(attacker.getClient().getChannel()).getVipChExpRate()), true, false, highestDamage);
  297.                 } else {
  298.                     attacker.gainExp(personalExp, true, false, highestDamage);
  299.                 }//这个是给VIP频道怪物经验的
  300.             }
  301.             attacker.mobKilled(this.getId());
  302.         }
  303.         if (exp < 0) {
  304.                 personalExp = Integer.MAX_VALUE;
  305.             }
  306.             int exped=personalExp *(attacker.hasexp()+attacker.hasEXPCard());
  307.             if(exped>2100000000){
  308.                 exped=2100000000;
  309.             }
  310.     }
  311.     }
  312.     public MapleCharacter killBy(MapleCharacter killer) {
  313.         // broadcastMessage(null, MaplePacketCreator.getPreKillthis(this.getObjectId()));
  314.         // update exp
  315.         long totalBaseExpL = this.getExp() * ChannelServer.getInstance(killer.getClient().getChannel()).getExpRate() * killer.getClient().getPlayer().hasEXPCard();
  316.         int totalBaseExp = (int) (Math.min(Integer.MAX_VALUE, totalBaseExpL));
  317.         AttackerEntry highest = null;
  318.         int highdamage = 0;
  319.         for (AttackerEntry attackEntry : attackers) {
  320.             if (attackEntry.getDamage() > highdamage) {
  321.                 highest = attackEntry;
  322.                 highdamage = attackEntry.getDamage();
  323.             }
  324.         }
  325.         for (AttackerEntry attackEntry : attackers) {
  326.             int baseExp = (int) Math.ceil(totalBaseExp * ((double) attackEntry.getDamage() / getMaxHp()));
  327.             attackEntry.killedMob(killer.getMap(), baseExp, attackEntry == highest);
  328.         }
  329.         if (this.getController() != null) { // this can/should only happen when a hidden gm attacks the monster
  330.             getController().getClient().getSession().write(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
  331.             getController().stopControllingMonster(this);
  332.         }
  333.         // maybe this isn't the best place to do it, fixme then
  334.         final List<Integer> toSpawn = this.getRevives();
  335.         if (toSpawn != null) {
  336.             final MapleMap reviveMap = killer.getMap();
  337.             TimerManager.getInstance().schedule(new Runnable() {
  338.                 public void run() {
  339.                     for (Integer mid : toSpawn) {
  340.                         MapleMonster mob = MapleLifeFactory.getMonster(mid);
  341.                         if (eventInstance != null) {
  342.                             eventInstance.registerMonster(mob);
  343.                         }
  344.                         mob.setPosition(getPosition());
  345.                         if (dropsDisabled()) {
  346.                             mob.disableDrops();
  347.                         }
  348.                         reviveMap.spawnRevives(mob);
  349.                     }
  350.                 }
  351.                 //}, this.getAnimationTime("die1") - MapleMonsterInformationProvider.APPROX_FADE_DELAY);
  352.             }, this.getAnimationTime("die1"));
  353.         }
  354.         if (eventInstance != null) {
  355.             eventInstance.unregisterMonster(this);
  356.         }
  357.         for (MonsterListener listener : listeners.toArray(new MonsterListener[listeners.size()])) {
  358.             listener.monsterKilled(this, highestDamageChar);
  359.         }
  360.         MapleCharacter ret = highestDamageChar;
  361.         highestDamageChar = null; // may not keep hard references to chars outside of PlayerStorage or MapleMap
  362.         return ret;
  363.     }
  364.     public boolean isAlive() {
  365.         return this.hp > 0;
  366.     }
  367.     public MapleCharacter getController() {
  368.         return controller.get();
  369.     }
  370.     public void setController(MapleCharacter controller) {
  371.         this.controller = new WeakReference<MapleCharacter>(controller);
  372.     }
  373.     public void switchController(MapleCharacter newController, boolean immediateAggro) {
  374.         MapleCharacter controllers = getController();
  375.         if (controllers == newController) {
  376.             return;
  377.         }
  378.         if (controllers != null) {
  379.             controllers.stopControllingMonster(this);
  380.             controllers.getClient().getSession().write(MaplePacketCreator.stopControllingMonster(getObjectId()));
  381.         }
  382.         newController.controlMonster(this, immediateAggro);
  383.         setController(newController);
  384.         if (immediateAggro) {
  385.             setControllerHasAggro(true);
  386.         }
  387.         setControllerKnowsAboutAggro(false);
  388.     }
  389.     public void addListener(MonsterListener listener) {
  390.         listeners.add(listener);
  391.     }
  392.     public void removeListener(MonsterListener listener) {
  393.         listeners.remove(listener);
  394.     }
  395.     public boolean isControllerHasAggro() {
  396.         if (fake) {
  397.             return false;
  398.         }
  399.         return controllerHasAggro;
  400.     }
  401.     public void setControllerHasAggro(boolean controllerHasAggro) {
  402.         if (fake) {
  403.             return;
  404.         }
  405.         this.controllerHasAggro = controllerHasAggro;
  406.     }
  407.     public boolean isControllerKnowsAboutAggro() {
  408.         if (fake) {
  409.             return false;
  410.         }
  411.         return controllerKnowsAboutAggro;
  412.     }
  413.     public void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
  414.         if (fake) {
  415.             return;
  416.         }
  417.         this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
  418.     }
  419.     public MaplePacket makeBossHPBarPacket() {
  420.         return MaplePacketCreator.showBossHP(getId(), getHp(), getMaxHp(), getTagColor(), getTagBgColor());
  421.     }
  422.     public boolean hasBossHPBar() {
  423.         return (isBoss() && getTagColor() > 0) || isHT();
  424.     }
  425.     private boolean isHT() {
  426.         return this.getId() == 8810018;
  427.     }
  428.     @Override
  429.     public void sendSpawnData(MapleClient client) {
  430.         if (!isAlive() || client.getPlayer().isFake()) {
  431.             return;
  432.         }
  433.         if (isFake()) {
  434.             client.getSession().write(MaplePacketCreator.spawnFakeMonster(this, 0));
  435.         } else {
  436.             client.getSession().write(MaplePacketCreator.spawnMonster(this, false));
  437.         }
  438.         if (stati.size() > 0) {
  439.             for (MonsterStatusEffect mse : activeEffects) {
  440.                 MaplePacket packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), mse.getStati(), mse.getSkill().getId(), false, 0);
  441.                 client.getSession().write(packet);
  442.             }
  443.         }
  444.         if (hasBossHPBar()) {
  445.             client.getSession().write(makeBossHPBarPacket());
  446.         }
  447.     }
  448.     @Override
  449.     public void sendDestroyData(MapleClient client) {
  450.         client.getSession().write(MaplePacketCreator.killMonster(getObjectId(), false));
  451.     }
  452.     @Override
  453.     public String toString() {
  454.         return getName() + "(" + getId() + ") at " + getPosition().x + "/" + getPosition().y + " with " + getHp() + "/" + getMaxHp() +
  455.                 "hp, " + getMp() + "/" + getMaxMp() + " mp (alive: " + isAlive() + " oid: " + getObjectId() + ")";
  456.     }
  457.     @Override
  458.     public MapleMapObjectType getType() {
  459.         return MapleMapObjectType.MONSTER;
  460.     }
  461.     public EventInstanceManager getEventInstance() {
  462.         return eventInstance;
  463.     }
  464.     public void setEventInstance(EventInstanceManager eventInstance) {
  465.         this.eventInstance = eventInstance;
  466.     }
  467.     public boolean isMobile() {
  468.         return stats.isMobile();
  469.     }
  470.     public ElementalEffectiveness getEffectiveness(Element e) {
  471.         if (activeEffects.size() > 0 && stati.get(MonsterStatus.DOOM) != null) {
  472.             return ElementalEffectiveness.NORMAL; // like blue snails
  473.         }
  474.         return stats.getEffectiveness(e);
  475.     }
  476.     public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration) {
  477.         return applyStatus(from, status, poison, duration, false);
  478.     }
  479.     public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration, boolean venom) {
  480.         switch (stats.getEffectiveness(status.getSkill().getElement())) {
  481.             case IMMUNE:
  482.             case STRONG:
  483.                 return false;
  484.             case NORMAL:
  485.             case WEAK:
  486.                 break;
  487.             default:
  488.                 throw new RuntimeException("Unknown elemental effectiveness: " + stats.getEffectiveness(status.getSkill().getElement()));
  489.         }
  490.         // compos don't have an elemental (they have 2 - so we have to hack here...)
  491.         ElementalEffectiveness effectiveness = null;
  492.         switch (status.getSkill().getId()) {
  493.             case 2111006:
  494.                 effectiveness = stats.getEffectiveness(Element.POISON);
  495.                 if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) {
  496.                     return false;
  497.                 }
  498.                 break;
  499.             case 2211006:
  500.                 effectiveness = stats.getEffectiveness(Element.ICE);
  501.                 if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) {
  502.                     return false;
  503.                 }
  504.                 break;
  505.             case 4120005:
  506.             case 4220005:
  507.                 effectiveness = stats.getEffectiveness(Element.POISON);
  508.                 if (effectiveness == ElementalEffectiveness.WEAK) {
  509.                     return false;
  510.                 }
  511.                 break;
  512.         }
  513.         if (poison && getHp() <= 1) {
  514.             return false;
  515.         }
  516.         if (isBoss() && !(status.getStati().containsKey(MonsterStatus.SPEED))) {
  517.             return false;
  518.         }
  519.         for (MonsterStatus stat : status.getStati().keySet()) {
  520.             MonsterStatusEffect oldEffect = stati.get(stat);
  521.             if (oldEffect != null) {
  522.                 oldEffect.removeActiveStatus(stat);
  523.                 if (oldEffect.getStati().size() == 0) {
  524.                     oldEffect.getCancelTask().cancel(false);
  525.                     oldEffect.cancelPoisonSchedule();
  526.                     activeEffects.remove(oldEffect);
  527.                 }
  528.             }
  529.         }
  530.         TimerManager timerManager = TimerManager.getInstance();
  531.         final Runnable cancelTask = new Runnable() {
  532.             @Override
  533.             public void run() {
  534.                 if (isAlive()) {
  535.                     MaplePacket packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), status.getStati());
  536.                     map.broadcastMessage(packet, getPosition());
  537.                     if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) {
  538.                         getController().getClient().getSession().write(packet);
  539.                     }
  540.                 }
  541.                 activeEffects.remove(status);
  542.                 for (MonsterStatus stat : status.getStati().keySet()) {
  543.                     stati.remove(stat);
  544.                 }
  545.                 setVenomMulti(0);
  546.                 status.cancelPoisonSchedule();
  547.             }
  548.         };
  549.         if (poison) {
  550.             int poisonLevel = from.getSkillLevel(status.getSkill());
  551.             int poisonDamage = Math.min(Short.MAX_VALUE, (int) (getMaxHp() / (70.0 - poisonLevel) + 0.999));
  552.             status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
  553.             status.setPoisonSchedule(timerManager.register(new PoisonTask(poisonDamage, from, status, cancelTask, false), 1000, 1000));
  554.         } else if (venom) {
  555.             if (from.getJob() == MapleJob.NIGHTLORD || from.getJob() == MapleJob.SHADOWER) {
  556.                 int poisonLevel = 0;
  557.                 int matk = 0;
  558.                 if (from.getJob() == MapleJob.NIGHTLORD) {
  559.                     poisonLevel = from.getSkillLevel(SkillFactory.getSkill(4120005));
  560.                     if (poisonLevel <= 0) {
  561.                         return false;
  562.                     }
  563.                     matk = SkillFactory.getSkill(4120005).getEffect(poisonLevel).getMatk();
  564.                 } else if (from.getJob() == MapleJob.SHADOWER) {
  565.                     poisonLevel = from.getSkillLevel(SkillFactory.getSkill(4220005));
  566.                     if (poisonLevel <= 0) {
  567.                         return false;
  568.                     }
  569.                     matk = SkillFactory.getSkill(4220005).getEffect(poisonLevel).getMatk();
  570.                 } else {
  571.                     return false;
  572.                 }
  573.                 Random r = new Random();
  574.                 int luk = from.getLuk();
  575.                 int maxDmg = (int) Math.ceil(Math.min(Short.MAX_VALUE, 0.2 * luk * matk));
  576.                 int minDmg = (int) Math.ceil(Math.min(Short.MAX_VALUE, 0.1 * luk * matk));
  577.                 int gap = maxDmg - minDmg;
  578.                 if (gap == 0) {
  579.                     gap = 1;
  580.                 }
  581.                 int poisonDamage = 0;
  582.                 for (int i = 0; i < getVenomMulti(); i++) {
  583.                     poisonDamage = poisonDamage + (r.nextInt(gap) + minDmg);
  584.                 }
  585.                 poisonDamage = Math.min(Short.MAX_VALUE, poisonDamage);
  586.                 status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
  587.                 status.setPoisonSchedule(timerManager.register(new PoisonTask(poisonDamage, from, status, cancelTask, false), 1000, 1000));
  588.             } else {
  589.                 return false;
  590.             }
  591.         } else if (status.getSkill().getId() == 4111003) { // shadow web
  592.             int webDamage = (int) (getMaxHp() / 50.0 + 0.999);
  593.             // actually shadow web works different but similar...
  594.             status.setPoisonSchedule(timerManager.schedule(new PoisonTask(webDamage, from, status, cancelTask, true), 3500));
  595.         }
  596.         for (MonsterStatus stat : status.getStati().keySet()) {
  597.             stati.put(stat, status);
  598.         }
  599.         activeEffects.add(status);
  600.         int animationTime = status.getSkill().getAnimationTime();
  601.         MaplePacket packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status.getStati(), status.getSkill().getId(), false, 0);
  602.         map.broadcastMessage(packet, getPosition());
  603.         if (getController() != null && !getController().isMapObjectVisible(this)) {
  604.             getController().getClient().getSession().write(packet);
  605.         }
  606.         ScheduledFuture<?> schedule = timerManager.schedule(cancelTask, duration + animationTime);
  607.         status.setCancelTask(schedule);
  608.         return true;
  609.     }
  610.     public void applyMonsterBuff(final MonsterStatus status, final int x, int skillId, long duration, MobSkill skill) {
  611.         TimerManager timerManager = TimerManager.getInstance();
  612.         final Runnable cancelTask = new Runnable() {
  613.             @Override
  614.             public void run() {
  615.                 if (isAlive()) {
  616.                     MaplePacket packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), Collections.singletonMap(status, Integer.valueOf(x)));
  617.                     map.broadcastMessage(packet, getPosition());
  618.                     if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) {
  619.                         getController().getClient().getSession().write(packet);
  620.                     }
  621.                     removeMonsterBuff(status);
  622.                 }
  623.             }
  624.         };
  625.         MaplePacket packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), Collections.singletonMap(status, x), skillId, true, 0, skill);
  626.         map.broadcastMessage(packet, getPosition());
  627.         if (getController() != null && !getController().isMapObjectVisible(this)) {
  628.             getController().getClient().getSession().write(packet);
  629.         }
  630.         timerManager.schedule(cancelTask, duration);
  631.         addMonsterBuff(status);
  632.     }
  633.     public void addMonsterBuff(MonsterStatus status) {
  634.         this.monsterBuffs.add(status);
  635.     }
  636.     public void removeMonsterBuff(MonsterStatus status) {
  637.         this.monsterBuffs.remove(status);
  638.     }
  639.     public boolean isBuffed(MonsterStatus status) {
  640.         return this.monsterBuffs.contains(status);
  641.     }
  642.     public void setFake(boolean fake) {
  643.         this.fake = fake;
  644.     }
  645.     public boolean isFake() {
  646.         return fake;
  647.     }
  648.     public MapleMap getMap() {
  649.         return map;
  650.     }
  651.     public List<Pair<Integer, Integer>> getSkills() {
  652.         return this.stats.getSkills();
  653.     }
  654.     public boolean hasSkill(int skillId, int level) {
  655.         return stats.hasSkill(skillId, level);
  656.     }
  657.     public boolean canUseSkill(MobSkill toUse) {
  658.         if (toUse == null) {
  659.             return false;
  660.         }
  661.         for (Pair<Integer, Integer> skill : usedSkills) {
  662.             if (skill.getLeft() == toUse.getSkillId() && skill.getRight() == toUse.getSkillLevel()) {
  663.                 return false;
  664.             }
  665.         }
  666.         if (toUse.getLimit() > 0) {
  667.             if (this.skillsUsed.containsKey(new Pair<Integer, Integer>(toUse.getSkillId(), toUse.getSkillLevel()))) {
  668.                 int times = this.skillsUsed.get(new Pair<Integer, Integer>(toUse.getSkillId(), toUse.getSkillLevel()));
  669.                 if (times >= toUse.getLimit()) {
  670.                     return false;
  671.                 }
  672.             }
  673.         }
  674.         if (toUse.getSkillId() == 200) {
  675.             Collection<MapleMapObject> mmo = getMap().getMapObjects();
  676.             int i = 0;
  677.             for (MapleMapObject mo : mmo) {
  678.                 if (mo.getType() == MapleMapObjectType.MONSTER) {
  679.                     i++;
  680.                 }
  681.             }
  682.             if (i > 100) {
  683.                 return false;
  684.             }
  685.         }
  686.         return true;
  687.     }
  688.     public void usedSkill(final int skillId, final int level, long cooltime) {
  689.         this.usedSkills.add(new Pair<Integer, Integer>(skillId, level));
  690.         if (this.skillsUsed.containsKey(new Pair<Integer, Integer>(skillId, level))) {
  691.             int times = this.skillsUsed.get(new Pair<Integer, Integer>(skillId, level)) + 1;
  692.             this.skillsUsed.remove(new Pair<Integer, Integer>(skillId, level));
  693.             this.skillsUsed.put(new Pair<Integer, Integer>(skillId, level), times);
  694.         } else {
  695.             this.skillsUsed.put(new Pair<Integer, Integer>(skillId, level), 1);
  696.         }
  697.         final MapleMonster mons = this;
  698.         TimerManager tMan = TimerManager.getInstance();
  699.         tMan.schedule(
  700.                 new Runnable() {
  701.                     @Override
  702.                     public void run() {
  703.                         mons.clearSkill(skillId, level);
  704.                     }
  705.                 }, cooltime);
  706.     }
  707.     public void clearSkill(int skillId, int level) {
  708.         int index = -1;
  709.         for (Pair<Integer, Integer> skill : usedSkills) {
  710.             if (skill.getLeft() == skillId && skill.getRight() == level) {
  711.                 index = usedSkills.indexOf(skill);
  712.                 break;
  713.             }
  714.         }
  715.         if (index != -1) {
  716.             usedSkills.remove(index);
  717.         }
  718.     }
  719.     public int getNoSkills() {
  720.         return this.stats.getNoSkills();
  721.     }
  722.     public boolean isFirstAttack() {
  723.         return this.stats.isFirstAttack();
  724.     }
  725.     public int getBuffToGive() {
  726.         return this.stats.getBuffToGive();
  727.     }
  728.     public List<MonsterStatus> getMonsterBuffs() {
  729.         return monsterBuffs;
  730.     }
  731.     private final class PoisonTask implements Runnable {
  732.         private final int poisonDamage;
  733.         private final MapleCharacter chr;
  734.         private final MonsterStatusEffect status;
  735.         private final Runnable cancelTask;
  736.         private final boolean shadowWeb;
  737.         private final MapleMap map;
  738.         private PoisonTask(int poisonDamage, MapleCharacter chr, MonsterStatusEffect status, Runnable cancelTask, boolean shadowWeb) {
  739.             this.poisonDamage = poisonDamage;
  740.             this.chr = chr;
  741.             this.status = status;
  742.             this.cancelTask = cancelTask;
  743.             this.shadowWeb = shadowWeb;
  744.             this.map = chr.getMap();
  745.         }
  746.         @Override
  747.         public void run() {
  748.             int damage = poisonDamage;
  749.             if (damage >= hp) {
  750.                 damage = hp - 1;
  751.                 if (!shadowWeb) {
  752.                     cancelTask.run();
  753.                     status.getCancelTask().cancel(false);
  754.                 }
  755.             }
  756.             if (hp > 1 && damage > 0) {
  757.                 damage(chr, damage, false);
  758.                 if (shadowWeb) {
  759.                     map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
  760.                 }
  761.             }
  762.         }
  763.     }
  764.     public String getName() {
  765.         return stats.getName();
  766.     }
  767.     private class AttackingMapleCharacter {
  768.         private MapleCharacter attacker;
  769.         private long lastAttackTime;
  770.         public AttackingMapleCharacter(MapleCharacter attacker, long lastAttackTime) {
  771.             super();
  772.             this.attacker = attacker;
  773.             this.lastAttackTime = lastAttackTime;
  774.         }
  775.         public long getLastAttackTime() {
  776.             return lastAttackTime;
  777.         }
  778.         public void setLastAttackTime(long lastAttackTime) {
  779.             this.lastAttackTime = lastAttackTime;
  780.         }
  781.         public MapleCharacter getAttacker() {
  782.             return attacker;
  783.         }
  784.     }
  785.     private interface AttackerEntry {
  786.         List<AttackingMapleCharacter> getAttackers();
  787.         public void addDamage(MapleCharacter from, int damage, boolean updateAttackTime);
  788.         public int getDamage();
  789.         public boolean contains(MapleCharacter chr);
  790.         public void killedMob(MapleMap map, int baseExp, boolean mostDamage);
  791.     }
  792.     private class SingleAttackerEntry implements AttackerEntry {
  793.         private int damage;
  794.         private int chrid;
  795.         private long lastAttackTime;
  796.         private ChannelServer cserv;
  797.         public SingleAttackerEntry(MapleCharacter from, ChannelServer cserv) {
  798.             this.chrid = from.getId();
  799.             this.cserv = cserv;
  800.         }
  801.         @Override
  802.         public void addDamage(MapleCharacter from, int damage, boolean updateAttackTime) {
  803.             if (chrid == from.getId()) {
  804.                 this.damage += damage;
  805.             } else {
  806.                 throw new IllegalArgumentException("Not the attacker of this entry");
  807.             }
  808.             if (updateAttackTime) {
  809.                 lastAttackTime = System.currentTimeMillis();
  810.             }
  811.         }
  812.         @Override
  813.         public List<AttackingMapleCharacter> getAttackers() {
  814.             MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(chrid);
  815.             if (chr != null) {
  816.                 return Collections.singletonList(new AttackingMapleCharacter(chr, lastAttackTime));
  817.             } else {
  818.                 return Collections.emptyList();
  819.             }
  820.         }
  821.         @Override
  822.         public boolean contains(MapleCharacter chr) {
  823.             return chrid == chr.getId();
  824.         }
  825.         @Override
  826.         public int getDamage() {
  827.             return damage;
  828.         }
  829.         @Override
  830.         public void killedMob(MapleMap map, int baseExp, boolean mostDamage) {
  831.             MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(chrid);
  832.             if (chr != null && chr.getMap() == map) {
  833.                 giveExpToCharacter(chr, baseExp, mostDamage, 1);
  834.             }
  835.         }
  836.         @Override
  837.         public int hashCode() {
  838.             return chrid;
  839.         }
  840.         @Override
  841.         public boolean equals(Object obj) {
  842.             if (this == obj) {
  843.                 return true;
  844.             }
  845.             if (obj == null) {
  846.                 return false;
  847.             }
  848.             if (getClass() != obj.getClass()) {
  849.                 return false;
  850.             }
  851.             final SingleAttackerEntry other = (SingleAttackerEntry) obj;
  852.             return chrid == other.chrid;
  853.         }
  854.     }
  855.     private static class OnePartyAttacker {
  856.         public MapleParty lastKnownParty;
  857.         public int damage;
  858.         public long lastAttackTime;
  859.         public OnePartyAttacker(MapleParty lastKnownParty, int damage) {
  860.             super();
  861.             this.lastKnownParty = lastKnownParty;
  862.             this.damage = damage;
  863.             this.lastAttackTime = System.currentTimeMillis();
  864.         }
  865.     }
  866.     private class PartyAttackerEntry implements AttackerEntry {
  867.         private int totDamage;
  868.         //private Map<String, Pair<Integer, MapleParty>> attackers;
  869.         private Map<Integer, OnePartyAttacker> attackers;
  870.         private int partyid;
  871.         private ChannelServer cserv;
  872.         public PartyAttackerEntry(int partyid, ChannelServer cserv) {
  873.             this.partyid = partyid;
  874.             this.cserv = cserv;
  875.             attackers = new HashMap<Integer, OnePartyAttacker>(6);
  876.         }
  877.         public List<AttackingMapleCharacter> getAttackers() {
  878.             List<AttackingMapleCharacter> ret = new ArrayList<AttackingMapleCharacter>(attackers.size());
  879.             for (Entry<Integer, OnePartyAttacker> entry : attackers.entrySet()) {
  880.                 MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(entry.getKey());
  881.                 if (chr != null) {
  882.                     ret.add(new AttackingMapleCharacter(chr, entry.getValue().lastAttackTime));
  883.                 }
  884.             }
  885.             return ret;
  886.         }
  887.         private Map<MapleCharacter, OnePartyAttacker> resolveAttackers() {
  888.             Map<MapleCharacter, OnePartyAttacker> ret = new HashMap<MapleCharacter, OnePartyAttacker>(attackers.size());
  889.             for (Entry<Integer, OnePartyAttacker> aentry : attackers.entrySet()) {
  890.                 MapleCharacter chr = cserv.getPlayerStorage().getCharacterById(aentry.getKey());
  891.                 if (chr != null) {
  892.                     ret.put(chr, aentry.getValue());
  893.                 }
  894.             }
  895.             return ret;
  896.         }
  897.         @Override
  898.         public boolean contains(MapleCharacter chr) {
  899.             return attackers.containsKey(chr.getId());
  900.         }
  901.         @Override
  902.         public int getDamage() {
  903.             return totDamage;
  904.         }
  905.         public void addDamage(MapleCharacter from, int damage, boolean updateAttackTime) {
  906.             OnePartyAttacker oldPartyAttacker = attackers.get(from.getId());
  907.             if (oldPartyAttacker != null) {
  908.                 oldPartyAttacker.damage += damage;
  909.                 oldPartyAttacker.lastKnownParty = from.getParty();
  910.                 if (updateAttackTime) {
  911.                     oldPartyAttacker.lastAttackTime = System.currentTimeMillis();
  912.                 }
  913.             } else {
  914.                 // TODO actually this causes wrong behaviour when the party changes between attacks
  915.                 // only the last setup will get exp - but otherwise we'd have to store the full party
  916.                 // constellation for every attack/everytime it changes, might be wanted/needed in the
  917.                 // future but not now
  918.                 OnePartyAttacker onePartyAttacker = new OnePartyAttacker(from.getParty(), damage);
  919.                 attackers.put(from.getId(), onePartyAttacker);
  920.                 if (!updateAttackTime) {
  921.                     onePartyAttacker.lastAttackTime = 0;
  922.                 }
  923.             }
  924.             totDamage += damage;
  925.         }
  926.         @Override
  927.         public void killedMob(MapleMap map, int baseExp, boolean mostDamage) {
  928.             Map<MapleCharacter, OnePartyAttacker> attackers_ = resolveAttackers();
  929.             MapleCharacter highest = null;
  930.             int highestDamage = 0;
  931.             Map<MapleCharacter, Integer> expMap = new ArrayMap<MapleCharacter, Integer>(6);
  932.             for (Entry<MapleCharacter, OnePartyAttacker> attacker : attackers_.entrySet()) {
  933.                 MapleParty party = attacker.getValue().lastKnownParty;
  934.                 double averagePartyLevel = 0;
  935.                 List<MapleCharacter> expApplicable = new ArrayList<MapleCharacter>();
  936.                 for (MaplePartyCharacter partychar : party.getMembers()) {
  937.                     if (attacker.getKey().getLevel() - partychar.getLevel() <= 5 ||
  938.                             getLevel() - partychar.getLevel() <= 5) {
  939.                         MapleCharacter pchr = cserv.getPlayerStorage().getCharacterByName(partychar.getName());
  940.                         if (pchr != null) {
  941.                             if (pchr.isAlive() && pchr.getMap() == map) {
  942.                                 expApplicable.add(pchr);
  943.                                 averagePartyLevel += pchr.getLevel();
  944.                             }
  945.                         }
  946.                     }
  947.                 }
  948.                 double expBonus = 1.0;
  949.                 if (expApplicable.size() > 1) {
  950.                     expBonus = 1.10 + 0.05 * expApplicable.size();
  951.                     averagePartyLevel /= expApplicable.size();
  952.                 }
  953.                 int iDamage = attacker.getValue().damage;
  954.                 if (iDamage > highestDamage) {
  955.                     highest = attacker.getKey();
  956.                     highestDamage = iDamage;
  957.                 }
  958.                 double innerBaseExp = baseExp * ((double) iDamage / totDamage);
  959.                 double expFraction = (innerBaseExp * expBonus) / (expApplicable.size() + 1);
  960.                 for (MapleCharacter expReceiver : expApplicable) {
  961.                     Integer oexp = expMap.get(expReceiver);
  962.                     int iexp;
  963.                     if (oexp == null) {
  964.                         iexp = 0;
  965.                     } else {
  966.                         iexp = oexp.intValue();
  967.                     }
  968.                     double expWeight = (expReceiver == attacker.getKey() ? 2.0 : 1.0);
  969.                     double levelMod = expReceiver.getLevel() / averagePartyLevel;
  970.                     if (levelMod > 1.0 || this.attackers.containsKey(expReceiver.getId())) {
  971.                         levelMod = 1.0;
  972.                     }
  973.                     iexp += (int) Math.round(expFraction * expWeight * levelMod);
  974.                     expMap.put(expReceiver, Integer.valueOf(iexp));
  975.                 }
  976.             }
  977.             // FUCK we are done -.-
  978.             for (Entry<MapleCharacter, Integer> expReceiver : expMap.entrySet()) {
  979.                 boolean white = mostDamage ? expReceiver.getKey() == highest : false;
  980.                 giveExpToCharacter(expReceiver.getKey(), expReceiver.getValue(), white, expMap.size());
  981.             }
  982.         }
  983.         @Override
  984.         public int hashCode() {
  985.             final int prime = 31;
  986.             int result = 1;
  987.             result = prime * result + partyid;
  988.             return result;
  989.         }
  990.         @Override
  991.         public boolean equals(Object obj) {
  992.             if (this == obj) {
  993.                 return true;
  994.             }
  995.             if (obj == null) {
  996.                 return false;
  997.             }
  998.             if (getClass() != obj.getClass()) {
  999.                 return false;
  1000.             }
  1001.             final PartyAttackerEntry other = (PartyAttackerEntry) obj;
  1002.             if (partyid != other.partyid) {
  1003.                 return false;
  1004.             }
  1005.             return true;
  1006.         }
  1007.     }
  1008.     public int getPADamage() {
  1009.         return stats.getPADamage();
  1010.     }
  1011. }