PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/Trunk/src/net/sf/odinms/server/life/MapleMonster.java

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