silentium.gameserver.model.actor.L2Character.java Source code

Java tutorial

Introduction

Here is the source code for silentium.gameserver.model.actor.L2Character.java

Source

/*
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */
package silentium.gameserver.model.actor;

import static silentium.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static silentium.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Future;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import silentium.commons.utils.Point3D;
import silentium.commons.utils.Rnd;
import silentium.gameserver.GameTimeController;
import silentium.gameserver.ThreadPoolManager;
import silentium.gameserver.ai.AttackableAI;
import silentium.gameserver.ai.CharacterAI;
import silentium.gameserver.ai.CtrlEvent;
import silentium.gameserver.ai.CtrlIntention;
import silentium.gameserver.configs.MainConfig;
import silentium.gameserver.configs.NPCConfig;
import silentium.gameserver.configs.PlayersConfig;
import silentium.gameserver.data.xml.DoorData;
import silentium.gameserver.data.xml.MapRegionData;
import silentium.gameserver.data.xml.MapRegionData.TeleportWhereType;
import silentium.gameserver.geo.GeoData;
import silentium.gameserver.geo.pathfinding.AbstractNodeLoc;
import silentium.gameserver.geo.pathfinding.PathFinding;
import silentium.gameserver.handler.ISkillHandler;
import silentium.gameserver.handler.SkillHandler;
import silentium.gameserver.instancemanager.DimensionalRiftManager;
import silentium.gameserver.instancemanager.TownManager;
import silentium.gameserver.model.ChanceSkillList;
import silentium.gameserver.model.CharEffectList;
import silentium.gameserver.model.FusionSkill;
import silentium.gameserver.model.IChanceSkillTrigger;
import silentium.gameserver.model.L2CharPosition;
import silentium.gameserver.model.L2Effect;
import silentium.gameserver.model.L2ItemInstance;
import silentium.gameserver.model.L2Object;
import silentium.gameserver.model.L2Party;
import silentium.gameserver.model.L2Skill;
import silentium.gameserver.model.L2Skill.SkillTargetType;
import silentium.gameserver.model.L2World;
import silentium.gameserver.model.L2WorldRegion;
import silentium.gameserver.model.Location;
import silentium.gameserver.model.actor.instance.L2DoorInstance;
import silentium.gameserver.model.actor.instance.L2NpcWalkerInstance;
import silentium.gameserver.model.actor.instance.L2PcInstance;
import silentium.gameserver.model.actor.instance.L2PcInstance.SkillDat;
import silentium.gameserver.model.actor.instance.L2PetInstance;
import silentium.gameserver.model.actor.instance.L2RiftInvaderInstance;
import silentium.gameserver.model.actor.knownlist.CharKnownList;
import silentium.gameserver.model.actor.position.CharPosition;
import silentium.gameserver.model.actor.stat.CharStat;
import silentium.gameserver.model.actor.status.CharStatus;
import silentium.gameserver.model.itemcontainer.Inventory;
import silentium.gameserver.model.quest.Quest;
import silentium.gameserver.network.SystemMessageId;
import silentium.gameserver.network.serverpackets.AbstractNpcInfo.NpcInfo;
import silentium.gameserver.network.serverpackets.*;
import silentium.gameserver.network.serverpackets.FlyToLocation.FlyType;
import silentium.gameserver.skills.AbnormalEffect;
import silentium.gameserver.skills.Calculator;
import silentium.gameserver.skills.Formulas;
import silentium.gameserver.skills.Stats;
import silentium.gameserver.skills.basefuncs.Func;
import silentium.gameserver.skills.effects.EffectChanceSkillTrigger;
import silentium.gameserver.tables.SkillTable.FrequentSkill;
import silentium.gameserver.taskmanager.AttackStanceTaskManager;
import silentium.gameserver.templates.chars.L2CharTemplate;
import silentium.gameserver.templates.chars.L2NpcTemplate;
import silentium.gameserver.templates.item.L2Item;
import silentium.gameserver.templates.item.L2Weapon;
import silentium.gameserver.templates.item.L2WeaponType;
import silentium.gameserver.templates.skills.L2EffectType;
import silentium.gameserver.templates.skills.L2SkillType;
import silentium.gameserver.utils.Broadcast;
import silentium.gameserver.utils.L2TIntObjectHashMap;
import silentium.gameserver.utils.Util;

import com.google.common.collect.Sets;

/**
 * L2Character is the mother class of all character objects of the world (PC, NPC...) :<br>
 * <br>
 * <b><u> Instances using it </u> :</b> <li>L2CastleGuardInstance</li> <li>L2DoorInstance</li> <li>L2Npc</li> <li>L2Playable</li>
 */
public abstract class L2Character extends L2Object {
    public static final Logger _log = LoggerFactory.getLogger(L2Character.class.getName());

    private Set<L2Character> _attackByList;

    private volatile boolean _isCastingNow = false;
    private volatile boolean _isCastingSimultaneouslyNow = false;
    private L2Skill _lastSkillCast;
    private L2Skill _lastSimultaneousSkillCast;

    private boolean _isFlying = false; // Is flying wyvern ?
    private boolean _isRiding = false; // Is riding strider ?

    private boolean _isImmobilized = false;
    private boolean _isOverloaded = false;
    private boolean _isParalyzed = false;
    private boolean _isDead = false;
    private boolean _isPendingRevive = false;
    private boolean _isRunning = false;
    protected boolean _isTeleporting = false;
    protected boolean _showSummonAnimation = false;

    protected boolean _isInvul = false;
    private boolean _isMortal = true;

    private boolean _isNoRndWalk = false;
    private boolean _AIdisabled = false;

    private CharStat _stat;
    private CharStatus _status;
    private L2CharTemplate _template; // The link on the L2CharTemplate object containing generic and static properties

    private String _title;
    private String _aiClass = "default";

    private double _hpUpdateIncCheck = .0;
    private double _hpUpdateDecCheck = .0;
    private double _hpUpdateInterval = .0;
    private boolean _champion = false;

    private Calculator[] _calculators;
    protected final L2TIntObjectHashMap<L2Skill> _skills;

    private ChanceSkillList _chanceSkills;
    protected FusionSkill _fusionSkill;

    /** Zone system */
    public static final byte ZONE_PVP = 0;
    public static final byte ZONE_PEACE = 1;
    public static final byte ZONE_SIEGE = 2;
    public static final byte ZONE_MOTHERTREE = 3;
    public static final byte ZONE_CLANHALL = 4;
    public static final byte ZONE_NOLANDING = 5;
    public static final byte ZONE_WATER = 6;
    public static final byte ZONE_JAIL = 7;
    public static final byte ZONE_MONSTERTRACK = 8;
    public static final byte ZONE_CASTLE = 9;
    public static final byte ZONE_SWAMP = 10;
    public static final byte ZONE_NOSUMMONFRIEND = 11;
    public static final byte ZONE_NOSTORE = 12;
    public static final byte ZONE_TOWN = 13;
    public static final byte ZONE_SCRIPT = 14;
    public static final byte ZONE_HQ = 15;
    public static final byte ZONE_DANGERAREA = 16;
    public static final byte ZONE_CASTONARTIFACT = 17;
    public static final byte ZONE_NORESTART = 18;

    private final byte[] _zones = new byte[19];
    protected byte _zoneValidateCounter = 4;

    private boolean _isRaid = false;

    /**
     * Constructor of L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * Each L2Character owns generic and static properties (ex : all Keltir have the same number of HP...). All of those properties are stored in
     * a different template for each type of L2Character. Each template is loaded once in the server cache memory (reduce memory use). When a new
     * instance of L2Character 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><BR>
     * <BR>
     * <li>Set the _template of the L2Character</li> <li>Set _overloaded to false (the charcater can take more items)</li><BR>
     * <BR>
     * <li>If L2Character is a L2Npc, copy skills from template to object</li> <li>If L2Character is a L2Npc, link _calculators to
     * NPC_STD_CALCULATOR</li><BR>
     * <BR>
     * <li>If L2Character is NOT a L2Npc, create an empty _skills slot</li> <li>If L2Character is a L2PcInstance or L2Summon, copy basic
     * Calculator set to object</li><BR>
     * <BR>
     * 
     * @param objectId
     *            Identifier of the object to initialized
     * @param template
     *            The L2CharTemplate to apply to the object
     */
    public L2Character(int objectId, L2CharTemplate template) {
        super(objectId);
        initCharStat();
        initCharStatus();

        // Set its template to the new L2Character
        _template = template;

        if (this instanceof L2Npc) {
            // Copy the Standard Calcultors of the L2Npc in _calculators
            _calculators = NPC_STD_CALCULATOR;

            // Copy the skills of the L2Npc from its template to the L2Character 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 L2Npc, affects others L2Npc of the same type too.
            _skills = new L2TIntObjectHashMap<>();
            if (((L2NpcTemplate) template).getSkills() != null)
                _skills.putAll(((L2NpcTemplate) template).getSkills());

            if (_skills != null) {
                for (L2Skill skill : getAllSkills())
                    addStatFuncs(skill.getStatFuncs(null, this));
            }
        } else {
            // If L2Character is a L2PcInstance or a L2Summon, create the basic calculator set
            _calculators = new Calculator[Stats.NUM_STATS];

            if (this instanceof L2Summon) {
                // Copy the skills of the L2Summon from its template to the L2Character 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 L2Summon, affects others L2Summon of the same type too.
                _skills = new L2TIntObjectHashMap<>();
                _skills.putAll(((L2NpcTemplate) template).getSkills());
                if (_skills != null) {
                    for (L2Skill skill : getAllSkills())
                        addStatFuncs(skill.getStatFuncs(null, this));
                }
            }
            // Initialize the FastMap _skills to null
            else
                _skills = new L2TIntObjectHashMap<>();

            Formulas.addFuncsToNewCharacter(this);
        }
    }

    protected void initCharStatusUpdateValues() {
        _hpUpdateInterval = getMaxHp() / 352.0; // MAX_HP div MAX_HP_BAR_PX
        _hpUpdateIncCheck = getMaxHp();
        _hpUpdateDecCheck = getMaxHp() - _hpUpdateInterval;
    }

    /**
     * Remove the L2Character from the world when the decay task is launched.<BR>
     * <BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T REMOVE the object from _allObjects of L2World </B></FONT><BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND Server->Client packets to players</B></FONT><BR>
     * <BR>
     */
    public void onDecay() {
        L2WorldRegion reg = getWorldRegion();
        decayMe();
        if (reg != null)
            reg.removeFromZones(this);
    }

    @Override
    public void onSpawn() {
        super.onSpawn();
        revalidateZone(true);
    }

    public void onTeleported() {
        if (!isTeleporting())
            return;

        spawnMe(getPosition().getX(), getPosition().getY(), getPosition().getZ());
        setIsTeleporting(false);

        if (_isPendingRevive)
            doRevive();
    }

    /**
     * @return character inventory, default null, overridden in L2Playable types and in L2Npc.
     */
    public Inventory getInventory() {
        return null;
    }

    public boolean destroyItemByItemId(String process, int itemId, int count, L2Object reference,
            boolean sendMessage) {
        return true;
    }

    public boolean destroyItem(String process, int objectId, int count, L2Object reference, boolean sendMessage) {
        return true;
    }

    public final boolean isInsideZone(final byte zone) {
        return zone == ZONE_PVP ? _zones[ZONE_PVP] > 0 && _zones[ZONE_PEACE] == 0 : _zones[zone] > 0;
    }

    public final void setInsideZone(final byte zone, final boolean state) {
        if (state)
            _zones[zone]++;
        else {
            _zones[zone]--;
            if (_zones[zone] < 0)
                _zones[zone] = 0;
        }
    }

    /**
     * @return true if the player is GM.
     */
    public boolean isGM() {
        return false;
    }

    /**
     * Add L2Character instance that is attacking to the attacker list.
     * 
     * @param player
     *            The L2Character that attacks this one.
     */
    public void addAttackerToAttackByList(L2Character player) {
        // DS: moved to L2Attackable
    }

    /**
     * Send a packet to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character.
     * 
     * @param mov
     *            The packet to send.
     */
    public void broadcastPacket(L2GameServerPacket mov) {
        Broadcast.toSelfAndKnownPlayers(this, mov);
    }

    /**
     * Send a packet to the L2Character AND to all L2PcInstance in the radius (max knownlist radius) from the L2Character.
     * 
     * @param mov
     *            The packet to send.
     * @param radius
     *            The radius to make check on.
     */
    public void broadcastPacket(L2GameServerPacket mov, int radius) {
        Broadcast.toSelfAndKnownPlayersInRadius(this, mov, radius);
    }

    /**
     * @param barPixels
     * @return boolean true if hp update should be done, false if not.
     */
    protected boolean needHpUpdate(int barPixels) {
        double currentHp = getCurrentHp();

        if (currentHp <= 1.0 || getMaxHp() < barPixels)
            return true;

        if (currentHp <= _hpUpdateDecCheck || currentHp >= _hpUpdateIncCheck) {
            if (currentHp == getMaxHp()) {
                _hpUpdateIncCheck = currentHp + 1;
                _hpUpdateDecCheck = currentHp - _hpUpdateInterval;
            } else {
                double doubleMulti = currentHp / _hpUpdateInterval;
                int intMulti = (int) doubleMulti;

                _hpUpdateDecCheck = _hpUpdateInterval * (doubleMulti < intMulti ? intMulti-- : intMulti);
                _hpUpdateIncCheck = _hpUpdateDecCheck + _hpUpdateInterval;
            }
            return true;
        }
        return false;
    }

    /**
     * Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <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 L2Character called _statusListener that must be informed of HP/MP updates of this L2Character</li><BR>
     * <BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T SEND CP information</B></FONT><BR>
     * <BR>
     * <B><U> Overriden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance : Send current HP,MP and CP to the L2PcInstance and only current HP, MP and Level to all other L2PcInstance of the Party</li>
     * <BR>
     * <BR>
     */
    public void broadcastStatusUpdate() {
        if (getStatus().getStatusListener().isEmpty())
            return;

        if (!needHpUpdate(352))
            return;

        _log.debug("Broadcast Status Update for " + getObjectId() + "(" + getName() + "). HP: " + getCurrentHp());

        // Create the Server->Client packet StatusUpdate with current HP
        StatusUpdate su = new StatusUpdate(this);
        su.addAttribute(StatusUpdate.CUR_HP, (int) getCurrentHp());

        // Go through the StatusListener
        for (L2Character temp : getStatus().getStatusListener()) {
            if (temp != null)
                temp.sendPacket(su);
        }
    }

    /**
     * <B><U> Overriden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance</li><BR>
     * <BR>
     * 
     * @param mov
     *            The packet to send.
     */
    public void sendPacket(L2GameServerPacket mov) {
        // default implementation
    }

    /**
     * <B><U> Overridden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance</li><BR>
     * <BR>
     * 
     * @param text
     *            The string to send.
     */
    public void sendMessage(String text) {
        // default implementation
    }

    /**
     * Teleport a L2Character and its pet if necessary.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Stop the movement of the L2Character</li> <li>Set the x,y,z position of the L2Object and if necessary modify its _worldRegion</li> <li>
     * Send a Server->Client packet TeleportToLocationt to the L2Character AND to all L2PcInstance in its _KnownPlayers</li> <li>Modify the
     * position of the pet if necessary</li><BR>
     * <BR>
     * 
     * @param x
     * @param y
     * @param z
     * @param allowRandomOffset
     */
    public void teleToLocation(int x, int y, int z, boolean allowRandomOffset) {
        // Stop movement
        stopMove(null, false);
        abortAttack();
        abortCast();

        setIsTeleporting(true);
        setTarget(null);

        getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);

        if (PlayersConfig.RESPAWN_RANDOM_ENABLED && allowRandomOffset) {
            x += Rnd.get(-PlayersConfig.RESPAWN_RANDOM_MAX_OFFSET, PlayersConfig.RESPAWN_RANDOM_MAX_OFFSET);
            y += Rnd.get(-PlayersConfig.RESPAWN_RANDOM_MAX_OFFSET, PlayersConfig.RESPAWN_RANDOM_MAX_OFFSET);
        }

        z += 5;

        _log.debug("Teleporting to: " + x + ", " + y + ", " + z);

        // Send a Server->Client packet TeleportToLocationt to the L2Character AND to all L2PcInstance in the _KnownPlayers of the
        // L2Character
        broadcastPacket(new TeleportToLocation(this, x, y, z));

        // remove the object from its old location
        decayMe();

        // Set the x,y,z position of the L2Object and if necessary modify its _worldRegion
        getPosition().setXYZ(x, y, z);

        if (!(this instanceof L2PcInstance)
                || (((L2PcInstance) this).getClient() != null && ((L2PcInstance) this).getClient().isDetached()))
            onTeleported();

