L2Attackable.java 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453
  1. /*
  2. * Copyright © 2004-2021 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server 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 GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver.gameserver.model.actor;
  20. import static com.l2jserver.gameserver.config.Configuration.character;
  21. import static com.l2jserver.gameserver.config.Configuration.customs;
  22. import static com.l2jserver.gameserver.config.Configuration.general;
  23. import static com.l2jserver.gameserver.config.Configuration.hunting;
  24. import static com.l2jserver.gameserver.config.Configuration.npc;
  25. import static com.l2jserver.gameserver.config.Configuration.rates;
  26. import java.util.ArrayList;
  27. import java.util.Collection;
  28. import java.util.LinkedList;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.concurrent.ConcurrentHashMap;
  32. import java.util.concurrent.atomic.AtomicReference;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;
  35. import com.l2jserver.commons.util.Rnd;
  36. import com.l2jserver.gameserver.ThreadPoolManager;
  37. import com.l2jserver.gameserver.ai.CtrlEvent;
  38. import com.l2jserver.gameserver.ai.CtrlIntention;
  39. import com.l2jserver.gameserver.ai.L2AttackableAI;
  40. import com.l2jserver.gameserver.ai.L2CharacterAI;
  41. import com.l2jserver.gameserver.ai.L2FortSiegeGuardAI;
  42. import com.l2jserver.gameserver.ai.L2SiegeGuardAI;
  43. import com.l2jserver.gameserver.datatables.EventDroplist;
  44. import com.l2jserver.gameserver.datatables.EventDroplist.DateDrop;
  45. import com.l2jserver.gameserver.datatables.ItemTable;
  46. import com.l2jserver.gameserver.enums.InstanceType;
  47. import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
  48. import com.l2jserver.gameserver.instancemanager.WalkingManager;
  49. import com.l2jserver.gameserver.model.AbsorberInfo;
  50. import com.l2jserver.gameserver.model.AggroInfo;
  51. import com.l2jserver.gameserver.model.DamageDoneInfo;
  52. import com.l2jserver.gameserver.model.L2CommandChannel;
  53. import com.l2jserver.gameserver.model.L2Party;
  54. import com.l2jserver.gameserver.model.L2Seed;
  55. import com.l2jserver.gameserver.model.actor.instance.L2GrandBossInstance;
  56. import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance;
  57. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  58. import com.l2jserver.gameserver.model.actor.instance.L2ServitorInstance;
  59. import com.l2jserver.gameserver.model.actor.knownlist.AttackableKnownList;
  60. import com.l2jserver.gameserver.model.actor.status.AttackableStatus;
  61. import com.l2jserver.gameserver.model.actor.tasks.attackable.CommandChannelTimer;
  62. import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
  63. import com.l2jserver.gameserver.model.drops.DropListScope;
  64. import com.l2jserver.gameserver.model.events.EventDispatcher;
  65. import com.l2jserver.gameserver.model.events.impl.character.npc.attackable.OnAttackableAggroRangeEnter;
  66. import com.l2jserver.gameserver.model.events.impl.character.npc.attackable.OnAttackableAttack;
  67. import com.l2jserver.gameserver.model.events.impl.character.npc.attackable.OnAttackableKill;
  68. import com.l2jserver.gameserver.model.holders.ItemHolder;
  69. import com.l2jserver.gameserver.model.items.L2Item;
  70. import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
  71. import com.l2jserver.gameserver.model.skills.Skill;
  72. import com.l2jserver.gameserver.model.zone.ZoneId;
  73. import com.l2jserver.gameserver.network.SystemMessageId;
  74. import com.l2jserver.gameserver.network.clientpackets.Say2;
  75. import com.l2jserver.gameserver.network.serverpackets.CreatureSay;
  76. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  77. import com.l2jserver.gameserver.taskmanager.DecayTaskManager;
  78. import com.l2jserver.gameserver.util.Util;
  79. public class L2Attackable extends L2Npc {
  80. private static final Logger LOG = LoggerFactory.getLogger(L2Attackable.class);
  81. // Raid
  82. private boolean _isRaid = false;
  83. private boolean _isRaidMinion = false;
  84. //
  85. private boolean _champion = false;
  86. private final Map<L2Character, AggroInfo> _aggroList = new ConcurrentHashMap<>();
  87. private boolean _isReturningToSpawnPoint = false;
  88. private boolean _canReturnToSpawnPoint = true;
  89. private boolean _seeThroughSilentMove = false;
  90. // Manor
  91. private boolean _seeded = false;
  92. private L2Seed _seed = null;
  93. private int _seederObjId = 0;
  94. private final AtomicReference<ItemHolder> _harvestItem = new AtomicReference<>();
  95. // Spoil
  96. private int _spoilerObjectId;
  97. private final AtomicReference<Collection<ItemHolder>> _sweepItems = new AtomicReference<>();
  98. // Over-hit
  99. private boolean _overhit;
  100. private double _overhitDamage;
  101. private L2Character _overhitAttacker;
  102. // Command channel
  103. private volatile L2CommandChannel _firstCommandChannelAttacked = null;
  104. private CommandChannelTimer _commandChannelTimer = null;
  105. private long _commandChannelLastAttack = 0;
  106. // Soul crystal
  107. private boolean _absorbed;
  108. private final Map<Integer, AbsorberInfo> _absorbersList = new ConcurrentHashMap<>();
  109. // Misc
  110. private boolean _mustGiveExpSp;
  111. protected int _onKillDelay = 5000;
  112. /**
  113. * Creates an attackable NPC.
  114. * @param template the attackable NPC template
  115. */
  116. public L2Attackable(L2NpcTemplate template) {
  117. super(template);
  118. setInstanceType(InstanceType.L2Attackable);
  119. setIsInvul(false);
  120. _mustGiveExpSp = true;
  121. }
  122. @Override
  123. public AttackableKnownList getKnownList() {
  124. return (AttackableKnownList) super.getKnownList();
  125. }
  126. @Override
  127. public void initKnownList() {
  128. setKnownList(new AttackableKnownList(this));
  129. }
  130. @Override
  131. public AttackableStatus getStatus() {
  132. return (AttackableStatus) super.getStatus();
  133. }
  134. @Override
  135. public void initCharStatus() {
  136. setStatus(new AttackableStatus(this));
  137. }
  138. @Override
  139. protected L2CharacterAI initAI() {
  140. return new L2AttackableAI(this);
  141. }
  142. public final Map<L2Character, AggroInfo> getAggroList() {
  143. return _aggroList;
  144. }
  145. public final boolean isReturningToSpawnPoint() {
  146. return _isReturningToSpawnPoint;
  147. }
  148. public final void setisReturningToSpawnPoint(boolean value) {
  149. _isReturningToSpawnPoint = value;
  150. }
  151. public final boolean canReturnToSpawnPoint() {
  152. return _canReturnToSpawnPoint;
  153. }
  154. public final void setCanReturnToSpawnPoint(boolean value) {
  155. _canReturnToSpawnPoint = value;
  156. }
  157. public boolean canSeeThroughSilentMove() {
  158. return _seeThroughSilentMove;
  159. }
  160. public void setSeeThroughSilentMove(boolean val) {
  161. _seeThroughSilentMove = val;
  162. }
  163. /**
  164. * Use the skill if minimum checks are pass.
  165. * @param skill the skill
  166. */
  167. public void useMagic(Skill skill) {
  168. if ((skill == null) || isAlikeDead() || skill.isPassive() || isCastingNow() || isSkillDisabled(skill)) {
  169. return;
  170. }
  171. if ((getCurrentMp() < (getStat().getMpConsume1(skill) + getStat().getMpConsume2(skill))) || (getCurrentHp() <= skill.getHpConsume())) {
  172. return;
  173. }
  174. if (!skill.isStatic()) {
  175. if (skill.isMagic()) {
  176. if (isMuted()) {
  177. return;
  178. }
  179. } else {
  180. if (isPhysicalMuted()) {
  181. return;
  182. }
  183. }
  184. }
  185. final var targets = skill.getTargets(this);
  186. if (!targets.isEmpty()) {
  187. getAI().setIntention(CtrlIntention.AI_INTENTION_CAST, skill, targets.get(0));
  188. }
  189. }
  190. /**
  191. * Reduce the current HP of the L2Attackable.
  192. * @param damage The HP decrease value
  193. * @param attacker The L2Character who attacks
  194. */
  195. @Override
  196. public void reduceCurrentHp(double damage, L2Character attacker, Skill skill) {
  197. reduceCurrentHp(damage, attacker, true, false, skill);
  198. }
  199. /**
  200. * Reduce the current HP of the L2Attackable, update its _aggroList and launch the doDie Task if necessary.
  201. * @param damage The HP decrease value
  202. * @param attacker The L2Character who attacks
  203. * @param awake The awake state (If True : stop sleeping)
  204. * @param isDOT
  205. * @param skill
  206. */
  207. @Override
  208. public void reduceCurrentHp(double damage, L2Character attacker, boolean awake, boolean isDOT, Skill skill) {
  209. if (isRaid() && !isMinion() && (attacker != null) && (attacker.getParty() != null) && attacker.getParty().isInCommandChannel() && attacker.getParty().getCommandChannel().meetRaidWarCondition(this)) {
  210. if (_firstCommandChannelAttacked == null) // looting right isn't set
  211. {
  212. synchronized (this) {
  213. if (_firstCommandChannelAttacked == null) {
  214. _firstCommandChannelAttacked = attacker.getParty().getCommandChannel();
  215. if (_firstCommandChannelAttacked != null) {
  216. _commandChannelTimer = new CommandChannelTimer(this);
  217. _commandChannelLastAttack = System.currentTimeMillis();
  218. ThreadPoolManager.getInstance().scheduleGeneral(_commandChannelTimer, 10000); // check for last attack
  219. _firstCommandChannelAttacked.broadcastPacket(new CreatureSay(0, Say2.PARTYROOM_ALL, "", "You have looting rights!")); // TODO: retail msg
  220. }
  221. }
  222. }
  223. } else if (attacker.getParty().getCommandChannel().equals(_firstCommandChannelAttacked)) // is in same channel
  224. {
  225. _commandChannelLastAttack = System.currentTimeMillis(); // update last attack time
  226. }
  227. }
  228. if (isEventMob()) {
  229. return;
  230. }
  231. // Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList
  232. if (attacker != null) {
  233. addDamage(attacker, (int) damage, skill);
  234. }
  235. // If this L2Attackable is a L2MonsterInstance and it has spawned minions, call its minions to battle
  236. if (this instanceof L2MonsterInstance) {
  237. L2MonsterInstance master = (L2MonsterInstance) this;
  238. if (master.hasMinions()) {
  239. master.getMinionList().onAssist(this, attacker);
  240. }
  241. master = master.getLeader();
  242. if ((master != null) && master.hasMinions()) {
  243. master.getMinionList().onAssist(this, attacker);
  244. }
  245. }
  246. // Reduce the current HP of the L2Attackable and launch the doDie Task if necessary
  247. super.reduceCurrentHp(damage, attacker, awake, isDOT, skill);
  248. }
  249. public synchronized void setMustRewardExpSp(boolean value) {
  250. _mustGiveExpSp = value;
  251. }
  252. public synchronized boolean getMustRewardExpSP() {
  253. return _mustGiveExpSp;
  254. }
  255. /**
  256. * Kill the L2Attackable (the corpse disappeared after 7 seconds), distribute rewards (EXP, SP, Drops...) and notify Quest Engine.<br>
  257. * Actions:<br>
  258. * Distribute Exp and SP rewards to L2PcInstance (including Summon owner) that hit the L2Attackable and to their Party members<br>
  259. * Notify the Quest Engine of the L2Attackable death if necessary.<br>
  260. * Kill the L2NpcInstance (the corpse disappeared after 7 seconds)<br>
  261. * Caution: This method DOESN'T GIVE rewards to L2PetInstance.
  262. * @param killer The L2Character that has killed the L2Attackable
  263. */
  264. @Override
  265. public boolean doDie(L2Character killer) {
  266. // Kill the L2NpcInstance (the corpse disappeared after 7 seconds)
  267. if (!super.doDie(killer)) {
  268. return false;
  269. }
  270. if ((killer != null) && killer.isPlayable()) {
  271. // Delayed notification
  272. EventDispatcher.getInstance().notifyEventAsyncDelayed(new OnAttackableKill(killer.getActingPlayer(), this, killer.isSummon()), this, _onKillDelay);
  273. }
  274. // Notify to minions if there are.
  275. if (isMonster()) {
  276. final L2MonsterInstance mob = (L2MonsterInstance) this;
  277. if ((mob.getLeader() != null) && mob.getLeader().hasMinions()) {
  278. var respawnTime = npc().getCustomMinionsRespawnTime().get(getId());
  279. respawnTime = respawnTime != null ? respawnTime * 1000 : -1;
  280. mob.getLeader().getMinionList().onMinionDie(mob, respawnTime);
  281. }
  282. if (mob.hasMinions()) {
  283. mob.getMinionList().onMasterDie(false);
  284. }
  285. }
  286. return true;
  287. }
  288. /**
  289. * Distribute Exp and SP rewards to L2PcInstance (including Summon owner) that hit the L2Attackable and to their Party members.<br>
  290. * Actions:<br>
  291. * Get the L2PcInstance owner of the L2ServitorInstance (if necessary) and L2Party in progress.<br>
  292. * Calculate the Experience and SP rewards in function of the level difference.<br>
  293. * Add Exp and SP rewards to L2PcInstance (including Summon penalty) and to Party members in the known area of the last attacker.<br>
  294. * Caution : This method DOESN'T GIVE rewards to L2PetInstance.
  295. * @param lastAttacker The L2Character that has killed the L2Attackable
  296. */
  297. @Override
  298. protected void calculateRewards(L2Character lastAttacker) {
  299. try {
  300. if (_aggroList.isEmpty()) {
  301. return;
  302. }
  303. // NOTE: Concurrent-safe map is used because while iterating to verify all conditions sometimes an entry must be removed.
  304. final Map<L2PcInstance, DamageDoneInfo> rewards = new ConcurrentHashMap<>();
  305. L2PcInstance maxDealer = null;
  306. int maxDamage = 0;
  307. long totalDamage = 0;
  308. // While Iterating over This Map Removing Object is Not Allowed
  309. // Go through the _aggroList of the L2Attackable
  310. for (AggroInfo info : _aggroList.values()) {
  311. if (info == null) {
  312. continue;
  313. }
  314. // Get the L2Character corresponding to this attacker
  315. final L2PcInstance attacker = info.getAttacker().getActingPlayer();
  316. if (attacker != null) {
  317. // Get damages done by this attacker
  318. final int damage = info.getDamage();
  319. // Prevent unwanted behavior
  320. if (damage > 1) {
  321. // Check if damage dealer isn't too far from this (killed monster)
  322. if (!Util.checkIfInRange(character().getPartyRange(), this, attacker, true)) {
  323. continue;
  324. }
  325. totalDamage += damage;
  326. // Calculate real damages (Summoners should get own damage plus summon's damage)
  327. final DamageDoneInfo reward = rewards.computeIfAbsent(attacker, DamageDoneInfo::new);
  328. reward.addDamage(damage);
  329. if (reward.getDamage() > maxDamage) {
  330. maxDealer = attacker;
  331. maxDamage = reward.getDamage();
  332. }
  333. }
  334. }
  335. }
  336. // Manage Base, Quests and Sweep drops of the L2Attackable
  337. doItemDrop((maxDealer != null) && maxDealer.isOnline() ? maxDealer : lastAttacker);
  338. // Manage drop of Special Events created by GM for a defined period
  339. doEventDrop(lastAttacker);
  340. if (!getMustRewardExpSP()) {
  341. return;
  342. }
  343. if (!rewards.isEmpty()) {
  344. for (DamageDoneInfo reward : rewards.values()) {
  345. if (reward == null) {
  346. continue;
  347. }
  348. // Attacker to be rewarded
  349. final L2PcInstance attacker = reward.getAttacker();
  350. // Total amount of damage done
  351. final int damage = reward.getDamage();
  352. // Get party
  353. final L2Party attackerParty = attacker.getParty();
  354. // Penalty applied to the attacker's XP
  355. // If this attacker have servitor, get Exp Penalty applied for the servitor.
  356. final float penalty = attacker.hasServitor() ? ((L2ServitorInstance) attacker.getSummon()).getExpMultiplier() : 1;
  357. // If there's NO party in progress
  358. if (attackerParty == null) {
  359. // Calculate Exp and SP rewards
  360. if (attacker.getKnownList().knowsObject(this)) {
  361. // Calculate the difference of level between this attacker (player or servitor owner) and the L2Attackable
  362. // mob = 24, atk = 10, diff = -14 (full xp)
  363. // mob = 24, atk = 28, diff = 4 (some xp)
  364. // mob = 24, atk = 50, diff = 26 (no xp)
  365. final int levelDiff = attacker.getLevel() - getLevel();
  366. final int[] expSp = calculateExpAndSp(levelDiff, damage, totalDamage);
  367. long exp = expSp[0];
  368. int sp = expSp[1];
  369. if (customs().championEnable() && isChampion()) {
  370. exp *= customs().getChampionRewardsExpSp();
  371. sp *= customs().getChampionRewardsExpSp();
  372. }
  373. exp *= penalty;
  374. // Check for an over-hit enabled strike
  375. L2Character overhitAttacker = getOverhitAttacker();
  376. if (isOverhit() && (overhitAttacker != null) && (overhitAttacker.getActingPlayer() != null) && (attacker == overhitAttacker.getActingPlayer())) {
  377. attacker.sendPacket(SystemMessageId.OVER_HIT);
  378. exp += calculateOverhitExp(exp);
  379. }
  380. // Distribute the Exp and SP between the L2PcInstance and its L2Summon
  381. if (!attacker.isDead()) {
  382. attacker.addExpAndSp(exp, sp, useVitalityRate());
  383. if (exp > 0) {
  384. attacker.updateVitalityPoints(getVitalityPoints(damage), true, false);
  385. if (!attacker.isInsideZone(ZoneId.PEACE) && ((attacker.getLevel() - getLevel()) <= 9)) {
  386. if (hunting().getNevitEnable()) {
  387. attacker.getHuntingSystem().startHuntingSystemTask();
  388. if (attacker.getHuntingSystem().getHuntingBonusTime() < hunting().getHuntingBonusMaxTime() || !hunting().getHuntingBonusLimit()) {
  389. attacker.getHuntingSystem().addPoints(hunting().getNevitNormalPoints());
  390. }
  391. }
  392. attacker.getRecSystem().startBonusTask(true);
  393. }
  394. }
  395. }
  396. }
  397. } else {
  398. // share with party members
  399. int partyDmg = 0;
  400. float partyMul = 1;
  401. int partyLvl = 0;
  402. // Get all L2Character that can be rewarded in the party
  403. final List<L2PcInstance> rewardedMembers = new ArrayList<>();
  404. // Go through all L2PcInstance in the party
  405. final List<L2PcInstance> groupMembers = attackerParty.isInCommandChannel() ? attackerParty.getCommandChannel().getMembers() : attackerParty.getMembers();
  406. for (L2PcInstance partyPlayer : groupMembers) {
  407. if ((partyPlayer == null) || partyPlayer.isDead()) {
  408. continue;
  409. }
  410. // Get the RewardInfo of this L2PcInstance from L2Attackable rewards
  411. final DamageDoneInfo reward2 = rewards.get(partyPlayer);
  412. // If the L2PcInstance is in the L2Attackable rewards add its damages to party damages
  413. if (reward2 != null) {
  414. if (Util.checkIfInRange(character().getPartyRange(), this, partyPlayer, true)) {
  415. partyDmg += reward2.getDamage(); // Add L2PcInstance damages to party damages
  416. rewardedMembers.add(partyPlayer);
  417. if (partyPlayer.getLevel() > partyLvl) {
  418. if (attackerParty.isInCommandChannel()) {
  419. partyLvl = attackerParty.getCommandChannel().getLevel();
  420. } else {
  421. partyLvl = partyPlayer.getLevel();
  422. }
  423. }
  424. }
  425. rewards.remove(partyPlayer); // Remove the L2PcInstance from the L2Attackable rewards
  426. } else {
  427. // Add L2PcInstance of the party (that have attacked or not) to members that can be rewarded
  428. // and in range of the monster.
  429. if (Util.checkIfInRange(character().getPartyRange(), this, partyPlayer, true)) {
  430. rewardedMembers.add(partyPlayer);
  431. if (partyPlayer.getLevel() > partyLvl) {
  432. if (attackerParty.isInCommandChannel()) {
  433. partyLvl = attackerParty.getCommandChannel().getLevel();
  434. } else {
  435. partyLvl = partyPlayer.getLevel();
  436. }
  437. }
  438. }
  439. }
  440. }
  441. // If the party didn't killed this L2Attackable alone
  442. if (partyDmg < totalDamage) {
  443. partyMul = ((float) partyDmg / totalDamage);
  444. }
  445. // Calculate the level difference between Party and L2Attackable
  446. final int levelDiff = partyLvl - getLevel();
  447. // Calculate Exp and SP rewards
  448. final int[] expSp = calculateExpAndSp(levelDiff, partyDmg, totalDamage);
  449. long exp = expSp[0];
  450. int sp = expSp[1];
  451. if (customs().championEnable() && isChampion()) {
  452. exp *= customs().getChampionRewardsExpSp();
  453. sp *= customs().getChampionRewardsExpSp();
  454. }
  455. exp *= partyMul;
  456. sp *= partyMul;
  457. // Check for an over-hit enabled strike
  458. // (When in party, the over-hit exp bonus is given to the whole party and split proportionally through the party members)
  459. L2Character overhitAttacker = getOverhitAttacker();
  460. if (isOverhit() && (overhitAttacker != null) && (overhitAttacker.getActingPlayer() != null) && (attacker == overhitAttacker.getActingPlayer())) {
  461. attacker.sendPacket(SystemMessageId.OVER_HIT);
  462. exp += calculateOverhitExp(exp);
  463. }
  464. // Distribute Experience and SP rewards to L2PcInstance Party members in the known area of the last attacker
  465. if (partyDmg > 0) {
  466. attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl, partyDmg, this);
  467. }
  468. }
  469. }
  470. }
  471. } catch (Exception ex) {
  472. LOG.error("Error calculating rewards!", ex);
  473. }
  474. }
  475. @Override
  476. public void addAttackerToAttackByList(L2Character player) {
  477. if ((player == null) || (player == this) || getAttackByList().contains(player)) {
  478. return;
  479. }
  480. getAttackByList().add(player);
  481. }
  482. /**
  483. * Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList.
  484. * @param attacker The L2Character that gave damages to this L2Attackable
  485. * @param damage The number of damages given by the attacker L2Character
  486. * @param skill
  487. */
  488. public void addDamage(L2Character attacker, int damage, Skill skill) {
  489. if (attacker == null) {
  490. return;
  491. }
  492. // Notify the L2Attackable AI with EVT_ATTACKED
  493. if (!isDead()) {
  494. try {
  495. // If monster is on walk - stop it
  496. if (isWalker() && !isCoreAIDisabled() && WalkingManager.getInstance().isOnWalk(this)) {
  497. WalkingManager.getInstance().stopMoving(this, false, true);
  498. }
  499. getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, attacker);
  500. addDamageHate(attacker, damage, (damage * 100) / (getLevel() + 7));
  501. final L2PcInstance player = attacker.getActingPlayer();
  502. if (player != null) {
  503. EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAttack(player, this, damage, skill, attacker.isSummon()), this);
  504. }
  505. } catch (Exception ex) {
  506. LOG.error("Error adding damage!", ex);
  507. }
  508. }
  509. }
  510. /**
  511. * Adds damage and hate to the attacker aggression list for this character.
  512. * @param attacker The L2Character that gave damages to this L2Attackable
  513. * @param damage The number of damages given by the attacker L2Character
  514. * @param aggro The hate (=damage) given by the attacker L2Character
  515. */
  516. public void addDamageHate(L2Character attacker, int damage, long aggro) {
  517. if (attacker == null || attacker == this) {
  518. return;
  519. }
  520. // Get the AggroInfo of the attacker L2Character from the _aggroList of the L2Attackable
  521. final AggroInfo ai = _aggroList.computeIfAbsent(attacker, AggroInfo::new);
  522. ai.addDamage(damage);
  523. // Traps does not cause aggro
  524. // making this hack because not possible to determine if damage made by trap
  525. // so just check for triggered trap here
  526. final L2PcInstance targetPlayer = attacker.getActingPlayer();
  527. if ((targetPlayer == null) || (targetPlayer.getTrap() == null) || !targetPlayer.getTrap().isTriggered()) {
  528. ai.addHate(aggro);
  529. }
  530. if ((targetPlayer != null) && (aggro == 0)) {
  531. addDamageHate(attacker, 0, 1);
  532. // Set the intention to the L2Attackable to AI_INTENTION_ACTIVE
  533. if (getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE) {
  534. getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  535. }
  536. // Notify to scripts
  537. EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAggroRangeEnter(this, targetPlayer, attacker.isSummon()), this);
  538. } else if ((targetPlayer == null) && (aggro == 0)) {
  539. aggro = 1;
  540. ai.addHate(1);
  541. }
  542. // Set the intention to the L2Attackable to AI_INTENTION_ACTIVE
  543. if ((aggro != 0) && (getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE)) {
  544. getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  545. }
  546. }
  547. public void reduceHate(L2Character target, long amount) {
  548. if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI)) {
  549. // TODO: this just prevents error until siege guards are handled properly
  550. stopHating(target);
  551. setTarget(null);
  552. getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
  553. return;
  554. }
  555. if (target == null) // whole aggrolist
  556. {
  557. L2Character mostHated = getMostHated();
  558. if (mostHated == null) // makes target passive for a moment more
  559. {
  560. ((L2AttackableAI) getAI()).setGlobalAggro(-25);
  561. return;
  562. }
  563. for (AggroInfo ai : _aggroList.values()) {
  564. ai.addHate(amount);
  565. }
  566. amount = getHating(mostHated);
  567. if (amount >= 0) {
  568. ((L2AttackableAI) getAI()).setGlobalAggro(-25);
  569. clearAggroList();
  570. getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  571. setWalking();
  572. }
  573. return;
  574. }
  575. AggroInfo ai = _aggroList.get(target);
  576. if (ai == null) {
  577. LOG.info("Target {} not present in aggro list of {}.", target, this);
  578. return;
  579. }
  580. ai.addHate(amount);
  581. if ((ai.getHate() >= 0) && (getMostHated() == null)) {
  582. ((L2AttackableAI) getAI()).setGlobalAggro(-25);
  583. clearAggroList();
  584. getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  585. setWalking();
  586. }
  587. }
  588. /**
  589. * Clears _aggroList hate of the L2Character without removing from the list.
  590. * @param target
  591. */
  592. public void stopHating(L2Character target) {
  593. if (target == null) {
  594. return;
  595. }
  596. AggroInfo ai = _aggroList.get(target);
  597. if (ai != null) {
  598. ai.stopHate();
  599. }
  600. }
  601. /**
  602. * @return the most hated L2Character of the L2Attackable _aggroList.
  603. */
  604. public L2Character getMostHated() {
  605. if (_aggroList.isEmpty() || isAlikeDead()) {
  606. return null;
  607. }
  608. L2Character mostHated = null;
  609. long maxHate = 0;
  610. // While Interacting over This Map Removing Object is Not Allowed
  611. // Go through the aggroList of the L2Attackable
  612. for (AggroInfo ai : _aggroList.values()) {
  613. if (ai == null) {
  614. continue;
  615. }
  616. if (ai.checkHate(this) > maxHate) {
  617. mostHated = ai.getAttacker();
  618. maxHate = ai.getHate();
  619. }
  620. }
  621. return mostHated;
  622. }
  623. /**
  624. * @return the 2 most hated L2Character of the L2Attackable _aggroList.
  625. */
  626. public List<L2Character> get2MostHated() {
  627. if (_aggroList.isEmpty() || isAlikeDead()) {
  628. return null;
  629. }
  630. L2Character mostHated = null;
  631. L2Character secondMostHated = null;
  632. long maxHate = 0;
  633. List<L2Character> result = new ArrayList<>();
  634. // While iterating over this map removing objects is not allowed
  635. // Go through the aggroList of the L2Attackable
  636. for (AggroInfo ai : _aggroList.values()) {
  637. if (ai == null) {
  638. continue;
  639. }
  640. if (ai.checkHate(this) > maxHate) {
  641. secondMostHated = mostHated;
  642. mostHated = ai.getAttacker();
  643. maxHate = ai.getHate();
  644. }
  645. }
  646. result.add(mostHated);
  647. if (getAttackByList().contains(secondMostHated)) {
  648. result.add(secondMostHated);
  649. } else {
  650. result.add(null);
  651. }
  652. return result;
  653. }
  654. public List<L2Character> getHateList() {
  655. if (_aggroList.isEmpty() || isAlikeDead()) {
  656. return null;
  657. }
  658. List<L2Character> result = new ArrayList<>();
  659. for (AggroInfo ai : _aggroList.values()) {
  660. if (ai == null) {
  661. continue;
  662. }
  663. ai.checkHate(this);
  664. result.add(ai.getAttacker());
  665. }
  666. return result;
  667. }
  668. /**
  669. * @param target The L2Character whose hate level must be returned
  670. * @return the hate level of the L2Attackable against this L2Character contained in _aggroList.
  671. */
  672. public long getHating(final L2Character target) {
  673. if (_aggroList.isEmpty() || (target == null)) {
  674. return 0;
  675. }
  676. final AggroInfo ai = _aggroList.get(target);
  677. if (ai == null) {
  678. return 0;
  679. }
  680. if (ai.getAttacker() instanceof L2PcInstance) {
  681. L2PcInstance act = (L2PcInstance) ai.getAttacker();
  682. if (act.isInvisible() || ai.getAttacker().isInvul() || act.isSpawnProtected()) {
  683. // Remove Object Should Use This Method and Can be Blocked While Interacting
  684. _aggroList.remove(target);
  685. return 0;
  686. }
  687. }
  688. if (!ai.getAttacker().isVisible() || ai.getAttacker().isInvisible()) {
  689. _aggroList.remove(target);
  690. return 0;
  691. }
  692. if (ai.getAttacker().isAlikeDead()) {
  693. ai.stopHate();
  694. return 0;
  695. }
  696. return ai.getHate();
  697. }
  698. public void doItemDrop(L2Character mainDamageDealer) {
  699. doItemDrop(getTemplate(), mainDamageDealer);
  700. }
  701. /**
  702. * Manage Base, Quests and Special Events drops of L2Attackable (called by calculateRewards).<br>
  703. * Concept:<br>
  704. * During a Special Event all L2Attackable can drop extra Items.<br>
  705. * Those extra Items are defined in the table allNpcDateDrops of the EventDroplist.<br>
  706. * Each Special Event has a start and end date to stop to drop extra Items automatically.<br>
  707. * Actions:<br>
  708. * Manage drop of Special Events created by GM for a defined period.<br>
  709. * Get all possible drops of this L2Attackable from L2NpcTemplate and add it Quest drops.<br>
  710. * For each possible drops (base + quests), calculate which one must be dropped (random).<br>
  711. * Get each Item quantity dropped (random).<br>
  712. * Create this or these L2ItemInstance corresponding to each Item Identifier dropped.<br>
  713. * If the autoLoot mode is actif and if the L2Character that has killed the L2Attackable is a L2PcInstance, Give the item(s) to the L2PcInstance that has killed the L2Attackable.<br>
  714. * If the autoLoot mode isn't actif or if the L2Character that has killed the L2Attackable is not a L2PcInstance, add this or these item(s) in the world as a visible object at the position where mob was last.
  715. * @param npcTemplate
  716. * @param mainDamageDealer
  717. */
  718. public void doItemDrop(L2NpcTemplate npcTemplate, L2Character mainDamageDealer) {
  719. if (mainDamageDealer == null) {
  720. return;
  721. }
  722. L2PcInstance player = mainDamageDealer.getActingPlayer();
  723. // Don't drop anything if the last attacker or owner isn't L2PcInstance
  724. if (player == null) {
  725. return;
  726. }
  727. CursedWeaponsManager.getInstance().checkDrop(this, player);
  728. if (isSpoiled()) {
  729. _sweepItems.set(npcTemplate.calculateDrops(DropListScope.CORPSE, this, player));
  730. }
  731. Collection<ItemHolder> deathItems = npcTemplate.calculateDrops(DropListScope.DEATH, this, player);
  732. if (deathItems != null) {
  733. for (ItemHolder drop : deathItems) {
  734. L2Item item = ItemTable.getInstance().getTemplate(drop.getId());
  735. // Check if the autoLoot mode is active
  736. // Give the item(s) to the L2PcInstance inventory has killed the L2Attackable
  737. if ((!item.hasExImmediateEffect() && ((!isRaid() && character().autoLoot()) || (isRaid() && character().autoLootRaids()))) || (item.hasExImmediateEffect() && character().autoLootHerbs())) {
  738. player.doAutoLoot(this, drop);
  739. } else if ((!item.hasExImmediateEffect() && ((!isRaid() && ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(item.getId())))))) || //
  740. (item.hasExImmediateEffect() && ((player.isAutoLootHerb() && customs().getAutoLootHerbsList().contains(item.getId()))))) {
  741. player.doAutoLoot(this, drop);
  742. } else {
  743. dropItem(player, drop); // Drop the rest or all item(s) on the ground
  744. }
  745. // Broadcast message if RaidBoss was defeated
  746. if (isRaid() && !isRaidMinion() && (drop.getCount() > 0)) {
  747. final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_DIED_DROPPED_S3_S2);
  748. sm.addCharName(this);
  749. sm.addItemName(item);
  750. sm.addLong(drop.getCount());
  751. broadcastPacket(sm);
  752. }
  753. }
  754. }
  755. // Apply Special Item drop with random(rnd) quantity(qty) for champions.
  756. if (customs().championEnable() && isChampion() && ((customs().getChampionRewardLowerLvlItemChance() > 0) || (customs().getChampionRewardHigherLvlItemChance() > 0))) {
  757. int champqty = Rnd.get(customs().getChampionRewardItemQty());
  758. ItemHolder item = new ItemHolder(customs().getChampionRewardItemID(), ++champqty);
  759. // Give the item(s) to the L2PcInstance inventory has killed the L2Attackable
  760. if ((player.getLevel() <= getLevel()) && (Rnd.get(100) < customs().getChampionRewardLowerLvlItemChance())) {
  761. if (character().autoLoot()) {
  762. player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
  763. } else if ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(item.getId()))) {
  764. player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
  765. } else {
  766. dropItem(player, item);
  767. }
  768. } else if ((player.getLevel() > getLevel()) && (Rnd.get(100) < customs().getChampionRewardHigherLvlItemChance())) {
  769. if (character().autoLoot()) {
  770. player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
  771. } else if ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(item.getId()))) {
  772. player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
  773. } else {
  774. dropItem(player, item);
  775. }
  776. }
  777. }
  778. }
  779. /**
  780. * Manage Special Events drops created by GM for a defined period.<br>
  781. * Concept:<br>
  782. * During a Special Event all L2Attackable can drop extra Items.<br>
  783. * Those extra Items are defined in the table allNpcDateDrops of the EventDroplist.<br>
  784. * Each Special Event has a start and end date to stop to drop extra Items automatically.<br>
  785. * Actions: <I>If an extra drop must be generated</I><br>
  786. * Get an Item Identifier (random) from the DateDrop Item table of this Event.<br>
  787. * Get the Item quantity dropped (random).<br>
  788. * Create this or these L2ItemInstance corresponding to this Item Identifier.<br>
  789. * If the autoLoot mode is actif and if the L2Character that has killed the L2Attackable is a L2PcInstance, Give the item(s) to the L2PcInstance that has killed the L2Attackable<br>
  790. * If the autoLoot mode isn't actif or if the L2Character that has killed the L2Attackable is not a L2PcInstance, add this or these item(s) in the world as a visible object at the position where mob was last
  791. * @param lastAttacker The L2Character that has killed the L2Attackable
  792. */
  793. public void doEventDrop(L2Character lastAttacker) {
  794. if (lastAttacker == null) {
  795. return;
  796. }
  797. L2PcInstance player = lastAttacker.getActingPlayer();
  798. // Don't drop anything if the last attacker or owner isn't L2PcInstance
  799. if (player == null) {
  800. return;
  801. }
  802. if ((player.getLevel() - getLevel()) > 9) {
  803. return;
  804. }
  805. // Go through DateDrop of EventDroplist allNpcDateDrops within the date range
  806. for (DateDrop drop : EventDroplist.getInstance().getAllDrops()) {
  807. if (Rnd.get(1000000) < drop.getEventDrop().getDropChance()) {
  808. final int itemId = drop.getEventDrop().getItemIdList()[Rnd.get(drop.getEventDrop().getItemIdList().length)];
  809. final long itemCount = Rnd.get(drop.getEventDrop().getMinCount(), drop.getEventDrop().getMaxCount());
  810. // Give the item(s) to the L2PcInstance inventory has killed the L2Attackable
  811. if (character().autoLoot()) {
  812. player.doAutoLoot(this, itemId, itemCount);
  813. } else if ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(itemId))) {
  814. player.doAutoLoot(this, itemId, itemCount);
  815. } else {
  816. dropItem(player, itemId, itemCount); // Drop the rest or all item(s) on the ground
  817. }
  818. }
  819. }
  820. }
  821. /**
  822. * @return the active weapon of this L2Attackable (= null).
  823. */
  824. public L2ItemInstance getActiveWeapon() {
  825. return null;
  826. }
  827. /**
  828. * Verifies if the creature is in the aggro list.
  829. * @param creature the creature
  830. * @return {@code true} if the creature is in the aggro list, {@code false} otherwise
  831. */
  832. public boolean isInAggroList(L2Character creature) {
  833. return _aggroList.containsKey(creature);
  834. }
  835. /**
  836. * Clear the _aggroList of the L2Attackable.
  837. */
  838. public void clearAggroList() {
  839. _aggroList.clear();
  840. // clear overhit values
  841. _overhit = false;
  842. _overhitDamage = 0;
  843. _overhitAttacker = null;
  844. }
  845. /**
  846. * @return {@code true} if there is a loot to sweep, {@code false} otherwise.
  847. */
  848. @Override
  849. public boolean isSweepActive() {
  850. return _sweepItems.get() != null;
  851. }
  852. /**
  853. * @return a copy of dummy items for the spoil loot.
  854. */
  855. public List<L2Item> getSpoilLootItems() {
  856. final Collection<ItemHolder> sweepItems = _sweepItems.get();
  857. final List<L2Item> lootItems = new LinkedList<>();
  858. if (sweepItems != null) {
  859. for (ItemHolder item : sweepItems) {
  860. lootItems.add(ItemTable.getInstance().getTemplate(item.getId()));
  861. }
  862. }
  863. return lootItems;
  864. }
  865. /**
  866. * @return table containing all L2ItemInstance that can be spoiled.
  867. */
  868. public Collection<ItemHolder> takeSweep() {
  869. return _sweepItems.getAndSet(null);
  870. }
  871. /**
  872. * @return table containing all L2ItemInstance that can be harvested.
  873. */
  874. public ItemHolder takeHarvest() {
  875. return _harvestItem.getAndSet(null);
  876. }
  877. /**
  878. * Checks if the corpse is too old.
  879. * @param attacker the player to validate
  880. * @param remainingTime the time to check
  881. * @param sendMessage if {@code true} will send a message of corpse too old
  882. * @return {@code true} if the corpse is too old
  883. */
  884. public boolean isOldCorpse(L2PcInstance attacker, int remainingTime, boolean sendMessage) {
  885. if (isDead() && (DecayTaskManager.getInstance().getRemainingTime(this) < remainingTime)) {
  886. if (sendMessage && (attacker != null)) {
  887. attacker.sendPacket(SystemMessageId.CORPSE_TOO_OLD_SKILL_NOT_USED);
  888. }
  889. return true;
  890. }
  891. return false;
  892. }
  893. /**
  894. * @param sweeper the player to validate.
  895. * @param sendMessage sendMessage if {@code true} will send a message of sweep not allowed.
  896. * @return {@code true} if is the spoiler or is in the spoiler party.
  897. */
  898. public boolean checkSpoilOwner(L2PcInstance sweeper, boolean sendMessage) {
  899. if ((sweeper.getObjectId() != getSpoilerObjectId()) && !sweeper.isInLooterParty(getSpoilerObjectId())) {
  900. if (sendMessage) {
  901. sweeper.sendPacket(SystemMessageId.SWEEP_NOT_ALLOWED);
  902. }
  903. return false;
  904. }
  905. return true;
  906. }
  907. /**
  908. * Set the over-hit flag on the L2Attackable.
  909. * @param status The status of the over-hit flag
  910. */
  911. public void overhitEnabled(boolean status) {
  912. _overhit = status;
  913. }
  914. /**
  915. * Set the over-hit values like the attacker who did the strike and the amount of damage done by the skill.
  916. * @param attacker The L2Character who hit on the L2Attackable using the over-hit enabled skill
  917. * @param damage The amount of damage done by the over-hit enabled skill on the L2Attackable
  918. */
  919. public void setOverhitValues(L2Character attacker, double damage) {
  920. // Calculate the over-hit damage
  921. // Ex: mob had 10 HP left, over-hit skill did 50 damage total, over-hit damage is 40
  922. double overhitDmg = -(getCurrentHp() - damage);
  923. if (overhitDmg < 0) {
  924. // we didn't killed the mob with the over-hit strike. (it wasn't really an over-hit strike)
  925. // let's just clear all the over-hit related values
  926. overhitEnabled(false);
  927. _overhitDamage = 0;
  928. _overhitAttacker = null;
  929. return;
  930. }
  931. overhitEnabled(true);
  932. _overhitDamage = overhitDmg;
  933. _overhitAttacker = attacker;
  934. }
  935. /**
  936. * Return the L2Character who hit on the L2Attackable using an over-hit enabled skill.
  937. * @return L2Character attacker
  938. */
  939. public L2Character getOverhitAttacker() {
  940. return _overhitAttacker;
  941. }
  942. /**
  943. * Return the amount of damage done on the L2Attackable using an over-hit enabled skill.
  944. * @return double damage
  945. */
  946. public double getOverhitDamage() {
  947. return _overhitDamage;
  948. }
  949. /**
  950. * @return True if the L2Attackable was hit by an over-hit enabled skill.
  951. */
  952. public boolean isOverhit() {
  953. return _overhit;
  954. }
  955. /**
  956. * Activate the absorbed soul condition on the L2Attackable.
  957. */
  958. public void absorbSoul() {
  959. _absorbed = true;
  960. }
  961. /**
  962. * @return True if the L2Attackable had his soul absorbed.
  963. */
  964. public boolean isAbsorbed() {
  965. return _absorbed;
  966. }
  967. /**
  968. * Adds an attacker that successfully absorbed the soul of this L2Attackable into the _absorbersList.
  969. * @param attacker
  970. */
  971. public void addAbsorber(L2PcInstance attacker) {
  972. // If we have no _absorbersList initiated, do it
  973. final AbsorberInfo ai = _absorbersList.get(attacker.getObjectId());
  974. // If the L2Character attacker isn't already in the _absorbersList of this L2Attackable, add it
  975. if (ai == null) {
  976. _absorbersList.put(attacker.getObjectId(), new AbsorberInfo(attacker.getObjectId(), getCurrentHp()));
  977. } else {
  978. ai.setAbsorbedHp(getCurrentHp());
  979. }
  980. // Set this L2Attackable as absorbed
  981. absorbSoul();
  982. }
  983. public void resetAbsorbList() {
  984. _absorbed = false;
  985. _absorbersList.clear();
  986. }
  987. public Map<Integer, AbsorberInfo> getAbsorbersList() {
  988. return _absorbersList;
  989. }
  990. /**
  991. * Calculate the Experience and SP to distribute to attacker (L2PcInstance, L2ServitorInstance or L2Party) of the L2Attackable.
  992. * @param diff The difference of level between attacker (L2PcInstance, L2ServitorInstance or L2Party) and the L2Attackable
  993. * @param damage The damages given by the attacker (L2PcInstance, L2ServitorInstance or L2Party)
  994. * @param totalDamage The total damage done
  995. * @return
  996. */
  997. private int[] calculateExpAndSp(int diff, int damage, long totalDamage) {
  998. double xp;
  999. double sp;
  1000. if (diff < -5) {
  1001. diff = -5; // makes possible to use ALT_GAME_EXPONENT configuration
  1002. }
  1003. xp = ((double) getExpReward() * damage) / totalDamage;
  1004. if (character().getExponentXp() != 0) {
  1005. xp *= Math.pow(2., -diff / character().getExponentXp());
  1006. }
  1007. sp = ((double) getSpReward() * damage) / totalDamage;
  1008. if (character().getExponentSp() != 0) {
  1009. sp *= Math.pow(2., -diff / character().getExponentSp());
  1010. }
  1011. if ((character().getExponentXp() == 0) && (character().getExponentSp() == 0)) {
  1012. // formula revised May 07
  1013. if (diff > 5) {
  1014. double pow = Math.pow((double) 5 / 6, diff - 5);
  1015. xp = xp * pow;
  1016. sp = sp * pow;
  1017. }
  1018. if (xp <= 0) {
  1019. xp = 0;
  1020. sp = 0;
  1021. } else if (sp <= 0) {
  1022. sp = 0;
  1023. }
  1024. }
  1025. return new int[] {
  1026. (int) xp,
  1027. (int) sp
  1028. };
  1029. }
  1030. public long calculateOverhitExp(long normalExp) {
  1031. // Get the percentage based on the total of extra (over-hit) damage done relative to the total (maximum) amount of HP on the L2Attackable
  1032. double overhitPercentage = ((getOverhitDamage() * 100) / getMaxHp());
  1033. // Over-hit damage percentages are limited to 25% max
  1034. if (overhitPercentage > 25) {
  1035. overhitPercentage = 25;
  1036. }
  1037. // Get the overhit exp bonus according to the above over-hit damage percentage
  1038. // (1/1 basis - 13% of over-hit damage, 13% of extra exp is given, and so on...)
  1039. double overhitExp = ((overhitPercentage / 100) * normalExp);
  1040. // Return the rounded amount of exp points to be added to the player's normal exp reward
  1041. return Math.round(overhitExp);
  1042. }
  1043. @Override
  1044. public boolean canBeAttacked() {
  1045. return true;
  1046. }
  1047. @Override
  1048. public void onSpawn() {
  1049. super.onSpawn();
  1050. // Clear mob spoil, seed
  1051. setSpoilerObjectId(0);
  1052. // Clear all aggro char from list
  1053. clearAggroList();
  1054. // Clear Harvester reward
  1055. _harvestItem.set(null);
  1056. // Clear mod Seeded stat
  1057. _seeded = false;
  1058. _seed = null;
  1059. _seederObjId = 0;
  1060. // Clear overhit value
  1061. overhitEnabled(false);
  1062. _sweepItems.set(null);
  1063. resetAbsorbList();
  1064. setWalking();
  1065. // check the region where this mob is, do not activate the AI if region is inactive.
  1066. if (!isInActiveRegion()) {
  1067. if (hasAI()) {
  1068. getAI().stopAITask();
  1069. }
  1070. }
  1071. }
  1072. /**
  1073. * Checks if its spoiled.
  1074. * @return {@code true} if its spoiled, {@code false} otherwise
  1075. */
  1076. public final boolean isSpoiled() {
  1077. return _spoilerObjectId != 0;
  1078. }
  1079. /**
  1080. * Gets the spoiler object id.
  1081. * @return the spoiler object id if its spoiled, 0 otherwise
  1082. */
  1083. public final int getSpoilerObjectId() {
  1084. return _spoilerObjectId;
  1085. }
  1086. /**
  1087. * Sets the spoiler object id.
  1088. * @param spoilerObjectId the spoiler object id
  1089. */
  1090. public final void setSpoilerObjectId(int spoilerObjectId) {
  1091. _spoilerObjectId = spoilerObjectId;
  1092. }
  1093. /**
  1094. * Sets state of the mob to seeded. Parameters needed to be set before.
  1095. * @param seeder
  1096. */
  1097. public final void setSeeded(L2PcInstance seeder) {
  1098. if ((_seed != null) && (_seederObjId == seeder.getObjectId())) {
  1099. _seeded = true;
  1100. int count = 1;
  1101. for (int skillId : getTemplate().getSkills().keySet()) {
  1102. switch (skillId) {
  1103. case 4303 -> count *= 2; // Strong type x2
  1104. case 4304 -> count *= 3; // Strong type x3
  1105. case 4305 -> count *= 4; // Strong type x4
  1106. case 4306 -> count *= 5; // Strong type x5
  1107. case 4307 -> count *= 6; // Strong type x6
  1108. case 4308 -> count *= 7; // Strong type x7
  1109. case 4309 -> count *= 8; // Strong type x8
  1110. case 4310 -> count *= 9; // Strong type x9
  1111. }
  1112. }
  1113. // hi-lvl mobs bonus
  1114. final int diff = getLevel() - _seed.getLevel() - 5;
  1115. if (diff > 0) {
  1116. count += diff;
  1117. }
  1118. _harvestItem.set(new ItemHolder(_seed.getCropId(), count * rates().getRateDropManor()));
  1119. }
  1120. }
  1121. /**
  1122. * Sets the seed parameters, but not the seed state
  1123. * @param seed - instance {@link L2Seed} of used seed
  1124. * @param seeder - player who sows the seed
  1125. */
  1126. public final void setSeeded(L2Seed seed, L2PcInstance seeder) {
  1127. if (!_seeded) {
  1128. _seed = seed;
  1129. _seederObjId = seeder.getObjectId();
  1130. }
  1131. }
  1132. public final int getSeederId() {
  1133. return _seederObjId;
  1134. }
  1135. public final L2Seed getSeed() {
  1136. return _seed;
  1137. }
  1138. public final boolean isSeeded() {
  1139. return _seeded;
  1140. }
  1141. /**
  1142. * Set delay for onKill() call, in ms Default: 5000 ms
  1143. * @param delay
  1144. */
  1145. public final void setOnKillDelay(int delay) {
  1146. _onKillDelay = delay;
  1147. }
  1148. public final int getOnKillDelay() {
  1149. return _onKillDelay;
  1150. }
  1151. /**
  1152. * Check if the server allows Random Animation.
  1153. */
  1154. // This is located here because L2Monster and L2FriendlyMob both extend this class. The other non-pc instances extend either L2NpcInstance or L2MonsterInstance.
  1155. @Override
  1156. public boolean hasRandomAnimation() {
  1157. return ((general().getMaxMonsterAnimation() > 0) && isRandomAnimationEnabled() && !(this instanceof L2GrandBossInstance));
  1158. }
  1159. @Override
  1160. public boolean isMob() {
  1161. return true; // This means we use MAX_MONSTER_ANIMATION instead of MAX_NPC_ANIMATION
  1162. }
  1163. public void setCommandChannelTimer(CommandChannelTimer commandChannelTimer) {
  1164. _commandChannelTimer = commandChannelTimer;
  1165. }
  1166. public CommandChannelTimer getCommandChannelTimer() {
  1167. return _commandChannelTimer;
  1168. }
  1169. public L2CommandChannel getFirstCommandChannelAttacked() {
  1170. return _firstCommandChannelAttacked;
  1171. }
  1172. public void setFirstCommandChannelAttacked(L2CommandChannel firstCommandChannelAttacked) {
  1173. _firstCommandChannelAttacked = firstCommandChannelAttacked;
  1174. }
  1175. /**
  1176. * @return the _commandChannelLastAttack
  1177. */
  1178. public long getCommandChannelLastAttack() {
  1179. return _commandChannelLastAttack;
  1180. }
  1181. /**
  1182. * @param channelLastAttack the _commandChannelLastAttack to set
  1183. */
  1184. public void setCommandChannelLastAttack(long channelLastAttack) {
  1185. _commandChannelLastAttack = channelLastAttack;
  1186. }
  1187. public void returnHome() {
  1188. clearAggroList();
  1189. if (hasAI() && (getSpawn() != null)) {
  1190. getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, getSpawn().getLocation(this));
  1191. }
  1192. }
  1193. /*
  1194. * Return vitality points decrease (if positive) or increase (if negative) based on damage. Maximum for damage = maxHp.
  1195. */
  1196. public float getVitalityPoints(int damage) {
  1197. // sanity check
  1198. if (damage <= 0) {
  1199. return 0;
  1200. }
  1201. final float divider = (getLevel() > 0) && (getExpReward() > 0) ? (getTemplate().getBaseHpMax() * 9 * getLevel() * getLevel()) / (100 * getExpReward()) : 0;
  1202. if (divider == 0) {
  1203. return 0;
  1204. }
  1205. // negative value - vitality will be consumed
  1206. return -Math.min(damage, getMaxHp()) / divider;
  1207. }
  1208. /*
  1209. * True if vitality rate for exp and sp should be applied
  1210. */
  1211. public boolean useVitalityRate() {
  1212. return isChampion() ? customs().championEnableVitality() : true;
  1213. }
  1214. /** Return True if the L2Character is RaidBoss or his minion. */
  1215. @Override
  1216. public boolean isRaid() {
  1217. return _isRaid;
  1218. }
  1219. /**
  1220. * Set this Npc as a Raid instance.
  1221. * @param isRaid
  1222. */
  1223. public void setIsRaid(boolean isRaid) {
  1224. _isRaid = isRaid;
  1225. }
  1226. /**
  1227. * Set this Npc as a Minion instance.
  1228. * @param val
  1229. */
  1230. public void setIsRaidMinion(boolean val) {
  1231. _isRaid = val;
  1232. _isRaidMinion = val;
  1233. }
  1234. @Override
  1235. public boolean isRaidMinion() {
  1236. return _isRaidMinion;
  1237. }
  1238. @Override
  1239. public boolean isMinion() {
  1240. return getLeader() != null;
  1241. }
  1242. /**
  1243. * @return leader of this minion or null.
  1244. */
  1245. public L2Attackable getLeader() {
  1246. return null;
  1247. }
  1248. public void setChampion(boolean champ) {
  1249. _champion = champ;
  1250. }
  1251. @Override
  1252. public boolean isChampion() {
  1253. return _champion;
  1254. }
  1255. @Override
  1256. public boolean isAttackable() {
  1257. return true;
  1258. }
  1259. }