скил храмовника

  • Автор темы Автор темы uter81
  • Дата начала Дата начала

uter81

Друг форума
Участник
Старожил I степени
Сообщения
469
Розыгрыши
0
Репутация
2
Реакции
19
Баллы
960
Хроники
  1. Master Class
Исходники
Присутствуют
Сборка
l2jMobius
Подскажите пожалуйста, скилл не правильно работает, при заюзе не накладывает эффек на пати и на самого перса. в описании должен накладывать на перса скилл 31166, а на группу 31165.
Подобный не нашел что бы сделать по аналогии.
сборка Superion
Код:
<skill id="31164" toLevel="1" name="Templar's Spirited Cry">
        <icon>icon.skill31164</icon>
        <abnormalTime>20</abnormalTime>
        <abnormalType>V_RARE_TEMPLAR_ROAR_F_TA</abnormalType>
        <abnormalVisualEffect>V_RARE_TEMPLAR_ROAR_F_TA</abnormalVisualEffect>
        <operateType>A2</operateType>
        <affectLimit>5-12</affectLimit>
        <affectRange>400</affectRange>
        <magicCriticalRate>5</magicCriticalRate>
        <magicLevel>110</magicLevel>
        <itemConsumeId>1785</itemConsumeId> <!-- Soulstone -->
        <itemConsumeCount>3</itemConsumeCount>
        <mpConsume>61</mpConsume>
        <isDebuff>true</isDebuff>
        <effectRange>400</effectRange>
        <hitTime>3500</hitTime>
        <coolTime>300</coolTime>
        <effectPoint>-350</effectPoint>
        <reuseDelay>10000</reuseDelay>
        <targetType>SELF</targetType>
        <affectScope>POINT_BLANK</affectScope>
        <affectObject>NOT_FRIEND</affectObject>
        <conditions>
            <condition name="EquipWeapon">
                <weaponType>
                    <item>SWORD</item>
                    <item>BLUNT</item>
                </weaponType>
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31170</skillId> <!-- Paladin's Fervent Cry -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31167</skillId> <!-- Guardian's Thunderous Cry -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31173</skillId> <!-- Avenger's Bitter Cry -->
            </condition>
        </conditions>
        <effects>
            <effect name="PhysicalDamage">
                <pAtkMod>1.80</pAtkMod>
                <overHit>true</overHit>
            </effect>
            <effect name="PhysicalSkillCriticalDamage">
                <amount>10</amount>
                <mode>PER</mode>
            </effect>
            <effect name="MagicCriticalDamage">
                <amount>10</amount>
                <mode>PER</mode>
            </effect>
            <effect name="CriticalDamage">
                <amount>10</amount>
                <mode>PER</mode>
            </effect>
            <effect name="CallSkill">
                <skillId>31165</skillId> <!-- Приказ Шилен -->
                <skillLevel>1</skillLevel>
            </effect>
        </effects>
    </skill>
 
зато спавны птс-ные.
:Noooo: :ROFLMAO:
П.с. где именно, в ПТС?

Я не фанат и не любитель таких людишек как мобиус, но у парня есть свой бизнес, и пускай делайт бабки как ему этого хочется, он никого не заставляет брать его код. Думаю не надо валить весь мусор на него, взал пакетку у него ну всё, скажы спс и проходи мимо, остальное делай сам в чём проблема? ааа да яж не умею бляяя, а учить книжки тоже лень, я только играть умею и знаю как должно работать, но все ж хочется в код воткнуть своё, а он сволочь за меня не делает как я хочу так. Тут уже рождается ненависть :ROFLMAO:
т.е. ты считаешь, то что он в каждой обнове выпиливает часть кода или ломает его (он рабочий но не верный в расчетах/логике), то это норма ?

И в целом, да ты прав, даже и пакетку не стоет брать (ну там реализации же нет).
 
Последнее редактирование модератором:
EnterWorld.java работает, остальное ручками
 
XML:
<effect name="TriggerSkillByTargetType">
                <allySkill>47042</allySkill>
                <enemySkill>47043</enemySkill>
                <allySkillLevel>
                    <value level="1">2</value>
                </allySkillLevel>
                <enemySkillLevel>
                    <value level="1">2</value>
                </enemySkillLevel>
            </effect>
 
Вот прямо копируй эти 3 скила, должно работать
XML:
<skill id="31164" toLevel="1" name="Templar's Spirited Cry">
        <!-- Shillien Templar's spirited cry resounds on the battlefield. Attacks nearby enemies dealing them damage equal to 180% of your P. Atk. High Critical rate. For 20 sec., the affected enemies' all received Critical Damage +10%. Applies the <font color="ECECEC">Shillien's Order</font> effect on nearby party members, and the <font color="ECECEC">Shillien's Leadership</font> effect on you. Consumes 3 Soulstones. The effect does not stack with Guardian's Thunderous Cry, Paladin's Fervent Cry and Avenger's Bitter Cry. Requires a sword or a blunt weapon. <font color="ECECEC"><Shillien's Order></font>. For 10 sec., nearby party members' all received Critical Damage -5%. <font color="ECECEC"><Shillien's Leadership></font>. For 10 sec., your P. Atk. +5%, P. Skill Power +20%, P. Skill Critical Rate +5%, P. Skill Critical Damage +20%, all received Critical Damage -5%. Base success rate: 60%. Inflicted status: Debilitation. -->
        <icon>icon.skill31164</icon>
        <operateType>A2</operateType>
        <targetType>SELF</targetType>
       <abnormalLevel>3</abnormalLevel>
        <abnormalTime>20</abnormalTime>
        <abnormalType>V_RARE_TEMPLAR_ROAR_F_TA</abnormalType>
        <abnormalVisualEffect>V_RARE_TEMPLAR_ROAR_F_TA</abnormalVisualEffect>
        <activateRate>60</activateRate>
        <affectLimit>5-12</affectLimit>
        <affectObject>NOT_FRIEND</affectObject>
        <affectRange>400</affectRange>
        <affectScope>POINT_BLANK</affectScope>
        <coolTime>500</coolTime>
        <effectPoint>-350</effectPoint>
        <effectRange>400</effectRange>
        <hitTime>3000</hitTime>
        <isDebuff>true</isDebuff>
        <itemConsumeCount>3</itemConsumeCount>
        <itemConsumeId>1785</itemConsumeId> <!-- Soulstone -->
        <magicCriticalRate>5</magicCriticalRate>
        <magicLevel>110</magicLevel>
        <mpConsume>61</mpConsume>
        <reuseDelay>10000</reuseDelay>
        <conditions>
            <condition name="EquipWeapon">
                <weaponType>
                    <item>SWORD</item>
                    <item>BLUNT</item>
                </weaponType>
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31170</skillId> <!-- Paladin's Fervent Cry -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31167</skillId> <!-- Guardian's Thunderous Cry -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31173</skillId> <!-- Avenger's Bitter Cry -->
            </condition>
        </conditions>
        <effects>
            <effect name="PhysicalDamage">
                <pAtkMod>1.80</pAtkMod>
                <overHit>true</overHit>
            </effect>
            <effect name="DefenceCriticalDamage">
                <amount>-10</amount>
                <mode>PER</mode>
            </effect>
            <effect name="DefencePhysicalSkillCriticalDamage">
                <amount>-10</amount>
                <mode>PER</mode>
            </effect>
            <effect name="DefenceMagicCriticalDamage">
                <amount>-10</amount>
                <mode>PER</mode>
            </effect>
           <effect name="TriggerSkill">
                <skillId>31165</skillId> <!-- Shillien's Order -->
                <skillLevel>1</skillLevel>
                <targetType>MY_PARTY</targetType>
            </effect>
            <effect name="TriggerSkill">
                <skillId>31166</skillId> <!-- Shillien's Leadership -->
                <skillLevel>1</skillLevel>
                <targetType>SELF</targetType>
            </effect>
        </effects>
    </skill>
    <skill id="31165" toLevel="1" name="Shillien's Order">
        <!-- Due to Templar's Spirited Cry, for 10 sec., all received Critical Damage -5%. The effect does not stack with those of Guardian's Order, Paladin's Order and Avenger's Order. -->
        <icon>icon.skill31164</icon>
        <operateType>A2</operateType>
        <targetType>MY_PARTY</targetType>
        <abnormalLevel>1</abnormalLevel>
        <abnormalTime>10</abnormalTime>
        <affectObject>FRIEND</affectObject>
        <affectRange>450</affectRange>
        <affectScope>PARTY</affectScope>
        <effectPoint>712</effectPoint>
        <magicCriticalRate>5</magicCriticalRate>
        <magicLevel>110</magicLevel>
        <conditions>
            <condition name="EquipWeapon">
                <weaponType>
                    <item>SWORD</item>
                    <item>BLUNT</item>
                </weaponType>
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31171</skillId> <!-- Paladin's Order -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31168</skillId> <!-- Guardian's Order -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31174</skillId> <!-- Avenger's Order -->
            </condition>
        </conditions>
        <effects>
            <effect name="DefenceCriticalDamage">
                <amount>5</amount>
                <mode>PER</mode>
            </effect>
            <effect name="DefenceMagicCriticalDamage">
                <amount>5</amount>
                <mode>PER</mode>
            </effect>
        </effects>
    </skill>
    <skill id="31166" toLevel="1" name="Shillien's Leadership">
        <!-- The Knight has the battlefield under control. For 10 sec., P. Atk. +5%, P. Skill Power +20%, P. Skill Critical Rate +5%, P. Skill Critical Damage +20%, all received Critical Damage -5%. -->
        <icon>icon.skill31164</icon>
        <operateType>A2</operateType>
        <abnormalLevel>2</abnormalLevel>
        <abnormalTime>10</abnormalTime>
        <effectPoint>712</effectPoint>
        <targetType>SELF</targetType>
        <affectScope>SINGLE</affectScope>
        <conditions>
            <condition name="OpNotAffectedBySkill">
                <skillId>31169</skillId> <!-- Guardian's Leadership -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31172</skillId> <!-- Paladin's Leadership -->
            </condition>
            <condition name="OpNotAffectedBySkill">
                <skillId>31175</skillId> <!--Avenger's Leadership -->
            </condition>
        </conditions>
        <effects>
            <effect name="PhysicalAttack">
                <amount>5</amount>
                <mode>PER</mode>
            </effect>
            <effect name="PhysicalSkillPower">
                <amount>20</amount>
                <mode>PER</mode>
            </effect>
            <effect name="PhysicalSkillCriticalRate">
                <amount>5</amount>
                <mode>PER</mode>
            </effect>
            <effect name="PhysicalSkillCriticalDamage">
                <amount>20</amount>
                <mode>PER</mode>
            </effect>
            <effect name="DefenceCriticalDamage">
                <amount>5</amount>
                <mode>PER</mode>
            </effect>
            <effect name="DefenceMagicCriticalDamage">
                <amount>5</amount>
                <mode>PER</mode>
            </effect>
            <effect name="DefencePhysicalSkillCriticalDamage">
                <amount>2</amount>
                <mode>PER</mode>
            </effect>
        </effects>
    </skill>
По аналогии сделая скилы и для остальных паладинов тоже.
попробую вечером, хорошо

попробую вечером, хорошо

не получается , так же вешает бафф который должен быть на храмовнике, висит на мобах вместе с дебаффом
и на пати не вешается

1767971035660.webp
 
Последнее редактирование модератором:
не получается , так же вешает бафф который должен быть на храмовнике, висит на мобах вместе с дебаффом
и на пати не вешается

Посмотреть вложение 92800
Если вы все 3 скила перезаписали то должно быть всё нормально, я сам тестил и всё по описанию, все скилы раскидывает как нужно.

31164 - на монстрах
31165 - на пати
31166 - на кастере
 
Если вы все 3 скила перезаписали то должно быть всё нормально, я сам тестил и всё по описанию, все скилы раскидывает как нужно.

31164 - на монстрах
31165 - на пати
31166 - на кастере
скопировал выше из ваше поста не работает почему то как нужно....
 
Тогда у вас сам эффект TriggerSkill не правилно работает, он багнутый у мобиуса не правильно определяет таргет

замените
_targetType = params.getEnum("targetType", TargetType.class, TargetType.TARGET);
на
_targetType = params.getEnum("targetType", TargetType.class, TargetType.SELF);

и в самом конце
SkillCaster.triggerCast(effector, effected, _skill.getSkill());
на
SkillCaster.triggerCast(effector, (Creature) target, _skill.getSkill());

Больше идей нет.
 
Оверпостинг
Тогда у вас сам эффект TriggerSkill не правилно работает, он багнутый у мобиуса не правильно определяет таргет

замените
_targetType = params.getEnum("targetType", TargetType.class, TargetType.TARGET);
на
_targetType = params.getEnum("targetType", TargetType.class, TargetType.SELF);

и в самом конце
SkillCaster.triggerCast(effector, effected, _skill.getSkill());
на
SkillCaster.triggerCast(effector, (Creature) target, _skill.getSkill());

Больше идей нет.
это в каком файле

вот часть кода в нем нашел параметры похожие
Код:
_reuseHashCode = SkillData.getSkillHashCode(_reuseDelayGroup > 0 ? _reuseDelayGroup : _id, _level, _subLevel);
        _targetType = set.getEnum("targetType", TargetType.class, TargetType.SELF);
        _affectScope = set.getEnum("affectScope", AffectScope.class, AffectScope.SINGLE);
        _affectObject = set.getEnum("affectObject", AffectObject.class, AffectObject.ALL);
        _affectRange = set.getInt("affectRange", 0);

Код:
package org.l2jmobius.gameserver.model.actor;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Predicate;
import java.util.logging.Logger;

import org.l2jmobius.Config;
import org.l2jmobius.commons.threads.ThreadPool;
import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.ai.Action;
import org.l2jmobius.gameserver.ai.AttackableAI;
import org.l2jmobius.gameserver.ai.CreatureAI;
import org.l2jmobius.gameserver.ai.Intention;
import org.l2jmobius.gameserver.cache.RelationCache;
import org.l2jmobius.gameserver.data.xml.CategoryData;
import org.l2jmobius.gameserver.data.xml.DoorData;
import org.l2jmobius.gameserver.data.xml.FenceData;
import org.l2jmobius.gameserver.data.xml.NpcData;
import org.l2jmobius.gameserver.data.xml.SkillData;
import org.l2jmobius.gameserver.data.xml.TransformData;
import org.l2jmobius.gameserver.enums.CategoryType;
import org.l2jmobius.gameserver.enums.ShotType;
import org.l2jmobius.gameserver.enums.StatusUpdateType;
import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.geoengine.GeoEngine;
import org.l2jmobius.gameserver.geoengine.pathfinding.GeoLocation;
import org.l2jmobius.gameserver.geoengine.pathfinding.PathFinding;
import org.l2jmobius.gameserver.managers.IdManager;
import org.l2jmobius.gameserver.managers.MapRegionManager;
import org.l2jmobius.gameserver.managers.QuestManager;
import org.l2jmobius.gameserver.managers.ZoneManager;
import org.l2jmobius.gameserver.model.AccessLevel;
import org.l2jmobius.gameserver.model.EffectList;
import org.l2jmobius.gameserver.model.Hit;
import org.l2jmobius.gameserver.model.Location;
import org.l2jmobius.gameserver.model.Party;
import org.l2jmobius.gameserver.model.Spawn;
import org.l2jmobius.gameserver.model.TimeStamp;
import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.WorldObject;
import org.l2jmobius.gameserver.model.WorldRegion;
import org.l2jmobius.gameserver.model.actor.enums.creature.AttributeType;
import org.l2jmobius.gameserver.model.actor.enums.creature.InstanceType;
import org.l2jmobius.gameserver.model.actor.enums.creature.Race;
import org.l2jmobius.gameserver.model.actor.enums.creature.Team;
import org.l2jmobius.gameserver.model.actor.enums.player.PlayerCondOverride;
import org.l2jmobius.gameserver.model.actor.enums.player.TeleportWhereType;
import org.l2jmobius.gameserver.model.actor.holders.creature.IgnoreSkillHolder;
import org.l2jmobius.gameserver.model.actor.instance.FriendlyNpc;
import org.l2jmobius.gameserver.model.actor.instance.GrandBoss;
import org.l2jmobius.gameserver.model.actor.instance.Monster;
import org.l2jmobius.gameserver.model.actor.instance.Trap;
import org.l2jmobius.gameserver.model.actor.stat.CreatureStat;
import org.l2jmobius.gameserver.model.actor.status.CreatureStatus;
import org.l2jmobius.gameserver.model.actor.tasks.creature.NotifyAITask;
import org.l2jmobius.gameserver.model.actor.templates.CreatureTemplate;
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
import org.l2jmobius.gameserver.model.actor.transform.Transform;
import org.l2jmobius.gameserver.model.clan.Clan;
import org.l2jmobius.gameserver.model.effects.EffectFlag;
import org.l2jmobius.gameserver.model.events.Containers;
import org.l2jmobius.gameserver.model.events.EventDispatcher;
import org.l2jmobius.gameserver.model.events.EventType;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureAttack;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureAttackAvoid;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureAttacked;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureDamageDealt;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureDamageReceived;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureDeath;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureKilled;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureSee;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureSkillFinishCast;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureSkillUse;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureTeleport;
import org.l2jmobius.gameserver.model.events.impl.creature.OnCreatureTeleported;
import org.l2jmobius.gameserver.model.events.impl.creature.npc.OnAttackableFactionCall;
import org.l2jmobius.gameserver.model.events.listeners.AbstractEventListener;
import org.l2jmobius.gameserver.model.events.returns.DamageReturn;
import org.l2jmobius.gameserver.model.events.returns.LocationReturn;
import org.l2jmobius.gameserver.model.instancezone.Instance;
import org.l2jmobius.gameserver.model.interfaces.ILocational;
import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enums.ItemProcessType;
import org.l2jmobius.gameserver.model.item.enums.ItemSkillType;
import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.EtcItemType;
import org.l2jmobius.gameserver.model.item.type.WeaponType;
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
import org.l2jmobius.gameserver.model.options.OptionSkillHolder;
import org.l2jmobius.gameserver.model.options.OptionSkillType;
import org.l2jmobius.gameserver.model.skill.AbnormalType;
import org.l2jmobius.gameserver.model.skill.BuffFinishTask;
import org.l2jmobius.gameserver.model.skill.BuffInfo;
import org.l2jmobius.gameserver.model.skill.CommonSkill;
import org.l2jmobius.gameserver.model.skill.Skill;
import org.l2jmobius.gameserver.model.skill.SkillCaster;
import org.l2jmobius.gameserver.model.skill.SkillCastingType;
import org.l2jmobius.gameserver.model.skill.SkillChannelized;
import org.l2jmobius.gameserver.model.skill.SkillChannelizer;
import org.l2jmobius.gameserver.model.skill.enums.BasicProperty;
import org.l2jmobius.gameserver.model.skill.enums.SkillFinishType;
import org.l2jmobius.gameserver.model.skill.holders.SkillHolder;
import org.l2jmobius.gameserver.model.stats.BaseStat;
import org.l2jmobius.gameserver.model.stats.BasicPropertyResist;
import org.l2jmobius.gameserver.model.stats.Formulas;
import org.l2jmobius.gameserver.model.stats.MoveType;
import org.l2jmobius.gameserver.model.stats.Stat;
import org.l2jmobius.gameserver.model.zone.ZoneId;
import org.l2jmobius.gameserver.model.zone.ZoneRegion;
import org.l2jmobius.gameserver.network.Disconnection;
import org.l2jmobius.gameserver.network.GameClient;
import org.l2jmobius.gameserver.network.SystemMessageId;
import org.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import org.l2jmobius.gameserver.network.serverpackets.Attack;
import org.l2jmobius.gameserver.network.serverpackets.ChangeMoveType;
import org.l2jmobius.gameserver.network.serverpackets.ChangeWaitType;
import org.l2jmobius.gameserver.network.serverpackets.ExTeleportToLocationActivate;
import org.l2jmobius.gameserver.network.serverpackets.FakePlayerInfo;
import org.l2jmobius.gameserver.network.serverpackets.MoveToLocation;
import org.l2jmobius.gameserver.network.serverpackets.MoveToPawn;
import org.l2jmobius.gameserver.network.serverpackets.NpcInfo;
import org.l2jmobius.gameserver.network.serverpackets.Revive;
import org.l2jmobius.gameserver.network.serverpackets.ServerObjectInfo;
import org.l2jmobius.gameserver.network.serverpackets.ServerPacket;
import org.l2jmobius.gameserver.network.serverpackets.SetupGauge;
import org.l2jmobius.gameserver.network.serverpackets.SocialAction;
import org.l2jmobius.gameserver.network.serverpackets.StatusUpdate;
import org.l2jmobius.gameserver.network.serverpackets.StopMove;
import org.l2jmobius.gameserver.network.serverpackets.SystemMessage;
import org.l2jmobius.gameserver.network.serverpackets.TeleportToLocation;
import org.l2jmobius.gameserver.network.serverpackets.UserInfo;
import org.l2jmobius.gameserver.taskmanagers.AttackStanceTaskManager;
import org.l2jmobius.gameserver.taskmanagers.CreatureAttackTaskManager;
import org.l2jmobius.gameserver.taskmanagers.CreatureSeeTaskManager;
import org.l2jmobius.gameserver.taskmanagers.GameTimeTaskManager;
import org.l2jmobius.gameserver.taskmanagers.MovementTaskManager;
import org.l2jmobius.gameserver.util.Broadcast;
import org.l2jmobius.gameserver.util.LocationUtil;

public abstract class Creature extends WorldObject
{
    public static final Logger LOGGER = Logger.getLogger(Creature.class.getName());
    
    private final Set<WeakReference<Creature>> _attackByList = ConcurrentHashMap.newKeySet(1);
    
    private boolean _isDead = false;
    private boolean _isImmobilized = false;
    private boolean _isOverloaded = false; // the char is carrying too much
    private boolean _isPendingRevive = false;
    private boolean _isRunning = isPlayer();
    protected boolean _showSummonAnimation = false;
    protected boolean _isTeleporting = false;
    private boolean _isInvul = false;
    private boolean _isUndying = false;
    private boolean _isFlying = false;
    
    private boolean _blockActions = false;
    private final Map<Integer, AtomicInteger> _blockActionsAllowedSkills = new ConcurrentHashMap<>();
    
    private CreatureStat _stat;
    private CreatureStatus _status;
    private CreatureTemplate _template; // The link on the CreatureTemplate object containing generic and static properties of this Creature type (ex : Max HP, Speed...)
    private String _title;
    
    public static final double MAX_HP_BAR_PX = 352.0;
    
    private double _hpUpdateIncCheck = .0;
    private double _hpUpdateDecCheck = .0;
    private double _hpUpdateInterval = .0;
    
    private int _reputation = 0;
    
    /** Map containing all skills of this character. */
    private final ConcurrentSkipListMap<Integer, Skill> _skills = new ConcurrentSkipListMap<>();
    /** Map containing the skill reuse time stamps. */
    private final Map<Long, TimeStamp> _reuseTimeStampsSkills = new ConcurrentHashMap<>();
    /** Map containing the item reuse time stamps. */
    private final Map<Integer, TimeStamp> _reuseTimeStampsItems = new ConcurrentHashMap<>();
    /** Map containing all the disabled skills. */
    private final Map<Long, Long> _disabledSkills = new ConcurrentHashMap<>();
    private boolean _allSkillsDisabled;
    
    private final byte[] _zones = new byte[ZoneId.getZoneCount()];
    protected final Location _lastZoneValidateLocation = new Location(super.getX(), super.getY(), super.getZ());
    
    private final StampedLock _attackLock = new StampedLock();
    
    private Team _team = Team.NONE;
    
    protected long _exceptions = 0;
    
    private boolean _lethalable = true;
    
    private final Map<Integer, OptionSkillHolder> _triggerSkills = new ConcurrentHashMap<>(1);
    
