Package l2p.gameserver.model

Source Code of l2p.gameserver.model.L2Character

package l2p.gameserver.model;

import l2p.Config;
import l2p.common.ThreadPoolManager;
import l2p.extensions.listeners.MethodCollection;
import l2p.extensions.listeners.PropertyCollection;
import l2p.extensions.listeners.StatsChangeListener;
import l2p.extensions.multilang.CustomMessage;
import l2p.extensions.scripts.ScriptManager;
import l2p.extensions.scripts.ScriptManager.ScriptClassAndMethod;
import l2p.gameserver.ai.CtrlEvent;
import static l2p.gameserver.ai.CtrlEvent.EVT_FORGET_OBJECT;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import l2p.gameserver.ai.DefaultAI;
import l2p.gameserver.ai.L2CharacterAI;
import l2p.gameserver.ai.L2PlayableAI.nextAction;
import l2p.gameserver.cache.Msg;
import l2p.gameserver.geodata.GeoEngine;
import l2p.gameserver.geodata.GeoMove;
import l2p.gameserver.instancemanager.DimensionalRiftManager;
import l2p.gameserver.model.L2ObjectTasks.AltMagicUseTask;
import l2p.gameserver.model.L2ObjectTasks.CancelAttackStanceTask;
import l2p.gameserver.model.L2ObjectTasks.CastEndTimeTask;
import l2p.gameserver.model.L2ObjectTasks.EnableSkillTask;
import l2p.gameserver.model.L2ObjectTasks.HitTask;
import l2p.gameserver.model.L2ObjectTasks.MagicLaunchedTask;
import l2p.gameserver.model.L2ObjectTasks.MagicUseTask;
import l2p.gameserver.model.L2ObjectTasks.MoveNextTask;
import l2p.gameserver.model.L2ObjectTasks.NotifyAITask;
import l2p.gameserver.model.L2Skill.SkillTargetType;
import l2p.gameserver.model.L2Skill.SkillType;
import l2p.gameserver.model.L2Skill.TriggerActionType;
import l2p.gameserver.model.L2Zone.ZoneType;
import l2p.gameserver.model.entity.Duel;
import l2p.gameserver.model.entity.Duel.DuelState;
import l2p.gameserver.model.entity.vehicle.L2AirShip;
import l2p.gameserver.model.entity.vehicle.L2Ship;
import l2p.gameserver.model.instances.L2MinionInstance;
import l2p.gameserver.model.instances.L2MonsterInstance;
import l2p.gameserver.model.instances.L2NpcInstance;
import l2p.gameserver.model.instances.L2TrapInstance;
import l2p.gameserver.model.items.L2ItemInstance;
import l2p.gameserver.model.quest.QuestEventType;
import l2p.gameserver.model.quest.QuestState;
import l2p.gameserver.serverpackets.Attack;
import l2p.gameserver.serverpackets.AutoAttackStart;
import l2p.gameserver.serverpackets.AutoAttackStop;
import l2p.gameserver.serverpackets.ChangeMoveType;
import l2p.gameserver.serverpackets.ChangeWaitType;
import l2p.gameserver.serverpackets.CharMoveToLocation;
import l2p.gameserver.serverpackets.ExMoveToLocationAirShip;
import l2p.gameserver.serverpackets.FlyToLocation;
import l2p.gameserver.serverpackets.FlyToLocation.FlyType;
import l2p.gameserver.serverpackets.L2GameServerPacket;
import l2p.gameserver.serverpackets.MagicSkillCanceled;
import l2p.gameserver.serverpackets.MagicSkillLaunched;
import l2p.gameserver.serverpackets.MagicSkillUse;
import l2p.gameserver.serverpackets.MyTargetSelected;
import l2p.gameserver.serverpackets.Revive;
import l2p.gameserver.serverpackets.SetupGauge;
import l2p.gameserver.serverpackets.StatusUpdate;
import l2p.gameserver.serverpackets.StopMove;
import l2p.gameserver.serverpackets.SystemMessage;
import l2p.gameserver.serverpackets.TeleportToLocation;
import l2p.gameserver.serverpackets.ValidateLocation;
import l2p.gameserver.serverpackets.VehicleDeparture;
import l2p.gameserver.skills.AbnormalEffect;
import l2p.gameserver.skills.Calculator;
import l2p.gameserver.skills.EffectType;
import l2p.gameserver.skills.Env;
import l2p.gameserver.skills.Formulas;
import l2p.gameserver.skills.Formulas.AttackInfo;
import l2p.gameserver.skills.SkillTimeStamp;
import l2p.gameserver.skills.Stats;
import l2p.gameserver.skills.funcs.Func;
import l2p.gameserver.tables.MapRegion;
import l2p.gameserver.taskmanager.RegenTaskManager;
import l2p.gameserver.templates.L2CharTemplate;
import l2p.gameserver.templates.L2NpcTemplate;
import l2p.gameserver.templates.L2Weapon;
import l2p.gameserver.templates.L2Weapon.WeaponType;
import l2p.util.GArray;
import l2p.util.GCSArray;
import l2p.util.Location;
import l2p.util.Rnd;
import l2p.util.Util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class L2Character extends L2Object
{
  public enum TargetDirection
  {
    NONE,
    FRONT,
    SIDE,
    BEHIND
  }

  public L2Character getFollowTarget()
  {
    return L2ObjectsStorage.getAsCharacter(followTargetStoreId);
  }

  public void setFollowTarget(L2Character target)
  {
    followTargetStoreId = target == null ? 0 : target.getStoredId();
  }
  protected static final Logger _log = Logger.getLogger(L2Character.class.getName());
  public static final double HEADINGS_IN_PI = 10430.378350470452724949566316381;
  public static final int INTERACTION_DISTANCE = 200;
  /**
   * Array containing all clients that need to be notified about hp/mp updates of the L2Character
   */
  private CopyOnWriteArraySet<L2Character> _statusListener;
  public ScheduledFuture<?> _skillScheduledTask;
  public int _scheduledCastCount;
  public int _scheduledCastInterval;
  public Future<?> _skillTask;
  public Future<?> _skillLaunchedTask;
  public Future<?> _stanceTask;
  private long _stanceInited;
  private double _lastHpUpdate = -99999999;
  protected double _currentCp = 0;
  protected double _currentHp = 1;
  protected double _currentMp = 1;
  protected boolean _isAttackAborted;
  protected long _attackEndTime;
  protected long _attackReuseEndTime;
  /**
   * HashMap(Integer, L2Skill) containing all skills of the L2Character
   */
  protected final ConcurrentHashMap<Integer, L2Skill> _skills = new ConcurrentHashMap<Integer, L2Skill>();
  protected ConcurrentHashMap<TriggerActionType, ConcurrentLinkedQueue<L2Skill>> _skillsOnAction;
  private L2Skill _castingSkill;
  private long _castInterruptTime;
  private long _animationEndTime;
  /**
   * Table containing all skillId that are disabled
   */
  protected GCSArray<Integer> _disabledSkills;
  protected EffectList _effectList;
  private boolean _massUpdating;
  private GArray<Stats> _blockedStats;
  /**
   * Map 32 bits (0x00000000) containing all abnormal effect in progress
   */
  private int _abnormalEffects;
  private int _abnormalEffects2;
  private boolean _flying;
  private boolean _riding;
  private boolean _fakeDeath;
  private boolean _fishing;
  protected boolean _isInvul;
  protected boolean _isPendingRevive;
  protected boolean _isTeleporting;
  protected boolean _overloaded;
  protected boolean _killedAlready;
  protected boolean _killedAlreadyPlayer;
  protected boolean _killedAlreadyPet;
  private byte _isBlessedByNoblesse; // Восстанавливает все бафы после смерти
  private byte _isSalvation; // Восстанавливает все бафы после смерти и полностью CP, MP, HP
  private byte _buffImmunity; // Иммунитет к бафам/дебафам
  private HashMap<Integer, Byte> _skillMastery;
  private boolean _afraid;
  private boolean _meditated;
  private boolean _muted;
  private boolean _pmuted;
  private boolean _amuted;
  private boolean _paralyzed;
  private boolean _rooted;
  private boolean _sleeping;
  private boolean _stunned;
  private boolean _imobilised;
  private boolean _confused;
  private boolean _blocked;
  private boolean _healBlocked;
  private boolean _running;
  public Future<?> _moveTask;
  public final MoveNextTask _moveTaskRunnable;
  public boolean isMoving;
  public boolean isFollow;
  protected ArrayList<Location> moveList = new ArrayList<Location>();
  protected Location destination = null;
  /**
   * при moveToLocation используется для хранения геокоординат в которые мы двигаемся для того что бы избежать повторного построения одного и того же пути
   * при followToCharacter используется для хранения мировых координат в которых находилась последний раз преследуемая цель для отслеживания необходимости перестраивания пути
   */
  protected final Location movingDestTempPos = new Location();
  public int _offset;
  protected boolean _forestalling;
  protected long castingTargetStoreId, followTargetStoreId, targetStoreId;
  protected final ArrayList<ArrayList<Location>> _targetRecorder = new ArrayList<ArrayList<Location>>();
  protected long _followTimestamp, _startMoveTime, _arriveTime;
  protected double _previousSpeed = -1;
  private int _heading;
  private final Calculator[] _calculators;
  protected L2CharTemplate _template;
  protected L2CharTemplate _baseTemplate;
  protected L2CharacterAI _ai;
  private static final String EMPTY_STRING = new String();
  protected String _name;
  protected String _title;
  protected HashMap<Integer, Long> _traps;
  protected final ReentrantLock dieLock = new ReentrantLock(), statusListenerLock = new ReentrantLock(), regenLock = new ReentrantLock();

  public L2Character(int objectId, L2CharTemplate template)
  {
    super(objectId);
    // Set its template to the new L2Character
    _template = template;
    _baseTemplate = template;
    _calculators = new Calculator[Stats.NUM_STATS];
    if(isPlayer())
    {
      for(Stats stat : Stats.values())
      {
        _calculators[stat.ordinal()] = new Calculator(stat, this);
      }
    }
    if(template != null && (isNpc() || this instanceof L2Summon))
    {
      if(((L2NpcTemplate) template).getSkills().size() > 0)
      {
        for(L2Skill skill : ((L2NpcTemplate) template).getSkills().values())
        {
          addSkill(skill);
        }
      }
    }
    _moveTaskRunnable = new MoveNextTask(this); //FIXME check hasAI???
    Formulas.addFuncsToNewCharacter(this);
  }

  public final void abortAttack(boolean force, boolean message)
  {
    if(isAttackingNow())
    {
      if(force)
      {
        _isAttackAborted = true;
      }
      getAI().setIntention(AI_INTENTION_ACTIVE);
      if(isPlayer())
      {
        sendActionFailed();
        sendPacket(new SystemMessage(SystemMessage.C1S_ATTACK_FAILED).addName(this));
      }
    }
  }

  public final void abortCast(boolean force)
  {
    if(isCastingNow() && (force || canAbortCast()))
    {
      _castInterruptTime = 0;
      L2Skill castingSkill = _castingSkill;
      if(castingSkill != null)
      {
        if(castingSkill.isUsingWhileCasting())
        {
          L2Character target = getAI().getAttackTarget();
          if(target != null)
          {
            target.getEffectList().stopEffect(castingSkill.getId());
          }
        }
        HashMap<Integer, Byte> skillMastery = _skillMastery;
        if(skillMastery != null)
        {
          skillMastery.remove(castingSkill.getId());
        }
        _castingSkill = null;
        _flyLoc = null;
      }
      if(_skillTask != null)
      {
        _skillTask.cancel(false); // cancels the skill hit scheduled task
        _skillTask = null;
      }
      if(_skillLaunchedTask != null)
      {
        _skillLaunchedTask.cancel(false); // cancels the skill hit scheduled task
        _skillLaunchedTask = null;
      }
      broadcastPacket(new MagicSkillCanceled(_objectId)); // broadcast packet to stop animations client-side
      getAI().setIntention(AI_INTENTION_ACTIVE);
      if(isPlayer())
      {
        sendPacket(Msg.CASTING_HAS_BEEN_INTERRUPTED);
      }
    }
  }

  public final boolean canAbortCast()
  {
    return _castInterruptTime > System.currentTimeMillis();
  }

  public boolean absorbAndReflect(L2Character target, L2Skill skill, double damage)
  {
    if(target.isDead())
    {
      return false;
    }
    boolean bow = getActiveWeaponItem() != null && (getActiveWeaponItem().getItemType() == WeaponType.BOW || getActiveWeaponItem().getItemType() == WeaponType.CROSSBOW);
    double value = 0;
    if(skill != null && skill.isMagic())
    {
      value = target.calcStat(Stats.REFLECT_AND_BLOCK_MSKILL_DAMAGE_CHANCE, 0, this, skill);
    }
    else if(skill != null && skill.getCastRange() <= 200)
    {
      value = target.calcStat(Stats.REFLECT_AND_BLOCK_PSKILL_DAMAGE_CHANCE, 0, this, skill);
    }
    else if(skill == null && !bow)
    {
      value = target.calcStat(Stats.REFLECT_AND_BLOCK_DAMAGE_CHANCE, 0, this, null);
    }
    if(value > 0)
    {
      applyReflectDamage(target, damage, 100.);
      return true;
    }
    if(skill != null && skill.isMagic())
    {
      value = target.calcStat(Stats.REFLECT_MSKILL_DAMAGE_PERCENT, 0, this, skill);
    }
    else if(skill != null && skill.getCastRange() <= 200)
    {
      value = target.calcStat(Stats.REFLECT_PSKILL_DAMAGE_PERCENT, 0, this, skill);
    }
    else if(skill == null && !bow)
    {
      value = target.calcStat(Stats.REFLECT_DAMAGE_PERCENT, 0, this, null);
    }
    if(value > 0)
    {
      applyReflectDamage(target, damage, value);
    }
    if(skill != null || bow)
    {
      return false;
    }
    // вампирик
    damage = (int) (damage - target.getCurrentCp());
    if(damage <= 0)
    {
      return false;
    }
    double absorb = calcStat(Stats.ABSORB_DAMAGE_PERCENT, 0, target, null);
    if(absorb > 0 && !target.isDoor())
    {
      setCurrentHp(_currentHp + damage * absorb * Config.ALT_ABSORB_DAMAGE_MODIFIER / 100, false);
    }
    absorb = calcStat(Stats.ABSORB_DAMAGEMP_PERCENT, 0, target, null);
    if(absorb > 0 && !target.isHealBlocked(true))
    {
      setCurrentMp(_currentMp + damage * absorb * Config.ALT_ABSORB_DAMAGE_MODIFIER / 100);
    }
    return false;
  }

  public void applyReflectDamage(L2Character target, double damage, double reflect)
  {
    double rdmg = damage * reflect / 100.;
    rdmg = Math.min(rdmg, target.getCurrentHp());
    if(isPlayable() && !target.isNpc())
    {
      reduceCurrentHp(rdmg, this, null, true, true, false, false);
    }
    else
    {
      reduceCurrentHp(rdmg, target, null, true, true, false, false);
    }
    if(target.isPlayer() && rdmg >= 1.)
    {
      target.sendPacket(new SystemMessage(SystemMessage.C1_HAS_GIVEN_C2_DAMAGE_OF_S3).addName(target).addName(this).addNumber((long) rdmg));
    }
  }

  public void addBlockStats(GArray<Stats> stats)
  {
    if(_blockedStats == null)
    {
      _blockedStats = new GArray<Stats>();
    }
    _blockedStats.addAll(stats);
  }

  public L2Skill addSkill(L2Skill newSkill)
  {
    if(newSkill == null)
    {
      return null;
    }
    L2Skill oldSkill = _skills.get(newSkill.getId());
    if(oldSkill != null && oldSkill.getLevel() == newSkill.getLevel())
    {
      return newSkill;
    }
    // Replace oldSkill by newSkill or Add the newSkill
    _skills.put(newSkill.getId(), newSkill);
    if(newSkill.isOnAction())
    {
      addTriggerableSkill(newSkill);
    }
    // If an old skill has been replaced, remove all its Func objects
    if(oldSkill != null)
    {
      removeStatsOwner(oldSkill);
    }
    // Add Func objects of newSkill to the calculator set of the L2Character
    addStatFuncs(newSkill.getStatFuncs());
    return oldSkill;
  }

  public final synchronized void addStatFunc(Func f)
  {
    if(f == null)
    {
      return;
    }
    int stat = f._stat.ordinal();
    if(_calculators[stat] == null)
    {
      _calculators[stat] = new Calculator(f._stat, this);
    }
    _calculators[stat].addFunc(f);
  }

  public final synchronized void addStatListener(StatsChangeListener l)
  {
    if(l == null || l._stat == null)
    {
      return;
    }
    int stat = l._stat.ordinal();
    if(_calculators[stat] == null)
    {
      _calculators[stat] = new Calculator(l._stat, this);
    }
    _calculators[l._stat.ordinal()].addListener(l);
  }

  public final synchronized void addStatFuncs(Func[] funcs)
  {
    for(Func f : funcs)
    {
      addStatFunc(f);
    }
  }

  public void altOnMagicUseTimer(L2Character aimingTarget, L2Skill skill)
  {
    if(isAlikeDead())
    {
      return;
    }
    int magicId = skill.getDisplayId();
    int level = Math.max(1, getSkillDisplayLevel(skill.getId()));
    GArray<L2Character> targets = skill.getTargets(this, aimingTarget, true);
    broadcastPacket(new MagicSkillLaunched(_objectId, magicId, level, targets, skill.isOffensive()));
    double mpConsume2 = skill.getMpConsume2();
    if(mpConsume2 > 0)
    {
      if(_currentMp < mpConsume2)
      {
        sendPacket(Msg.NOT_ENOUGH_MP);
        return;
      }
      if(skill.isMagic())
      {
        reduceCurrentMp(calcStat(Stats.MP_MAGIC_SKILL_CONSUME, mpConsume2, aimingTarget, skill), null);
      }
      else
      {
        reduceCurrentMp(calcStat(Stats.MP_PHYSICAL_SKILL_CONSUME, mpConsume2, aimingTarget, skill), null);
      }
    }
    callSkill(skill, targets, false);
  }

  public void altUseSkill(L2Skill skill, L2Character target)
  {
    if(skill == null)
    {
      return;
    }
    int magicId = skill.getId();
    if(isSkillDisabled(magicId))
    {
      sendReuseMessage(skill);
      return;
    }
    if(target == null)
    {
      target = skill.getAimingTarget(this, getTarget());
      if(target == null)
      {
        return;
      }
    }
    int itemConsume[] = skill.getItemConsume();
    if(itemConsume[0] > 0)
    {
      for(int i = 0; i < itemConsume.length; i++)
      {
        if(!consumeItem(skill.getItemConsumeId()[i], itemConsume[i]))
        {
          sendPacket(Msg.INCORRECT_ITEM_COUNT);
          sendChanges();
          return;
        }
      }
    }
    if(skill.getSoulsConsume() > getConsumedSouls())
    {
      sendPacket(Msg.THERE_IS_NOT_ENOUGHT_SOUL);
      return;
    }
    fireMethodInvoked(MethodCollection.onStartAltCast, new Object[]
    {
      skill, target
    });
    if(skill.getSoulsConsume() > 0)
    {
      setConsumedSouls(getConsumedSouls() - skill.getSoulsConsume(), null);
    }
    int level = Math.max(1, getSkillDisplayLevel(magicId));
    Formulas.calcSkillMastery(skill, this);
    long reuseDelay = Formulas.calcSkillReuseDelay(this, skill);
    if(!skill.isToggle())
    {
      broadcastPacket(new MagicSkillUse(this, target, skill.getDisplayId(), level, skill.getHitTime(), reuseDelay));
    }
    // Не показывать сообщение для хербов и кубиков
    if(!(skill.getId() >= 4049 && skill.getId() <= 4055 || skill.getId() >= 4164 && skill.getId() <= 4166 || skill.getId() >= 2278 && skill.getId() <= 2285 || skill.getId() >= 2512 && skill.getId() <= 2514 || skill.getId() == 5115 || skill.getId() == 5116 || skill.getId() == 2580))
    {
      if(!skill.isHandler())
      {
        sendPacket(new SystemMessage(SystemMessage.YOU_USE_S1).addSkillName(magicId, level));
      }
      else
      {
        sendPacket(new SystemMessage(SystemMessage.YOU_USE_S1).addItemName(skill.getItemConsumeId()[0]));
      }
    }
    // Skill reuse check
    if(reuseDelay > 10)
    {
      disableItem(skill, reuseDelay, reuseDelay);
      disableSkill(skill.getId(), reuseDelay);
    }
    ThreadPoolManager.getInstance().scheduleAi(new AltMagicUseTask(this, target, skill), skill.getHitTime(), isPlayable());
  }

  public void sendReuseMessage(L2Skill skill)
  {
    if(isPet() || isSummon())
    {
      L2Player player = getPlayer();
      if(player != null && isSkillDisabled(skill.getId()))
      {
        player.sendPacket(new SystemMessage(SystemMessage.THAT_PET_SERVITOR_SKILL_CANNOT_BE_USED_BECAUSE_IT_IS_RECHARGING));
      }
      return;
    }
    if(!isPlayer() || isCastingNow())
    {
      return;
    }
    SkillTimeStamp sts = ((L2Player) this).getSkillReuseTimeStamps().get(skill.getId());
    if(sts == null || !sts.hasNotPassed())
    {
      return;
    }
    long timeleft = sts.getReuseCurrent();
    if(!Config.ALT_SHOW_REUSE_MSG && timeleft < 10000 || timeleft < 500)
    {
      return;
    }
    long hours = timeleft / 3600000;
    long minutes = (timeleft - hours * 3600000) / 60000;
    long seconds = (long) Math.ceil((timeleft - hours * 3600000 - minutes * 60000) / 1000.);
    if(hours > 0)
    {
      sendPacket(new SystemMessage(SystemMessage.THERE_ARE_S2_HOURS_S3_MINUTES_AND_S4_SECONDS_REMAINING_IN_S1S_REUSE_TIME).addSkillName(skill.getId(), skill.getDisplayLevel()).addNumber(hours).addNumber(minutes).addNumber(seconds));
    }
    else if(minutes > 0)
    {
      sendPacket(new SystemMessage(SystemMessage.THERE_ARE_S2_MINUTES_S3_SECONDS_REMAINING_IN_S1S_REUSE_TIME).addSkillName(skill.getId(), skill.getDisplayLevel()).addNumber(minutes).addNumber(seconds));
    }
    else
    {
      sendPacket(new SystemMessage(SystemMessage.THERE_ARE_S2_SECONDS_REMAINING_IN_S1S_REUSE_TIME).addSkillName(skill.getId(), skill.getDisplayLevel()).addNumber(seconds));
    }
  }

  public void broadcastPacket(L2GameServerPacket... packets)
  {
    sendPacket(packets);
    broadcastPacketToOthers(packets);
  }

  public void broadcastPacketToOthers(L2GameServerPacket... packets)
  {
    if(!isVisible() || packets.length == 0)
    {
      return;
    }
    GArray<L2GameServerPacket> packetsNoBuffs = null;
    for(L2GameServerPacket packet : packets)
    {
      if(notShowPacket(packet))
      {
        packetsNoBuffs = new GArray<L2GameServerPacket>(packets.length);
        break;
      }
    }
    if(packetsNoBuffs != null)
    {
      for(L2GameServerPacket packet : packets)
      {
        if(notShowPacket(packet))
        {
          packetsNoBuffs.add(packet);
        }
      }
    }
    for(L2Player target : L2World.getAroundPlayers(this))
    {
      if(target != null && _objectId != target.getObjectId())
      {
        if(packetsNoBuffs != null && target.isNotShowBuffAnim())
        {
          target.sendPackets(packetsNoBuffs);
        }
        else
        {
          target.sendPacket(packets);
        }
      }
    }
  }

  private boolean notShowPacket(L2GameServerPacket packet)
  {
    if(packet instanceof MagicSkillLaunched)
    {
      return !((MagicSkillLaunched) packet).isOffensive();
    }
    if(packet instanceof MagicSkillUse)
    {
      int id = ((MagicSkillUse) packet).getSkillId();
      // Соулшоты (вероятно, можно добавить и другие скиллы)
      return id == 2061 || id == 2160 || id == 2161 || id == 2162 || id == 2163 || id == 2164 || id == 2033 || id == 2008 || id == 2009 || id == 2039 || id == 2150 || id == 2151 || id == 2152 || id == 2153 || id == 2154;
    }
    return false;
  }

  public void addStatusListener(L2Character object)
  {
    if(object == this)
    {
      return;
    }
    statusListenerLock.lock();
    try
    {
      if(_statusListener == null)
      {
        _statusListener = new CopyOnWriteArraySet<L2Character>();
      }
      _statusListener.add(object);
    }
    finally
    {
      statusListenerLock.unlock();
    }
  }

  public void removeStatusListener(L2Character object)
  {
    statusListenerLock.lock();
    try
    {
      if(_statusListener == null)
      {
        return;
      }
      _statusListener.remove(object);
      if(_statusListener.isEmpty())
      {
        _statusListener = null;
      }
    }
    finally
    {
      statusListenerLock.unlock();
    }
  }

  public StatusUpdate makeStatusUpdate(int... fields)
  {
    StatusUpdate su = new StatusUpdate(getObjectId());
    for(int field : fields)
    {
      switch(field)
      {
        case StatusUpdate.CUR_HP:
          su.addAttribute(field, (int) getCurrentHp());
          break;
        case StatusUpdate.MAX_HP:
          su.addAttribute(field, getMaxHp());
          break;
        case StatusUpdate.CUR_MP:
          su.addAttribute(field, (int) getCurrentMp());
          break;
        case StatusUpdate.MAX_MP:
          su.addAttribute(field, getMaxMp());
          break;
        case StatusUpdate.KARMA:
          su.addAttribute(field, getKarma());
          break;
        case StatusUpdate.CUR_CP:
          su.addAttribute(field, (int) getCurrentCp());
          break;
        case StatusUpdate.MAX_CP:
          su.addAttribute(field, getMaxCp());
          break;
        default:
          System.out.println("unknown StatusUpdate field: " + field);
          Thread.dumpStack();
          break;
      }
    }
    return su;
  }

  public void broadcastStatusUpdate()
  {
    CopyOnWriteArraySet<L2Character> list = _statusListener;
    if(list == null || list.isEmpty())
    {
      return;
    }
    if(!needStatusUpdate())
    {
      return;
    }
    StatusUpdate su = makeStatusUpdate(StatusUpdate.CUR_HP, StatusUpdate.CUR_MP, StatusUpdate.CUR_CP);
    for(L2Character temp : list)
    {
      if(!Config.FORCE_STATUSUPDATE)
      {
        if(temp.getTarget() == this)
        {
          temp.sendPacket(su);
        }
      }
      else
      {
        temp.sendPacket(su);
      }
    }
  }

  public int calcHeading(Location dest)
  {
    if(dest == null)
    {
      return 0;
    }
    if(Math.abs(getX() - dest.x) == 0 && Math.abs(getY() - dest.y) == 0)
    {
      return _heading;
    }
    return calcHeading(dest.x, dest.y);
  }

  public int calcHeading(int x_dest, int y_dest)
  {
    return (int) (Math.atan2(getY() - y_dest, getX() - x_dest) * HEADINGS_IN_PI) + 32768;
  }

  public final double calcStat(Stats stat, double init)
  {
    return calcStat(stat, init, null, null);
  }

  public final double calcStat(Stats stat, double init, L2Character object, L2Skill skill)
  {
    int id = stat.ordinal();
    Calculator c = _calculators[id];
    if(c == null)
    {
      return init;
    }
    Env env = new Env();
    env.character = this;
    env.target = object;
    env.skill = skill;
    env.value = init;
    c.calc(env);
    return env.value;
  }

  public final double calcStat(Stats stat, L2Character object, L2Skill skill)
  {
    Env env = new Env(this, object, skill);
    stat.getInit().calc(env);
    int id = stat.ordinal();
    Calculator c = _calculators[id];
    if(c != null)
    {
      c.calc(env);
    }
    return env.value;
  }

  /**
   * Return the Attack Speed of the L2Character (delay (in milliseconds) before next attack).
   */
  public int calculateAttackDelay()
  {
    return Formulas.calcPAtkSpd(getPAtkSpd());
  }

  public void callSkill(L2Skill skill, GArray<L2Character> targets, boolean useActionSkills)
  {
    try
    {
      if(useActionSkills && !skill.isUsingWhileCasting() && _skillsOnAction != null)
      {
        if(skill.isOffensive())
        {
          if(skill.isMagic())
          {
            ConcurrentLinkedQueue<L2Skill> SkillsOnMagicAttack = getTriggerableSkills().get(TriggerActionType.OFFENSIVE_MAGICAL_SKILL_USE);
            if(SkillsOnMagicAttack != null)
            {
              for(L2Skill sk : SkillsOnMagicAttack)
              {
                if(Rnd.chance(sk.getChanceForAction(TriggerActionType.OFFENSIVE_MAGICAL_SKILL_USE)) && sk.checkCondition(this, sk.getAimingTarget(this, this.getTarget()), false, false, true))
                {
                  L2Skill.broadcastUseAnimation(sk, this, targets);
                  Formulas.calcSkillMastery(sk, this);
                  callSkill(sk, targets, false);
                }
              }
            }
          }
          else
          {
            ConcurrentLinkedQueue<L2Skill> SkillsOnSkillAttack = getTriggerableSkills().get(TriggerActionType.OFFENSIVE_PHYSICAL_SKILL_USE);
            if(SkillsOnSkillAttack != null)
            {
              for(L2Skill sk : SkillsOnSkillAttack)
              {
                if(Rnd.chance(sk.getChanceForAction(TriggerActionType.OFFENSIVE_PHYSICAL_SKILL_USE)) && sk.checkCondition(this, sk.getAimingTarget(this, this.getTarget()), false, false, true))
                {
                  L2Skill.broadcastUseAnimation(sk, this, targets);
                  Formulas.calcSkillMastery(sk, this);
                  callSkill(sk, targets, false);
                }
              }
            }
          }
          for(L2Character target : targets)
          {
            useActionSkill(skill, target, this, TriggerActionType.UNDER_SKILL_ATTACK);
          }
        }
        else if(skill.isMagic())
        {
          ConcurrentLinkedQueue<L2Skill> SkillsOnMagicSupport = getTriggerableSkills().get(TriggerActionType.SUPPORT_MAGICAL_SKILL_USE);
          if(SkillsOnMagicSupport != null)
          {
            for(L2Skill sk : SkillsOnMagicSupport)
            {
              if(Rnd.chance(sk.getChanceForAction(TriggerActionType.SUPPORT_MAGICAL_SKILL_USE)) && sk.checkCondition(this, sk.getAimingTarget(this, this.getTarget()), false, false, true))
              {
                L2Skill.broadcastUseAnimation(sk, this, targets);
                Formulas.calcSkillMastery(sk, this);
                callSkill(sk, targets, false);
              }
            }
          }
        }
        if(isPlayer())
        {
          L2Player pl = (L2Player) this;
          for(L2Character target : targets)
          {
            if(target != null && target.isNpc())
            {
              L2NpcInstance npc = (L2NpcInstance) target;
              GArray<QuestState> ql = pl.getQuestsForEvent(npc, QuestEventType.MOB_TARGETED_BY_SKILL);
              if(ql != null)
              {
                for(QuestState qs : ql)
                {
                  qs.getQuest().notifySkillUse(npc, skill, qs);
                }
              }
            }
          }
        }
      }
      if(skill.getNegateSkill() > 0)
      {
        for(L2Character target : targets)
        {
          for(L2Effect e : target.getEffectList().getAllEffects())
          {
            L2Skill efs = e.getSkill();
            if(efs.getId() == skill.getNegateSkill() && efs.isCancelable() && (skill.getNegatePower() <= 0 || efs.getPower() <= skill.getNegatePower()))
            {
              e.exit();
            }
          }
        }
      }
      if(skill.getCancelTarget() > 0)
      {
        for(L2Character target : targets)
        {
          if(Rnd.chance(skill.getCancelTarget()))
          {
            if(target.getCastingSkill() != null && (target.getCastingSkill().getSkillType() == SkillType.TAKECASTLE || target.getCastingSkill().getSkillType() == SkillType.TAKEFORTRESS || target.getCastingSkill().getSkillType() == SkillType.TAKEFLAG))
            {
              continue;
            }
            if(!target.isRaid())
            {
              target.abortAttack(true, true);
              target.abortCast(true);
              target.setTarget(null);
            }
          }
        }
      }
      if(skill.isSkillInterrupt())
      {
        for(L2Character target : targets)
        {
          if(!target.isRaid())
          {
            if(target.getCastingSkill() != null && !target.getCastingSkill().isMagic())
            {
              target.abortCast(false);
            }
            target.abortAttack(true, true);
          }
        }
      }
      if(skill.isOffensive())
      {
        startAttackStanceTask();
      }
      skill.getEffects(this, this, false, true);
      skill.useSkill(this, targets);
    }
    catch(Exception e)
    {
      _log.log(Level.WARNING, "", e);
    }
  }

  public boolean checkBlockedStat(Stats stat)
  {
    return _blockedStats != null && _blockedStats.contains(stat);
  }

  public boolean checkReflectSkill(L2Character attacker, L2Skill skill)
  {
    if(isInvul() || attacker.isInvul() || !skill.isOffensive()) // Не отражаем, если есть неуязвимость, иначе она может отмениться
    {
      return false;
    }
    // Из магических скилов отражаются только скилы наносящие урон по ХП.
    if(skill.isMagic() && skill.getSkillType() != SkillType.MDAM)
    {
      return false;
    }
    if(Rnd.chance(calcStat(skill.isMagic() ? Stats.REFLECT_MAGIC_SKILL : Stats.REFLECT_PHYSIC_SKILL, 0, attacker, skill)))
    {
      sendPacket(new SystemMessage(SystemMessage.YOU_COUNTERED_C1S_ATTACK).addName(attacker));
      attacker.sendPacket(new SystemMessage(SystemMessage.C1_DODGES_THE_ATTACK).addName(this));
      return true;
    }
    return false;
  }

  public void doCounterAttack(L2Skill skill, L2Character attacker)
  {
    if(isInvul() || attacker.isInvul()) // Не отражаем, если есть неуязвимость, иначе она может отмениться
    {
      return;
    }
    if(skill == null || skill.isMagic() || !skill.isOffensive() || skill.getCastRange() > 200)
    {
      return;
    }
    if(Rnd.chance(calcStat(Stats.COUNTER_ATTACK, 0, attacker, skill)))
    {
      double damage = 1189 * getPAtk(attacker) / Math.max(attacker.getPDef(this), 1);
      attacker.sendPacket(new SystemMessage(SystemMessage.C1S_IS_PERFORMING_A_COUNTERATTACK).addName(this));
      sendPacket(new SystemMessage(SystemMessage.C1S_IS_PERFORMING_A_COUNTERATTACK).addName(this));
      sendPacket(new SystemMessage(SystemMessage.C1_HAS_GIVEN_C2_DAMAGE_OF_S3).addName(this).addName(attacker).addNumber((long) damage));
      attacker.reduceCurrentHp(damage, this, skill, true, true, false, false);
    }
  }

  /**
   * Disable this skill id for the duration of the delay in milliseconds.
   *
   * @param skillId
   * @param delay   (seconds * 1000)
   */
  public void disableSkill(int skillId, long delay)
  {
    if(delay > 10)
    {
      if(_disabledSkills == null)
      {
        _disabledSkills = new GCSArray<Integer>();
      }
      _disabledSkills.add(skillId);
      ThreadPoolManager.getInstance().scheduleAi(new EnableSkillTask(this, skillId), delay, isPlayable());
    }
  }

  public void doAttack(L2Character target)
  {
    if(target == null || isAMuted() || isAttackingNow() || isAlikeDead() || target.isAlikeDead() || !isInRange(target, 2000))
    {
      return;
    }
    fireMethodInvoked(MethodCollection.onStartAttack, new Object[]
    {
      this, target
    });
    // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack)
    int sAtk = Math.max(calculateAttackDelay(), 333);
    int ssGrade = 0;
    L2Weapon weaponItem = getActiveWeaponItem();
    if(weaponItem != null)
    {
      if(isPlayer() && weaponItem.getAttackReuseDelay() > 0)
      {
        int reuse = (int) (weaponItem.getAttackReuseDelay() * getReuseModifier(target) * 666 * calcStat(Stats.ATK_BASE, 0, target, null) / 293. / getPAtkSpd());
        if(reuse > 0)
        {
          sendPacket(new SetupGauge(SetupGauge.RED, reuse));
          _attackReuseEndTime = reuse + System.currentTimeMillis() - 75;
          if(reuse > sAtk)
          {
            ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT, null, null), reuse, isPlayable());
          }
        }
      }
      ssGrade = weaponItem.getCrystalType().externalOrdinal;
    }
    _attackEndTime = sAtk + System.currentTimeMillis() + 10000;
    _isAttackAborted = false;
    Attack attack = new Attack(this, target, getChargedSoulShot(), ssGrade);
    setHeading(target, true);
    // Select the type of attack to start
    if(weaponItem == null)
    {
      doAttackHitSimple(attack, target, 1., !isPlayer(), sAtk, true);
    }
    else
    {
      switch(weaponItem.getItemType())
      {
        case BOW:
        case CROSSBOW:
          doAttackHitByBow(attack, target, sAtk);
          break;
        case POLE:
          doAttackHitByPole(attack, target, sAtk);
          break;
        case DUAL:
        case DUALFIST:
        case DUALDAGGER:
          doAttackHitByDual(attack, target, sAtk);
          break;
        default:
          doAttackHitSimple(attack, target, 1., true, sAtk, true);
      }
    }
    if(attack.hasHits())
    {
      broadcastPacket(attack);
    }
  }

  private void doAttackHitSimple(Attack attack, L2Character target, double multiplier, boolean unchargeSS, int sAtk, boolean notify)
  {
    int damage1 = 0;
    boolean shld1 = false;
    boolean crit1 = false;
    boolean miss1 = Formulas.calcHitMiss(this, target);
    if(!miss1)
    {
      AttackInfo info = Formulas.calcPhysDam(this, target, null, false, false, attack._soulshot, false);
      damage1 = (int) (info.damage * multiplier);
      shld1 = info.shld;
      crit1 = info.crit;
    }
    else if(target.isPlayer() && !target.isInvul())
    {
      target.sendPacket(new SystemMessage(SystemMessage.C1_HAS_EVADED_C2S_ATTACK).addName(target).addName(this));
    }
    ThreadPoolManager.getInstance().scheduleAi(new HitTask(this, target, damage1, crit1, miss1, attack._soulshot, shld1, unchargeSS, notify), sAtk, isPlayable());
    attack.addHit(target, damage1, miss1, crit1, shld1);
  }

  private void doAttackHitByBow(Attack attack, L2Character target, int sAtk)
  {
    L2Weapon activeWeapon = getActiveWeaponItem();
    if(activeWeapon == null)
    {
      return;
    }
    int damage1 = 0;
    boolean shld1 = false;
    boolean crit1 = false;
    // Calculate if hit is missed or not
    boolean miss1 = Formulas.calcHitMiss(this, target);
    reduceArrowCount();
    if(!miss1)
    {
      AttackInfo info = Formulas.calcPhysDam(this, target, null, false, false, attack._soulshot, false);
      damage1 = (int) info.damage;
      shld1 = info.shld;
      crit1 = info.crit;
      int range = activeWeapon.getAttackRange();
      damage1 *= Math.min(range, getDistance(target)) / range * .4 + 0.8; // разброс 20% в обе стороны
    }
    ThreadPoolManager.getInstance().scheduleAi(new HitTask(this, target, damage1, crit1, miss1, attack._soulshot, shld1, true, true), sAtk, isPlayable());
    attack.addHit(target, damage1, miss1, crit1, shld1);
  }

  private void doAttackHitByDual(Attack attack, L2Character target, int sAtk)
  {
    int damage1 = 0;
    int damage2 = 0;
    boolean shld1 = false;
    boolean shld2 = false;
    boolean crit1 = false;
    boolean crit2 = false;
    boolean miss1 = Formulas.calcHitMiss(this, target);
    boolean miss2 = Formulas.calcHitMiss(this, target);
    if(!miss1)
    {
      AttackInfo info = Formulas.calcPhysDam(this, target, null, true, false, attack._soulshot, false);
      damage1 = (int) info.damage;
      shld1 = info.shld;
      crit1 = info.crit;
    }
    if(!miss2)
    {
      AttackInfo info = Formulas.calcPhysDam(this, target, null, true, false, attack._soulshot, false);
      damage2 = (int) info.damage;
      shld2 = info.shld;
      crit2 = info.crit;
    }
    // Create a new hit task with Medium priority for hit 1 and for hit 2 with a higher delay
    ThreadPoolManager.getInstance().scheduleAi(new HitTask(this, target, damage1, crit1, miss1, attack._soulshot, shld1, true, false), sAtk / 2, isPlayable());
    ThreadPoolManager.getInstance().scheduleAi(new HitTask(this, target, damage2, crit2, miss2, attack._soulshot, shld2, false, true), sAtk, isPlayable());
    attack.addHit(target, damage1, miss1, crit1, shld1);
    attack.addHit(target, damage2, miss2, crit2, shld2);
  }

  private void doAttackHitByPole(Attack attack, L2Character target, int sAtk)
  {
    int angle = (int) calcStat(Stats.POLE_ATTACK_ANGLE, 90, target, null);
    int range = (int) calcStat(Stats.POWER_ATTACK_RANGE, getTemplate().baseAtkRange, target, null);
    // Используем Math.round т.к. обычный кастинг обрезает к меньшему
    // double d = 2.95. int i = (int)d, выйдет что i = 2
    // если 1% угла или 1 дистанции не играет огромной роли, то для
    // количества целей это критично
    int attackcountmax = (int) Math.round(calcStat(Stats.POLE_TARGERT_COUNT, 3, target, null));
    if(isBoss())
    {
      attackcountmax += 27;
    }
    else if(isRaid())
    {
      attackcountmax += 12;
    }
    else if(isMonster() && getLevel() > 0)
    {
      attackcountmax += getLevel() / 7.5;
    }
    double mult = 1;
    int attackcount = 1;
    for(L2Character t : getAroundCharacters(range, 200))
    {
      if(attackcount <= attackcountmax)
      {
        if(t != null && !t.isDead() && t.isAutoAttackable(this))
        {
          if(t == getAI().getAttackTarget() || !isInFront(t, angle))
          {
            continue;
          }
          doAttackHitSimple(attack, t, mult, attackcount == 0, sAtk, false);
          mult *= 0.85;
          attackcount++;
        }
      }
      else
      {
        break;
      }
    }
    doAttackHitSimple(attack, target, 1., true, sAtk, true);
  }

  public long getAnimationEndTime()
  {
    return _animationEndTime;
  }

  public void doCast(L2Skill skill, L2Character target, boolean forceUse)
  {
    // Прерывать дуэли если цель не дуэлянт
    if(getDuel() != null)
    {
      if(target.getDuel() != getDuel())
      {
        getDuel().setDuelState(getPlayer().getStoredId(), DuelState.Interrupted);
      }
      else if(isPlayer() && getDuel().getDuelState(getStoredId()) == DuelState.Interrupted)
      {
        sendPacket(Msg.INVALID_TARGET);
        return;
      }
    }
    if(skill == null)
    {
      sendActionFailed();
      return;
    }
    int itemConsume[] = skill.getItemConsume();
    if(itemConsume[0] > 0)
    {
      for(int i = 0; i < itemConsume.length; i++)
      {
        if(!consumeItem(skill.getItemConsumeId()[i], itemConsume[i]))
        {
          sendPacket(Msg.INCORRECT_ITEM_COUNT);
          sendChanges();
          return;
        }
      }
    }
    int magicId = skill.getId();
    if(target == null)
    {
      target = skill.getAimingTarget(this, getTarget());
    }
    if(target == null)
    {
      return;
    }
    fireMethodInvoked(MethodCollection.onStartCast, new Object[]
    {
      skill, target, forceUse
    });
    setHeading(target, true);
    int level = Math.max(1, getSkillDisplayLevel(magicId));
    int skillTime = skill.isSkillTimePermanent() ? skill.getHitTime() : Formulas.calcMAtkSpd(this, skill, skill.getHitTime());
    int skillInterruptTime = skill.isMagic() ? Formulas.calcMAtkSpd(this, skill, skill.getSkillInterruptTime()) : 0;
    int minCastTime = Math.min(Config.SKILLS_CAST_TIME_MIN, skill.getHitTime());
    if(skillTime < minCastTime)
    {
      skillTime = minCastTime;
      skillInterruptTime = 0;
    }
    _animationEndTime = System.currentTimeMillis() + skillTime;
    if(skill.isMagic() && !skill.isSkillTimePermanent() && getChargedSpiritShot() > 0)
    {
      skillTime = (int) (0.70 * skillTime);
      skillInterruptTime = (int) (0.70 * skillInterruptTime);
    }
    Formulas.calcSkillMastery(skill, this); // Calculate skill mastery for current cast
    long reuseDelay = Math.max(500, Formulas.calcSkillReuseDelay(this, skill));
    broadcastPacket(new MagicSkillUse(this, target, skill.getDisplayId(), level, skillTime, reuseDelay));
    disableItem(skill, reuseDelay, reuseDelay);
    disableSkill(skill.getId(), reuseDelay);
    if(isPlayer())
    {
      if(!skill.isHandler())
      {
        sendPacket(new SystemMessage(SystemMessage.YOU_USE_S1).addSkillName(magicId, level));
      }
      else
      {
        sendPacket(new SystemMessage(SystemMessage.YOU_USE_S1).addItemName(skill.getItemConsumeId()[0]));
      }
    }
    if(skill.getTargetType() == SkillTargetType.TARGET_HOLY)
    {
      target.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, this, 1);
    }
    double mpConsume1 = skill.isUsingWhileCasting() ? skill.getMpConsume() : skill.getMpConsume1();
    if(mpConsume1 > 0)
    {
      if(skill.isMusic())
      {
        double inc = mpConsume1 / 2;
        double add = 0;
        for(L2Effect e : getEffectList().getAllEffects())
        {
          if(e.getSkill().getId() != skill.getId() && e.getSkill().isMusic() && e.getTimeLeft() > 30000)
          {
            add += inc;
          }
        }
        mpConsume1 += add;
        //mpConsume1 = calcStat(Stats.MP_DANCE_SKILL_CONSUME, mpConsume1, target, skill);
      }
      else if(skill.isMagic())
      {
        reduceCurrentMp(calcStat(Stats.MP_MAGIC_SKILL_CONSUME, mpConsume1, target, skill), null);
      }
      else
      {
        reduceCurrentMp(calcStat(Stats.MP_PHYSICAL_SKILL_CONSUME, mpConsume1, target, skill), null);
      }
    }
    _flyLoc = null;
    if(skill.getFlyType() == FlyType.DUMMY || skill.getFlyType() == FlyType.CHARGE)
    {
      Location flyLoc = getFlyLocation(target, skill);
      if(flyLoc != null)
      {
        _flyLoc = flyLoc;
        broadcastPacket(new FlyToLocation(this, flyLoc, skill.getFlyType()));
      }
      else
      {
        sendPacket(Msg.CANNOT_SEE_TARGET);
        return;
      }
    }
    _castingSkill = skill;
    _castInterruptTime = System.currentTimeMillis() + skillInterruptTime;
    setCastingTarget(target);
    if(skill.isUsingWhileCasting())
    {
      callSkill(skill, skill.getTargets(this, target, forceUse), true);
    }
    if(isPlayer())
    {
      sendPacket(new SetupGauge(SetupGauge.BLUE, skillTime));
    }
    _scheduledCastCount = skill.getCastCount();
    _scheduledCastInterval = skill.getCastCount() > 0 ? skillTime / _scheduledCastCount : skillTime;
    // Create a task MagicUseTask with Medium priority to launch the MagicSkill at the end of the casting time
    _skillLaunchedTask = ThreadPoolManager.getInstance().scheduleAi(new MagicLaunchedTask(this, forceUse), skillInterruptTime, isPlayable());
    _skillTask = ThreadPoolManager.getInstance().scheduleAi(new MagicUseTask(this, forceUse), skill.getCastCount() > 0 ? skillTime / skill.getCastCount() : skillTime, isPlayable());
  }
  private Location _flyLoc;

  private Location getFlyLocation(L2Object target, L2Skill skill)
  {
    if(target != null && target != this)
    {
      Location loc;
      double radian = Util.convertHeadingToRadian(target.getHeading());
      if(skill.isFlyToBack())
      {
        loc = new Location(target.getX() + (int) (Math.sin(radian) * 40), target.getY() - (int) (Math.cos(radian) * 40), target.getZ());
      }
      else
      {
        loc = new Location(target.getX() - (int) (Math.sin(radian) * 40), target.getY() + (int) (Math.cos(radian) * 40), target.getZ());
      }
      if(isFlying())
      {
        if(isPlayer() && ((L2Player) this).isInFlyingTransform() && (loc.z <= 0 || loc.z >= 6000))
        {
          return null;
        }
        if(GeoEngine.moveCheckInAir(getX(), getY(), getZ(), loc.x, loc.y, loc.z, getColRadius(), getReflection().getGeoIndex()) == null)
        {
          return null;
        }
      }
      else
      {
        loc.correctGeoZ();
        if(!GeoEngine.canMoveToCoord(getX(), getY(), getZ(), loc.x, loc.y, loc.z, getReflection().getGeoIndex()))
        {
          loc = target.getLoc(); // Если не получается встать рядом с объектом, пробуем встать прямо в него
          if(!GeoEngine.canMoveToCoord(getX(), getY(), getZ(), loc.x, loc.y, loc.z, getReflection().getGeoIndex()))
          {
            return null;
          }
        }
      }
      return loc;
    }
    double radian = Util.convertHeadingToRadian(getHeading());
    int x1 = -(int) (Math.sin(radian) * skill.getFlyRadius());
    int y1 = (int) (Math.cos(radian) * skill.getFlyRadius());
    if(isFlying())
    {
      return GeoEngine.moveCheckInAir(getX(), getY(), getZ(), getX() + x1, getY() + y1, getZ(), getColRadius(), getReflection().getGeoIndex());
    }
    return GeoEngine.moveCheck(getX(), getY(), getZ(), getX() + x1, getY() + y1, getReflection().getGeoIndex());
  }

  public void doDie(L2Character killer)
  {
    // killing is only possible one time
    dieLock.lock();
    try
    {
      if(_killedAlready)
      {
        return;
      }
      _killedAlready = true;
    }
    finally
    {
      dieLock.unlock();
    }
    fireMethodInvoked(MethodCollection.doDie, new Object[]
    {
      killer
    });
    if(killer != null)
    {
      killer.fireMethodInvoked(MethodCollection.onKill, new Object[]
      {
        this
      });
      if(isPlayer() && killer.isPlayable())
      {
        _currentCp = 0;
      }
    }
    setTarget(null);
    stopMove();
    _currentHp = 0;
    // Stop all active skills effects in progress on the L2Character
    setMassUpdating(true);
    if(isBlessedByNoblesse() || isSalvation())
    {
      if(isSalvation() && !getPlayer().isInOlympiadMode())
      {
        getPlayer().reviveRequest(getPlayer(), 100, false);
      }
      for(L2Effect e : getEffectList().getAllEffects())
      // Noblesse Blessing Buff/debuff effects are retained after
      // death. However, Noblesse Blessing and Lucky Charm are lost as normal.
      {
        if(e.getEffectType() == EffectType.BlessNoblesse || e.getSkill().getId() == L2Skill.SKILL_FORTUNE_OF_NOBLESSE || e.getSkill().getId() == L2Skill.SKILL_RAID_BLESSING)
        {
          e.exit();
        }
      }
    }
    else
    {
      for(L2Effect e : getEffectList().getAllEffects())
      // Трансформы и Battlefield Death Syndrome при смерти не слетают.
      // Charm of Courage тоже, он удаляется позже
      {
        if(e.getEffectType() != EffectType.Transformation && e.getSkill().getId() != L2Skill.SKILL_CHARM_OF_COURAGE && e.getSkill().getId() != L2Skill.SKILL_BATTLEFIELD_DEATH_SYNDROME)
        {
          e.exit();
        }
      }
    }
    setMassUpdating(false);
    sendChanges();
    updateEffectIcons();
    broadcastStatusUpdate();
    ThreadPoolManager.getInstance().executeGeneral(new NotifyAITask(this, CtrlEvent.EVT_DEAD, killer, null));
    Object[] script_args = new Object[]
    {
      this, killer
    };
    for(ScriptClassAndMethod handler : ScriptManager.onDie)
    {
      callScripts(handler.scriptClass, handler.method, script_args);
    }
  }

  /**
   * Sets HP, MP and CP and revives the L2Character.
   */
  public void doRevive()
  {
    if(!isTeleporting())
    {
      setIsPendingRevive(false);
      if(isSalvation())
      {
        for(L2Effect e : getEffectList().getAllEffects())
        {
          if(e.getEffectType() == EffectType.Salvation)
          {
            e.exit();
            break;
          }
        }
        setCurrentCp(getMaxCp());
        setCurrentHp(getMaxHp(), true);
        setCurrentMp(getMaxMp());
      }
      else
      {
        if(isPlayer() && Config.RESPAWN_RESTORE_CP >= 0)
        {
          setCurrentCp(getMaxCp() * Config.RESPAWN_RESTORE_CP);
        }
        setCurrentHp(Math.max(1, getMaxHp() * Config.RESPAWN_RESTORE_HP), true);
        if(Config.RESPAWN_RESTORE_MP >= 0)
        {
          setCurrentMp(getMaxMp() * Config.RESPAWN_RESTORE_MP);
        }
      }
      broadcastPacket(new Revive(this));
    }
    else
    {
      setIsPendingRevive(true);
    }
  }

  public void enableSkill(Integer skillId)
  {
    if(_disabledSkills == null)
    {
      return;
    }
    _disabledSkills.remove(skillId);
  }

  /**
   * Return a map of 32 bits (0x00000000) containing all abnormal effects
   */
  public int getAbnormalEffect()
  {
    return _abnormalEffects;
  }

  /**
   * Return a map of 32 bits (0x00000000) containing all special effects
   */
  public int getAbnormalEffect2()
  {
    return _abnormalEffects2;
  }

  public int getAccuracy()
  {
    return (int) (calcStat(Stats.ACCURACY_COMBAT, 0, null, null));
  }

  /**
   * Возвращает тип атакующего элемента и его силу.
   *
   * @return массив, в котором:
   *         <li>[0]: тип элемента,
   *         <li>[1]: его сила
   */
  public int[] getAttackElement()
  {
    return Formulas.calcAttackElement(this);
  }

  /**
   * Возвращает защиту от элемента: огонь.
   *
   * @return значение защиты
   */
  public int getDefenceFire()
  {
    return (int) -calcStat(Stats.FIRE_RECEPTIVE, 0, null, null);
  }

  /**
   * Возвращает защиту от элемента: вода.
   *
   * @return значение защиты
   */
  public int getDefenceWater()
  {
    return (int) -calcStat(Stats.WATER_RECEPTIVE, 0, null, null);
  }

  /**
   * Возвращает защиту от элемента: воздух.
   *
   * @return значение защиты
   */
  public int getDefenceWind()
  {
    return (int) -calcStat(Stats.WIND_RECEPTIVE, 0, null, null);
  }

  /**
   * Возвращает защиту от элемента: земля.
   *
   * @return значение защиты
   */
  public int getDefenceEarth()
  {
    return (int) -calcStat(Stats.EARTH_RECEPTIVE, 0, null, null);
  }

  /**
   * Возвращает защиту от элемента: свет.
   *
   * @return значение защиты
   */
  public int getDefenceHoly()
  {
    return (int) -calcStat(Stats.SACRED_RECEPTIVE, 0, null, null);
  }

  /**
   * Возвращает защиту от элемента: тьма.
   *
   * @return значение защиты
   */
  public int getDefenceUnholy()
  {
    return (int) -calcStat(Stats.UNHOLY_RECEPTIVE, 0, null, null);
  }

  /**
   * Возвращает коллекцию скиллов для быстрого перебора
   */
  public Collection<L2Skill> getAllSkills()
  {
    return _skills.values();
  }

  /**
   * Возвращает массив скиллов для безопасного перебора
   */
  public final L2Skill[] getAllSkillsArray()
  {
    Collection<L2Skill> vals = _skills.values();
    return vals.toArray(new L2Skill[vals.size()]);
  }

  public final float getAttackSpeedMultiplier()
  {
    return (float) (1.1 * getPAtkSpd() / getTemplate().basePAtkSpd);
  }

  public int getBuffLimit()
  {
    return (int) calcStat(Stats.BUFF_LIMIT, Config.ALT_BUFF_LIMIT, null, null);
  }

  public L2Skill getCastingSkill()
  {
    return _castingSkill;
  }

  public final L2Character getCharTarget()
  {
    L2Object target = getTarget();
    if(target == null || !target.isCharacter())
    {
      return null;
    }
    return (L2Character) target;
  }

  public byte getCON()
  {
    return (byte) calcStat(Stats.STAT_CON, _template.baseCON, null, null);
  }

  /**
   * Возвращает шанс физического крита (1000 == 100%)
   */
  public int getCriticalHit(L2Character target, L2Skill skill)
  {
    return (int) calcStat(Stats.CRITICAL_BASE, _template.baseCritRate, target, skill);
  }

  /**
   * Возвращает шанс магического крита в процентах
   */
  public double getMagicCriticalRate(L2Character target, L2Skill skill)
  {
    return calcStat(Stats.MCRITICAL_RATE, target, skill);
  }

  /**
   * Return the current CP of the L2Character.
   */
  public final double getCurrentCp()
  {
    return _currentCp;
  }

  public final double getCurrentCpRatio()
  {
    return getCurrentCp() / getMaxCp();
  }

  public final double getCurrentCpPercents()
  {
    return getCurrentCpRatio() * 100f;
  }

  public final boolean isCurrentCpFull()
  {
    return getCurrentCp() >= getMaxCp();
  }

  public final boolean isCurrentCpZero()
  {
    return getCurrentCp() < 1;
  }

  public final double getCurrentHp()
  {
    return _currentHp;
  }

  public final double getCurrentHpRatio()
  {
    return getCurrentHp() / getMaxHp();
  }

  public final double getCurrentHpPercents()
  {
    return getCurrentHpRatio() * 100f;
  }

  public final boolean isCurrentHpFull()
  {
    return getCurrentHp() >= getMaxHp();
  }

  public final boolean isCurrentHpZero()
  {
    return getCurrentHp() < 1;
  }

  public final double getCurrentMp()
  {
    return _currentMp;
  }

  public final double getCurrentMpRatio()
  {
    return getCurrentMp() / getMaxMp();
  }

  public final double getCurrentMpPercents()
  {
    return getCurrentMpRatio() * 100f;
  }

  public final boolean isCurrentMpFull()
  {
    return getCurrentMp() >= getMaxMp();
  }

  public final boolean isCurrentMpZero()
  {
    return getCurrentMp() < 1;
  }

  public Location getDestination()
  {
    return destination;
  }

  public byte getDEX()
  {
    return (byte) calcStat(Stats.STAT_DEX, _template.baseDEX, null, null);
  }

  public int getEvasionRate(L2Character target)
  {
    return (int) (calcStat(Stats.EVASION_RATE, 0, target, null));
  }

  /**
   * If <b>boolean toChar is true heading calcs this->target, else target->this.
   */
  public int getHeadingTo(L2Object target, boolean toChar)
  {
    if(target == null || target == this)
    {
      return -1;
    }
    int dx = target.getX() - getX();
    int dy = target.getY() - getY();
    int heading = (int) (Math.atan2(-dy, -dx) * HEADINGS_IN_PI + 32768);
    heading = toChar ? target.getHeading() - heading : getHeading() - heading;
    if(heading < 0)
    {
      heading = heading + 1 + Integer.MAX_VALUE & 0xFFFF;
    }
    else if(heading > 0xFFFF)
    {
      heading &= 0xFFFF;
    }
    return heading;
  }

  public TargetDirection getDirectionTo(L2Object target, boolean toChar)
  {
    int targeth = getHeadingTo(target, toChar);
    if(targeth == -1)
    {
      return TargetDirection.NONE;
    }
    if(targeth <= 10923 || targeth >= 54613)
    {
      return TargetDirection.BEHIND;
    }
    if(targeth >= 21845 && targeth <= 43691)
    {
      return TargetDirection.FRONT;
    }
    return TargetDirection.SIDE;
  }

  public byte getINT()
  {
    return (byte) calcStat(Stats.STAT_INT, _template.baseINT, null, null);
  }

  public GArray<L2Character> getAroundCharacters(int radius, int height)
  {
    if(!isVisible())
    {
      return new GArray<L2Character>(0);
    }
    return L2World.getAroundCharacters(this, radius, height);
  }

  public GArray<L2NpcInstance> getAroundNpc(int range, int height)
  {
    if(!isVisible())
    {
      return new GArray<L2NpcInstance>(0);
    }
    return L2World.getAroundNpc(this, range, height);
  }

  public boolean knowsObject(L2Object obj)
  {
    return L2World.getAroundObjectById(this, obj.getObjectId()) != null;
  }

  public final L2Skill getKnownSkill(int skillId)
  {
    return _skills.get(skillId);
  }

  public final int getMagicalAttackRange(L2Skill skill)
  {
    if(skill != null)
    {
      return (int) calcStat(Stats.MAGIC_ATTACK_RANGE, skill.getCastRange(), null, skill);
    }
    return getTemplate().baseAtkRange;
  }

  public int getMAtk(L2Character target, L2Skill skill)
  {
    if(skill != null && skill.getMatak() > 0)
    {
      return skill.getMatak();
    }
    return (int) calcStat(Stats.MAGIC_ATTACK, _template.baseMAtk, target, skill);
  }

  public int getMAtkSpd()
  {
    return (int) (calcStat(Stats.MAGIC_ATTACK_SPEED, _template.baseMAtkSpd, null, null));
  }

  public final int getMaxCp()
  {
    return (int) calcStat(Stats.MAX_CP, _template.baseCpMax, null, null);
  }

  public int getMaxHp()
  {
    return (int) calcStat(Stats.MAX_HP, _template.baseHpMax, null, null);
  }

  public int getMaxMp()
  {
    return (int) calcStat(Stats.MAX_MP, _template.baseMpMax, null, null);
  }

  public int getMDef(L2Character target, L2Skill skill)
  {
    return Math.max((int) calcStat(Stats.MAGIC_DEFENCE, _template.baseMDef, target, skill), 1);
  }

  public byte getMEN()
  {
    return (byte) calcStat(Stats.STAT_MEN, _template.baseMEN, null, null);
  }

  public float getMinDistance(L2Object obj)
  {
    float distance = getTemplate().collisionRadius;
    if(obj != null && obj.isCharacter())
    {
      distance += ((L2Character) obj).getTemplate().collisionRadius;
    }
    return distance;
  }

  public float getMovementSpeedMultiplier()
  {
    return getRunSpeed() * 1f / _template.baseRunSpd;
  }

  @Override
  public float getMoveSpeed()
  {
    if(isRunning())
    {
      return getRunSpeed();
    }
    return getWalkSpeed();
  }

  @Override
  public String getName()
  {
    return _name == null ? EMPTY_STRING : _name;
  }

  public int getPAtk(L2Character target)
  {
    return (int) calcStat(Stats.POWER_ATTACK, _template.basePAtk, target, null);
  }

  public int getPAtkSpd()
  {
    return (int) (calcStat(Stats.POWER_ATTACK_SPEED, _template.basePAtkSpd, null, null));
  }

  public int getPDef(L2Character target)
  {
    return (int) calcStat(Stats.POWER_DEFENCE, _template.basePDef, target, null);
  }

  public final int getPhysicalAttackRange()
  {
    return (int) calcStat(Stats.POWER_ATTACK_RANGE, getTemplate().baseAtkRange, null, null);
  }

  public final int getRandomDamage()
  {
    L2Weapon weaponItem = getActiveWeaponItem();
    if(weaponItem == null)
    {
      return 5 + (int) Math.sqrt(getLevel());
    }
    return weaponItem.getRandomDamage();
  }

  public double getReuseModifier(L2Character target)
  {
    return calcStat(Stats.ATK_REUSE, 1, target, null);
  }

  public int getRunSpeed()
  {
    if(isInWater())
    {
      return getSwimSpeed();
    }
    return getSpeed(_template.baseRunSpd);
  }

  public final int getWalkSpeed()
  {
    if(isInWater())
    {
      return getSwimSpeed();
    }
    return getSpeed(_template.baseWalkSpd);
  }

  public int getSpeed(int baseSpeed)
  {
    if(isInWater())
    {
      return getSwimSpeed();
    }
    return (int) calcStat(Stats.RUN_SPEED, baseSpeed, null, null);
    /* Раньше это еще делилось на штраф от пенальти на армор + 0.5
    но штраф на бег идет через скилл штрафа,
    надо ли деление на модификатор 0.5 ?
     */
  }

  public final int getShldDef()
  {
    if(isPlayer())
    {
      return (int) calcStat(Stats.SHIELD_DEFENCE, 0, null, null);
    }
    return (int) calcStat(Stats.SHIELD_DEFENCE, _template.baseShldDef, null, null);
  }

  public final short getSkillDisplayLevel(Integer skillId)
  {
    L2Skill skill = _skills.get(skillId);
    if(skill == null)
    {
      return -1;
    }
    return skill.getDisplayLevel();
  }

  public final short getSkillLevel(Integer skillId)
  {
    L2Skill skill = _skills.get(skillId);
    if(skill == null)
    {
      return -1;
    }
    return skill.getLevel();
  }

  public byte getSkillMastery(Integer skillId)
  {
    if(_skillMastery == null)
    {
      return 0;
    }
    Byte val = _skillMastery.get(skillId);
    return val == null ? 0 : val;
  }

  public void removeSkillMastery(Integer skillId)
  {
    if(_skillMastery != null)
    {
      _skillMastery.remove(skillId);
    }
  }

  public final GArray<L2Skill> getSkillsByType(SkillType type)
  {
    GArray<L2Skill> result = new GArray<L2Skill>();
    for(L2Skill sk : _skills.values())
    {
      if(sk.getSkillType() == type)
      {
        result.add(sk);
      }
    }
    return result;
  }

  public byte getSTR()
  {
    return (byte) calcStat(Stats.STAT_STR, _template.baseSTR, null, null);
  }

  public int getSwimSpeed()
  {
    return (int) calcStat(Stats.RUN_SPEED, Config.SWIMING_SPEED, null, null);
  }

  public L2Object getTarget()
  {
    return L2ObjectsStorage.get(targetStoreId);
  }

  public final int getTargetId()
  {
    L2Object target = getTarget();
    return target == null ? -1 : target.getObjectId();
  }

  public L2CharTemplate getTemplate()
  {
    return _template;
  }

  public L2CharTemplate getBaseTemplate()
  {
    return _baseTemplate;
  }

  public String getTitle()
  {
    if(_title != null && _title.length() > 16)
    {
      return _title.substring(0, 16);
    }
    return _title;
  }

  public byte getWIT()
  {
    return (byte) calcStat(Stats.STAT_WIT, _template.baseWIT, null, null);
  }

  public double headingToRadians(int heading)
  {
    return (heading - 32768) / HEADINGS_IN_PI;
  }

  public final boolean isAlikeDead()
  {
    return _fakeDeath || _currentHp < 0.5;
  }

  public boolean isAttackAborted()
  {
    return _isAttackAborted;
  }

  public final boolean isAttackingNow()
  {
    return _attackEndTime > System.currentTimeMillis();
  }

  public boolean isBehindTarget()
  {
    if(getTarget() != null && getTarget().isCharacter())
    {
      int head = getHeadingTo(getTarget(), true);
      return head != -1 && (head <= 10430 || head >= 55105);
    }
    return false;
  }

  public boolean isToSideOfTarget()
  {
    if(getTarget() != null && getTarget().isCharacter())
    {
      int head = getHeadingTo(getTarget(), true);
      return head != -1 && (head <= 22337 || head >= 43197);
    }
    return false;
  }

  public boolean isToSideOfTarget(L2Object target)
  {
    if(target != null && target.isCharacter())
    {
      int head = getHeadingTo(target, true);
      return head != -1 && (head <= 22337 || head >= 43197);
    }
    return false;
  }

  public boolean isBehindTarget(L2Object target)
  {
    if(target != null && target.isCharacter())
    {
      int head = getHeadingTo(target, true);
      return head != -1 && (head <= 10430 || head >= 55105);
    }
    return false;
  }

  public final boolean isBlessedByNoblesse()
  {
    return _isBlessedByNoblesse > 0;
  }

  public final boolean isSalvation()
  {
    return _isSalvation > 0;
  }

  public final boolean isEffectImmune()
  {
    return _buffImmunity > 0;
  }

  public boolean isDead()
  {
    return _currentHp < 0.5;
  }

  @Override
  public final boolean isFlying()
  {
    return _flying;
  }

  public final boolean isInCombat()
  {
    return _stanceTask != null;
  }

  /**
   * Return True if the target is front L2Character and can be seen.
   * degrees = 0..180, front->sides->back
   */
  public boolean isInFront(L2Object target, int degrees)
  {
    int head = getHeadingTo(target, false);
    return head <= 32768 * degrees / 180 || head >= 65536 - 32768 * degrees / 180;
  }

  public boolean isInvul()
  {
    return _isInvul;
  }

  public boolean isMageClass()
  {
    return getTemplate().baseMAtk > 3;
  }

  public final boolean isRiding()
  {
    return _riding;
  }

  public final boolean isRunning()
  {
    return _running;
  }

  public boolean isSkillDisabled(Integer skillId)
  {
    return _disabledSkills != null && _disabledSkills.contains(skillId);
  }

  public final boolean isTeleporting()
  {
    return _isTeleporting;
  }

  /**
   * Возвращает позицию цели, в которой она будет через пол секунды.
   */
  public Location getIntersectionPoint(L2Character target)
  {
    if(!isInFront(target, 90))
    {
      return new Location(target.getX(), target.getY(), target.getZ());
    }
    double angle = Util.convertHeadingToDegree(target.getHeading()); // угол в градусах
    double radian = Math.toRadians(angle - 90); // угол в радианах
    double range = target.getMoveSpeed() / 2; // расстояние, пройденное за 1 секунду, равно скорости. Берем половину.
    return new Location((int) (target.getX() - range * Math.sin(radian)), (int) (target.getY() + range * Math.cos(radian)), target.getZ());
  }

  public Location applyOffset(Location point, int offset)
  {
    if(offset <= 0)
    {
      return point;
    }
    long dx = point.x - getX();
    long dy = point.y - getY();
    long dz = point.z - getZ();
    double distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
    if(distance <= offset)
    {
      point.set(getX(), getY(), getZ());
      return point;
    }
    if(distance >= 1)
    {
      double cut = offset / distance;
      point.x -= (int) (dx * cut + 0.5f);
      point.y -= (int) (dy * cut + 0.5f);
      point.z -= (int) (dz * cut + 0.5f);
      if(!isFlying() && !isInVehicle() && !isSwimming() && !isVehicle())
      {
        point.correctGeoZ();
      }
    }
    return point;
  }

  public ArrayList<Location> applyOffset(ArrayList<Location> points, int offset)
  {
    offset = offset >> 4;
    if(offset <= 0)
    {
      return points;
    }
    long dx = points.get(points.size() - 1).x - points.get(0).x;
    long dy = points.get(points.size() - 1).y - points.get(0).y;
    long dz = points.get(points.size() - 1).z - points.get(0).z;
    double distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
    if(distance <= offset)
    {
      Location point = points.get(0);
      points.clear();
      points.add(point);
      return points;
    }
    if(distance >= 1)
    {
      double cut = offset / distance;
      int num = (int) (points.size() * cut + 0.5f);
      for(int i = 1; i <= num && points.size() > 0; i++)
      {
        points.remove(points.size() - 1);
      }
    }
    return points;
  }

  public boolean setSimplePath(Location dest)
  {
    ArrayList<Location> moveList = GeoMove.constructMoveList(getLoc(), dest);
    if(moveList.isEmpty())
    {
      return false;
    }
    _targetRecorder.clear();
    _targetRecorder.add(moveList);
    return true;
  }

  public boolean buildPathTo(int dest_x, int dest_y, int dest_z, int offset, boolean pathFind, boolean _follow)
  {
    int ref = getReflection().getGeoIndex();
    Location dest;
    if(_forestalling && isFollow && getFollowTarget() != null && getFollowTarget().isMoving)
    {
      dest = getIntersectionPoint(getFollowTarget());
    }
    else
    {
      dest = new Location(dest_x, dest_y, dest_z);
    }
    if(isInVehicle() || isVehicle())
    {
      applyOffset(dest, offset);
      return setSimplePath(dest);
    }
    if(isFlying() || isSwimming() || isInWater() || L2World.isWater(dest))
    {
      applyOffset(dest, offset);
      if(GeoEngine.canSeeCoord(this, dest.x, dest.y, dest.z, isFlying()))
      {
        return setSimplePath(dest);
      }
      Location nextloc;
      if(isFlying())
      {
        nextloc = GeoEngine.moveCheckInAir(getX(), getY(), getZ(), dest.x, dest.y, dest.z, getColRadius(), ref);
      }
      else
      {
        nextloc = GeoEngine.moveInWaterCheck(getX(), getY(), getZ(), dest.x, dest.y, dest.z, ref);
      }
      if(nextloc != null && !nextloc.equals(getX(), getY(), getZ()))
      {
        return setSimplePath(nextloc);
      }
      return false;
    }
    ArrayList<Location> moveList = GeoEngine.MoveList(getX(), getY(), getZ(), dest.x, dest.y, ref, true); // onlyFullPath = true - проверяем весь путь до конца
    if(moveList != null) // null - до конца пути дойти нельзя
    {
      if(moveList.isEmpty()) // уже стоим на нужной клетке
      {
        return false;
      }
      applyOffset(moveList, offset);
      if(moveList.isEmpty()) // уже стоим на нужной клетке
      {
        return false;
      }
      _targetRecorder.clear();
      _targetRecorder.add(moveList);
      return true;
    }
    if(!Config.GEODATA_ENABLED)
    {
      applyOffset(dest, offset);
      setSimplePath(dest);
      return true;
    }
    if(pathFind)
    {
      ArrayList<ArrayList<Location>> targets = GeoMove.findMovePath(getX(), getY(), getZ(), dest.clone(), this, true, ref);
      if(!targets.isEmpty())
      {
        moveList = targets.remove(targets.size() - 1);
        applyOffset(moveList, offset);
        if(!moveList.isEmpty())
        {
          targets.add(moveList);
        }
        if(!targets.isEmpty())
        {
          _targetRecorder.clear();
          _targetRecorder.addAll(targets);
          return true;
        }
      }
    }
    if(_follow)
    {
      return false;
    }
    applyOffset(dest, offset);
    moveList = GeoEngine.MoveList(getX(), getY(), getZ(), dest.x, dest.y, ref, false); // onlyFullPath = false - идем до куда можем
    if(moveList != null && !moveList.isEmpty()) // null - нет геодаты, empty - уже стоим на нужной клетке
    {
      _targetRecorder.clear();
      _targetRecorder.add(moveList);
      return true;
    }
    return false;
  }

  public boolean followToCharacter(L2Character target, int offset, boolean forestalling)
  {
    synchronized(_targetRecorder)
    {
      offset = Math.max(offset, 10);
      if(isFollow && target == getFollowTarget() && offset == _offset)
      {
        return true;
      }
      getAI().clearNextAction();
      if(isMovementDisabled() || target == null || isInVehicle() || isSwimming())
      {
        stopMove();
        return false;
      }
      if(Math.abs(getZ() - target.getZ()) > 1000 && !isFlying())
      {
        stopMove();
        sendPacket(Msg.CANNOT_SEE_TARGET);
        return false;
      }
      if(_moveTask != null)
      {
        _moveTask.cancel(false);
        _moveTask = null;
      }
      //TODO сравнить с ним и без
      //broadcastPacket(new StopMove(this));
      isFollow = true;
      setFollowTarget(target);
      _forestalling = forestalling;
      if(buildPathTo(target.getX(), target.getY(), target.getZ(), offset, true, !target.isDoor()))
      {
        movingDestTempPos.set(target.getX(), target.getY(), target.getZ());
      }
      else
      {
        isFollow = false;
        return false;
      }
      _offset = offset;
      moveNext(true);
      return true;
    }
  }

  public boolean moveToLocation(Location loc, int offset, boolean pathfinding)
  {
    return moveToLocation(loc.x, loc.y, loc.z, offset, pathfinding);
  }

  public boolean moveToLocation(int x_dest, int y_dest, int z_dest, int offset, boolean pathfinding)
  {
    synchronized(_targetRecorder)
    {
      offset = Math.max(offset, 0);
      Location dst_geoloc = new Location(x_dest, y_dest, z_dest).world2geo();
      if(isMoving && !isFollow && movingDestTempPos.equals(dst_geoloc))
      {
        sendActionFailed();
        return true;
      }
      getAI().clearNextAction();
      if(isMovementDisabled())
      {
        getAI().setNextAction(nextAction.MOVE, new Location(x_dest, y_dest, z_dest), offset, pathfinding, false);
        sendActionFailed();
        return false;
      }
      isFollow = false;
      if(_moveTask != null)
      {
        _moveTask.cancel(false);
        _moveTask = null;
      }
      //TODO сравнить с ним и без
      //broadcastPacket(new StopMove(this));
      if(isPlayer())
      {
        getAI().changeIntention(AI_INTENTION_ACTIVE, null, null);
      }
      if(buildPathTo(x_dest, y_dest, z_dest, offset, pathfinding, false))
      {
        movingDestTempPos.set(dst_geoloc);
      }
      else
      {
        isMoving = false;
        sendActionFailed();
        return false;
      }
    }
    moveNext(true);
    return true;
  }

  /**
   * должно вызыватся только из synchronized(_targetRecorder)
   *
   * @param firstMove
   */
  public void moveNext(boolean firstMove)
  {
    _previousSpeed = getMoveSpeed();
    if(_previousSpeed <= 0)
    {
      stopMove();
      return;
    }
    if(!firstMove)
    {
      Location dest = destination;
      if(dest != null)
      {
        setLoc(dest, true);
      }
    }
    double distance;
    synchronized(_targetRecorder)
    {
      if(_targetRecorder.isEmpty())
      {
        isMoving = false;
        destination = null;
        if(isFollow)
        {
          isFollow = false;
          ThreadPoolManager.getInstance().executeAi(new NotifyAITask(this, CtrlEvent.EVT_ARRIVED_TARGET, null, null), isPlayable());
        }
        else
        {
          ThreadPoolManager.getInstance().executeAi(new NotifyAITask(this, CtrlEvent.EVT_ARRIVED, null, null), isPlayable());
        }
        validateLocation(isPlayer() ? 2 : 1);
        return;
      }
      moveList = _targetRecorder.remove(0);
      Location begin = moveList.get(0).clone().geo2world();
      Location end = moveList.get(moveList.size() - 1).clone().geo2world();
      destination = end;
      distance = begin.distance3D(end);
      isMoving = true;
    }
    broadcastMove();
    setHeading(calcHeading(destination));
    _startMoveTime = _followTimestamp = System.currentTimeMillis();
    _moveTask = ThreadPoolManager.getInstance().scheduleMove(_moveTaskRunnable.setDist(distance), getMoveTickInterval());
  }

  public int getMoveTickInterval()
  {
    return (int) ((isPlayer() ? 16000 : 32000) / getMoveSpeed());
  }

  private void broadcastMove()
  {
    if(isAirShip())
    {
      broadcastPacket(new ExMoveToLocationAirShip((L2AirShip) this, getLoc(), getDestination()));
    }
    else if(isShip())
    {
      broadcastPacket(new VehicleDeparture((L2Ship) this));
    }
    else
    {
      validateLocation(isPlayer() ? 2 : 1);
      broadcastPacket(new CharMoveToLocation(this));
    }
  }

  /**
   * Останавливает движение и рассылает ValidateLocation
   */
  public void stopMove()
  {
    stopMove(true);
  }

  /**
   * Останавливает движение
   *
   * @param validate - рассылать ли ValidateLocation
   */
  public void stopMove(boolean validate)
  {
    if(isMoving)
    {
      synchronized(_targetRecorder)
      {
        isMoving = false;
        destination = null;
        if(_moveTask != null)
        {
          _moveTask.cancel(false);
          _moveTask = null;
        }
        _targetRecorder.clear();
      }
      broadcastPacket(new StopMove(this));
      if(validate)
      {
        validateLocation(1);
      }
    }
    isFollow = false;
  }

  protected boolean needStatusUpdate()
  {
    if(Config.FORCE_STATUSUPDATE)
    {
      return true;
    }
    if(!isNpc())
    {
      return true;
    }
    double _intervalHpUpdate = getMaxHp() / 352;
    if(_lastHpUpdate == -99999999)
    {
      _lastHpUpdate = -9999999;
      return true;
    }
    if(getCurrentHp() <= 0 || getMaxHp() < 352)
    {
      return true;
    }
    if(_lastHpUpdate + _intervalHpUpdate < getCurrentHp() && getCurrentHp() > _lastHpUpdate)
    {
      _lastHpUpdate = getCurrentHp();
      return true;
    }
    if(_lastHpUpdate - _intervalHpUpdate > getCurrentHp() && getCurrentHp() < _lastHpUpdate)
    {
      _lastHpUpdate = getCurrentHp();
      return true;
    }
    return false;
  }

  public void onDecay()
  {
    decayMe();
    fireMethodInvoked(MethodCollection.onDecay, null);
  }

  @Override
  public void onForcedAttack(L2Player player, boolean shift)
  {
    player.sendPacket(new MyTargetSelected(getObjectId(), player.getLevel() - getLevel()));
    if(!isAttackable(player) || player.isConfused() || player.isBlocked())
    {
      player.sendActionFailed();
      return;
    }
    player.getAI().Attack(this, true, shift);
  }

  public void onHitTimer(L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, boolean shld, boolean unchargeSS)
  {
    if(isAlikeDead())
    {
      sendActionFailed();
      return;
    }
    if(target.isDead() || !isInRange(target, 2000))
    {
      sendActionFailed();
      return;
    }
    if(isPlayable() && target.isPlayable() && isInZoneBattle() != target.isInZoneBattle())
    {
      L2Player player = getPlayer();
      if(player != null)
      {
        player.sendPacket(Msg.INVALID_TARGET);
        player.sendActionFailed();
      }
      return;
    }
    fireMethodInvoked(MethodCollection.OnAttacked, new Object[]
    {
      this,
      target,
      damage,
      crit,
      miss,
      soulshot,
      shld,
      unchargeSS
    });
    // if hitted by a cursed weapon, Cp is reduced to 0, if a cursed weapon is hitted by a Hero, Cp is reduced to 0
    if(!miss && target.isPlayer() && (isCursedWeaponEquipped() || getActiveWeaponInstance() != null && getActiveWeaponInstance().isHeroWeapon() && target.isCursedWeaponEquipped()))
    {
      target.setCurrentCp(0);
    }
    if(target.isStunned() && Formulas.calcStunBreak(crit))
    {
      target.getEffectList().stopEffects(EffectType.Stun);
      target.getEffectList().stopEffects(EffectType.Turner); // stun from bluff
    }
    if(isPlayer())
    {
      if(crit)
      {
        sendPacket(new SystemMessage(SystemMessage.C1_HAD_A_CRITICAL_HIT).addName(this));
      }
      if(miss)
      {
        sendPacket(new SystemMessage(SystemMessage.C1S_ATTACK_WENT_ASTRAY).addName(this));
      }
      else if(!target.isInvul())
      {
        sendPacket(new SystemMessage(SystemMessage.C1_HAS_GIVEN_C2_DAMAGE_OF_S3).addName(this).addName(target).addNumber(damage));
      }
    }
    else if(this instanceof L2Summon)
    {
      ((L2Summon) this).displayHitMessage(target, damage, crit, miss);
    }
    if(target.isPlayer())
    {
      L2Player enemy = (L2Player) target;
      if(shld && damage > 1)
      {
        enemy.sendPacket(Msg.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);
      }
      else if(shld && damage == 1)
      {
        enemy.sendPacket(Msg.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
      }
    }
    // Reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary
    if(!miss && damage > 0)
    {
      target.reduceCurrentHp(damage, this, null, true, true, false, true);
      target.getAI().notifyEvent(CtrlEvent.EVT_ATTACKED, this, damage);
      target.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, this, 0);
      // Скиллы, кастуемые при физ атаке
      if(!target.isDead())
      {
        if(crit)
        {
          useActionSkill(null, this, target, TriggerActionType.CRIT);
        }
        useActionSkill(null, this, target, TriggerActionType.ATTACK);
        useActionSkill(null, target, this, TriggerActionType.UNDER_ATTACK);
        // Проверка на мираж
        if(getTarget() != null && isPlayer())
        {
          if(Rnd.chance(target.calcStat(Stats.CANCEL_TARGET, 0, this, null)))
          {
            setTarget(null);
          }
        }
        // Manage attack or cast break of the target (calculating rate, sending message...)
        if(Formulas.calcCastBreak(target, crit))
        {
          target.abortCast(false);
        }
      }
      if(soulshot && unchargeSS)
      {
        unChargeShots(false);
      }
    }
    if(miss)
    {
      useActionSkill(null, target, this, TriggerActionType.UNDER_MISSED_ATTACK);
    }
    startAttackStanceTask();
    if(checkPvP(target, null))
    {
      startPvPFlag(target);
    }
  }

  private void useActionSkill(L2Skill exclude, L2Character actor, L2Character target, TriggerActionType type)
  {
    if(actor.getTriggerableSkills() == null)
    {
      return;
    }
    ConcurrentLinkedQueue<L2Skill> triggerableSkills = actor.getTriggerableSkills().get(type);
    if(triggerableSkills != null)
    {
      for(L2Skill skill : triggerableSkills)
      {
        if(skill != exclude && Rnd.chance(skill.getChanceForAction(type)))
        {
          useActionSkill(skill, actor, target);
          actor.sendPacket(new SystemMessage(SystemMessage.S1_HAS_SUCCEEDED).addSkillName(skill.getId(), skill.getLevel()));
        }
      }
    }
  }

  private static void useActionSkill(L2Skill skill, L2Character actor, L2Character target)
  {
    L2Character aimingTarget = skill.getAimingTarget(actor, target);
    if(skill.checkCondition(actor, aimingTarget, false, false, true))
    {
      GArray<L2Character> targets = skill.getTargets(actor, aimingTarget, false);
      L2Skill.broadcastUseAnimation(skill, actor, targets);
      Formulas.calcSkillMastery(skill, actor);
      actor.callSkill(skill, targets, false);
    }
  }

  public void onMagicUseTimer(L2Character aimingTarget, L2Skill skill, boolean forceUse)
  {
    if(skill == null)
    {
      onCastEndTime();
      sendPacket(Msg.ActionFail);
      return;
    }
    _castInterruptTime = 0;
    if(skill.isUsingWhileCasting())
    {
      aimingTarget.getEffectList().stopEffect(skill.getId());
      onCastEndTime();
      return;
    }
    if(!skill.isOffensive() && getAggressionTarget() != null)
    {
      forceUse = true;
    }
    if(!skill.checkCondition(this, aimingTarget, forceUse, false, false))
    {
      onCastEndTime();
      return;
    }
    if(skill.getCastRange() < 32767 && skill.getSkillType() != SkillType.TAKECASTLE && skill.getSkillType() != SkillType.TAKEFORTRESS && !GeoEngine.canSeeTarget(this, aimingTarget, isFlying()))
    {
      sendPacket(Msg.CANNOT_SEE_TARGET);
      broadcastPacket(new MagicSkillCanceled(_objectId));
      onCastEndTime();
      return;
    }
    GArray<L2Character> targets = skill.getTargets(this, aimingTarget, forceUse);
    int hpConsume = skill.getHpConsume();
    if(hpConsume > 0)
    {
      setCurrentHp(Math.max(0, _currentHp - hpConsume), false);
    }
    double mpConsume2 = skill.getMpConsume2();
    if(mpConsume2 > 0)
    {
      if(skill.isMusic())
      {
        double inc = mpConsume2 / 2;
        double add = 0;
        for(L2Effect e : getEffectList().getAllEffects())
        {
          if(e.getSkill().getId() != skill.getId() && e.getSkill().isMusic() && e.getTimeLeft() > 30000)
          {
            add += inc;
          }
        }
        mpConsume2 += add;
        mpConsume2 = calcStat(Stats.MP_DANCE_SKILL_CONSUME, mpConsume2, aimingTarget, skill);
      }
      else if(skill.isMagic())
      {
        mpConsume2 = calcStat(Stats.MP_MAGIC_SKILL_CONSUME, mpConsume2, aimingTarget, skill);
      }
      else
      {
        mpConsume2 = calcStat(Stats.MP_PHYSICAL_SKILL_CONSUME, mpConsume2, aimingTarget, skill);
      }
      if(_currentMp < mpConsume2 && isPlayable())
      {
        sendPacket(Msg.NOT_ENOUGH_MP);
        onCastEndTime();
        return;
      }
      reduceCurrentMp(mpConsume2, null);
    }
    callSkill(skill, targets, true);
    if(skill.getNumCharges() > 0)
    {
      setIncreasedForce(getIncreasedForce() - skill.getNumCharges());
    }
    if(skill.isSoulBoost())
    {
      setConsumedSouls(getConsumedSouls() - Math.min(getConsumedSouls(), 5), null);
    }
    else if(skill.getSoulsConsume() > 0)
    {
      setConsumedSouls(getConsumedSouls() - skill.getSoulsConsume(), null);
    }
    Location flyLoc;
    switch(skill.getFlyType())
    {
      case THROW_UP:
      case THROW_HORIZONTAL:
        for(L2Character target : targets)
        {
          target.setHeading(this, false);
          flyLoc = getFlyLocation(null, skill);
          target.setLoc(flyLoc);
          broadcastPacket(new FlyToLocation(target, flyLoc, skill.getFlyType()));
        }
        break;
      case DUMMY:
      case CHARGE:
        flyLoc = _flyLoc;
        _flyLoc = null;
        if(flyLoc != null)
        {
          setLoc(flyLoc);
          validateLocation(1);
        }
        break;
    }
    if(isPlayer() && getTarget() != null && skill.isOffensive())
    {
      for(L2Character target : targets)
      {
        if(Rnd.chance(target.calcStat(Stats.CANCEL_TARGET, 0, aimingTarget, skill)))
        {
          clearCastVars();
          getAI().notifyEvent(EVT_FORGET_OBJECT, target);
          return;
        }
      }
    }
    if(_scheduledCastCount > 0)
    {
      _scheduledCastCount--;
      _skillLaunchedTask = ThreadPoolManager.getInstance().scheduleAi(new MagicLaunchedTask(this, forceUse), _scheduledCastInterval, isPlayable());
      _skillTask = ThreadPoolManager.getInstance().scheduleAi(new MagicUseTask(this, forceUse), _scheduledCastInterval, isPlayable());
      return;
    }
    int skillCoolTime = Formulas.calcMAtkSpd(this, skill, skill.getCoolTime());
    if(skillCoolTime > 0)
    {
      ThreadPoolManager.getInstance().scheduleAi(new CastEndTimeTask(this), skillCoolTime, isPlayable());
    }
    else
    {
      onCastEndTime();
    }
  }

  public void onCastEndTime()
  {
    clearCastVars();
    getAI().notifyEvent(CtrlEvent.EVT_FINISH_CASTING, null, null);
  }

  public void clearCastVars()
  {
    _castingSkill = null;
    _skillTask = null;
    _skillLaunchedTask = null;
    _flyLoc = null;
  }

  public void reduceCurrentHp(double i, L2Character attacker, L2Skill skill, boolean awake, boolean standUp, boolean directHp, boolean canReflect)
  {
    fireMethodInvoked(MethodCollection.ReduceCurrentHp, new Object[]
    {
      i,
      attacker,
      skill,
      awake,
      standUp,
      directHp,
    });
    if(attacker == null || isDead() || attacker.isDead())
    {
      return;
    }
    if(isInvul() && attacker != this)
    {
      attacker.sendPacket(Msg.THE_ATTACK_HAS_BEEN_BLOCKED);
      return;
    }
    // 5182 = Blessing of protection, работает если разница уровней больше 10 и не в зоне осады
    if(attacker.isPlayer() && Math.abs(attacker.getLevel() - getLevel()) > 10)
    {
      // ПК не может нанести урон чару с блессингом
      if(attacker.getKarma() > 0 && getEffectList().getEffectsBySkillId(5182) != null && !isInZone(ZoneType.Siege))
      {
        return;
      }
      // чар с блессингом не может нанести урон ПК
      if(getKarma() > 0 && attacker.getEffectList().getEffectsBySkillId(5182) != null && !attacker.isInZone(ZoneType.Siege))
      {
        return;
      }
    }
    if(awake && isSleeping())
    {
      getEffectList().stopEffects(EffectType.Sleep);
    }
    if(isMeditated() && attacker != this)
    {
      L2Effect effect = getEffectList().getEffectByType(EffectType.Meditation);
      if(effect != null)
      {
        GArray<L2Effect> effects = getEffectList().getEffectsBySkill(effect.getSkill());
        if(effects != null)
        {
          for(L2Effect ef : effects)
          {
            if(ef.getEffectType() != EffectType.Debuff)
            {
              ef.exit();
            }
          }
        }
      }
    }
    if(standUp && isPlayer())
    {
      standUp();
      if(isFakeDeath())
      {
        L2Effect fakeDeath = getEffectList().getEffectByType(EffectType.FakeDeath);
        if(fakeDeath == null)
        {
          stopFakeDeath();
        }
        else if(fakeDeath.getTime() > 2000)
        {
          getEffectList().stopEffects(EffectType.FakeDeath);
        }
      }
    }
    if(attacker != this)
    {
      startAttackStanceTask();
      if(isInvisible() && getEffectList().getEffectByType(EffectType.Invisible) != null)
      {
        getEffectList().stopEffects(EffectType.Invisible);
      }
    }
    if(canReflect && attacker.absorbAndReflect(this, skill, i))
    {
      return;
    }
    if(attacker.isPlayable())
    {
      L2Playable pAttacker = (L2Playable) attacker;
      // Flag the attacker if it's a L2Player outside a PvP area
      if(!isDead() && pAttacker.checkPvP(this, null))
      {
        pAttacker.startPvPFlag(this);
      }
      if(isMonster() && skill != null && skill.isOverhit())
      {
        // Calculate the over-hit damage
        // Ex: mob had 10 HP left, over-hit skill did 50 damage total, over-hit damage is 40
        double overhitDmg = (_currentHp - i) * -1;
        if(overhitDmg <= 0)
        {
          setOverhitDamage(0);
          setOverhitAttacker(null);
        }
        else
        {
          setOverhitDamage(overhitDmg);
          setOverhitAttacker(attacker);
        }
      }
      double ii;
      if(!directHp && _currentCp > 0)
      {
        i = _currentCp - i;
        ii = i;
        if(ii < 0)
        {
          ii *= -1;
        }
        if(i < 0)
        {
          i = 0;
        }
        setCurrentCp(i);
      }
      else
      {
        ii = i;
      }
      if(_currentCp == 0 || directHp)
      {
        ii = _currentHp - ii;
        if(ii < 0)
        {
          ii = 0;
        }
        if(isNpc())
        {
          pAttacker.addDamage((L2NpcInstance) this, (int) (_currentHp - ii));
        }
        if(!onDieTrigger && ii < 0.5 && _skillsOnAction != null)
        {
          onDieTrigger = true;
          ConcurrentLinkedQueue<L2Skill> SkillsOnDie = _skillsOnAction.get(TriggerActionType.DIE);
          if(SkillsOnDie != null)
          {
            for(L2Skill sk : SkillsOnDie)
            {
              if(Rnd.chance(sk.getChanceForAction(TriggerActionType.DIE)))
              {
                useActionSkill(sk, this, attacker);
              }
            }
          }
          onDieTrigger = false;
        }
        setCurrentHp(ii, false);
      }
    }
    else
    {
      if(_currentHp - i < 0.5)
      {
        useActionSkill(null, this, this, TriggerActionType.DIE);
      }
      setCurrentHp(Math.max(_currentHp - i, 0), false);
    }
    if(isDead())
    {
      doDie(attacker);
    }
  }
  private boolean onDieTrigger = false;

  public void reduceCurrentMp(double i, L2Character attacker)
  {
    if(attacker != null && attacker != this)
    {
      if(isSleeping())
      {
        getEffectList().stopEffects(EffectType.Sleep);
      }
      if(isMeditated())
      {
        L2Effect effect = getEffectList().getEffectByType(EffectType.Meditation);
        if(effect != null)
        {
          GArray<L2Effect> effects = getEffectList().getEffectsBySkill(effect.getSkill());
          if(effects != null)
          {
            for(L2Effect ef : effects)
            {
              if(ef.getEffectType() != EffectType.Debuff)
              {
                ef.exit();
              }
            }
          }
        }
      }
    }
    if(isInvul() && attacker != null && attacker != this)
    {
      attacker.sendPacket(Msg.THE_ATTACK_HAS_BEEN_BLOCKED);
      return;
    }
    // 5182 = Blessing of protection, работает если разница уровней больше 10 и не в зоне осады
    if(attacker != null && attacker.isPlayer() && Math.abs(attacker.getLevel() - getLevel()) > 10)
    {
      // ПК не может нанести урон чару с блессингом
      if(attacker.getKarma() > 0 && getEffectList().getEffectsBySkillId(5182) != null && !isInZone(ZoneType.Siege))
      {
        return;
      }
      // чар с блессингом не может нанести урон ПК
      if(getKarma() > 0 && attacker.getEffectList().getEffectsBySkillId(5182) != null && !attacker.isInZone(ZoneType.Siege))
      {
        return;
      }
    }
    i = _currentMp - i;
    if(i < 0)
    {
      i = 0;
    }
    setCurrentMp(i);
    if(attacker != null && attacker != this)
    {
      startAttackStanceTask();
    }
  }

  public double relativeSpeed(L2Object target)
  {
    return getMoveSpeed() - target.getMoveSpeed() * Math.cos(headingToRadians(getHeading()) - headingToRadians(target.getHeading()));
  }

  public void removeAllSkills()
  {
    for(L2Skill s : getAllSkillsArray())
    {
      removeSkill(s);
    }
  }

  public void removeBlockStats(GArray<Stats> stats)
  {
    if(_blockedStats != null)
    {
      _blockedStats.removeAll(stats);
      if(_blockedStats.isEmpty())
      {
        _blockedStats = null;
      }
    }
  }

  public L2Skill removeSkill(L2Skill skill)
  {
    if(skill == null)
    {
      return null;
    }
    return removeSkillById(skill.getId());
  }

  public L2Skill removeSkillById(Integer id)
  {
    // Remove the skill from the L2Character _skills
    L2Skill oldSkill = _skills.remove(id);
    removeTriggerableSkill(id);
    // Remove all its Func objects from the L2Character calculator set
    if(oldSkill != null)
    {
      removeStatsOwner(oldSkill);
      if(Config.ALT_DELETE_SA_BUFFS && (oldSkill.isItemSkill() || oldSkill.isHandler()))
      {
        // Завершаем все эффекты, принадлежащие старому скиллу
        GArray<L2Effect> effects = getEffectList().getEffectsBySkill(oldSkill);
        if(effects != null)
        {
          for(L2Effect effect : effects)
          {
            effect.exit();
          }
        }
        // И с петов тоже
        L2Summon pet = getPet();
        if(pet != null)
        {
          effects = pet.getEffectList().getEffectsBySkill(oldSkill);
          if(effects != null)
          {
            for(L2Effect effect : effects)
            {
              effect.exit();
            }
          }
        }
      }
    }
    return oldSkill;
  }

  public ConcurrentHashMap<TriggerActionType, ConcurrentLinkedQueue<L2Skill>> getTriggerableSkills()
  {
    return _skillsOnAction;
  }

  public void addTriggerableSkill(L2Skill newSkill)
  {
    for(TriggerActionType e : newSkill.getTriggerActions().keySet())
    {
      if(_skillsOnAction == null)
      {
        _skillsOnAction = new ConcurrentHashMap<TriggerActionType, ConcurrentLinkedQueue<L2Skill>>();
      }
      ConcurrentLinkedQueue<L2Skill> hs = _skillsOnAction.get(e);
      if(hs == null)
      {
        hs = new ConcurrentLinkedQueue<L2Skill>();
        _skillsOnAction.put(e, hs);
      }
      hs.add(newSkill);
      if(e == TriggerActionType.ADD)
      {
        if(Rnd.chance(newSkill.getChanceForAction(TriggerActionType.ADD)))
        {
          useActionSkill(newSkill, this, this);
        }
      }
    }
  }

  public void removeTriggerableSkill(int id)
  {
    if(_skillsOnAction != null)
    {
      for(ConcurrentLinkedQueue<L2Skill> s : _skillsOnAction.values())
      {
        for(L2Skill sk : s)
        {
          if(sk != null && sk.getId() == id)
          {
            s.remove(sk);
          }
        }
      }
    }
  }

  public final synchronized void removeStatFunc(Func f)
  {
    if(f == null)
    {
      return;
    }
    int stat = f._stat.ordinal();
    if(_calculators.length > stat && _calculators[stat] != null)
    {
      _calculators[stat].removeFunc(f);
    }
  }

  public final synchronized void removeStatFuncs(Func[] funcs)
  {
    for(Func f : funcs)
    {
      removeStatFunc(f);
    }
  }

  public final void removeStatsOwner(Object owner)
  {
    for(int i = 0; i < _calculators.length; i++)
    {
      if(_calculators[i] != null)
      {
        _calculators[i].removeOwner(owner);
      }
    }
  }

  public void sendActionFailed()
  {
    sendPacket(Msg.ActionFail);
  }

  @Override
  public boolean hasAI()
  {
    return _ai != null;
  }

  @Override
  public L2CharacterAI getAI()
  {
    if(_ai == null)
    {
      _ai = new L2CharacterAI(this);
    }
    return _ai;
  }

  public L2CharacterAI setAI(L2CharacterAI new_ai)
  {
    if(new_ai == null)
    {
      return _ai = null;
    }
    if(_ai != null)
    {
      _ai.stopAITask();
    }
    _ai = new_ai;
    return _ai;
  }

  public final void setCurrentHp(double newHp, boolean canRessurect)
  {
    newHp = Math.min(getMaxHp(), Math.max(0, newHp));
    if(_currentHp == newHp)
    {
      return;
    }
    if(newHp >= 0.5 && isDead() && !canRessurect)
    {
      return;
    }
    double hpStart = _currentHp;
    dieLock.lock();
    _currentHp = newHp;
    if(!isDead())
    {
      _killedAlready = false;
      _killedAlreadyPlayer = false;
      _killedAlreadyPet = false;
    }
    dieLock.unlock();
    startRegeneration();
    firePropertyChanged(PropertyCollection.HitPoints, hpStart, _currentHp);
    checkHpMessages(hpStart, newHp);
    broadcastStatusUpdate();
  }

  public final void setCurrentMp(double newMp)
  {
    newMp = Math.min(getMaxMp(), Math.max(0, newMp));
    if(_currentMp == newMp)
    {
      return;
    }
    _currentMp = newMp;
    startRegeneration();
    broadcastStatusUpdate();
  }

  public final void setCurrentCp(double newCp)
  {
    if(!isPlayer())
    {
      return;
    }
    newCp = Math.min(getMaxCp(), Math.max(0, newCp));
    if(_currentCp == newCp)
    {
      return;
    }
    _currentCp = newCp;
    startRegeneration();
    broadcastStatusUpdate();
  }

  public void setCurrentHpMp(double newHp, double newMp, boolean canRessurect)
  {
    newHp = Math.min(getMaxHp(), Math.max(0, newHp));
    newMp = Math.min(getMaxMp(), Math.max(0, newMp));
    if(_currentHp == newHp && _currentMp == newMp)
    {
      return;
    }
    if(newHp >= 0.5 && isDead() && !canRessurect)
    {
      return;
    }
    double hpStart = _currentHp;
    dieLock.lock();
    _currentHp = newHp;
    _currentMp = newMp;
    if(!isDead())
    {
      _killedAlready = false;
    }
    dieLock.unlock();
    startRegeneration();
    firePropertyChanged(PropertyCollection.HitPoints, hpStart, _currentHp);
    checkHpMessages(hpStart, newHp);
    broadcastStatusUpdate();
  }

  public void setCurrentHpMp(double newHp, double newMp)
  {
    setCurrentHpMp(newHp, newMp, false);
  }

  public final void setFlying(boolean mode)
  {
    _flying = mode;
  }

  @Override
  public final int getHeading()
  {
    if(isAttackingNow() || isCastingNow())
    {
      L2CharacterAI ai = getAI();
      if(ai != null)
      {
        L2Character target = ai.getAttackTarget();
        if(target != null)
        {
          setHeading(target, true);
        }
      }
    }
    return _heading;
  }

  @Override
  public void setHeading(int heading)
  {
    if(heading < 0)
    {
      heading = heading + 1 + Integer.MAX_VALUE & 0xFFFF;
    }
    else if(heading > 0xFFFF)
    {
      heading &= 0xFFFF;
    }
    _heading = heading;
  }

  public final void setHeading(L2Character target, boolean toChar)
  {
    if(target == null || target == this)
    {
      return;
    }
    setHeading(new Location(target.getX(), target.getY(), target.getZ()), toChar); // не менять на getLoc() иначе будет цикл из за getHeading() внутри getLoc()
  }

  public final void setHeading(Location target, boolean toChar)
  {
    setHeading((int) (Math.atan2(getY() - target.y, getX() - target.x) * HEADINGS_IN_PI) + (toChar ? 32768 : 0));
  }

  public final void setIsBlessedByNoblesse(boolean value)
  {
    if(value)
    {
      _isBlessedByNoblesse++;
    }
    else
    {
      _isBlessedByNoblesse--;
    }
  }

  public final void setIsSalvation(boolean value)
  {
    if(value)
    {
      _isSalvation++;
    }
    else
    {
      _isSalvation--;
    }
  }

  public final void setBuffImmunity(boolean value)
  {
    if(value)
    {
      _buffImmunity++;
    }
    else
    {
      _buffImmunity--;
    }
  }

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

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

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

  public final void setName(String name)
  {
    _name = name;
  }

  public L2Character getCastingTarget()
  {
    return L2ObjectsStorage.getAsCharacter(castingTargetStoreId);
  }

  public void setCastingTarget(L2Character target)
  {
    castingTargetStoreId = target == null ? 0 : target.getStoredId();
  }

  public final void setRiding(boolean mode)
  {
    _riding = mode;
  }

  public final void setRunning()
  {
    if(!_running)
    {
      _running = true;
      broadcastPacket(new ChangeMoveType(this));
    }
  }

  public void setSkillMastery(Integer skill, byte mastery)
  {
    if(_skillMastery == null)
    {
      _skillMastery = new HashMap<Integer, Byte>();
    }
    _skillMastery.put(skill, mastery);
  }
  private L2Character _aggressionTarget = null;

  public void setAggressionTarget(L2Character target)
  {
    _aggressionTarget = target;
  }

  public L2Character getAggressionTarget()
  {
    return _aggressionTarget;
  }

  public void setTarget(L2Object object)
  {
    if(object != null && !object.isVisible())
    {
      object = null;
    }
    if(object == null)
    {
      if(isAttackingNow() && getAI().getAttackTarget() == getTarget())
      {
        abortAttack(false, true);
      }
      if(isCastingNow() && getAI().getAttackTarget() == getTarget())
      {
        abortCast(false);
      }
    }
    targetStoreId = object == null ? 0 : object.getStoredId();
  }

  protected void setTemplate(L2CharTemplate template)
  {
    _template = template;
  }

  public void setTitle(String title)
  {
    _title = title;
  }

  public void setWalking()
  {
    if(_running)
    {
      _running = false;
      broadcastPacket(new ChangeMoveType(this));
    }
  }

  public void startAbnormalEffect(AbnormalEffect ae)
  {
    if(ae == AbnormalEffect.NULL)
    {
      _abnormalEffects = AbnormalEffect.NULL.getMask();
      _abnormalEffects2 = AbnormalEffect.NULL.getMask();
    }
    else if(ae.isSpecial())
    {
      _abnormalEffects2 |= ae.getMask();
    }
    else
    {
      _abnormalEffects |= ae.getMask();
    }
    updateAbnormalEffect();
  }

  @Override
  public void startAttackStanceTask()
  {
    if(System.currentTimeMillis() < _stanceInited + 10000)
    {
      return;
    }
    _stanceInited = System.currentTimeMillis();
    // Бесконечной рекурсии не будет, потому что выше проверка на _stanceInited
    if(this instanceof L2Summon && getPlayer() != null)
    {
      getPlayer().startAttackStanceTask();
    }
    else if(isPlayer() && getPet() != null)
    {
      getPet().startAttackStanceTask();
    }
    if(_stanceTask != null)
    {
      _stanceTask.cancel(false);
    }
    else
    {
      broadcastPacket(new AutoAttackStart(getObjectId()));
    }
    _stanceTask = ThreadPoolManager.getInstance().scheduleAi(new CancelAttackStanceTask(this), 15000, isPlayable());
  }

  public void stopAttackStanceTask()
  {
    broadcastPacket(new AutoAttackStop(getObjectId()));
    _stanceTask.cancel(false);
    _stanceTask = null;
  }

  public void startRegeneration()
  {
    if(!isDead() && (_currentHp < getMaxHp() || _currentMp < getMaxMp() || _currentCp < getMaxCp()))
    {
      regenLock.lock();
      try
      {
        long tick = RegenTaskManager.getInstance().getTick();
        if(_regenTick >= tick)
        {
          return;
        }
        _regenTick = tick;
      }
      finally
      {
        regenLock.unlock();
      }
      RegenTaskManager.getInstance().addRegenTask(this);
    }
  }
  public long _regenTick;

  public void doRegen()
  {
    if(isDead() || isHealBlocked(false))
    {
      return;
    }
    try
    {
      double addHp = 0;
      double addMp = 0;
      int maxHp = getMaxHp();
      int maxMp = getMaxMp();
      if(_currentHp < maxHp)
      {
        addHp += Formulas.calcHpRegen(this);
      }
      if(_currentMp < maxMp)
      {
        addMp += Formulas.calcMpRegen(this);
      }
      // Added regen bonus when character is sitting
      if(isPlayer() && Config.REGEN_SIT_WAIT)
      {
        L2Player pl = (L2Player) this;
        if(pl.isSitting())
        {
          pl.updateWaitSitTime();
          if(pl.getWaitSitTime() > 5)
          {
            addHp += pl.getWaitSitTime();
            addMp += pl.getWaitSitTime();
          }
        }
      }
      else if(isRaid())
      {
        addHp *= Config.RATE_RAID_REGEN;
        addMp *= Config.RATE_RAID_REGEN;
      }
      double hpStart = _currentHp;
      _currentHp += Math.max(0, Math.min(addHp, calcStat(Stats.HP_LIMIT, null, null) * maxHp / 100. - _currentHp));
      _currentMp += Math.max(0, Math.min(addMp, calcStat(Stats.MP_LIMIT, null, null) * maxMp / 100. - _currentMp));
      _currentHp = Math.min(maxHp, _currentHp);
      _currentMp = Math.min(maxMp, _currentMp);
      if(isPlayer())
      {
        int maxCp = getMaxCp();
        _currentCp += Math.max(0, Math.min(Formulas.calcCpRegen(L2Character.this), calcStat(Stats.CP_LIMIT, null, null) * maxCp / 100. - _currentCp));
        _currentCp = Math.min(maxCp, _currentCp);
      }
      firePropertyChanged(PropertyCollection.HitPoints, hpStart, _currentHp);
      checkHpMessages(hpStart, _currentHp);
      broadcastStatusUpdate();
      startRegeneration();
    }
    catch(Throwable e)
    {
      e.printStackTrace();
    }
  }

  public void stopAbnormalEffect(AbnormalEffect ae)
  {
    if(ae.isSpecial())
    {
      _abnormalEffects2 &= ~ae.getMask();
    }
    else
    {
      _abnormalEffects &= ~ae.getMask();
    }
    updateAbnormalEffect();
  }

  public void block()
  {
    _blocked = true;
  }

  public void unblock()
  {
    _blocked = false;
  }

  public void startConfused()
  {
    if(!_confused)
    {
      _confused = true;
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopConfused()
  {
    if(_confused)
    {
      _confused = false;
      updateAbnormalEffect();
      abortAttack(true, true);
      abortCast(true);
      stopMove();
      getAI().setAttackTarget(null);
    }
  }

  public void startFakeDeath()
  {
    if(!_fakeDeath)
    {
      if(isPlayer())
      {
        this.clearHateList(true);
      }
      _fakeDeath = true;
      getAI().notifyEvent(CtrlEvent.EVT_FAKE_DEATH, null, null);
      broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_START_FAKEDEATH));
      updateAbnormalEffect();
    }
  }

  public void stopFakeDeath()
  {
    if(_fakeDeath)
    {
      _fakeDeath = false;
      broadcastPacket(new ChangeWaitType(this, ChangeWaitType.WT_STOP_FAKEDEATH));
      updateAbnormalEffect();
      broadcastPacket(new Revive(this));
    }
  }

  public void breakFakeDeath()
  {
    getEffectList().stopAllSkillEffects(EffectType.FakeDeath);
    stopFakeDeath();
  }

  public void startFear()
  {
    if(!_afraid)
    {
      _afraid = true;
      abortAttack(true, true);
      abortCast(true);
      sendActionFailed();
      stopMove();
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopFear()
  {
    if(_afraid)
    {
      _afraid = false;
      updateAbnormalEffect();
    }
  }

  public void startMuted()
  {
    if(!_muted)
    {
      _muted = true;
      if(getCastingSkill() != null && getCastingSkill().isMagic())
      {
        abortCast(true);
      }
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopMuted()
  {
    if(_muted)
    {
      _muted = false;
      updateAbnormalEffect();
    }
  }

  public void startPMuted()
  {
    if(!_pmuted)
    {
      _pmuted = true;
      if(getCastingSkill() != null && !getCastingSkill().isMagic())
      {
        abortCast(true);
      }
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopPMuted()
  {
    if(_pmuted)
    {
      _pmuted = false;
      updateAbnormalEffect();
    }
  }

  public void startAMuted()
  {
    if(!_amuted)
    {
      _amuted = true;
      abortCast(true);
      abortAttack(true, true);
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopAMuted()
  {
    if(_amuted)
    {
      _amuted = false;
      updateAbnormalEffect();
    }
  }

  public void startRooted()
  {
    if(!_rooted)
    {
      _rooted = true;
      stopMove();
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopRooting()
  {
    if(_rooted)
    {
      _rooted = false;
      updateAbnormalEffect();
    }
  }

  public void startSleeping()
  {
    if(!_sleeping)
    {
      _sleeping = true;
      abortAttack(true, true);
      abortCast(true);
      sendActionFailed();
      stopMove();
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopSleeping()
  {
    if(_sleeping)
    {
      _sleeping = false;
      updateAbnormalEffect();
    }
  }

  public void startStunning()
  {
    if(!_stunned)
    {
      _stunned = true;
      abortAttack(true, true);
      abortCast(true);
      sendActionFailed();
      stopMove();
      startAttackStanceTask();
      updateAbnormalEffect();
    }
  }

  public void stopStunning()
  {
    if(_stunned)
    {
      _stunned = false;
      updateAbnormalEffect();
    }
  }

  public void setMeditated(boolean meditated)
  {
    _meditated = meditated;
  }

  public void setParalyzed(boolean paralyzed)
  {
    if(_paralyzed != paralyzed)
    {
      _paralyzed = paralyzed;
      if(paralyzed)
      {
        abortAttack(true, true);
        abortCast(true);
        sendActionFailed();
        stopMove();
      }
    }
  }

  public void setImobilised(boolean imobilised)
  {
    if(_imobilised != imobilised)
    {
      _imobilised = imobilised;
      if(imobilised)
      {
        stopMove();
      }
      updateAbnormalEffect();
    }
  }

  public void setHealBlocked(boolean value)
  {
    _healBlocked = value;
  }

  /**
   * if True, the L2Player can't take more item
   */
  public void setOverloaded(boolean overloaded)
  {
    _overloaded = overloaded;
  }

  public boolean isConfused()
  {
    return _confused;
  }

  public boolean isFakeDeath()
  {
    return _fakeDeath;
  }

  public boolean isAfraid()
  {
    return _afraid;
  }

  public boolean isBlocked()
  {
    return _blocked;
  }

  public boolean isMuted(L2Skill skill)
  {
    if(skill == null || skill.isNotAffectedByMute())
    {
      return false;
    }
    return _muted && skill.isMagic() || _pmuted && !skill.isMagic();
  }

  public boolean isPMuted()
  {
    return _pmuted;
  }

  public boolean isMMuted()
  {
    return _muted;
  }

  public boolean isAMuted()
  {
    return _amuted;
  }

  public boolean isRooted()
  {
    return _rooted;
  }

  public boolean isSleeping()
  {
    return _sleeping;
  }

  public boolean isStunned()
  {
    return _stunned;
  }

  public boolean isMeditated()
  {
    return _meditated;
  }

  public boolean isParalyzed()
  {
    return _paralyzed;
  }

  public boolean isImobilised()
  {
    return _imobilised || getRunSpeed() < 1;
  }

  public boolean isHealBlocked(boolean checkInvul)
  {
    return _healBlocked || checkInvul && _isInvul;
  }

  public boolean isCastingNow()
  {
    return _skillTask != null;
  }

  public boolean isMovementDisabled()
  {
    return isSitting() || isStunned() || isRooted() || isSleeping() || isParalyzed() || isImobilised() || isAlikeDead() || isAttackingNow() || isCastingNow() || _overloaded || _fishing || isPlayer() && (isTeleporting() || ((L2Player) this).isLogoutStarted());
  }

  public boolean isActionsDisabled()
  {
    return isStunned() || isSleeping() || isParalyzed() || isAttackingNow() || isCastingNow() || isAlikeDead() || isPlayer() && (isTeleporting() || ((L2Player) this).isLogoutStarted());
  }

  public boolean isPotionsDisabled()
  {
    return isStunned() || isSleeping() || isParalyzed() || isAlikeDead() || isPlayer() && (isTeleporting() || ((L2Player) this).isLogoutStarted());
  }

  public boolean isToggleDisabled()
  {
    return isStunned() || isSleeping() || isParalyzed() || isPlayer() && (isTeleporting() || ((L2Player) this).isLogoutStarted());
  }

  public final boolean isAttackingDisabled()
  {
    return _attackReuseEndTime > System.currentTimeMillis();
  }

  public boolean isOutOfControl()
  {
    return isConfused() || isAfraid() || isBlocked() || isPlayer() && (isTeleporting() || ((L2Player) this).isLogoutStarted());
  }

  public void teleToLocation(Location loc)
  {
    teleToLocation(loc.x, loc.y, loc.z, getReflection().getId());
  }

  public void teleToLocation(Location loc, long ref)
  {
    teleToLocation(loc.x, loc.y, loc.z, ref);
  }

  public void teleToLocation(int x, int y, int z)
  {
    teleToLocation(x, y, z, getReflection().getId());
  }

  public void teleToLocation(int x, int y, int z, long ref)
  {
    if(isFakeDeath())
    {
      breakFakeDeath();
    }
    if(isTeleporting())
    {
      return;
    }
    abortCast(true);
    if(isPlayable())
    {
      clearHateList(true);
    }
    if(!isVehicle() && !isFlying() && !L2World.isWater(new Location(x, y, z)))
    {
      z = GeoEngine.getHeight(x, y, z, getReflection().getGeoIndex());
    }
    if(isPlayer() && DimensionalRiftManager.getInstance().checkIfInRiftZone(getLoc(), true))
    {
      L2Player player = (L2Player) this;
      if(player.isInParty() && player.getParty().isInDimensionalRift())
      {
        Location newCoords = DimensionalRiftManager.getInstance().getRoom(0, 0).getTeleportCoords();
        x = newCoords.x;
        y = newCoords.y;
        z = newCoords.z;
        player.getParty().getDimensionalRift().usedTeleport(player);
      }
    }
    setTarget(null);
    if(isPlayer())
    {
      L2Player player = (L2Player) this;
      if(player.isLogoutStarted())
      {
        return;
      }
      setIsTeleporting(true);
      decayMe();
      setXYZInvisible(x, y, z);
      if(ref != getReflection().getId())
      {
        setReflection(ref);
      }
      // Нужно при телепорте с более высокой точки на более низкую, иначе наносится вред от "падения"
      setLastClientPosition(null);
      setLastServerPosition(null);
      player.sendPacket(new TeleportToLocation(player, x, y, z));
    }
    else
    {
      setXYZ(x, y, z);
      broadcastPacket(new TeleportToLocation(this, x, y, z));
    }
  }

  public void onTeleported()
  {
    L2Player activeChar = (L2Player) this;
    if(activeChar.isFakeDeath())
    {
      activeChar.breakFakeDeath();
    }
    if(activeChar.isInVehicle())
    {
      activeChar.setXYZInvisible(activeChar.getVehicle().getLoc());
    }
    // 2 секунд после телепорта на персонажа не агрятся мобы
    activeChar.setNonAggroTime(System.currentTimeMillis() + 2000);
    spawnMe();
    setLastClientPosition(getLoc());
    setLastServerPosition(getLoc());
    setIsTeleporting(false);
    if(_isPendingRevive)
    {
      doRevive();
    }
    if(activeChar.getTrainedBeast() != null)
    {
      activeChar.getTrainedBeast().setXYZ(getX() + Rnd.get(-100, 100), getY() + Rnd.get(-100, 100), getZ());
    }
    activeChar.checkWaterState();
    sendActionFailed();
    getAI().notifyEvent(CtrlEvent.EVT_TELEPORTED);
    activeChar.sendUserInfo(true);
    if(activeChar.getPet() != null)
    {
      activeChar.getPet().teleportToOwner();
    }
  }

  public void teleToClosestTown()
  {
    teleToLocation(MapRegion.getTeleToClosestTown(this), 0);
  }

  public void teleToSecondClosestTown()
  {
    teleToLocation(MapRegion.getTeleToSecondClosestTown(this), 0);
  }

  public void teleToCastle()
  {
    teleToLocation(MapRegion.getTeleToCastle(this), 0);
  }

  public void teleToFortress()
  {
    teleToLocation(MapRegion.getTeleToFortress(this), 0);
  }

  public void teleToClanhall()
  {
    teleToLocation(MapRegion.getTeleToClanHall(this), 0);
  }

  public void teleToHeadquarter()
  {
    teleToLocation(MapRegion.getTeleToHeadquarter(this), 0);
  }

  public void sendMessage(CustomMessage message)
  {
    sendMessage(message.toString());
  }
  private long _nonAggroTime;

  public long getNonAggroTime()
  {
    return _nonAggroTime;
  }

  public void setNonAggroTime(long time)
  {
    _nonAggroTime = time;
  }

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

  @Override
  public float getColRadius()
  {
    return getTemplate().collisionRadius;
  }

  @Override
  public float getColHeight()
  {
    return getTemplate().collisionHeight;
  }

  public boolean canAttackCharacter(L2Character target)
  {
    return target.getPlayer() != null;
  }

  public class HateInfo
  {
    public L2NpcInstance npc;
    public int hate;
    public int damage;

    HateInfo(L2NpcInstance attacker)
    {
      npc = attacker;
    }
  }
  private ConcurrentHashMap<L2NpcInstance, HateInfo> _hateList = null;

  public void addDamage(L2NpcInstance npc, int damage)
  {
    addDamageHate(npc, damage, damage);
    // Добавляем хейта к хозяину саммона
    if(npc.hasAI() && npc.getAI() instanceof DefaultAI && (isSummon() || isPet()) && getPlayer() != null)
    {
      getPlayer().addDamageHate(npc, damage, npc.getTemplate().getAIParams().getBool("searchingMaster", false) ? damage : 1);
    }
  }

  public void addDamageHate(L2NpcInstance npc, int damage, int aggro)
  {
    if(npc == null)
    {
      return;
    }
    if(damage <= 0 && aggro <= 0)
    {
      return;
    }
    if(damage > 0 && aggro <= 0)
    {
      aggro = damage;
    }
    if(_hateList == null)
    {
      _hateList = new ConcurrentHashMap<L2NpcInstance, HateInfo>();
    }
    HateInfo ai = _hateList.get(npc);
    if(ai != null)
    {
      ai.damage += damage;
      ai.hate += aggro;
      ai.hate = Math.max(ai.hate, 0);
    }
    else if(aggro > 0)
    {
      ai = new HateInfo(npc);
      ai.damage = damage;
      ai.hate = aggro;
      _hateList.put(npc, ai);
    }
  }

  public ConcurrentHashMap<L2NpcInstance, HateInfo> getHateList()
  {
    if(_hateList == null)
    {
      return new ConcurrentHashMap<L2NpcInstance, HateInfo>();
    }
    return _hateList;
  }

  public void removeFromHatelist(L2NpcInstance npc, boolean onlyHate)
  {
    if(npc != null && _hateList != null)
    {
      if(onlyHate)
      {
        HateInfo i = _hateList.get(npc);
        if(i != null)
        {
          i.hate = 0;
        }
      }
      else
      {
        _hateList.remove(npc);
      }
    }
  }

  public void clearHateList(boolean onlyHate)
  {
    if(_hateList != null)
    {
      if(onlyHate)
      {
        for(HateInfo i : _hateList.values())
        {
          i.hate = 0;
        }
      }
      else
      {
        _hateList = null;
      }
    }
  }

  public EffectList getEffectList()
  {
    if(_effectList == null)
    {
      _effectList = new EffectList(this);
    }
    return _effectList;
  }

  public void setEffectList(EffectList el)
  {
    _effectList = el;
  }

  public boolean isMassUpdating()
  {
    return _massUpdating;
  }

  public void setMassUpdating(boolean updating)
  {
    _massUpdating = updating;
  }

  public Collection<L2TrapInstance> getTraps()
  {
    if(_traps == null)
    {
      return null;
    }
    Collection<L2TrapInstance> result = new GArray<L2TrapInstance>(getTrapsCount());
    L2TrapInstance trap;
    for(Integer trapId : _traps.keySet())
    {
      if((trap = (L2TrapInstance) L2ObjectsStorage.get(_traps.get(trapId))) != null)
      {
        result.add(trap);
      }
      else
      {
        _traps.remove(trapId);
      }
    }
    return result;
  }

  public int getTrapsCount()
  {
    return _traps == null ? 0 : _traps.size();
  }

  public void addTrap(L2TrapInstance trap)
  {
    if(_traps == null)
    {
      _traps = new HashMap<Integer, Long>();
    }
    _traps.put(trap.getObjectId(), trap.getStoredId());
  }

  public void removeTrap(L2TrapInstance trap)
  {
    HashMap<Integer, Long> traps = _traps;
    if(traps == null || traps.isEmpty())
    {
      return;
    }
    traps.remove(trap.getObjectId());
  }

  public void destroyFirstTrap()
  {
    HashMap<Integer, Long> traps = _traps;
    if(traps == null || traps.isEmpty())
    {
      return;
    }
    L2TrapInstance trap;
    for(Integer trapId : traps.keySet())
    {
      if((trap = (L2TrapInstance) L2ObjectsStorage.get(traps.get(trapId))) != null)
      {
        trap.destroy();
        return;
      }
      traps.remove(trapId);
      return;
    }
  }

  public void destroyAllTraps()
  {
    HashMap<Integer, Long> traps = _traps;
    if(traps == null || traps.isEmpty())
    {
      return;
    }
    GArray<L2TrapInstance> toRemove = new GArray<L2TrapInstance>();
    for(Integer trapId : traps.keySet())
    {
      toRemove.add((L2TrapInstance) L2ObjectsStorage.get(traps.get(trapId)));
    }
    for(L2TrapInstance t : toRemove)
    {
      if(t != null)
      {
        t.destroy();
      }
    }
  }

  public boolean paralizeOnAttack(L2Character attacker)
  {
    // Mystic Immunity Makes a target temporarily immune to raid curce
//    if(attacker.getEffectList().getEffectsBySkillId(L2Skill.SKILL_MYSTIC_IMMUNITY) != null)
//    {
//      return false;
//    }
    int max_attacker_level = 0xFFFF;
    L2MonsterInstance leader;
    if(isRaid() || (isMinion() && (leader = ((L2MinionInstance) this).getLeader()) != null && leader.isRaid()))
    {
      max_attacker_level = getLevel() + Config.RAID_MAX_LEVEL_DIFF;
    }
    else if(getAI() instanceof DefaultAI)
    {
      int max_level_diff = ((DefaultAI) getAI()).getInt("ParalizeOnAttack", -1000);
      if(max_level_diff != -1000)
      {
        max_attacker_level = getLevel() + max_level_diff;
      }
    }
    if(attacker.getLevel() > max_attacker_level)
    {
      if(max_attacker_level > 0)
      {
        attacker.sendMessage(new CustomMessage("l2p.gameserver.model.L2Character.ParalizeOnAttack", attacker).addCharName(this).addNumber(max_attacker_level));
      }
      return true;
    }
    return false;
  }

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

  @Override
  public void deleteMe()
  {
    setTarget(null);
    stopMove();
    super.deleteMe();
  }
  // ---------------------------- Not Implemented -------------------------------

  public void addExpAndSp(long addToExp, long addToSp)
  {
  }

  public void addExpAndSp(long addToExp, long addToSp, boolean applyBonus, boolean appyToPet)
  {
  }

  public void broadcastUserInfo(boolean force)
  {
  }

  public void checkHpMessages(double currentHp, double newHp)
  {
  }

  public boolean checkPvP(L2Character target, L2Skill skill)
  {
    return false;
  }

  public boolean consumeItem(int itemConsumeId, int itemCount)
  {
    return true;
  }

  public void doPickupItem(L2Object object)
  {
  }

  public boolean isFearImmune()
  {
    return false;
  }

  public boolean isLethalImmune()
  {
    return getMaxHp() >= 50000;
  }

  public boolean getChargedSoulShot()
  {
    return false;
  }

  public int getChargedSpiritShot()
  {
    return 0;
  }

  public Duel getDuel()
  {
    return null;
  }

  public int getIncreasedForce()
  {
    return 0;
  }

  public int getConsumedSouls()
  {
    return 0;
  }

  public int getKarma()
  {
    return 0;
  }

  public double getLevelMod()
  {
    return 1;
  }

  public int getNpcId()
  {
    return 0;
  }

  public L2Summon getPet()
  {
    return null;
  }

  public int getPvpFlag()
  {
    return 0;
  }

  public int getTeam()
  {
    return 0;
  }

  public boolean isSitting()
  {
    return false;
  }

  public boolean isUndead()
  {
    return false;
  }

  public boolean isUsingDualWeapon()
  {
    return false;
  }

  public boolean isParalyzeImmune()
  {
    return false;
  }

  public void reduceArrowCount()
  {
  }

  public void sendChanges()
  {
  }

  public void sendMessage(String message)
  {
  }

  public void sendPacket(L2GameServerPacket... mov)
  {
  }

  public void setIncreasedForce(int i)
  {
  }

  public void setConsumedSouls(int i, L2NpcInstance monster)
  {
  }

  public void sitDown()
  {
  }

  public void standUp()
  {
  }

  public void startPvPFlag(L2Character target)
  {
  }

  public boolean unChargeShots(boolean spirit)
  {
    return false;
  }

  public void updateEffectIcons()
  {
  }

  public void updateStats()
  {
  }

  public void callMinionsToAssist(L2Character attacker)
  {
  }

  public void setOverhitAttacker(L2Character attacker)
  {
  }

  public void setOverhitDamage(double damage)
  {
  }

  public boolean hasMinions()
  {
    return false;
  }

  public boolean isCursedWeaponEquipped()
  {
    return false;
  }

  public boolean isHero()
  {
    return false;
  }

  public int getAccessLevel()
  {
    return 0;
  }

  public void spawnWayPoints(Vector<Location> recorder)
  {
  }

  public void setFollowStatus(boolean state, boolean changeIntention)
  {
  }

  public void setLastClientPosition(Location charPosition)
  {
  }

  public void setLastServerPosition(Location charPosition)
  {
  }

  public boolean hasRandomAnimation()
  {
    return true;
  }

  public boolean hasRandomWalk()
  {
    return true;
  }

  public int getClanCrestId()
  {
    return 0;
  }

  public int getClanCrestLargeId()
  {
    return 0;
  }

  public int getAllyCrestId()
  {
    return 0;
  }

  public void disableItem(L2Skill handler, long timeTotal, long timeLeft)
  {
  }

  public float getRateAdena()
  {
    return 1.0f;
  }

  public float getRateItems()
  {
    return 1.0f;
  }

  public double getRateExp()
  {
    return 1.;
  }

  public double getRateSp()
  {
    return 1.;
  }

  public float getRateSpoil()
  {
    return 1.0f;
  }

  @Override
  public void setXYZInvisible(int x, int y, int z)
  {
    stopMove();
    super.setXYZInvisible(x, y, z);
  }

  @Override
  public void setXYZ(int x, int y, int z, boolean MoveTask)
  {
    if(!MoveTask)
    {
      stopMove();
    }
    super.setXYZ(x, y, z, MoveTask);
  }

  public void validateLocation(int broadcast)
  {
    if(isVehicle() || isInVehicle()) // FIXME для кораблей что-то иное
    {
      return;
    }
    L2GameServerPacket sp = new ValidateLocation(this);
    if(broadcast == 0)
    {
      sendPacket(sp);
    }
    else if(broadcast == 1)
    {
      broadcastPacket(sp);
    }
    else
    {
      broadcastPacketToOthers(sp);
    }
  }
  // --------------------------- End Of Not Implemented ------------------------------
  // --------------------------------- Abstract --------------------------------------

  public abstract byte getLevel();

  public abstract void updateAbnormalEffect();

  public abstract L2ItemInstance getActiveWeaponInstance();

  public abstract L2Weapon getActiveWeaponItem();

  public abstract L2ItemInstance getSecondaryWeaponInstance();

  public abstract L2Weapon getSecondaryWeaponItem();
  // ----------------------------- End Of Abstract -----------------------------------

}
TOP

Related Classes of l2p.gameserver.model.L2Character

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.