        revalidateZone(true);
    }

    public void teleToLocation(int x, int y, int z) {
        teleToLocation(x, y, z, false);
    }

    public void teleToLocation(Location loc, boolean allowRandomOffset) {
        int x = loc.getX();
        int y = loc.getY();
        int z = loc.getZ();

        if (this instanceof L2PcInstance
                && DimensionalRiftManager.getInstance().checkIfInRiftZone(getX(), getY(), getZ(), true)) // true
        // ->
        // ignore
        // waiting
        // room
        // :)
        {
            L2PcInstance player = (L2PcInstance) this;
            player.sendMessage("You have been sent to the waiting room.");
            if (player.isInParty() && player.getParty().isInDimensionalRift()) {
                player.getParty().getDimensionalRift().usedTeleport(player);
            }
            int[] newCoords = DimensionalRiftManager.getInstance().getRoom((byte) 0, (byte) 0).getTeleportCoords();
            x = newCoords[0];
            y = newCoords[1];
            z = newCoords[2];
        }
        teleToLocation(x, y, z, allowRandomOffset);
    }

    public void teleToLocation(TeleportWhereType teleportWhere) {
        teleToLocation(MapRegionData.getInstance().getTeleToLocation(this, teleportWhere), true);
    }

    // =========================================================
    // Method - Private
    /**
     * Launch a physical attack against a target (Simple, Bow, Pole or Dual).<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Get the active weapon (always equipped in the right hand)</li><BR>
     * <BR>
     * <li>If weapon is a bow, check for arrows, MP and bow re-use delay (if necessary, equip the L2PcInstance 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><BR>
     * <BR>
     * <li>Get the Attack Speed of the L2Character (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 L2Character AND to all L2PcInstance in the _KnownPlayers of the
     * L2Character</li> <li>Notify AI with EVT_READY_TO_ACT</li><BR>
     * <BR>
     * 
     * @param target
     *            The L2Character targeted
     */
    protected void doAttack(L2Character target) {
        _log.debug(getName() + " doAttack: target=" + target);

        if (!isAlikeDead() && target != null) {
            if (this instanceof L2Npc && target.isAlikeDead() || !getKnownList().knowsObject(target)) {
                getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            } else if (this instanceof L2PcInstance) {
                if (target.isDead()) {
                    getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }

                L2PcInstance actor = (L2PcInstance) this;
                // Players riding wyvern can only do melee attacks
                if (actor.isMounted() && actor.getMountNpcId() == 12621) {
                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
            }
        }

        if (isAttackingDisabled())
            return;

        if (this instanceof L2PcInstance) {
            if (((L2PcInstance) this).inObserverMode()) {
                sendPacket(SystemMessage.getSystemMessage(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE));
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }

            // Checking if target has moved to peace zone
            if (target.isInsidePeaceZone((L2PcInstance) this)) {
                getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
                sendPacket(ActionFailed.STATIC_PACKET);
                return;
            }
        }

        stopEffectsOnAction();

        // Get the active weapon instance (always equipped in the right hand)
        L2ItemInstance weaponInst = getActiveWeaponInstance();

        // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand)
        L2Weapon weaponItem = getActiveWeaponItem();

        if (weaponItem != null && weaponItem.getItemType() == L2WeaponType.FISHINGROD) {
            // You can't make an attack with a fishing pole.
            sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CANNOT_ATTACK_WITH_FISHING_POLE));
            getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);

            sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }

        // GeoData Los Check here (or dz > 1000)
        if (!GeoData.getInstance().canSeeTarget(this, target)) {
            sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CANT_SEE_TARGET));
            getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
            sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }

        // Check for a bow
        if ((weaponItem != null && weaponItem.getItemType() == L2WeaponType.BOW)) {
            // Check for arrows and MP
            if (this instanceof L2PcInstance) {
                // Equip arrows needed in left hand and send a Server->Client packet ItemList to the L2PcINstance then return True
                if (!checkAndEquipArrows()) {
                    // Cancel the action because the L2PcInstance have no arrow
                    getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);

                    sendPacket(ActionFailed.STATIC_PACKET);
                    sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_ARROWS));
                    return;
                }

                // Verify if the bow can be use
                if (_disableBowAttackEndTime <= GameTimeController.getGameTicks()) {
                    // Verify if L2PcInstance owns enough MP
                    int saMpConsume = (int) getStat().calcStat(Stats.MP_CONSUME, 0, null, null);
                    int mpConsume = saMpConsume == 0 ? weaponItem.getMpConsume() : saMpConsume;
                    mpConsume = (int) calcStat(Stats.BOW_MP_CONSUME_RATE, mpConsume, null, null);

                    if (getCurrentMp() < mpConsume) {
                        // If L2PcInstance doesn't have enough MP, stop the attack
                        ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT),
                                1000);

                        sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_MP));
                        sendPacket(ActionFailed.STATIC_PACKET);
                        return;
                    }
                    // If L2PcInstance have enough MP, the bow consummes it
                    if (mpConsume > 0)
                        getStatus().reduceMp(mpConsume);

                    // Set the period of bow non re-use
                    _disableBowAttackEndTime = 5 * GameTimeController.TICKS_PER_SECOND
                            + GameTimeController.getGameTicks();
                } else {
                    // Cancel the action because the bow can't be re-use at this moment
                    ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT), 1000);

                    sendPacket(ActionFailed.STATIC_PACKET);
                    return;
                }
            } else if (this instanceof L2Npc) {
                if (_disableBowAttackEndTime > GameTimeController.getGameTicks())
                    return;
            }
        }

        // Add the L2PcInstance to _knownObjects and _knownPlayer of the target
        target.getKnownList().addKnownObject(this);

        // Verify if soulshots are charged.
        boolean wasSSCharged;

        if (this instanceof L2Summon && !(this instanceof L2PetInstance && weaponInst != null))
            wasSSCharged = (((L2Summon) this).getChargedSoulShot() != L2ItemInstance.CHARGED_NONE);
        else
            wasSSCharged = (weaponInst != null && weaponInst.getChargedSoulshot() != L2ItemInstance.CHARGED_NONE);

        if (this instanceof L2Attackable) {
            if (((L2Npc) this).useSoulShot())
                wasSSCharged = true;
        }

        // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack)
        int timeAtk = calculateTimeBetweenAttacks(target, weaponItem);
        // the hit is calculated to happen halfway to the animation - might need further tuning e.g. in bow case
        int timeToHit = timeAtk / 2;
        _attackEndTime = GameTimeController.getGameTicks();
        _attackEndTime += (timeAtk / GameTimeController.MILLIS_IN_TICK);
        _attackEndTime -= 1;

        int ssGrade = 0;

        if (weaponItem != null)
            ssGrade = weaponItem.getCrystalType();

        // Create a Server->Client packet Attack
        Attack attack = new Attack(this, wasSSCharged, ssGrade);

        // Set the Attacking Body part to CHEST
        setAttackingBodypart();

        // Make sure that char is facing selected target
        setHeading(Util.calculateHeadingFrom(this, target));

        // Get the Attack Reuse Delay of the L2Weapon
        int reuse = calculateReuseTime(target, weaponItem);
        boolean hitted;

        // Select the type of attack to start
        if (weaponItem == null)
            hitted = doAttackHitSimple(attack, target, timeToHit);
        else if (weaponItem.getItemType() == L2WeaponType.BOW)
            hitted = doAttackHitByBow(attack, target, timeAtk, reuse);
        else if (weaponItem.getItemType() == L2WeaponType.POLE)
            hitted = doAttackHitByPole(attack, target, timeToHit);
        else if (isUsingDualWeapon())
            hitted = doAttackHitByDual(attack, target, timeToHit);
        else
            hitted = doAttackHitSimple(attack, target, timeToHit);

        // Flag the attacker if it's a L2PcInstance outside a PvP area
        L2PcInstance player = getActingPlayer();

        if (player != null) {
            AttackStanceTaskManager.getInstance().addAttackStanceTask(player);

            if (player.getPet() != target)
                player.updatePvPStatus(target);
        }

        // Check if hit isn't missed
        if (!hitted)
            // Abort the attack of the L2Character and send Server->Client ActionFailed packet
            abortAttack();
        else {
            // IA implementation for ON_ATTACK_ACT (mob which attacks a player).
            if (this instanceof L2Attackable) {
                try {
                    // Bypass behavior if the victim isn't a player
                    L2PcInstance victim = target.getActingPlayer();
                    if (victim != null) {
                        L2Npc mob = ((L2Npc) this);
                        if (mob.getTemplate().getEventQuests(Quest.QuestEventType.ON_ATTACK_ACT) != null)
                            for (Quest quest : mob.getTemplate().getEventQuests(Quest.QuestEventType.ON_ATTACK_ACT))
                                quest.notifyAttackAct(mob, victim);
                    }
                } catch (Exception e) {
                    _log.warn(e.getLocalizedMessage(), e);
                }
            }

            // If we didn't miss the hit, discharge the shoulshots, if any
            if (this instanceof L2Summon && !(this instanceof L2PetInstance && weaponInst != null))
                ((L2Summon) this).setChargedSoulShot(L2ItemInstance.CHARGED_NONE);
            else if (weaponInst != null)
                weaponInst.setChargedSoulshot(L2ItemInstance.CHARGED_NONE);

            if (player != null) {
                if (player.isCursedWeaponEquipped()) {
                    // If hitted by a cursed weapon, Cp is reduced to 0
                    if (!target.isInvul())
                        target.setCurrentCp(0);
                } else if (player.isHero()) {
                    if (target instanceof L2PcInstance && ((L2PcInstance) target).isCursedWeaponEquipped())
                        // If a cursed weapon is hitted by a Hero, Cp is reduced to 0
                        target.setCurrentCp(0);
                }
            }
        }

        // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack
        // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character
        if (attack.hasHits())
            broadcastPacket(attack);

        // Notify AI with EVT_READY_TO_ACT
        ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT), timeAtk + reuse);
    }

    private class AutoSS implements Runnable {
        private L2Character _character;
        private L2Skill _skill = null;

        public AutoSS(L2Character Character, L2Skill Skill) {
            _character = Character;
            _skill = Skill;
        }

        @Override
        public void run() {
            // Recharge AutoSoulShot
            if (_skill != null) {
                if (_skill.useSoulShot()) {
                    // if (_character instanceof L2Npc)
                    // ((L2Npc) _character).rechargeAutoSoulShot(true, false);
                    /* else */if (_character instanceof L2PcInstance)
                        ((L2PcInstance) _character).rechargeAutoSoulShot(true, false, false);
                    else if (_character instanceof L2Summon)
                        ((L2Summon) _character).getOwner().rechargeAutoSoulShot(true, false, true);
                } else if (_skill.useSpiritShot()) {
                    // if (_character instanceof L2Npc)
                    // ((L2Npc) _character).rechargeAutoSoulShot(true, true);
                    /* else */if (_character instanceof L2PcInstance)
                        ((L2PcInstance) _character).rechargeAutoSoulShot(true, true, false);
                    else if (_character instanceof L2Summon)
                        ((L2Summon) _character).getOwner().rechargeAutoSoulShot(false, true, true);
                }
            } else {
                if (_character instanceof L2PcInstance)
                    ((L2PcInstance) _character).rechargeAutoSoulShot(true, false, false);
                else if (_character instanceof L2Summon)
                    ((L2Summon) _character).getOwner().rechargeAutoSoulShot(true, false, true);
            }
        }
    }

    /**
     * Launch a Bow attack.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Calculate if hit is missed or not</li> <li>Consumme arrows</li> <li>If hit isn't missed, calculate if shield defense is efficient</li>
     * <li>If hit isn't missed, calculate if hit is critical</li> <li>If hit isn't missed, calculate physical damages</li> <li>If the L2Character
     * is a L2PcInstance, Send a Server->Client packet SetupGauge</li> <li>Create a new hit task with Medium priority</li> <li>Calculate and set
     * the disable delay of the bow in function of the Attack Speed</li> <li>Add this hit to the Server-Client packet Attack</li><BR>
     * <BR>
     * 
     * @param attack
     *            Server->Client packet Attack in which the hit will be added
     * @param target
     *            The L2Character targeted
     * @param sAtk
     *            The Attack Speed of the attacker
     * @param reuse
     *            The reuse timer of the item.
     * @return True if the hit isn't missed
     */
    private boolean doAttackHitByBow(Attack attack, L2Character target, int sAtk, int reuse) {
        int damage1 = 0;
        byte shld1 = 0;
        boolean crit1 = false;

        // Calculate if hit is missed or not
        boolean miss1 = Formulas.calcHitMiss(this, target);

        // Consume arrows
        reduceArrowCount();

        _move = null;

        // Check if hit isn't missed
        if (!miss1) {
            // Calculate if shield defense is efficient
            shld1 = Formulas.calcShldUse(this, target);

            // Calculate if hit is critical
            crit1 = Formulas.calcCrit(getStat().getCriticalHit(target, null));

            // Calculate physical damages
            damage1 = (int) Formulas.calcPhysDam(this, target, null, shld1, crit1, false, attack.soulshot);
        }

        // Check if the L2Character is a L2PcInstance
        if (this instanceof L2PcInstance) {
            // Send a system message
            sendPacket(SystemMessage.getSystemMessage(SystemMessageId.GETTING_READY_TO_SHOOT_AN_ARROW));

            // Send a Server->Client packet SetupGauge
            SetupGauge sg = new SetupGauge(SetupGauge.RED, sAtk + reuse);
            sendPacket(sg);
        }

        // Create a new hit task with Medium priority
        ThreadPoolManager.getInstance()
                .scheduleAi(new HitTask(target, damage1, crit1, miss1, attack.soulshot, shld1), sAtk);
        ThreadPoolManager.getInstance().scheduleGeneral(new AutoSS(this, null), sAtk);

        // Calculate and set the disable delay of the bow in function of the Attack Speed
        _disableBowAttackEndTime = (sAtk + reuse) / GameTimeController.MILLIS_IN_TICK
                + GameTimeController.getGameTicks();

        // Add this hit to the Server-Client packet Attack
        attack.hit(attack.createHit(target, damage1, miss1, crit1, shld1));

        // Return true if hit isn't missed
        return !miss1;
    }

    /**
     * Launch a Dual attack.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Calculate if hits are missed or not</li> <li>If hits aren't missed, calculate if shield defense is efficient</li> <li>If hits aren't
     * missed, calculate if hit is critical</li> <li>If hits aren't missed, calculate physical damages</li> <li>Create 2 new hit tasks with
     * Medium priority</li> <li>Add those hits to the Server-Client packet Attack</li><BR>
     * <BR>
     * 
     * @param attack
     *            Server->Client packet Attack in which the hit will be added
     * @param target
     *            The L2Character targeted
     * @param sAtk
     *            The Attack Speed of the attacker
     * @return True if hit 1 or hit 2 isn't missed
     */
    private boolean doAttackHitByDual(Attack attack, L2Character target, int sAtk) {
        int damage1 = 0;
        int damage2 = 0;
        byte shld1 = 0;
        byte shld2 = 0;
        boolean crit1 = false;
        boolean crit2 = false;

        // Calculate if hits are missed or not
        boolean miss1 = Formulas.calcHitMiss(this, target);
        boolean miss2 = Formulas.calcHitMiss(this, target);

        // Check if hit 1 isn't missed
        if (!miss1) {
            // Calculate if shield defense is efficient against hit 1
            shld1 = Formulas.calcShldUse(this, target);

            // Calculate if hit 1 is critical
            crit1 = Formulas.calcCrit(getStat().getCriticalHit(target, null));

            // Calculate physical damages of hit 1
            damage1 = (int) Formulas.calcPhysDam(this, target, null, shld1, crit1, true, attack.soulshot);
            damage1 /= 2;
        }

        // Check if hit 2 isn't missed
        if (!miss2) {
            // Calculate if shield defense is efficient against hit 2
            shld2 = Formulas.calcShldUse(this, target);

            // Calculate if hit 2 is critical
            crit2 = Formulas.calcCrit(getStat().getCriticalHit(target, null));

            // Calculate physical damages of hit 2
            damage2 = (int) Formulas.calcPhysDam(this, target, null, shld2, crit2, true, attack.soulshot);
            damage2 /= 2;
        }

        if (this instanceof L2Attackable) {
            if (((L2Attackable) this)._soulshotcharged) {
                // Create a new hit task with Medium priority for hit 1
                ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, true, shld1),
                        sAtk / 2);

                // Create a new hit task with Medium priority for hit 2 with a higher delay
                ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage2, crit2, miss2, true, shld2),
                        sAtk);
            } else {
                ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, false, shld1),
                        sAtk / 2);

                // Create a new hit task with Medium priority for hit 2 with a higher delay
                ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage2, crit2, miss2, false, shld2),
                        sAtk);
            }
        } else {
            // Create a new hit task with Medium priority for hit 1
            ThreadPoolManager.getInstance()
                    .scheduleAi(new HitTask(target, damage1, crit1, miss1, attack.soulshot, shld1), sAtk / 2);

            // Create a new hit task with Medium priority for hit 2 with a higher delay
            ThreadPoolManager.getInstance()
                    .scheduleAi(new HitTask(target, damage2, crit2, miss2, attack.soulshot, shld2), sAtk);

            ThreadPoolManager.getInstance().scheduleGeneral(new AutoSS(this, null), sAtk);
        }

        // Add those hits to the Server-Client packet Attack
        attack.hit(attack.createHit(target, damage1, miss1, crit1, shld1),
                attack.createHit(target, damage2, miss2, crit2, shld2));

        // Return true if hit 1 or hit 2 isn't missed
        return (!miss1 || !miss2);
    }

    /**
     * Launch a Pole attack.<BR>
     * <B><U> Actions</U> :</B><BR>
     * <li>Get all visible objects in a spherical area near the L2Character to obtain possible targets</li> <li>If possible target is the
     * L2Character targeted, launch a simple attack against it</li> <li>If possible target isn't the L2Character targeted but is attackable,
     * launch a simple attack against it</li><BR>
     * 
     * @param attack
     *            Server->Client packet Attack in which the hit will be added
     * @param target
     *            The L2Character targeted
     * @param sAtk
     *            The Attack Speed of the attacker
     * @return True if one hit isn't missed
     */
    private boolean doAttackHitByPole(Attack attack, L2Character target, int sAtk) {
        int maxRadius = getPhysicalAttackRange();
        int maxAngleDiff = (int) getStat().calcStat(Stats.POWER_ATTACK_ANGLE, 120, null, null);

        _log.debug("doAttackHitByPole: Max radius = " + maxRadius + " Max angle = " + maxAngleDiff);

        // Get the number of targets (-1 because the main target is already used)
        int attackRandomCountMax = (int) getStat().calcStat(Stats.ATTACK_COUNT_MAX, 0, null, null) - 1;
        int attackcount = 0;

        boolean hitted = doAttackHitSimple(attack, target, 100, sAtk);
        double attackpercent = 85;
        L2Character temp;

        Collection<L2Object> objs = getKnownList().getKnownObjects().values();
        for (L2Object obj : objs) {
            if (obj == target)
                continue;

            // Check if the L2Object is a L2Character
            if (obj instanceof L2Character) {
                if (obj instanceof L2PetInstance && this instanceof L2PcInstance
                        && ((L2PetInstance) obj).getOwner() == ((L2PcInstance) this))
                    continue;

                if (!Util.checkIfInRange(maxRadius, this, obj, false))
                    continue;

                // otherwise hit too high/low. 650 because mob z coord sometimes wrong on hills
                if (Math.abs(obj.getZ() - getZ()) > 650)
                    continue;

                if (!isFacing(obj, maxAngleDiff))
                    continue;

                if (this instanceof L2Attackable && obj instanceof L2PcInstance
                        && getTarget() instanceof L2Attackable)
                    continue;

                if (this instanceof L2Attackable && obj instanceof L2Attackable
                        && ((L2Attackable) this).getEnemyClan() == null && ((L2Attackable) this).getIsChaos() == 0)
                    continue;

                if (this instanceof L2Attackable && obj instanceof L2Attackable
                        && !((L2Attackable) this).getEnemyClan().equals(((L2Attackable) obj).getClan())
                        && ((L2Attackable) this).getIsChaos() == 0)
                    continue;

                temp = (L2Character) obj;

                // Launch an attack on each character, until attackRandomCountMax is reached.
                if (!temp.isAlikeDead()) {
                    if (temp == getAI().getAttackTarget() || temp.isAutoAttackable(this)) {
                        attackcount++;
                        if (attackcount > attackRandomCountMax)
                            break;

                        hitted |= doAttackHitSimple(attack, temp, attackpercent, sAtk);
                        attackpercent /= 1.15;
                    }
                }
            }
        }
        // Return true if one hit isn't missed
        return hitted;
    }

    /**
     * Launch a simple attack.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Calculate if hit is missed or not</li> <li>If hit isn't missed, calculate if shield defense is efficient</li> <li>If hit isn't missed,
     * calculate if hit is critical</li> <li>If hit isn't missed, calculate physical damages</li> <li>Create a new hit task with Medium priority</li>
     * <li>Add this hit to the Server-Client packet Attack</li><BR>
     * <BR>
     * 
     * @param attack
     *            Server->Client packet Attack in which the hit will be added
     * @param target
     *            The L2Character targeted
     * @param sAtk
     *            The Attack Speed of the attacker
     * @return True if the hit isn't missed
     */
    private boolean doAttackHitSimple(Attack attack, L2Character target, int sAtk) {
        return doAttackHitSimple(attack, target, 100, sAtk);
    }

    private boolean doAttackHitSimple(Attack attack, L2Character target, double attackpercent, int sAtk) {
        int damage1 = 0;
        byte shld1 = 0;
        boolean crit1 = false;

        // Calculate if hit is missed or not
        boolean miss1 = Formulas.calcHitMiss(this, target);

        // Check if hit isn't missed
        if (!miss1) {
            // Calculate if shield defense is efficient
            shld1 = Formulas.calcShldUse(this, target);

            // Calculate if hit is critical
            crit1 = Formulas.calcCrit(getStat().getCriticalHit(target, null));

            // Calculate physical damages
            damage1 = (int) Formulas.calcPhysDam(this, target, null, shld1, crit1, false, attack.soulshot);

            if (attackpercent != 100)
                damage1 = (int) (damage1 * attackpercent / 100);
        }

        // Create a new hit task with Medium priority
        if (this instanceof L2Attackable) {
            if (((L2Attackable) this)._soulshotcharged)
                ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, true, shld1),
                        sAtk);
            else
                ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, false, shld1),
                        sAtk);
        } else {
            ThreadPoolManager.getInstance()
                    .scheduleAi(new HitTask(target, damage1, crit1, miss1, attack.soulshot, shld1), sAtk);
            ThreadPoolManager.getInstance().scheduleGeneral(new AutoSS(this, null), sAtk);
        }

        // Add this hit to the Server-Client packet Attack
        attack.hit(attack.createHit(target, damage1, miss1, crit1, shld1));

        // Return true if hit isn't missed
        return !miss1;
    }

    /**
     * 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><BR>
     * <BR>
     * <li>Verify the possibilty 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 L2Charcater 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 MagicSkillUse (to diplay 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><BR>
     * <BR>
     * 
     * @param skill
     *            The L2Skill to use
     */
    public void doCast(L2Skill skill) {
        beginCast(skill, false);
    }

    public void doSimultaneousCast(L2Skill skill) {
        beginCast(skill, true);
    }

    private void beginCast(L2Skill skill, boolean simultaneously) {
        if (!checkDoCastConditions(skill)) {
            if (simultaneously)
                setIsCastingSimultaneouslyNow(false);
            else
                setIsCastingNow(false);

            if (this instanceof L2PcInstance)
                getAI().setIntention(AI_INTENTION_ACTIVE);

            return;
        }
        // Override casting type
        if (skill.isSimultaneousCast() && !simultaneously)
            simultaneously = true;

        stopEffectsOnAction();

        // Set the target of the skill in function of Skill Type and Target Type
        L2Character target = null;
        // Get all possible targets of the skill in a table in function of the skill target type
        L2Object[] targets = skill.getTargetList(this);

        boolean doit = false;

        // AURA skills should always be using caster as target
        switch (skill.getTargetType()) {
        case TARGET_AREA_SUMMON: // We need it to correct facing
            target = getPet();
            break;
        case TARGET_AURA:
        case TARGET_FRONT_AURA:
        case TARGET_BEHIND_AURA:
        case TARGET_GROUND:
            target = this;
            break;
        case TARGET_SELF:
        case TARGET_PET:
        case TARGET_SUMMON:
        case TARGET_OWNER_PET:
        case TARGET_PARTY:
        case TARGET_CLAN:
        case TARGET_ALLY:
            doit = true;
        default:
            if (targets.length == 0) {
                if (simultaneously)
                    setIsCastingSimultaneouslyNow(false);
                else
                    setIsCastingNow(false);
                // Send a Server->Client packet ActionFailed to the L2PcInstance
                if (this instanceof L2PcInstance) {
                    sendPacket(ActionFailed.STATIC_PACKET);
                    getAI().setIntention(AI_INTENTION_ACTIVE);
                }
                return;
            }

            switch (skill.getSkillType()) {
            case BUFF:
            case HEAL:
            case COMBATPOINTHEAL:
            case MANAHEAL:
            case SEED:
            case REFLECT:
                doit = true;
                break;
            }

            target = (doit) ? (L2Character) targets[0] : (L2Character) getTarget();
        }
        beginCast(skill, simultaneously, target, targets);
    }

    private void beginCast(L2Skill skill, boolean simultaneously, L2Character target, L2Object[] targets) {
        if (target == null) {
            if (simultaneously)
                setIsCastingSimultaneouslyNow(false);
            else
                setIsCastingNow(false);

            if (this instanceof L2PcInstance) {
                sendPacket(ActionFailed.STATIC_PACKET);
                getAI().setIntention(AI_INTENTION_ACTIVE);
            }
            return;
        }

        // Get the casting time of the skill (base)
        int hitTime = skill.getHitTime();
        int coolTime = skill.getCoolTime();

        boolean effectWhileCasting = skill.getSkillType() == L2SkillType.FUSION
                || skill.getSkillType() == L2SkillType.SIGNET_CASTTIME;

        // Calculate the casting time of the skill (base + modifier of MAtkSpd)
        // Don't modify the skill time for FUSION skills. The skill time for those skills represent the buff time.
        if (!effectWhileCasting) {
            hitTime = Formulas.calcAtkSpd(this, skill, hitTime);
            if (coolTime > 0)
                coolTime = Formulas.calcAtkSpd(this, skill, coolTime);
        }

        int shotSave = L2ItemInstance.CHARGED_NONE;

        // Calculate altered Cast Speed due to BSpS/SpS
        L2ItemInstance weaponInst = getActiveWeaponInstance();
        if (weaponInst != null) {
            if (skill.isMagic() && !effectWhileCasting && skill.getTargetType() != SkillTargetType.TARGET_SELF) {
                if ((weaponInst.getChargedSpiritshot() == L2ItemInstance.CHARGED_BLESSED_SPIRITSHOT)
                        || (weaponInst.getChargedSpiritshot() == L2ItemInstance.CHARGED_SPIRITSHOT)) {
                    // Only takes 70% of the time to cast a BSpS/SpS cast
                    hitTime = (int) (0.70 * hitTime);
                    coolTime = (int) (0.70 * coolTime);

                    // Because the following are magic skills that do not actively 'eat' BSpS/SpS,
                    // I must 'eat' them here so players don't take advantage of infinite speed increase
                    switch (skill.getSkillType()) {
                    case BUFF:
                    case MANAHEAL:
                    case MANARECHARGE:
                    case RESURRECT:
                    case RECALL:
                        weaponInst.setChargedSpiritshot(L2ItemInstance.CHARGED_NONE);
                        break;
                    }
                }
            }

            // Save shots value for repeats
            if (skill.useSoulShot())
                shotSave = weaponInst.getChargedSoulshot();
            else if (skill.useSpiritShot())
                shotSave = weaponInst.getChargedSpiritshot();
            ThreadPoolManager.getInstance().scheduleGeneral(new AutoSS(this, skill), hitTime + 15); // TODO:  
            // ?. (?) Demon
        }

        if (this instanceof L2Npc) {
            if (((L2Npc) this).useSpiritShot()) {
                hitTime = (int) (0.70 * hitTime);
                coolTime = (int) (0.70 * coolTime);
            }
        }

        // Don't modify skills HitTime if staticHitTime is specified for skill in datapack.
        if (skill.isStaticHitTime()) {
            hitTime = skill.getHitTime();
            coolTime = skill.getCoolTime();
        }
        // if basic hitTime is higher than 500 than the min hitTime is 500
        else if (skill.getHitTime() >= 500 && hitTime < 500)
            hitTime = 500;

        // Set the _castInterruptTime and casting status (L2PcInstance already has this true)
        if (simultaneously) {
            // queue herbs and potions
            if (isCastingSimultaneouslyNow()) {
                ThreadPoolManager.getInstance().scheduleAi(new UsePotionTask(this, skill), 100);
                return;
            }
            setIsCastingSimultaneouslyNow(true);
            setLastSimultaneousSkillCast(skill);
        } else {
            setIsCastingNow(true);
            _castInterruptTime = -2 + GameTimeController.getGameTicks()
                    + hitTime / GameTimeController.MILLIS_IN_TICK;
            setLastSkillCast(skill);
        }

        // Init the reuse time of the skill
        int reuseDelay;

        if (skill.isStaticReuse())
            reuseDelay = (skill.getReuseDelay());
        else {
            if (skill.isMagic())
                reuseDelay = (int) (skill.getReuseDelay() * getStat().getMReuseRate(skill));
            else
                reuseDelay = (int) (skill.getReuseDelay() * getStat().getPReuseRate(skill));

            // reuse is influenced by atkspd / matkspd
            reuseDelay *= 333.0 / (skill.isMagic() ? getMAtkSpd() : getPAtkSpd());
        }

        boolean skillMastery = Formulas.calcSkillMastery(this, skill);

        // Skill reuse check
        if (reuseDelay > 30000 && !skillMastery)
            addTimeStamp(skill, reuseDelay);

        // Check if this skill consume mp on start casting
        int initmpcons = getStat().getMpInitialConsume(skill);
        if (initmpcons > 0) {
            getStatus().reduceMp(initmpcons);
            StatusUpdate su = new StatusUpdate(this);
            su.addAttribute(StatusUpdate.CUR_MP, (int) getCurrentMp());
            sendPacket(su);
        }

        // Disable the skill during the re-use delay and create a task EnableSkill with Medium priority to enable it at the end of
        // the re-use delay
        if (reuseDelay > 10) {
            if (skillMastery) {
                reuseDelay = 100;

                if (getActingPlayer() != null)
                    getActingPlayer().sendPacket(SystemMessageId.SKILL_READY_TO_USE_AGAIN);
            }

            disableSkill(skill, reuseDelay);
        }

        // Make sure that char is facing selected target
        if (target != this)
            setHeading(Util.calculateHeadingFrom(this, target));

        // For force buff skills, start the effect as long as the player is casting.
        if (effectWhileCasting) {
            // Consume Items if necessary and Send the Server->Client packet InventoryUpdate with Item modification to all the
            // L2Character
            if (skill.getItemConsumeId() > 0) {
                if (!destroyItemByItemId("Consume", skill.getItemConsumeId(), skill.getItemConsume(), null, true)) {
                    sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS));
                    if (simultaneously)
                        setIsCastingSimultaneouslyNow(false);
                    else
                        setIsCastingNow(false);

                    if (this instanceof L2PcInstance)
                        getAI().setIntention(AI_INTENTION_ACTIVE);
                    return;
                }
            }

            if (skill.getSkillType() == L2SkillType.FUSION)
                startFusionSkill(target, skill);
            else
                callSkill(skill, targets);
        }

        // Get the Display Identifier for a skill that client can't display
        int displayId = skill.getDisplayId();

        // Get the level of the skill
        int level = skill.getLevel();
        if (level < 1)
            level = 1;

        // Send a Server->Client packet MagicSkillUse with target, displayId, level, skillTime, reuseDelay
        // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character
        if (!skill.isPotion()) {
            broadcastPacket(new MagicSkillUse(this, target, displayId, level, hitTime, reuseDelay, false));
            broadcastPacket(new MagicSkillLaunched(this, displayId, level,
                    (targets == null || targets.length == 0) ? new L2Object[] { target } : targets));
        } else
            broadcastPacket(new MagicSkillUse(this, target, displayId, level, 0, 0));

        if (this instanceof L2Playable) {
            // Send a system message USE_S1 to the L2Character
            if (this instanceof L2PcInstance && skill.getId() != 1312) {
                SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.USE_S1);
                sm.addSkillName(skill);
                sendPacket(sm);
            }

            if (!effectWhileCasting && skill.getItemConsumeId() > 0) {
                if (!destroyItemByItemId("Consume", skill.getItemConsumeId(), skill.getItemConsume(), null, true)) {
                    getActingPlayer().sendPacket(SystemMessageId.NOT_ENOUGH_ITEMS);
                    abortCast();
                    return;
                }
            }

            // Before start AI Cast Broadcast Fly Effect is Need
            if (this instanceof L2PcInstance && skill.getFlyType() != null)
                ThreadPoolManager.getInstance().scheduleEffect(new FlyToLocationTask(this, target, skill), 50);
        }

        MagicUseTask mut = new MagicUseTask(targets, skill, hitTime, coolTime, simultaneously, shotSave);

        // launch the magic in hitTime milliseconds
        if (hitTime > 410) {
            // Send a Server->Client packet SetupGauge with the color of the gauge and the casting time
            if (this instanceof L2PcInstance && !effectWhileCasting)
                sendPacket(new SetupGauge(SetupGauge.BLUE, hitTime));

            if (effectWhileCasting)
                mut.phase = 2;

            if (simultaneously) {
                Future<?> future = _skillCast2;
                if (future != null) {
                    future.cancel(true);
                    _skillCast2 = null;
                }

                // Create a task MagicUseTask to launch the MagicSkill at the end of the casting time (hitTime)
                // For client animation reasons (party buffs especially) 400 ms before!
                _skillCast2 = ThreadPoolManager.getInstance().scheduleEffect(mut, hitTime - 400);
            } else {
                Future<?> future = _skillCast;
                if (future != null) {
                    future.cancel(true);
                    _skillCast = null;
                }

                // Create a task MagicUseTask to launch the MagicSkill at the end of the casting time (hitTime)
                // For client animation reasons (party buffs especially) 400 ms before!
                _skillCast = ThreadPoolManager.getInstance().scheduleEffect(mut, hitTime - 400);
            }
        } else {
            mut.hitTime = 0;
            onMagicLaunchedTimer(mut);
        }
    }

    /**
     * Check if casting of skill is possible
     * 
     * @param skill
     * @return True if casting is possible
     */
    protected boolean checkDoCastConditions(L2Skill skill) {
        if (skill == null || isSkillDisabled(skill)) {
            // Send a Server->Client packet ActionFailed to the L2PcInstance
            sendPacket(ActionFailed.STATIC_PACKET);
            return false;
        }

        // Check if the caster has enough MP
        if (getCurrentMp() < getStat().getMpConsume(skill) + getStat().getMpInitialConsume(skill)) {
            // Send a System Message to the caster
            sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_MP));

            // Send a Server->Client packet ActionFailed to the L2PcInstance
            sendPacket(ActionFailed.STATIC_PACKET);
            return false;
        }

        // Check if the caster has enough HP
        if (getCurrentHp() <= skill.getHpConsume()) {
            // Send a System Message to the caster
            sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_HP));

            // Send a Server->Client packet ActionFailed to the L2PcInstance
            sendPacket(ActionFailed.STATIC_PACKET);
            return false;
        }

        // Verify the different types of silence (magic and physic)
        if (!skill.isPotion() && ((skill.isMagic() && isMuted()) || isPhysicalMuted())) {
            // Send a Server->Client packet ActionFailed to the L2PcInstance
            sendPacket(ActionFailed.STATIC_PACKET);
            return false;
        }

        // prevent casting signets to peace zone
        if (skill.getSkillType() == L2SkillType.SIGNET || skill.getSkillType() == L2SkillType.SIGNET_CASTTIME) {
            L2WorldRegion region = getWorldRegion();
            if (region == null)
                return false;

            if (skill.getTargetType() == SkillTargetType.TARGET_GROUND && this instanceof L2PcInstance) {
                Point3D wp = ((L2PcInstance) this).getCurrentSkillWorldPosition();
                if (!region.checkEffectRangeInsidePeaceZone(skill, wp.getX(), wp.getY(), wp.getZ())) {
                    sendPacket(
                            SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED).addSkillName(skill));
                    return false;
                }
            } else if (!region.checkEffectRangeInsidePeaceZone(skill, getX(), getY(), getZ())) {
                sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED).addSkillName(skill));
                return false;
            }
        }

        // Check if the caster owns the weapon needed
        if (!skill.getWeaponDependancy(this)) {
            // Send a Server->Client packet ActionFailed to the L2PcInstance
            sendPacket(ActionFailed.STATIC_PACKET);
            return false;
        }

        // Check if the spell consumes an Item
        if (skill.getItemConsumeId() > 0 && getInventory() != null) {
            // Get the L2ItemInstance consumed by the spell
            L2ItemInstance requiredItems = getInventory().getItemByItemId(skill.getItemConsumeId());

            // Check if the caster owns enough consumed Item to cast
            if (requiredItems == null || requiredItems.getCount() < skill.getItemConsume()) {
                // Checked: when a summon skill failed, server show required consume item count
                if (skill.getSkillType() == L2SkillType.SUMMON) {
                    SystemMessage sm = SystemMessage
                            .getSystemMessage(SystemMessageId.SUMMONING_SERVITOR_COSTS_S2_S1);
                    sm.addItemName(skill.getItemConsumeId());
                    sm.addNumber(skill.getItemConsume());
                    sendPacket(sm);
                    return false;
                }

                sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NUMBER_INCORRECT));
                return false;
            }
        }

        return true;
    }

    /**
     * Index according to skill id the current timestamp of use, overridden in L2PcInstance.
     * 
     * @param skill
     *            id
     * @param reuse
     *            delay
     */
    public void addTimeStamp(L2Skill skill, long reuse) {
    }

    public void startFusionSkill(L2Character target, L2Skill skill) {
        if (skill.getSkillType() != L2SkillType.FUSION)
            return;

        if (_fusionSkill == null)
            _fusionSkill = new FusionSkill(this, target, skill);
    }

    /**
     * Kill the L2Character.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <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 L2Character</li> <li>Send the Server->Client packet StatusUpdate with current HP and MP to all other
     * L2PcInstance to inform</li> <li>Notify L2Character AI</li><BR>
     * <BR>
     * <B><U> Overridden in </U> :</B><BR>
     * <BR>
     * <li>L2Npc : Create a DecayTask to remove the corpse of the L2Npc after 7 seconds</li> <li>L2Attackable : Distribute rewards (EXP, SP,
     * Drops...) and notify Quest Engine</li> <li>L2PcInstance : Apply Death Penalty, Manage gain/loss Karma and Item Drop</li><BR>
     * <BR>
     * 
     * @param killer
     *            The L2Character who killed it
     * @return true if successful.
     */
    public boolean doDie(L2Character killer) {
        // killing is only possible one time
        synchronized (this) {
            if (isDead())
                return false;

            // now reset currentHp to zero
            setCurrentHp(0);

            setIsDead(true);
        }

        // Set target to null and cancel Attack or Cast
        setTarget(null);

        // Stop movement
        stopMove(null);

        // Stop Regeneration task, and removes all current effects
        getStatus().stopHpMpRegeneration();
        stopAllEffectsExceptThoseThatLastThroughDeath();

        calculateRewards(killer);

        // Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform
        broadcastStatusUpdate();

        // Notify L2Character AI
        if (hasAI())
            getAI().notifyEvent(CtrlEvent.EVT_DEAD, null);

        if (getWorldRegion() != null)
            getWorldRegion().onDeath(this);

        getAttackByList().clear();

        // If character is PhoenixBlessed a resurrection popup will show up
        if (this instanceof L2Summon) {
            if (((L2Summon) this).isPhoenixBlessed() && ((L2Summon) this).getOwner() != null)
                ((L2Summon) this).getOwner().reviveRequest(((L2Summon) this).getOwner(), null, true);
        } else if (this instanceof L2PcInstance) {
            if (((L2Playable) this).isPhoenixBlessed())
                ((L2PcInstance) this).reviveRequest(((L2PcInstance) this), null, false);
            else if (isAffected(CharEffectList.EFFECT_FLAG_CHARM_OF_COURAGE) && ((L2PcInstance) this).isInSiege())
                ((L2PcInstance) this).reviveRequest(((L2PcInstance) this), null, false);
        }

        try {
            if (_fusionSkill != null)
                abortCast();

            for (L2Character character : getKnownList().getKnownCharacters())
                if (character.getFusionSkill() != null && character.getFusionSkill().getTarget() == this)
                    character.abortCast();
        } catch (Exception e) {
            _log.warn(e.getLocalizedMessage(), e);
        }

        return true;
    }

    public void deleteMe() {
        if (hasAI())
            getAI().stopAITask();
    }

    protected void calculateRewards(L2Character killer) {
    }

    /** Sets HP, MP and CP and revives the L2Character. */
    public void doRevive() {
        if (!isDead())
            return;

        if (!isTeleporting()) {
            setIsPendingRevive(false);
            setIsDead(false);
            boolean restorefull = false;

            if (this instanceof L2Playable && ((L2Playable) this).isPhoenixBlessed()) {
                restorefull = true;
                ((L2Playable) this).stopPhoenixBlessing(null);
            }

            if (restorefull) {
                _status.setCurrentHp(getMaxHp());
                _status.setCurrentMp(getMaxMp());
            } else
                _status.setCurrentHp(getMaxHp() * PlayersConfig.RESPAWN_RESTORE_HP);

            // Start broadcast status
            broadcastPacket(new Revive(this));

            // Start paralyze task if it's a player
            if (this instanceof L2PcInstance) {
                final L2PcInstance player = ((L2PcInstance) this);

                // Schedule a paralyzed task to wait for the animation to finish
                ThreadPoolManager.getInstance().scheduleGeneral(new Runnable() {
                    @Override
                    public void run() {
                        player.setIsParalyzed(false);
                    }
                }, player.getAnimationTimer());
                setIsParalyzed(true);
            }

            if (getWorldRegion() != null)
                getWorldRegion().onRevive(this);
        } else
            setIsPendingRevive(true);
    }

    /**
     * Revives the L2Character using skill.
     * 
     * @param revivePower
     */
    public void doRevive(double revivePower) {
        doRevive();
    }

    /**
     * @return the CharacterAI of the L2Character and if its null create a new one.
     */
    public CharacterAI getAI() {
        CharacterAI ai = _ai; // copy handle
        if (ai == null) {
            synchronized (this) {
                if (_ai == null)
                    _ai = new CharacterAI(new AIAccessor());
                return _ai;
            }
        }
        return ai;
    }

    public void setAI(CharacterAI newAI) {
        CharacterAI oldAI = getAI();
        if (oldAI != null && oldAI != newAI && oldAI instanceof AttackableAI)
            ((AttackableAI) oldAI).stopAITask();

        _ai = newAI;
    }

    /**
     * @return True if the L2Character has a CharacterAI.
     */
    public boolean hasAI() {
        return _ai != null;
    }

    /**
     * @return True if the L2Character is RaidBoss or his minion.
     */
    public boolean isRaid() {
        return _isRaid;
    }

    /**
     * Set this Npc as a Raid instance.
     * 
     * @param isRaid
     */
    public void setIsRaid(boolean isRaid) {
        _isRaid = isRaid;
    }

    /**
     * @return True if the L2Character is minion.
     */
    public boolean isMinion() {
        return false;
    }

    /**
     * @return True if the L2Character is Raid minion.
     */
    public boolean isRaidMinion() {
        return false;
    }

    /**
     * @return a list of L2Character that attacked.
     */
    public final Set<L2Character> getAttackByList() {
        if (_attackByList != null)
            return _attackByList;

        synchronized (this) {
            if (_attackByList == null)
                _attackByList = Sets.newSetFromMap(new WeakHashMap<L2Character, Boolean>());
        }
        return _attackByList;
    }

    public final L2Skill getLastSimultaneousSkillCast() {
        return _lastSimultaneousSkillCast;
    }

    public void setLastSimultaneousSkillCast(L2Skill skill) {
        _lastSimultaneousSkillCast = skill;
    }

    public final L2Skill getLastSkillCast() {
        return _lastSkillCast;
    }

    public void setLastSkillCast(L2Skill skill) {
        _lastSkillCast = skill;
    }

    public final boolean isNoRndWalk() {
        return _isNoRndWalk;
    }

    public final void setIsNoRndWalk(boolean value) {
        _isNoRndWalk = value;
    }

    public final boolean isAfraid() {
        return isAffected(CharEffectList.EFFECT_FLAG_FEAR);
    }

    public final boolean isConfused() {
        return isAffected(CharEffectList.EFFECT_FLAG_CONFUSED);
    }

    public final boolean isMuted() {
        return isAffected(CharEffectList.EFFECT_FLAG_MUTED);
    }

    public final boolean isPhysicalMuted() {
        return isAffected(CharEffectList.EFFECT_FLAG_PHYSICAL_MUTED);
    }

    public final boolean isRooted() {
        return isAffected(CharEffectList.EFFECT_FLAG_ROOTED);
    }

    public final boolean isSleeping() {
        return isAffected(CharEffectList.EFFECT_FLAG_SLEEP);
    }

    public final boolean isStunned() {
        return isAffected(CharEffectList.EFFECT_FLAG_STUNNED);
    }

    public final boolean isBetrayed() {
        return isAffected(CharEffectList.EFFECT_FLAG_BETRAYED);
    }

    public final boolean isImmobileUntilAttacked() {
        return isAffected(CharEffectList.EFFECT_FLAG_MEDITATING);
    }

    /**
     * @return True if the L2Character can't use its skills (ex : stun, sleep...).
     */
    public final boolean isAllSkillsDisabled() {
        return _allSkillsDisabled || isStunned() || isImmobileUntilAttacked() || isSleeping() || isParalyzed();
    }

    /**
     * @return True if the L2Character can't attack (stun, sleep, attackEndTime, fakeDeath, paralyse).
     */
    public boolean isAttackingDisabled() {
        return isFlying() || isStunned() || isImmobileUntilAttacked() || isSleeping()
                || _attackEndTime > GameTimeController.getGameTicks() || isParalyzed() || isAlikeDead()
                || isCoreAIDisabled();
    }

    public final Calculator[] getCalculators() {
        return _calculators;
    }

    public final boolean isFlying() {
        return _isFlying;
    }

    public final void setIsFlying(boolean mode) {
        _isFlying = mode;
    }

    public boolean isImmobilized() {
        return _isImmobilized;
    }

    public void setIsImmobilized(boolean value) {
        _isImmobilized = value;
    }

    /**
     * @return True if the L2Character is dead or use fake death.
     */
    public boolean isAlikeDead() {
        return _isDead;
    }

    /**
     * @return True if the L2Character is dead.
     */
    public final boolean isDead() {
        return _isDead;
    }

    public final void setIsDead(boolean value) {
        _isDead = value;
    }

    /**
     * @return True if the L2Character can't move (stun, root, sleep, overload, paralyzed).
     */
    public boolean isMovementDisabled() {
        return isStunned() || isImmobileUntilAttacked() || isRooted() || isSleeping() || isOverloaded()
                || isParalyzed() || isImmobilized() || isAlikeDead() || isTeleporting();
    }

    /**
     * @return True if the L2Character can be controlled by the player (confused, afraid).
     */
    public final boolean isOutOfControl() {
        return isConfused() || isAfraid();
    }

    public final boolean isOverloaded() {
        return _isOverloaded;
    }

    public final void setIsOverloaded(boolean value) {
        _isOverloaded = value;
    }

    public final boolean isParalyzed() {
        return _isParalyzed || isAffected(CharEffectList.EFFECT_FLAG_PARALYZED);
    }

    public final void setIsParalyzed(boolean value) {
        _isParalyzed = value;
    }

    public final boolean isPendingRevive() {
        return isDead() && _isPendingRevive;
    }

    public final void setIsPendingRevive(boolean value) {
        _isPendingRevive = value;
    }

    /**
     * Overriden in L2PcInstance.
     * 
     * @return the L2Summon of the L2Character.
     */
    public L2Summon getPet() {
        return null;
    }

    public final boolean isRiding() {
        return _isRiding;
    }

    public final void setIsRiding(boolean mode) {
        _isRiding = mode;
    }

    public final boolean isRunning() {
        return _isRunning;
    }

    public final void setIsRunning(boolean value) {
        _isRunning = value;
        if (getRunSpeed() != 0)
            broadcastPacket(new ChangeMoveType(this));

        if (this instanceof L2PcInstance)
            ((L2PcInstance) this).broadcastUserInfo();
        else if (this instanceof L2Summon)
            ((L2Summon) this).broadcastStatusUpdate();
        else if (this instanceof L2Npc) {
            Collection<L2PcInstance> plrs = getKnownList().getKnownPlayers().values();
            for (L2PcInstance player : plrs) {
                if (player == null)
                    continue;

                if (getRunSpeed() == 0)
                    player.sendPacket(new ServerObjectInfo((L2Npc) this, player));
                else
                    player.sendPacket(new NpcInfo((L2Npc) this, player));
            }
        }
    }

    /** Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance. */
    public final void setRunning() {
        if (!isRunning())
            setIsRunning(true);
    }

    public final boolean isTeleporting() {
        return _isTeleporting;
    }

    public final void setIsTeleporting(boolean value) {
        _isTeleporting = value;
    }

    public void setIsInvul(boolean b) {
        _isInvul = b;
    }

    public boolean isInvul() {
        return _isInvul || _isTeleporting;
    }

    public void setIsMortal(boolean b) {
        _isMortal = b;
    }

    public boolean isMortal() {
        return _isMortal;
    }

    public boolean isUndead() {
        return false;
    }

    @Override
    public void initKnownList() {
        setKnownList(new CharKnownList(this));
    }

    @Override
    public CharKnownList getKnownList() {
        return ((CharKnownList) super.getKnownList());
    }

    public void initCharStat() {
        _stat = new CharStat(this);
    }

    public CharStat getStat() {
        return _stat;
    }

    public final void setStat(CharStat value) {
        _stat = value;
    }

    public void initCharStatus() {
        _status = new CharStatus(this);
    }

    public CharStatus getStatus() {
        return _status;
    }

    public final void setStatus(CharStatus value) {
        _status = value;
    }

    @Override
    public void initPosition() {
        setObjectPosition(new CharPosition(this));
    }

    @Override
    public CharPosition getPosition() {
        return (CharPosition) super.getPosition();
    }

    public L2CharTemplate getTemplate() {
        return _template;
    }

    /**
     * Set the template of the L2Character.<BR>
     * <BR>
     * Each L2Character owns generic and static properties (ex : all Keltir have the same number of HP...). All of those properties are stored in
     * a different template for each type of L2Character. Each template is loaded once in the server cache memory (reduce memory use). When a new
     * instance of L2Character is spawned, server just create a link between the instance and the template This link is stored in
     * <B>_template</B>
     * 
     * @param template
     *            The template to set up.
     */
    protected final void setTemplate(L2CharTemplate template) {
        _template = template;
    }

    /**
     * @return the Title of the L2Character.
     */
    public final String getTitle() {
        return _title;
    }

    /**
     * Set the Title of the L2Character. Concatens it if length > 16.
     * 
     * @param value
     *            The String to test.
     */
    public final void setTitle(String value) {
        if (value == null)
            _title = "";
        else
            _title = value.length() > 16 ? value.substring(0, 15) : value;
    }

    /** Set the L2Character movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance. */
    public final void setWalking() {
        if (isRunning())
            setIsRunning(false);
    }

    /**
     * Task lauching the function onHitTimer().<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if
     * attacker is a L2PcInstance)</li> <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they
     * are L2PcInstance</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><BR>
     * <BR>
     */
    class HitTask implements Runnable {
        L2Character _hitTarget;
        int _damage;
        boolean _crit;
        boolean _miss;
        byte _shld;
        boolean _soulshot;

        public HitTask(L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, byte shld) {
            _hitTarget = target;
            _damage = damage;
            _crit = crit;
            _shld = shld;
            _miss = miss;
            _soulshot = soulshot;
        }

        @Override
        public void run() {
            try {
                onHitTimer(_hitTarget, _damage, _crit, _miss, _soulshot, _shld);
            } catch (Exception e) {
                _log.warn("Failed executing HitTask.", e);
            }
        }
    }

    /** Task lauching the magic skill phases */
    class MagicUseTask implements Runnable {
        L2Object[] targets;
        L2Skill skill;
        int hitTime;
        int coolTime;
        int phase;
        boolean simultaneously;
        int shots;

        public MagicUseTask(L2Object[] tgts, L2Skill s, int hit, int coolT, boolean simultaneous, int shot) {
            targets = tgts;
            skill = s;
            phase = 1;
            hitTime = hit;
            coolTime = coolT;
            simultaneously = simultaneous;
            shots = shot;
        }

        @Override
        public void run() {
            try {
                switch (phase) {
                case 1:
                    onMagicLaunchedTimer(this);
                    break;
                case 2:
                    onMagicHitTimer(this);
                    break;
                case 3:
                    onMagicFinalizer(this);
                    break;
                default:
                    break;
                }
            } catch (Exception e) {
                _log.warn("Failed executing MagicUseTask.", e);
                if (simultaneously)
                    setIsCastingSimultaneouslyNow(false);
                else
                    setIsCastingNow(false);
            }
        }
    }

    /** Task launching the function useMagic() */
    private static class QueuedMagicUseTask implements Runnable {
        L2PcInstance _currPlayer;
        L2Skill _queuedSkill;
        boolean _isCtrlPressed;
        boolean _isShiftPressed;

        public QueuedMagicUseTask(L2PcInstance currPlayer, L2Skill queuedSkill, boolean isCtrlPressed,
                boolean isShiftPressed) {
            _currPlayer = currPlayer;
            _queuedSkill = queuedSkill;
            _isCtrlPressed = isCtrlPressed;
            _isShiftPressed = isShiftPressed;
        }

        @Override
        public void run() {
            try {
                _currPlayer.useMagic(_queuedSkill, _isCtrlPressed, _isShiftPressed);
            } catch (Exception e) {
                _log.warn("Failed executing QueuedMagicUseTask.", e);
            }
        }
    }

    /** Task of AI notification */
    public class NotifyAITask implements Runnable {
        private final CtrlEvent _evt;

        NotifyAITask(CtrlEvent evt) {
            _evt = evt;
        }

        @Override
        public void run() {
            try {
                getAI().notifyEvent(_evt, null);
            } catch (Throwable t) {
                _log.warn(t.getLocalizedMessage(), t);
            }
        }
    }

    /** Task lauching the magic skill phases */
    class FlyToLocationTask implements Runnable {
        private final L2Object _tgt;
        private final L2Character _actor;
        private final L2Skill _skill;

        public FlyToLocationTask(L2Character actor, L2Object target, L2Skill skill) {
            _actor = actor;
            _tgt = target;
            _skill = skill;
        }

        @Override
        public void run() {
            try {
                FlyType _flyType;

                _flyType = FlyType.valueOf(_skill.getFlyType());

                broadcastPacket(new FlyToLocation(_actor, _tgt, _flyType));
                getPosition().setXYZ(_tgt.getX(), _tgt.getY(), _tgt.getZ());
            } catch (Exception e) {
                _log.warn("Failed executing FlyToLocationTask.", e);
            }
        }
    }

    // =========================================================
    /** Map 32 bits (0x0000) containing all abnormal effect in progress */
    private int _AbnormalEffects;

    protected CharEffectList _effects = new CharEffectList(this);

    // Method - Public
    /**
     * Launch and add L2Effect (including Stack Group management) to L2Character and update client magic icone.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. The
     * Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR>
     * <BR>
     * Several same effect can't be used on a L2Character at the same time. Indeed, effects are not stackable and the last cast will replace the
     * previous in progress. More, some effects belong to the same Stack Group (ex WindWald and Haste Potion). If 2 effects of a same group are
     * used at the same time on a L2Character, only the more efficient (identified by its priority order) will be preserve.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Add the L2Effect to the L2Character _effects</li> <li>If this effect doesn't belong to a Stack Group, add its Funcs to the Calculator
     * set of the L2Character (remove the old one if necessary)</li> <li>If this effect has higher priority in its Stack Group, add its Funcs to
     * the Calculator set of the L2Character (remove previous stacked effect Funcs if necessary)</li> <li>If this effect has NOT higher priority
     * in its Stack Group, set the effect to Not In Use</li> <li>Update active skills in progress icones on player client</li><BR>
     * 
     * @param newEffect
     */
    public void addEffect(L2Effect newEffect) {
        _effects.queueEffect(newEffect, false);
    }

    /**
     * Stop and remove L2Effect (including Stack Group management) from L2Character and update client magic icone.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. The
     * Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR>
     * <BR>
     * Several same effect can't be used on a L2Character at the same time. Indeed, effects are not stackable and the last cast will replace the
     * previous in progress. More, some effects belong to the same Stack Group (ex WindWald and Haste Potion). If 2 effects of a same group are
     * used at the same time on a L2Character, only the more efficient (identified by its priority order) will be preserve.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Remove Func added by this effect from the L2Character Calculator (Stop L2Effect)</li> <li>If the L2Effect belongs to a not empty Stack
     * Group, replace theses Funcs by next stacked effect Funcs</li> <li>Remove the L2Effect from _effects of the L2Character</li> <li>Update
     * active skills in progress icones on player client</li><BR>
     * 
     * @param effect
     */
    public final void removeEffect(L2Effect effect) {
        _effects.queueEffect(effect, true);
    }

    public final void startAbnormalEffect(AbnormalEffect mask) {
        _AbnormalEffects |= mask.getMask();
        updateAbnormalEffect();
    }

    public final void startAbnormalEffect(int mask) {
        _AbnormalEffects |= mask;
        updateAbnormalEffect();
    }

    public final void stopAbnormalEffect(AbnormalEffect mask) {
        _AbnormalEffects &= ~mask.getMask();
        updateAbnormalEffect();
    }

    public final void stopAbnormalEffect(int mask) {
        _AbnormalEffects &= ~mask;
        updateAbnormalEffect();
    }

    /**
     * Stop all active skills effects in progress on the L2Character.<BR>
     * <BR>
     */
    public void stopAllEffects() {
        _effects.stopAllEffects();
    }

    public void stopAllEffectsExceptThoseThatLastThroughDeath() {
        _effects.stopAllEffectsExceptThoseThatLastThroughDeath();
    }

    /**
     * Confused
     */
    public final void startConfused() {
        getAI().notifyEvent(CtrlEvent.EVT_CONFUSED);
        updateAbnormalEffect();
    }

    public final void stopConfused(L2Effect effect) {
        if (effect == null)
            stopEffects(L2EffectType.CONFUSION);
        else
            removeEffect(effect);

        if (!(this instanceof L2PcInstance))
            getAI().notifyEvent(CtrlEvent.EVT_THINK);
        updateAbnormalEffect();
    }

    /**
     * Fake Death
     */
    public final void startFakeDeath() {
        if (!(this instanceof L2PcInstance))
            return;

        ((L2PcInstance) this).setIsFakeDeath(true);
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyEvent(CtrlEvent.EVT_FAKE_DEATH);
        broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_START_FAKEDEATH));
    }

    public final void stopFakeDeath(boolean removeEffects) {
        if (!(this instanceof L2PcInstance))
            return;

        final L2PcInstance player = ((L2PcInstance) this);

        if (removeEffects)
            stopEffects(L2EffectType.FAKE_DEATH);

        // if this is a player instance, start the grace period for this character (grace from mobs only)!
        player.setIsFakeDeath(false);
        player.setRecentFakeDeath(true);

        broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_STOP_FAKEDEATH));
        broadcastPacket(new Revive(this));

        // Schedule a paralyzed task to wait for the animation to finish
        ThreadPoolManager.getInstance().scheduleGeneral(new Runnable() {
            @Override
            public void run() {
                player.setIsParalyzed(false);
            }
        }, player.getAnimationTimer());
        setIsParalyzed(true);
    }

    /**
     * Fear
     */
    public final void startFear() {
        getAI().notifyEvent(CtrlEvent.EVT_AFRAID);
        updateAbnormalEffect();
    }

    public final void stopFear(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.FEAR);
        updateAbnormalEffect();
    }

    /**
     * ImmobileUntilAttacked
     */
    public final void startImmobileUntilAttacked() {
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyEvent(CtrlEvent.EVT_SLEEPING, null);
        updateAbnormalEffect();
    }

    public final void stopImmobileUntilAttacked(L2Effect effect) {
        if (effect == null)
            stopEffects(L2EffectType.IMMOBILEUNTILATTACKED);
        else {
            removeEffect(effect);
            stopSkillEffects(effect.getSkill().getId());
        }

        getAI().notifyEvent(CtrlEvent.EVT_THINK, null);
        updateAbnormalEffect();
    }

    /**
     * Muted
     */
    public final void startMuted() {
        abortCast();
        getAI().notifyEvent(CtrlEvent.EVT_MUTED);
        updateAbnormalEffect();
    }

    public final void stopMuted(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.MUTE);

        updateAbnormalEffect();
    }

    /**
     * Paralize
     */
    public final void startParalyze() {
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyEvent(CtrlEvent.EVT_PARALYZED);
    }

    public final void stopParalyze(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.PARALYZE);

        if (!(this instanceof L2PcInstance))
            getAI().notifyEvent(CtrlEvent.EVT_THINK);
    }

    /**
     * PsychicalMuted
     */
    public final void startPhysicalMuted() {
        getAI().notifyEvent(CtrlEvent.EVT_MUTED);
        updateAbnormalEffect();
    }

    public final void stopPhysicalMuted(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.PHYSICAL_MUTE);

        updateAbnormalEffect();
    }

    /**
     * Root
     */
    public final void startRooted() {
        stopMove(null);
        getAI().notifyEvent(CtrlEvent.EVT_ROOTED);
        updateAbnormalEffect();
    }

    public final void stopRooting(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.ROOT);

        if (!(this instanceof L2PcInstance))
            getAI().notifyEvent(CtrlEvent.EVT_THINK);
        updateAbnormalEffect();
    }

    /**
     * Sleep
     */
    public final void startSleeping() {
        /* Aborts any attacks/casts if slept */
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyEvent(CtrlEvent.EVT_SLEEPING);
        updateAbnormalEffect();
    }

    public final void stopSleeping(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.SLEEP);

        if (!(this instanceof L2PcInstance))
            getAI().notifyEvent(CtrlEvent.EVT_THINK);
        updateAbnormalEffect();
    }

    /**
     * Stun
     */
    public final void startStunning() {
        /* Aborts any attacks/casts if stunned */
        abortAttack();
        abortCast();
        stopMove(null);
        getAI().notifyEvent(CtrlEvent.EVT_STUNNED);

        if (!(this instanceof L2Summon))
            getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);

        updateAbnormalEffect();
    }

    public final void stopStunning(boolean removeEffects) {
        if (removeEffects)
            stopEffects(L2EffectType.STUN);

        if (!(this instanceof L2PcInstance))
            getAI().notifyEvent(CtrlEvent.EVT_THINK);
        updateAbnormalEffect();
    }

    /**
     * Stop and remove the L2Effects corresponding to the L2Skill Identifier and update client magic icon.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. The
     * Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR>
     * <BR>
     * 
     * @param skillId
     *            The L2Skill Identifier of the L2Effect to remove from _effects
     */
    public final void stopSkillEffects(int skillId) {
        _effects.stopSkillEffects(skillId);
    }

    /**
     * Stop and remove the L2Effects corresponding to the L2SkillType and update client magic icon.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. The
     * Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR>
     * <BR>
     * 
     * @param skillType
     *            The L2SkillType of the L2Effect to remove from _effects
     * @param negateLvl
     */
    public final void stopSkillEffects(L2SkillType skillType, int negateLvl) {
        _effects.stopSkillEffects(skillType, negateLvl);
    }

    public final void stopSkillEffects(L2SkillType skillType) {
        _effects.stopSkillEffects(skillType, -1);
    }

    /**
     * Stop and remove all L2Effect of the selected type (ex : BUFF, DMG_OVER_TIME...) from the L2Character and update client magic icone.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. The
     * Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Remove Func added by this effect from the L2Character Calculator (Stop L2Effect)</li> <li>Remove the L2Effect from _effects of the
     * L2Character</li> <li>Update active skills in progress icones on player client</li><BR>
     * <BR>
     * 
     * @param type
     *            The type of effect to stop ((ex : BUFF, DMG_OVER_TIME...)
     */
    public final void stopEffects(L2EffectType type) {
        _effects.stopEffects(type);
    }

    /**
     * Exits all buffs effects of the skills with "removedOnAnyAction" set. Called on any action except movement (attack, cast).
     */
    public final void stopEffectsOnAction() {
        _effects.stopEffectsOnAction();
    }

    /**
     * Exits all buffs effects of the skills with "removedOnDamage" set. Called on decreasing HP and mana burn.
     * 
     * @param awake
     */
    public final void stopEffectsOnDamage(boolean awake) {
        _effects.stopEffectsOnDamage(awake);
    }

    /**
     * <B><U> Overridden in</U> :</B><BR>
     * <BR>
     * <li>L2Npc</li> <li>L2PcInstance</li> <li>L2Summon</li> <li>L2DoorInstance</li><BR>
     * <BR>
     */
    public abstract void updateAbnormalEffect();

    /**
     * Update active skills in progress (In Use and Not In Use because stacked) icones 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 icone on the client.<BR>
     * <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><BR>
     * <BR>
     */
    public final void updateEffectIcons() {
        updateEffectIcons(false);
    }

    /**
     * Updates Effect Icons for this character(palyer/summon) and his party if any<BR>
     * Overridden in:<BR>
     * L2PcInstance<BR>
     * L2Summon<BR>
     * 
     * @param partyOnly
     */
    public void updateEffectIcons(boolean partyOnly) {
        // overridden
    }

    /**
     * In Server->Client packet, each effect is represented by 1 bit of the map (ex : BLEEDING = 0x0001 (bit 1), SLEEP = 0x0080 (bit 8)...). The
     * map is calculated by applying a BINARY OR operation on each effect.
     * 
     * @return a map of 16 bits (0x0000) containing all abnormal effect in progress for this L2Character.
     */
    public int getAbnormalEffect() {
        int ae = _AbnormalEffects;
        if (isStunned())
            ae |= AbnormalEffect.STUN.getMask();
        if (isRooted())
            ae |= AbnormalEffect.ROOT.getMask();
        if (isSleeping())
            ae |= AbnormalEffect.SLEEP.getMask();
        if (isConfused())
            ae |= AbnormalEffect.FEAR.getMask();
        if (isAfraid())
            ae |= AbnormalEffect.FEAR.getMask();
        if (isMuted())
            ae |= AbnormalEffect.MUTED.getMask();
        if (isPhysicalMuted())
            ae |= AbnormalEffect.MUTED.getMask();
        if (isImmobileUntilAttacked())
            ae |= AbnormalEffect.FLOATING_ROOT.getMask();

        return ae;
    }

    /**
     * Return all active skills effects in progress on the L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in <B>_effects</B>. The Integer key of _effects is the L2Skill
     * Identifier that has created the effect.<BR>
     * <BR>
     * 
     * @return A table containing all active skills effect in progress on the L2Character
     */
    public final L2Effect[] getAllEffects() {
        return _effects.getAllEffects();
    }

    /**
     * Return L2Effect in progress on the L2Character corresponding to the L2Skill Identifier.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in <B>_effects</B>.
     * 
     * @param skillId
     *            The L2Skill Identifier of the L2Effect to return from the _effects
     * @return The L2Effect corresponding to the L2Skill Identifier
     */
    public final L2Effect getFirstEffect(int skillId) {
        return _effects.getFirstEffect(skillId);
    }

    /**
     * Return the first L2Effect in progress on the L2Character created by the L2Skill.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in <B>_effects</B>.
     * 
     * @param skill
     *            The L2Skill whose effect must be returned
     * @return The first L2Effect created by the L2Skill
     */
    public final L2Effect getFirstEffect(L2Skill skill) {
        return _effects.getFirstEffect(skill);
    }

    /**
     * Return the first L2Effect in progress on the L2Character corresponding to the Effect Type (ex : BUFF, STUN, ROOT...).<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All active skills effects in progress on the L2Character are identified in ConcurrentHashMap(Integer,L2Effect) <B>_effects</B>. The
     * Integer key of _effects is the L2Skill Identifier that has created the L2Effect.<BR>
     * <BR>
     * 
     * @param tp
     *            The Effect Type of skills whose effect must be returned
     * @return The first L2Effect corresponding to the Effect Type
     */
    public final L2Effect getFirstEffect(L2EffectType tp) {
        return _effects.getFirstEffect(tp);
    }

    // =========================================================
    // NEED TO ORGANIZE AND MOVE TO PROPER PLACE
    /** This class permit to the L2Character AI to obtain informations and uses L2Character method */
    public class AIAccessor {
        public AIAccessor() {
        }

        /**
         * @return the L2Character managed by this Accessor AI.
         */
        public L2Character getActor() {
            return L2Character.this;
        }

        /**
         * Accessor to L2Character moveToLocation() method with an interaction area.
         * 
         * @param x
         * @param y
         * @param z
         * @param offset
         */
        public void moveTo(int x, int y, int z, int offset) {
            moveToLocation(x, y, z, offset);
        }

        /**
         * Accessor to L2Character moveToLocation() method without interaction area.
         * 
         * @param x
         * @param y
         * @param z
         */
        public void moveTo(int x, int y, int z) {
            moveToLocation(x, y, z, 0);
        }

        /**
         * Accessor to L2Character stopMove() method.
         * 
         * @param pos
         *            The L2CharPosition position.
         */
        public void stopMove(L2CharPosition pos) {
            L2Character.this.stopMove(pos);
        }

        /**
         * Accessor to L2Character doAttack() method.
         * 
         * @param target
         *            The target to make checks on.
         */
        public void doAttack(L2Character target) {
            L2Character.this.doAttack(target);
        }

        /**
         * Accessor to L2Character doCast() method.
         * 
         * @param skill
         *            The skill object to launch.
         */
        public void doCast(L2Skill skill) {
            L2Character.this.doCast(skill);
        }

        /**
         * @param evt
         *            An event which happens.
         * @return a new NotifyAITask.
         */
        public NotifyAITask newNotifyTask(CtrlEvent evt) {
            return new NotifyAITask(evt);
        }

        /**
         * Cancel the AI.
         */
        public void detachAI() {
            _ai = null;
        }
    }

    /**
     * This class group all mouvement data.<BR>
     * <BR>
     * <B><U> Data</U> :</B><BR>
     * <BR>
     * <li>_moveTimestamp : Last time position update</li> <li>_xDestination, _yDestination, _zDestination : Position of the destination</li> <li>
     * _xMoveFrom, _yMoveFrom, _zMoveFrom : Position of the origin</li> <li>_moveStartTime : Start time of the movement</li> <li>_ticksToMove :
     * Nb of ticks between the start and the destination</li> <li>_xSpeedTicks, _ySpeedTicks : Speed in unit/ticks</li><BR>
     * <BR>
     */
    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 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<AbstractNodeLoc> geoPath;
        public int geoPathAccurateTx;
        public int geoPathAccurateTy;
        public int geoPathGtx;
        public int geoPathGty;
    }

    /** Table containing all skillId that are disabled */
    protected Map<Integer, Long> _disabledSkills;
    private boolean _allSkillsDisabled;

    /** Movement data of this L2Character */
    protected MoveData _move;

    /** Orientation of the L2Character */
    private int _heading;

    /** L2Charcater targeted by the L2Character */
    private L2Object _target;

    // set by the start of attack, in game ticks
    private int _attackEndTime;
    private int _attacking;
    private int _disableBowAttackEndTime;
    private int _castInterruptTime;

    /** Table of calculators containing all standard NPC calculator (ex : ACCURACY_COMBAT, EVASION_RATE */
    private static final Calculator[] NPC_STD_CALCULATOR;
    static {
        NPC_STD_CALCULATOR = Formulas.getStdNPCCalculators();
    }

    protected CharacterAI _ai;

    /** Future Skill Cast */
    protected Future<?> _skillCast;
    protected Future<?> _skillCast2;

    /**
     * Add a Func to the Calculator set of the L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * A L2Character owns a table of Calculators called <B>_calculators</B>. Each Calculator (a calculator per state) own a table of Func object.
     * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). To reduce cache
     * memory use, L2Npcs who don't have skills share the same Calculator set called <B>NPC_STD_CALCULATOR</B>.<BR>
     * <BR>
     * That's why, if a L2Npc is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR must be create in its
     * _calculators before addind new Func object.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>If _calculators is linked to NPC_STD_CALCULATOR, create a copy of NPC_STD_CALCULATOR in _calculators</li> <li>Add the Func object to
     * _calculators</li><BR>
     * <BR>
     * 
     * @param f
     *            The Func object to add to the Calculator corresponding to the state affected
     */
    public final void addStatFunc(Func f) {
        if (f == null)
            return;

        synchronized (_calculators) {
            // Check if Calculator set is linked to the standard Calculator set of NPC
            if (_calculators == NPC_STD_CALCULATOR) {
                // Create a copy of the standard NPC Calculator set
                _calculators = new Calculator[Stats.NUM_STATS];

                for (int i = 0; i < Stats.NUM_STATS; i++) {
                    if (NPC_STD_CALCULATOR[i] != null)
                        _calculators[i] = new Calculator(NPC_STD_CALCULATOR[i]);
                }
            }

            // Select the Calculator of the affected state in the Calculator set
            int stat = f.stat.ordinal();

            if (_calculators[stat] == null)
                _calculators[stat] = new Calculator();

            // Add the Func to the calculator corresponding to the state
            _calculators[stat].addFunc(f);
        }
    }

    /**
     * Add a list of Funcs to the Calculator set of the L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * A L2Character owns a table of Calculators called <B>_calculators</B>. Each Calculator (a calculator per state) own a table of Func object.
     * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). <BR>
     * <BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is ONLY for L2PcInstance</B></FONT><BR>
     * <BR>
     * <B><U> Example of use </U> :</B><BR>
     * <BR>
     * <li>Equip an item from inventory</li> <li>Learn a new passive skill</li> <li>Use an active skill</li><BR>
     * <BR>
     * 
     * @param funcs
     *            The list of Func objects to add to the Calculator corresponding to the state affected
     */
    public final void addStatFuncs(Func[] funcs) {
        FastList<Stats> modifiedStats = new FastList<>();

        for (Func f : funcs) {
            modifiedStats.add(f.stat);
            addStatFunc(f);
        }
        broadcastModifiedStats(modifiedStats);
    }

    /**
     * Remove all Func objects with the selected owner from the Calculator set of the L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * A L2Character owns a table of Calculators called <B>_calculators</B>. Each Calculator (a calculator per state) own a table of Func object.
     * A Func object is a mathematic function that permit to calculate the modifier of a state (ex : REGENERATE_HP_RATE...). To reduce cache
     * memory use, L2Npcs who don't have skills share the same Calculator set called <B>NPC_STD_CALCULATOR</B>.<BR>
     * <BR>
     * That's why, if a L2Npc is under a skill/spell effect that modify one of its state, a copy of the NPC_STD_CALCULATOR must be create in its
     * _calculators before addind new Func object.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Remove all Func objects of the selected owner from _calculators</li><BR>
     * <BR>
     * <li>If L2Character is a L2Npc and _calculators is equal to NPC_STD_CALCULATOR, free cache memory and just create a link on
     * NPC_STD_CALCULATOR in _calculators</li><BR>
     * <BR>
     * <B><U> Example of use </U> :</B><BR>
     * <BR>
     * <li>Unequip an item from inventory</li> <li>Stop an active skill</li><BR>
     * <BR>
     * 
     * @param owner
     *            The Object(Skill, Item...) that has created the effect
     */
    public final void removeStatsOwner(Object owner) {
        FastList<Stats> modifiedStats = null;

        int i = 0;
        // Go through the Calculator set
        synchronized (_calculators) {
            for (Calculator calc : _calculators) {
                if (calc != null) {
                    // Delete all Func objects of the selected owner
                    if (modifiedStats != null)
                        modifiedStats.addAll(calc.removeOwner(owner));
                    else
                        modifiedStats = calc.removeOwner(owner);

                    if (calc.size() == 0)
                        _calculators[i] = null;
                }
                i++;
            }

            // If possible, free the memory and just create a link on NPC_STD_CALCULATOR
            if (this instanceof L2Npc) {
                i = 0;
                for (; i < Stats.NUM_STATS; i++) {
                    if (!Calculator.equalsCals(_calculators[i], NPC_STD_CALCULATOR[i]))
                        break;
                }

                if (i >= Stats.NUM_STATS)
                    _calculators = NPC_STD_CALCULATOR;
            }

            if (owner instanceof L2Effect) {
                if (!((L2Effect) owner).preventExitUpdate)
                    broadcastModifiedStats(modifiedStats);
            } else
                broadcastModifiedStats(modifiedStats);
        }
    }

    private void broadcastModifiedStats(FastList<Stats> stats) {
        if (stats == null || stats.isEmpty())
            return;

        boolean broadcastFull = false;
        StatusUpdate su = null;

        if (this instanceof L2Summon && ((L2Summon) this).getOwner() != null)
            ((L2Summon) this).updateAndBroadcastStatusAndInfos(1);
        else {
            for (Stats stat : stats) {
                if (stat == Stats.POWER_ATTACK_SPEED) {
                    if (su == null)
                        su = new StatusUpdate(this);

                    su.addAttribute(StatusUpdate.ATK_SPD, getPAtkSpd());
                } else if (stat == Stats.MAGIC_ATTACK_SPEED) {
                    if (su == null)
                        su = new StatusUpdate(this);

                    su.addAttribute(StatusUpdate.CAST_SPD, getMAtkSpd());
                } else if (stat == Stats.MAX_HP && this instanceof L2Attackable) {
                    if (su == null)
                        su = new StatusUpdate(this);

                    su.addAttribute(StatusUpdate.MAX_HP, getMaxHp());
                } else if (stat == Stats.RUN_SPEED)
                    broadcastFull = true;
            }
        }

        if (this instanceof L2PcInstance) {
            if (broadcastFull)
                ((L2PcInstance) this).updateAndBroadcastStatus(2);
            else {
                ((L2PcInstance) this).updateAndBroadcastStatus(1);
                if (su != null)
                    broadcastPacket(su);
            }
        } else if (this instanceof L2Npc) {
            if (broadcastFull) {
                Collection<L2PcInstance> plrs = getKnownList().getKnownPlayers().values();
                for (L2PcInstance player : plrs) {
                    if (player == null)
                        continue;

                    if (getRunSpeed() == 0)
                        player.sendPacket(new ServerObjectInfo((L2Npc) this, player));
                    else
                        player.sendPacket(new NpcInfo((L2Npc) this, player));
                }
            } else if (su != null)
                broadcastPacket(su);
        } else if (su != null)
            broadcastPacket(su);
    }

    /**
     * @return the orientation of the L2Character.
     */
    public final int getHeading() {
        return _heading;
    }

    /**
     * Set the orientation of the L2Character.
     * 
     * @param heading
     */
    public final void setHeading(int heading) {
        _heading = heading;
    }

    public final int getXdestination() {
        MoveData m = _move;
        if (m != null)
            return m._xDestination;

        return getX();
    }

    public final int getYdestination() {
        MoveData m = _move;
        if (m != null)
            return m._yDestination;

        return getY();
    }

    public final int getZdestination() {
        MoveData m = _move;
        if (m != null)
            return m._zDestination;

        return getZ();
    }

    /**
     * @return True if the L2Character is in combat.
     */
    public boolean isInCombat() {
        return (getAI().getAttackTarget() != null || getAI().isAutoAttacking());
    }

    /**
     * @return True if the L2Character is moving.
     */
    public final boolean isMoving() {
        return _move != null;
    }

    /**
     * @return True if the L2Character is travelling a calculated path.
     */
    public final boolean isOnGeodataPath() {
        MoveData m = _move;
        if (m == null)
            return false;
        if (m.onGeodataPathIndex == -1)
            return false;
        if (m.onGeodataPathIndex == m.geoPath.size() - 1)
            return false;
        return true;
    }

    /**
     * @return True if the L2Character is casting.
     */
    public final boolean isCastingNow() {
        return _isCastingNow;
    }

    public void setIsCastingNow(boolean value) {
        _isCastingNow = value;
    }

    public final boolean isCastingSimultaneouslyNow() {
        return _isCastingSimultaneouslyNow;
    }

    public void setIsCastingSimultaneouslyNow(boolean value) {
        _isCastingSimultaneouslyNow = value;
    }

    /**
     * @return True if the cast of the L2Character can be aborted.
     */
    public final boolean canAbortCast() {
        return _castInterruptTime > GameTimeController.getGameTicks();
    }

    /**
     * @return True if the L2Character is attacking.
     */
    public boolean isAttackingNow() {
        return _attackEndTime > GameTimeController.getGameTicks();
    }

    /**
     * @return True if the L2Character has aborted its attack.
     */
    public final boolean isAttackAborted() {
        return _attacking <= 0;
    }

    /**
     * Abort the attack of the L2Character and send Server->Client ActionFailed packet.
     */
    public final void abortAttack() {
        if (isAttackingNow()) {
            _attacking = 0;
            sendPacket(ActionFailed.STATIC_PACKET);
        }
    }

    /**
     * @return the body part (paperdoll slot) we are targeting right now.
     */
    public final int getAttackingBodyPart() {
        return _attacking;
    }

    /**
     * Abort the cast of the L2Character and send Server->Client MagicSkillCanceld/ActionFailed packet.<BR>
     * <BR>
     */
    public final void abortCast() {
        if (isCastingNow() || isCastingSimultaneouslyNow()) {
            Future<?> future = _skillCast;
            // cancels the skill hit scheduled task
            if (future != null) {
                future.cancel(true);
                _skillCast = null;
            }
            future = _skillCast2;
            if (future != null) {
                future.cancel(true);
                _skillCast2 = null;
            }

            if (getFusionSkill() != null)
                getFusionSkill().onCastAbort();

            L2Effect mog = getFirstEffect(L2EffectType.SIGNET_GROUND);
            if (mog != null)
                mog.exit();

            if (_allSkillsDisabled)
                enableAllSkills(); // this remains for forced skill use, e.g. scroll of escape

            setIsCastingNow(false);
            setIsCastingSimultaneouslyNow(false);

            // safeguard for cannot be interrupt any more
            _castInterruptTime = 0;

            if (this instanceof L2PcInstance)
                getAI().notifyEvent(CtrlEvent.EVT_FINISH_CASTING); // setting back previous intention

            broadcastPacket(new MagicSkillCanceld(getObjectId())); // broadcast packet to stop animations client-side
            sendPacket(ActionFailed.STATIC_PACKET); // send an "action failed" packet to the caster
        }
    }

    /**
     * Update the position of the L2Character 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
     * L2Character. 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>
     * <BR>
     * When the movement is started (ex : by MovetoLocation), this method will be called each 0.1 sec to estimate and update the L2Character
     * position on the server. Note, that the current server position can differe from the current client position even if each movement is
     * straight foward. That's why, client send regularly a Client->Server ValidatePosition packet to eventually correct the gap on the server.
     * But, it's always the server position that is used in range calculation.<BR>
     * <BR>
     * At the end of the estimated movement time, the L2Character position is automatically set to the destination position even if the movement
     * is not finished.<BR>
     * <BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : The current Z position is obtained FROM THE CLIENT by the Client->Server ValidatePosition Packet.
     * But x and y positions must be calculated to avoid that players try to modify their movement speed.</B></FONT><BR>
     * <BR>
     * 
     * @param gameTicks
     *            Nb of ticks since the server start
     * @return True if the movement is finished
     */
    public boolean updatePosition(int gameTicks) {
        // Get movement data
        MoveData m = _move;

        if (m == null)
            return true;

        if (!isVisible()) {
            _move = null;
            return true;
        }

        // Check if this is the first update
        if (m._moveTimestamp == 0) {
            m._moveTimestamp = m._moveStartTime;
            m._xAccurate = getX();
            m._yAccurate = getY();
        }

        // Check if the position has already been calculated
        if (m._moveTimestamp == gameTicks)
            return false;

        int xPrev = getX();
        int yPrev = getY();
        int zPrev = getZ(); // the z coordinate may be modified by coordinate synchronizations

        double dx, dy, dz;
        if (MainConfig.COORD_SYNCHRONIZE == 1)
        // the only method that can modify x,y while moving (otherwise _move would/should be set null)
        {
            dx = m._xDestination - xPrev;
            dy = m._yDestination - yPrev;
        } else
        // otherwise we need saved temporary values to avoid rounding errors
        {
            dx = m._xDestination - m._xAccurate;
            dy = m._yDestination - m._yAccurate;
        }

        final boolean isFloating = isFlying() || isInsideZone(L2Character.ZONE_WATER);

        // Z coordinate will follow geodata or client values
        if (MainConfig.GEODATA > 0 && MainConfig.COORD_SYNCHRONIZE == 2 && !isFloating && !m.disregardingGeodata
                && GameTimeController.getGameTicks() % 10 == 0 // once
                // a
                // second
                // to
                // reduce
                // possible
                // cpu
                // load
                && GeoData.getInstance().hasGeo(xPrev, yPrev)) {
            short geoHeight = GeoData.getInstance().getSpawnHeight(xPrev, yPrev, zPrev - 30, zPrev + 30, null);
            dz = m._zDestination - geoHeight;
            // quite a big difference, compare to validatePosition packet
            if (this instanceof L2PcInstance && Math.abs(((L2PcInstance) this).getClientZ() - geoHeight) > 200
                    && Math.abs(((L2PcInstance) this).getClientZ() - geoHeight) < 1500) {
                dz = m._zDestination - zPrev; // allow diff
            } else if (isInCombat() && Math.abs(dz) > 200 && (dx * dx + dy * dy) < 40000) // allow mob to climb up to pcinstance
                dz = m._zDestination - zPrev; // climbing
            else
                zPrev = geoHeight;
        } else
            dz = m._zDestination - zPrev;

        double delta = dx * dx + dy * dy;
        if (delta < 10000 && (dz * dz > 2500) // close enough, allows error between client and server geodata if it cannot be
        // avoided
                && !isFloating) // should not be applied on vertical movements in water or during flight
            delta = Math.sqrt(delta);
        else
            delta = Math.sqrt(delta + dz * dz);

        double distFraction = Double.MAX_VALUE;
        if (delta > 1) {
            final double distPassed = getStat().getMoveSpeed() * (gameTicks - m._moveTimestamp)
                    / GameTimeController.TICKS_PER_SECOND;
            distFraction = distPassed / delta;
        }

        if (distFraction > 1) // already there
            // Set the position of the L2Character to the destination
            super.getPosition().setXYZ(m._xDestination, m._yDestination, m._zDestination);
        else {
            m._xAccurate += dx * distFraction;
            m._yAccurate += dy * distFraction;

            // Set the position of the L2Character to estimated after parcial move
            super.getPosition().setXYZ((int) (m._xAccurate), (int) (m._yAccurate),
                    zPrev + (int) (dz * distFraction + 0.5));
        }
        revalidateZone(false);

        // Set the timer of last position update to now
        m._moveTimestamp = gameTicks;

        return (distFraction > 1);
    }

    public void revalidateZone(boolean force) {
        if (getWorldRegion() == null)
            return;

        // This function is called too often from movement code
        if (force)
            _zoneValidateCounter = 4;
        else {
            _zoneValidateCounter--;
            if (_zoneValidateCounter < 0)
                _zoneValidateCounter = 4;
            else
                return;
        }
        getWorldRegion().revalidateZones(this);
    }

    /**
     * Stop movement of the L2Character (Called by AI Accessor only).<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Delete movement data of the L2Character</li> <li>Set the current position (x,y,z), its current L2WorldRegion if necessary and its
     * heading</li> <li>Remove the L2Object object from _gmList** of GmListTable</li> <li>Remove object from _knownObjects and _knownPlayer* of
     * all surrounding L2WorldRegion L2Characters</li><BR>
     * <BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : This method DOESN'T send Server->Client packet StopMove/StopRotation </B></FONT><BR>
     * <BR>
     * 
     * @param pos
     */
    public void stopMove(L2CharPosition pos) {
        stopMove(pos, false);
    }

    public void stopMove(L2CharPosition pos, boolean updateKnownObjects) {
        // Delete movement data of the L2Character
        _move = null;

        // Set the current position (x,y,z), its current L2WorldRegion if necessary and its heading
        // All data are contained in a L2CharPosition object
        if (pos != null) {
            getPosition().setXYZ(pos.x, pos.y, pos.z);
            setHeading(pos.heading);
            revalidateZone(true);
        }
        broadcastPacket(new StopMove(this));

        if (NPCConfig.MOVE_BASED_KNOWNLIST && updateKnownObjects)
            getKnownList().findObjects();
    }

    /**
     * @return Returns the showSummonAnimation.
     */
    public boolean isShowSummonAnimation() {
        return _showSummonAnimation;
    }

    /**
     * @param showSummonAnimation
     *            The showSummonAnimation to set.
     */
    public void setShowSummonAnimation(boolean showSummonAnimation) {
        _showSummonAnimation = showSummonAnimation;
    }

    /**
     * Target a L2Object (add the target to the L2Character _target, _knownObject and L2Character to _KnownObject of the L2Object).<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * The L2Object (including L2Character) targeted is identified in <B>_target</B> of the L2Character<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Set the _target of L2Character to L2Object</li> <li>If necessary, add L2Object to _knownObject of the L2Character</li> <li>If
     * necessary, add L2Character to _KnownObject of the L2Object</li> <li>If object==null, cancel Attak or Cast</li><BR>
     * <BR>
     * <B><U> Overridden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance : Remove the L2PcInstance from the old target _statusListener and add it to the new target if it was a L2Character</li><BR>
     * <BR>
     * 
     * @param object
     *            L2object to target
     */
    public void setTarget(L2Object object) {
        if (object != null) {
            if (!object.isVisible())
                object = null;
            else if (object != _target) {
                getKnownList().addKnownObject(object);
                object.getKnownList().addKnownObject(this);
            }
        }

        _target = object;
    }

    /**
     * @return the identifier of the L2Object targeted or -1.
     */
    public final int getTargetId() {
        if (_target != null)
            return _target.getObjectId();

        return -1;
    }

    /**
     * @return the L2Object targeted or null.
     */
    public final L2Object getTarget() {
        return _target;
    }

    /**
     * Calculate movement data for a move to location action and add the L2Character to movingObjects of GameTimeController (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
     * L2Character. 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>
     * <BR>
     * All L2Character in movement are identified in <B>movingObjects</B> of GameTimeController that will call the updatePosition method of those
     * L2Character each 0.1s.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Get current position of the L2Character</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 L2Character _move object to MoveData object</li> <li>Add the L2Character to
     * movingObjects of the GameTimeController</li> <li>Create a task to notify the AI that L2Character arrives at a check point of the movement</li>
     * <BR>
     * <BR>
     * <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><BR>
     * <BR>
     * <li>AI : onIntentionMoveTo(L2CharPosition), onIntentionPickUp(L2Object), onIntentionInteract(L2Object)</li> <li>FollowTask</li> <BR>
     * <BR>
     * 
     * @param x
     *            The X position of the destination
     * @param y
     *            The Y position of the destination
     * @param z
     *            The Y position of the destination
     * @param offset
     *            The size of the interaction area of the L2Character targeted
     */
    protected void moveToLocation(int x, int y, int z, int offset) {
        // Get the Move Speed of the L2Charcater
        float speed = getStat().getMoveSpeed();
        if (speed <= 0 || isMovementDisabled())
            return;

        // Get current position of the L2Character
        final int curX = super.getX();
        final int curY = super.getY();
        final int curZ = super.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.sqrt(dx * dx + dy * 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
        if (MainConfig.GEODATA > 0 && isInsideZone(ZONE_WATER) && distance > 700) {
            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.sqrt(dx * dx + dy * dy);
        }

        _log.debug("distance to target:" + distance);

        // Define movement angles needed
        // ^
        // | X (x,y)
        // | /
        // | /distance
        // | /
        // |/ angle
        // X ---------->
        // (curx,cury)

        double cos;
        double sin;

        // Check if a movement offset is defined or no distance to go through
        if (offset > 0 || distance < 1) {
            // 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 || distance - offset <= 0) {
                // Notify the AI that the L2Character is arrived at destination
                getAI().notifyEvent(CtrlEvent.EVT_ARRIVED);

                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
        MoveData m = new MoveData();

        // GEODATA MOVEMENT CHECKS AND PATHFINDING
        m.onGeodataPathIndex = -1; // Initialize not on geodata path
        m.disregardingGeodata = false;

        if (MainConfig.GEODATA > 0 && !isFlying() // flying chars not checked - even canSeeTarget doesn't work yet
                && (!isInsideZone(ZONE_WATER) || isInsideZone(ZONE_SIEGE)) // swimming also not checked unless in siege zone - but
                // distance is limited
                && !(this instanceof L2NpcWalkerInstance)) // npc walkers not checked
        {
            final boolean isInVehicle = this instanceof L2PcInstance && ((L2PcInstance) this).getVehicle() != null;
            if (isInVehicle)
                m.disregardingGeodata = true;

            double originalDistance = distance;
            int originalX = x;
            int originalY = y;
            int originalZ = z;
            int gtx = (originalX - L2World.MAP_MIN_X) >> 4;
            int gty = (originalY - L2World.MAP_MIN_Y) >> 4;

            // Movement checks:
            // when geodata == 2, for all characters except mobs returning home (could be changed later to teleport if pathfinding
            // fails)
            // when geodata == 1, for l2playableinstance and l2riftinstance only
            if ((MainConfig.GEODATA == 2
                    && !(this instanceof L2Attackable && ((L2Attackable) this).isReturningToSpawnPoint()))
                    || (this instanceof L2PcInstance && !(isInVehicle && distance > 1500))
                    || (this instanceof L2Summon && !(getAI().getIntention() == AI_INTENTION_FOLLOW)) // assuming
                    // intention_follow
                    // only when
                    // following owner
                    || isAfraid() || this instanceof L2RiftInvaderInstance) {
                if (isOnGeodataPath()) {
                    try {
                        if (gtx == _move.geoPathGtx && gty == _move.geoPathGty)
                            return;

                        _move.onGeodataPathIndex = -1; // Set not on geodata path
                    } catch (NullPointerException e) {
                        // nothing
                    }
                }

                if (curX < L2World.MAP_MIN_X || curX > L2World.MAP_MAX_X || curY < L2World.MAP_MIN_Y
                        || curY > L2World.MAP_MAX_Y) {
                    // Temporary fix for character outside world region errors
                    _log.warn("Character " + getName() + " outside world area, in coordinates x:" + curX + " y:"
                            + curY);
                    getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);

                    if (this instanceof L2PcInstance)
                        ((L2PcInstance) this).logout();
                    else if (this instanceof L2Summon)
                        return; // prevention when summon get out of world coords, player will not loose him, unsummon handled
                                // from pcinstance
                    else
                        onDecay();

                    return;
                }
                Location destiny = GeoData.getInstance().moveCheck(curX, curY, curZ, x, y, z);
                // location different if destination wasn't reached (or just z coord is different)
                x = destiny.getX();
                y = destiny.getY();
                z = destiny.getZ();
                dx = x - curX;
                dy = y - curY;
                dz = z - curZ;
                distance = verticalMovementOnly ? Math.abs(dz * dz) : Math.sqrt(dx * dx + dy * dy);
            }
            // Pathfinding checks. Only when geodata setting is 2, the LoS check gives shorter result
            // than the original movement was and the LoS gives a shorter distance than 2000
            // This way of detecting need for pathfinding could be changed.
            if (MainConfig.GEODATA == 2 && originalDistance - distance > 30 && distance < 2000 && !isAfraid()) {
                // Path calculation -- overrides previous movement check
                if ((this instanceof L2Playable && !isInVehicle) || isMinion() || isInCombat()) {
                    m.geoPath = PathFinding.getInstance().findPath(curX, curY, curZ, originalX, originalY,
                            originalZ, this instanceof L2Playable);
                    if (m.geoPath == null || m.geoPath.size() < 2) // No path found
                    {
                        // * Even though there's no path found (remember geonodes aren't perfect),
                        // the mob is attacking and right now we set it so that the mob will go
                        // after target anyway, is dz is small enough.
                        // * With cellpathfinding this approach could be changed but would require taking
                        // off the geonodes and some more checks.
                        // * Summons will follow their masters no matter what.
                        // * Currently minions also must move freely since AttackableAI commands
                        // them to move along with their leader
                        if (this instanceof L2PcInstance
                                || (!(this instanceof L2Playable) && !isMinion() && Math.abs(z - curZ) > 140)
                                || (this instanceof L2Summon && !((L2Summon) this).getFollowStatus())) {
                            getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
                            return;
                        }

                        m.disregardingGeodata = true;
                        x = originalX;
                        y = originalY;
                        z = originalZ;
                        distance = originalDistance;
                    } else {
                        m.onGeodataPathIndex = 0; // on first segment
                        m.geoPathGtx = gtx;
                        m.geoPathGty = gty;
                        m.geoPathAccurateTx = originalX;
                        m.geoPathAccurateTy = originalY;

                        x = m.geoPath.get(m.onGeodataPathIndex).getX();
                        y = m.geoPath.get(m.onGeodataPathIndex).getY();
                        z = m.geoPath.get(m.onGeodataPathIndex).getZ();

                        // check for doors in the route
                        if (DoorData.getInstance().checkIfDoorsBetween(curX, curY, curZ, x, y, z)) {
                            m.geoPath = null;
                            getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
                            return;
                        }
                        for (int i = 0; i < m.geoPath.size() - 1; i++) {
                            if (DoorData.getInstance().checkIfDoorsBetween(m.geoPath.get(i),
                                    m.geoPath.get(i + 1))) {
                                m.geoPath = null;
                                getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
                                return;
                            }
                        }

                        dx = x - curX;
                        dy = y - curY;
                        dz = z - curZ;
                        distance = verticalMovementOnly ? Math.abs(dz * dz) : Math.sqrt(dx * dx + dy * dy);
                        sin = dy / distance;
                        cos = dx / distance;
                    }
                }
            }
            // If no distance to go through, the movement is canceled
            if (distance < 1 && (MainConfig.GEODATA == 2 || this instanceof L2Playable
                    || this instanceof L2RiftInvaderInstance || isAfraid())) {
                if (this instanceof L2Summon)
                    ((L2Summon) this).setFollowStatus(false);
                getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
                return;
            }
        }

        // Apply Z distance for flying or swimming for correct timing calculations
        if ((isFlying() || isInsideZone(ZONE_WATER)) && !verticalMovementOnly)
            distance = Math.sqrt(distance * distance + dz * dz);

        // Caclulate the Nb of ticks between the current position and the destination
        // One tick added for rounding reasons
        int ticksToMove = 1 + (int) (GameTimeController.TICKS_PER_SECOND * distance / speed);
        m._xDestination = x;
        m._yDestination = y;
        m._zDestination = z; // this is what was requested from client

        // Calculate and set the heading of the L2Character
        m._heading = 0; // initial value for coordinate sync
        // Does not broke heading on vertical movements
        if (!verticalMovementOnly)
            setHeading(Util.calculateHeadingFrom(cos, sin));

        _log.debug("dist:" + distance + "speed:" + speed + " ttt:" + ticksToMove + " heading:" + getHeading());

        m._moveStartTime = GameTimeController.getGameTicks();

        // Set the L2Character _move object to MoveData object
        _move = m;

        // Add the L2Character to movingObjects of the GameTimeController
        GameTimeController.getInstance().registerMovingObject(this);

        // Create a task to notify the AI that L2Character arrives at a check point of the movement
        if (ticksToMove * GameTimeController.MILLIS_IN_TICK > 3000)
            ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_ARRIVED_REVALIDATE), 2000);
    }

    public boolean moveToNextRoutePoint() {
        if (!isOnGeodataPath()) {
            // Cancel the move action
            _move = null;
            return false;
        }

        // Get the Move Speed of the L2Charcater
        float speed = getStat().getMoveSpeed();
        if (speed <= 0 || isMovementDisabled()) {
            // Cancel the move action
            _move = null;
            return false;
        }

        MoveData md = _move;
        if (md == null)
            return false;

        // Create and Init a MoveData object
        MoveData m = new MoveData();

        // Update MoveData object
        m.onGeodataPathIndex = md.onGeodataPathIndex + 1; // next segment
        m.geoPath = md.geoPath;
        m.geoPathGtx = md.geoPathGtx;
        m.geoPathGty = md.geoPathGty;
        m.geoPathAccurateTx = md.geoPathAccurateTx;
        m.geoPathAccurateTy = md.geoPathAccurateTy;

        if (md.onGeodataPathIndex == md.geoPath.size() - 2) {
            m._xDestination = md.geoPathAccurateTx;
            m._yDestination = md.geoPathAccurateTy;
            m._zDestination = md.geoPath.get(m.onGeodataPathIndex).getZ();
        } else {
            m._xDestination = md.geoPath.get(m.onGeodataPathIndex).getX();
            m._yDestination = md.geoPath.get(m.onGeodataPathIndex).getY();
            m._zDestination = md.geoPath.get(m.onGeodataPathIndex).getZ();
        }
        double dx = (m._xDestination - super.getX());
        double dy = (m._yDestination - super.getY());
        double distance = Math.sqrt(dx * dx + dy * dy);
        // Calculate and set the heading of the L2Character
        if (distance != 0)
            setHeading(Util.calculateHeadingFrom(getX(), getY(), m._xDestination, m._yDestination));

        // Caclulate the Nb of ticks between the current position and the destination
        // One tick added for rounding reasons
        int ticksToMove = 1 + (int) (GameTimeController.TICKS_PER_SECOND * distance / speed);

        m._heading = 0; // initial value for coordinate sync
        m._moveStartTime = GameTimeController.getGameTicks();

        _log.debug("time to target:" + ticksToMove);

        // Set the L2Character _move object to MoveData object
        _move = m;

        // Add the L2Character to movingObjects of the GameTimeController
        // The GameTimeController manage objects movement
        GameTimeController.getInstance().registerMovingObject(this);

        // Create a task to notify the AI that L2Character arrives at a check point of the movement
        if (ticksToMove * GameTimeController.MILLIS_IN_TICK > 3000)
            ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_ARRIVED_REVALIDATE), 2000);

        // Send a Server->Client packet MoveToLocation to the actor and all L2PcInstance in its _knownPlayers
        MoveToLocation msg = new MoveToLocation(this);
        broadcastPacket(msg);

        return true;
    }

    public boolean validateMovementHeading(int heading) {
        MoveData m = _move;

        if (m == null)
            return true;

        boolean result = true;
        if (m._heading != heading) {
            result = (m._heading == 0); // initial value or false
            m._heading = heading;
        }

        return result;
    }

    /**
     * Return the squared distance between the current position of the L2Character and the given object.<BR>
     * <BR>
     * 
     * @param object
     *            L2Object
     * @return the squared distance
     */
    public final double getDistanceSq(L2Object object) {
        return getDistanceSq(object.getX(), object.getY(), object.getZ());
    }

    /**
     * Return the squared distance between the current position of the L2Character and the given x, y, z.<BR>
     * <BR>
     * 
     * @param x
     *            X position of the target
     * @param y
     *            Y position of the target
     * @param z
     *            Z position of the target
     * @return the squared distance
     */
    public final double getDistanceSq(int x, int y, int z) {
        double dx = x - getX();
        double dy = y - getY();
        double dz = z - getZ();

        return (dx * dx + dy * dy + dz * dz);
    }

    /**
     * Return the squared plan distance between the current position of the L2Character and the given object.<BR>
     * (check only x and y, not z)<BR>
     * <BR>
     * 
     * @param object
     *            L2Object
     * @return the squared plan distance
     */
    public final double getPlanDistanceSq(L2Object object) {
        return getPlanDistanceSq(object.getX(), object.getY());
    }

    /**
     * Return the squared plan distance between the current position of the L2Character and the given x, y, z.<BR>
     * (check only x and y, not z)<BR>
     * <BR>
     * 
     * @param x
     *            X position of the target
     * @param y
     *            Y position of the target
     * @return the squared plan distance
     */
    public final double getPlanDistanceSq(int x, int y) {
        double dx = x - getX();
        double dy = y - getY();

        return (dx * dx + dy * dy);
    }

    /**
     * Check if this object is inside the given radius around the given object. Warning: doesn't cover collision radius!<BR>
     * <BR>
     * 
     * @param object
     *            the target
     * @param radius
     *            the radius around the target
     * @param checkZ
     *            should we check Z axis also
     * @param strictCheck
     *            true if (distance < radius), false if (distance <= radius)
     * @return true is the L2Character is inside the radius.
     */
    public final boolean isInsideRadius(L2Object object, int radius, boolean checkZ, boolean strictCheck) {
        return isInsideRadius(object.getX(), object.getY(), object.getZ(), radius, checkZ, strictCheck);
    }

    /**
     * Check if this object is inside the given plan radius around the given point. Warning: doesn't cover collision radius!<BR>
     * <BR>
     * 
     * @param x
     *            X position of the target
     * @param y
     *            Y position of the target
     * @param radius
     *            the radius around the target
     * @param strictCheck
     *            true if (distance < radius), false if (distance <= radius)
     * @return true is the L2Character is inside the radius.
     */
    public final boolean isInsideRadius(int x, int y, int radius, boolean strictCheck) {
        return isInsideRadius(x, y, 0, radius, false, strictCheck);
    }

    /**
     * Check if this object is inside the given radius around the given point.<BR>
     * <BR>
     * 
     * @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
     * @param checkZ
     *            should we check Z axis also
     * @param strictCheck
     *            true if (distance < radius), false if (distance <= radius)
     * @return true is the L2Character is inside the radius.
     */
    public final boolean isInsideRadius(int x, int y, int z, int radius, boolean checkZ, boolean strictCheck) {
        double dx = x - getX();
        double dy = y - getY();
        double dz = z - getZ();

        if (strictCheck) {
            if (checkZ)
                return (dx * dx + dy * dy + dz * dz) < radius * radius;

            return (dx * dx + dy * dy) < radius * radius;
        }

        if (checkZ)
            return (dx * dx + dy * dy + dz * dz) <= radius * radius;

        return (dx * dx + dy * dy) <= radius * radius;
    }

    /**
     * Set _attacking corresponding to Attacking Body part to CHEST.<BR>
     * <BR>
     */
    public void setAttackingBodypart() {
        _attacking = Inventory.PAPERDOLL_CHEST;
    }

    /**
     * @return True if arrows are available.
     */
    protected boolean checkAndEquipArrows() {
        return true;
    }

    /**
     * Add Exp and Sp to the L2Character.
     * 
     * @param addToExp
     *            An int value.
     * @param addToSp
     *            An int value.
     */
    public void addExpAndSp(long addToExp, int addToSp) {
        // Dummy method (overridden by players and pets)
    }

    /**
     * @return the active weapon instance (always equipped in the right hand).
     */
    public abstract L2ItemInstance getActiveWeaponInstance();

    /**
     * @return the active weapon item (always equipped in the right hand).
     */
    public abstract L2Weapon getActiveWeaponItem();

    /**
     * @return the secondary weapon instance (always equipped in the left hand).
     */
    public abstract L2ItemInstance getSecondaryWeaponInstance();

    /**
     * @return the secondary {@link L2Item} item (always equiped in the left hand).
     */
    public abstract L2Item getSecondaryWeaponItem();

    /**
     * Manage hit process (called by Hit Task).<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if
     * attacker is a L2PcInstance)</li> <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they
     * are L2PcInstance</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><BR>
     * <BR>
     * 
     * @param target
     *            The L2Character targeted
     * @param damage
     *            Nb of HP to reduce
     * @param crit
     *            True if hit is critical
     * @param miss
     *            True if hit is missed
     * @param soulshot
     *            True if SoulShot are charged
     * @param shld
     *            True if shield is efficient
     */
    protected void onHitTimer(L2Character target, int damage, boolean crit, boolean miss, boolean soulshot,
            byte shld) {
        // If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL
        if (target == null || isAlikeDead()) {
            getAI().notifyEvent(CtrlEvent.EVT_CANCEL);
            return;
        }

        if ((this instanceof L2Npc && target.isAlikeDead()) || target.isDead()
                || (!getKnownList().knowsObject(target) && !(this instanceof L2DoorInstance))) {
            getAI().notifyEvent(CtrlEvent.EVT_CANCEL);

            sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }

        if (miss) {
            // Notify target AI
            if (target.hasAI())
                target.getAI().notifyEvent(CtrlEvent.EVT_EVADED, this);

            // ON_EVADED_HIT
            if (target.getChanceSkills() != null)
                target.getChanceSkills().onEvadedHit(this);

            if (target instanceof L2PcInstance)
                ((L2PcInstance) target).sendPacket(
                        SystemMessage.getSystemMessage(SystemMessageId.AVOIDED_S1_ATTACK).addCharName(this));
        }

        // If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance
        if (!isAttackAborted()) {
            // Character will be petrified if attacking a raid that's more than 8 levels lower
            if (target.isRaid() && !NPCConfig.RAID_DISABLE_CURSE) {
                if (getLevel() > target.getLevel() + 8) {
                    L2Skill skill = FrequentSkill.RAID_CURSE2.getSkill();
                    if (skill != null) {
                        // Send visual and skill effects. Caster is the victim.
                        broadcastPacket(new MagicSkillUse(this, this, skill.getId(), skill.getLevel(), 300, 0));
                        skill.getEffects(this, this);
                    }

                    damage = 0; // prevents messing up drop calculation
                }
            }

            // Send message about damage/crit or miss
            sendDamageMessage(target, damage, false, crit, miss);

            // If the target is a player, start AutoAttack
            if (target instanceof L2PcInstance)
                ((L2PcInstance) target).getAI().clientStartAutoAttack();

            if (!miss && damage > 0) {
                L2Weapon weapon = getActiveWeaponItem();
                boolean isBow = (weapon != null && weapon.getItemType() == L2WeaponType.BOW);
                int reflectedDamage = 0;

                // Reflect damage system - do not reflect if weapon is a bow or target is invulnerable
                if (!isBow && !target.isInvul()) {
                    // quick fix for no drop from raid if boss attack high-level char with damage reflection
                    if (!target.isRaid() || getActingPlayer() == null
                            || getActingPlayer().getLevel() <= target.getLevel() + 8) {
                        // Calculate reflection damage to reduce HP of attacker if necessary
                        double reflectPercent = target.getStat().calcStat(Stats.REFLECT_DAMAGE_PERCENT, 0, null,
                                null);
                        if (reflectPercent > 0) {
                            reflectedDamage = (int) (reflectPercent / 100. * damage);

                            // You can't kill someone from a reflect. If value > current HPs, make damages equal to current HP -
                            // 1.
                            int currentHp = (int) getCurrentHp();
                            if (reflectedDamage >= currentHp)
                                reflectedDamage = currentHp - 1;
                        }
                    }
                }

                // Reduce target HPs
                target.reduceCurrentHp(damage, this, null);

                // Reduce attacker HPs in case of a reflect.
                if (reflectedDamage > 0)
                    reduceCurrentHp(reflectedDamage, target, true, false, null);

                if (!isBow) // Do not absorb if weapon is of type bow
                {
                    // Absorb HP from the damage inflicted
                    double absorbPercent = getStat().calcStat(Stats.ABSORB_DAMAGE_PERCENT, 0, null, null);

                    if (absorbPercent > 0) {
                        int maxCanAbsorb = (int) (getMaxHp() - getCurrentHp());
                        int absorbDamage = (int) (absorbPercent / 100. * damage);

                        if (absorbDamage > maxCanAbsorb)
                            absorbDamage = maxCanAbsorb; // Can't absord more than max hp

                        if (absorbDamage > 0)
                            setCurrentHp(getCurrentHp() + absorbDamage);
                    }
                }

                // Notify AI with EVT_ATTACKED
                if (target.hasAI())
                    target.getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, this);

                getAI().clientStartAutoAttack();

                // Manage cast break of the target (calculating rate, sending message...)
                Formulas.calcCastBreak(target, damage);

                // Maybe launch chance skills on us
                if (_chanceSkills != null) {
                    _chanceSkills.onHit(target, damage, false, crit);

                    // Reflect triggers onHit
                    if (reflectedDamage > 0)
                        _chanceSkills.onHit(target, damage, true, false);
                }

                // Maybe launch chance skills on target
                if (target.getChanceSkills() != null)
                    target.getChanceSkills().onHit(this, damage, true, crit);
            }

            // Launch weapon Special ability effect if available
            L2Weapon activeWeapon = getActiveWeaponItem();

            if (activeWeapon != null)
                activeWeapon.getSkillEffects(this, target, crit);

            return;
        }

        if (!isCastingNow() && !isCastingSimultaneouslyNow())
            getAI().notifyEvent(CtrlEvent.EVT_CANCEL);
    }

    /**
     * Break an attack and send Server->Client ActionFailed packet and a System Message to the L2Character.<BR>
     * <BR>
     */
    public void breakAttack() {
        if (isAttackingNow()) {
            // Abort the attack of the L2Character and send Server->Client ActionFailed packet
            abortAttack();

            if (this instanceof L2PcInstance)
                sendPacket(SystemMessage.getSystemMessage(SystemMessageId.ATTACK_FAILED));
        }
    }

    /**
     * Break a cast and send Server->Client ActionFailed packet and a System Message to the L2Character.<BR>
     * <BR>
     */
    public void breakCast() {
        // damage can only cancel magical skills
        if (isCastingNow() && canAbortCast() && getLastSkillCast() != null && getLastSkillCast().isMagic()) {
            // Abort the cast of the L2Character and send Server->Client MagicSkillCanceld/ActionFailed packet.
            abortCast();

            if (this instanceof L2PcInstance)
                sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CASTING_INTERRUPTED));
        }
    }

    /**
     * Reduce the arrow number of the L2Character.<BR>
     * <BR>
     * <B><U> Overriden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance</li><BR>
     * <BR>
     */
    protected void reduceArrowCount() {
        // default is to do nothing
    }

    /**
     * Manage Forced attack (shift + select target).<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>If L2Character 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 L2Character is a L2ArtefactInstance, send a Server->Client
     * packet ActionFailed</li> <li>Send a Server->Client packet MyTargetSelected to start attack and Notify AI with AI_INTENTION_ATTACK</li><BR>
     * <BR>
     * 
     * @param player
     *            The L2PcInstance to attack
     */
    @Override
    public void onForcedAttack(L2PcInstance player) {
        if (isInsidePeaceZone(player)) {
            // If L2Character or target is in a peace zone, send a system message TARGET_IN_PEACEZONE a Server->Client packet
            // ActionFailed
            player.sendPacket(SystemMessageId.TARGET_IN_PEACEZONE);
            player.sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }

        if (player.isInOlympiadMode() && player.getTarget() != null && player.getTarget() instanceof L2Playable) {
            L2PcInstance target = player.getTarget().getActingPlayer();
            if (target == null || (target.isInOlympiadMode()
                    && (!player.isOlympiadStart() || player.getOlympiadGameId() != target.getOlympiadGameId()))) {
                // if L2PcInstance 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().isAttackable()
                && !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 (!GeoData.getInstance().canSeeTarget(player, this)) {
            player.sendPacket(SystemMessageId.CANT_SEE_TARGET);
            player.sendPacket(ActionFailed.STATIC_PACKET);
            return;
        }

        // Notify AI with AI_INTENTION_ATTACK
        player.getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, this);
    }

    /**
     * This method checks if the player given as argument can interact with the L2Npc.
     * 
     * @param player
     *            The player to test
     * @return true if the player can interact with the L2Npc
     */
    public boolean canInteract(L2PcInstance player) {
        // Can't interact while casting a spell.
        if (player.isCastingNow() || player.isCastingSimultaneouslyNow())
            return false;

        // Can't interact while died.
        if (player.isDead() || player.isFakeDeath())
            return false;

        // Can't interact sitted.
        if (player.isSitting())
            return false;

        // Can't interact in shop mode, or during a transaction or a request.
        if (player.getPrivateStoreType() != 0 || player.isProcessingTransaction())
            return false;

        // Can't interact if regular distance doesn't match.
        if (!isInsideRadius(player, L2Npc.INTERACTION_DISTANCE, true, false))
            return false;

        return true;
    }

    /**
     * @param attacker
     *            The attacker to test.
     * @return True if inside peace zone.
     */
    public boolean isInsidePeaceZone(L2PcInstance attacker) {
        return isInsidePeaceZone(attacker, this);
    }

    public static boolean isInsidePeaceZone(L2PcInstance attacker, L2Object target) {
        return (!attacker.getAccessLevel().allowPeaceAttack() && isInsidePeaceZone((L2Object) attacker, target));
    }

    public static boolean isInsidePeaceZone(L2Object attacker, L2Object target) {
        if (target == null)
            return false;

        if (target instanceof L2Npc || attacker instanceof L2Npc)
            return false;

        if (PlayersConfig.KARMA_PLAYER_CAN_BE_KILLED_IN_PZ) {
            // allows red to be attacked and red to attack flagged players
            if (target.getActingPlayer() != null && target.getActingPlayer().getKarma() > 0)
                return false;

            if (attacker.getActingPlayer() != null && attacker.getActingPlayer().getKarma() > 0
                    && target.getActingPlayer() != null && target.getActingPlayer().getPvpFlag() > 0)
                return false;

            if (attacker instanceof L2Character && target instanceof L2Character)
                return (((L2Character) target).isInsideZone(ZONE_PEACE)
                        || ((L2Character) attacker).isInsideZone(ZONE_PEACE));

            if (attacker instanceof L2Character)
                return (TownManager.getTown(target.getX(), target.getY(), target.getZ()) != null
                        || ((L2Character) attacker).isInsideZone(ZONE_PEACE));
        }

        if (attacker instanceof L2Character && target instanceof L2Character)
            return (((L2Character) target).isInsideZone(ZONE_PEACE)
                    || ((L2Character) attacker).isInsideZone(ZONE_PEACE));

        if (attacker instanceof L2Character)
            return (TownManager.getTown(target.getX(), target.getY(), target.getZ()) != null
                    || ((L2Character) attacker).isInsideZone(ZONE_PEACE));

        return (TownManager.getTown(target.getX(), target.getY(), target.getZ()) != null
                || TownManager.getTown(attacker.getX(), attacker.getY(), attacker.getZ()) != null);
    }

    /**
     * @return true if this character is inside an active grid.
     */
    public boolean isInActiveRegion() {
        try {
            L2WorldRegion region = L2World.getInstance().getRegion(getX(), getY());
            return ((region != null) && (region.isActive()));
        } catch (Exception e) {
            if (this instanceof L2PcInstance) {
                _log.warn("Player " + getName() + " at bad coords: (x: " + getX() + ", y: " + getY() + ", z: "
                        + getZ() + ").");
                ((L2PcInstance) this).sendMessage("Error with your coordinates! Please reboot your game fully!");
                ((L2PcInstance) this).teleToLocation(80753, 145481, -3532, false); // Near Giran luxury shop
            } else {
                _log.warn("Object " + getName() + " at bad coords: (x: " + getX() + ", y: " + getY() + ", z: "
                        + getZ() + ").");
                decayMe();
            }
            return false;
        }
    }

    /**
     * @return True if the L2Character has a Party in progress.
     */
    public boolean isInParty() {
        return false;
    }

    /**
     * @return the L2Party object of the L2Character.
     */
    public L2Party getParty() {
        return null;
    }

    /**
     * @param target
     *            The target to test.
     * @param weapon
     *            The wepaon to test.
     * @return The Attack Speed of the L2Character (delay (in milliseconds) before next attack).
     */
    public int calculateTimeBetweenAttacks(L2Character target, L2Weapon weapon) {
        double atkSpd = 0;
        if (weapon != null) {
            switch (weapon.getItemType()) {
            case BOW:
                atkSpd = getStat().getPAtkSpd();
                return (int) (1500 * 345 / atkSpd);
            case DAGGER:
                atkSpd = getStat().getPAtkSpd();
                // atkSpd /= 1.15;
                break;
            default:
                atkSpd = getStat().getPAtkSpd();
            }
        } else
            atkSpd = getPAtkSpd();

        return Formulas.calcPAtkSpd(this, target, atkSpd);
    }

    public int calculateReuseTime(L2Character target, L2Weapon weapon) {
        if (weapon == null)
            return 0;

        // only bows should continue for now
        int reuse = weapon.getReuseDelay();
        if (reuse == 0)
            return 0;

        reuse *= getStat().getWeaponReuseModifier(target);
        double atkSpd = getStat().getPAtkSpd();
        switch (weapon.getItemType()) {
        case BOW:
            return (int) (reuse * 345 / atkSpd);
        default:
            return (int) (reuse * 312 / atkSpd);
        }
    }

    /**
     * @return True if the L2Character use a dual weapon.
     */
    public boolean isUsingDualWeapon() {
        return false;
    }

    /**
     * Add a skill to the L2Character _skills and its Func objects to the calculator set of the L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All skills own by a L2Character are identified in <B>_skills</B><BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Replace oldSkill by newSkill or Add the newSkill</li> <li>If an old skill has been replaced, remove all its Func objects of
     * L2Character calculator set</li> <li>Add Func objects of newSkill to the calculator set of the L2Character</li><BR>
     * <BR>
     * <B><U> Overriden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance : Save update in the character_skills table of the database</li><BR>
     * <BR>
     * 
     * @param newSkill
     *            The L2Skill to add to the L2Character
     * @return The L2Skill replaced or null if just added a new L2Skill
     */
    public L2Skill addSkill(L2Skill newSkill) {
        L2Skill oldSkill = null;

        if (newSkill != null) {
            // 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) {
                // if skill came with another one, we should delete the other one too.
                if (oldSkill.triggerAnotherSkill())
                    removeSkill(oldSkill.getTriggeredId(), true);

                removeStatsOwner(oldSkill);
            }
            // Add Func objects of newSkill to the calculator set of the L2Character
            addStatFuncs(newSkill.getStatFuncs(null, this));

            if (oldSkill != null && _chanceSkills != null)
                removeChanceSkill(oldSkill.getId());

            if (newSkill.isChance())
                addChanceTrigger(newSkill);
        }

        return oldSkill;
    }

    /**
     * Remove a skill from the L2Character and its Func objects from calculator set of the L2Character.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All skills own by a L2Character are identified in <B>_skills</B><BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Remove the skill from the L2Character _skills</li> <li>Remove all its Func objects from the L2Character calculator set</li> <BR>
     * <BR>
     * <B><U> Overriden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance : Save update in the character_skills table of the database</li><BR>
     * <BR>
     * 
     * @param skill
     *            The L2Skill to remove from the L2Character
     * @return The L2Skill removed
     */
    public L2Skill removeSkill(L2Skill skill) {
        if (skill == null)
            return null;

        return removeSkill(skill.getId(), true);
    }

    public L2Skill removeSkill(L2Skill skill, boolean cancelEffect) {
        if (skill == null)
            return null;

        // Remove the skill from the L2Character _skills
        return removeSkill(skill.getId(), cancelEffect);
    }

    public L2Skill removeSkill(int skillId) {
        return removeSkill(skillId, true);
    }

    public L2Skill removeSkill(int skillId, boolean cancelEffect) {
        // Remove the skill from the L2Character _skills
        L2Skill oldSkill = _skills.remove(skillId);

        // Remove all its Func objects from the L2Character calculator set
        if (oldSkill != null) {
            // this is just a fail-safe againts buggers and gm dummies...
            if ((oldSkill.triggerAnotherSkill()) && oldSkill.getTriggeredId() > 0)
                removeSkill(oldSkill.getTriggeredId(), true);

            // Stop casting if this skill is used right now
            if (getLastSkillCast() != null && isCastingNow()) {
                if (oldSkill.getId() == getLastSkillCast().getId())
                    abortCast();
            }
            if (getLastSimultaneousSkillCast() != null && isCastingSimultaneouslyNow()) {
                if (oldSkill.getId() == getLastSimultaneousSkillCast().getId())
                    abortCast();
            }

            if (cancelEffect || oldSkill.isToggle()) {
                removeStatsOwner(oldSkill);
                stopSkillEffects(oldSkill.getId());
            }

            if (oldSkill.isChance() && _chanceSkills != null)
                removeChanceSkill(oldSkill.getId());
        }

        return oldSkill;
    }

    public void removeChanceSkill(int id) {
        if (_chanceSkills == null)
            return;

        synchronized (_chanceSkills) {
            for (IChanceSkillTrigger trigger : _chanceSkills.keySet()) {
                if (!(trigger instanceof L2Skill))
                    continue;
                if (((L2Skill) trigger).getId() == id)
                    _chanceSkills.remove(trigger);
            }
        }
    }

    public void addChanceTrigger(IChanceSkillTrigger trigger) {
        if (_chanceSkills == null) {
            synchronized (this) {
                if (_chanceSkills == null)
                    _chanceSkills = new ChanceSkillList(this);
            }
        }
        _chanceSkills.put(trigger, trigger.getTriggeredChanceCondition());
    }

    public void removeChanceEffect(EffectChanceSkillTrigger effect) {
        if (_chanceSkills == null)
            return;
        _chanceSkills.remove(effect);
    }

    public void onStartChanceEffect() {
        if (_chanceSkills == null)
            return;

        _chanceSkills.onStart();
    }

    public void onActionTimeChanceEffect() {
        if (_chanceSkills == null)
            return;

        _chanceSkills.onActionTime();
    }

    public void onExitChanceEffect() {
        if (_chanceSkills == null)
            return;

        _chanceSkills.onExit();
    }

    /**
     * @return A skill array fed with all skills that L2Character owns.
     */
    public final L2Skill[] getAllSkills() {
        if (_skills == null)
            return new L2Skill[0];

        return _skills.values(new L2Skill[0]);
    }

    public ChanceSkillList getChanceSkills() {
        return _chanceSkills;
    }

    /**
     * Return the level of a skill owned by the L2Character.
     * 
     * @param skillId
     *            The identifier of the L2Skill whose level must be returned
     * @return The level of the L2Skill identified by skillId
     */
    public int getSkillLevel(int skillId) {
        final L2Skill skill = getKnownSkill(skillId);
        if (skill == null)
            return -1;

        return skill.getLevel();
    }

    /**
     * @param skillId
     *            The identifier of the L2Skill to check the knowledge
     * @return True if the skill is known by the L2Character.
     */
    public final L2Skill getKnownSkill(int skillId) {
        if (_skills == null)
            return null;

        return _skills.get(skillId);
    }

    /**
     * Return the number of skills of type(Buff, Debuff, HEAL_PERCENT, MANAHEAL_PERCENT) affecting this L2Character.<BR>
     * <BR>
     * 
     * @return The number of Buffs affecting this L2Character
     */
    public int getBuffCount() {
        return _effects.getBuffCount();
    }

    public int getDanceCount() {
        return _effects.getDanceCount();
    }

    /**
     * Manage the magic skill launching task (MP, HP, Item consummation...) and display the magic skill animation on client.<BR>
     * <BR>
     * <B><U> Actions</U> :</B><BR>
     * <BR>
     * <li>Send a Server->Client packet MagicSkillLaunched (to display magic skill animation) to all L2PcInstance of L2Charcater _knownPlayers</li>
     * <li>Consumme MP, HP and Item if necessary</li> <li>Send a Server->Client packet StatusUpdate with MP modification to the L2PcInstance</li>
     * <li>Launch the magic skill in order to calculate its effects</li> <li>If the skill type is PDAM, notify the AI of the target with
     * AI_INTENTION_ATTACK</li> <li>Notify the AI of the L2Character with EVT_FINISH_CASTING</li><BR>
     * <BR>
     * <FONT COLOR=#FF0000><B> <U>Caution</U> : A magic skill casting MUST BE in progress</B></FONT><BR>
     * <BR>
     * 
     * @param mut
     */
    public void onMagicLaunchedTimer(MagicUseTask mut) {
        final L2Skill skill = mut.skill;
        L2Object[] targets = mut.targets;

        if (skill == null || targets == null) {
            abortCast();
            return;
        }

        if (targets.length == 0) {
            switch (skill.getTargetType()) {
            // only AURA-type skills can be cast without target
            case TARGET_AURA:
            case TARGET_FRONT_AURA:
            case TARGET_BEHIND_AURA:
                break;
            default:
                abortCast();
                return;
            }
        }

        // Escaping from under skill's radius and peace zone check. First version, not perfect in AoE skills.
        int escapeRange = 0;
        if (skill.getEffectRange() > escapeRange)
            escapeRange = skill.getEffectRange();
        else if (skill.getCastRange() < 0 && skill.getSkillRadius() > 80)
            escapeRange = skill.getSkillRadius();

        if (targets.length > 0 && escapeRange > 0) {
            int _skiprange = 0;
            int _skipgeo = 0;
            int _skippeace = 0;
            List<L2Character> targetList = new FastList<>(targets.length);
            for (L2Object target : targets) {
                if (target instanceof L2Character) {
                    if (!Util.checkIfInRange(escapeRange, this, target, true)) {
                        _skiprange++;
                        continue;
                    }
                    if (skill.getSkillRadius() > 0 && skill.isOffensive() && MainConfig.GEODATA > 0
                            && !GeoData.getInstance().canSeeTarget(this, target)) {
                        _skipgeo++;
                        continue;
                    }
                    if (skill.isOffensive()) {
                        if (this instanceof L2PcInstance) {
                            if (((L2Character) target).isInsidePeaceZone((L2PcInstance) this)) {
                                _skippeace++;
                                continue;
                            }
                        } else {
                            if (isInsidePeaceZone(this, target)) {
                                _skippeace++;
                                continue;
                            }
                        }
                    }
                    targetList.add((L2Character) target);
                }
            }
            if (targetList.isEmpty()) {
                if (this instanceof L2PcInstance) {
                    if (_skiprange > 0)
                        sendPacket(SystemMessage.getSystemMessage(SystemMessageId.DIST_TOO_FAR_CASTING_STOPPED));
                    else if (_skipgeo > 0)
                        sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CANT_SEE_TARGET));
                    else if (_skippeace > 0)
                        sendPacket(SystemMessage.getSystemMessage(SystemMessageId.TARGET_IN_PEACEZONE));
                }
                abortCast();
                return;
            }
            mut.targets = targetList.toArray(new L2Character[targetList.size()]);
        }

        // Ensure that a cast is in progress
        // Check if player is using fake death.
        // Potions can be used while faking death.
        if ((mut.simultaneously && !isCastingSimultaneouslyNow()) || (!mut.simultaneously && !isCastingNow())
                || (isAlikeDead() && !skill.isPotion())) {
            // now cancels both, simultaneous and normal
            getAI().notifyEvent(CtrlEvent.EVT_CANCEL);
            return;
        }

        mut.phase = 2;
        if (mut.hitTime == 0)
            onMagicHitTimer(mut);
        else
            _skillCast = ThreadPoolManager.getInstance().scheduleEffect(mut, 400);
    }

    /*
     * Runs in the end of skill casting
     */
    public void onMagicHitTimer(MagicUseTask mut) {
        final L2Skill skill = mut.skill;
        final L2Object[] targets = mut.targets;

        if (skill == null || targets == null) {
            abortCast();
            return;
        }

        if (getFusionSkill() != null) {
            if (mut.simultaneously) {
                _skillCast2 = null;
                setIsCastingSimultaneouslyNow(false);
            } else {
                _skillCast = null;
                setIsCastingNow(false);
            }
            getFusionSkill().onCastAbort();
            notifyQuestEventSkillFinished(skill, targets[0]);
            return;
        }
        L2Effect mog = getFirstEffect(L2EffectType.SIGNET_GROUND);
        if (mog != null) {
            if (mut.simultaneously) {
                _skillCast2 = null;
                setIsCastingSimultaneouslyNow(false);
            } else {
                _skillCast = null;
                setIsCastingNow(false);
            }
            mog.exit();
            notifyQuestEventSkillFinished(skill, targets[0]);
            return;
        }

        try {
            // Go through targets table
            for (L2Object tgt : targets) {
                if (tgt instanceof L2Playable) {
                    L2Character target = (L2Character) tgt;

                    if (skill.getSkillType() == L2SkillType.BUFF || skill.getSkillType() == L2SkillType.FUSION
                            || skill.getSkillType() == L2SkillType.SEED) {
                        SystemMessage smsg = SystemMessage.getSystemMessage(SystemMessageId.YOU_FEEL_S1_EFFECT);
                        smsg.addSkillName(skill);
                        target.sendPacket(smsg);
                    }

                    if (this instanceof L2PcInstance && target instanceof L2Summon)
                        ((L2Summon) target).updateAndBroadcastStatus(1);
                }
            }

            StatusUpdate su = new StatusUpdate(this);
            boolean isSendStatus = false;

            // Consume MP of the L2Character and Send the Server->Client packet StatusUpdate with current HP and MP to all other
            // L2PcInstance to inform
            double mpConsume = getStat().getMpConsume(skill);

            if (mpConsume > 0) {
                if (mpConsume > getCurrentMp()) {
                    sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_MP));
                    abortCast();
                    return;
                }

                getStatus().reduceMp(mpConsume);
                su.addAttribute(StatusUpdate.CUR_MP, (int) getCurrentMp());
                isSendStatus = true;
            }

            // Consume HP if necessary and Send the Server->Client packet StatusUpdate with current HP and MP to all other
            // L2PcInstance to inform
            if (skill.getHpConsume() > 0) {
                double consumeHp;

                consumeHp = calcStat(Stats.HP_CONSUME_RATE, skill.getHpConsume(), null, null);
                if (consumeHp + 1 >= getCurrentHp())
                    consumeHp = getCurrentHp() - 1.0;

                getStatus().reduceHp(consumeHp, this, true);

                su.addAttribute(StatusUpdate.CUR_HP, (int) getCurrentHp());
                isSendStatus = true;
            }

            // Send a Server->Client packet StatusUpdate with MP modification to the L2PcInstance
            if (isSendStatus)
                sendPacket(su);

            if (this instanceof L2PcInstance) {
                int charges = ((L2PcInstance) this).getCharges();
                // check for charges
                if (skill.getMaxCharges() == 0 && charges < skill.getNumCharges()) {
                    SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
                    sm.addSkillName(skill);
                    sendPacket(sm);
                    abortCast();
                    return;
                }
                // generate charges if any
                if (skill.getNumCharges() > 0) {
                    if (skill.getMaxCharges() > 0)
                        ((L2PcInstance) this).increaseCharges(skill.getNumCharges(), skill.getMaxCharges());
                    else
                        ((L2PcInstance) this).decreaseCharges(skill.getNumCharges());
                }
            }

            // Launch the magic skill in order to calculate its effects
            callSkill(mut.skill, mut.targets);
        } catch (NullPointerException e) {
            _log.warn(e.getLocalizedMessage(), e);
        }

        mut.phase = 3;
        if (mut.hitTime == 0 || mut.coolTime == 0)
            onMagicFinalizer(mut);
        else {
            if (mut.simultaneously)
                _skillCast2 = ThreadPoolManager.getInstance().scheduleEffect(mut, mut.coolTime);
            else
                _skillCast = ThreadPoolManager.getInstance().scheduleEffect(mut, mut.coolTime);
        }
    }

    /*
     * Runs after skill hitTime+coolTime
     */
    public void onMagicFinalizer(MagicUseTask mut) {
        if (mut.simultaneously) {
            _skillCast2 = null;
            setIsCastingSimultaneouslyNow(false);
            return;
        }

        _skillCast = null;
        setIsCastingNow(false);
        _castInterruptTime = 0;

        final L2Skill skill = mut.skill;
        final L2Object target = mut.targets.length > 0 ? mut.targets[0] : null;

        // Attack target after skill use
        if (skill.nextActionIsAttack() && getTarget() instanceof L2Character && getTarget() != this
                && getTarget() == target && getTarget().isAttackable()) {
            if (getAI() == null || getAI().getNextIntention() == null
                    || getAI().getNextIntention().getCtrlIntention() != CtrlIntention.AI_INTENTION_MOVE_TO)
                getAI().setIntention(CtrlIntention.AI_INTENTION_ATTACK, target);
        }

        if (skill.isOffensive() && !(skill.getSkillType() == L2SkillType.UNLOCK)
                && !(skill.getSkillType() == L2SkillType.DELUXE_KEY_UNLOCK))
            getAI().clientStartAutoAttack();

        // Notify the AI of the L2Character with EVT_FINISH_CASTING
        getAI().notifyEvent(CtrlEvent.EVT_FINISH_CASTING);

        notifyQuestEventSkillFinished(skill, target);

        /*
         * If character is a player, then wipe their current cast state and check if a skill is queued. If there is a queued skill, launch it and
         * wipe the queue.
         */
        if (this instanceof L2PcInstance) {
            L2PcInstance currPlayer = (L2PcInstance) this;
            SkillDat queuedSkill = currPlayer.getQueuedSkill();

            currPlayer.setCurrentSkill(null, false, false);

            if (queuedSkill != null) {
                currPlayer.setQueuedSkill(null, false, false);
                ThreadPoolManager.getInstance().executeTask(new QueuedMagicUseTask(currPlayer,
                        queuedSkill.getSkill(), queuedSkill.isCtrlPressed(), queuedSkill.isShiftPressed()));
            }
        }
    }

    // Quest event ON_SPELL_FINISHED
    protected void notifyQuestEventSkillFinished(L2Skill skill, L2Object target) {
    }

    public Map<Integer, Long> getDisabledSkills() {
        return _disabledSkills;
    }

    /**
     * Enable a skill (remove it from _disabledSkills of the L2Character).<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All skills disabled are identified by their skillId in <B>_disabledSkills</B> of the L2Character <BR>
     * <BR>
     * 
     * @param skill
     *            The L2Skill to enable
     */
    public void enableSkill(L2Skill skill) {
        if (skill == null || _disabledSkills == null)
            return;

        _disabledSkills.remove(Integer.valueOf(skill.getReuseHashCode()));
    }

    /**
     * Disable this skill id for the duration of the delay in milliseconds.
     * 
     * @param skill
     * @param delay
     *            (seconds * 1000)
     */
    public void disableSkill(L2Skill skill, long delay) {
        if (skill == null)
            return;

        if (_disabledSkills == null)
            _disabledSkills = Collections.synchronizedMap(new FastMap<Integer, Long>());

        _disabledSkills.put(Integer.valueOf(skill.getReuseHashCode()),
                delay > 10 ? System.currentTimeMillis() + delay : Long.MAX_VALUE);
    }

    /**
     * Check if a skill is disabled.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All skills disabled are identified by their reuse hashcodes in <B>_disabledSkills</B> of the L2Character <BR>
     * <BR>
     * 
     * @param skill
     *            The L2Skill to check
     * @return true if the skill is currently disabled.
     */
    public boolean isSkillDisabled(L2Skill skill) {
        if (skill == null)
            return true;

        return isSkillDisabled(skill.getReuseHashCode());
    }

    /**
     * Check if a skill is disabled.<BR>
     * <BR>
     * <B><U> Concept</U> :</B><BR>
     * <BR>
     * All skills disabled are identified by their reuse hashcodes in <B>_disabledSkills</B> of the L2Character <BR>
     * <BR>
     * 
     * @param reuseHashcode
     *            The reuse hashcode of the skillId/level to check
     * @return true if the skill is currently disabled.
     */
    public boolean isSkillDisabled(int reuseHashcode) {
        if (isAllSkillsDisabled())
            return true;

        if (_disabledSkills == null)
            return false;

        final Long timeStamp = _disabledSkills.get(Integer.valueOf(reuseHashcode));
        if (timeStamp == null)
            return false;

        if (timeStamp < System.currentTimeMillis()) {
            _disabledSkills.remove(Integer.valueOf(reuseHashcode));
            return false;
        }

        return true;
    }

    /**
     * Disable all skills (set _allSkillsDisabled to True).<BR>
     * <BR>
     */
    public void disableAllSkills() {
        _log.debug("All skills disabled for {}", toString());
        _allSkillsDisabled = true;
    }

    /**
     * Enable all skills (set _allSkillsDisabled to False).<BR>
     * <BR>
     */
    public void enableAllSkills() {
        _log.debug("All skills enabled for {}", toString());
        _allSkillsDisabled = false;
    }

    /**
     * Launch the magic skill and calculate its effects on each target contained in the targets table.<BR>
     * <BR>
     * 
     * @param skill
     *            The L2Skill to use
     * @param targets
     *            The table of L2Object targets
     */
    public void callSkill(L2Skill skill, L2Object[] targets) {
        try {
            // Get the skill handler corresponding to the skill type (PDAM, MDAM, SWEEP...) started in gameserver
            ISkillHandler handler = SkillHandler.getInstance().getSkillHandler(skill.getSkillType());
            L2Weapon activeWeapon = getActiveWeaponItem();

            // Check if the toggle skill effects are already in progress on the L2Character
            if (skill.isToggle() && getFirstEffect(skill.getId()) != null)
                return;

            // Initial checks
            for (L2Object trg : targets) {
                if (trg instanceof L2Character) {
                    // Set some values inside target's instance for later use
                    L2Character target = (L2Character) trg;

                    if (!NPCConfig.RAID_DISABLE_CURSE) {
                        // Raidboss curse.
                        L2Character targetsAttackTarget = null;
                        L2Character targetsCastTarget = null;

                        if (target.hasAI()) {
                            targetsAttackTarget = target.getAI().getAttackTarget();
                            targetsCastTarget = target.getAI().getCastTarget();
                        }

                        if ((target.isRaid() && getLevel() > target.getLevel() + 8)
                                || (!skill.isOffensive() && targetsAttackTarget != null
                                        && targetsAttackTarget.isRaid()
                                        && targetsAttackTarget.getAttackByList().contains(target)
                                        && getLevel() > targetsAttackTarget.getLevel() + 8)
                                || (!skill.isOffensive() && targetsCastTarget != null && targetsCastTarget.isRaid()
                                        && targetsCastTarget.getAttackByList().contains(target)
                                        && getLevel() > targetsCastTarget.getLevel() + 8)) {
                            L2Skill curse = FrequentSkill.RAID_CURSE.getSkill();
                            if (curse != null) {
                                // Send visual and skill effects. Caster is the victim.
                                broadcastPacket(
                                        new MagicSkillUse(this, this, curse.getId(), curse.getLevel(), 300, 0));
                                curse.getEffects(this, this);
                            }
                            return;
                        }
                    }

                    // Check if over-hit is possible
                    if (skill.isOverhit()) {
                        if (target instanceof L2Attackable)
                            ((L2Attackable) target).overhitEnabled(true);
                    }

                    // crafting does not trigger any chance skills
                    switch (skill.getSkillType()) {
                    case COMMON_CRAFT:
                    case DWARVEN_CRAFT:
                        break;
                    default:
                        // Launch weapon Special ability skill effect if available
                        if (activeWeapon != null && !target.isDead()) {
                            if (activeWeapon.getSkillEffects(this, target, skill).length > 0
                                    && this instanceof L2PcInstance) {
                                SystemMessage sm = SystemMessage
                                        .getSystemMessage(SystemMessageId.S1_HAS_BEEN_ACTIVATED);
                                sm.addSkillName(skill);
                                sendPacket(sm);
                            }
                        }

                        // Maybe launch chance skills on us
                        if (_chanceSkills != null)
                            _chanceSkills.onSkillHit(target, false, skill.isMagic(), skill.isOffensive(),
                                    skill.getElement());
                        // Maybe launch chance skills on target
                        if (target.getChanceSkills() != null)
                            target.getChanceSkills().onSkillHit(this, true, skill.isMagic(), skill.isOffensive(),
                                    skill.getElement());
                    }
                }
            }

            // Launch the magic skill and calculate its effects
            if (handler != null)
                handler.useSkill(this, skill, targets);
            else
                skill.useSkill(this, targets);

            L2PcInstance player = getActingPlayer();
            if (player != null) {
                for (L2Object target : targets) {
                    // EVT_ATTACKED and PvPStatus
                    if (target instanceof L2Character) {
                        if (skill.isOffensive()) {
                            if (target instanceof L2Playable) {
                                // Signets are a special case, casted on target_self but don't harm self
                                if (skill.getSkillType() != L2SkillType.SIGNET
                                        && skill.getSkillType() != L2SkillType.SIGNET_CASTTIME) {
                                    ((L2Character) target).getAI().clientStartAutoAttack();

                                    // attack of the own pet does not flag player
                                    if (player.getPet() != target)
                                        player.updatePvPStatus((L2Character) target);
                                }
                            } else if (target instanceof L2Attackable) {
                                switch (skill.getId()) {
                                case 51: // Lure
                                case 511: // Temptation
                                    break;
                                default:
                                    // add attacker into list
                                    ((L2Character) target).addAttackerToAttackByList(this);
                                }
                            }
                            // notify target AI about the attack
                            if (((L2Character) target).hasAI()) {
                                switch (skill.getSkillType()) {
                                case AGGREDUCE:
                                case AGGREDUCE_CHAR:
                                case AGGREMOVE:
                                    break;
                                default:
                                    ((L2Character) target).getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, this);
                                }
                            }
                        } else {
                            if (target instanceof L2PcInstance) {
                                // Casting non offensive skill on player with pvp flag set or with karma
                                if (!(target.equals(this) || target.equals(player))
                                        && (((L2PcInstance) target).getPvpFlag() > 0
                                                || ((L2PcInstance) target).getKarma() > 0))
                                    player.updatePvPStatus();
                            } else if (target instanceof L2Attackable) {
                                switch (skill.getSkillType()) {
                                case SUMMON:
                                case BEAST_FEED:
                                case UNLOCK:
                                case UNLOCK_SPECIAL:
                                case DELUXE_KEY_UNLOCK:
                                    break;
                                default:
                                    player.updatePvPStatus();
                                }
                            }
                        }
                    }
                }

                // Mobs in range 1000 see spell
                Collection<L2Object> objs = player.getKnownList().getKnownObjects().values();
                for (L2Object spMob : objs) {
                    if (spMob instanceof L2Npc) {
                        L2Npc npcMob = (L2Npc) spMob;

                        if ((npcMob.isInsideRadius(player, 1000, true, true))
                                && (npcMob.getTemplate().getEventQuests(Quest.QuestEventType.ON_SKILL_SEE) != null))
                            for (Quest quest : npcMob.getTemplate()
                                    .getEventQuests(Quest.QuestEventType.ON_SKILL_SEE))
                                quest.notifySkillSee(npcMob, player, skill, targets, this instanceof L2Summon);
                    }
                }
            }

            // Notify AI
            if (skill.isOffensive()) {
                switch (skill.getSkillType()) {
                case AGGREDUCE:
                case AGGREDUCE_CHAR:
                case AGGREMOVE:
                    break;
                default:
                    for (L2Object target : targets) {
                        // notify target AI about the attack
                        if (target instanceof L2Character && ((L2Character) target).hasAI())
                            ((L2Character) target).getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, this);
                    }
                    break;
                }
            }
        } catch (Exception e) {
            _log.warn(getClass().getSimpleName() + ": callSkill() failed.", e);
        }
    }

    /**
     * @param target
     *            Target to check.
     * @return True if the L2Character is behind the target and can't be seen.
     */
    public boolean isBehind(L2Object target) {
        double angleChar, angleTarget, angleDiff, maxAngleDiff = 60;

        if (target == null)
            return false;

        if (target instanceof L2Character) {
            L2Character target1 = (L2Character) target;
            angleChar = Util.calculateAngleFrom(this, target1);
            angleTarget = Util.convertHeadingToDegree(target1.getHeading());
            angleDiff = angleChar - angleTarget;

            if (angleDiff <= -360 + maxAngleDiff)
                angleDiff += 360;

            if (angleDiff >= 360 - maxAngleDiff)
                angleDiff -= 360;

            if (Math.abs(angleDiff) <= maxAngleDiff) {
                _log.debug("Char " + getName() + " is behind " + target.getName());

                return true;
            }
        } else {
            _log.debug("isBehindTarget's target not an Character.");
        }
        return false;
    }

    public boolean isBehindTarget() {
        return isBehind(getTarget());
    }

    /**
     * @param target
     *            Target to check.
     * @return True if the target is facing the L2Character.
     */
    public boolean isInFrontOf(L2Character target) {
        double angleChar, angleTarget, angleDiff, maxAngleDiff = 60;
        if (target == null)
            return false;

        angleTarget = Util.calculateAngleFrom(target, this);
        angleChar = Util.convertHeadingToDegree(target.getHeading());
        angleDiff = angleChar - angleTarget;

        if (angleDiff <= -360 + maxAngleDiff)
            angleDiff += 360;

        if (angleDiff >= 360 - maxAngleDiff)
            angleDiff -= 360;

        if (Math.abs(angleDiff) <= maxAngleDiff)
            return true;

        return false;
    }

    /**
     * @param target
     *            Target to check.
     * @param maxAngle
     *            The angle to check.
     * @return true if target is in front of L2Character (shield def etc)
     */
    public boolean isFacing(L2Object target, int maxAngle) {
        double angleChar, angleTarget, angleDiff, maxAngleDiff;
        if (target == null)
            return false;

        maxAngleDiff = maxAngle / 2;
        angleTarget = Util.calculateAngleFrom(this, target);
        angleChar = Util.convertHeadingToDegree(getHeading());
        angleDiff = angleChar - angleTarget;

        if (angleDiff <= -360 + maxAngleDiff)
            angleDiff += 360;

        if (angleDiff >= 360 - maxAngleDiff)
            angleDiff -= 360;

        if (Math.abs(angleDiff) <= maxAngleDiff)
            return true;

        return false;
    }

    public boolean isInFrontOfTarget() {
        L2Object target = getTarget();
        if (target instanceof L2Character)
            return isInFrontOf((L2Character) target);

        return false;
    }

    /**
     * @return the level modifier (overriden in summons).
     */
    public double getLevelMod() {
        return 1;
    }

    public final void setSkillCast(Future<?> newSkillCast) {
        _skillCast = newSkillCast;
    }

    /**
     * Sets _isCastingNow to true and _castInterruptTime is calculated from end time (ticks)
     * 
     * @param newSkillCastEndTick
     */
    public final void forceIsCasting(int newSkillCastEndTick) {
        setIsCastingNow(true);
        // for interrupt -400 ms
        _castInterruptTime = newSkillCastEndTick - 4;
    }

    /**
     * @param target
     *            Target to check.
     * @return a Random Damage in function of the weapon.
     */
    public final int getRandomDamage(L2Character target) {
        L2Weapon weaponItem = getActiveWeaponItem();

        if (weaponItem == null)
            return 5 + (int) Math.sqrt(getLevel());

        return weaponItem.getRandomDamage();
    }

    @Override
    public String toString() {
        return "mob " + getObjectId();
    }

    public int getAttackEndTime() {
        return _attackEndTime;
    }

    /**
     * @return the level of the L2Character.
     */
    public abstract int getLevel();

    // =========================================================
    // Stat - NEED TO REMOVE ONCE L2CHARSTAT IS COMPLETE
    // Property - Public
    public final double calcStat(Stats stat, double init, L2Character target, L2Skill skill) {
        return getStat().calcStat(stat, init, target, skill);
    }

    // Property - Public
    public int getCON() {
        return getStat().getCON();
    }

    public int getDEX() {
        return getStat().getDEX();
    }

    public int getINT() {
        return getStat().getINT();
    }

    public int getMEN() {
        return getStat().getMEN();
    }

    public int getSTR() {
        return getStat().getSTR();
    }

    public int getWIT() {
        return getStat().getWIT();
    }

    public int getAccuracy() {
        return getStat().getAccuracy();
    }

    public final float getAttackSpeedMultiplier() {
        return getStat().getAttackSpeedMultiplier();
    }

    public int getCriticalHit(L2Character target, L2Skill skill) {
        return getStat().getCriticalHit(target, skill);
    }

    public int getEvasionRate(L2Character target) {
        return getStat().getEvasionRate(target);
    }

    public int getMDef(L2Character target, L2Skill skill) {
        return getStat().getMDef(target, skill);
    }

    public int getPDef(L2Character target) {
        return getStat().getPDef(target);
    }

    public final int getShldDef() {
        return getStat().getShldDef();
    }

    public final int getPhysicalAttackRange() {
        return getStat().getPhysicalAttackRange();
    }

    public final int getMagicalAttackRange(L2Skill skill) {
        return getStat().getMagicalAttackRange(skill);
    }

    public int getPAtk(L2Character target) {
        return getStat().getPAtk(target);
    }

    public int getPAtkSpd() {
        return getStat().getPAtkSpd();
    }

    public int getMAtk(L2Character target, L2Skill skill) {
        return getStat().getMAtk(target, skill);
    }

    public int getMAtkSpd() {
        return getStat().getMAtkSpd();
    }

    public final int getMCriticalHit(L2Character target, L2Skill skill) {
        return getStat().getMCriticalHit(target, skill);
    }

    public int getMaxMp() {
        return getStat().getMaxMp();
    }

    public int getMaxHp() {
        return getStat().getMaxHp();
    }

    public final int getMaxCp() {
        return getStat().getMaxCp();
    }

    public double getMReuseRate(L2Skill skill) {
        return getStat().getMReuseRate(skill);
    }

    public float getMovementSpeedMultiplier() {
        return getStat().getMovementSpeedMultiplier();
    }

    public double getPAtkAnimals(L2Character target) {
        return getStat().getPAtkAnimals(target);
    }

    public double getPAtkDragons(L2Character target) {
        return getStat().getPAtkDragons(target);
    }

    public double getPAtkInsects(L2Character target) {
        return getStat().getPAtkInsects(target);
    }

    public double getPAtkMonsters(L2Character target) {
        return getStat().getPAtkMonsters(target);
    }

    public double getPAtkPlants(L2Character target) {
        return getStat().getPAtkPlants(target);
    }

    public double getPAtkGiants(L2Character target) {
        return getStat().getPAtkGiants(target);
    }

    public double getPAtkMagicCreatures(L2Character target) {
        return getStat().getPAtkMagicCreatures(target);
    }

    public double getPDefAnimals(L2Character target) {
        return getStat().getPDefAnimals(target);
    }

    public double getPDefDragons(L2Character target) {
        return getStat().getPDefDragons(target);
    }

    public double getPDefInsects(L2Character target) {
        return getStat().getPDefInsects(target);
    }

    public double getPDefMonsters(L2Character target) {
        return getStat().getPDefMonsters(target);
    }

    public double getPDefPlants(L2Character target) {
        return getStat().getPDefPlants(target);
    }

    public double getPDefGiants(L2Character target) {
        return getStat().getPDefGiants(target);
    }

    public double getPDefMagicCreatures(L2Character target) {
        return getStat().getPDefMagicCreatures(target);
    }

    public int getRunSpeed() {
        return getStat().getRunSpeed();
    }

    public final int getWalkSpeed() {
        return getStat().getWalkSpeed();
    }

    // =========================================================
    // Status - NEED TO REMOVE ONCE L2CHARTATUS IS COMPLETE
    // Method - Public
    public void addStatusListener(L2Character object) {
        getStatus().addStatusListener(object);
    }

    public void reduceCurrentHp(double i, L2Character attacker, L2Skill skill) {
        reduceCurrentHp(i, attacker, true, false, skill);
    }

    public void reduceCurrentHpByDOT(double i, L2Character attacker, L2Skill skill) {
        reduceCurrentHp(i, attacker, !skill.isToggle(), true, skill);
    }

    public void reduceCurrentHp(double i, L2Character attacker, boolean awake, boolean isDOT, L2Skill skill) {
        if (NPCConfig.CHAMPION_ENABLE && isChampion() && NPCConfig.CHAMPION_HP != 0)
            getStatus().reduceHp(i / NPCConfig.CHAMPION_HP, attacker, awake, isDOT, false);
        else
            getStatus().reduceHp(i, attacker, awake, isDOT, false);
    }

    public void reduceCurrentMp(double i) {
        getStatus().reduceMp(i);
    }

    public void removeStatusListener(L2Character object) {
        getStatus().removeStatusListener(object);
    }

    protected void stopHpMpRegeneration() {
        getStatus().stopHpMpRegeneration();
    }

    // Property - Public
    public final double getCurrentCp() {
        return getStatus().getCurrentCp();
    }

    public final void setCurrentCp(Double newCp) {
        setCurrentCp((double) newCp);
    }

    public final void setCurrentCp(double newCp) {
        getStatus().setCurrentCp(newCp);
    }

    public final double getCurrentHp() {
        return getStatus().getCurrentHp();
    }

    public final void setCurrentHp(double newHp) {
        getStatus().setCurrentHp(newHp);
    }

    public final void setCurrentHpMp(double newHp, double newMp) {
        getStatus().setCurrentHpMp(newHp, newMp);
    }

    public final double getCurrentMp() {
        return getStatus().getCurrentMp();
    }

    public final void setCurrentMp(Double newMp) {
        setCurrentMp((double) newMp);
    }

    public final void setCurrentMp(double newMp) {
        getStatus().setCurrentMp(newMp);
    }

    // =========================================================

    public void setAiClass(String aiClass) {
        _aiClass = aiClass;
    }

    public String getAiClass() {
        return _aiClass;
    }

    public void setChampion(boolean champ) {
        _champion = champ;
    }

    public boolean isChampion() {
        return _champion;
    }

    /**
     * Send system message about damage.<BR>
     * <BR>
     * <B><U> Overriden in </U> :</B><BR>
     * <BR>
     * <li>L2PcInstance <li>L2SummonInstance <li>L2PetInstance</li><BR>
     * <BR>
     * 
     * @param target
     * @param damage
     * @param mcrit
     * @param pcrit
     * @param miss
     */
    public void sendDamageMessage(L2Character target, int damage, boolean mcrit, boolean pcrit, boolean miss) {
    }

    public FusionSkill getFusionSkill() {
        return _fusionSkill;
    }

    public void setFusionSkill(FusionSkill fb) {
        _fusionSkill = fb;
    }

    public int getAttackElementValue(byte attackAttribute) {
        return getStat().getAttackElementValue(attackAttribute);
    }

    public int getDefenseElementValue(byte defenseAttribute) {
        return getStat().getDefenseElementValue(defenseAttribute);
    }

    /**
     * Check if target is affected with special buff
     * 
     * @see CharEffectList#isAffected(int)
     * @param flag
     *            int
     * @return boolean
     */
    public boolean isAffected(int flag) {
        return _effects.isAffected(flag);
    }

    /**
     * Check player max buff count
     * 
     * @return max buff count
     */
    public int getMaxBuffCount() {
        return PlayersConfig.BUFFS_MAX_AMOUNT + Math.max(0, getSkillLevel(L2Skill.SKILL_DIVINE_INSPIRATION));
    }

    /**
     * @return a multiplier based on weapon random damage.
     */
    public final double getRandomDamageMultiplier() {
        L2Weapon activeWeapon = getActiveWeaponItem();
        int random;

        if (activeWeapon != null)
            random = activeWeapon.getRandomDamage();
        else
            random = 5 + (int) Math.sqrt(getLevel());

        return (1 + ((double) Rnd.get(0 - random, random) / 100));
    }

    public void disableCoreAI(boolean val) {
        _AIdisabled = val;
    }

    public boolean isCoreAIDisabled() {
        return _AIdisabled;
    }

    /** Task for potion and herb queue */
    private static class UsePotionTask implements Runnable {
        private final L2Character _activeChar;
        private final L2Skill _skill;

        UsePotionTask(L2Character activeChar, L2Skill skill) {
            _activeChar = activeChar;
            _skill = skill;
        }

        @Override
        public void run() {
            try {
                _activeChar.doSimultaneousCast(_skill);
            } catch (Exception e) {
                _log.warn(e.getLocalizedMessage(), e);
            }
        }
    }

    private int _PremiumService;

    public void setPremiumService(int PS) {
        _PremiumService = PS;
    }

    public int getPremiumService() {
        return _PremiumService;
    }
}