    private final Map<Integer, IgnoreSkillHolder> _ignoreSkillEffects = new ConcurrentHashMap<>(1);
    /** Creatures effect list. */
    private final EffectList _effectList = new EffectList(this);
    /** The creature that summons this character. */
    private Creature _summoner = null;
    
    /** Map of summoned NPCs by this creature. */
    private Map<Integer, Npc> _summonedNpcs = null;
    
    private SkillChannelizer _channelizer = null;
    
    private SkillChannelized _channelized = null;
    
    private final BuffFinishTask _buffFinishTask = new BuffFinishTask();
    
    private Optional<Transform> _transform = Optional.empty();
    
    /** Movement data of this Creature */
    protected MoveData _move;
    private boolean _cursorKeyMovement = false;
    
    private ScheduledFuture<?> _broadcastModifiedStatTask = null;
    private final Set<Stat> _broadcastModifiedStatChanges = EnumSet.noneOf(Stat.class);
    
    /** This creature's target. */
    private WorldObject _target;
    
    // set by the start of attack, in game ticks
    private volatile long _attackEndTime;
    private volatile long _disableRangedAttackEndTime;
    
    private CreatureAI _ai = null;
    
    /** Future Skill Cast */
    protected Map<SkillCastingType, SkillCaster> _skillCasters = new ConcurrentHashMap<>();
    
    private final AtomicInteger _abnormalShieldBlocks = new AtomicInteger();
    private final Map<Integer, Double> _multipliedAbnormalTimes = new ConcurrentHashMap<>();
    private final Map<Integer, Integer> _addedAbnormalTimes = new ConcurrentHashMap<>();
    
    private final Map<Integer, RelationCache> _knownRelations = new ConcurrentHashMap<>();
    
    private Set<Creature> _seenCreatures = null;
    private int _seenCreatureRange = Config.ALT_PARTY_RANGE;
    
    private final Map<StatusUpdateType, Long> _statusUpdates = new ConcurrentHashMap<>();
    
    /** A map holding info about basic property mesmerizing system. */
    private final Map<BasicProperty, BasicPropertyResist> _basicPropertyResists = new ConcurrentHashMap<>(1);
    
    /** A set containing the shot types currently charged. */
    private Set<ShotType> _chargedShots = EnumSet.noneOf(ShotType.class);
    
    /** A list containing the dropped items of this fake player. */
    private final List<Item> _fakePlayerDrops = new CopyOnWriteArrayList<>();
    
    private OnCreatureAttack _onCreatureAttack = null;
    private OnCreatureAttacked _onCreatureAttacked = null;
    private OnCreatureDamageDealt _onCreatureDamageDealt = null;
    private OnCreatureDamageReceived _onCreatureDamageReceived = null;
    private OnCreatureAttackAvoid _onCreatureAttackAvoid = null;
    public OnCreatureSkillFinishCast onCreatureSkillFinishCast = null;
    public OnCreatureSkillUse onCreatureSkillUse = null;
    
    /**
     * Creates a creature.
     * @param template the creature template
     */
    public Creature(CreatureTemplate template)
    {
        this(IdManager.getInstance().getNextId(), template);
    }
    
    /**
     * Constructor of Creature.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * Each Creature owns generic and static properties (ex : all Keltir have the same number of HP...).<br>
     * All of those properties are stored in a different template for each type of Creature. Each template is loaded once in the server cache memory (reduce memory use).<br>
     * When a new instance of Creature is spawned, server just create a link between the instance and the template This link is stored in <b>_template</b><br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Set the _template of the Creature</li>
     * <li>Set _overloaded to false (the character can take more items)</li>
     * <li>If Creature is a Npc, copy skills from template to object</li>
     * <li>If Creature is a Npc, link _calculators to NPC_STD_CALCULATOR</li>
     * <li>If Creature is NOT a Npc, create an empty _skills slot</li>
     * <li>If Creature is a Player or Summon, copy basic Calculator set to object</li>
     * </ul>
     * @param objectId Identifier of the object to initialized
     * @param template The CreatureTemplate to apply to the object
     */
    public Creature(int objectId, CreatureTemplate template)
    {
        super(objectId);
        if (template == null)
        {
            throw new NullPointerException("Template is null!");
        }
        
        setInstanceType(InstanceType.Creature);
        // Set its template to the new Creature
        _template = template;
        initCharStat();
        initCharStatus();
        
        if (isNpc())
        {
            // Copy the skills of the Npc from its template to the Creature Instance
            // The skills list can be affected by spell effects so it's necessary to make a copy
            // to avoid that a spell affecting a Npc, affects others Npc of the same type too.
            for (Skill skill : template.getSkills().values())
            {
                addSkill(skill);
            }
        }
        else if (isSummon())
        {
            // Copy the skills of the Summon from its template to the Creature Instance
            // The skills list can be affected by spell effects so it's necessary to make a copy
            // to avoid that a spell affecting a Summon, affects others Summon of the same type too.
            for (Skill skill : template.getSkills().values())
            {
                addSkill(skill);
            }
        }
        
        setInvul(true);
    }
    
    public EffectList getEffectList()
    {
        return _effectList;
    }
    
    /**
     * @return character inventory, default null, overridden in Playable types and in Npc
     */
    public Inventory getInventory()
    {
        return null;
    }
    
    public boolean destroyItemByItemId(ItemProcessType process, int itemId, long count, WorldObject reference, boolean sendMessage)
    {
        // Default: NPCs consume virtual items for their skills
        // TODO: should be logged if even happens.. should be false
        return true;
    }
    
    public boolean destroyItem(ItemProcessType process, int objectId, long count, WorldObject reference, boolean sendMessage)
    {
        // Default: NPCs consume virtual items for their skills
        // TODO: should be logged if even happens.. should be false
        return true;
    }
    
    /**
     * Check if the character is in the given zone Id.
     * @param zone the zone Id to check
     * @return {code true} if the character is in that zone
     */
    @Override
    public boolean isInsideZone(ZoneId zone)
    {
        final Instance instance = getInstanceWorld();
        switch (zone)
        {
            case PVP:
            {
                if ((instance != null) && instance.isPvP())
                {
                    return true;
                }
                return (_zones[ZoneId.PVP.ordinal()] > 0) && (_zones[ZoneId.PEACE.ordinal()] == 0) && (_zones[ZoneId.NO_PVP.ordinal()] == 0);
            }
            case PEACE:
            {
                if ((instance != null) && instance.isPvP())
                {
                    return false;
                }
            }
        }
        return _zones[zone.ordinal()] > 0;
    }
    
    /**
     * @param zone
     * @param state
     */
    public void setInsideZone(ZoneId zone, boolean state)
    {
        synchronized (_zones)
        {
            if (state)
            {
                _zones[zone.ordinal()]++;
            }
            else if (_zones[zone.ordinal()] > 0)
            {
                _zones[zone.ordinal()]--;
            }
        }
    }
    
    /**
     * @return {@code true} if this creature is transformed including stance transformation {@code false} otherwise.
     */
    public boolean isTransformed()
    {
        return _transform.isPresent();
    }
    
    /**
     * @param filter any conditions to be checked for the transformation, {@code null} otherwise.
     * @return {@code true} if this creature is transformed under the given filter conditions, {@code false} otherwise.
     */
    public boolean checkTransformed(Predicate<Transform> filter)
    {
        return _transform.filter(filter).isPresent();
    }
    
    /**
     * Tries to transform this creature with the specified template id.
     * @param id the id of the transformation template
     * @param addSkills {@code true} if skills of this transformation template should be added, {@code false} otherwise.
     * @return {@code true} if template is found and transformation is done, {@code false} otherwise.
     */
    public boolean transform(int id, boolean addSkills)
    {
        final Transform transform = TransformData.getInstance().getTransform(id);
        if (transform != null)
        {
            transform(transform, addSkills);
            return true;
        }
        return false;
    }
    
    public void transform(Transform transformation, boolean addSkills)
    {
        if (!Config.ALLOW_MOUNTS_DURING_SIEGE && transformation.isRiding() && isInsideZone(ZoneId.SIEGE))
        {
            return;
        }
        
        _transform = Optional.of(transformation);
        transformation.onTransform(this, addSkills);
    }
    
    public void untransform()
    {
        _transform.ifPresent(t -> t.onUntransform(this));
        _transform = Optional.empty();
        
        // Mobius: Tempfix for untransform not showing stats.
        // Resend UserInfo to player.
        if (isPlayer())
        {
            getStat().recalculateStats(true);
            asPlayer().updateUserInfo();
        }
    }
    
    public Optional<Transform> getTransformation()
    {
        return _transform;
    }
    
    /**
     * This returns the transformation Id of the current transformation. For example, if a player is transformed as a Buffalo, and then picks up the Zariche, the transform Id returned will be that of the Zariche, and NOT the Buffalo.
     * @return Transformation Id
     */
    public int getTransformationId()
    {
        return _transform.map(Transform::getId).orElse(0);
    }
    
    public int getTransformationDisplayId()
    {
        return _transform.filter(transform -> !transform.isStance()).map(Transform::getDisplayId).orElse(0);
    }
    
    public float getCollisionRadius()
    {
        final float defaultCollisionRadius = _template.getCollisionRadius();
        return _transform.map(transform -> transform.getCollisionRadius(this, defaultCollisionRadius)).orElse(defaultCollisionRadius);
    }
    
    public float getCollisionHeight()
    {
        final float defaultCollisionHeight = _template.getCollisionHeight();
        return _transform.map(transform -> transform.getCollisionHeight(this, defaultCollisionHeight)).orElse(defaultCollisionHeight);
    }
    
    /**
     * This will return true if the player is GM,<br>
     * but if the player is not GM it will return false.
     * @return GM status
     */
    public boolean isGM()
    {
        return false;
    }
    
    /**
     * Overridden in Player.
     * @return the access level.
     */
    public AccessLevel getAccessLevel()
    {
        return null;
    }
    
    protected void initCharStatusUpdateValues()
    {
        _hpUpdateIncCheck = _stat.getMaxHp();
        _hpUpdateInterval = _hpUpdateIncCheck / MAX_HP_BAR_PX;
        _hpUpdateDecCheck = _hpUpdateIncCheck - _hpUpdateInterval;
    }
    
    /**
     * Remove the Creature from the world when the decay task is launched.<br>
     * <font color=#FF0000><b><u>Caution</u>: This method DOESN'T REMOVE the object from _allObjects of World </b></font><br>
     * <font color=#FF0000><b><u>Caution</u>: This method DOESN'T SEND Server->Client packets to players</b></font>
     */
    public void onDecay()
    {
        if (isPlayer())
        {
            final Player player = asPlayer();
            if (player.isInTimedHuntingZone())
            {
                player.stopTimedHuntingZoneTask();
                abortCast();
                stopMove(null);
                teleToLocation(MapRegionManager.getInstance().getTeleToLocation(this, TeleportWhereType.TOWN));
                setInstance(null);
            }
            else if (Config.DISCONNECT_AFTER_DEATH)
            {
                Disconnection.of(player).deleteMe().defaultSequence(new SystemMessage(SystemMessageId.SIXTY_MIN_HAVE_PASSED_AFTER_THE_DEATH_OF_YOUR_CHARACTER_SO_YOU_WERE_DISCONNECTED_FROM_THE_GAME));
            }
        }
        else
        {
            decayMe();
            final ZoneRegion region = ZoneManager.getInstance().getRegion(this);
            if (region != null)
            {
                region.removeFromZones(this);
            }
            
            // Removes itself from the summoned list.
            if ((_summoner != null))
            {
                _summoner.removeSummonedNpc(getObjectId());
            }
            
            // Enable AI.
            _disabledAI = false;
            
            _onCreatureAttack = null;
            _onCreatureAttacked = null;
            _onCreatureDamageDealt = null;
            _onCreatureDamageReceived = null;
            _onCreatureAttackAvoid = null;
            onCreatureSkillFinishCast = null;
            onCreatureSkillUse = null;
        }
    }
    
    @Override
    public void onSpawn()
    {
        super.onSpawn();
        
        _buffFinishTask.start();
        
        revalidateZone(true);
        
        // Custom boss announcements configuration.
        if (this instanceof GrandBoss)
        {
            if (Config.GRANDBOSS_SPAWN_ANNOUNCEMENTS && (!isInInstance() || Config.GRANDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion())
            {
                final String name = NpcData.getInstance().getTemplate(getId()).getName();
                if ((name != null) && !Config.RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS.contains(getId()))
                {
                    Broadcast.toAllOnlinePlayers(name + " возродился!");
                    Broadcast.toAllOnlinePlayersOnScreen(name + " возродился!");
                }
            }
        }
        else if (isRaid() && Config.RAIDBOSS_SPAWN_ANNOUNCEMENTS && (!isInInstance() || Config.RAIDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion())
        {
            final String name = NpcData.getInstance().getTemplate(getId()).getName();
            if ((name != null) && !Config.RAIDBOSSES_EXCLUDED_FROM_SPAWN_ANNOUNCEMENTS.contains(getId()))
            {
                Broadcast.toAllOnlinePlayers(name + " возродился!");
                Broadcast.toAllOnlinePlayersOnScreen(name + " возродился!");
            }
        }
    }
    
    public synchronized void onTeleported()
    {
        if (!_isTeleporting)
        {
            return;
        }
        
        spawnMe(getX(), getY(), getZ());
        setTeleporting(false);
        
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_TELEPORTED, this))
        {
            EventDispatcher.getInstance().notifyEventAsync(new OnCreatureTeleported(this), this);
        }
    }
    
    /**
     * Add Creature instance that is attacking to the attacker list.
     * @param creature The Creature that attacks this one
     */
    public void addAttackerToAttackByList(Creature creature)
    {
        // DS: moved to Attackable
    }
    
    /**
     * Send a packet to the Creature AND to all Player in the _KnownPlayers of the Creature.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * Player in the detection area of the Creature are identified in <b>_knownPlayers</b>.<br>
     * In order to inform other players of state modification on the Creature, server just need to go through _knownPlayers to send Server->Client Packet
     * @param packet
     */
    public void broadcastPacket(ServerPacket packet)
    {
        broadcastPacket(packet, true);
    }
    
    public void broadcastPacket(ServerPacket packet, boolean includeSelf)
    {
        // TODO: Maybe add some nearby player count logic here.
        packet.sendInBroadcast();
        
        World.getInstance().forEachVisibleObject(this, Player.class, player ->
        {
            if (isVisibleFor(player))
            {
                player.sendPacket(packet);
            }
        });
    }
    
    /**
     * Send a packet to the Creature AND to all Player in the radius (max knownlist radius) from the Creature.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * Player in the detection area of the Creature are identified in <b>_knownPlayers</b>.<br>
     * In order to inform other players of state modification on the Creature, server just need to go through _knownPlayers to send Server->Client Packet
     * @param packet
     * @param radiusInKnownlist
     */
    public void broadcastPacket(ServerPacket packet, int radiusInKnownlist)
    {
        packet.sendInBroadcast();
        
        World.getInstance().forEachVisibleObjectInRange(this, Player.class, radiusInKnownlist, player ->
        {
            if (isVisibleFor(player))
            {
                player.sendPacket(packet);
            }
        });
    }
    
    public void broadcastMoveToLocation()
    {
        broadcastMoveToLocation(false);
    }
    
    public void broadcastMoveToLocation(boolean force)
    {
        final MoveData move = _move;
        if (move == null)
        {
            return;
        }
        
        // Broadcast MoveToLocation when forced or once per second.
        final int gameTicks = GameTimeTaskManager.getInstance().getGameTicks();
        if (!force && (move.moveTimestamp > 0) && ((gameTicks - move.lastBroadcastTime) < 10))
        {
            return;
        }
        move.lastBroadcastTime = gameTicks;
        
        if (isPlayable())
        {
            broadcastPacket(new MoveToLocation(this));
        }
        else
        {
            final CreatureAI ai = hasAI() ? getAI() : null;
            final Intention intention = ai != null ? ai.getIntention() : null;
            final WorldObject target = ((intention == Intention.ATTACK) || (intention == Intention.FOLLOW)) ? _target : null;
            if (target != null)
            {
                if (target != this)
                {
                    broadcastPacket(new MoveToPawn(this, target, getAI().getClientMovingToPawnOffset()));
                }
                else
                {
                    broadcastPacket(new MoveToLocation(this));
                }
            }
            else
            {
                final WorldRegion region = getWorldRegion();
                if (((region != null) && region.areNeighborsActive()))
                {
                    broadcastPacket(new MoveToLocation(this));
                }
            }
        }
    }
    
    public void broadcastSocialAction(int id)
    {
        if (isPlayable())
        {
            broadcastPacket(new SocialAction(getObjectId(), id));
        }
        else
        {
            final WorldRegion region = getWorldRegion();
            if ((region != null) && region.areNeighborsActive())
            {
                broadcastPacket(new SocialAction(getObjectId(), id));
            }
        }
    }
    
    /**
     * @return true if hp update should be done, false if not.
     */
    protected boolean needHpUpdate()
    {
        final double currentHp = _status.getCurrentHp();
        final double maxHp = _stat.getMaxHp();
        if ((currentHp <= 1.0) || (maxHp < MAX_HP_BAR_PX))
        {
            return true;
        }
        
        if ((currentHp <= _hpUpdateDecCheck) || (currentHp >= _hpUpdateIncCheck))
        {
            if (currentHp == maxHp)
            {
                _hpUpdateIncCheck = currentHp + 1;
                _hpUpdateDecCheck = currentHp - _hpUpdateInterval;
            }
            else
            {
                final double doubleMulti = currentHp / _hpUpdateInterval;
                int intMulti = (int) doubleMulti;
                _hpUpdateDecCheck = _hpUpdateInterval * (doubleMulti < intMulti ? intMulti - 1 : intMulti);
                _hpUpdateIncCheck = _hpUpdateDecCheck + _hpUpdateInterval;
            }
            
            return true;
        }
        
        return false;
    }
    
    public void broadcastStatusUpdate()
    {
        broadcastStatusUpdate(null);
    }
    
    /**
     * Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Create the Server->Client packet StatusUpdate with current HP and MP</li>
     * <li>Send the Server->Client packet StatusUpdate with current HP and MP to all Creature called _statusListener that must be informed of HP/MP updates of this Creature</li>
     * </ul>
     * <font color=#FF0000><b><u>Caution</u>: This method DOESN'T SEND CP information</b></font>
     * @param caster
     */
    public void broadcastStatusUpdate(Creature caster)
    {
        final StatusUpdate su = new StatusUpdate(this);
        if (caster != null)
        {
            su.addCaster(caster);
        }
        
        // HP
        su.addUpdate(StatusUpdateType.MAX_HP, _stat.getMaxHp());
        su.addUpdate(StatusUpdateType.CUR_HP, (long) _status.getCurrentHp());
        
        // MP
        computeStatusUpdate(su, StatusUpdateType.MAX_MP);
        computeStatusUpdate(su, StatusUpdateType.CUR_MP);
        broadcastPacket(su);
    }
    
    /**
     * @param text
     */
    public void sendMessage(String text)
    {
        // default implementation
    }
    
