1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453 |
- /*
- * Copyright © 2004-2021 L2J Server
- *
- * This file is part of L2J Server.
- *
- * L2J Server is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * L2J Server is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.l2jserver.gameserver.model.actor;
- import static com.l2jserver.gameserver.config.Configuration.character;
- import static com.l2jserver.gameserver.config.Configuration.customs;
- import static com.l2jserver.gameserver.config.Configuration.general;
- import static com.l2jserver.gameserver.config.Configuration.hunting;
- import static com.l2jserver.gameserver.config.Configuration.npc;
- import static com.l2jserver.gameserver.config.Configuration.rates;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.atomic.AtomicReference;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.l2jserver.commons.util.Rnd;
- import com.l2jserver.gameserver.ThreadPoolManager;
- import com.l2jserver.gameserver.ai.CtrlEvent;
- import com.l2jserver.gameserver.ai.CtrlIntention;
- import com.l2jserver.gameserver.ai.L2AttackableAI;
- import com.l2jserver.gameserver.ai.L2CharacterAI;
- import com.l2jserver.gameserver.ai.L2FortSiegeGuardAI;
- import com.l2jserver.gameserver.ai.L2SiegeGuardAI;
- import com.l2jserver.gameserver.datatables.EventDroplist;
- import com.l2jserver.gameserver.datatables.EventDroplist.DateDrop;
- import com.l2jserver.gameserver.datatables.ItemTable;
- import com.l2jserver.gameserver.enums.InstanceType;
- import com.l2jserver.gameserver.instancemanager.CursedWeaponsManager;
- import com.l2jserver.gameserver.instancemanager.WalkingManager;
- import com.l2jserver.gameserver.model.AbsorberInfo;
- import com.l2jserver.gameserver.model.AggroInfo;
- import com.l2jserver.gameserver.model.DamageDoneInfo;
- import com.l2jserver.gameserver.model.L2CommandChannel;
- import com.l2jserver.gameserver.model.L2Party;
- import com.l2jserver.gameserver.model.L2Seed;
- import com.l2jserver.gameserver.model.actor.instance.L2GrandBossInstance;
- import com.l2jserver.gameserver.model.actor.instance.L2MonsterInstance;
- import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
- import com.l2jserver.gameserver.model.actor.instance.L2ServitorInstance;
- import com.l2jserver.gameserver.model.actor.knownlist.AttackableKnownList;
- import com.l2jserver.gameserver.model.actor.status.AttackableStatus;
- import com.l2jserver.gameserver.model.actor.tasks.attackable.CommandChannelTimer;
- import com.l2jserver.gameserver.model.actor.templates.L2NpcTemplate;
- import com.l2jserver.gameserver.model.drops.DropListScope;
- import com.l2jserver.gameserver.model.events.EventDispatcher;
- import com.l2jserver.gameserver.model.events.impl.character.npc.attackable.OnAttackableAggroRangeEnter;
- import com.l2jserver.gameserver.model.events.impl.character.npc.attackable.OnAttackableAttack;
- import com.l2jserver.gameserver.model.events.impl.character.npc.attackable.OnAttackableKill;
- import com.l2jserver.gameserver.model.holders.ItemHolder;
- import com.l2jserver.gameserver.model.items.L2Item;
- import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
- import com.l2jserver.gameserver.model.skills.Skill;
- import com.l2jserver.gameserver.model.zone.ZoneId;
- import com.l2jserver.gameserver.network.SystemMessageId;
- import com.l2jserver.gameserver.network.clientpackets.Say2;
- import com.l2jserver.gameserver.network.serverpackets.CreatureSay;
- import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
- import com.l2jserver.gameserver.taskmanager.DecayTaskManager;
- import com.l2jserver.gameserver.util.Util;
- public class L2Attackable extends L2Npc {
- private static final Logger LOG = LoggerFactory.getLogger(L2Attackable.class);
- // Raid
- private boolean _isRaid = false;
- private boolean _isRaidMinion = false;
- //
- private boolean _champion = false;
- private final Map<L2Character, AggroInfo> _aggroList = new ConcurrentHashMap<>();
- private boolean _isReturningToSpawnPoint = false;
- private boolean _canReturnToSpawnPoint = true;
- private boolean _seeThroughSilentMove = false;
- // Manor
- private boolean _seeded = false;
- private L2Seed _seed = null;
- private int _seederObjId = 0;
- private final AtomicReference<ItemHolder> _harvestItem = new AtomicReference<>();
- // Spoil
- private int _spoilerObjectId;
- private final AtomicReference<Collection<ItemHolder>> _sweepItems = new AtomicReference<>();
- // Over-hit
- private boolean _overhit;
- private double _overhitDamage;
- private L2Character _overhitAttacker;
- // Command channel
- private volatile L2CommandChannel _firstCommandChannelAttacked = null;
- private CommandChannelTimer _commandChannelTimer = null;
- private long _commandChannelLastAttack = 0;
- // Soul crystal
- private boolean _absorbed;
- private final Map<Integer, AbsorberInfo> _absorbersList = new ConcurrentHashMap<>();
- // Misc
- private boolean _mustGiveExpSp;
- protected int _onKillDelay = 5000;
-
- /**
- * Creates an attackable NPC.
- * @param template the attackable NPC template
- */
- public L2Attackable(L2NpcTemplate template) {
- super(template);
- setInstanceType(InstanceType.L2Attackable);
- setIsInvul(false);
- _mustGiveExpSp = true;
- }
-
- @Override
- public AttackableKnownList getKnownList() {
- return (AttackableKnownList) super.getKnownList();
- }
-
- @Override
- public void initKnownList() {
- setKnownList(new AttackableKnownList(this));
- }
-
- @Override
- public AttackableStatus getStatus() {
- return (AttackableStatus) super.getStatus();
- }
-
- @Override
- public void initCharStatus() {
- setStatus(new AttackableStatus(this));
- }
-
- @Override
- protected L2CharacterAI initAI() {
- return new L2AttackableAI(this);
- }
-
- public final Map<L2Character, AggroInfo> getAggroList() {
- return _aggroList;
- }
-
- public final boolean isReturningToSpawnPoint() {
- return _isReturningToSpawnPoint;
- }
-
- public final void setisReturningToSpawnPoint(boolean value) {
- _isReturningToSpawnPoint = value;
- }
-
- public final boolean canReturnToSpawnPoint() {
- return _canReturnToSpawnPoint;
- }
-
- public final void setCanReturnToSpawnPoint(boolean value) {
- _canReturnToSpawnPoint = value;
- }
-
- public boolean canSeeThroughSilentMove() {
- return _seeThroughSilentMove;
- }
-
- public void setSeeThroughSilentMove(boolean val) {
- _seeThroughSilentMove = val;
- }
-
- /**
- * Use the skill if minimum checks are pass.
- * @param skill the skill
- */
- public void useMagic(Skill skill) {
- if ((skill == null) || isAlikeDead() || skill.isPassive() || isCastingNow() || isSkillDisabled(skill)) {
- return;
- }
-
- if ((getCurrentMp() < (getStat().getMpConsume1(skill) + getStat().getMpConsume2(skill))) || (getCurrentHp() <= skill.getHpConsume())) {
- return;
- }
-
- if (!skill.isStatic()) {
- if (skill.isMagic()) {
- if (isMuted()) {
- return;
- }
- } else {
- if (isPhysicalMuted()) {
- return;
- }
- }
- }
-
- final var targets = skill.getTargets(this);
- if (!targets.isEmpty()) {
- getAI().setIntention(CtrlIntention.AI_INTENTION_CAST, skill, targets.get(0));
- }
- }
-
- /**
- * Reduce the current HP of the L2Attackable.
- * @param damage The HP decrease value
- * @param attacker The L2Character who attacks
- */
- @Override
- public void reduceCurrentHp(double damage, L2Character attacker, Skill skill) {
- reduceCurrentHp(damage, attacker, true, false, skill);
- }
-
- /**
- * Reduce the current HP of the L2Attackable, update its _aggroList and launch the doDie Task if necessary.
- * @param damage The HP decrease value
- * @param attacker The L2Character who attacks
- * @param awake The awake state (If True : stop sleeping)
- * @param isDOT
- * @param skill
- */
- @Override
- public void reduceCurrentHp(double damage, L2Character attacker, boolean awake, boolean isDOT, Skill skill) {
- if (isRaid() && !isMinion() && (attacker != null) && (attacker.getParty() != null) && attacker.getParty().isInCommandChannel() && attacker.getParty().getCommandChannel().meetRaidWarCondition(this)) {
- if (_firstCommandChannelAttacked == null) // looting right isn't set
- {
- synchronized (this) {
- if (_firstCommandChannelAttacked == null) {
- _firstCommandChannelAttacked = attacker.getParty().getCommandChannel();
- if (_firstCommandChannelAttacked != null) {
- _commandChannelTimer = new CommandChannelTimer(this);
- _commandChannelLastAttack = System.currentTimeMillis();
- ThreadPoolManager.getInstance().scheduleGeneral(_commandChannelTimer, 10000); // check for last attack
- _firstCommandChannelAttacked.broadcastPacket(new CreatureSay(0, Say2.PARTYROOM_ALL, "", "You have looting rights!")); // TODO: retail msg
- }
- }
- }
- } else if (attacker.getParty().getCommandChannel().equals(_firstCommandChannelAttacked)) // is in same channel
- {
- _commandChannelLastAttack = System.currentTimeMillis(); // update last attack time
- }
- }
-
- if (isEventMob()) {
- return;
- }
-
- // Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList
- if (attacker != null) {
- addDamage(attacker, (int) damage, skill);
- }
-
- // If this L2Attackable is a L2MonsterInstance and it has spawned minions, call its minions to battle
- if (this instanceof L2MonsterInstance) {
- L2MonsterInstance master = (L2MonsterInstance) this;
-
- if (master.hasMinions()) {
- master.getMinionList().onAssist(this, attacker);
- }
-
- master = master.getLeader();
- if ((master != null) && master.hasMinions()) {
- master.getMinionList().onAssist(this, attacker);
- }
- }
- // Reduce the current HP of the L2Attackable and launch the doDie Task if necessary
- super.reduceCurrentHp(damage, attacker, awake, isDOT, skill);
- }
-
- public synchronized void setMustRewardExpSp(boolean value) {
- _mustGiveExpSp = value;
- }
-
- public synchronized boolean getMustRewardExpSP() {
- return _mustGiveExpSp;
- }
-
- /**
- * Kill the L2Attackable (the corpse disappeared after 7 seconds), distribute rewards (EXP, SP, Drops...) and notify Quest Engine.<br>
- * Actions:<br>
- * Distribute Exp and SP rewards to L2PcInstance (including Summon owner) that hit the L2Attackable and to their Party members<br>
- * Notify the Quest Engine of the L2Attackable death if necessary.<br>
- * Kill the L2NpcInstance (the corpse disappeared after 7 seconds)<br>
- * Caution: This method DOESN'T GIVE rewards to L2PetInstance.
- * @param killer The L2Character that has killed the L2Attackable
- */
- @Override
- public boolean doDie(L2Character killer) {
- // Kill the L2NpcInstance (the corpse disappeared after 7 seconds)
- if (!super.doDie(killer)) {
- return false;
- }
-
- if ((killer != null) && killer.isPlayable()) {
- // Delayed notification
- EventDispatcher.getInstance().notifyEventAsyncDelayed(new OnAttackableKill(killer.getActingPlayer(), this, killer.isSummon()), this, _onKillDelay);
- }
-
- // Notify to minions if there are.
- if (isMonster()) {
- final L2MonsterInstance mob = (L2MonsterInstance) this;
- if ((mob.getLeader() != null) && mob.getLeader().hasMinions()) {
- var respawnTime = npc().getCustomMinionsRespawnTime().get(getId());
- respawnTime = respawnTime != null ? respawnTime * 1000 : -1;
- mob.getLeader().getMinionList().onMinionDie(mob, respawnTime);
- }
-
- if (mob.hasMinions()) {
- mob.getMinionList().onMasterDie(false);
- }
- }
- return true;
- }
-
- /**
- * Distribute Exp and SP rewards to L2PcInstance (including Summon owner) that hit the L2Attackable and to their Party members.<br>
- * Actions:<br>
- * Get the L2PcInstance owner of the L2ServitorInstance (if necessary) and L2Party in progress.<br>
- * Calculate the Experience and SP rewards in function of the level difference.<br>
- * Add Exp and SP rewards to L2PcInstance (including Summon penalty) and to Party members in the known area of the last attacker.<br>
- * Caution : This method DOESN'T GIVE rewards to L2PetInstance.
- * @param lastAttacker The L2Character that has killed the L2Attackable
- */
- @Override
- protected void calculateRewards(L2Character lastAttacker) {
- try {
- if (_aggroList.isEmpty()) {
- return;
- }
-
- // NOTE: Concurrent-safe map is used because while iterating to verify all conditions sometimes an entry must be removed.
- final Map<L2PcInstance, DamageDoneInfo> rewards = new ConcurrentHashMap<>();
-
- L2PcInstance maxDealer = null;
- int maxDamage = 0;
- long totalDamage = 0;
- // While Iterating over This Map Removing Object is Not Allowed
- // Go through the _aggroList of the L2Attackable
- for (AggroInfo info : _aggroList.values()) {
- if (info == null) {
- continue;
- }
-
- // Get the L2Character corresponding to this attacker
- final L2PcInstance attacker = info.getAttacker().getActingPlayer();
- if (attacker != null) {
- // Get damages done by this attacker
- final int damage = info.getDamage();
-
- // Prevent unwanted behavior
- if (damage > 1) {
- // Check if damage dealer isn't too far from this (killed monster)
- if (!Util.checkIfInRange(character().getPartyRange(), this, attacker, true)) {
- continue;
- }
-
- totalDamage += damage;
-
- // Calculate real damages (Summoners should get own damage plus summon's damage)
- final DamageDoneInfo reward = rewards.computeIfAbsent(attacker, DamageDoneInfo::new);
- reward.addDamage(damage);
-
- if (reward.getDamage() > maxDamage) {
- maxDealer = attacker;
- maxDamage = reward.getDamage();
- }
- }
- }
- }
-
- // Manage Base, Quests and Sweep drops of the L2Attackable
- doItemDrop((maxDealer != null) && maxDealer.isOnline() ? maxDealer : lastAttacker);
-
- // Manage drop of Special Events created by GM for a defined period
- doEventDrop(lastAttacker);
-
- if (!getMustRewardExpSP()) {
- return;
- }
-
- if (!rewards.isEmpty()) {
- for (DamageDoneInfo reward : rewards.values()) {
- if (reward == null) {
- continue;
- }
-
- // Attacker to be rewarded
- final L2PcInstance attacker = reward.getAttacker();
-
- // Total amount of damage done
- final int damage = reward.getDamage();
-
- // Get party
- final L2Party attackerParty = attacker.getParty();
-
- // Penalty applied to the attacker's XP
- // If this attacker have servitor, get Exp Penalty applied for the servitor.
- final float penalty = attacker.hasServitor() ? ((L2ServitorInstance) attacker.getSummon()).getExpMultiplier() : 1;
-
- // If there's NO party in progress
- if (attackerParty == null) {
- // Calculate Exp and SP rewards
- if (attacker.getKnownList().knowsObject(this)) {
- // Calculate the difference of level between this attacker (player or servitor owner) and the L2Attackable
- // mob = 24, atk = 10, diff = -14 (full xp)
- // mob = 24, atk = 28, diff = 4 (some xp)
- // mob = 24, atk = 50, diff = 26 (no xp)
- final int levelDiff = attacker.getLevel() - getLevel();
-
- final int[] expSp = calculateExpAndSp(levelDiff, damage, totalDamage);
- long exp = expSp[0];
- int sp = expSp[1];
-
- if (customs().championEnable() && isChampion()) {
- exp *= customs().getChampionRewardsExpSp();
- sp *= customs().getChampionRewardsExpSp();
- }
-
- exp *= penalty;
-
- // Check for an over-hit enabled strike
- L2Character overhitAttacker = getOverhitAttacker();
- if (isOverhit() && (overhitAttacker != null) && (overhitAttacker.getActingPlayer() != null) && (attacker == overhitAttacker.getActingPlayer())) {
- attacker.sendPacket(SystemMessageId.OVER_HIT);
- exp += calculateOverhitExp(exp);
- }
-
- // Distribute the Exp and SP between the L2PcInstance and its L2Summon
- if (!attacker.isDead()) {
- attacker.addExpAndSp(exp, sp, useVitalityRate());
- if (exp > 0) {
- attacker.updateVitalityPoints(getVitalityPoints(damage), true, false);
- if (!attacker.isInsideZone(ZoneId.PEACE) && ((attacker.getLevel() - getLevel()) <= 9)) {
- if (hunting().getNevitEnable()) {
- attacker.getHuntingSystem().startHuntingSystemTask();
- if (attacker.getHuntingSystem().getHuntingBonusTime() < hunting().getHuntingBonusMaxTime() || !hunting().getHuntingBonusLimit()) {
- attacker.getHuntingSystem().addPoints(hunting().getNevitNormalPoints());
- }
- }
-
- attacker.getRecSystem().startBonusTask(true);
- }
- }
- }
- }
- } else {
- // share with party members
- int partyDmg = 0;
- float partyMul = 1;
- int partyLvl = 0;
-
- // Get all L2Character that can be rewarded in the party
- final List<L2PcInstance> rewardedMembers = new ArrayList<>();
- // Go through all L2PcInstance in the party
- final List<L2PcInstance> groupMembers = attackerParty.isInCommandChannel() ? attackerParty.getCommandChannel().getMembers() : attackerParty.getMembers();
- for (L2PcInstance partyPlayer : groupMembers) {
- if ((partyPlayer == null) || partyPlayer.isDead()) {
- continue;
- }
-
- // Get the RewardInfo of this L2PcInstance from L2Attackable rewards
- final DamageDoneInfo reward2 = rewards.get(partyPlayer);
-
- // If the L2PcInstance is in the L2Attackable rewards add its damages to party damages
- if (reward2 != null) {
- if (Util.checkIfInRange(character().getPartyRange(), this, partyPlayer, true)) {
- partyDmg += reward2.getDamage(); // Add L2PcInstance damages to party damages
- rewardedMembers.add(partyPlayer);
-
- if (partyPlayer.getLevel() > partyLvl) {
- if (attackerParty.isInCommandChannel()) {
- partyLvl = attackerParty.getCommandChannel().getLevel();
- } else {
- partyLvl = partyPlayer.getLevel();
- }
- }
- }
- rewards.remove(partyPlayer); // Remove the L2PcInstance from the L2Attackable rewards
- } else {
- // Add L2PcInstance of the party (that have attacked or not) to members that can be rewarded
- // and in range of the monster.
- if (Util.checkIfInRange(character().getPartyRange(), this, partyPlayer, true)) {
- rewardedMembers.add(partyPlayer);
- if (partyPlayer.getLevel() > partyLvl) {
- if (attackerParty.isInCommandChannel()) {
- partyLvl = attackerParty.getCommandChannel().getLevel();
- } else {
- partyLvl = partyPlayer.getLevel();
- }
- }
- }
- }
- }
-
- // If the party didn't killed this L2Attackable alone
- if (partyDmg < totalDamage) {
- partyMul = ((float) partyDmg / totalDamage);
- }
-
- // Calculate the level difference between Party and L2Attackable
- final int levelDiff = partyLvl - getLevel();
-
- // Calculate Exp and SP rewards
- final int[] expSp = calculateExpAndSp(levelDiff, partyDmg, totalDamage);
- long exp = expSp[0];
- int sp = expSp[1];
-
- if (customs().championEnable() && isChampion()) {
- exp *= customs().getChampionRewardsExpSp();
- sp *= customs().getChampionRewardsExpSp();
- }
-
- exp *= partyMul;
- sp *= partyMul;
-
- // Check for an over-hit enabled strike
- // (When in party, the over-hit exp bonus is given to the whole party and split proportionally through the party members)
- L2Character overhitAttacker = getOverhitAttacker();
- if (isOverhit() && (overhitAttacker != null) && (overhitAttacker.getActingPlayer() != null) && (attacker == overhitAttacker.getActingPlayer())) {
- attacker.sendPacket(SystemMessageId.OVER_HIT);
- exp += calculateOverhitExp(exp);
- }
-
- // Distribute Experience and SP rewards to L2PcInstance Party members in the known area of the last attacker
- if (partyDmg > 0) {
- attackerParty.distributeXpAndSp(exp, sp, rewardedMembers, partyLvl, partyDmg, this);
- }
- }
- }
- }
- } catch (Exception ex) {
- LOG.error("Error calculating rewards!", ex);
- }
- }
-
- @Override
- public void addAttackerToAttackByList(L2Character player) {
- if ((player == null) || (player == this) || getAttackByList().contains(player)) {
- return;
- }
- getAttackByList().add(player);
- }
-
- /**
- * Add damage and hate to the attacker AggroInfo of the L2Attackable _aggroList.
- * @param attacker The L2Character that gave damages to this L2Attackable
- * @param damage The number of damages given by the attacker L2Character
- * @param skill
- */
- public void addDamage(L2Character attacker, int damage, Skill skill) {
- if (attacker == null) {
- return;
- }
-
- // Notify the L2Attackable AI with EVT_ATTACKED
- if (!isDead()) {
- try {
- // If monster is on walk - stop it
- if (isWalker() && !isCoreAIDisabled() && WalkingManager.getInstance().isOnWalk(this)) {
- WalkingManager.getInstance().stopMoving(this, false, true);
- }
-
- getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, attacker);
- addDamageHate(attacker, damage, (damage * 100) / (getLevel() + 7));
-
- final L2PcInstance player = attacker.getActingPlayer();
- if (player != null) {
- EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAttack(player, this, damage, skill, attacker.isSummon()), this);
- }
- } catch (Exception ex) {
- LOG.error("Error adding damage!", ex);
- }
- }
- }
-
- /**
- * Adds damage and hate to the attacker aggression list for this character.
- * @param attacker The L2Character that gave damages to this L2Attackable
- * @param damage The number of damages given by the attacker L2Character
- * @param aggro The hate (=damage) given by the attacker L2Character
- */
- public void addDamageHate(L2Character attacker, int damage, long aggro) {
- if (attacker == null || attacker == this) {
- return;
- }
-
- // Get the AggroInfo of the attacker L2Character from the _aggroList of the L2Attackable
- final AggroInfo ai = _aggroList.computeIfAbsent(attacker, AggroInfo::new);
- ai.addDamage(damage);
-
- // Traps does not cause aggro
- // making this hack because not possible to determine if damage made by trap
- // so just check for triggered trap here
- final L2PcInstance targetPlayer = attacker.getActingPlayer();
- if ((targetPlayer == null) || (targetPlayer.getTrap() == null) || !targetPlayer.getTrap().isTriggered()) {
- ai.addHate(aggro);
- }
-
- if ((targetPlayer != null) && (aggro == 0)) {
- addDamageHate(attacker, 0, 1);
-
- // Set the intention to the L2Attackable to AI_INTENTION_ACTIVE
- if (getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE) {
- getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
- }
-
- // Notify to scripts
- EventDispatcher.getInstance().notifyEventAsync(new OnAttackableAggroRangeEnter(this, targetPlayer, attacker.isSummon()), this);
- } else if ((targetPlayer == null) && (aggro == 0)) {
- aggro = 1;
- ai.addHate(1);
- }
-
- // Set the intention to the L2Attackable to AI_INTENTION_ACTIVE
- if ((aggro != 0) && (getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE)) {
- getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
- }
- }
-
- public void reduceHate(L2Character target, long amount) {
- if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI)) {
- // TODO: this just prevents error until siege guards are handled properly
- stopHating(target);
- setTarget(null);
- getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
- return;
- }
-
- if (target == null) // whole aggrolist
- {
- L2Character mostHated = getMostHated();
- if (mostHated == null) // makes target passive for a moment more
- {
- ((L2AttackableAI) getAI()).setGlobalAggro(-25);
- return;
- }
-
- for (AggroInfo ai : _aggroList.values()) {
- ai.addHate(amount);
- }
-
- amount = getHating(mostHated);
- if (amount >= 0) {
- ((L2AttackableAI) getAI()).setGlobalAggro(-25);
- clearAggroList();
- getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
- setWalking();
- }
- return;
- }
-
- AggroInfo ai = _aggroList.get(target);
- if (ai == null) {
- LOG.info("Target {} not present in aggro list of {}.", target, this);
- return;
- }
-
- ai.addHate(amount);
- if ((ai.getHate() >= 0) && (getMostHated() == null)) {
- ((L2AttackableAI) getAI()).setGlobalAggro(-25);
- clearAggroList();
- getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
- setWalking();
- }
- }
-
- /**
- * Clears _aggroList hate of the L2Character without removing from the list.
- * @param target
- */
- public void stopHating(L2Character target) {
- if (target == null) {
- return;
- }
- AggroInfo ai = _aggroList.get(target);
- if (ai != null) {
- ai.stopHate();
- }
- }
-
- /**
- * @return the most hated L2Character of the L2Attackable _aggroList.
- */
- public L2Character getMostHated() {
- if (_aggroList.isEmpty() || isAlikeDead()) {
- return null;
- }
-
- L2Character mostHated = null;
- long maxHate = 0;
-
- // While Interacting over This Map Removing Object is Not Allowed
- // Go through the aggroList of the L2Attackable
- for (AggroInfo ai : _aggroList.values()) {
- if (ai == null) {
- continue;
- }
-
- if (ai.checkHate(this) > maxHate) {
- mostHated = ai.getAttacker();
- maxHate = ai.getHate();
- }
- }
-
- return mostHated;
- }
-
- /**
- * @return the 2 most hated L2Character of the L2Attackable _aggroList.
- */
- public List<L2Character> get2MostHated() {
- if (_aggroList.isEmpty() || isAlikeDead()) {
- return null;
- }
-
- L2Character mostHated = null;
- L2Character secondMostHated = null;
- long maxHate = 0;
- List<L2Character> result = new ArrayList<>();
-
- // While iterating over this map removing objects is not allowed
- // Go through the aggroList of the L2Attackable
- for (AggroInfo ai : _aggroList.values()) {
- if (ai == null) {
- continue;
- }
-
- if (ai.checkHate(this) > maxHate) {
- secondMostHated = mostHated;
- mostHated = ai.getAttacker();
- maxHate = ai.getHate();
- }
- }
-
- result.add(mostHated);
-
- if (getAttackByList().contains(secondMostHated)) {
- result.add(secondMostHated);
- } else {
- result.add(null);
- }
- return result;
- }
-
- public List<L2Character> getHateList() {
- if (_aggroList.isEmpty() || isAlikeDead()) {
- return null;
- }
-
- List<L2Character> result = new ArrayList<>();
- for (AggroInfo ai : _aggroList.values()) {
- if (ai == null) {
- continue;
- }
- ai.checkHate(this);
-
- result.add(ai.getAttacker());
- }
- return result;
- }
-
- /**
- * @param target The L2Character whose hate level must be returned
- * @return the hate level of the L2Attackable against this L2Character contained in _aggroList.
- */
- public long getHating(final L2Character target) {
- if (_aggroList.isEmpty() || (target == null)) {
- return 0;
- }
-
- final AggroInfo ai = _aggroList.get(target);
- if (ai == null) {
- return 0;
- }
-
- if (ai.getAttacker() instanceof L2PcInstance) {
- L2PcInstance act = (L2PcInstance) ai.getAttacker();
- if (act.isInvisible() || ai.getAttacker().isInvul() || act.isSpawnProtected()) {
- // Remove Object Should Use This Method and Can be Blocked While Interacting
- _aggroList.remove(target);
- return 0;
- }
- }
-
- if (!ai.getAttacker().isVisible() || ai.getAttacker().isInvisible()) {
- _aggroList.remove(target);
- return 0;
- }
-
- if (ai.getAttacker().isAlikeDead()) {
- ai.stopHate();
- return 0;
- }
- return ai.getHate();
- }
-
- public void doItemDrop(L2Character mainDamageDealer) {
- doItemDrop(getTemplate(), mainDamageDealer);
- }
-
- /**
- * Manage Base, Quests and Special Events drops of L2Attackable (called by calculateRewards).<br>
- * Concept:<br>
- * During a Special Event all L2Attackable can drop extra Items.<br>
- * Those extra Items are defined in the table allNpcDateDrops of the EventDroplist.<br>
- * Each Special Event has a start and end date to stop to drop extra Items automatically.<br>
- * Actions:<br>
- * Manage drop of Special Events created by GM for a defined period.<br>
- * Get all possible drops of this L2Attackable from L2NpcTemplate and add it Quest drops.<br>
- * For each possible drops (base + quests), calculate which one must be dropped (random).<br>
- * Get each Item quantity dropped (random).<br>
- * Create this or these L2ItemInstance corresponding to each Item Identifier dropped.<br>
- * 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>
- * 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.
- * @param npcTemplate
- * @param mainDamageDealer
- */
- public void doItemDrop(L2NpcTemplate npcTemplate, L2Character mainDamageDealer) {
- if (mainDamageDealer == null) {
- return;
- }
-
- L2PcInstance player = mainDamageDealer.getActingPlayer();
-
- // Don't drop anything if the last attacker or owner isn't L2PcInstance
- if (player == null) {
- return;
- }
-
- CursedWeaponsManager.getInstance().checkDrop(this, player);
-
- if (isSpoiled()) {
- _sweepItems.set(npcTemplate.calculateDrops(DropListScope.CORPSE, this, player));
- }
-
- Collection<ItemHolder> deathItems = npcTemplate.calculateDrops(DropListScope.DEATH, this, player);
- if (deathItems != null) {
- for (ItemHolder drop : deathItems) {
- L2Item item = ItemTable.getInstance().getTemplate(drop.getId());
- // Check if the autoLoot mode is active
- // Give the item(s) to the L2PcInstance inventory has killed the L2Attackable
- if ((!item.hasExImmediateEffect() && ((!isRaid() && character().autoLoot()) || (isRaid() && character().autoLootRaids()))) || (item.hasExImmediateEffect() && character().autoLootHerbs())) {
- player.doAutoLoot(this, drop);
- } else if ((!item.hasExImmediateEffect() && ((!isRaid() && ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(item.getId())))))) || //
- (item.hasExImmediateEffect() && ((player.isAutoLootHerb() && customs().getAutoLootHerbsList().contains(item.getId()))))) {
- player.doAutoLoot(this, drop);
- } else {
- dropItem(player, drop); // Drop the rest or all item(s) on the ground
- }
-
- // Broadcast message if RaidBoss was defeated
- if (isRaid() && !isRaidMinion() && (drop.getCount() > 0)) {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_DIED_DROPPED_S3_S2);
- sm.addCharName(this);
- sm.addItemName(item);
- sm.addLong(drop.getCount());
- broadcastPacket(sm);
- }
- }
- }
-
- // Apply Special Item drop with random(rnd) quantity(qty) for champions.
- if (customs().championEnable() && isChampion() && ((customs().getChampionRewardLowerLvlItemChance() > 0) || (customs().getChampionRewardHigherLvlItemChance() > 0))) {
- int champqty = Rnd.get(customs().getChampionRewardItemQty());
- ItemHolder item = new ItemHolder(customs().getChampionRewardItemID(), ++champqty);
-
- // Give the item(s) to the L2PcInstance inventory has killed the L2Attackable
- if ((player.getLevel() <= getLevel()) && (Rnd.get(100) < customs().getChampionRewardLowerLvlItemChance())) {
- if (character().autoLoot()) {
- player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
- } else if ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(item.getId()))) {
- player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
- } else {
- dropItem(player, item);
- }
- } else if ((player.getLevel() > getLevel()) && (Rnd.get(100) < customs().getChampionRewardHigherLvlItemChance())) {
- if (character().autoLoot()) {
- player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
- } else if ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(item.getId()))) {
- player.addItem("ChampionLoot", item.getId(), item.getCount(), this, true);
- } else {
- dropItem(player, item);
- }
- }
- }
- }
-
- /**
- * Manage Special Events drops created by GM for a defined period.<br>
- * Concept:<br>
- * During a Special Event all L2Attackable can drop extra Items.<br>
- * Those extra Items are defined in the table allNpcDateDrops of the EventDroplist.<br>
- * Each Special Event has a start and end date to stop to drop extra Items automatically.<br>
- * Actions: <I>If an extra drop must be generated</I><br>
- * Get an Item Identifier (random) from the DateDrop Item table of this Event.<br>
- * Get the Item quantity dropped (random).<br>
- * Create this or these L2ItemInstance corresponding to this Item Identifier.<br>
- * 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>
- * 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
- * @param lastAttacker The L2Character that has killed the L2Attackable
- */
- public void doEventDrop(L2Character lastAttacker) {
- if (lastAttacker == null) {
- return;
- }
-
- L2PcInstance player = lastAttacker.getActingPlayer();
-
- // Don't drop anything if the last attacker or owner isn't L2PcInstance
- if (player == null) {
- return;
- }
-
- if ((player.getLevel() - getLevel()) > 9) {
- return;
- }
-
- // Go through DateDrop of EventDroplist allNpcDateDrops within the date range
- for (DateDrop drop : EventDroplist.getInstance().getAllDrops()) {
- if (Rnd.get(1000000) < drop.getEventDrop().getDropChance()) {
- final int itemId = drop.getEventDrop().getItemIdList()[Rnd.get(drop.getEventDrop().getItemIdList().length)];
- final long itemCount = Rnd.get(drop.getEventDrop().getMinCount(), drop.getEventDrop().getMaxCount());
- // Give the item(s) to the L2PcInstance inventory has killed the L2Attackable
- if (character().autoLoot()) {
- player.doAutoLoot(this, itemId, itemCount);
- } else if ((player.isAutoLoot() && !player.isAutoLootItem()) || (player.isAutoLootItem() && customs().getAutoLootItemsList().contains(itemId))) {
- player.doAutoLoot(this, itemId, itemCount);
- } else {
- dropItem(player, itemId, itemCount); // Drop the rest or all item(s) on the ground
- }
- }
- }
- }
-
- /**
- * @return the active weapon of this L2Attackable (= null).
- */
- public L2ItemInstance getActiveWeapon() {
- return null;
- }
-
- /**
- * Verifies if the creature is in the aggro list.
- * @param creature the creature
- * @return {@code true} if the creature is in the aggro list, {@code false} otherwise
- */
- public boolean isInAggroList(L2Character creature) {
- return _aggroList.containsKey(creature);
- }
-
- /**
- * Clear the _aggroList of the L2Attackable.
- */
- public void clearAggroList() {
- _aggroList.clear();
-
- // clear overhit values
- _overhit = false;
- _overhitDamage = 0;
- _overhitAttacker = null;
- }
-
- /**
- * @return {@code true} if there is a loot to sweep, {@code false} otherwise.
- */
- @Override
- public boolean isSweepActive() {
- return _sweepItems.get() != null;
- }
-
- /**
- * @return a copy of dummy items for the spoil loot.
- */
- public List<L2Item> getSpoilLootItems() {
- final Collection<ItemHolder> sweepItems = _sweepItems.get();
- final List<L2Item> lootItems = new LinkedList<>();
- if (sweepItems != null) {
- for (ItemHolder item : sweepItems) {
- lootItems.add(ItemTable.getInstance().getTemplate(item.getId()));
- }
- }
- return lootItems;
- }
-
- /**
- * @return table containing all L2ItemInstance that can be spoiled.
- */
- public Collection<ItemHolder> takeSweep() {
- return _sweepItems.getAndSet(null);
- }
-
- /**
- * @return table containing all L2ItemInstance that can be harvested.
- */
- public ItemHolder takeHarvest() {
- return _harvestItem.getAndSet(null);
- }
-
- /**
- * Checks if the corpse is too old.
- * @param attacker the player to validate
- * @param remainingTime the time to check
- * @param sendMessage if {@code true} will send a message of corpse too old
- * @return {@code true} if the corpse is too old
- */
- public boolean isOldCorpse(L2PcInstance attacker, int remainingTime, boolean sendMessage) {
- if (isDead() && (DecayTaskManager.getInstance().getRemainingTime(this) < remainingTime)) {
- if (sendMessage && (attacker != null)) {
- attacker.sendPacket(SystemMessageId.CORPSE_TOO_OLD_SKILL_NOT_USED);
- }
- return true;
- }
- return false;
- }
-
- /**
- * @param sweeper the player to validate.
- * @param sendMessage sendMessage if {@code true} will send a message of sweep not allowed.
- * @return {@code true} if is the spoiler or is in the spoiler party.
- */
- public boolean checkSpoilOwner(L2PcInstance sweeper, boolean sendMessage) {
- if ((sweeper.getObjectId() != getSpoilerObjectId()) && !sweeper.isInLooterParty(getSpoilerObjectId())) {
- if (sendMessage) {
- sweeper.sendPacket(SystemMessageId.SWEEP_NOT_ALLOWED);
- }
- return false;
- }
- return true;
- }
-
- /**
- * Set the over-hit flag on the L2Attackable.
- * @param status The status of the over-hit flag
- */
- public void overhitEnabled(boolean status) {
- _overhit = status;
- }
-
- /**
- * Set the over-hit values like the attacker who did the strike and the amount of damage done by the skill.
- * @param attacker The L2Character who hit on the L2Attackable using the over-hit enabled skill
- * @param damage The amount of damage done by the over-hit enabled skill on the L2Attackable
- */
- public void setOverhitValues(L2Character attacker, double damage) {
- // Calculate the over-hit damage
- // Ex: mob had 10 HP left, over-hit skill did 50 damage total, over-hit damage is 40
- double overhitDmg = -(getCurrentHp() - damage);
- if (overhitDmg < 0) {
- // we didn't killed the mob with the over-hit strike. (it wasn't really an over-hit strike)
- // let's just clear all the over-hit related values
- overhitEnabled(false);
- _overhitDamage = 0;
- _overhitAttacker = null;
- return;
- }
- overhitEnabled(true);
- _overhitDamage = overhitDmg;
- _overhitAttacker = attacker;
- }
-
- /**
- * Return the L2Character who hit on the L2Attackable using an over-hit enabled skill.
- * @return L2Character attacker
- */
- public L2Character getOverhitAttacker() {
- return _overhitAttacker;
- }
-
- /**
- * Return the amount of damage done on the L2Attackable using an over-hit enabled skill.
- * @return double damage
- */
- public double getOverhitDamage() {
- return _overhitDamage;
- }
-
- /**
- * @return True if the L2Attackable was hit by an over-hit enabled skill.
- */
- public boolean isOverhit() {
- return _overhit;
- }
-
- /**
- * Activate the absorbed soul condition on the L2Attackable.
- */
- public void absorbSoul() {
- _absorbed = true;
- }
-
- /**
- * @return True if the L2Attackable had his soul absorbed.
- */
- public boolean isAbsorbed() {
- return _absorbed;
- }
-
- /**
- * Adds an attacker that successfully absorbed the soul of this L2Attackable into the _absorbersList.
- * @param attacker
- */
- public void addAbsorber(L2PcInstance attacker) {
- // If we have no _absorbersList initiated, do it
- final AbsorberInfo ai = _absorbersList.get(attacker.getObjectId());
-
- // If the L2Character attacker isn't already in the _absorbersList of this L2Attackable, add it
- if (ai == null) {
- _absorbersList.put(attacker.getObjectId(), new AbsorberInfo(attacker.getObjectId(), getCurrentHp()));
- } else {
- ai.setAbsorbedHp(getCurrentHp());
- }
-
- // Set this L2Attackable as absorbed
- absorbSoul();
- }
-
- public void resetAbsorbList() {
- _absorbed = false;
- _absorbersList.clear();
- }
-
- public Map<Integer, AbsorberInfo> getAbsorbersList() {
- return _absorbersList;
- }
-
- /**
- * Calculate the Experience and SP to distribute to attacker (L2PcInstance, L2ServitorInstance or L2Party) of the L2Attackable.
- * @param diff The difference of level between attacker (L2PcInstance, L2ServitorInstance or L2Party) and the L2Attackable
- * @param damage The damages given by the attacker (L2PcInstance, L2ServitorInstance or L2Party)
- * @param totalDamage The total damage done
- * @return
- */
- private int[] calculateExpAndSp(int diff, int damage, long totalDamage) {
- double xp;
- double sp;
-
- if (diff < -5) {
- diff = -5; // makes possible to use ALT_GAME_EXPONENT configuration
- }
-
- xp = ((double) getExpReward() * damage) / totalDamage;
- if (character().getExponentXp() != 0) {
- xp *= Math.pow(2., -diff / character().getExponentXp());
- }
-
- sp = ((double) getSpReward() * damage) / totalDamage;
- if (character().getExponentSp() != 0) {
- sp *= Math.pow(2., -diff / character().getExponentSp());
- }
-
- if ((character().getExponentXp() == 0) && (character().getExponentSp() == 0)) {
- // formula revised May 07
- if (diff > 5) {
- double pow = Math.pow((double) 5 / 6, diff - 5);
- xp = xp * pow;
- sp = sp * pow;
- }
-
- if (xp <= 0) {
- xp = 0;
- sp = 0;
- } else if (sp <= 0) {
- sp = 0;
- }
- }
- return new int[] {
- (int) xp,
- (int) sp
- };
- }
-
- public long calculateOverhitExp(long normalExp) {
- // Get the percentage based on the total of extra (over-hit) damage done relative to the total (maximum) amount of HP on the L2Attackable
- double overhitPercentage = ((getOverhitDamage() * 100) / getMaxHp());
-
- // Over-hit damage percentages are limited to 25% max
- if (overhitPercentage > 25) {
- overhitPercentage = 25;
- }
-
- // Get the overhit exp bonus according to the above over-hit damage percentage
- // (1/1 basis - 13% of over-hit damage, 13% of extra exp is given, and so on...)
- double overhitExp = ((overhitPercentage / 100) * normalExp);
-
- // Return the rounded amount of exp points to be added to the player's normal exp reward
- return Math.round(overhitExp);
- }
-
- @Override
- public boolean canBeAttacked() {
- return true;
- }
-
- @Override
- public void onSpawn() {
- super.onSpawn();
- // Clear mob spoil, seed
- setSpoilerObjectId(0);
- // Clear all aggro char from list
- clearAggroList();
- // Clear Harvester reward
- _harvestItem.set(null);
- // Clear mod Seeded stat
- _seeded = false;
- _seed = null;
- _seederObjId = 0;
- // Clear overhit value
- overhitEnabled(false);
-
- _sweepItems.set(null);
- resetAbsorbList();
-
- setWalking();
-
- // check the region where this mob is, do not activate the AI if region is inactive.
- if (!isInActiveRegion()) {
- if (hasAI()) {
- getAI().stopAITask();
- }
- }
- }
-
- /**
- * Checks if its spoiled.
- * @return {@code true} if its spoiled, {@code false} otherwise
- */
- public final boolean isSpoiled() {
- return _spoilerObjectId != 0;
- }
-
- /**
- * Gets the spoiler object id.
- * @return the spoiler object id if its spoiled, 0 otherwise
- */
- public final int getSpoilerObjectId() {
- return _spoilerObjectId;
- }
-
- /**
- * Sets the spoiler object id.
- * @param spoilerObjectId the spoiler object id
- */
- public final void setSpoilerObjectId(int spoilerObjectId) {
- _spoilerObjectId = spoilerObjectId;
- }
-
- /**
- * Sets state of the mob to seeded. Parameters needed to be set before.
- * @param seeder
- */
- public final void setSeeded(L2PcInstance seeder) {
- if ((_seed != null) && (_seederObjId == seeder.getObjectId())) {
- _seeded = true;
-
- int count = 1;
- for (int skillId : getTemplate().getSkills().keySet()) {
- switch (skillId) {
- case 4303 -> count *= 2; // Strong type x2
- case 4304 -> count *= 3; // Strong type x3
- case 4305 -> count *= 4; // Strong type x4
- case 4306 -> count *= 5; // Strong type x5
- case 4307 -> count *= 6; // Strong type x6
- case 4308 -> count *= 7; // Strong type x7
- case 4309 -> count *= 8; // Strong type x8
- case 4310 -> count *= 9; // Strong type x9
- }
- }
-
- // hi-lvl mobs bonus
- final int diff = getLevel() - _seed.getLevel() - 5;
- if (diff > 0) {
- count += diff;
- }
- _harvestItem.set(new ItemHolder(_seed.getCropId(), count * rates().getRateDropManor()));
- }
- }
-
- /**
- * Sets the seed parameters, but not the seed state
- * @param seed - instance {@link L2Seed} of used seed
- * @param seeder - player who sows the seed
- */
- public final void setSeeded(L2Seed seed, L2PcInstance seeder) {
- if (!_seeded) {
- _seed = seed;
- _seederObjId = seeder.getObjectId();
- }
- }
-
- public final int getSeederId() {
- return _seederObjId;
- }
-
- public final L2Seed getSeed() {
- return _seed;
- }
-
- public final boolean isSeeded() {
- return _seeded;
- }
-
- /**
- * Set delay for onKill() call, in ms Default: 5000 ms
- * @param delay
- */
- public final void setOnKillDelay(int delay) {
- _onKillDelay = delay;
- }
-
- public final int getOnKillDelay() {
- return _onKillDelay;
- }
-
- /**
- * Check if the server allows Random Animation.
- */
- // This is located here because L2Monster and L2FriendlyMob both extend this class. The other non-pc instances extend either L2NpcInstance or L2MonsterInstance.
- @Override
- public boolean hasRandomAnimation() {
- return ((general().getMaxMonsterAnimation() > 0) && isRandomAnimationEnabled() && !(this instanceof L2GrandBossInstance));
- }
-
- @Override
- public boolean isMob() {
- return true; // This means we use MAX_MONSTER_ANIMATION instead of MAX_NPC_ANIMATION
- }
-
- public void setCommandChannelTimer(CommandChannelTimer commandChannelTimer) {
- _commandChannelTimer = commandChannelTimer;
- }
-
- public CommandChannelTimer getCommandChannelTimer() {
- return _commandChannelTimer;
- }
-
- public L2CommandChannel getFirstCommandChannelAttacked() {
- return _firstCommandChannelAttacked;
- }
-
- public void setFirstCommandChannelAttacked(L2CommandChannel firstCommandChannelAttacked) {
- _firstCommandChannelAttacked = firstCommandChannelAttacked;
- }
-
- /**
- * @return the _commandChannelLastAttack
- */
- public long getCommandChannelLastAttack() {
- return _commandChannelLastAttack;
- }
-
- /**
- * @param channelLastAttack the _commandChannelLastAttack to set
- */
- public void setCommandChannelLastAttack(long channelLastAttack) {
- _commandChannelLastAttack = channelLastAttack;
- }
-
- public void returnHome() {
- clearAggroList();
-
- if (hasAI() && (getSpawn() != null)) {
- getAI().setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, getSpawn().getLocation(this));
- }
- }
-
- /*
- * Return vitality points decrease (if positive) or increase (if negative) based on damage. Maximum for damage = maxHp.
- */
- public float getVitalityPoints(int damage) {
- // sanity check
- if (damage <= 0) {
- return 0;
- }
-
- final float divider = (getLevel() > 0) && (getExpReward() > 0) ? (getTemplate().getBaseHpMax() * 9 * getLevel() * getLevel()) / (100 * getExpReward()) : 0;
- if (divider == 0) {
- return 0;
- }
-
- // negative value - vitality will be consumed
- return -Math.min(damage, getMaxHp()) / divider;
- }
-
- /*
- * True if vitality rate for exp and sp should be applied
- */
- public boolean useVitalityRate() {
- return isChampion() ? customs().championEnableVitality() : true;
- }
-
- /** Return True if the L2Character is RaidBoss or his minion. */
- @Override
- public boolean isRaid() {
- return _isRaid;
- }
-
- /**
- * Set this Npc as a Raid instance.
- * @param isRaid
- */
- public void setIsRaid(boolean isRaid) {
- _isRaid = isRaid;
- }
-
- /**
- * Set this Npc as a Minion instance.
- * @param val
- */
- public void setIsRaidMinion(boolean val) {
- _isRaid = val;
- _isRaidMinion = val;
- }
-
- @Override
- public boolean isRaidMinion() {
- return _isRaidMinion;
- }
-
- @Override
- public boolean isMinion() {
- return getLeader() != null;
- }
-
- /**
- * @return leader of this minion or null.
- */
- public L2Attackable getLeader() {
- return null;
- }
-
- public void setChampion(boolean champ) {
- _champion = champ;
- }
-
- @Override
- public boolean isChampion() {
- return _champion;
- }
-
- @Override
- public boolean isAttackable() {
- return true;
- }
- }
|