    /**
     * Teleport a Creature and its pet if necessary.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Stop the movement of the Creature</li>
     * <li>Set the x,y,z position of the WorldObject and if necessary modify its _worldRegion</li>
     * <li>Send a Server->Client packet TeleportToLocationt to the Creature AND to all Player in its _KnownPlayers</li>
     * <li>Modify the position of the pet if necessary</li>
     * </ul>
     * @param xValue
     * @param yValue
     * @param zValue
     * @param headingValue
     * @param instanceValue
     */
    public void teleToLocation(int xValue, int yValue, int zValue, int headingValue, Instance instanceValue)
    {
        // Prevent teleporting for players that disconnected unexpectedly.
        if (isPlayer() && !asPlayer().isOnline())
        {
            return;
        }
        
        int x = xValue;
        int y = yValue;
        int z = _isFlying ? zValue : GeoEngine.getInstance().getHeight(x, y, zValue);
        int heading = headingValue;
        Instance instance = instanceValue;
        
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_TELEPORT, this))
        {
            final LocationReturn term = EventDispatcher.getInstance().notifyEvent(new OnCreatureTeleport(this, x, y, z, heading, instance), this, LocationReturn.class);
            if (term != null)
            {
                if (term.terminate())
                {
                    return;
                }
                else if (term.overrideLocation())
                {
                    x = term.getX();
                    y = term.getY();
                    z = term.getZ();
                    heading = term.getHeading();
                    instance = term.getInstance();
                }
            }
        }
        
        // Prepare creature for teleport.
        if (_isPendingRevive)
        {
            doRevive();
        }
        
        // Abort any client actions, casting and remove target.
        sendPacket(ActionFailed.get(SkillCastingType.NORMAL));
        sendPacket(ActionFailed.get(SkillCastingType.NORMAL_SECOND));
        if (isMoving())
        {
            stopMove(null);
        }
        abortCast();
        setTarget(null);
        
        setTeleporting(true);
        
        getAI().setIntention(Intention.ACTIVE);
        
        // Remove the object from its old location.
        decayMe();
        
        // Adjust position a bit.
        z += 5;
        
        // Send teleport packet where needed.
        broadcastPacket(new TeleportToLocation(this, x, y, z, heading));
        
        // Change instance world.
        if (getInstanceWorld() != instance)
        {
            setInstance(instance);
        }
        
        // Set the x,y,z position of the WorldObject and if necessary modify its _worldRegion.
        setXYZ(x, y, z);
        // Also adjust heading.
        if (heading != 0)
        {
            setHeading(heading);
        }
        
        // Send teleport finished packet to player.
        sendPacket(new ExTeleportToLocationActivate(this));
        
        // Allow recall of the detached characters.
        if (isPlayer())
        {
            final Player player = asPlayer();
            final GameClient client = player.getClient();
            if ((client != null) && client.isDetached())
            {
                onTeleported();
            }
        }
        else
        {
            onTeleported();
        }
        
        revalidateZone(true);
    }
    
    public void teleToLocation(int x, int y, int z)
    {
        teleToLocation(x, y, z, 0, getInstanceWorld());
    }
    
    public void teleToLocation(int x, int y, int z, Instance instance)
    {
        teleToLocation(x, y, z, 0, instance);
    }
    
    public void teleToLocation(int x, int y, int z, int heading)
    {
        teleToLocation(x, y, z, heading, getInstanceWorld());
    }
    
    public void teleToLocation(int x, int y, int z, int heading, boolean randomOffset)
    {
        teleToLocation(x, y, z, heading, (randomOffset) ? Config.MAX_OFFSET_ON_TELEPORT : 0, getInstanceWorld());
    }
    
    public void teleToLocation(int x, int y, int z, int heading, boolean randomOffset, Instance instance)
    {
        teleToLocation(x, y, z, heading, (randomOffset) ? Config.MAX_OFFSET_ON_TELEPORT : 0, instance);
    }
    
    public void teleToLocation(int x, int y, int z, int heading, int randomOffset)
    {
        teleToLocation(x, y, z, heading, randomOffset, getInstanceWorld());
    }
    
    public void teleToLocation(int xValue, int yValue, int z, int heading, int randomOffset, Instance instance)
    {
        int x = xValue;
        int y = yValue;
        if (Config.OFFSET_ON_TELEPORT_ENABLED && (randomOffset > 0))
        {
            x += Rnd.get(-randomOffset, randomOffset);
            y += Rnd.get(-randomOffset, randomOffset);
        }
        teleToLocation(x, y, z, heading, instance);
    }
    
    public void teleToLocation(ILocational loc)
    {
        teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading());
    }
    
    public void teleToLocation(ILocational loc, Instance instance)
    {
        teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), instance);
    }
    
    public void teleToLocation(ILocational loc, int randomOffset)
    {
        teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), randomOffset);
    }
    
    public void teleToLocation(ILocational loc, int randomOffset, Instance instance)
    {
        teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), randomOffset, instance);
    }
    
    public void teleToLocation(ILocational loc, boolean randomOffset)
    {
        teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), (randomOffset) ? Config.MAX_OFFSET_ON_TELEPORT : 0);
    }
    
    public void teleToLocation(ILocational loc, boolean randomOffset, Instance instance)
    {
        teleToLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getHeading(), randomOffset, instance);
    }
    
    public void teleToLocation(TeleportWhereType teleportWhere)
    {
        teleToLocation(teleportWhere, getInstanceWorld());
    }
    
    public void teleToLocation(TeleportWhereType teleportWhere, Instance instance)
    {
        teleToLocation(MapRegionManager.getInstance().getTeleToLocation(this, teleportWhere), true, instance);
    }
    
    /**
     * Launch a physical attack against a target (Simple, Bow, Pole or Dual).<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Get the active weapon (always equipped in the right hand)</li>
     * <li>If weapon is a bow, check for arrows, MP and bow re-use delay (if necessary, equip the Player with arrows in left hand)</li>
     * <li>If weapon is a bow, consume MP and set the new period of bow non re-use</li>
     * <li>Get the Attack Speed of the Creature (delay (in milliseconds) before next attack)</li>
     * <li>Select the type of attack to start (Simple, Bow, Pole or Dual) and verify if SoulShot are charged then start calculation</li>
     * <li>If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack to the Creature AND to all Player in the _KnownPlayers of the Creature</li>
     * <li>Notify AI with READY_TO_ACT</li>
     * </ul>
     * @param target The Creature targeted
     */
    public void doAutoAttack(Creature target)
    {
        final long stamp = _attackLock.tryWriteLock();
        if (stamp == 0)
        {
            return;
        }
        try
        {
            if ((target == null) || (!isPlayable() && isAttackDisabled()) || !target.isTargetable())
            {
                return;
            }
            
            if (!isAlikeDead())
            {
                if (((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) || (isPlayer() && target.isDead()))
                {
                    getAI().setIntention(Intention.ACTIVE);
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
                
                if (checkTransformed(transform -> !transform.canAttack()))
                {
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
            }
            
            final Player player = asPlayer();
            if (player != null)
            {
                if (player.inObserverMode())
                {
                    sendPacket(SystemMessageId.YOU_CANNOT_USE_THIS_FUNCTION_IN_THE_SPECTATOR_MODE);
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
                else if (player.isSiegeFriend(target))
                {
                    sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE);
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
                // Checking if target has moved to peace zone
                else if (target.isInsidePeaceZone(this))
                {
                    getAI().setIntention(Intention.ACTIVE);
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
                // Events.
                else if (player.isOnEvent() && !player.isOnSoloEvent() && target.isPlayable() && (player.getTeam() == target.asPlayer().getTeam()))
                {
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
            }
            else if (isInsidePeaceZone(this, target))
            {
                getAI().setIntention(Intention.ACTIVE);
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }
            
            stopEffectsOnAction();
            
            // GeoData Los Check here (or dz > 1000)
            if (!GeoEngine.getInstance().canSeeTarget(this, target))
            {
                sendPacket(SystemMessageId.CANNOT_SEE_TARGET);
                getAI().setIntention(Intention.ACTIVE);
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }
            
            // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand)
            final Weapon weaponItem = getActiveWeaponItem();
            final WeaponType weaponType = getAttackType();
            
            // BOW and CROSSBOW checks
            if (weaponItem != null)
            {
                if (!weaponItem.isAttackWeapon() && !isGM())
                {
                    if (weaponItem.getItemType() == WeaponType.FISHINGROD)
                    {
                        sendPacket(SystemMessageId.YOU_CANNOT_ATTACK_WHILE_FISHING);
                    }
                    else
                    {
                        sendPacket(SystemMessageId.YOU_CANNOT_ATTACK_WITH_THIS_WEAPON);
                    }
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
                
                // Ranged weapon checks.
                if (weaponItem.getItemType().isRanged())
                {
                    // Check if bow delay is still active.
                    if (_disableRangedAttackEndTime > System.nanoTime())
                    {
                        if (isPlayer())
                        {
                            ThreadPool.schedule(new NotifyAITask(this, Action.READY_TO_ACT), 300);
                            sendPacket(ActionFailed.STATIC_PACKET);
                        }
                        return;
                    }
                    
                    // Check for MP
                    if (isPlayer())
                    {
                        // Checking if target has moved to peace zone - only for player-bow attacks at the moment
                        // Other melee is checked in movement code and for offensive spells a check is done every time
                        if (target.isInsidePeaceZone(this))
                        {
                            getAI().setIntention(Intention.ACTIVE);
                            sendPacket(SystemMessageId.YOU_CANNOT_ATTACK_IN_A_PEACEFUL_ZONE);
                            sendPacket(ActionFailed.STATIC_PACKET);
                            return;
                        }
                        
                        // Check if player has enough MP to shoot.
                        int mpConsume = weaponItem.getMpConsume();
                        if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance()))
                        {
                            mpConsume = weaponItem.getReducedMpConsume();
                        }
                        mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume;
                        if (_status.getCurrentMp() < mpConsume)
                        {
                            // If Player doesn't have enough MP, stop the attack
                            ThreadPool.schedule(new NotifyAITask(this, Action.READY_TO_ACT), 1000);
                            sendPacket(SystemMessageId.NOT_ENOUGH_MP);
                            sendPacket(ActionFailed.STATIC_PACKET);
                            return;
                        }
                        
                        // If Player have enough MP, the bow consumes it
                        if (mpConsume > 0)
                        {
                            _status.reduceMp(mpConsume);
                        }
                    }
                }
            }
            
            // Mobius: Do not move when attack is launched.
            if (isMoving())
            {
                stopMove(getLocation());
            }
            
            final WeaponType attackType = getAttackType();
            final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == ItemTemplate.SLOT_LR_HAND);
            final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd());
            final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false);
            final long currentTime = System.nanoTime();
            _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(timeAtk);
            // Precaution. It has happened in the past. Probably impossible to happen now, but will not risk it.
            if (_attackEndTime < currentTime)
            {
                _attackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(Integer.MAX_VALUE);
            }
            
            // Make sure that char is facing selected target
            // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target)));
            setHeading(LocationUtil.calculateHeadingFrom(this, target));
            
            // Always try to charge soulshots.
            if (!isChargedShot(ShotType.SOULSHOTS) && !isChargedShot(ShotType.BLESSED_SOULSHOTS))
            {
                rechargeShots(true, false, false);
            }
            
            // Get the Attack Reuse Delay of the Weapon
            final Attack attack = generateAttackTargetData(target, weaponItem, attackType);
            boolean crossbow = false;
            switch (attackType)
            {
                case CROSSBOW:
                case TWOHANDCROSSBOW:
                {
                    crossbow = true;
                    // fallthrough
                }
                case BOW:
                {
                    // Old method.
                    final int reuse = Formulas.calculateReuseTime(this, weaponItem);
                    // Try to do what is expected by having more attack speed.
                    // final int reuse = (int) (Formulas.calculateReuseTime(this, weaponItem) / (Math.max(1, _stat.getAttackSpeedMultiplier() - 1)));
                    
                    // Consume ammunition.
                    final Inventory inventory = getInventory();
                    if (inventory != null)
                    {
                        inventory.reduceAmmunitionCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW);
                    }
                    
                    // Check if the Creature is a Player
                    if (isPlayer())
                    {
                        if (crossbow)
                        {
                            sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE);
                        }
                        
                        sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse));
                    }
                    
                    // Calculate and set the disable delay of the bow in function of the Attack Speed
                    _disableRangedAttackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(reuse);
                    // Precaution. It happened in the past for _attackEndTime. Will not risk it.
                    if (_disableRangedAttackEndTime < currentTime)
                    {
                        _disableRangedAttackEndTime = currentTime + TimeUnit.MILLISECONDS.toNanos(Integer.MAX_VALUE);
                    }
                    CreatureAttackTaskManager.getInstance().onHitTimeNotDual(this, weaponItem, attack, timeToHit, timeAtk);
                    break;
                }
                case FIST:
                {
                    if (!isPlayer())
                    {
                        CreatureAttackTaskManager.getInstance().onHitTimeNotDual(this, weaponItem, attack, timeToHit, timeAtk);
                        break;
                    }
                    // fallthrough
                }
                case DUAL:
                case DUALFIST:
                case DUALBLUNT:
                case DUALDAGGER:
                {
                    final int delayForSecondAttack = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit;
                    CreatureAttackTaskManager.getInstance().onFirstHitTimeForDual(this, weaponItem, attack, timeToHit, timeAtk, delayForSecondAttack);
                    break;
                }
                default:
                {
                    CreatureAttackTaskManager.getInstance().onHitTimeNotDual(this, weaponItem, attack, timeToHit, timeAtk);
                    break;
                }
            }
            
            // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack
            // to the Creature AND to all Player in the _KnownPlayers of the Creature
            if (attack.hasHits())
            {
                broadcastPacket(attack);
            }
            
            // Flag the attacker if it's a Player outside a PvP area
            if ((player != null) && !player.isInsideZone(ZoneId.PVP) && (player != target)) // Prevent players from flagging in PvP Zones.
            {
                AttackStanceTaskManager.getInstance().addAttackStanceTask(player);
                player.updatePvPStatus(target);
            }
            
            if (isFakePlayer() && !Config.FAKE_PLAYER_AUTO_ATTACKABLE && (target.isPlayable() || target.isFakePlayer()))
            {
                final Npc npc = asNpc();
                if (!npc.isScriptValue(1))
                {
                    npc.setScriptValue(1); // in combat
                    broadcastInfo(); // update flag status
                    QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK", npc, null);
                }
            }
        }
        finally
        {
            _attackLock.unlockWrite(stamp);
        }
    }
    
    private Attack generateAttackTargetData(Creature target, Weapon weapon, WeaponType weaponType)
    {
        final boolean isDual = (WeaponType.DUAL == weaponType) || (WeaponType.DUALBLUNT == weaponType) || (WeaponType.DUALDAGGER == weaponType) || (WeaponType.DUALFIST == weaponType);
        final Attack attack = new Attack(this, target);
        boolean shotConsumed = false;
        
        // Calculate the main target hit.
        Hit hit = generateHit(target, weapon, shotConsumed, isDual);
        attack.addHit(hit);
        shotConsumed = hit.isShotUsed();
        
        // Second hit for the dual attack.
        if (isDual)
        {
            hit = generateHit(target, weapon, shotConsumed, isDual);
            attack.addHit(hit);
            shotConsumed = hit.isShotUsed();
        }
        
        // H5 Changes: without Polearm Mastery (skill 216) max simultaneous attacks is 3 (1 by default + 2 in skill 3599).
        int attackCountMax = (int) _stat.getValue(Stat.ATTACK_COUNT_MAX, 1);
        if ((attackCountMax > 1) && (_stat.getValue(Stat.PHYSICAL_POLEARM_TARGET_SINGLE, 0) <= 0))
        {
            final double headingAngle = LocationUtil.convertHeadingToDegree(getHeading());
            final int maxRadius = _stat.getPhysicalAttackRadius();
            final int physicalAttackAngle = _stat.getPhysicalAttackAngle();
            for (Creature obj : World.getInstance().getVisibleObjectsInRange(this, Creature.class, maxRadius))
            {
                // Skip main target.
                if (obj == target)
                {
                    continue;
                }
                
                // Skip dead or fake dead target.
                if (obj.isAlikeDead())
                {
                    continue;
                }
                
                // Check if target is auto attackable.
                if (!obj.isAutoAttackable(this))
                {
                    continue;
                }
                
                // Check if target is within attack angle.
                if (Math.abs(calculateDirectionTo(obj) - headingAngle) > physicalAttackAngle)
                {
                    continue;
                }
                
                // Launch a simple attack against the additional target.
                hit = generateHit(obj, weapon, shotConsumed, false);
                attack.addHit(hit);
                shotConsumed = hit.isShotUsed();
                if (--attackCountMax <= 0)
                {
                    break;
                }
            }
        }
        
        return attack;
    }
    
    private Hit generateHit(Creature target, Weapon weapon, boolean shotConsumedValue, boolean halfDamage)
    {
        int damage = 0;
        byte shld = 0;
        boolean crit = false;
        boolean shotConsumed = shotConsumedValue;
        boolean shotBlessed = false;
        final boolean miss = Formulas.calcHitMiss(this, target);
        if (!shotConsumed)
        {
            if (isChargedShot(ShotType.BLESSED_SOULSHOTS))
            {
                shotBlessed = true;
                shotConsumed = !miss && unchargeShot(ShotType.BLESSED_SOULSHOTS);
            }
            else
            {
                shotConsumed = !miss && unchargeShot(ShotType.SOULSHOTS);
            }
        }
        
        final int ssGrade = (shotConsumed && (weapon != null)) ? weapon.getItemGrade().ordinal() : 0;
        
        // Check if hit isn't missed
        if (!miss)
        {
            shld = Formulas.calcShldUse(this, target);
            crit = Formulas.calcCrit(_stat.getCriticalHit(), this, target, null);
            damage = (int) Formulas.calcAutoAttackDamage(this, target, shld, crit, shotConsumed, shotBlessed);
            if (halfDamage)
            {
                damage /= 2;
            }
        }
        
        return new Hit(target, damage, miss, crit, shld, shotConsumed, ssGrade);
    }
    
    public void doCast(Skill skill)
    {
        doCast(skill, null, false, false);
    }
    
    /**
     * Manage the casting task (casting and interrupt time, re-use delay...) and display the casting bar and animation on client.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Verify the possibility of the the cast : skill is a spell, caster isn't muted...</li>
     * <li>Get the list of all targets (ex : area effects) and define the Creature targeted (its stats will be used in calculation)</li>
     * <li>Calculate the casting time (base + modifier of MAtkSpd), interrupt time and re-use delay</li>
     * <li>Send a Server->Client packet MagicSkillUser (to display casting animation), a packet SetupGauge (to display casting bar) and a system message</li>
     * <li>Disable all skills during the casting time (create a task EnableAllSkills)</li>
     * <li>Disable the skill during the re-use delay (create a task EnableSkill)</li>
     * <li>Create a task MagicUseTask (that will call method onMagicUseTimer) to launch the Magic Skill at the end of the casting time</li>
     * </ul>
     * @param skill The Skill to use
     * @param item the referenced item of this skill cast
     * @param ctrlPressed if the player has pressed ctrl key during casting, aka force use.
     * @param shiftPressed if the player has pressed shift key during casting, aka do not move.
     */
    public synchronized void doCast(Skill skill, Item item, boolean ctrlPressed, boolean shiftPressed)
    {
        // Attackables cannot cast while moving.
        if (isAttackable() && isMoving())
        {
            return;
        }
        
        // Get proper casting type.
        SkillCastingType castingType = SkillCastingType.NORMAL;
        if (skill.canDoubleCast() && isAffected(EffectFlag.DOUBLE_CAST) && isCastingNow(castingType))
        {
            castingType = SkillCastingType.NORMAL_SECOND;
        }
        
        // Try casting the skill
        final SkillCaster skillCaster = SkillCaster.castSkill(this, _target, skill, item, castingType, ctrlPressed, shiftPressed);
        if ((skillCaster == null) && isPlayer())
        {
            // Skill casting failed, notify player.
            sendPacket(ActionFailed.get(castingType));
            getAI().setIntention(Intention.ACTIVE);
        }
        
        // Players which are 9 levels above a Raid Boss and cast a skill nearby, are silenced with the Raid Curse skill.
        if (!Config.RAID_DISABLE_CURSE && isPlayer())
        {
            World.getInstance().forEachVisibleObjectInRange(this, Attackable.class, Config.ALT_PARTY_RANGE, attackable ->
            {
                if (attackable.giveRaidCurse() && attackable.isInCombat() && ((getLevel() - attackable.getLevel()) > 8))
                {
                    final CommonSkill curse = skill.isBad() ? CommonSkill.RAID_CURSE2 : CommonSkill.RAID_CURSE;
                    curse.getSkill().applyEffects(attackable, this);
                }
            });
        }
    }
    
    /**
     * Gets the item reuse time stamps map.
     * @return the item reuse time stamps map
     */
    public Map<Integer, TimeStamp> getItemReuseTimeStamps()
    {
        return _reuseTimeStampsItems;
    }
    
    /**
     * Adds a item reuse time stamp.
     * @param item the item
     * @param reuse the reuse
     */
    public void addTimeStampItem(Item item, long reuse)
    {
        addTimeStampItem(item, reuse, -1);
    }
    
    /**
     * Adds a item reuse time stamp.<br>
     * Used for restoring purposes.
     * @param item the item
     * @param reuse the reuse
     * @param systime the system time
     */
    public void addTimeStampItem(Item item, long reuse, long systime)
    {
        _reuseTimeStampsItems.put(item.getObjectId(), new TimeStamp(item, reuse, systime));
    }
    
    /**
     * Gets the item remaining reuse time for a given item object ID.
     * @param itemObjId the item object ID
     * @return if the item has a reuse time stamp, the remaining time, otherwise -1
     */
    public long getItemRemainingReuseTime(int itemObjId)
    {
        final TimeStamp reuseStamp = _reuseTimeStampsItems.get(itemObjId);
        return reuseStamp != null ? reuseStamp.getRemaining() : -1;
    }
    
    /**
     * Gets the item remaining reuse time for a given shared reuse item group.
     * @param group the shared reuse item group
     * @return if the shared reuse item group has a reuse time stamp, the remaining time, otherwise -1
     */
    public long getReuseDelayOnGroup(int group)
    {
        if ((group > 0) && !_reuseTimeStampsItems.isEmpty())
        {
            final long currentTime = System.currentTimeMillis();
            for (TimeStamp ts : _reuseTimeStampsItems.values())
            {
                if (ts.getSharedReuseGroup() == group)
                {
                    final long stamp = ts.getStamp();
                    if (currentTime < stamp)
                    {
                        return Math.max(stamp - currentTime, 0);
                    }
                }
            }
        }
        return -1;
    }
    
    /**
     * Gets the skill reuse time stamps map.
     * @return the skill reuse time stamps map
     */
    public Map<Long, TimeStamp> getSkillReuseTimeStamps()
    {
        return _reuseTimeStampsSkills;
    }
    
    /**
     * Adds the skill reuse time stamp.
     * @param skill the skill
     * @param reuse the delay
     */
    public void addTimeStamp(Skill skill, long reuse)
    {
        addTimeStamp(skill, reuse, -1);
    }
    
    /**
     * Adds the skill reuse time stamp.<br>
     * Used for restoring purposes.
     * @param skill the skill
     * @param reuse the reuse
     * @param systime the system time
     */
    public void addTimeStamp(Skill skill, long reuse, long systime)
    {
        _reuseTimeStampsSkills.put(skill.getReuseHashCode(), new TimeStamp(skill, reuse, systime));
    }
    
    /**
     * Removes a skill reuse time stamp.
     * @param skill the skill to remove
     */
    public void removeTimeStamp(Skill skill)
    {
        _reuseTimeStampsSkills.remove(skill.getReuseHashCode());
    }
    
    /**
     * Removes all skill reuse time stamps.
     */
    public void resetTimeStamps()
    {
        _reuseTimeStampsSkills.clear();
    }
    
    /**
     * Gets the skill remaining reuse time for a given skill hash code.
     * @param hashCode the skill hash code
     * @return if the skill has a reuse time stamp, the remaining time, otherwise -1
     */
    public long getSkillRemainingReuseTime(long hashCode)
    {
        final TimeStamp reuseStamp = _reuseTimeStampsSkills.get(hashCode);
        return reuseStamp != null ? reuseStamp.getRemaining() : -1;
    }
    
    /**
     * Verifies if the skill is under reuse time.
     * @param hashCode the skill hash code
     * @return {@code true} if the skill is under reuse time, {@code false} otherwise
     */
    public boolean hasSkillReuse(long hashCode)
    {
        final TimeStamp reuseStamp = _reuseTimeStampsSkills.get(hashCode);
        return (reuseStamp != null) && reuseStamp.hasNotPassed();
    }
    
    /**
     * Gets the skill reuse time stamp.
     * @param hashCode the skill hash code
     * @return if the skill has a reuse time stamp, the skill reuse time stamp, otherwise {@code null}
     */
    public synchronized TimeStamp getSkillReuseTimeStamp(long hashCode)
    {
        return _reuseTimeStampsSkills.get(hashCode);
    }
    
    /**
     * Gets the disabled skills map.
     * @return the disabled skills map
     */
    public Map<Long, Long> getDisabledSkills()
    {
        return _disabledSkills;
    }
    
    /**
     * Enables a skill.
     * @param skill the skill to enable
     */
    public void enableSkill(Skill skill)
    {
        if (skill == null)
        {
            return;
        }
        _disabledSkills.remove(skill.getReuseHashCode());
    }
    
    /**
     * Disables a skill for a given time.<br>
     * If delay is lesser or equal than zero, skill will be disabled "forever".
     * @param skill the skill to disable
     * @param delay delay in milliseconds
     */
    public void disableSkill(Skill skill, long delay)
    {
        if (skill == null)
        {
            return;
        }
        _disabledSkills.put(skill.getReuseHashCode(), delay > 0 ? System.currentTimeMillis() + delay : Long.MAX_VALUE);
    }
    
    /**
     * Removes all the disabled skills.
     */
    public void resetDisabledSkills()
    {
        _disabledSkills.clear();
    }
    
    /**
     * Verifies if the skill is disabled.
     * @param skill the skill
     * @return {@code true} if the skill is disabled, {@code false} otherwise
     */
    public boolean isSkillDisabled(Skill skill)
    {
        if (skill == null)
        {
            return false;
        }
        
        if (_allSkillsDisabled || (!skill.canCastWhileDisabled() && isAllSkillsDisabled()))
        {
            return true;
        }
        
        if (isAffected(EffectFlag.CONDITIONAL_BLOCK_ACTIONS) && !isBlockedActionsAllowedSkill(skill))
        {
            return true;
        }
        
        final long hashCode = skill.getReuseHashCode();
        if (hasSkillReuse(hashCode))
        {
            return true;
        }
        
        if (_disabledSkills.isEmpty())
        {
            return false;
        }
        final Long stamp = _disabledSkills.get(hashCode);
        if (stamp == null)
        {
            return false;
        }
        if (stamp < System.currentTimeMillis())
        {
            _disabledSkills.remove(hashCode);
            return false;
        }
        
        return true;
    }
    
    /**
     * Disables all skills.
     */
    public void disableAllSkills()
    {
        _allSkillsDisabled = true;
    }
    
    /**
     * Enables all skills, except those under reuse time or previously disabled.
     */
    public void enableAllSkills()
    {
        _allSkillsDisabled = false;
    }
    
    /**
     * Kill the Creature.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Set target to null and cancel Attack or Cast</li>
     * <li>Stop movement</li>
     * <li>Stop HP/MP/CP Regeneration task</li>
     * <li>Stop all active skills effects in progress on the Creature</li>
     * <li>Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform</li>
     * <li>Notify Creature AI</li>
     * </ul>
     * @param killer The Creature who killed it
     * @return false if the creature hasn't been killed.
     */
    public boolean doDie(Creature killer)
    {
        // killing is only possible one time
        synchronized (this)
        {
            if (_isDead)
            {
                return false;
            }
            
            // now reset currentHp to zero
            setCurrentHp(0);
            setDead(true);
        }
        
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DEATH, this))
        {
            EventDispatcher.getInstance().notifyEvent(new OnCreatureDeath(killer, this), this);
        }
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_KILLED, killer))
        {
            EventDispatcher.getInstance().notifyEvent(new OnCreatureKilled(killer, this), killer);
        }
        
        abortAttack();
        abortCast();
        
        // Calculate rewards for main damage dealer.
        final Creature mainDamageDealer = isMonster() ? asMonster().getMainDamageDealer() : null;
        calculateRewards(mainDamageDealer != null ? mainDamageDealer : killer);
        
        // Set target to null and cancel Attack or Cast
        setTarget(null);
        
        // Stop movement
        stopMove(null);
        
        // Stop HP/MP/CP Regeneration task
        _status.stopHpMpRegeneration();
        
        if (isAttackable())
        {
            final Spawn spawn = asNpc().getSpawn();
            if ((spawn != null) && spawn.isRespawnEnabled())
            {
                stopAllEffects();
            }
            else
            {
                _effectList.stopAllEffectsWithoutExclusions(true, true);
            }
            
            // Clan help range aggro on kill.
            if ((killer != null) && killer.isPlayable() && !killer.asPlayer().isGM())
            {
                final NpcTemplate template = asAttackable().getTemplate();
                final Set<Integer> clans = template.getClans();
                if ((clans != null) && !clans.isEmpty())
                {
                    World.getInstance().forEachVisibleObjectInRange(this, Attackable.class, template.getClanHelpRange(), called ->
                    {
                        // Don't call dead npcs, npcs without ai or npcs which are too far away.
                        if (called.isDead() || !called.hasAI() || (Math.abs(killer.getZ() - called.getZ()) > 600))
                        {
                            return;
                        }
                        // Don't call npcs who are already doing some action (e.g. attacking, casting).
                        if ((called.getAI().getIntention() != Intention.IDLE) && (called.getAI().getIntention() != Intention.ACTIVE))
                        {
                            return;
                        }
                        // Don't call npcs who aren't in the same clan.
                        if (!template.isClan(called.getTemplate().getClans()))
                        {
                            return;
                        }
                        
                        // By default, when a faction member calls for help, attack the caller's attacker.
                        called.getAI().notifyAction(Action.AGGRESSION, killer, 1);
                        
                        if (EventDispatcher.getInstance().hasListener(EventType.ON_ATTACKABLE_FACTION_CALL, called))
                        {
                            EventDispatcher.getInstance().notifyEventAsync(new OnAttackableFactionCall(called, asAttackable(), killer.asPlayer(), killer.isSummon()), called);
                        }
                    });
                }
            }
        }
        else
        {
            stopAllEffectsExceptThoseThatLastThroughDeath();
        }
        
        // Send the Server->Client packet StatusUpdate with current HP and MP to all other Player to inform
        broadcastStatusUpdate();
        
        // Notify Creature AI
        if (hasAI())
        {
            getAI().notifyAction(Action.DEATH);
        }
        
        ZoneManager.getInstance().getRegion(this).onDeath(this);
        
        getAttackByList().clear();
        
        if (isChannelized())
        {
            getSkillChannelized().abortChannelization();
        }
        
        // Custom boss announcements configuration.
        if (this instanceof GrandBoss)
        {
            if (Config.GRANDBOSS_DEFEAT_ANNOUNCEMENTS && (!isInInstance() || Config.GRANDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion())
            {
                final String name = NpcData.getInstance().getTemplate(getId()).getName();
                if ((name != null) && !Config.RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS.contains(getId()))
                {
                    Broadcast.toAllOnlinePlayers(name + " был повержен!");
                    Broadcast.toAllOnlinePlayersOnScreen(name + " был повержен!");
                }
            }
        }
        else if (isRaid() && Config.RAIDBOSS_DEFEAT_ANNOUNCEMENTS && (!isInInstance() || Config.RAIDBOSS_INSTANCE_ANNOUNCEMENTS) && !isMinion() && !isRaidMinion())
        {
            final String name = NpcData.getInstance().getTemplate(getId()).getName();
            if ((name != null) && !Config.RAIDBOSSES_EXCLUDED_FROM_DEFEAT_ANNOUNCEMENTS.contains(getId()))
            {
                Broadcast.toAllOnlinePlayers(name + " был повержен!");
                Broadcast.toAllOnlinePlayersOnScreen(name + " был повержен!");
            }
        }
        
        return true;
    }
    
    @Override
    public boolean decayMe()
    {
        if (hasAI())
        {
            if (isAttackable())
            {
                getAttackByList().clear();
                asAttackable().clearAggroList();
                getAI().setIntention(Intention.IDLE);
            }
            getAI().stopAITask();
        }
        return super.decayMe();
    }
    
    public boolean deleteMe()
    {
        if (hasAI())
        {
            getAI().stopAITask();
        }
        
        // Removes itself from the summoned list.
        if ((_summoner != null))
        {
            _summoner.removeSummonedNpc(getObjectId());
        }
        
        // Remove all active, passive and option effects, do not broadcast changes.
        _effectList.stopAllEffectsWithoutExclusions(false, false);
        
        // Forget all seen creatures.
        if (_seenCreatures != null)
        {
            CreatureSeeTaskManager.getInstance().remove(this);
            _seenCreatures.clear();
        }
        
        // Cancel the BuffFinishTask related to this creature.
        _buffFinishTask.stop();
        
        // Set world region to null.
        setWorldRegion(null);
        
        return true;
    }
    
    protected void calculateRewards(Creature killer)
    {
    }
    
    /** Sets HP, MP and CP and revives the Creature. */
    public void doRevive()
    {
        if (!_isDead)
        {
            return;
        }
        if (!_isTeleporting)
        {
            setIsPendingRevive(false);
            setDead(false);
            
            if ((Config.RESPAWN_RESTORE_CP > 0) && (_status.getCurrentCp() < (_stat.getMaxCp() * Config.RESPAWN_RESTORE_CP)))
            {
                _status.setCurrentCp(_stat.getMaxCp() * Config.RESPAWN_RESTORE_CP);
            }
            if ((Config.RESPAWN_RESTORE_HP > 0) && (_status.getCurrentHp() < (_stat.getMaxHp() * Config.RESPAWN_RESTORE_HP)))
            {
                _status.setCurrentHp(_stat.getMaxHp() * Config.RESPAWN_RESTORE_HP);
            }
            if ((Config.RESPAWN_RESTORE_MP > 0) && (_status.getCurrentMp() < (_stat.getMaxMp() * Config.RESPAWN_RESTORE_MP)))
            {
                _status.setCurrentMp(_stat.getMaxMp() * Config.RESPAWN_RESTORE_MP);
            }
            
            // Start broadcast status
            broadcastPacket(new Revive(this));
            ZoneManager.getInstance().getRegion(this).onRevive(this);
        }
        else
        {
            setIsPendingRevive(true);
        }
    }
    
    /**
     * Revives the Creature using skill.
     * @param revivePower
     */
    public void doRevive(double revivePower)
    {
        doRevive();
    }
    
    /**
     * Gets this creature's AI.
     * @return the AI
     */
    public CreatureAI getAI()
    {
        CreatureAI ai = _ai;
        if (ai == null)
        {
            synchronized (this)
            {
                ai = _ai;
                if (ai == null)
                {
                    _ai = ai = initAI();
                }
            }
        }
        return ai;
    }
    
    /**
     * Initialize this creature's AI.<br>
     * OOP approach to be overridden in child classes.
     * @return the new AI
     */
    protected CreatureAI initAI()
    {
        return new CreatureAI(this);
    }
    
    public void detachAI()
    {
        if (isWalker())
        {
            return;
        }
        setAI(null);
    }
    
    public void setAI(CreatureAI newAI)
    {
        final CreatureAI oldAI = _ai;
        if ((oldAI != null) && (oldAI != newAI) && (oldAI instanceof AttackableAI))
        {
            oldAI.stopAITask();
        }
        _ai = newAI;
    }
    
    /**
     * Verifies if this creature has an AI,
     * @return {@code true} if this creature has an AI, {@code false} otherwise
     */
    public boolean hasAI()
    {
        return _ai != null;
    }
    
    /**
     * @return True if the Creature is RaidBoss or his minion.
     */
    public boolean isRaid()
    {
        return false;
    }
    
    /**
     * @return True if the Creature is minion.
     */
    public boolean isMinion()
    {
        return false;
    }
    
    /**
     * @return True if the Creature is minion of RaidBoss.
     */
    public boolean isRaidMinion()
    {
        return false;
    }
    
    /**
     * @return a list of Creature that attacked.
     */
    public Set<WeakReference<Creature>> getAttackByList()
    {
        return _attackByList;
    }
    
    public boolean isControlBlocked()
    {
        return isAffected(EffectFlag.BLOCK_CONTROL);
    }
    
    /**
     * @return True if the Creature can't use its skills (ex : stun, sleep...).
     */
    public boolean isAllSkillsDisabled()
    {
        return _allSkillsDisabled || hasBlockActions();
    }
    
    /**
     * @return True if the Creature can't attack (attackEndTime, attackMute, fake death, stun, sleep, paralyze).
     */
    public boolean isAttackDisabled()
    {
        return isAttackingNow() || isDisabled();
    }
    
    /**
     * @return True if the Creature is disabled (attackMute, fake death, stun, sleep, paralyze).
     */
    public boolean isDisabled()
    {
        return _disabledAI || isAlikeDead() || isPhysicalAttackMuted() || hasBlockActions();
    }
    
    public boolean isConfused()
    {
        return isAffected(EffectFlag.CONFUSED);
    }
    
    /**
     * @return True if the Creature is dead or use fake death.
     */
    public boolean isAlikeDead()
    {
        return _isDead;
    }
    
    /**
     * @return True if the Creature is dead.
     */
    public boolean isDead()
    {
        return _isDead;
    }
    
    public void setDead(boolean value)
    {
        _isDead = value;
    }
    
    public boolean isImmobilized()
    {
        return _isImmobilized;
    }
    
    public void setImmobilized(boolean value)
    {
        _isImmobilized = value;
    }
    
    public boolean isMuted()
    {
        return isAffected(EffectFlag.MUTED);
    }
    
    public boolean isPhysicalMuted()
    {
        return isAffected(EffectFlag.PSYCHICAL_MUTED);
    }
    
    public boolean isPhysicalAttackMuted()
    {
        return isAffected(EffectFlag.PSYCHICAL_ATTACK_MUTED);
    }
    
    /**
     * @return True if the Creature can't move (stun, root, sleep, overload, paralyzed).
     */
    public boolean isMovementDisabled()
    {
        // check for isTeleporting to prevent teleport cheating (if appear packet not received)
        return hasBlockActions() || isRooted() || _isOverloaded || _isImmobilized || isAlikeDead() || _isTeleporting;
    }
    
    public boolean isOverloaded()
    {
        return _isOverloaded;
    }
    
    /**
     * Set the overloaded status of the Creature is overloaded (if True, the Player can't take more item).
     * @param value
     */
    public void setOverloaded(boolean value)
    {
        _isOverloaded = value;
    }
    
    public boolean isPendingRevive()
    {
        return _isDead && _isPendingRevive;
    }
    
    public void setIsPendingRevive(boolean value)
    {
        _isPendingRevive = value;
    }
    
    public boolean isDisarmed()
    {
        return isAffected(EffectFlag.DISARMED);
    }
    
    /**
     * @return the summon
     */
    public Summon getPet()
    {
        return null;
    }
    
    /**
     * @return the summon
     */
    public Map<Integer, Summon> getServitors()
    {
        return Collections.emptyMap();
    }
    
    public Summon getServitor(int objectId)
    {
        return null;
    }
    
    /**
     * @return {@code true} if the character has a summon, {@code false} otherwise
     */
    public boolean hasSummon()
    {
        return (getPet() != null) || !getServitors().isEmpty();
    }
    
    /**
     * @return {@code true} if the character has a pet, {@code false} otherwise
     */
    public boolean hasPet()
    {
        return getPet() != null;
    }
    
    public boolean hasServitor(int objectId)
    {
        return getServitors().containsKey(objectId);
    }
    
    /**
     * @return {@code true} if the character has a servitor, {@code false} otherwise
     */
    public boolean hasServitors()
    {
        return !getServitors().isEmpty();
    }
    
    public void removeServitor(int objectId)
    {
        getServitors().remove(objectId);
    }
    
    public boolean isRooted()
    {
        return isAffected(EffectFlag.ROOTED);
    }
    
    /**
     * @return True if the Creature is running.
     */
    public boolean isRunning()
    {
        return _isRunning;
    }
    
    private void setRunning(boolean value)
    {
        if (_isRunning == value)
        {
            return;
        }
        
        _isRunning = value;
        if (_stat.getRunSpeed() != 0)
        {
            broadcastPacket(new ChangeMoveType(this));
        }
        if (isPlayer())
        {
            asPlayer().broadcastUserInfo();
        }
        else if (isSummon())
        {
            broadcastStatusUpdate();
        }
        else if (isNpc())
        {
            World.getInstance().forEachVisibleObject(this, Player.class, player ->
            {
                if (!isVisibleFor(player))
                {
                    return;
                }
                
                if (isFakePlayer())
                {
                    player.sendPacket(new FakePlayerInfo(asNpc()));
                }
                else if (_stat.getRunSpeed() == 0)
                {
                    player.sendPacket(new ServerObjectInfo(asNpc(), player));
                }
                else
                {
                    player.sendPacket(new NpcInfo(asNpc()));
                }
            });
        }
    }
    
    /** Set the Creature movement type to run and send Server->Client packet ChangeMoveType to all others Player. */
    public void setRunning()
    {
        setRunning(true);
    }
    
    public boolean hasBlockActions()
    {
        return _blockActions || isAffected(EffectFlag.BLOCK_ACTIONS) || isAffected(EffectFlag.CONDITIONAL_BLOCK_ACTIONS);
    }
    
    public void setBlockActions(boolean blockActions)
    {
        _blockActions = blockActions;
    }
    
    public boolean isBetrayed()
    {
        return isAffected(EffectFlag.BETRAYED);
    }
    
    public boolean isTeleporting()
    {
        return _isTeleporting;
    }
    
    public void setTeleporting(boolean value)
    {
        _isTeleporting = value;
    }
    
    public void setInvul(boolean value)
    {
        _isInvul = value;
    }
    
    @Override
    public boolean isInvul()
    {
        return _isInvul || _isTeleporting;
    }
    
    public void setUndying(boolean undying)
    {
        _isUndying = undying;
    }
    
    public boolean isUndying()
    {
        return _isUndying || isInvul() || isAffected(EffectFlag.IGNORE_DEATH) || isInsideZone(ZoneId.UNDYING);
    }
    
    public boolean isHpBlocked()
    {
        return isInvul() || isAffected(EffectFlag.HP_BLOCK);
    }
    
    public boolean isMpBlocked()
    {
        return isInvul() || isAffected(EffectFlag.MP_BLOCK);
    }
    
    public boolean isBuffBlocked()
    {
        return isAffected(EffectFlag.BUFF_BLOCK);
    }
    
    public boolean isDebuffBlocked()
    {
        return isInvul() || isAffected(EffectFlag.DEBUFF_BLOCK);
    }
    
    public boolean isUndead()
    {
        return false;
    }
    
    public boolean isResurrectionBlocked()
    {
        return isAffected(EffectFlag.BLOCK_RESURRECTION);
    }
    
    public boolean isFlying()
    {
        return _isFlying;
    }
    
    public void setFlying(boolean mode)
    {
        _isFlying = mode;
    }
    
    public CreatureStat getStat()
    {
        return _stat;
    }
    
    /**
     * Initializes the CharStat class of the WorldObject, is overwritten in classes that require a different CharStat Type.<br>
     * Removes the need for instanceof checks.
     */
    public void initCharStat()
    {
        _stat = new CreatureStat(this);
    }
    
    public void setStat(CreatureStat value)
    {
        _stat = value;
    }
    
    public CreatureStatus getStatus()
    {
        return _status;
    }
    
    /**
     * Initializes the CharStatus class of the WorldObject, is overwritten in classes that require a different CharStatus Type.<br>
     * Removes the need for instanceof checks.
     */
    public void initCharStatus()
    {
        _status = new CreatureStatus(this);
    }
    
    public void setStatus(CreatureStatus value)
    {
        _status = value;
    }
    
    public CreatureTemplate getTemplate()
    {
        return _template;
    }
    
    /**
     * Set the template of the Creature.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * Each Creature owns generic and static properties (ex : all Keltir have the same number of HP...).<br>
     * All of those properties are stored in a different template for each type of Creature.<br>
     * Each template is loaded once in the server cache memory (reduce memory use).<br>
     * When a new instance of Creature is spawned, server just create a link between the instance and the template This link is stored in <b>_template</b>.
     * @param template
     */
    protected void setTemplate(CreatureTemplate template)
    {
        _template = template;
    }
    
    /**
     * @return the Title of the Creature.
     */
    public String getTitle()
    {
        // Custom level titles
        if (isMonster() && (Config.SHOW_NPC_LEVEL || Config.SHOW_NPC_AGGRESSION))
        {
            String t1 = "";
            if (Config.SHOW_NPC_LEVEL)
            {
                t1 += "Lv " + getLevel();
            }
            String t2 = "";
            if (Config.SHOW_NPC_AGGRESSION)
            {
                if (!t1.isEmpty())
                {
                    t2 += " ";
                }
                final Monster monster = asMonster();
                if (monster.isAggressive())
                {
                    t2 += "[A]"; // Aggressive.
                }
                if ((monster.getTemplate().getClans() != null) && (monster.getTemplate().getClanHelpRange() > 0))
                {
                    t2 += "[G]"; // Group.
                }
            }
            t1 += t2;
            if ((_title != null) && !_title.isEmpty())
            {
                t1 += " " + _title;
            }
            return isChampion() ? Config.CHAMP_TITLE + " " + t1 : t1;
        }
        // Champion titles
        if (isChampion())
        {
            return Config.CHAMP_TITLE;
        }
        // Set trap title
        if (isTrap())
        {
            final Player owner = ((Trap) this).getOwner();
            if (owner != null)
            {
                _title = owner.getName();
            }
        }
        return _title != null ? _title : "";
    }
    
    /**
     * Set the Title of the Creature.
     * @param value
     */
    public void setTitle(String value)
    {
        if (value == null)
        {
            _title = "";
        }
        else
        {
            _title = isPlayer() && (value.length() > 21) ? value.substring(0, 20) : value;
        }
    }
    
    /**
     * Set the Creature movement type to walk and send Server->Client packet ChangeMoveType to all others Player.
     */
    public void setWalking()
    {
        setRunning(false);
    }
    
    /**
     * Active the abnormal effect Fake Death flag, notify the Creature AI and send Server->Client UserInfo/CharInfo packet.
     */
    public void startFakeDeath()
    {
        if (!isPlayer())
        {
            return;
        }
        
        // Aborts any attacks/casts if fake dead
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyAction(Action.FAKE_DEATH);
        broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_START_FAKEDEATH));
        
        // Remove target from those that have the untargetable creature on target.
        if (Config.FAKE_DEATH_UNTARGET)
        {
            World.getInstance().forEachVisibleObject(this, Creature.class, c ->
            {
                if (c.getTarget() == this)
                {
                    c.setTarget(null);
                }
            });
        }
    }
    
    public void startParalyze()
    {
        // Aborts any attacks/casts if paralyzed
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyAction(Action.BLOCKED);
    }
    
    /**
     * Stop all active skills effects in progress on the Creature.
     */
    public void stopAllEffects()
    {
        _effectList.stopAllEffects(true);
    }
    
    /**
     * Stops all effects, except those that last through death.
     */
    public void stopAllEffectsExceptThoseThatLastThroughDeath()
    {
        _effectList.stopAllEffectsExceptThoseThatLastThroughDeath();
    }
    
    /**
     * Stop and remove the effects corresponding to the skill ID.
     * @param type determines the system message that will be sent.
     * @param skillId the skill Id
     */
    public void stopSkillEffects(SkillFinishType type, int skillId)
    {
        _effectList.stopSkillEffects(type, skillId);
    }
    
    public void stopSkillEffects(Skill skill)
    {
        _effectList.stopSkillEffects(SkillFinishType.REMOVED, skill.getId());
    }
    
    public void stopEffects(EffectFlag effectFlag)
    {
        _effectList.stopEffects(effectFlag);
    }
    
    /**
     * Exits all buffs effects of the skills with "removedOnAnyAction" set.<br>
     * Called on any action except movement (attack, cast).
     */
    public void stopEffectsOnAction()
    {
        _effectList.stopEffectsOnAction();
    }
    
    /**
     * Exits all buffs effects of the skills with "removedOnDamage" set.<br>
     * Called on decreasing HP and mana burn.
     */
    public void stopEffectsOnDamage()
    {
        _effectList.stopEffectsOnDamage();
    }
    
    /**
     * Stop a specified/all Fake Death abnormal Effect.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Delete a specified/all (if effect=null) Fake Death abnormal Effect from Creature and update client magic icon</li>
     * <li>Set the abnormal effect flag _fake_death to False</li>
     * <li>Notify the Creature AI</li>
     * </ul>
     * @param removeEffects
     */
    public void stopFakeDeath(boolean removeEffects)
    {
        if (removeEffects)
        {
            stopEffects(EffectFlag.FAKE_DEATH);
        }
        
        // if this is a player instance, start the grace period for this character (grace from mobs only)!
        if (isPlayer())
        {
            asPlayer().setRecentFakeDeath(true);
        }
        
        broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_STOP_FAKEDEATH));
        // TODO: Temp hack: players see FD on ppl that are moving: Teleport to someone who uses FD - if he gets up he will fall down again for that client -
        // even tho he is actually standing... Probably bad info in CharInfo packet?
        broadcastPacket(new Revive(this));
    }
    
    /**
     * Stop all block actions (stun) effects.
     * @param removeEffects {@code true} removes all block actions effects, {@code false} only notifies AI to think.
     */
    public void stopStunning(boolean removeEffects)
    {
        if (removeEffects)
        {
            _effectList.stopEffects(AbnormalType.STUN);
        }
        
        if (!isPlayer())
        {
            getAI().notifyAction(Action.THINK);
        }
    }
    
    /**
     * Stop Effect: Transformation.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Remove Transformation Effect</li>
     * <li>Notify the Creature AI</li>
     * <li>Send Server->Client UserInfo/CharInfo packet</li>
     * </ul>
     * @param removeEffects
     */
    public void stopTransformation(boolean removeEffects)
    {
        if (removeEffects && !_effectList.stopEffects(AbnormalType.TRANSFORM))
        {
            _effectList.stopEffects(AbnormalType.CHANGEBODY);
        }
        
        if (_transform.isPresent())
        {
            untransform();
        }
        
        if (!isPlayer())
        {
            getAI().notifyAction(Action.THINK);
        }
        updateAbnormalVisualEffects();
    }
    
    /**
     * Updates the visual abnormal state of this character.
     */
    public void updateAbnormalVisualEffects()
    {
        // overridden
    }
    
    /**
     * Update active skills in progress (In Use and Not In Use because stacked) icons on client.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * All active skills effects in progress (In Use and Not In Use because stacked) are represented by an icon on the client.<br>
     * <font color=#FF0000><b><u>Caution</u>: This method ONLY UPDATE the client of the player and not clients of all players in the party.</b></font>
     */
    public void updateEffectIcons()
    {
        updateEffectIcons(false);
    }
    
    /**
     * Updates Effect Icons for this character(player/summon) and his party if any.
     * @param partyOnly
     */
    public void updateEffectIcons(boolean partyOnly)
    {
        // overridden
    }
    
    public boolean isAffectedBySkill(SkillHolder skill)
    {
        return isAffectedBySkill(skill.getSkillId());
    }
    
    public boolean isAffectedBySkill(int skillId)
    {
        return _effectList.isAffectedBySkill(skillId);
    }
    
    public int getAffectedSkillLevel(int skillId)
    {
        final BuffInfo info = _effectList.getBuffInfoBySkillId(skillId);
        return info == null ? 0 : info.getSkill().getLevel();
    }
    
    /**
     * This class groups all movement data.
     */
    public static class MoveData
    {
        // When we retrieve x/y/z we use GameTimeControl.getGameTicks()
        // If we are moving, but move timestamp==gameticks, we don't need to recalculate position.
        public int moveStartTime;
        public int moveTimestamp; // Last movement update.
        public int xDestination;
        public int yDestination;
        public int zDestination;
        public double xAccurate; // Otherwise there would be rounding errors.
        public double yAccurate;
        public double zAccurate;
        public int heading;
        
        public boolean disregardingGeodata;
        public int onGeodataPathIndex;
        public List<GeoLocation> geoPath;
        public int geoPathAccurateTx;
        public int geoPathAccurateTy;
        public int geoPathGtx;
        public int geoPathGty;
        
        public int lastBroadcastTime;
    }
    
    public void broadcastModifiedStats(Set<Stat> changed)
    {
        if (!isSpawned())
        {
            return;
        }
        
        if ((changed == null) || changed.isEmpty())
        {
            return;
        }
        
        // Don't broadcast modified stats on login.
        if (isPlayer() && !asPlayer().isOnline())
        {
            return;
        }
        
        synchronized (_broadcastModifiedStatChanges)
        {
            _broadcastModifiedStatChanges.addAll(changed);
        }
        if (_broadcastModifiedStatTask == null)
        {
            _broadcastModifiedStatTask = ThreadPool.schedule(() ->
            {
                final Set<Stat> currentChanges;
                synchronized (_broadcastModifiedStatChanges)
                {
                    if (_broadcastModifiedStatChanges.isEmpty())
                    {
                        return;
                    }
                    
                    currentChanges = EnumSet.copyOf(_broadcastModifiedStatChanges);
                    _broadcastModifiedStatChanges.clear();
                }
                
                // If this creature was previously moving, but now due to stat change can no longer move, broadcast StopMove packet.
                if (isMoving() && (getMoveSpeed() <= 0))
                {
                    stopMove(null);
                }
                
                if (isSummon())
                {
                    final Summon summon = asSummon();
                    if (summon.getOwner() != null)
                    {
                        summon.updateAndBroadcastStatus(1);
                    }
                }
                else if (isPlayer())
                {
                    final Player player = asPlayer();
                    final UserInfo info = new UserInfo(player, false);
                    info.addComponentType(UserInfoType.SLOTS, UserInfoType.ENCHANTLEVEL);
                    
                    boolean updateWeight = false;
                    for (Stat stat : currentChanges)
                    {
                        switch (stat)
                        {
                            case MOVE_SPEED:
                            case RUN_SPEED:
                            case WALK_SPEED:
                            case SWIM_RUN_SPEED:
                            case SWIM_WALK_SPEED:
                            case FLY_RUN_SPEED:
                            case FLY_WALK_SPEED:
                            {
                                info.addComponentType(UserInfoType.MULTIPLIER);
                                break;
                            }
                            case PHYSICAL_ATTACK_SPEED:
                            {
                                info.addComponentType(UserInfoType.MULTIPLIER, UserInfoType.STATS);
                                break;
                            }
                            case PHYSICAL_ATTACK:
                            case PHYSICAL_DEFENCE:
                            case EVASION_RATE:
                            case ACCURACY_COMBAT:
                            case CRITICAL_RATE:
                            case CRITICAL_RATE_SKILL:
                            case MAGIC_CRITICAL_RATE:
                            case MAGIC_EVASION_RATE:
                            case ACCURACY_MAGIC:
                            case MAGIC_ATTACK:
                            case MAGIC_ATTACK_SPEED:
                            case MAGICAL_DEFENCE:
                            {
                                info.addComponentType(UserInfoType.STATS);
                                break;
                            }
                            case MAX_CP:
                            {
                                info.addComponentType(UserInfoType.MAX_HPCPMP);
                                break;
                            }
                            case MAX_HP:
                            {
                                info.addComponentType(UserInfoType.MAX_HPCPMP);
                                break;
                            }
                            case MAX_MP:
                            {
                                info.addComponentType(UserInfoType.MAX_HPCPMP);
                                break;
                            }
                            case STAT_STR:
                            case STAT_CON:
                            case STAT_DEX:
                            case STAT_INT:
                            case STAT_WIT:
                            case STAT_MEN:
                            {
                                info.addComponentType(UserInfoType.BASE_STATS);
                                updateWeight = true;
                                break;
                            }
                            case FIRE_RES:
                            case WATER_RES:
                            case WIND_RES:
                            case EARTH_RES:
                            case HOLY_RES:
                            case DARK_RES:
                            {
                                info.addComponentType(UserInfoType.ELEMENTALS);
                                break;
                            }
                            case FIRE_POWER:
                            case WATER_POWER:
                            case WIND_POWER:
                            case EARTH_POWER:
                            case HOLY_POWER:
                            case DARK_POWER:
                            {
                                info.addComponentType(UserInfoType.ATK_ELEMENTAL);
                                break;
                            }
                            case WEIGHT_LIMIT:
                            case WEIGHT_PENALTY:
                            {
                                updateWeight = true;
                                break;
                            }
                        }
                    }
                    
                    if (updateWeight)
                    {
                        player.refreshOverloaded(true);
                    }
                    
                    sendPacket(info);
                    
                    player.broadcastCharInfo();
                    
                    if (hasServitors() && hasAbnormalType(AbnormalType.ABILITY_CHANGE))
                    {
                        getServitors().values().forEach(Summon::broadcastStatusUpdate);
                    }
                }
                else if (isNpc())
                {
                    World.getInstance().forEachVisibleObject(this, Player.class, player ->
                    {
                        if (!isVisibleFor(player))
                        {
                            return;
                        }
                        
                        if (isFakePlayer())
                        {
                            player.sendPacket(new FakePlayerInfo(asNpc()));
                        }
                        else if (getRunSpeed() == 0)
                        {
                            player.sendPacket(new ServerObjectInfo(asNpc(), player));
                        }
                        else
                        {
                            player.sendPacket(new NpcInfo(asNpc()));
                        }
                    });
                }
                
                _broadcastModifiedStatTask = null;
            }, 50);
        }
    }
    
    /**
     * @return the X destination of the Creature or the X position if not in movement.
     */
    public int getXdestination()
    {
        final MoveData move = _move;
        if (move != null)
        {
            return move.xDestination;
        }
        
        return getX();
    }
    
    /**
     * @return the Y destination of the Creature or the Y position if not in movement.
     */
    public int getYdestination()
    {
        final MoveData move = _move;
        if (move != null)
        {
            return move.yDestination;
        }
        
        return getY();
    }
    
    /**
     * @return the Z destination of the Creature or the Z position if not in movement.
     */
    public int getZdestination()
    {
        final MoveData move = _move;
        if (move != null)
        {
            return move.zDestination;
        }
        
        return getZ();
    }
    
    /**
     * @return True if the Creature is in combat.
     */
    public boolean isInCombat()
    {
        return hasAI() && getAI().isAutoAttacking();
    }
    
    /**
     * @return True if the Creature is moving.
     */
    public boolean isMoving()
    {
        return _move != null;
    }
    
    /**
     * @return True if the Creature is traveling a calculated path.
     */
    public boolean isOnGeodataPath()
    {
        final MoveData move = _move;
        if (move == null)
        {
            return false;
        }
        
        return isOnGeodataPath(move);
    }
    
    /**
     * @param move the MoveData to check (must not be null).
     * @return True if the Creature is traveling a calculated path.
     */
    public boolean isOnGeodataPath(MoveData move)
    {
        if (move.onGeodataPathIndex == -1)
        {
            return false;
        }
        
        if (move.onGeodataPathIndex == (move.geoPath.size() - 1))
        {
            return false;
        }
        
        return true;
    }
    
    /**
     * This method returns a list of {@link GeoLocation} objects representing the movement path.<br>
     * If the move operation is defined (not null), it returns the path from the 'geoPath' field of the move.<br>
     * Otherwise, it returns null.
     * @return List of {@link GeoLocation} representing the movement path, or null if move is undefined.
     */
    public List<GeoLocation> getGeoPath()
    {
        final MoveData move = _move;
        if (move != null)
        {
            return move.geoPath;
        }
        
        return null;
    }
    
    /**
     * @return True if the Creature is casting any kind of skill, including simultaneous skills like potions.
     */
    public boolean isCastingNow()
    {
        return !_skillCasters.isEmpty();
    }
    
    public boolean isCastingNow(SkillCastingType skillCastingType)
    {
        return _skillCasters.containsKey(skillCastingType);
    }
    
    public boolean isCastingNow(Predicate<SkillCaster> filter)
    {
        for (SkillCaster skillCaster : _skillCasters.values())
        {
            if (filter.test(skillCaster))
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * @return True if the Creature is attacking.
     */
    public boolean isAttackingNow()
    {
        return _attackEndTime > System.nanoTime();
    }
    
    /**
     * Abort the attack of the Creature and send Server->Client ActionFailed packet.
     */
    public void abortAttack()
    {
        if (isAttackingNow())
        {
            CreatureAttackTaskManager.getInstance().abortAttack(this);
            sendPacket(ActionFailed.STATIC_PACKET);
        }
    }
    
    /**
     * Abort the cast of all skills.
     */
    public void abortAllSkillCasters()
    {
        for (SkillCaster skillCaster : getSkillCasters())
        {
            skillCaster.stopCasting(true);
            if (isPlayer())
            {
                asPlayer().setQueuedSkill(null, null, false, false);
            }
        }
    }
    
    /**
     * Abort the cast of normal non-simultaneous skills.
     * @return {@code true} if a skill casting has been aborted, {@code false} otherwise.
     */
    public boolean abortCast()
    {
        return abortCast(SkillCaster::isAnyNormalType);
    }
    
    /**
     * Try to break this character's casting using the given filters.
     * @param filter
     * @return {@code true} if a skill casting has been aborted, {@code false} otherwise.
     */
    public boolean abortCast(Predicate<SkillCaster> filter)
    {
        final SkillCaster skillCaster = getSkillCaster(SkillCaster::canAbortCast, filter);
        if (skillCaster != null)
        {
            skillCaster.stopCasting(true);
            if (isPlayer())
            {
                asPlayer().setQueuedSkill(null, null, false, false);
            }
            return true;
        }
        return false;
    }
    
    /**
     * Update the position of the Creature during a movement and return True if the movement is finished.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * At the beginning of the move action, all properties of the movement are stored in the MoveData object called <b>_move</b> of the Creature.<br>
     * The position of the start point and of the destination permit to estimated in function of the movement speed the time to achieve the destination.<br>
     * When the movement is started (ex : by MovetoLocation), this method will be called each 0.1 sec to estimate and update the Creature position on the server.<br>
     * Note, that the current server position can differe from the current client position even if each movement is straight foward.<br>
     * That's why, client send regularly a Client->Server ValidatePosition packet to eventually correct the gap on the server.<br>
     * But, it's always the server position that is used in range calculation. At the end of the estimated movement time,<br>
     * the Creature position is automatically set to the destination position even if the movement is not finished.<br>
     * <font color=#FF0000><b><u>Caution</u>: The current Z position is obtained FROM THE CLIENT by the Client->Server ValidatePosition Packet.<br>
     * But x and y positions must be calculated to avoid that players try to modify their movement speed.</b></font>
     * @return True if the movement is finished
     */
    public boolean updatePosition()
    {
        if (!isSpawned())
        {
            _move = null;
            return true;
        }
        
        // Get movement data
        final MoveData move = _move;
        if (move == null)
        {
            return true;
        }
        
        // Check if this is the first update
        if (move.moveTimestamp == 0)
        {
            move.moveTimestamp = move.moveStartTime;
            move.xAccurate = getX();
            move.yAccurate = getY();
        }
        
        // Check if the position has already been calculated
        final int gameTicks = GameTimeTaskManager.getInstance().getGameTicks();
        if (move.moveTimestamp == gameTicks)
        {
            return false;
        }
        
        final int xPrev = getX();
        final int yPrev = getY();
        final int zPrev = getZ(); // the z coordinate may be modified by coordinate synchronizations
        double dx = move.xDestination - move.xAccurate;
        double dy = move.yDestination - move.yAccurate;
        double dz = move.zDestination - zPrev; // Z coordinate will follow client values
        
        if (isPlayer() && !_isFlying)
        {
            // In case of cursor movement, avoid moving through obstacles.
            if (_cursorKeyMovement)
            {
                final double angle = LocationUtil.convertHeadingToDegree(getHeading());
                final double radian = Math.toRadians(angle);
                final double course = Math.toRadians(180);
                final double frontDistance = 10 * (_stat.getMoveSpeed() / 100);
                final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance);
                final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance);
                final int x = xPrev + x1;
                final int y = yPrev + y1;
                if (!GeoEngine.getInstance().canMoveToTarget(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceWorld()))
                {
                    _move.onGeodataPathIndex = -1;
                    stopMove(asPlayer().getLastServerPosition());
                    return true;
                }
            }
            else // Mouse click movement.
            {
                // Stop movement when player has clicked far away and intersected with an obstacle.
                final double distance = Math.hypot(dx, dy);
                if (distance > 3000)
                {
                    final double angle = LocationUtil.convertHeadingToDegree(getHeading());
                    final double radian = Math.toRadians(angle);
                    final double course = Math.toRadians(180);
                    final double frontDistance = 10 * (_stat.getMoveSpeed() / 100);
                    final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance);
                    final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance);
                    final int x = xPrev + x1;
                    final int y = yPrev + y1;
                    if (!GeoEngine.getInstance().canMoveToTarget(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceWorld()))
                    {
                        _move.onGeodataPathIndex = -1;
                        if (hasAI())
                        {
                            if (getAI().isFollowing())
                            {
                                getAI().stopFollow();
                            }
                            getAI().setIntention(Intention.IDLE);
                        }
                        return true;
                    }
                }
                else // Check for nearby doors or fences.
                {
                    if (hasAI() && (getAI().getIntention() == Intention.ATTACK)) // Support for player attack with direct movement. Tested at retail on May 11th 2023.
                    {
                        final double angle = LocationUtil.convertHeadingToDegree(getHeading());
                        final double radian = Math.toRadians(angle);
                        final double course = Math.toRadians(180);
                        final double frontDistance = 10 * (_stat.getMoveSpeed() / 100);
                        final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance);
                        final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance);
                        final int x = xPrev + x1;
                        final int y = yPrev + y1;
                        if (!GeoEngine.getInstance().canMoveToTarget(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceWorld()))
                        {
                            _move.onGeodataPathIndex = -1;
                            broadcastPacket(new StopMove(this));
                            return true;
                        }
                    }
                    else // Check for nearby doors or fences.
                    {
                        final WorldRegion region = getWorldRegion();
                        if (region != null)
                        {
                            final boolean hasDoors = !region.getDoors().isEmpty();
                            final boolean hasFences = !region.getFences().isEmpty();
                            if (hasDoors || hasFences)
                            {
                                final double angle = LocationUtil.convertHeadingToDegree(getHeading());
                                final double radian = Math.toRadians(angle);
                                final double course = Math.toRadians(180);
                                final double frontDistance = 10 * (_stat.getMoveSpeed() / 100);
                                final int x1 = (int) (Math.cos(Math.PI + radian + course) * frontDistance);
                                final int y1 = (int) (Math.sin(Math.PI + radian + course) * frontDistance);
                                final int x = xPrev + x1;
                                final int y = yPrev + y1;
                                if ((hasDoors && DoorData.getInstance().checkIfDoorsBetween(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceWorld(), false)) //
                                    || (hasFences && FenceData.getInstance().checkIfFenceBetween(xPrev, yPrev, zPrev, x, y, zPrev, getInstanceWorld())))
                                {
                                    _move.onGeodataPathIndex = -1;
                                    if (hasAI())
                                    {
                                        if (getAI().isFollowing())
                                        {
                                            getAI().stopFollow();
                                        }
                                        getAI().setIntention(Intention.IDLE);
                                    }
                                    stopMove(null);
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        
        // Distance from destination.
        double delta = (dx * dx) + (dy * dy);
        final boolean isFloating = _isFlying || (isInsideZone(ZoneId.WATER) && !isInsideZone(ZoneId.CASTLE));
        if (!isFloating && (delta < 10000) && ((dz * dz) > 2500)) // Close enough, allows error between client and server geodata if it cannot be avoided.
        {
            delta = Math.sqrt(delta);
        }
        else
        {
            delta = Math.sqrt(delta + (dz * dz));
        }
        
        // Prevent non playables teleporting to another ground layer while moving.
        if (!isPlayer() && !isFloating && (Math.abs(move.zDestination - zPrev) > 300))
        {
            move.zDestination = zPrev;
        }
        
        // Target collision should be subtracted from current distance.
        final double collision;
        final WorldObject target = _target;
        if ((target != null) && target.isCreature() && hasAI() && (getAI().getIntention() == Intention.ATTACK))
        {
            collision = target.asCreature().getCollisionRadius();
        }
        else
        {
            collision = getCollisionRadius();
        }
        delta = Math.max(0.00001, delta - collision);
        
        double distFraction = Double.MAX_VALUE;
        if (delta > 1)
        {
            final double distPassed = (_stat.getMoveSpeed() * (gameTicks - move.moveTimestamp)) / GameTimeTaskManager.TICKS_PER_SECOND;
            distFraction = distPassed / delta;
        }
        
        final boolean arrived = distFraction > 1.79;
        if (arrived)
        {
            // Set the position of the Creature to the destination.
            super.setXYZ(move.xDestination, move.yDestination, move.zDestination);
        }
        else
        {
            move.xAccurate += dx * distFraction;
            move.yAccurate += dy * distFraction;
            
            // Set the position of the Creature to estimated after parcial move.
            super.setXYZ((int) move.xAccurate, (int) move.yAccurate, zPrev + (int) ((dz * distFraction) + 0.895));
        }
        revalidateZone(false);
        
        // Set the timer of last position update to now.
        move.moveTimestamp = gameTicks;
        
        // Broadcast MoveToLocation on arrived.
        if (arrived && !isOnGeodataPath())
        {
            broadcastMoveToLocation(true);
        }
        else if (isAttackable() && (target != null)) // Attackable with target.
        {
            broadcastMoveToLocation();
        }
        
        return arrived;
    }
    
    public void revalidateZone(boolean force)
    {
        // This function is called too often from movement code.
        if (!force && (calculateDistance3D(_lastZoneValidateLocation) < (isNpc() && !isInCombat() ? Config.MAX_DRIFT_RANGE : 100)))
        {
            return;
        }
        _lastZoneValidateLocation.setXYZ(this);
        
        final ZoneRegion region = ZoneManager.getInstance().getRegion(this);
        if (region != null)
        {
            region.revalidateZones(this);
        }
        else // Precaution. Moved at invalid region?
        {
            World.getInstance().disposeOutOfBoundsObject(this);
        }
    }
    
    /**
     * Stop movement of the Creature (Called by AI Accessor only).<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Delete movement data of the Creature</li>
     * <li>Set the current position (x,y,z), its current WorldRegion if necessary and its heading</li>
     * <li>Remove the WorldObject object from _gmList of GmListTable</li>
     * <li>Remove object from _knownObjects and _knownPlayer of all surrounding WorldRegion Creatures</li>
     * </ul>
     * <font color=#FF0000><b><u>Caution</u>: This method DOESN'T send Server->Client packet StopMove/StopRotation</b></font>
     * @param loc
     */
    public void stopMove(Location loc)
    {
        // Delete movement data of the Creature.
        _move = null;
        _cursorKeyMovement = false;
        
        // All data are contained in a Location object.
        if (loc != null)
        {
            setXYZ(loc.getX(), loc.getY(), loc.getZ());
            setHeading(loc.getHeading());
            revalidateZone(true);
        }
        
        broadcastPacket(new StopMove(this));
    }
    
    /**
     * @return Returns the showSummonAnimation.
     */
    public boolean isShowSummonAnimation()
    {
        return _showSummonAnimation;
    }
    
    /**
     * @param showSummonAnimation The showSummonAnimation to set.
     */
    public void setShowSummonAnimation(boolean showSummonAnimation)
    {
        _showSummonAnimation = showSummonAnimation;
    }
    
    /**
     * Target a WorldObject (add the target to the Creature _target, _knownObject and Creature to _KnownObject of the WorldObject).<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * The WorldObject (including Creature) targeted is identified in <b>_target</b> of the Creature.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Set the _target of Creature to WorldObject</li>
     * <li>If necessary, add WorldObject to _knownObject of the Creature</li>
     * <li>If necessary, add Creature to _KnownObject of the WorldObject</li>
     * <li>If object==null, cancel Attak or Cast</li>
     * </ul>
     * @param object L2object to target
     */
    public void setTarget(WorldObject object)
    {
        if ((object != null) && !object.isSpawned())
        {
            _target = null;
            return;
        }
        _target = object;
    }
    
    /**
     * @return the identifier of the WorldObject targeted or -1.
     */
    public int getTargetId()
    {
        if (_target != null)
        {
            return _target.getObjectId();
        }
        return 0;
    }
    
    /**
     * @return the WorldObject targeted or null.
     */
    public WorldObject getTarget()
    {
        return _target;
    }
    
    // called from AIAccessor only
    
    /**
     * Calculate movement data for a move to location action and add the Creature to MOVING_OBJECTS of MovementTaskManager (only called by AI Accessor).<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * At the beginning of the move action, all properties of the movement are stored in the MoveData object called <b>_move</b> of the Creature.<br>
     * The position of the start point and of the destination permit to estimated in function of the movement speed the time to achieve the destination.<br>
     * All Creature in movement are identified in <b>MOVING_OBJECTS</b> of MovementTaskManager that will call the updatePosition method of those Creature each 0.1s.<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Get current position of the Creature</li>
     * <li>Calculate distance (dx,dy) between current position and destination including offset</li>
     * <li>Create and Init a MoveData object</li>
     * <li>Set the Creature _move object to MoveData object</li>
     * <li>Add the Creature to MOVING_OBJECTS of the MovementTaskManager</li>
     * <li>Create a task to notify the AI that Creature arrives at a check point of the movement</li>
     * </ul>
     * <font color=#FF0000><b><u>Caution</u>: This method DOESN'T send Server->Client packet MoveToPawn/MoveToLocation.</b></font><br>
     * <br>
     * <b><u>Example of use</u>:</b>
     * <ul>
     * <li>AI : onIntentionMoveTo(Location), onIntentionPickUp(WorldObject), onIntentionInteract(WorldObject)</li>
     * <li>FollowTask</li>
     * </ul>
     * @param xValue The X position of the destination
     * @param yValue The Y position of the destination
     * @param zValue The Y position of the destination
     * @param offsetValue The size of the interaction area of the Creature targeted
     */
    public void moveToLocation(int xValue, int yValue, int zValue, int offsetValue)
    {
        // Get the Move Speed of the Creature
        final double speed = _stat.getMoveSpeed();
        if ((speed <= 0) || isMovementDisabled())
        {
            sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }
        
        int x = xValue;
        int y = yValue;
        int z = zValue;
        int offset = offsetValue;
        
        // Get current position of the Creature
        final int curX = getX();
        final int curY = getY();
        final int curZ = getZ();
        
        // Calculate distance (dx,dy) between current position and destination
        // TODO: improve Z axis move/follow support when dx,dy are small compared to dz
        double dx = (x - curX);
        double dy = (y - curY);
        double dz = (z - curZ);
        double distance = Math.hypot(dx, dy);
        
        final boolean verticalMovementOnly = _isFlying && (distance == 0) && (dz != 0);
        if (verticalMovementOnly)
        {
            distance = Math.abs(dz);
        }
        
        // Make water move short and use no geodata checks for swimming chars distance in a click can easily be over 3000.
        final boolean isInWater = isInsideZone(ZoneId.WATER) && !isInsideZone(ZoneId.CASTLE);
        if (isInWater && (distance > 700))
        {
            final double divider = 700 / distance;
            x = curX + (int) (divider * dx);
            y = curY + (int) (divider * dy);
            z = curZ + (int) (divider * dz);
            dx = (x - curX);
            dy = (y - curY);
            dz = (z - curZ);
            distance = Math.hypot(dx, dy);
        }
        
        // @formatter:off
        // Define movement angles needed
        // ^
        // |    X (x,y)
        // |   /
        // |  / distance
        // | /
        // |/ angle
        // X ---------->
        // (curx,cury)
        // @formatter:on
        
        double cos;
        double sin;
        
        // Check if a movement offset is defined or no distance to go through
        if ((offset > 0) || (distance < 1.79))
        {
            // approximation for moving closer when z coordinates are different
            // TODO: handle Z axis movement better
            offset -= Math.abs(dz);
            if (offset < 5)
            {
                offset = 5;
            }
            
            // If no distance to go through, the movement is canceled
            if ((distance < 1.79) || ((distance - offset) <= 0))
            {
                // Notify the AI that the Creature is arrived at destination
                getAI().notifyAction(Action.ARRIVED);
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }
            
            // Calculate movement angles needed
            sin = dy / distance;
            cos = dx / distance;
            distance -= (offset - 5); // due to rounding error, we have to move a bit closer to be in range
            
            // Calculate the new destination with offset included
            x = curX + (int) (distance * cos);
            y = curY + (int) (distance * sin);
        }
        else
        {
            // Calculate movement angles needed
            sin = dy / distance;
            cos = dx / distance;
        }
        
        // Create and Init a MoveData object
        final MoveData move = new MoveData();
        
        // GEODATA MOVEMENT CHECKS AND PATHFINDING
        final WorldRegion region = getWorldRegion();
        move.disregardingGeodata = (region == null) || !region.areNeighborsActive();
        move.onGeodataPathIndex = -1; // Initialize not on geodata path
        if (!move.disregardingGeodata && !_isFlying && !isInWater && !isVehicle() && !_cursorKeyMovement)
        {
            final boolean isInVehicle = isPlayer() && (asPlayer().getVehicle() != null);
            if (isInVehicle)
            {
                move.disregardingGeodata = true;
            }
            
            // Movement checks.
            if ((Config.PATHFINDING > 0) && !(this instanceof FriendlyNpc))
            {
                int originalX = x;
                int originalY = y;
                final int originalZ = z;
                final double originalDistance = distance;
                final int gtx = (originalX - World.WORLD_X_MIN) >> 4;
                final int gty = (originalY - World.WORLD_Y_MIN) >> 4;
                if (isOnGeodataPath())
                {
                    try
                    {
                        if ((gtx == _move.geoPathGtx) && (gty == _move.geoPathGty))
                        {
                            sendPacket(ActionFailed.STATIC_PACKET);
                            return;
                        }
                        
                        _move.onGeodataPathIndex = -1; // Set not on geodata path.
                    }
                    catch (NullPointerException e)
                    {
                    }
                }
                
                // Support for player attack with direct movement. Tested at retail on May 11th 2023.
                boolean directMove = false;
                if (isPlayer() && hasAI() && (getAI().getIntention() == Intention.ATTACK))
                {
                    directMove = true;
                }
                
                if (directMove //
                    || (!isInVehicle // Not in vehicle.
                        && !(isPlayer() && (distance > 3000)) // Should be able to click far away and move.
                        && !(isMonster() && (Math.abs(dz) > 100)) // Monsters can move on ledges.
                        && !(((curZ - z) > 300) && (distance < 300)))) // Prohibit correcting destination if character wants to fall.
                {
                    // location different if destination wasn't reached (or just z coord is different)
                    final Location destiny = GeoEngine.getInstance().getValidLocation(curX, curY, curZ, x, y, z, getInstanceWorld());
                    x = destiny.getX();
                    y = destiny.getY();
                    if (!isPlayer())
                    {
                        z = destiny.getZ();
                    }
                    dx = x - curX;
                    dy = y - curY;
                    dz = z - curZ;
                    distance = verticalMovementOnly ? Math.pow(dz, 2) : Math.hypot(dx, dy);
                }
                
                // Pathfinding checks.
                if (!directMove && ((originalDistance - distance) > 30) && !isControlBlocked() && !isInVehicle)
                {
                    // Path calculation -- overrides previous movement check
                    move.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY, originalZ, getInstanceWorld(), isPlayer());
                    boolean found = (move.geoPath != null) && (move.geoPath.size() > 1);
                    
                    // If path not found and this is an Attackable, attempt to find closest path to destination.
                    if (!found && isAttackable())
                    {
                        int xMin = Math.min(curX, originalX);
                        int xMax = Math.max(curX, originalX);
                        int yMin = Math.min(curY, originalY);
                        int yMax = Math.max(curY, originalY);
                        final int maxDiff = Math.min(Math.max(xMax - xMin, yMax - yMin), 500);
                        xMin -= maxDiff;
                        xMax += maxDiff;
                        yMin -= maxDiff;
                        yMax += maxDiff;
                        int destinationX = 0;
                        int destinationY = 0;
                        double shortDistance = Double.MAX_VALUE;
                        double tempDistance;
                        List<GeoLocation> tempPath;
                        for (int sX = xMin; sX < xMax; sX += 500)
                        {
                            for (int sY = yMin; sY < yMax; sY += 500)
                            {
                                tempDistance = Math.hypot(sX - originalX, sY - originalY);
                                if (tempDistance < shortDistance)
                                {
                                    tempPath = PathFinding.getInstance().findPath(curX, curY, curZ, sX, sY, originalZ, getInstanceWorld(), false);
                                    found = (tempPath != null) && (tempPath.size() > 1);
                                    if (found)
                                    {
                                        shortDistance = tempDistance;
                                        move.geoPath = tempPath;
                                        destinationX = sX;
                                        destinationY = sY;
                                    }
                                }
                            }
                        }
                        found = (move.geoPath != null) && (move.geoPath.size() > 1);
                        if (found)
                        {
                            originalX = destinationX;
                            originalY = destinationY;
                        }
                    }
                    
                    if (found)
                    {
                        move.onGeodataPathIndex = 0; // On first segment.
                        move.geoPathGtx = gtx;
                        move.geoPathGty = gty;
                        move.geoPathAccurateTx = originalX;
                        move.geoPathAccurateTy = originalY;
                        x = move.geoPath.get(move.onGeodataPathIndex).getX();
                        y = move.geoPath.get(move.onGeodataPathIndex).getY();
                        z = move.geoPath.get(move.onGeodataPathIndex).getZ();
                        dx = x - curX;
                        dy = y - curY;
                        dz = z - curZ;
                        distance = verticalMovementOnly ? Math.pow(dz, 2) : Math.hypot(dx, dy);
                        sin = dy / distance;
                        cos = dx / distance;
                    }
                    else // No path found.
                    {
                        // When no move path was found, use direct movement. Tested at retail on October 21st 2024.
                        // if (isPlayer() && !_isFlying && !isInWater)
                        // {
                        // sendPacket(ActionFailed.STATIC_PACKET);
                        // return;
                        // }
                        
                        move.disregardingGeodata = true;
                        x = originalX;
                        y = originalY;
                        z = originalZ;
                        distance = originalDistance;
                    }
                }
                
                // Verify destination when using mouse movement and no path is found.
                if (isPlayable() && !_cursorKeyMovement && (move.geoPath == null))
                {
                    final Location destiny = GeoEngine.getInstance().getValidLocation(curX, curY, curZ, x, y, z, getInstanceWorld());
                    x = destiny.getX();
                    y = destiny.getY();
                    z = destiny.getZ();
                    dx = x - curX;
                    dy = y - curY;
                    dz = z - curZ;
                    distance = verticalMovementOnly ? Math.pow(dz, 2) : Math.hypot(dx, dy);
                }
            }
            
            // If no distance to go through, the movement is canceled
            if ((distance < 1.79) && ((Config.PATHFINDING > 0) || isPlayable()))
            {
                if (isSummon())
                {
                    // Do not break following owner.
                    if (getAI().getTarget() != asPlayer())
                    {
                        asSummon().setFollowStatus(false);
                        getAI().setIntention(Intention.IDLE);
                    }
                }
                else
                {
                    getAI().setIntention(Intention.IDLE);
                }
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }
        }
        
        // Apply Z distance for flying or swimming for correct timing calculations
        if ((_isFlying || isInWater) && !verticalMovementOnly)
        {
            distance = Math.hypot(distance, dz);
        }
        
        // Calculate the number of ticks between the current position and the destination.
        final int ticksToMove = (int) ((GameTimeTaskManager.TICKS_PER_SECOND * distance) / speed);
        move.xDestination = x;
        move.yDestination = y;
        move.zDestination = z; // this is what was requested from client
        
        // Calculate and set the heading of the Creature
        move.heading = 0; // initial value for coordinate sync
        // Does not break heading on vertical movements
        if (!verticalMovementOnly)
        {
            setHeading(LocationUtil.calculateHeadingFrom(cos, sin));
        }
        
        move.moveStartTime = GameTimeTaskManager.getInstance().getGameTicks();
        
        // Set the Creature _move object to MoveData object
        _move = move;
        
        // Add the Creature to moving objects of the MovementTaskManager.
        // The MovementTaskManager manages object movement.
        MovementTaskManager.getInstance().registerMovingObject(this);
        
        // Create a task to notify the AI that Creature arrives at a check point of the movement
        if ((ticksToMove * GameTimeTaskManager.MILLIS_IN_TICK) > 3000)
        {
            ThreadPool.schedule(new NotifyAITask(this, Action.ARRIVED_REVALIDATE), 2000);
        }
        // the Event.ARRIVED will be sent when the character will actually arrive to destination by MovementTaskManager
    }
    
    /**
     * Move to next route point.
     * @return true, if successful
     */
    public boolean moveToNextRoutePoint()
    {
        final MoveData move = _move;
        if (move == null)
        {
            return false;
        }
        
        if (!isOnGeodataPath(move))
        {
            // Cancel the move action
            _move = null;
            return false;
        }
        
        // Get the Move Speed of the Creature
        final double speed = _stat.getMoveSpeed();
        if ((speed <= 0) || isMovementDisabled())
        {
            // Cancel the move action
            _move = null;
            return false;
        }
        
        // Get current position of the Creature
        final int curX = getX();
        final int curY = getY();
        
        // Create and Init a MoveData object
        final MoveData newMove = new MoveData();
        
        // Update MoveData object
        newMove.onGeodataPathIndex = move.onGeodataPathIndex + 1; // next segment
        newMove.geoPath = move.geoPath;
        newMove.geoPathGtx = move.geoPathGtx;
        newMove.geoPathGty = move.geoPathGty;
        newMove.geoPathAccurateTx = move.geoPathAccurateTx;
        newMove.geoPathAccurateTy = move.geoPathAccurateTy;
        if (move.onGeodataPathIndex == (move.geoPath.size() - 2))
        {
            newMove.xDestination = move.geoPathAccurateTx;
            newMove.yDestination = move.geoPathAccurateTy;
            newMove.zDestination = move.geoPath.get(newMove.onGeodataPathIndex).getZ();
        }
        else
        {
            newMove.xDestination = move.geoPath.get(newMove.onGeodataPathIndex).getX();
            newMove.yDestination = move.geoPath.get(newMove.onGeodataPathIndex).getY();
            newMove.zDestination = move.geoPath.get(newMove.onGeodataPathIndex).getZ();
        }
        
        // Calculate and set the heading of the Creature.
        final double distance = Math.hypot(newMove.xDestination - curX, newMove.yDestination - curY);
        if (distance != 0)
        {
            setHeading(LocationUtil.calculateHeadingFrom(curX, curY, newMove.xDestination, newMove.yDestination));
        }
        
        // Calculate the number of ticks between the current position and the destination.
        final int ticksToMove = (int) ((GameTimeTaskManager.TICKS_PER_SECOND * distance) / speed);
        newMove.heading = 0; // initial value for coordinate sync
        newMove.moveStartTime = GameTimeTaskManager.getInstance().getGameTicks();
        
        // Set the Creature _move object to MoveData object
        _move = newMove;
        
        // Add the Creature to moving objects of the MovementTaskManager.
        // The MovementTaskManager manages object movement.
        MovementTaskManager.getInstance().registerMovingObject(this);
        
        // Create a task to notify the AI that Creature arrives at a check point of the movement
        if ((ticksToMove * GameTimeTaskManager.MILLIS_IN_TICK) > 3000)
        {
            ThreadPool.schedule(new NotifyAITask(this, Action.ARRIVED_REVALIDATE), 2000);
        }
        
        // the Event.ARRIVED will be sent when the character will actually arrive to destination by MovementTaskManager
        
        // Send a Server->Client packet MoveToLocation to the actor and all Player in its _knownPlayers
        broadcastMoveToLocation(true);
        return true;
    }
    
    /**
     * Validate movement heading.
     * @param heading the heading
     * @return true, if successful
     */
    public boolean validateMovementHeading(int heading)
    {
        final MoveData move = _move;
        if (move == null)
        {
            return true;
        }
        
        boolean result = true;
        if (move.heading != heading)
        {
            result = (move.heading == 0); // initial value or false
            move.heading = heading;
        }
        
        return result;
    }
    
    /**
     * Check if this object is inside the given 2D radius around the given point.
     * @param loc Location of the target
     * @param radius the radius around the target
     * @return true if the Creature is inside the radius.
     */
    public boolean isInsideRadius2D(ILocational loc, int radius)
    {
        return isInsideRadius2D(loc.getX(), loc.getY(), loc.getZ(), radius);
    }
    
    /**
     * Check if this object is inside the given 2D radius around the given point.
     * @param x X position of the target
     * @param y Y position of the target
     * @param z Z position of the target
     * @param radius the radius around the target
     * @return true if the Creature is inside the radius.
     */
    public boolean isInsideRadius2D(int x, int y, int z, int radius)
    {
        return calculateDistance2D(x, y, z) < radius;
    }
    
    /**
     * Check if this object is inside the given 3D radius around the given point.
     * @param loc Location of the target
     * @param radius the radius around the target
     * @return true if the Creature is inside the radius.
     */
    public boolean isInsideRadius3D(ILocational loc, int radius)
    {
        return isInsideRadius3D(loc.getX(), loc.getY(), loc.getZ(), radius);
    }
    
    /**
     * Check if this object is inside the given 3D radius around the given point.
     * @param x X position of the target
     * @param y Y position of the target
     * @param z Z position of the target
     * @param radius the radius around the target
     * @return true if the Creature is inside the radius.
     */
    public boolean isInsideRadius3D(int x, int y, int z, int radius)
    {
        return calculateDistance3D(x, y, z) < radius;
    }
    
    /**
     * <b><u>Overridden in</u>:</b>
     * <li>Player</li>
     * @return True if arrows are available.
     * @param type
     */
    protected boolean checkAndEquipAmmunition(EtcItemType type)
    {
        return true;
    }
    
    /**
     * Add Exp and Sp to the Creature.<br>
     * <br>
     * <b><u>Overridden in</u>:</b>
     * <li>Player</li>
     * <li>Pet</li><br>
     * @param addToExp
     * @param addToSp
     */
    public synchronized void addExpAndSp(double addToExp, double addToSp)
    {
        // Dummy method (overridden by players and pets)
    }
    
    /**
     * <b><u>Overridden in</u>:</b>
     * <li>Player</li>
     * @return the active weapon instance (always equipped in the right hand).
     */
    public abstract Item getActiveWeaponInstance();
    
    /**
     * <b><u>Overridden in</u>:</b>
     * <li>Player</li>
     * @return the active weapon item (always equipped in the right hand).
     */
    public abstract Weapon getActiveWeaponItem();
    
    /**
     * <b><u>Overridden in</u>:</b>
     * <li>Player</li>
     * @return the secondary weapon instance (always equipped in the left hand).
     */
    public abstract Item getSecondaryWeaponInstance();
    
    /**
     * <b><u>Overridden in</u>:</b>
     * <li>Player</li>
     * @return the secondary {@link ItemTemplate} item (always equipped in the left hand).
     */
    public abstract ItemTemplate getSecondaryWeaponItem();
    
    /**
     * Manage hit process (called by Hit Task).<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>If the attacker/target is dead or use fake death, notify the AI with CANCEL and send a Server->Client packet ActionFailed (if attacker is a Player)</li>
     * <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are Player</li>
     * <li>If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary</li>
     * <li>if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...)</li>
     * </ul>
     * @param weapon the weapon used for the hit
     * @param attack the attack data of targets to hit
     * @param hitTime the time it took for this hit to occur
     * @param attackTime the time it takes for the whole attack to complete
     */
    public void onHitTimeNotDual(Weapon weapon, Attack attack, int hitTime, int attackTime)
    {
        if (_isDead)
        {
            getAI().notifyAction(Action.CANCEL);
            return;
        }
        
        for (Hit hit : attack.getHits())
        {
            final Creature target = hit.getTarget().asCreature();
            if ((target == null) || target.isDead() || !isInSurroundingRegion(target))
            {
                continue;
            }
            
            if (hit.isMiss())
            {
                notifyAttackAvoid(target, false);
            }
            else
            {
                // Avoid arrows dealing damage when the target hides behind something.
                if ((weapon != null) && weapon.getItemType().isRanged() && !GeoEngine.getInstance().canSeeTarget(this, target))
                {
                    if (target.isPlayer())
                    {
                        final SystemMessage sm = new SystemMessage(SystemMessageId.YOU_HAVE_AVOIDED_C1_S_ATTACK);
                        sm.addString(getName());
                        target.sendPacket(sm);
                    }
                    if (isPlayer())
                    {
                        sendPacket(SystemMessageId.YOU_HAVE_MISSED);
                    }
                    continue;
                }
                
                onHitTarget(target, weapon, hit);
            }
        }
        
        CreatureAttackTaskManager.getInstance().onAttackFinish(this, attack, attackTime - hitTime);
    }
    
    public void onFirstHitTimeForDual(Weapon weapon, Attack attack, int hitTime, int attackTime, int delayForSecondAttack)
    {
        if (_isDead)
        {
            getAI().notifyAction(Action.CANCEL);
            return;
        }
        
        CreatureAttackTaskManager.getInstance().onSecondHitTimeForDual(this, weapon, attack, hitTime, attackTime, delayForSecondAttack);
        
        // First dual attack is the first hit only.
        final Hit hit = attack.getHits().get(0);
        final Creature target = hit.getTarget().asCreature();
        if ((target == null) || target.isDead() || !isInSurroundingRegion(target))
        {
            getAI().notifyAction(Action.CANCEL);
            return;
        }
        
        if (hit.isMiss())
        {
            notifyAttackAvoid(target, false);
        }
        else
        {
            onHitTarget(target, weapon, hit);
        }
    }
    
    public void onSecondHitTimeForDual(Weapon weapon, Attack attack, int hitTime1, int hitTime2, int attackTime)
    {
        if (_isDead)
        {
            getAI().notifyAction(Action.CANCEL);
            return;
        }
        
        // Second dual attack is the remaining hits (first hit not included)
        for (int i = 1; i < attack.getHits().size(); i++)
        {
            final Hit hit = attack.getHits().get(i);
            final Creature target = hit.getTarget().asCreature();
            if ((target == null) || target.isDead() || !isInSurroundingRegion(target))
            {
                continue;
            }
            
            if (hit.isMiss())
            {
                notifyAttackAvoid(target, false);
            }
            else
            {
                onHitTarget(target, weapon, hit);
            }
        }
        
        CreatureAttackTaskManager.getInstance().onAttackFinish(this, attack, attackTime - (hitTime1 + hitTime2));
    }
    
    public void onHitTarget(Creature target, Weapon weapon, Hit hit)
    {
        // reduce targets HP
        doAttack(hit.getDamage(), target, null, false, false, hit.isCritical(), false);
        
        // Notify to scripts when the attack has been done.
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ATTACK, this))
        {
            if (_onCreatureAttack == null)
            {
                _onCreatureAttack = new OnCreatureAttack();
            }
            _onCreatureAttack.setAttacker(this);
            _onCreatureAttack.setTarget(target);
            _onCreatureAttack.setSkill(null);
            EventDispatcher.getInstance().notifyEvent(_onCreatureAttack, this);
        }
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ATTACKED, target))
        {
            if (_onCreatureAttacked == null)
            {
                _onCreatureAttacked = new OnCreatureAttacked();
            }
            _onCreatureAttacked.setAttacker(this);
            _onCreatureAttacked.setTarget(target);
            _onCreatureAttacked.setSkill(null);
            EventDispatcher.getInstance().notifyEvent(_onCreatureAttacked, target);
        }
        
        if (!_triggerSkills.isEmpty())
        {
            for (OptionSkillHolder holder : _triggerSkills.values())
            {
                if (((!hit.isCritical() && (holder.getSkillType() == OptionSkillType.ATTACK)) || ((holder.getSkillType() == OptionSkillType.CRITICAL) && hit.isCritical())) && (Rnd.get(100) < holder.getChance()))
                {
                    SkillCaster.triggerCast(this, target, holder.getSkill(), null, false);
                }
            }
        }
        
        // Launch weapon Special ability effect if available
        if (hit.isCritical() && (weapon != null))
        {
            weapon.applyConditionalSkills(this, target, null, ItemSkillType.ON_CRITICAL_SKILL);
        }
        
        // If hit by a cursed weapon, CP is reduced to 0.
        // If a cursed weapon is hit by a Hero, CP is reduced to 0.
        if (isPlayer() && !target.isHpBlocked() && (asPlayer().isCursedWeaponEquipped() || (asPlayer().isHero() && target.isPlayer() && target.asPlayer().isCursedWeaponEquipped())))
        {
            target.setCurrentCp(0);
        }
    }
    
    public void onAttackFinish(Attack attack)
    {
        // Recharge any active auto-soulshot tasks for current creature after the attack has successfully hit.
        for (Hit hit : attack.getHits())
        {
            if (!hit.isMiss())
            {
                rechargeShots(true, false, false);
                break;
            }
        }
        
        // Notify that this character is ready to act for the next attack
        getAI().notifyAction(Action.READY_TO_ACT);
    }
    
    /**
     * Break an attack and send Server->Client ActionFailed packet and a System Message to the Creature.
     */
    public void breakAttack()
    {
        if (isAttackingNow())
        {
            // Abort the attack of the Creature and send Server->Client ActionFailed packet
            abortAttack();
            if (isPlayer())
            {
                // Send a system message
                sendPacket(SystemMessageId.YOUR_ATTACK_HAS_FAILED);
            }
        }
    }
    
    /**
     * Break a cast and send Server->Client ActionFailed packet and a System Message to the Creature.
     */
    public void breakCast()
    {
        // Break only one skill at a time while casting.
        final SkillCaster skillCaster = getSkillCaster(SkillCaster::isAnyNormalType);
        if ((skillCaster != null) && skillCaster.getSkill().isMagic())
        {
            // Abort the cast of the Creature and send Server->Client MagicSkillCanceled/ActionFailed packet.
            skillCaster.stopCasting(true);
            
            if (isPlayer())
            {
                // Send a system message
                sendPacket(SystemMessageId.YOUR_CASTING_HAS_BEEN_INTERRUPTED);
            }
        }
    }
    
    /**
     * Manage Forced attack (shift + select target).<br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>If Creature or target is in a town area, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed</li>
     * <li>If target is confused, send a Server->Client packet ActionFailed</li>
     * <li>If Creature is a Artefact, send a Server->Client packet ActionFailed</li>
     * <li>Send a Server->Client packet MyTargetSelected to start attack and Notify AI with ATTACK</li>
     * </ul>
     * @param player The Player to attack
     */
    @Override
    public void onForcedAttack(Player player)
    {
        if (isInsidePeaceZone(player))
        {
            // If Creature or target is in a peace zone, send a system message TARGET_IN_PEACEZONE a Server->Client packet ActionFailed
            player.sendPacket(SystemMessageId.YOU_CANNOT_ATTACK_THIS_TARGET_IN_A_PEACEFUL_ZONE);
            player.sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }
        if (player.isInOlympiadMode() && (player.getTarget() != null) && player.getTarget().isPlayable())
        {
            Player target = null;
            final WorldObject object = player.getTarget();
            if ((object != null) && object.isPlayable())
            {
                target = object.asPlayer();
            }
            
            if ((target == null) || (target.isInOlympiadMode() && (!player.isOlympiadStart() || (player.getOlympiadGameId() != target.getOlympiadGameId()))))
            {
                // if Player is in Olympia and the match isn't already start, send a Server->Client packet ActionFailed
                player.sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }
        }
        if ((player.getTarget() != null) && !player.getTarget().canBeAttacked() && !player.getAccessLevel().allowPeaceAttack())
        {
            // If target is not attackable, send a Server->Client packet ActionFailed
            player.sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }
        if (player.isConfused())
        {
            // If target is confused, send a Server->Client packet ActionFailed
            player.sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }
        
        // GeoData Los Check or dz > 1000
        // if (!GeoEngine.getInstance().canSeeTarget(player, this))
        // {
        // player.sendPacket(SystemMessageId.CANNOT_SEE_TARGET);
        // player.sendPacket(ActionFailed.STATIC_PACKET);
        // return;
        // }
        
        if (player.getBlockCheckerArena() != -1)
        {
            player.sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }
        // Notify AI with ATTACK
        player.getAI().setIntention(Intention.ATTACK, this);
    }
    
    /**
     * @param attacker
     * @return True if inside peace zone.
     */
    public boolean isInsidePeaceZone(WorldObject attacker)
    {
        return isInsidePeaceZone(attacker, this);
    }
    
    public boolean isInsidePeaceZone(WorldObject attacker, WorldObject target)
    {
        final Instance instanceWorld = getInstanceWorld();
        if ((target == null) || !((target.isPlayable() || target.isFakePlayer()) && attacker.isPlayable()) || ((instanceWorld != null) && instanceWorld.isPvP()))
        {
            return false;
        }
        
        final Player attackerPlayer = attacker.asPlayer();
        if (Config.ALT_GAME_KARMA_PLAYER_CAN_BE_KILLED_IN_PEACEZONE)
        {
            // Allows red to be attacked and red to attack flagged players.
            final Player targetPlayer = target.asPlayer();
            if ((targetPlayer != null) && (targetPlayer.getReputation() < 0))
            {
                return false;
            }
            
            if ((attackerPlayer != null) && (attackerPlayer.getReputation() < 0) && (targetPlayer != null) && (targetPlayer.getPvpFlag() > 0))
            {
                return false;
            }
        }
        
        if ((attackerPlayer != null) && attackerPlayer.getAccessLevel().allowPeaceAttack())
        {
            return false;
        }
        
        return (target.isInsideZone(ZoneId.PEACE) || attacker.isInsideZone(ZoneId.PEACE) || target.isInsideZone(ZoneId.NO_PVP) || attacker.isInsideZone(ZoneId.NO_PVP));
    }
    
    /**
     * @return true if this character is inside an active grid.
     */
    public boolean isInActiveRegion()
    {
        final WorldRegion region = getWorldRegion();
        return ((region != null) && (region.isActive()));
    }
    
    /**
     * @return True if the Creature has a Party in progress.
     */
    public boolean isInParty()
    {
        return false;
    }
    
    /**
     * @return the Party object of the Creature.
     */
    public Party getParty()
    {
        return null;
    }
    
    /**
     * Add a skill to the Creature _skills and its Func objects to the calculator set of the Creature.<br>
     * <br>
     * <b><u>Concept</u>:</b><br>
     * <br>
     * All skills own by a Creature are identified in <b>_skills</b><br>
     * <br>
     * <b><u>Actions</u>:</b>
     * <ul>
     * <li>Replace oldSkill by newSkill or Add the newSkill</li>
     * <li>If an old skill has been replaced, remove all its Func objects of Creature calculator set</li>
     * <li>Add Func objects of newSkill to the calculator set of the Creature</li>
     * </ul>
     * <br>
     * <b><u>Overridden in</u>:</b>
     * <ul>
     * <li>Player : Save update in the character_skills table of the database</li>
     * </ul>
     * @param skill The Skill to add to the Creature
     * @return The Skill replaced or null if just added a new Skill
     */
    public Skill addSkill(Skill skill)
    {
        Skill oldSkill = null;
        Skill newSkill = skill;
        if (newSkill != null)
        {
            // Mobius: Keep sublevel on skill level increase.
            final Skill existingSkill = _skills.get(newSkill.getId());
            if ((existingSkill != null) && (existingSkill.getSubLevel() > 0) && (newSkill.getSubLevel() == 0) && (existingSkill.getLevel() < newSkill.getLevel()))
            {
                newSkill = SkillData.getInstance().getSkill(newSkill.getId(), newSkill.getLevel(), existingSkill.getSubLevel());
            }
            
            // Replace oldSkill by newSkill or Add the newSkill
            oldSkill = _skills.put(newSkill.getId(), newSkill);
            // If an old skill has been replaced, remove all its Func objects
            if (oldSkill != null)
            {
                // Stop all effects of that skill
                if (oldSkill.isPassive())
                {
                    _effectList.stopSkillEffects(SkillFinishType.REMOVED, oldSkill);
                }
                
                _stat.recalculateStats(true);
            }
            
            if (newSkill.isPassive())
            {
                newSkill.applyEffects(this, this, false, true, false, 0, null);
            }
        }
        return oldSkill;
    }
    
    public Skill removeSkill(Skill skill, boolean cancelEffect)
    {
        return (skill != null) ? removeSkill(skill.getId(), cancelEffect) : null;
    }
    
    public Skill removeSkill(int skillId)
    {
        return removeSkill(skillId, true);
    }
    
    public Skill removeSkill(int skillId, boolean cancelEffect)
    {
        // Remove the skill from the Creature _skills
        final Skill oldSkill = _skills.remove(skillId);
        // Remove all its Func objects from the Creature calculator set
        if (oldSkill != null)
        {
            // Stop casting if this skill is used right now
            abortCast(s -> s.getSkill().getId() == skillId);
            
            // Stop effects.
            if (cancelEffect || oldSkill.isToggle() || oldSkill.isPassive())
            {
                stopSkillEffects(SkillFinishType.REMOVED, oldSkill.getId());
                _stat.recalculateStats(true);
            }
        }
        return oldSkill;
    }
    
    /**
     * @return all skills this creature currently has.
     */
    public Collection<Skill> getAllSkills()
    {
        return _skills.values();
    }
    
    public void removeAllSkills()
    {
        if (isPlayer())
        {
            final Player player = asPlayer();
            while (!_skills.isEmpty())
            {
                player.removeSkill(_skills.firstEntry().getValue());
            }
        }
        else
        {
            while (!_skills.isEmpty())
            {
                removeSkill(_skills.firstEntry().getValue(), true);
            }
        }
    }
    
    /**
     * @return the map containing this character skills.
     */
    public Map<Integer, Skill> getSkills()
    {
        return _skills;
    }
    
    /**
     * Return the level of a skill owned by the Creature.
     * @param skillId The identifier of the Skill whose level must be returned
     * @return The level of the Skill identified by skillId
     */
    public int getSkillLevel(int skillId)
    {
        final Skill skill = getKnownSkill(skillId);
        return (skill == null) ? 0 : skill.getLevel();
    }
    
    /**
     * @param skillId The identifier of the Skill to check the knowledge
     * @return the skill from the known skill.
     */
    public Skill getKnownSkill(int skillId)
    {
        return _skills.get(skillId);
    }
    
    /**
     * Return the number of buffs affecting this Creature.
     * @return The number of Buffs affecting this Creature
     */
    public int getBuffCount()
    {
        return _effectList.getBuffCount();
    }
    
    public int getDanceCount()
    {
        return _effectList.getDanceCount();
    }
    
    // Quest event ON_SPELL_FNISHED
    public void notifyQuestEventSkillFinished(Skill skill, WorldObject target)
    {
    }
    
    /**
     * @return the Level Modifier ((level + 89) / 100).
     */
    public double getLevelMod()
    {
        // Untested: (lvl + 89 + unk5,5forSkill4.0Else * odyssey_lvl_mod) / 100; odyssey_lvl_mod = (lvl-99) min 0.
        final double defaultLevelMod = ((getLevel() + 89) / 100d);
        return _transform.filter(transform -> !transform.isStance()).map(transform -> transform.getLevelMod(this)).orElse(defaultLevelMod);
    }
    
    private boolean _disabledAI = false;
    
    /**
     * Dummy value that gets overriden in Playable.
     * @return 0
     */
    public byte getPvpFlag()
    {
        return 0;
    }
    
    public void updatePvPFlag(int value)
    {
        // Overridden in Player
    }
    
    /**
     * @return a multiplier based on weapon random damage
     */
    public double getRandomDamageMultiplier()
    {
        final int random = (int) _stat.getValue(Stat.RANDOM_DAMAGE);
        return (1 + ((double) Rnd.get(-random, random) / 100));
    }
    
    public long getAttackEndTime()
    {
        return _attackEndTime;
    }
    
    public long getRangedAttackEndTime()
    {
        return _disableRangedAttackEndTime;
    }
    
    /**
     * Not Implemented.
     * @return
     */
    public abstract int getLevel();
    
    public int getAccuracy()
    {
        return _stat.getAccuracy();
    }
    
    public int getMagicAccuracy()
    {
        return _stat.getMagicAccuracy();
    }
    
    public int getMagicEvasionRate()
    {
        return _stat.getMagicEvasionRate();
    }
    
    public double getAttackSpeedMultiplier()
    {
        return _stat.getAttackSpeedMultiplier();
    }
    
    public double getCriticalDmg(int init)
    {
        return _stat.getCriticalDmg(init);
    }
    
    public int getCriticalHit()
    {
        return _stat.getCriticalHit();
    }
    
    public int getPSkillCriticalRate()
    {
        return _stat.getPSkillCriticalRate();
    }
    
    public int getEvasionRate()
    {
        return _stat.getEvasionRate();
    }
    
    public int getMagicalAttackRange(Skill skill)
    {
        return _stat.getMagicalAttackRange(skill);
    }
    
    public int getMaxCp()
    {
        return _stat.getMaxCp();
    }
    
    public int getMaxRecoverableCp()
    {
        return _stat.getMaxRecoverableCp();
    }
    
    public int getMAtk()
    {
        return _stat.getMAtk();
    }
    
    public int getWeaponBonusMAtk()
    {
        return _stat.getWeaponBonusMAtk();
    }
    
    public int getMAtkSpd()
    {
        return _stat.getMAtkSpd();
    }
    
    public int getMaxMp()
    {
        return _stat.getMaxMp();
    }
    
    public int getMaxRecoverableMp()
    {
        return _stat.getMaxRecoverableMp();
    }
    
    public long getMaxHp()
    {
        return _stat.getMaxHp();
    }
    
    public long getMaxRecoverableHp()
    {
        return _stat.getMaxRecoverableHp();
    }
    
    public int getMCriticalHit()
    {
        return _stat.getMCriticalHit();
    }
    
    public int getMDef()
    {
        return _stat.getMDef();
    }
    
    public int getPAtk()
    {
        return _stat.getPAtk();
    }
    
    public int getWeaponBonusPAtk()
    {
        return _stat.getWeaponBonusPAtk();
    }
    
    public int getPAtkSpd()
    {
        return _stat.getPAtkSpd();
    }
    
    public int getPDef()
    {
        return _stat.getPDef();
    }
    
    public int getPhysicalAttackRange()
    {
        return _stat.getPhysicalAttackRange();
    }
    
    public double getMovementSpeedMultiplier()
    {
        return _stat.getMovementSpeedMultiplier();
    }
    
    public double getRunSpeed()
    {
        return _stat.getRunSpeed();
    }
    
    public double getWalkSpeed()
    {
        return _stat.getWalkSpeed();
    }
    
    public double getSwimRunSpeed()
    {
        return _stat.getSwimRunSpeed();
    }
    
    public double getSwimWalkSpeed()
    {
        return _stat.getSwimWalkSpeed();
    }
    
    public double getMoveSpeed()
    {
        return _stat.getMoveSpeed();
    }
    
    public int getShldDef()
    {
        return _stat.getShldDef();
    }
    
    public int getSTR()
    {
        return _stat.getSTR();
    }
    
    public int getDEX()
    {
        return _stat.getDEX();
    }
    
    public int getCON()
    {
        return _stat.getCON();
    }
    
    public int getINT()
    {
        return _stat.getINT();
    }
    
    public int getWIT()
    {
        return _stat.getWIT();
    }
    
    public int getMEN()
    {
        return _stat.getMEN();
    }
    
    public int getLUC()
    {
        return _stat.getLUC();
    }
    
    public int getCHA()
    {
        return _stat.getCHA();
    }
    
    // Status - NEED TO REMOVE ONCE CREATURESTATUS IS COMPLETE
    public void addStatusListener(Creature object)
    {
        _status.addStatusListener(object);
    }
    
    public void doAttack(double damageValue, Creature target, Skill skill, boolean isDOT, boolean directlyToHp, boolean critical, boolean reflect)
    {
        // Check if fake players should aggro each other.
        if (isFakePlayer() && !Config.FAKE_PLAYER_AGGRO_FPC && target.isFakePlayer())
        {
            return;
        }
        
        // Start attack stance and notify being attacked.
        if (target.hasAI())
        {
            target.getAI().clientStartAutoAttack();
            target.getAI().notifyAction(Action.ATTACKED, this);
        }
        getAI().clientStartAutoAttack();
        
        // ImmobileDamageBonus and ImmobileDamageResist effect bonuses.
        double damage = damageValue;
        if (target.isImmobilized())
        {
            damage *= _stat.getMul(Stat.IMMOBILE_DAMAGE_BONUS, 1);
            damage *= Math.max(0.22, target.getStat().getMul(Stat.IMMOBILE_DAMAGE_RESIST, 1));
        }
        
        if (!reflect && !isDOT)
        {
            // RearDamage effect bonus.
            if (isBehind(target))
            {
                damage *= _stat.getMul(Stat.REAR_DAMAGE_RATE, 1);
            }
            
            // Counterattacks happen before damage received.
            if (!target.isDead() && (skill != null))
            {
                Formulas.calcCounterAttack(this, target, skill, true);
                
                // Shield Deflect Magic: Reflect all damage on caster.
                if (skill.isMagic() && (target.getStat().getValue(Stat.VENGEANCE_SKILL_MAGIC_DAMAGE, 0) > Rnd.get(100)))
                {
                    reduceCurrentHp(damage, target, skill, isDOT, directlyToHp, critical, true);
                    return;
                }
            }
        }
        
        // Absorb HP from the damage inflicted.
        final boolean isPvP = isPlayable() && (target.isPlayable() || target.isFakePlayer());
        if (!isPvP || Config.VAMPIRIC_ATTACK_AFFECTS_PVP)
        {
            if ((skill == null) || Config.VAMPIRIC_ATTACK_WORKS_WITH_SKILLS)
            {
                final double absorbHpPercent = getStat().getValue(Stat.ABSORB_DAMAGE_PERCENT, 0);
                if ((absorbHpPercent > 0) && (Rnd.nextDouble() < _stat.getValue(Stat.ABSORB_DAMAGE_CHANCE)))
                {
                    int absorbDamage = (int) Math.min(absorbHpPercent * damage, _stat.getMaxRecoverableHp() - _status.getCurrentHp());
                    absorbDamage = Math.min(absorbDamage, (int) target.getCurrentHp());
                    absorbDamage *= target.getStat().getValue(Stat.ABSORB_DAMAGE_DEFENCE, 1);
                    if (absorbDamage > 0)
                    {
                        setCurrentHp(_status.getCurrentHp() + absorbDamage);
                    }
                }
            }
        }
        
        // Absorb MP from the damage inflicted.
        if (!isPvP || Config.MP_VAMPIRIC_ATTACK_AFFECTS_PVP)
        {
            if ((skill != null) || Config.MP_VAMPIRIC_ATTACK_WORKS_WITH_MELEE)
            {
                final double absorbMpPercent = _stat.getValue(Stat.ABSORB_MANA_DAMAGE_PERCENT, 0);
                if ((absorbMpPercent > 0) && (Rnd.nextDouble() < _stat.getValue(Stat.ABSORB_MANA_DAMAGE_CHANCE)))
                {
                    int absorbDamage = (int) Math.min(absorbMpPercent * damage, _stat.getMaxRecoverableMp() - _status.getCurrentMp());
                    absorbDamage = Math.min(absorbDamage, (int) target.getCurrentMp());
                    if (absorbDamage > 0)
                    {
                        setCurrentMp(_status.getCurrentMp() + absorbDamage);
                    }
                }
            }
        }
        
        // Target receives the damage.
        target.reduceCurrentHp(damage, this, skill, isDOT, directlyToHp, critical, reflect);
        
        // Check if damage should be reflected or absorbed. When killing blow is made, the target doesn't reflect (vamp too?).
        if (!reflect && !isDOT && !target.isDead() && !target.isHpBlocked())
        {
            int reflectedDamage = 0;
            
            // Reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary
            final double reflectPercent = Math.min(target.getStat().getValue(Stat.REFLECT_DAMAGE_PERCENT, 0) - getStat().getValue(Stat.REFLECT_DAMAGE_PERCENT_DEFENSE, 0), target.isPlayer() ? Config.PLAYER_REFLECT_PERCENT_LIMIT : Config.NON_PLAYER_REFLECT_PERCENT_LIMIT);
            if (reflectPercent > 0)
            {
                reflectedDamage = (int) ((reflectPercent / 100.) * damage);
                reflectedDamage = (int) Math.min(reflectedDamage, target.getMaxHp());
                
                // Reflected damage is limited by P.Def/M.Def
                if ((skill != null) && skill.isMagic())
                {
                    reflectedDamage = (int) Math.min(reflectedDamage, target.getStat().getMDef() * 1.5);
                }
                else
                {
                    reflectedDamage = Math.min(reflectedDamage, target.getStat().getPDef());
                }
            }
            
            if (reflectedDamage > 0)
            {
                target.doAttack(reflectedDamage, this, skill, isDOT, directlyToHp, critical, true);
            }
        }
        
        // Break casting of target during attack.
        if (!target.isRaid() && Formulas.calcAtkBreak(target, damage))
        {
            target.breakAttack();
            target.breakCast();
        }
    }
    
    public void reduceCurrentHp(double amount, Creature attacker, Skill skill)
    {
        reduceCurrentHp(amount, attacker, skill, false, false, false, false);
    }
    
    public void reduceCurrentHp(double amountValue, Creature attacker, Skill skill, boolean isDOT, boolean directlyToHp, boolean critical, boolean reflect)
    {
        double amount = amountValue;
        
        // Auto attacks make you stand up.
        if (isPlayer() && asPlayer().isFakeDeath() && Config.FAKE_DEATH_DAMAGE_STAND && (amount > 0))
        {
            stopFakeDeath(true);
        }
        
        // Notify of this attack only if there is an attacking creature.
        if ((attacker != null) && EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DAMAGE_DEALT, attacker))
        {
            if (_onCreatureDamageDealt == null)
            {
                _onCreatureDamageDealt = new OnCreatureDamageDealt();
            }
            _onCreatureDamageDealt.setAttacker(attacker);
            _onCreatureDamageDealt.setTarget(this);
            _onCreatureDamageDealt.setDamage(amount);
            _onCreatureDamageDealt.setSkill(skill);
            _onCreatureDamageDealt.setCritical(critical);
            _onCreatureDamageDealt.setDamageOverTime(isDOT);
            _onCreatureDamageDealt.setReflect(reflect);
            EventDispatcher.getInstance().notifyEvent(_onCreatureDamageDealt, attacker);
        }
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_DAMAGE_RECEIVED, this))
        {
            if (_onCreatureDamageReceived == null)
            {
                _onCreatureDamageReceived = new OnCreatureDamageReceived();
            }
            _onCreatureDamageReceived.setAttacker(attacker);
            _onCreatureDamageReceived.setTarget(this);
            _onCreatureDamageReceived.setDamage(amount);
            _onCreatureDamageReceived.setSkill(skill);
            _onCreatureDamageReceived.setCritical(critical);
            _onCreatureDamageReceived.setDamageOverTime(isDOT);
            _onCreatureDamageReceived.setReflect(reflect);
            final DamageReturn term = EventDispatcher.getInstance().notifyEvent(_onCreatureDamageReceived, this, DamageReturn.class);
            if (term != null)
            {
                if (term.terminate())
                {
                    return;
                }
                else if (term.override())
                {
                    amount = term.getDamage();
                }
            }
        }
        
        final double damageCap = _stat.getValue(Stat.DAMAGE_LIMIT);
        if (damageCap > 0)
        {
            amount = Math.min(amount, damageCap);
        }
        
        // Calculate PvP/PvE damage received. It is a post-attack stat.
        if (attacker != null)
        {
            if (attacker.isPlayable())
            {
                amount *= (100 + Math.max(_stat.getValue(Stat.PVP_DAMAGE_TAKEN), -80)) / 100;
            }
            else
            {
                amount *= (100 + Math.max(_stat.getValue(Stat.PVE_DAMAGE_TAKEN), -80)) / 100;
            }
            
            if (attacker.isRaid() || attacker.isRaidMinion())
            {
                amount *= (100 + Math.max(_stat.getValue(Stat.PVE_DAMAGE_TAKEN_RAID), -80)) / 100;
            }
            else if (attacker.isMonster())
            {
                amount *= (100 + Math.max(_stat.getValue(Stat.PVE_DAMAGE_TAKEN_MONSTER), -80)) / 100;
            }
        }
        
        if (Config.CHAMPION_ENABLE && isChampion() && (Config.CHAMPION_HP != 0))
        {
            _status.reduceHp(amount / Config.CHAMPION_HP, attacker, (skill == null) || !skill.isToggle(), isDOT, false);
        }
        else if (isPlayer())
        {
            final Player player = asPlayer();
            player.addDamageTaken(attacker, skill != null ? skill.getDisplayId() : 0, amount, isDOT, reflect);
            if (!isDOT && (skill != null) && (skill.getCastRange() > 0) && (attacker != null) && !GeoEngine.getInstance().canSeeTarget(attacker, this))
            {
                amount = 0;
            }
            player.getStatus().reduceHp(amount, attacker, skill, (skill == null) || !skill.isToggle(), isDOT, false, directlyToHp);
        }
        else
        {
            _status.reduceHp(amount, attacker, (skill == null) || !skill.isToggle(), isDOT, false);
        }
        
        if (attacker != null)
        {
            attacker.sendDamageMessage(this, skill, (int) amount, critical, false);
        }
    }
    
    public void reduceCurrentMp(double amount)
    {
        _status.reduceMp(amount);
    }
    
    @Override
    public void removeStatusListener(Creature object)
    {
        _status.removeStatusListener(object);
    }
    
    protected void stopHpMpRegeneration()
    {
        _status.stopHpMpRegeneration();
    }
    
    public double getCurrentCp()
    {
        return _status.getCurrentCp();
    }
    
    public int getCurrentCpPercent()
    {
        return (int) ((_status.getCurrentCp() * 100) / _stat.getMaxCp());
    }
    
    public void setCurrentCp(double newCp)
    {
        _status.setCurrentCp(newCp);
    }
    
    public void setCurrentCp(double newCp, boolean broadcast)
    {
        _status.setCurrentCp(newCp, broadcast);
    }
    
    public double getCurrentHp()
    {
        return _status.getCurrentHp();
    }
    
    public int getCurrentHpPercent()
    {
        return (int) ((_status.getCurrentHp() * 100) / _stat.getMaxHp());
    }
    
    public void setCurrentHp(double newHp)
    {
        _status.setCurrentHp(newHp);
    }
    
    public void setCurrentHp(double newHp, boolean broadcast)
    {
        _status.setCurrentHp(newHp, broadcast);
    }
    
    public void setCurrentHpMp(double newHp, double newMp)
    {
        _status.setCurrentHpMp(newHp, newMp);
    }
    
    public double getCurrentMp()
    {
        return _status.getCurrentMp();
    }
    
    public int getCurrentMpPercent()
    {
        return (int) ((_status.getCurrentMp() * 100) / _stat.getMaxMp());
    }
    
    public void setCurrentMp(double newMp)
    {
        _status.setCurrentMp(newMp);
    }
    
    public void setCurrentMp(double newMp, boolean broadcast)
    {
        _status.setCurrentMp(newMp, false);
    }
    
    /**
     * @return the max weight that the Creature can load.
     */
    public int getMaxLoad()
    {
        if (isPlayer() || isPet())
        {
            // Weight Limit = (CON Modifier*69000) * Skills
            // Source http://l2p.bravehost.com/weightlimit.html (May 2007)
            final double baseLoad = Math.floor(BaseStat.CON.calcBonus(this) * 69000 * Config.ALT_WEIGHT_LIMIT);
            return (int) _stat.getValue(Stat.WEIGHT_LIMIT, baseLoad);
        }
        return 0;
    }
    
    public int getBonusWeightPenalty()
    {
        if (isPlayer() || isPet())
        {
            return (int) _stat.getValue(Stat.WEIGHT_PENALTY, 1);
        }
        return 0;
    }
    
    /**
     * @return the current weight of the Creature.
     */
    public int getCurrentLoad()
    {
        if (isPlayer() || isPet())
        {
            return getInventory().getTotalWeight();
        }
        return 0;
    }
    
    public boolean isChampion()
    {
        return false;
    }
    
    /**
     * Send system message about damage.
     * @param target
     * @param skill
     * @param damage
     * @param crit
     * @param miss
     */
    public void sendDamageMessage(Creature target, Skill skill, int damage, boolean crit, boolean miss)
    {
    }
    
    public AttributeType getAttackElement()
    {
        return _stat.getAttackElement();
    }
    
    public int getAttackElementValue(AttributeType attackAttribute)
    {
        return _stat.getAttackElementValue(attackAttribute);
    }
    
    public int getDefenseElementValue(AttributeType defenseAttribute)
    {
        return _stat.getDefenseElementValue(defenseAttribute);
    }
    
    public void disableCoreAI(boolean value)
    {
        _disabledAI = value;
    }
    
    public boolean isCoreAIDisabled()
    {
        return _disabledAI;
    }
    
    /**
     * @return true
     */
    public boolean giveRaidCurse()
    {
        return false;
    }
    
    /**
     * Check if target is affected with special buff
     * @param flag int
     * @return boolean
     * @see EffectList#isAffected(EffectFlag)
     */
    public boolean isAffected(EffectFlag flag)
    {
        return _effectList.isAffected(flag);
    }
    
    public Team getTeam()
    {
        return _team;
    }
    
    public void setTeam(Team team)
    {
        _team = team;
    }
    
    public void addOverrideCond(PlayerCondOverride... excs)
    {
        for (PlayerCondOverride exc : excs)
        {
            _exceptions |= exc.getMask();
        }
    }
    
    public void removeOverridedCond(PlayerCondOverride... excs)
    {
        for (PlayerCondOverride exc : excs)
        {
            _exceptions &= ~exc.getMask();
        }
    }
    
    public boolean canOverrideCond(PlayerCondOverride excs)
    {
        return (_exceptions & excs.getMask()) == excs.getMask();
    }
    
    public void setOverrideCond(long masks)
    {
        _exceptions = masks;
    }
    
    public void setLethalable(boolean value)
    {
        _lethalable = value;
    }
    
    public boolean isLethalable()
    {
        return _lethalable;
    }
    
    public boolean hasTriggerSkills()
    {
        return !_triggerSkills.isEmpty();
    }
    
    public Map<Integer, OptionSkillHolder> getTriggerSkills()
    {
        return _triggerSkills;
    }
    
    public void addTriggerSkill(OptionSkillHolder holder)
    {
        getTriggerSkills().put(holder.getSkill().getId(), holder);
    }
    
    public void removeTriggerSkill(OptionSkillHolder holder)
    {
        getTriggerSkills().remove(holder.getSkill().getId());
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @return {@code true} if current player can revive and shows 'To Village' button upon death, {@code false} otherwise.
     */
    public boolean canRevive()
    {
        return true;
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @param value
     */
    public void setCanRevive(boolean value)
    {
    }
    
    /**
     * Dummy method overriden in {@link Attackable}
     * @return {@code true} if there is a loot to sweep, {@code false} otherwise.
     */
    public boolean isSweepActive()
    {
        return false;
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @return the clan id of current character.
     */
    public int getClanId()
    {
        return 0;
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @return the clan of current character.
     */
    public Clan getClan()
    {
        return null;
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @return {@code true} if player is in academy, {@code false} otherwise.
     */
    public boolean isAcademyMember()
    {
        return false;
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @return the pledge type of current character.
     */
    public int getPledgeType()
    {
        return 0;
    }
    
    /**
     * Dummy method overriden in {@link Player}
     * @return the alliance id of current character.
     */
    public int getAllyId()
    {
        return 0;
    }
    
    /**
     * Notifies to listeners that current character avoid attack.
     * @param target
     * @param isDot
     */
    public void notifyAttackAvoid(Creature target, boolean isDot)
    {
        if (EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_ATTACK_AVOID, target))
        {
            if (_onCreatureAttackAvoid == null)
            {
                _onCreatureAttackAvoid = new OnCreatureAttackAvoid();
            }
            _onCreatureAttackAvoid.setAttacker(this);
            _onCreatureAttackAvoid.setTarget(target);
            _onCreatureAttackAvoid.setDamageOverTime(isDot);
            EventDispatcher.getInstance().notifyEvent(_onCreatureAttackAvoid, target);
        }
    }
    
    /**
     * @return {@link WeaponType} of current character's weapon or basic weapon type.
     */
    public WeaponType getAttackType()
    {
        final Weapon weapon = getActiveWeaponItem();
        if (weapon != null)
        {
            return weapon.getItemType();
        }
        
        final WeaponType defaultWeaponType = _template.getBaseAttackType();
        return _transform.map(transform -> transform.getBaseAttackType(this, defaultWeaponType)).orElse(defaultWeaponType);
    }
    
    public boolean isInCategory(CategoryType type)
    {
        return CategoryData.getInstance().isInCategory(type, getId());
    }
    
    public boolean isInOneOfCategory(CategoryType... types)
    {
        for (CategoryType type : types)
        {
            if (CategoryData.getInstance().isInCategory(type, getId()))
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * @return the character that summoned this NPC.
     */
    public Creature getSummoner()
    {
        return _summoner;
    }
    
    /**
     * @param summoner the summoner of this NPC.
     */
    public void setSummoner(Creature summoner)
    {
        _summoner = summoner;
    }
    
    /**
     * Adds a summoned NPC.
     * @param npc the summoned NPC
     */
    public void addSummonedNpc(Npc npc)
    {
        if (_summonedNpcs == null)
        {
            synchronized (this)
            {
                if (_summonedNpcs == null)
                {
                    _summonedNpcs = new ConcurrentHashMap<>();
                }
            }
        }
        
        _summonedNpcs.put(npc.getObjectId(), npc);
        
        npc.setSummoner(this);
    }
    
    /**
     * Removes a summoned NPC by object ID.
     * @param objectId the summoned NPC object ID
     */
    public void removeSummonedNpc(int objectId)
    {
        if (_summonedNpcs != null)
        {
            _summonedNpcs.remove(objectId);
        }
    }
    
    /**
     * Gets the summoned NPCs.
     * @return the summoned NPCs
     */
    public Collection<Npc> getSummonedNpcs()
    {
        return _summonedNpcs != null ? _summonedNpcs.values() : Collections.emptyList();
    }
    
    /**
     * Gets the summoned NPC by object ID.
     * @param objectId the summoned NPC object ID
     * @return the summoned NPC
     */
    public Npc getSummonedNpc(int objectId)
    {
        if (_summonedNpcs != null)
        {
            return _summonedNpcs.get(objectId);
        }
        return null;
    }
    
    /**
     * Gets the summoned NPC count.
     * @return the summoned NPC count
     */
    public int getSummonedNpcCount()
    {
        return _summonedNpcs != null ? _summonedNpcs.size() : 0;
    }
    
    /**
     * Resets the summoned NPCs list.
     */
    public void resetSummonedNpcs()
    {
        if (_summonedNpcs != null)
        {
            _summonedNpcs.clear();
        }
    }
    
    @Override
    public boolean isCreature()
    {
        return true;
    }
    
    @Override
    public Creature asCreature()
    {
        return this;
    }
    
    public int getMinShopDistance()
    {
        return 0;
    }
    
    public Collection<SkillCaster> getSkillCasters()
    {
        return _skillCasters.values();
    }
    
    public SkillCaster addSkillCaster(SkillCastingType castingType, SkillCaster skillCaster)
    {
        return _skillCasters.put(castingType, skillCaster);
    }
    
    public SkillCaster removeSkillCaster(SkillCastingType castingType)
    {
        return _skillCasters.remove(castingType);
    }
    
    @SafeVarargs
    public final List<SkillCaster> getSkillCasters(Predicate<SkillCaster> filterValue, Predicate<SkillCaster>... filters)
    {
        Predicate<SkillCaster> filter = filterValue;
        for (Predicate<SkillCaster> additionalFilter : filters)
        {
            filter = filter.and(additionalFilter);
        }
        
        final List<SkillCaster> result = new ArrayList<>();
        for (SkillCaster skillCaster : _skillCasters.values())
        {
            if (filter.test(skillCaster))
            {
                result.add(skillCaster);
            }
        }
        return result;
    }
    
    @SafeVarargs
    public final SkillCaster getSkillCaster(Predicate<SkillCaster> filterValue, Predicate<SkillCaster>... filters)
    {
        Predicate<SkillCaster> filter = filterValue;
        for (Predicate<SkillCaster> additionalFilter : filters)
        {
            filter = filter.and(additionalFilter);
        }
        
        for (SkillCaster skillCaster : _skillCasters.values())
        {
            if (filter.test(skillCaster))
            {
                return skillCaster;
            }
        }
        return null;
    }
    
    /**
     * @return {@code true} if current character is casting channeling skill, {@code false} otherwise.
     */
    public boolean isChanneling()
    {
        return (_channelizer != null) && _channelizer.isChanneling();
    }
    
    public SkillChannelizer getSkillChannelizer()
    {
        if (_channelizer == null)
        {
            _channelizer = new SkillChannelizer(this);
        }
        return _channelizer;
    }
    
    /**
     * @return {@code true} if current character is affected by channeling skill, {@code false} otherwise.
     */
    public boolean isChannelized()
    {
        return (_channelized != null) && !_channelized.isChannelized();
    }
    
    public SkillChannelized getSkillChannelized()
    {
        if (_channelized == null)
        {
            _channelized = new SkillChannelized();
        }
        return _channelized;
    }
    
    public void addIgnoreSkillEffects(SkillHolder holder)
    {
        final IgnoreSkillHolder ignoreSkillHolder = getIgnoreSkillEffects().get(holder.getSkillId());
        if (ignoreSkillHolder != null)
        {
            ignoreSkillHolder.increaseInstances();
            return;
        }
        getIgnoreSkillEffects().put(holder.getSkillId(), new IgnoreSkillHolder(holder));
    }
    
    public void removeIgnoreSkillEffects(SkillHolder holder)
    {
        final IgnoreSkillHolder ignoreSkillHolder = getIgnoreSkillEffects().get(holder.getSkillId());
        if ((ignoreSkillHolder != null) && (ignoreSkillHolder.decreaseInstances() < 1))
        {
            getIgnoreSkillEffects().remove(holder.getSkillId());
        }
    }
    
    public boolean isIgnoringSkillEffects(int skillId, int skillLevel)
    {
        if (!_ignoreSkillEffects.isEmpty())
        {
            final SkillHolder holder = getIgnoreSkillEffects().get(skillId);
            return ((holder != null) && ((holder.getSkillLevel() < 1) || (holder.getSkillLevel() == skillLevel)));
        }
        return false;
    }
    
    private Map<Integer, IgnoreSkillHolder> getIgnoreSkillEffects()
    {
        return _ignoreSkillEffects;
    }
    
    @Override
    public Collection<AbstractEventListener> getListeners(EventType type)
    {
        final Collection<AbstractEventListener> objectListeners = super.getListeners(type);
        final Collection<AbstractEventListener> templateListeners = _template.getListeners(type);
        final Collection<AbstractEventListener> globalListeners = isMonster() ? Containers.Monsters().getListeners(type) : isNpc() ? Containers.Npcs().getListeners(type) : isPlayer() ? Containers.Players().getListeners(type) : Collections.emptyList();
        
        // Avoid creating a new object.
        if (objectListeners.isEmpty() && templateListeners.isEmpty() && globalListeners.isEmpty())
        {
            return Collections.emptyList();
        }
        else if (!objectListeners.isEmpty() && templateListeners.isEmpty() && globalListeners.isEmpty())
        {
            return objectListeners;
        }
        else if (!templateListeners.isEmpty() && objectListeners.isEmpty() && globalListeners.isEmpty())
        {
            return templateListeners;
        }
        else if (!globalListeners.isEmpty() && objectListeners.isEmpty() && templateListeners.isEmpty())
        {
            return globalListeners;
        }
        
        final Collection<AbstractEventListener> allListeners = new ArrayList<>(objectListeners.size() + templateListeners.size() + globalListeners.size());
        allListeners.addAll(objectListeners);
        allListeners.addAll(templateListeners);
        allListeners.addAll(globalListeners);
        return allListeners;
    }
    
    public Race getRace()
    {
        return _template.getRace();
    }
    
    @Override
    public void setXYZ(int newX, int newY, int newZ)
    {
        // 0, 0 is not a valid location.
        if ((newX == 0) && (newY == 0))
        {
            return;
        }
        
        final ZoneRegion oldZoneRegion = ZoneManager.getInstance().getRegion(this);
        final ZoneRegion newZoneRegion = ZoneManager.getInstance().getRegion(newX, newY);
        
        // Mobius: Prevent moving to nonexistent regions.
        if (newZoneRegion == null)
        {
            return;
        }
        
        if (oldZoneRegion != newZoneRegion)
        {
            oldZoneRegion.removeFromZones(this);
            newZoneRegion.revalidateZones(this);
        }
        
        super.setXYZ(newX, newY, newZ);
    }
    
    public Map<Integer, RelationCache> getKnownRelations()
    {
        return _knownRelations;
    }
    
    @Override
    public boolean isTargetable()
    {
        return super.isTargetable() && !isAffected(EffectFlag.UNTARGETABLE);
    }
    
    public boolean isTargetingDisabled()
    {
        return isAffected(EffectFlag.TARGETING_DISABLED);
    }
    
    public boolean cannotEscape()
    {
        return isAffected(EffectFlag.CANNOT_ESCAPE);
    }
    
    /**
     * Sets amount of debuffs that player can avoid
     * @param times
     */
    public void setAbnormalShieldBlocks(int times)
    {
        _abnormalShieldBlocks.set(times);
    }
    
    /**
     * @return the amount of debuffs that player can avoid
     */
    public int getAbnormalShieldBlocks()
    {
        return _abnormalShieldBlocks.get();
    }
    
    /**
     * @return the amount of debuffs that player can avoid
     */
    public int decrementAbnormalShieldBlocks()
    {
        return _abnormalShieldBlocks.decrementAndGet();
    }
    
    /**
     * Adds a multiplied abnormal time for the specified skill.
     * @param skillId the ID of the skill to add multiplied abnormal time.
     * @param time the multiplier to apply for the abnormal time.
     */
    public void addMultipliedAbnormalTime(int skillId, double time)
    {
        final double oldTime = _multipliedAbnormalTimes.getOrDefault(skillId, 0d);
        if (oldTime != 0d)
        {
            _multipliedAbnormalTimes.put(skillId, oldTime * time);
        }
        else
        {
            _multipliedAbnormalTimes.put(skillId, time);
        }
    }
    
    /**
     * Adds an added abnormal time for the specified skill.
     * @param skillId the ID of the skill to add added abnormal time.
     * @param time the time value to add to the abnormal time.
     */
    public void addAddedAbnormalTime(int skillId, int time)
    {
        final int oldTime = _addedAbnormalTimes.getOrDefault(skillId, 0);
        if (oldTime != 0)
        {
            _addedAbnormalTimes.put(skillId, oldTime + time);
        }
        else
        {
            _addedAbnormalTimes.put(skillId, time);
        }
    }
    
    /**
     * Removes a multiplied abnormal time for the specified skill.
     * @param skillId the ID of the skill to remove multiplied abnormal time.
     * @param time the multiplier to divide the abnormal time.
     */
    public void removeMultipliedAbnormalTime(int skillId, double time)
    {
        final double oldTime = _multipliedAbnormalTimes.getOrDefault(skillId, 0d);
        if (oldTime == time)
        {
            _multipliedAbnormalTimes.remove(skillId);
        }
        else
        {
            _multipliedAbnormalTimes.put(skillId, oldTime / time);
        }
    }
    
    /**
     * Removes an added abnormal time for the specified skill.
     * @param skillId the ID of the skill to remove added abnormal time.
     * @param time the time value to subtract from the abnormal time.
     */
    public void removeAddedAbnormalTime(int skillId, int time)
    {
        final int oldTime = _addedAbnormalTimes.getOrDefault(skillId, 0);
        if (oldTime == time)
        {
            _addedAbnormalTimes.remove(skillId);
        }
        else
        {
            _addedAbnormalTimes.put(skillId, oldTime - time);
        }
    }
    
    /**
     * Gets the multiplied abnormal time for the specified skill.
     * @param skillId the ID of the skill to get the multiplied abnormal time.
     * @return the multiplied abnormal time for the skill, or 0 if none exists.
     */
    public double getMultipliedAbnormalTime(int skillId)
    {
        return _multipliedAbnormalTimes.getOrDefault(skillId, 0d);
    }
    
    /**
     * Gets the added abnormal time for the specified skill.
     * @param skillId the ID of the skill to get the added abnormal time.
     * @return the added abnormal time for the skill, or 0 if none exists.
     */
    public int getAddedAbnormalTime(int skillId)
    {
        return _addedAbnormalTimes.getOrDefault(skillId, 0);
    }
    
    public boolean hasAbnormalType(AbnormalType abnormalType)
    {
        return _effectList.hasAbnormalType(abnormalType);
    }
    
    public void addBlockActionsAllowedSkill(Integer skillId)
    {
        _blockActionsAllowedSkills.computeIfAbsent(skillId, k -> new AtomicInteger()).incrementAndGet();
    }
    
    public void removeBlockActionsAllowedSkill(Integer skillId)
    {
        _blockActionsAllowedSkills.computeIfPresent(skillId, (k, v) -> v.decrementAndGet() != 0 ? v : null);
    }
    
    public boolean isBlockedActionsAllowedSkill(Skill skill)
    {
        return _blockActionsAllowedSkills.containsKey(skill.getId());
    }
    
    protected void initSeenCreatures()
    {
        if (_seenCreatures == null)
        {
            synchronized (this)
            {
                if (_seenCreatures == null)
                {
                    if (isNpc())
                    {
                        final NpcTemplate template = asNpc().getTemplate();
                        if ((template != null) && (template.getAggroRange() > 0))
                        {
                            _seenCreatureRange = template.getAggroRange();
                        }
                    }
                    
                    _seenCreatures = ConcurrentHashMap.newKeySet(1);
                }
            }
        }
        
        CreatureSeeTaskManager.getInstance().add(this);
    }
    
    public void updateSeenCreatures()
    {
        if ((_seenCreatures == null) || _isDead || !isSpawned())
        {
            return;
        }
        
        // Check if region and its neighbors are active.
        final WorldRegion region = getWorldRegion();
        if ((region == null) || !region.areNeighborsActive())
        {
            return;
        }
        
        World.getInstance().forEachVisibleObjectInRange(this, Creature.class, _seenCreatureRange, creature ->
        {
            if (!creature.isInvisible() && _seenCreatures.add(creature) && EventDispatcher.getInstance().hasListener(EventType.ON_CREATURE_SEE, this))
            {
                EventDispatcher.getInstance().notifyEventAsync(new OnCreatureSee(this, creature), this);
            }
        });
    }
    
    public void removeSeenCreature(WorldObject worldObject)
    {
        if (_seenCreatures == null)
        {
            return;
        }
        
        _seenCreatures.remove(worldObject);
    }
    
    public MoveType getMoveType()
    {
        if (isMoving() && _isRunning)
        {
            return MoveType.RUNNING;
        }
        else if (isMoving() && !_isRunning)
        {
            return MoveType.WALKING;
        }
        return MoveType.STANDING;
    }
    
    protected void computeStatusUpdate(StatusUpdate su, StatusUpdateType type)
    {
        final long newValue = type.getValue(this);
        _statusUpdates.compute(type, (key, oldValue) ->
        {
            if ((oldValue == null) || (oldValue != newValue))
            {
                su.addUpdate(type, newValue);
                return newValue;
            }
            return oldValue;
        });
    }
    
    protected void addStatusUpdateValue(StatusUpdateType type)
    {
        _statusUpdates.put(type, type.getValue(this));
    }
    
    protected void initStatusUpdateCache()
    {
        addStatusUpdateValue(StatusUpdateType.MAX_HP);
        addStatusUpdateValue(StatusUpdateType.MAX_MP);
        addStatusUpdateValue(StatusUpdateType.CUR_HP);
        addStatusUpdateValue(StatusUpdateType.CUR_MP);
    }
    
    /**
     * Checks if the creature has basic property resist towards mesmerizing debuffs.
     * @return {@code true}.
     */
    public boolean hasBasicPropertyResist()
    {
        return true;
    }
    
    /**
     * Gets the basic property resist.
     * @param basicProperty the basic property
     * @return the basic property resist
     */
    public BasicPropertyResist getBasicPropertyResist(BasicProperty basicProperty)
    {
        return _basicPropertyResists.computeIfAbsent(basicProperty, k -> new BasicPropertyResist());
    }
    
    public int getReputation()
    {
        return _reputation;
    }
    
    public void setReputation(int reputation)
    {
        _reputation = reputation;
    }
    
    public boolean isChargedShot(ShotType type)
    {
        return _chargedShots.contains(type);
    }
    
    /**
     * @param type of the shot to charge
     * @return {@code true} if there was no shot of this type charged before, {@code false} otherwise.
     */
    public boolean chargeShot(ShotType type)
    {
        return _chargedShots.add(type);
    }
    
    /**
     * @param type of the shot to uncharge
     * @return {@code true} if there was a charged shot of this type, {@code false} otherwise.
     */
    public boolean unchargeShot(ShotType type)
    {
        return _chargedShots.remove(type);
    }
    
    public void unchargeAllShots()
    {
        _chargedShots = EnumSet.noneOf(ShotType.class);
    }
    
    public void rechargeShots(boolean physical, boolean magic, boolean fish)
    {
        // Dummy method to be overriden.
    }
    
    public void setCursorKeyMovement(boolean value)
    {
        _cursorKeyMovement = value;
    }
    
    public List<Item> getFakePlayerDrops()
    {
        return _fakePlayerDrops;
    }
    
    public void addBuffInfoTime(BuffInfo info)
    {
        _buffFinishTask.addBuffInfo(info);
    }
    
    public void removeBuffInfoTime(BuffInfo info)
    {
        _buffFinishTask.removeBuffInfo(info);
    }
}
 
Последнее редактирование модератором:
Да нее, ты не куда копнул, разговор об эффектах скилов, брат это точно не твоё дело, скоро включат ивент "Гильдия Алых весов", может смениш профу ;)

Это тут
..\game\data\scripts\handlers\effecthandlers\TriggerSkill.java
 
uter81, а вот интересно, ты судя по датам тем с 2022 во всем этом калупаешься. Хоть что-то удалось решить самостоятельно? Неужели за столько времени не приобрел никаких навыков? :LOL:
Посмотреть вложение 92805Посмотреть вложение 92806Посмотреть вложение 92807Посмотреть вложение 92808
приобрел, но в каждой сборке новый код, и новые баги, поэтому и спрашиваю, ведь форум для этого и создан, не так ли?

Да нее, ты не куда копнул, разговор об эффектах скилов, брат это точно не твоё дело, скоро включат ивент "Гильдия Алых весов", может смениш профу ;)

Это тут
..\game\data\scripts\handlers\effecthandlers\TriggerSkill.java
внес изменения, не работает :(
 
Последнее редактирование:
Ну за прошедшее время то можно было бы приобрести наверное достаточные навыки в программировании и в частности в яве.
И уже достаточно хорошо в итоге ориентироваться в коде любой, ну или почти любой сборки...

По сути многое из того что ты спрашиваешь в темах глубоких познаний в программировании и в структуре сборки не требует. И в целом достаточно легко чинится после минимального изучения кода, а так же разбора его работы к примеру в режиме отладки.
 
Последнее редактирование:

Похожие темы

Назад
Сверху Снизу