DefaultAI.java
	
	
		
			
	
	
	
		
		
	
	
	
		
			
	
	
	
		
		
	
				
			
		PHP:
	
	package l2p.gameserver.ai;
import l2p.commons.collections.CollectionUtils;
import l2p.commons.collections.LazyArrayList;
import l2p.commons.lang.reference.HardReference;
import l2p.commons.math.random.RndSelector;
import l2p.commons.threading.RunnableImpl;
import l2p.commons.util.Rnd;
import l2p.gameserver.configs.Config;
import l2p.gameserver.ThreadPoolManager;
import l2p.gameserver.configs.proprties.AIConfig;
import l2p.gameserver.data.xml.holder.NpcHolder;
import l2p.gameserver.geodata.GeoEngine;
import l2p.gameserver.model.AggroList.AggroInfo;
import l2p.gameserver.model.Creature;
import l2p.gameserver.model.MinionList;
import l2p.gameserver.model.Playable;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.Skill;
import l2p.gameserver.model.World;
import l2p.gameserver.model.WorldRegion;
import l2p.gameserver.model.Zone.ZoneType;
import l2p.gameserver.model.entity.SevenSigns;
import l2p.gameserver.model.instances.MinionInstance;
import l2p.gameserver.model.instances.MonsterInstance;
import l2p.gameserver.model.instances.NpcInstance;
import l2p.gameserver.model.quest.QuestEventType;
import l2p.gameserver.model.quest.QuestState;
import l2p.gameserver.serverpackets.MagicSkillUse;
import l2p.gameserver.serverpackets.StatusUpdate;
import l2p.gameserver.stats.Stats;
import l2p.gameserver.taskmanager.AiTaskManager;
import l2p.gameserver.utils.Location;
import l2p.gameserver.utils.NpcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;
public class DefaultAI extends CharacterAI
{
    protected static final Logger _log = LoggerFactory.getLogger(DefaultAI.class);
    public static enum TaskType
    {
        MOVE,
        ATTACK,
        CAST,
        BUFF
    }
    public static final int TaskDefaultWeight = 10000;
    public static class Task
    {
        public TaskType type;
        public Skill skill;
        public HardReference<? extends Creature> target;
        public Location loc;
        public boolean pathfind;
        public int weight = TaskDefaultWeight;
    }
    public void addTaskCast(Creature target, Skill skill)
    {
        Task task = new Task();
        task.type = TaskType.CAST;
        task.target = target.getRef();
        task.skill = skill;
        _tasks.add(task);
        _def_think = true;
    }
    public void addTaskBuff(Creature target, Skill skill)
    {
        Task task = new Task();
        task.type = TaskType.BUFF;
        task.target = target.getRef();
        task.skill = skill;
        _tasks.add(task);
        _def_think = true;
    }
    public void addTaskAttack(Creature target)
    {
        Task task = new Task();
        task.type = TaskType.ATTACK;
        task.target = target.getRef();
        _tasks.add(task);
        _def_think = true;
    }
    public void addTaskAttack(Creature target, Skill skill, int weight)
    {
        Task task = new Task();
        task.type = skill.isOffensive() ? TaskType.CAST : TaskType.BUFF;
        task.target = target.getRef();
        task.skill = skill;
        task.weight = weight;
        _tasks.add(task);
        _def_think = true;
    }
    public void addTaskMove(Location loc, boolean pathfind)
    {
        Task task = new Task();
        task.type = TaskType.MOVE;
        task.loc = loc;
        task.pathfind = pathfind;
        _tasks.add(task);
        _def_think = true;
    }
    protected void addTaskMove(int locX, int locY, int locZ, boolean pathfind)
    {
        addTaskMove(new Location(locX, locY, locZ), pathfind);
    }
    private static class TaskComparator implements Comparator<Task>
    {
        private static final Comparator<Task> instance = new TaskComparator();
        public static Comparator<Task> getInstance()
        {
            return instance;
        }
        @Override
        public int compare(Task o1, Task o2)
        {
            if(o1 == null || o2 == null)
                return 0;
            return o2.weight - o1.weight;
        }
    }
    protected class Teleport extends RunnableImpl
    {
        Location _destination;
        public Teleport(Location destination)
        {
            _destination = destination;
        }
        @Override
        public void runImpl() throws Exception
        {
            NpcInstance actor = getActor();
            if(actor != null)
                actor.teleToLocation(_destination);
        }
    }
    protected class RunningTask extends RunnableImpl
    {
        @Override
        public void runImpl() throws Exception
        {
            NpcInstance actor = getActor();
            if(actor != null)
                actor.setRunning();
            _runningTask = null;
        }
    }
    protected class MadnessTask extends RunnableImpl
    {
        @Override
        public void runImpl() throws Exception
        {
            NpcInstance actor = getActor();
            if(actor != null)
                actor.stopConfused();
            _madnessTask = null;
        }
    }
    protected class NearestTargetComparator implements Comparator<Creature>
    {
        private final Creature actor;
        public NearestTargetComparator(Creature actor)
        {
            this.actor = actor;
        }
        @Override
        public int compare(Creature o1, Creature o2)
        {
            double diff = actor.getDistance3D(o1) - actor.getDistance3D(o2);
            if(diff < 0)
                return -1;
            return diff > 0 ? 1 : 0;
        }
    }
    protected long AI_TASK_ATTACK_DELAY = AIConfig.AiTaskDelay;
    protected long AI_TASK_ACTIVE_DELAY = AIConfig.AiTaskActiveDelay;
    protected long AI_TASK_DELAY_CURRENT = AI_TASK_ACTIVE_DELAY;
    protected int MAX_PURSUE_RANGE;
    protected ScheduledFuture<?> _aiTask;
    protected ScheduledFuture<?> _runningTask;
    protected ScheduledFuture<?> _madnessTask;
    /** The flag used to indicate that a thinking action is in progress */
    private boolean _thinking = false;
    /** Показывает, есть ли задания */
    protected boolean _def_think = false;
    /** The L2NpcInstance aggro counter */
    protected long _globalAggro;
    protected long _randomAnimationEnd;
    protected int _pathfindFails;
    /** Список заданий */
    protected final NavigableSet<Task> _tasks = new ConcurrentSkipListSet<>(TaskComparator.getInstance());
    protected final Skill[] _damSkills, _dotSkills, _debuffSkills, _healSkills, _buffSkills, _stunSkills;
    protected long _lastActiveCheck;
    protected long _checkAggroTimestamp = 0;
    /** Время актуальности состояния атаки */
    protected long _attackTimeout;
    protected long _lastFactionNotifyTime = 0;
    protected long _minFactionNotifyInterval = 10000;
    protected final Comparator<Creature> _nearestTargetComparator;
    public DefaultAI(NpcInstance actor)
    {
        super(actor);
        setAttackTimeout(Long.MAX_VALUE);
        NpcInstance npc = getActor();
        _damSkills = npc.getTemplate().getDamageSkills();
        _dotSkills = npc.getTemplate().getDotSkills();
        _debuffSkills = npc.getTemplate().getDebuffSkills();
        _buffSkills = npc.getTemplate().getBuffSkills();
        _stunSkills = npc.getTemplate().getStunSkills();
        _healSkills = npc.getTemplate().getHealSkills();
        _nearestTargetComparator = new NearestTargetComparator(actor);
        // Preload some AI params
        MAX_PURSUE_RANGE = actor.getParameter("MaxPursueRange", actor.isRaid() ? AIConfig.MaxPursueRangeRaid : npc.isUnderground() ? AIConfig.MaxPursueUnderGroundRange : AIConfig.MaxPursueRange);
        _minFactionNotifyInterval = actor.getParameter("FactionNotifyInterval", 10000);
    }
    @Override
    public void runImpl() throws Exception
    {
        if(_aiTask == null)
            return;
        // проверяем, если NPC вышел в неактивный регион, отключаем AI
        if(!isGlobalAI() && System.currentTimeMillis() - _lastActiveCheck > 60000L)
        {
            _lastActiveCheck = System.currentTimeMillis();
            NpcInstance actor = getActor();
            WorldRegion region = actor == null ? null : actor.getCurrentRegion();
            if(region == null || !region.isActive())
            {
                stopAITask();
                return;
            }
        }
        onEvtThink();
    }
    @Override
    //public final synchronized void startAITask()
    public synchronized void startAITask()
    {
        if(_aiTask == null)
        {
            AI_TASK_DELAY_CURRENT = AI_TASK_ACTIVE_DELAY;
            _aiTask = AiTaskManager.getInstance().scheduleAtFixedRate(this, 0L, AI_TASK_DELAY_CURRENT);
        }
    }
    //protected final synchronized void switchAITask(long NEW_DELAY)
    protected synchronized void switchAITask(long NEW_DELAY)
    {
        if(_aiTask == null)
            return;
        if(AI_TASK_DELAY_CURRENT != NEW_DELAY)
        {
            _aiTask.cancel(false);
            AI_TASK_DELAY_CURRENT = NEW_DELAY;
            _aiTask = AiTaskManager.getInstance().scheduleAtFixedRate(this, 0L, AI_TASK_DELAY_CURRENT);
        }
    }
    @Override
    public final synchronized void stopAITask()
    {
        if(_aiTask != null)
        {
            _aiTask.cancel(false);
            _aiTask = null;
        }
    }
    /**
     * Определяет, может ли этот тип АИ видеть персонажей в режиме Silent Move.
     * @param target L2Playable цель
     * @return true если цель видна в режиме Silent Move
     */
    protected boolean canSeeInSilentMove(Playable target) {
        return getActor().getParameter("canSeeInSilentMove", false) || !target.isSilentMoving();
    }
    protected boolean canSeeInHide(Playable target)
    {
        return getActor().getParameter("canSeeInHide", false) || !target.isInvisible();
    }
    protected boolean checkAggression(Creature target)
    {
        NpcInstance actor = getActor();
        if(getIntention() != CtrlIntention.AI_INTENTION_ACTIVE || !isGlobalAggro())
            return false;
        if(target.isAlikeDead())
            return false;
        if(target.isNpc() && target.isInvul())
            return false;
        if(target.isPlayer() && target.getPlayer().isInAwayingMode() && !Config.AWAY_PLAYER_TAKE_AGGRO)
            return false;
        if(target.isPlayable())
        {
            if(!canSeeInSilentMove((Playable) target))
                return false;
            if(!canSeeInHide((Playable) target))
                return false;
            if(actor.getFaction().getName().equalsIgnoreCase("varka_silenos_clan") && target.getPlayer().getVarka() > 0)
                return false;
            if(actor.getFaction().getName().equalsIgnoreCase("ketra_orc_clan") && target.getPlayer().getKetra() > 0)
                return false;
            /*if(target.isFollow && !target.isPlayer() && target.getFollowTarget() != null && target.getFollowTarget().isPlayer())
                    return;*/
            if(target.isPlayer() && ((Player) target).isGM() && target.isInvisible())
                return false;
            if(((Playable) target).getNonAggroTime() > System.currentTimeMillis())
                return false;
            if(target.isPlayer() && !target.getPlayer().isActive())
                return false;
            if(actor.isMonster() && target.isInZonePeace())
                return false;
        }
        AggroInfo ai = actor.getAggroList().get(target);
        if(ai != null && ai.hate > 0)
        {
            if(!target.isInRangeZ(actor.getSpawnedLoc(), MAX_PURSUE_RANGE))
                return false;
        }
        else if(!actor.isAggressive() || !target.isInRangeZ(actor.getSpawnedLoc(), actor.getAggroRange()))
            return false;
        if(!canAttackCharacter(target))
            return false;
        if(!GeoEngine.canSeeTarget(actor, target, false))
            return false;
        actor.getAggroList().addDamageHate(target, 0, 2);
        if((target.isSummon() || target.isPet()))
            actor.getAggroList().addDamageHate(target.getPlayer(), 0, 1);
        startRunningTask(AI_TASK_ATTACK_DELAY);
        setIntention(CtrlIntention.AI_INTENTION_ATTACK, target);
        return true;
    }
    protected void setIsInRandomAnimation(long time)
    {
        _randomAnimationEnd = System.currentTimeMillis() + time;
    }
    protected boolean randomAnimation()
    {
        NpcInstance actor = getActor();
        if(actor.getParameter("noRandomAnimation", false))
            return false;
        if(actor.hasRandomAnimation() && !actor.isActionsDisabled() && !actor.isMoving && !actor.isInCombat() && Rnd.chance(AIConfig.RndAnimationRate))
        {
            setIsInRandomAnimation(3000);
            actor.onRandomAnimation();
            return true;
        }
        return false;
    }
    protected boolean randomWalk() {
        NpcInstance actor = getActor();
        return !actor.getParameter("noRandomWalk", false) && !actor.isMoving && maybeMoveToHome();
    }
    /**
     * @return true если действие выполнено, false если нет
     */
    protected boolean thinkActive() {
        NpcInstance actor = getActor();
        if (actor.isActionsDisabled())
            return true;
        if (_randomAnimationEnd > System.currentTimeMillis())
            return true;
        if (_def_think) {
            if (doTask())
                clearTasks();
            return true;
        }
        long now = System.currentTimeMillis();
        if (now - _checkAggroTimestamp > AIConfig.AggroCheckInterval) {
            _checkAggroTimestamp = now;
            boolean aggressive = Rnd.chance(actor.getParameter("SelfAggressive", actor.isAggressive() ? 100 : 0));
            if (!actor.getAggroList().isEmpty() || aggressive) {
                List<Creature> chars = World.getAroundCharacters(actor);
                CollectionUtils.eqSort(chars, _nearestTargetComparator);
                for (Creature cha : chars) {
                    if (aggressive || actor.getAggroList().get(cha) != null)
                        if (checkAggression(cha))
                            return true;
                }
            }
        }
        if (actor.isMinion()) {
            MonsterInstance leader = ((MinionInstance) actor).getLeader();
            if (leader != null) {
                double distance = actor.getDistance(leader.getX(), leader.getY());
                if (distance > 1000)
                    actor.teleToLocation(leader.getMinionPosition());
                else if (distance > 200)
                    addTaskMove(leader.getMinionPosition(), false);
                return true;
            }
        }
        return randomAnimation() || randomWalk();
    }
    @Override
    protected void onIntentionIdle()
    {
        NpcInstance actor = getActor();
        // Удаляем все задания
        clearTasks();
        actor.stopMove();
        actor.getAggroList().clear(true);
        setAttackTimeout(Long.MAX_VALUE);
        setAttackTarget(null);
        changeIntention(CtrlIntention.AI_INTENTION_IDLE, null, null);
    }
    @Override
    protected void onIntentionActive()
    {
        NpcInstance actor = getActor();
        actor.stopMove();
        setAttackTimeout(Long.MAX_VALUE);
        if(getIntention() != CtrlIntention.AI_INTENTION_ACTIVE)
        {
            switchAITask(AI_TASK_ACTIVE_DELAY);
            changeIntention(CtrlIntention.AI_INTENTION_ACTIVE, null, null);
        }
        onEvtThink();
    }
    @Override
    protected void onIntentionAttack(Creature target)
    {
        NpcInstance actor = getActor();
        // Удаляем все задания
        clearTasks();
        actor.stopMove();
        setAttackTarget(target);
        setAttackTimeout(getMaxAttackTimeout() + System.currentTimeMillis());
        setGlobalAggro(0);
        if(getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
        {
            changeIntention(CtrlIntention.AI_INTENTION_ATTACK, target, null);
            switchAITask(AI_TASK_ATTACK_DELAY);
        }
        onEvtThink();
    }
    protected boolean canAttackCharacter(Creature target)
    {
        return target.isPlayable();
    }
    protected boolean checkTarget(Creature target, int range)
    {
        NpcInstance actor = getActor();
        if(target == null || target.isAlikeDead() || !actor.isInRangeZ(target, range))
            return false;
        // если не видим чаров в хайде - не атакуем их
        final boolean hided = target.isPlayable() && !canSeeInHide((Playable)target);
        if(!hided && actor.isConfused())
            return true;
        //В состоянии атаки атакуем всех, на кого у нас есть хейт
        if(getIntention() == CtrlIntention.AI_INTENTION_ATTACK)
        {
            AggroInfo ai = actor.getAggroList().get(target);
            if (ai != null)
            {
                if (hided)
                {
                    ai.hate = 0; // очищаем хейт
                    return false;
                }
                return ai.hate > 0;
            }
            return false;
        }
        return canAttackCharacter(target);
    }
    public void setAttackTimeout(long time)
    {
        _attackTimeout = time;
    }
    protected long getAttackTimeout()
    {
        return _attackTimeout;
    }
    protected void thinkAttack()
    {
        NpcInstance actor = getActor();
        if(actor.isDead())
            return;
        Location loc = actor.getSpawnedLoc();
        if(!actor.isInRange(loc, MAX_PURSUE_RANGE))
        {
            returnHome();
            return;
        }
        if(doTask() && !actor.isAttackingNow() && !actor.isCastingNow())
        {
            if(!createNewTask())
            {
                if(System.currentTimeMillis() > getAttackTimeout())
                    returnHome();
            }
        }
    }
    @Override
    protected void onEvtSpawn()
    {
        setGlobalAggro(System.currentTimeMillis() + getActor().getParameter("globalAggro", 10000L));
        setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
    }
    @Override
    protected void onEvtReadyToAct()
    {
        onEvtThink();
    }
    @Override
    protected void onEvtArrivedTarget()
    {
        onEvtThink();
    }
    @Override
    protected void onEvtArrived()
    {
        onEvtThink();
    }
    protected boolean tryMoveToTarget(Creature target)
    {
        NpcInstance actor = getActor();
        if(target.isInvisible())
        {
            notifyEvent(CtrlEvent.EVT_THINK);
            return false;
        }
        if(!actor.followToCharacter(target, actor.getPhysicalAttackRange(), true))
            _pathfindFails++;
        if(_pathfindFails >= getMaxPathfindFails() && (System.currentTimeMillis() > getAttackTimeout() - getMaxAttackTimeout() + getTeleportTimeout()) && actor.isInRange(target, MAX_PURSUE_RANGE))
        {
            _pathfindFails = 0;
            if(target.isPlayable())
            {
                AggroInfo hate = actor.getAggroList().get(target);
                if(hate == null || hate.hate < 100)
                {
                    returnHome();
                    return false;
                }
            }
            Location loc = GeoEngine.moveCheckForAI(target.getLoc(), actor.getLoc(), actor.getGeoIndex());
            if(!GeoEngine.canMoveToCoord(actor.getX(), actor.getY(), actor.getZ(), loc.x, loc.y, loc.z, actor.getGeoIndex())) // Для подстраховки
                loc = target.getLoc();
            actor.teleToLocation(loc);
        }
        return true;
    }
    protected boolean maybeNextTask(Task currentTask)
    {
        // Следующее задание
        _tasks.remove(currentTask);
        // Если заданий больше нет - определить новое
        return _tasks.size() == 0;
    }
    protected boolean doTask()
    {
        NpcInstance actor = getActor();
        if(!_def_think)
            return true;
        Task currentTask = _tasks.pollFirst();
        if(currentTask == null)
        {
            clearTasks();
            return true;
        }
        if(actor.isDead() || actor.isAttackingNow() || actor.isCastingNow())
            return false;
        switch(currentTask.type)
        {
            // Задание "прибежать в заданные координаты"
            case MOVE:
            {
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                if(actor.isInRange(currentTask.loc, 100))
                    return maybeNextTask(currentTask);
                if(actor.isMoving)
                    return false;
                if(!actor.moveToLocation(currentTask.loc, 0, currentTask.pathfind))
                {
                    clientStopMoving();
                    _pathfindFails = 0;
                    actor.teleToLocation(currentTask.loc);
                    //actor.broadcastPacketToOthers(new MagicSkillUse(actor, actor, 2036, 1, 500, 600000));
                    //ThreadPoolManager.getInstance().scheduleAi(new Teleport(currentTask.loc), 500, false);
                    return maybeNextTask(currentTask);
                }
            }
                break;
            // Задание "добежать - ударить"
            case ATTACK:
            {
                Creature target = currentTask.target.get();
                if(!checkTarget(target, MAX_PURSUE_RANGE))
                    return true;
                setAttackTarget(target);
                if(actor.isMoving)
                    return Rnd.chance(25);
                if(actor.getRealDistance3D(target) <= actor.getPhysicalAttackRange() + 40 && GeoEngine.canSeeTarget(actor, target, false))
                {
                    clientStopMoving();
                    _pathfindFails = 0;
                    setAttackTimeout(getMaxAttackTimeout() + System.currentTimeMillis());
                    actor.doAttack(target);
                    return maybeNextTask(currentTask);
                }
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                tryMoveToTarget(target);
            }
                break;
            // Задание "добежать - атаковать скиллом"
            case CAST:
            {
                Creature target = currentTask.target.get();
                if(actor.isMuted(currentTask.skill) || actor.isSkillDisabled(currentTask.skill) || actor.isUnActiveSkill(currentTask.skill.getId()))
                    return true;
                boolean isAoE = currentTask.skill.getTargetType() == Skill.SkillTargetType.TARGET_AURA;
                int castRange = currentTask.skill.getAOECastRange();
                if(!checkTarget(target, MAX_PURSUE_RANGE + castRange))
                    return true;
                setAttackTarget(target);
                if(actor.getRealDistance3D(target) <= castRange + 60 && GeoEngine.canSeeTarget(actor, target, false))
                {
                    clientStopMoving();
                    _pathfindFails = 0;
                    setAttackTimeout(getMaxAttackTimeout() + System.currentTimeMillis());
                    actor.doCast(currentTask.skill, isAoE ? actor : target, !target.isPlayable());
                    return maybeNextTask(currentTask);
                }
                if(actor.isMoving)
                    return Rnd.chance(10);
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                tryMoveToTarget(target);
            }
                break;
            // Задание "добежать - применить скилл"
            case BUFF:
            {
                Creature target = currentTask.target.get();
                if(actor.isMuted(currentTask.skill) || actor.isSkillDisabled(currentTask.skill) || actor.isUnActiveSkill(currentTask.skill.getId()))
                    return true;
                if(target == null || target.isAlikeDead() || !actor.isInRange(target, 2000))
                    return true;
                boolean isAoE = currentTask.skill.getTargetType() == Skill.SkillTargetType.TARGET_AURA;
                int castRange = currentTask.skill.getAOECastRange();
                if(actor.isMoving)
                    return Rnd.chance(10);
                if(actor.getRealDistance3D(target) <= castRange + 60 && GeoEngine.canSeeTarget(actor, target, false))
                {
                    clientStopMoving();
                    _pathfindFails = 0;
                    actor.doCast(currentTask.skill, isAoE ? actor : target, !target.isPlayable());
                    return maybeNextTask(currentTask);
                }
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                tryMoveToTarget(target);
            }
                break;
        }
        return false;
    }
    protected boolean createNewTask()
    {
        return false;
    }
    protected boolean defaultNewTask()
    {
        clearTasks();
        NpcInstance actor = getActor();
        Creature target;
        if(actor == null || (target = prepareTarget()) == null)
            return false;
        double distance = actor.getDistance(target);
        return chooseTaskAndTargets(null, target, distance);
    }
    @Override
    protected void onEvtThink()
    {
        NpcInstance actor = getActor();
        if(_thinking || actor == null || actor.isActionsDisabled() || actor.isAfraid())
            return;
        if(_randomAnimationEnd > System.currentTimeMillis())
            return;
        if(actor.isRaid() && (actor.isInZonePeace() || actor.isInZoneBattle() || actor.isInZone(ZoneType.SIEGE)))
        {
            teleportHome();
            return;
        }
        _thinking = true;
        try
        {
            if(!AIConfig.BlockActiveTasks && getIntention() == CtrlIntention.AI_INTENTION_ACTIVE)
                thinkActive();
            else if(getIntention() == CtrlIntention.AI_INTENTION_ATTACK)
                thinkAttack();
        }
        finally
        {
            _thinking = false;
        }
    }
    @Override
    protected void onEvtDead(Creature killer)
    {
        NpcInstance actor = getActor();
        int transformer = actor.getParameter("transformOnDead", 0);
        int chance = actor.getParameter("transformChance", 100);
        if(transformer > 0 && Rnd.chance(chance))
        {
            NpcInstance npc = NpcUtils.spawnSingle(transformer, actor.getLoc(), actor.getReflection()) ;
            if(killer != null && killer.isPlayable())
            {
                npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, killer, 100);
                killer.setTarget(npc);
                killer.sendPacket(npc.makeStatusUpdate(StatusUpdate.CUR_HP, StatusUpdate.MAX_HP));
            }
        }
        super.onEvtDead(killer);
    }
    @Override
    protected void onEvtClanAttacked(Creature attacked, Creature attacker, int damage)
    {
        if(getIntention() != CtrlIntention.AI_INTENTION_ACTIVE || !isGlobalAggro())
            return;
        notifyEvent(CtrlEvent.EVT_AGGRESSION, attacker, 2);
    }
    @Override
    protected void onEvtAttacked(Creature attacker, int damage)
    {
        NpcInstance actor = getActor();
        if(attacker == null || actor.isDead())
            return;
        int transformer = actor.getParameter("transformOnUnderAttack", 0);
        if(transformer > 0)
        {
            int chance = actor.getParameter("transformChance", 5);
            if(chance == 100 || ((MonsterInstance) actor).getChampion() == 0 && actor.getCurrentHpPercents() > 50 && Rnd.chance(chance))
            {
                MonsterInstance npc = (MonsterInstance) NpcHolder.getInstance().getTemplate(transformer).getNewInstance();
                npc.setSpawnedLoc(actor.getLoc());
                npc.setReflection(actor.getReflection());
                npc.setChampion(((MonsterInstance) actor).getChampion());
                npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp(), true);
                npc.spawnMe(npc.getSpawnedLoc());
                npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, attacker, 100);
                actor.doDie(actor);
                actor.decayMe();
                attacker.setTarget(npc);
                attacker.sendPacket(npc.makeStatusUpdate(StatusUpdate.CUR_HP, StatusUpdate.MAX_HP));
                return;
            }
        }
        Player player = attacker.getPlayer();
        if(player != null)
        {
            //FIXME [G1ta0] затычка для 7 печатей, при атаке монстра 7 печатей телепортирует персонажа в ближайший город
            if((SevenSigns.getInstance().isSealValidationPeriod() || SevenSigns.getInstance().isCompResultsPeriod()) && actor.isSevenSignsMonster())
            {
                int pcabal = SevenSigns.getInstance().getPlayerCabal(player);
                int wcabal = SevenSigns.getInstance().getCabalHighestScore();
                if(pcabal != wcabal && wcabal != SevenSigns.CABAL_NULL)
                {
                    player.sendMessage("You have been teleported to the nearest town because you not signed for winning cabal.");
                    player.teleToClosestTown();
                    return;
                }
            }
            List<QuestState> quests = player.getQuestsForEvent(actor, QuestEventType.ATTACKED_WITH_QUEST);
            if(quests != null)
                for(QuestState qs : quests)
                    qs.getQuest().notifyAttack(actor, qs);
        }
        //Добавляем только хейт, урон, если атакующий - игровой персонаж, будет добавлен в L2NpcInstance.onReduceCurrentHp
        actor.getAggroList().addDamageHate(attacker, 0, damage);
        // Обычно 1 хейт добавляется хозяину суммона, чтобы после смерти суммона моб накинулся на хозяина.
        if(damage > 0 && (attacker.isSummon() || attacker.isPet()))
            actor.getAggroList().addDamageHate(attacker.getPlayer(), 0, actor.getParameter("searchingMaster", false) ? damage : 1);
        if(getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
        {
            if(!actor.isRunning())
                startRunningTask(AI_TASK_ATTACK_DELAY);
            setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
        }
        notifyFriends(attacker, damage);
    }
    @Override
    protected void onEvtAggression(Creature attacker, int aggro)
    {
        NpcInstance actor = getActor();
        if(attacker == null || actor.isDead())
            return;
        actor.getAggroList().addDamageHate(attacker, 0, aggro);
        // Обычно 1 хейт добавляется хозяину суммона, чтобы после смерти суммона моб накинулся на хозяина.
        if(aggro > 0 && (attacker.isSummon() || attacker.isPet()))
            actor.getAggroList().addDamageHate(attacker.getPlayer(), 0, actor.getParameter("searchingMaster", false) ? aggro : 1);
        if(getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
        {
            if(!actor.isRunning())
                startRunningTask(AI_TASK_ATTACK_DELAY);
            setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
        }
    }
    protected boolean maybeMoveToHome()
    {
        NpcInstance actor = getActor();
        if(actor.isDead())
            return false;
        boolean randomWalk = actor.hasRandomWalk();
        Location sloc = actor.getSpawnedLoc();
        // Random walk or not?
        if(randomWalk && (!AIConfig.RndWalk || !Rnd.chance(AIConfig.RndWalkRate)))
            return false;
        boolean isInRange = actor.isInRangeZ(sloc, AIConfig.MaxDriftRange);
        if(!randomWalk && isInRange)
            return false;
        Location pos = Location.findPointToStay(actor, sloc, 0, AIConfig.MaxDriftRange);
        actor.setWalking();
        // Телепортируемся домой, только если далеко от дома
        if(!actor.moveToLocation(pos.x, pos.y, pos.z, 0, true) && !isInRange)
            teleportHome();
        return true;
    }
    protected void returnHome()
    {
        returnHome(true, false);
    }
    protected void teleportHome()
    {
        returnHome(true, true);
    }
    protected void returnHome(boolean clearAggro, boolean teleport)
    {
        NpcInstance actor = getActor();
        Location sloc = actor.getSpawnedLoc();
        // Удаляем все задания
        clearTasks();
        actor.stopMove();
        if(clearAggro)
            actor.getAggroList().clear(true);
        setAttackTimeout(Long.MAX_VALUE);
        setAttackTarget(null);
        changeIntention(CtrlIntention.AI_INTENTION_ACTIVE, null, null);
        if(teleport)
        {
            actor.broadcastPacketToOthers(new MagicSkillUse(actor, actor, 2036, 1, 500, 0));
            actor.teleToLocation(sloc.x, sloc.y, GeoEngine.getHeight(sloc, actor.getGeoIndex()));
        }
        else
        {
            if(!clearAggro)
                actor.setRunning();
            else
                actor.setWalking();
            addTaskMove(sloc, false);
        }
    }
    protected Creature prepareTarget()
    {
        NpcInstance actor = getActor();
        if(actor.isConfused())
            return getAttackTarget();
        // Для "двинутых" боссов, иногда, выбираем случайную цель
        if(Rnd.chance(actor.getParameter("isMadness", 0)))
        {
            Creature randomHated = actor.getAggroList().getRandomHated();
            if(randomHated != null)
            {
                setAttackTarget(randomHated);
                if(_madnessTask == null && !actor.isConfused())
                {
                    actor.startConfused();
                    _madnessTask = ThreadPoolManager.getInstance().schedule(new MadnessTask(), 10000);
                }
                return randomHated;
            }
        }
        // Новая цель исходя из агрессивности
        List<Creature> hateList = actor.getAggroList().getHateList();
        Creature hated = null;
        for(Creature cha : hateList)
        {
            // Если у Монстра есть скилл "Searching Master" он должен атаковать хозяина пета в первую очередь.
            if((cha.isPet() || cha.isSummon()) && cha.getPlayer() != null)
                if(getActor().getSkillLevel(6019) == 1 && checkTarget(cha.getPlayer(), MAX_PURSUE_RANGE))
                    cha = cha.getPlayer();
            //Не подходит, очищаем хейт
            if(!checkTarget(cha, MAX_PURSUE_RANGE))
            {
                actor.getAggroList().remove(cha, true);
                continue;
            }
            hated = cha;
            break;
        }
        if(hated != null)
        {
            setAttackTarget(hated);
            return hated;
        }
        return null;
    }
    protected boolean canUseSkill(Skill skill, Creature target, double distance)
    {
        NpcInstance actor = getActor();
        if(skill == null || skill.isNotUsedByAI())
            return false;
        if(skill.getTargetType() == Skill.SkillTargetType.TARGET_SELF && target != actor)
            return false;
        int castRange = skill.getAOECastRange();
        if(castRange <= 200 && distance > 200)
            return false;
        if(actor.isSkillDisabled(skill) || actor.isMuted(skill) || actor.isUnActiveSkill(skill.getId()))
            return false;
        double mpConsume2 = skill.getMpConsume2();
        if(skill.isMagic())
            mpConsume2 = actor.calcStat(Stats.MP_MAGIC_SKILL_CONSUME, mpConsume2, target, skill);
        else
            mpConsume2 = actor.calcStat(Stats.MP_PHYSICAL_SKILL_CONSUME, mpConsume2, target, skill);
        if(actor.getCurrentMp() < mpConsume2)
            return false;
        if(target.getEffectList().getEffectsCountForSkill(skill.getId()) != 0)
            return false;
        return true;
    }
    protected boolean canUseSkill(Skill sk, Creature target)
    {
        return canUseSkill(sk, target, 0);
    }
    protected Skill[] selectUsableSkills(Creature target, double distance, Skill[] skills)
    {
        if(skills == null || skills.length == 0 || target == null)
            return null;
        Skill[] ret = null;
        int usable = 0;
        for(Skill skill : skills)
            if(canUseSkill(skill, target, distance))
            {
                if(ret == null)
                    ret = new Skill[skills.length];
                ret[usable++] = skill;
            }
        if(ret == null || usable == skills.length)
            return ret;
        if(usable == 0)
            return null;
        ret = Arrays.copyOf(ret, usable);
        return ret;
    }
    protected static Skill selectTopSkillByDamage(Creature actor, Creature target, double distance, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            weight = skill.getSimpleDamage(actor, target) * skill.getAOECastRange() / distance;
            if(weight < 1.)
                weight = 1.;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected static Skill selectTopSkillByDebuff(Creature target, double distance, Skill[] skills) //FIXME
    {
        if(skills == null || skills.length == 0)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            if(skill.getSameByStackType(target) != null)
                continue;
            if((weight = 100. * skill.getAOECastRange() / distance) <= 0)
                weight = 1;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected static Skill selectTopSkillByBuff(Creature target, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            if(skill.getSameByStackType(target) != null)
                continue;
            if((weight = skill.getPower()) <= 0)
                weight = 1;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected static Skill selectTopSkillByHeal(Creature target, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return null;
        double hpReduced = target.getMaxHp() - target.getCurrentHp();
        if(hpReduced < 1)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            if((weight = Math.abs(skill.getPower() - hpReduced)) <= 0)
                weight = 1;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected void addDesiredSkill(Map<Skill, Integer> skillMap, Creature target, double distance, Skill[] skills)
    {
        if(skills == null || skills.length == 0 || target == null)
            return;
        for(Skill sk : skills)
            addDesiredSkill(skillMap, target, distance, sk);
    }
    protected void addDesiredSkill(Map<Skill, Integer> skillMap, Creature target, double distance, Skill skill)
    {
        if(skill == null || target == null || !canUseSkill(skill, target))
            return;
        int weight = (int) -Math.abs(skill.getAOECastRange() - distance);
        if(skill.getAOECastRange() >= distance)
            weight += 1000000;
        else if(skill.isNotTargetAoE() && skill.getTargets(getActor(), target, false).size() == 0)
            return;
        skillMap.put(skill, weight);
    }
    protected void addDesiredHeal(Map<Skill, Integer> skillMap, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return;
        NpcInstance actor = getActor();
        double hpReduced = actor.getMaxHp() - actor.getCurrentHp();
        double hpPercent = actor.getCurrentHpPercents();
        if(hpReduced < 1)
            return;
        int weight;
        for(Skill sk : skills)
            if(canUseSkill(sk, actor) && sk.getPower() <= hpReduced)
            {
                weight = (int) sk.getPower();
                if(hpPercent < 50)
                    weight += 1000000;
                skillMap.put(sk, weight);
            }
    }
    protected void addDesiredBuff(Map<Skill, Integer> skillMap, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return;
        NpcInstance actor = getActor();
        for(Skill sk : skills)
            if(canUseSkill(sk, actor))
                skillMap.put(sk, 1000000);
    }
    protected Skill selectTopSkill(Map<Skill, Integer> skillMap)
    {
        if(skillMap == null || skillMap.isEmpty())
            return null;
        int nWeight, topWeight = Integer.MIN_VALUE;
        for(Skill next : skillMap.keySet())
            if((nWeight = skillMap.get(next)) > topWeight)
                topWeight = nWeight;
        if(topWeight == Integer.MIN_VALUE)
            return null;
        Skill[] skills = new Skill[skillMap.size()];
        nWeight = 0;
        for(Map.Entry<Skill, Integer> e : skillMap.entrySet())
        {
            if(e.getValue() < topWeight)
                continue;
            skills[nWeight++] = e.getKey();
        }
        return skills[Rnd.get(nWeight)];
    }
    protected boolean chooseTaskAndTargets(Skill skill, Creature target, double distance)
    {
        NpcInstance actor = getActor();
        // Использовать скилл если можно, иначе атаковать
        if(skill != null)
        {
            // Проверка цели, и смена если необходимо
            if(actor.isMovementDisabled() && distance > skill.getAOECastRange() + 60)
            {
                target = null;
                if(skill.isOffensive())
                {
                    LazyArrayList<Creature> targets = LazyArrayList.newInstance();
                    for(Creature cha : actor.getAggroList().getHateList())
                    {
                        if(!checkTarget(cha, skill.getAOECastRange() + 60) || !canUseSkill(skill, cha))
                            continue;
                        targets.add(cha);
                    }
                    if(!targets.isEmpty())
                        target = targets.get(Rnd.get(targets.size()));
                    LazyArrayList.recycle(targets);
                }
            }
            if(target == null)
                return false;
            // Добавить новое задание
            if(skill.isOffensive())
                addTaskCast(target, skill);
            else
                addTaskBuff(target, skill);
            return true;
        }
        // Смена цели, если необходимо
        if(actor.isMovementDisabled() && distance > actor.getPhysicalAttackRange() + 40)
        {
            target = null;
            LazyArrayList<Creature> targets = LazyArrayList.newInstance();
            for(Creature cha : actor.getAggroList().getHateList())
            {
                if(!checkTarget(cha, actor.getPhysicalAttackRange() + 40))
                    continue;
                targets.add(cha);
            }
            if(!targets.isEmpty())
                target = targets.get(Rnd.get(targets.size()));
            LazyArrayList.recycle(targets);
        }
        if(target == null)
            return false;
        // Добавить новое задание
        addTaskAttack(target);
        return true;
    }
    @Override
    public boolean isActive()
    {
        return _aiTask != null;
    }
    protected void clearTasks()
    {
        _def_think = false;
        _tasks.clear();
    }
    /** переход в режим бега через определенный интервал времени */
    protected void startRunningTask(long interval)
    {
        NpcInstance actor = getActor();
        if(actor != null && _runningTask == null && !actor.isRunning())
            _runningTask = ThreadPoolManager.getInstance().schedule(new RunningTask(), interval);
    }
    protected boolean isGlobalAggro()
    {
        if(_globalAggro == 0)
            return true;
        if(_globalAggro <= System.currentTimeMillis())
        {
            _globalAggro = 0;
            return true;
        }
        return false;
    }
    public void setGlobalAggro(long value)
    {
        _globalAggro = value;
    }
    @Override
    public NpcInstance getActor()
    {
        return (NpcInstance) super.getActor();
    }
    protected boolean defaultThinkBuff(int rateSelf)
    {
        return defaultThinkBuff(rateSelf, 0);
    }
    /**
     * Оповестить дружественные цели об атаке.
     * @param attacker
     * @param damage
     */
    protected void notifyFriends(Creature attacker, int damage)
    {
        NpcInstance actor = getActor();
        if(System.currentTimeMillis() - _lastFactionNotifyTime > _minFactionNotifyInterval)
        {
            _lastFactionNotifyTime = System.currentTimeMillis();
            if(actor.isMinion())
            {
                //Оповестить лидера об атаке
                MonsterInstance master = ((MinionInstance) actor).getLeader();
                if(master != null)
                {
                    if(!master.isDead() && master.isVisible())
                        master.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, attacker, damage);
                    //Оповестить минионов лидера об атаке
                    MinionList minionList = master.getMinionList();
                    if(minionList != null)
                        minionList.getAliveMinions().stream().filter(minion -> minion != actor).forEach(minion -> minion.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, attacker, damage));
                }
            }
            //Оповестить своих минионов об атаке
            MinionList minionList = actor.getMinionList();
            if(minionList != null && minionList.hasAliveMinions())
                for(MinionInstance minion : minionList.getAliveMinions())
                    minion.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, attacker, damage);
            //Оповестить социальных мобов
            for(NpcInstance npc : activeFactionTargets())
                npc.getAI().notifyEvent(CtrlEvent.EVT_CLAN_ATTACKED, new Object[] { actor, attacker, damage });
        }
    }
    protected List<NpcInstance> activeFactionTargets()
    {
        NpcInstance actor = getActor();
        if(actor.getFaction().isNone())
            return Collections.emptyList();
        return World.getAroundNpc(actor).stream().filter(npc -> !npc.isDead()).filter(npc -> npc.isInFaction(actor)).filter(npc -> npc.isInRangeZ(actor, npc.getFaction().getRange())).filter(npc -> GeoEngine.canSeeTarget(npc, actor, false)).collect(Collectors.toCollection(() -> new LazyArrayList<>()));
    }
    protected boolean defaultThinkBuff(int rateSelf, int rateFriends)
    {
        NpcInstance actor = getActor();
        if(actor.isDead())
            return true;
        //TODO сделать более разумный выбор баффа, сначала выбирать подходящие а потом уже рандомно 1 из них
        if(Rnd.chance(rateSelf))
        {
            double actorHp = actor.getCurrentHpPercents();
            Skill[] skills = actorHp < 50 ? selectUsableSkills(actor, 0, _healSkills) : selectUsableSkills(actor, 0, _buffSkills);
            if(skills == null || skills.length == 0)
                return false;
            Skill skill = skills[Rnd.get(skills.length)];
            addTaskBuff(actor, skill);
            return true;
        }
        if(Rnd.chance(rateFriends))
        {
            for(NpcInstance npc : activeFactionTargets())
            {
                double targetHp = npc.getCurrentHpPercents();
                Skill[] skills = targetHp < 50 ? selectUsableSkills(actor, 0, _healSkills) : selectUsableSkills(actor, 0, _buffSkills);
                if(skills == null || skills.length == 0)
                    continue;
                Skill skill = skills[Rnd.get(skills.length)];
                addTaskBuff(actor, skill);
                return true;
            }
        }
        return false;
    }
    protected boolean defaultFightTask()
    {
        clearTasks();
        NpcInstance actor = getActor();
        if(actor.isDead() || actor.isAMuted())
            return false;
        Creature target;
        if((target = prepareTarget()) == null)
            return false;
        double distance = actor.getDistance(target);
        double targetHp = target.getCurrentHpPercents();
        double actorHp = actor.getCurrentHpPercents();
        Skill[] dam = Rnd.chance(getRateDAM()) ? selectUsableSkills(target, distance, _damSkills) : null;
        Skill[] dot = Rnd.chance(getRateDOT()) ? selectUsableSkills(target, distance, _dotSkills) : null;
        Skill[] debuff = targetHp > 10 ? Rnd.chance(getRateDEBUFF()) ? selectUsableSkills(target, distance, _debuffSkills) : null : null;
        Skill[] stun = Rnd.chance(getRateSTUN()) ? selectUsableSkills(target, distance, _stunSkills) : null;
        Skill[] heal = actorHp < 50 ? Rnd.chance(getRateHEAL()) ? selectUsableSkills(actor, 0, _healSkills) : null : null;
        Skill[] buff = Rnd.chance(getRateBUFF()) ? selectUsableSkills(actor, 0, _buffSkills) : null;
        RndSelector<Skill[]> rnd = new RndSelector<>();
        if(!actor.isAMuted())
            rnd.add(null, getRatePHYS());
        rnd.add(dam, getRateDAM());
        rnd.add(dot, getRateDOT());
        rnd.add(debuff, getRateDEBUFF());
        rnd.add(heal, getRateHEAL());
        rnd.add(buff, getRateBUFF());
        rnd.add(stun, getRateSTUN());
        Skill[] selected = rnd.select();
        if(selected != null)
        {
            if(selected == dam || selected == dot)
                return chooseTaskAndTargets(selectTopSkillByDamage(actor, target, distance, selected), target, distance);
            if(selected == debuff || selected == stun)
                return chooseTaskAndTargets(selectTopSkillByDebuff(target, distance, selected), target, distance);
            if(selected == buff)
                return chooseTaskAndTargets(selectTopSkillByBuff(actor, selected), actor, distance);
            if(selected == heal)
                return chooseTaskAndTargets(selectTopSkillByHeal(actor, selected), actor, distance);
        }
        // TODO сделать лечение и баф дружественных целей
        return chooseTaskAndTargets(null, target, distance);
    }
    public int getRatePHYS()
    {
        return 100;
    }
    public int getRateDOT()
    {
        return 0;
    }
    public int getRateDEBUFF()
    {
        return 0;
    }
    public int getRateDAM()
    {
        return 0;
    }
    public int getRateSTUN()
    {
        return 0;
    }
    public int getRateBUFF()
    {
        return 0;
    }
    public int getRateHEAL()
    {
        return 0;
    }
    public boolean getIsMobile()
    {
        return !getActor().getParameter("isImmobilized", false);
    }
    public int getMaxPathfindFails()
    {
        return 3;
    }
    /**
     * Задержка, перед переключением в активный режим после атаки, если цель не найдена (вне зоны досягаемости, убита, очищен хейт)
     * @return
     */
    public int getMaxAttackTimeout()
    {
        return 15000;
    }
    /**
     * Задержка, перед телепортом к цели, если не удается дойти
     * @return
     */
    public int getTeleportTimeout()
    {
        return 10000;
    }
}
	
		PHP:
	
	package l2p.gameserver.ai;
import static java.lang.Integer.MIN_VALUE;
import static java.lang.Long.MAX_VALUE;
import static java.lang.Math.abs;
import static java.lang.System.currentTimeMillis;
import static java.util.Arrays.copyOf;
import static java.util.Collections.emptyList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;
import static l2p.commons.collections.CollectionUtils.eqSort;
import l2p.commons.collections.LazyArrayList;
import static l2p.commons.collections.LazyArrayList.newInstance;
import static l2p.commons.collections.LazyArrayList.recycle;
import l2p.commons.lang.reference.HardReference;
import l2p.commons.math.random.RndSelector;
import l2p.commons.threading.RunnableImpl;
import static l2p.commons.util.Rnd.chance;
import static l2p.commons.util.Rnd.get;
import static l2p.gameserver.ai.CtrlEvent.EVT_AGGRESSION;
import static l2p.gameserver.ai.CtrlEvent.EVT_CLAN_ATTACKED;
import static l2p.gameserver.ai.CtrlEvent.EVT_THINK;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static l2p.gameserver.ai.DefaultAI.TaskType.ATTACK;
import static l2p.gameserver.ai.DefaultAI.TaskType.BUFF;
import static l2p.gameserver.ai.DefaultAI.TaskType.CAST;
import static l2p.gameserver.ai.DefaultAI.TaskType.MOVE;
import static l2p.gameserver.configs.Config.AWAY_PLAYER_TAKE_AGGRO;
import static l2p.gameserver.configs.proprties.AIConfig.AggroCheckInterval;
import static l2p.gameserver.configs.proprties.AIConfig.AiTaskActiveDelay;
import static l2p.gameserver.configs.proprties.AIConfig.AiTaskDelay;
import static l2p.gameserver.configs.proprties.AIConfig.BlockActiveTasks;
import static l2p.gameserver.configs.proprties.AIConfig.MaxDriftRange;
import static l2p.gameserver.configs.proprties.AIConfig.MaxPursueRange;
import static l2p.gameserver.configs.proprties.AIConfig.MaxPursueRangeRaid;
import static l2p.gameserver.configs.proprties.AIConfig.MaxPursueUnderGroundRange;
import static l2p.gameserver.configs.proprties.AIConfig.RndAnimationRate;
import static l2p.gameserver.configs.proprties.AIConfig.RndWalk;
import static l2p.gameserver.configs.proprties.AIConfig.RndWalkRate;
import static l2p.gameserver.data.xml.holder.NpcHolder.getInstance;
import static l2p.gameserver.geodata.GeoEngine.canMoveToCoord;
import static l2p.gameserver.geodata.GeoEngine.canSeeTarget;
import static l2p.gameserver.geodata.GeoEngine.getHeight;
import static l2p.gameserver.geodata.GeoEngine.moveCheckForAI;
import l2p.gameserver.model.AggroList.AggroInfo;
import l2p.gameserver.model.Creature;
import l2p.gameserver.model.MinionList;
import l2p.gameserver.model.Playable;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.Skill;
import static l2p.gameserver.model.Skill.SkillTargetType.TARGET_AURA;
import static l2p.gameserver.model.Skill.SkillTargetType.TARGET_SELF;
import static l2p.gameserver.model.World.getAroundCharacters;
import static l2p.gameserver.model.World.getAroundNpc;
import l2p.gameserver.model.WorldRegion;
import static l2p.gameserver.model.Zone.ZoneType.SIEGE;
import static l2p.gameserver.model.entity.SevenSigns.CABAL_NULL;
import l2p.gameserver.model.instances.MinionInstance;
import l2p.gameserver.model.instances.MonsterInstance;
import l2p.gameserver.model.instances.NpcInstance;
import static l2p.gameserver.model.quest.QuestEventType.ATTACKED_WITH_QUEST;
import l2p.gameserver.model.quest.QuestState;
import l2p.gameserver.serverpackets.MagicSkillUse;
import static l2p.gameserver.serverpackets.StatusUpdate.CUR_HP;
import static l2p.gameserver.serverpackets.StatusUpdate.MAX_HP;
import static l2p.gameserver.stats.Stats.MP_MAGIC_SKILL_CONSUME;
import static l2p.gameserver.stats.Stats.MP_PHYSICAL_SKILL_CONSUME;
import l2p.gameserver.utils.Location;
import static l2p.gameserver.utils.Location.findPointToStay;
import static l2p.gameserver.utils.NpcUtils.spawnSingle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class DefaultAI extends CharacterAI
{
    protected static final Logger _log = LoggerFactory.getLogger(DefaultAI.class);
    /**
     * @return the AI_TASK_ATTACK_DELAY
     */
    public long getAI_TASK_ATTACK_DELAY() {
        return AI_TASK_ATTACK_DELAY;
    }
    /**
     * @param AI_TASK_ATTACK_DELAY the AI_TASK_ATTACK_DELAY to set
     */
    public void setAI_TASK_ATTACK_DELAY(long AI_TASK_ATTACK_DELAY) {
        this.AI_TASK_ATTACK_DELAY = AI_TASK_ATTACK_DELAY;
    }
    /**
     * @return the AI_TASK_ACTIVE_DELAY
     */
    public long getAI_TASK_ACTIVE_DELAY() {
        return AI_TASK_ACTIVE_DELAY;
    }
    /**
     * @param AI_TASK_ACTIVE_DELAY the AI_TASK_ACTIVE_DELAY to set
     */
    public void setAI_TASK_ACTIVE_DELAY(long AI_TASK_ACTIVE_DELAY) {
        this.AI_TASK_ACTIVE_DELAY = AI_TASK_ACTIVE_DELAY;
    }
    /**
     * @return the AI_TASK_DELAY_CURRENT
     */
    public long getAI_TASK_DELAY_CURRENT() {
        return AI_TASK_DELAY_CURRENT;
    }
    /**
     * @param AI_TASK_DELAY_CURRENT the AI_TASK_DELAY_CURRENT to set
     */
    public void setAI_TASK_DELAY_CURRENT(long AI_TASK_DELAY_CURRENT) {
        this.AI_TASK_DELAY_CURRENT = AI_TASK_DELAY_CURRENT;
    }
    /**
     * @return the MAX_PURSUE_RANGE
     */
    public int getMAX_PURSUE_RANGE() {
        return MAX_PURSUE_RANGE;
    }
    /**
     * @param MAX_PURSUE_RANGE the MAX_PURSUE_RANGE to set
     */
    public void setMAX_PURSUE_RANGE(int MAX_PURSUE_RANGE) {
        this.MAX_PURSUE_RANGE = MAX_PURSUE_RANGE;
    }
    /**
     * @return the _aiTask
     */
    public ScheduledFuture<?> getAiTask() {
        return _aiTask;
    }
    /**
     * @param _aiTask the _aiTask to set
     */
    public void setAiTask(ScheduledFuture<?> _aiTask) {
        this._aiTask = _aiTask;
    }
    /**
     * @return the _runningTask
     */
    public ScheduledFuture<?> getRunningTask() {
        return _runningTask;
    }
    /**
     * @param _runningTask the _runningTask to set
     */
    public void setRunningTask(ScheduledFuture<?> _runningTask) {
        this._runningTask = _runningTask;
    }
    /**
     * @return the _madnessTask
     */
    public ScheduledFuture<?> getMadnessTask() {
        return _madnessTask;
    }
    /**
     * @param _madnessTask the _madnessTask to set
     */
    public void setMadnessTask(ScheduledFuture<?> _madnessTask) {
        this._madnessTask = _madnessTask;
    }
    /**
     * @return the _thinking
     */
    public boolean isThinking() {
        return _thinking;
    }
    /**
     * @param _thinking the _thinking to set
     */
    public void setThinking(boolean _thinking) {
        this._thinking = _thinking;
    }
    /**
     * @return the _def_think
     */
    public boolean isDef_think() {
        return _def_think;
    }
    /**
     * @param _def_think the _def_think to set
     */
    public void setDef_think(boolean _def_think) {
        this._def_think = _def_think;
    }
    /**
     * @return the _globalAggro
     */
    public long getGlobalAggro() {
        return _globalAggro;
    }
    /**
     * @return the _randomAnimationEnd
     */
    public long getRandomAnimationEnd() {
        return _randomAnimationEnd;
    }
    /**
     * @param _randomAnimationEnd the _randomAnimationEnd to set
     */
    public void setRandomAnimationEnd(long _randomAnimationEnd) {
        this._randomAnimationEnd = _randomAnimationEnd;
    }
    /**
     * @return the _pathfindFails
     */
    public int getPathfindFails() {
        return _pathfindFails;
    }
    /**
     * @param _pathfindFails the _pathfindFails to set
     */
    public void setPathfindFails(int _pathfindFails) {
        this._pathfindFails = _pathfindFails;
    }
    /**
     * @return the _tasks
     */
    public NavigableSet<Task> getTasks() {
        return _tasks;
    }
    /**
     * @param _tasks the _tasks to set
     */
    public void setTasks(NavigableSet<Task> _tasks) {
        this._tasks = _tasks;
    }
    /**
     * @return the _damSkills
     */
    public Skill[] getDamSkills() {
        return _damSkills;
    }
    /**
     * @param _damSkills the _damSkills to set
     */
    public void setDamSkills(Skill[] _damSkills) {
        this._damSkills = _damSkills;
    }
    /**
     * @return the _dotSkills
     */
    public Skill[] getDotSkills() {
        return _dotSkills;
    }
    /**
     * @param _dotSkills the _dotSkills to set
     */
    public void setDotSkills(Skill[] _dotSkills) {
        this._dotSkills = _dotSkills;
    }
    /**
     * @return the _debuffSkills
     */
    public Skill[] getDebuffSkills() {
        return _debuffSkills;
    }
    /**
     * @param _debuffSkills the _debuffSkills to set
     */
    public void setDebuffSkills(Skill[] _debuffSkills) {
        this._debuffSkills = _debuffSkills;
    }
    /**
     * @return the _healSkills
     */
    public Skill[] getHealSkills() {
        return _healSkills;
    }
    /**
     * @param _healSkills the _healSkills to set
     */
    public void setHealSkills(Skill[] _healSkills) {
        this._healSkills = _healSkills;
    }
    /**
     * @return the _buffSkills
     */
    public Skill[] getBuffSkills() {
        return _buffSkills;
    }
    /**
     * @param _buffSkills the _buffSkills to set
     */
    public void setBuffSkills(Skill[] _buffSkills) {
        this._buffSkills = _buffSkills;
    }
    /**
     * @return the _stunSkills
     */
    public Skill[] getStunSkills() {
        return _stunSkills;
    }
    /**
     * @param _stunSkills the _stunSkills to set
     */
    public void setStunSkills(Skill[] _stunSkills) {
        this._stunSkills = _stunSkills;
    }
    /**
     * @return the _lastActiveCheck
     */
    public long getLastActiveCheck() {
        return _lastActiveCheck;
    }
    /**
     * @param _lastActiveCheck the _lastActiveCheck to set
     */
    public void setLastActiveCheck(long _lastActiveCheck) {
        this._lastActiveCheck = _lastActiveCheck;
    }
    /**
     * @return the _checkAggroTimestamp
     */
    public long getCheckAggroTimestamp() {
        return _checkAggroTimestamp;
    }
    /**
     * @param _checkAggroTimestamp the _checkAggroTimestamp to set
     */
    public void setCheckAggroTimestamp(long _checkAggroTimestamp) {
        this._checkAggroTimestamp = _checkAggroTimestamp;
    }
    /**
     * @return the _lastFactionNotifyTime
     */
    public long getLastFactionNotifyTime() {
        return _lastFactionNotifyTime;
    }
    /**
     * @param _lastFactionNotifyTime the _lastFactionNotifyTime to set
     */
    public void setLastFactionNotifyTime(long _lastFactionNotifyTime) {
        this._lastFactionNotifyTime = _lastFactionNotifyTime;
    }
    /**
     * @return the _minFactionNotifyInterval
     */
    public long getMinFactionNotifyInterval() {
        return _minFactionNotifyInterval;
    }
    /**
     * @param _minFactionNotifyInterval the _minFactionNotifyInterval to set
     */
    public void setMinFactionNotifyInterval(long _minFactionNotifyInterval) {
        this._minFactionNotifyInterval = _minFactionNotifyInterval;
    }
    /**
     * @return the _nearestTargetComparator
     */
    public Comparator<Creature> getNearestTargetComparator() {
        return _nearestTargetComparator;
    }
    /**
     * @param _nearestTargetComparator the _nearestTargetComparator to set
     */
    public void setNearestTargetComparator(Comparator<Creature> _nearestTargetComparator) {
        this._nearestTargetComparator = _nearestTargetComparator;
    }
    public static enum TaskType
    {
        MOVE,
        ATTACK,
        CAST,
        BUFF
    }
    
    public static final int TaskDefaultWeight = 10000;
    public static class Task
    {
        private TaskType type;
        private Skill skill;
        private HardReference<? extends Creature> target;
        private Location loc;
        private boolean pathfind;
        private int weight = TaskDefaultWeight;
        /**
         * @return the type
         */
        public TaskType getType() {
            return type;
        }
        /**
         * @param type the type to set
         */
        public void setType(TaskType type) {
            this.type = type;
        }
        /**
         * @return the skill
         */
        public Skill getSkill() {
            return skill;
        }
        /**
         * @param skill the skill to set
         */
        public void setSkill(Skill skill) {
            this.skill = skill;
        }
        /**
         * @return the target
         */
        public HardReference<? extends Creature> getTarget() {
            return target;
        }
        /**
         * @param target the target to set
         */
        public void setTarget(HardReference<? extends Creature> target) {
            this.target = target;
        }
        /**
         * @return the loc
         */
        public Location getLoc() {
            return loc;
        }
        /**
         * @param loc the loc to set
         */
        public void setLoc(Location loc) {
            this.loc = loc;
        }
        /**
         * @return the pathfind
         */
        public boolean isPathfind() {
            return pathfind;
        }
        /**
         * @param pathfind the pathfind to set
         */
        public void setPathfind(boolean pathfind) {
            this.pathfind = pathfind;
        }
        /**
         * @return the weight
         */
        public int getWeight() {
            return weight;
        }
        /**
         * @param weight the weight to set
         */
        public void setWeight(int weight) {
            this.weight = weight;
        }
    }
    public void addTaskCast(Creature target, Skill skill)
    {
        Task task = new Task();
                task.setType(CAST);
                task.setTarget(target.getRef());
                task.setSkill(skill);
        getTasks().add(task);
        setDef_think(true);
    }
    public void addTaskBuff(Creature target, Skill skill)
    {
        Task task = new Task();
                task.setType(BUFF);
                task.setTarget(target.getRef());
                task.setSkill(skill);
        getTasks().add(task);
        setDef_think(true);
    }
    public void addTaskAttack(Creature target)
    {
        Task task = new Task();
                task.setType(ATTACK);
                task.setTarget(target.getRef());
        getTasks().add(task);
        setDef_think(true);
    }
    public void addTaskAttack(Creature target, Skill skill, int weight)
    {
        Task task = new Task();
                task.setType(skill.isOffensive() ? CAST : BUFF);
                task.setTarget(target.getRef());
                task.setSkill(skill);
                task.setWeight(weight);
        getTasks().add(task);
        setDef_think(true);
    }
    public void addTaskMove(Location loc, boolean pathfind)
    {
        Task task = new Task();
                task.setType(MOVE);
                task.setLoc(loc);
                task.setPathfind(pathfind);
        getTasks().add(task);
        setDef_think(true);
    }
    protected void addTaskMove(int locX, int locY, int locZ, boolean pathfind)
    {
        addTaskMove(new Location(locX, locY, locZ), pathfind);
    }
    private static class TaskComparator implements Comparator<Task>
    {
        private static final Comparator<Task> instance = new TaskComparator();
        public static Comparator<Task> getInstance()
        {
            return instance;
        }
        @Override
        public int compare(Task o1, Task o2)
        {
            if(o1 == null || o2 == null)
                return 0;
            return o2.getWeight() - o1.getWeight();
        }
    }
    protected class Teleport extends RunnableImpl
    {
        private Location _destination;
        public Teleport(Location destination)
        {
            _destination = destination;
        }
        @Override
        public void runImpl() throws Exception
        {
            NpcInstance actor = getActor();
            if(actor != null)
                actor.teleToLocation(getDestination());
        }
        /**
         * @return the _destination
         */
        public Location getDestination() {
            return _destination;
        }
        /**
         * @param _destination the _destination to set
         */
        public void setDestination(Location _destination) {
            this._destination = _destination;
        }
    }
    protected class RunningTask extends RunnableImpl
    {
        @Override
        public void runImpl() throws Exception
        {
            NpcInstance actor = getActor();
            if(actor != null)
                actor.setRunning();
            setRunningTask(null);
        }
    }
    protected class MadnessTask extends RunnableImpl
    {
        @Override
        public void runImpl() throws Exception
        {
            NpcInstance actor = getActor();
            if(actor != null)
                actor.stopConfused();
            setMadnessTask(null);
        }
    }
    protected class NearestTargetComparator implements Comparator<Creature>
    {
        private final Creature actor;
        public NearestTargetComparator(Creature actor)
        {
            this.actor = actor;
        }
        @Override
        public int compare(Creature o1, Creature o2)
        {
            double diff = actor.getDistance3D(o1) - actor.getDistance3D(o2);
            if(diff < 0)
                return -1;
            return diff > 0 ? 1 : 0;
        }
    }
    private long AI_TASK_ATTACK_DELAY = AiTaskDelay;
    private long AI_TASK_ACTIVE_DELAY = AiTaskActiveDelay;
    private long AI_TASK_DELAY_CURRENT = AI_TASK_ACTIVE_DELAY;
    private int MAX_PURSUE_RANGE;
    private ScheduledFuture<?> _aiTask;
    private ScheduledFuture<?> _runningTask;
    private ScheduledFuture<?> _madnessTask;
    /** The flag used to indicate that a thinking action is in progress */
    private boolean _thinking = false;
    /** Показывает, есть ли задания */
    private boolean _def_think = false;
    /** The L2NpcInstance aggro counter */
    private long _globalAggro;
    private long _randomAnimationEnd;
    private int _pathfindFails;
    /** Список заданий */
    private NavigableSet<Task> _tasks = new ConcurrentSkipListSet<>(getInstance());
    private Skill[] _damSkills;
    private Skill[] _dotSkills;
    /** Время актуальности состояния атаки */
    private Skill[] _debuffSkills;
    private Skill[] _healSkills;
    private Skill[] _buffSkills;
    private Skill[] _stunSkills;
    private long _lastActiveCheck;
    private long _checkAggroTimestamp = 0;
    private long _attackTimeout;
    private long _lastFactionNotifyTime = 0;
    private long _minFactionNotifyInterval = 10000;
    private Comparator<Creature> _nearestTargetComparator;
    public DefaultAI(NpcInstance actor)
    {
        super(actor);
        setAttackTimeout(MAX_VALUE);
        NpcInstance npc = getActor();
        _damSkills = npc.getTemplate().getDamageSkills();
        _dotSkills = npc.getTemplate().getDotSkills();
        _debuffSkills = npc.getTemplate().getDebuffSkills();
        _buffSkills = npc.getTemplate().getBuffSkills();
        _stunSkills = npc.getTemplate().getStunSkills();
        _healSkills = npc.getTemplate().getHealSkills();
        _nearestTargetComparator = new NearestTargetComparator(actor);
        // Preload some AI params
        MAX_PURSUE_RANGE = actor.getParameter("MaxPursueRange", actor.isRaid() ? MaxPursueRangeRaid : npc.isUnderground() ? MaxPursueUnderGroundRange : MaxPursueRange);
        _minFactionNotifyInterval = actor.getParameter("FactionNotifyInterval", 10000);
    }
    @Override
    public void runImpl() throws Exception
    {
        if(getAiTask() == null)
            return;
        // проверяем, если NPC вышел в неактивный регион, отключаем AI
        if(!isGlobalAI() && currentTimeMillis() - getLastActiveCheck() > 60000L)
        {
            setLastActiveCheck(currentTimeMillis());
            NpcInstance actor = getActor();
            WorldRegion region = actor == null ? null : actor.getCurrentRegion();
            if(region == null || !region.isActive())
            {
                stopAITask();
                return;
            }
        }
        onEvtThink();
    }
    @Override
    //public final synchronized void startAITask()
    public synchronized void startAITask()
    {
        if(getAiTask() == null)
        {
            setAI_TASK_DELAY_CURRENT(getAI_TASK_ACTIVE_DELAY());
            setAiTask(getInstance().scheduleAtFixedRate(this, 0L, getAI_TASK_DELAY_CURRENT()));
        }
    }
    //protected final synchronized void switchAITask(long NEW_DELAY)
    protected synchronized void switchAITask(long NEW_DELAY)
    {
        if(getAiTask() == null)
            return;
        if(getAI_TASK_DELAY_CURRENT() != NEW_DELAY)
        {
            getAiTask().cancel(false);
            setAI_TASK_DELAY_CURRENT(NEW_DELAY);
            setAiTask(getInstance().scheduleAtFixedRate(this, 0L, getAI_TASK_DELAY_CURRENT()));
        }
    }
    @Override
    public final synchronized void stopAITask()
    {
        if(getAiTask() != null)
        {
            getAiTask().cancel(false);
            setAiTask(null);
        }
    }
    /**
     * Определяет, может ли этот тип АИ видеть персонажей в режиме Silent Move.
     * @param target L2Playable цель
     * @return true если цель видна в режиме Silent Move
     */
    protected boolean canSeeInSilentMove(Playable target) {
        return getActor().getParameter("canSeeInSilentMove", false) || !target.isSilentMoving();
    }
    protected boolean canSeeInHide(Playable target)
    {
        return getActor().getParameter("canSeeInHide", false) || !target.isInvisible();
    }
    protected boolean checkAggression(Creature target)
    {
        NpcInstance actor = getActor();
        if(getIntention() != AI_INTENTION_ACTIVE || !isGlobalAggro())
            return false;
        if(target.isAlikeDead())
            return false;
        if(target.isNpc() && target.isInvul())
            return false;
        if(target.isPlayer() && target.getPlayer().isInAwayingMode() && !AWAY_PLAYER_TAKE_AGGRO)
            return false;
        if(target.isPlayable())
        {
            if(!canSeeInSilentMove((Playable) target))
                return false;
            if(!canSeeInHide((Playable) target))
                return false;
            if(actor.getFaction().getName().equalsIgnoreCase("varka_silenos_clan") && target.getPlayer().getVarka() > 0)
                return false;
            if(actor.getFaction().getName().equalsIgnoreCase("ketra_orc_clan") && target.getPlayer().getKetra() > 0)
                return false;
            /*if(target.isFollow && !target.isPlayer() && target.getFollowTarget() != null && target.getFollowTarget().isPlayer())
                    return;*/
            if(target.isPlayer() && ((Player) target).isGM() && target.isInvisible())
                return false;
            if(((Playable) target).getNonAggroTime() > currentTimeMillis())
                return false;
            if(target.isPlayer() && !target.getPlayer().isActive())
                return false;
            if(actor.isMonster() && target.isInZonePeace())
                return false;
        }
        AggroInfo ai = actor.getAggroList().get(target);
        if(ai != null && ai.hate > 0)
        {
            if(!target.isInRangeZ(actor.getSpawnedLoc(), MAX_PURSUE_RANGE))
                return false;
        }
        else if(!actor.isAggressive() || !target.isInRangeZ(actor.getSpawnedLoc(), actor.getAggroRange()))
            return false;
        if(!canAttackCharacter(target))
            return false;
        if(!canSeeTarget(actor, target, false))
            return false;
        actor.getAggroList().addDamageHate(target, 0, 2);
        if((target.isSummon() || target.isPet()))
            actor.getAggroList().addDamageHate(target.getPlayer(), 0, 1);
        startRunningTask(getAI_TASK_ATTACK_DELAY());
        setIntention(AI_INTENTION_ATTACK, target);
        return true;
    }
    protected void setIsInRandomAnimation(long time)
    {
        setRandomAnimationEnd(currentTimeMillis() + time);
    }
    protected boolean randomAnimation()
    {
        NpcInstance actor = getActor();
        if(actor.getParameter("noRandomAnimation", false))
            return false;
        if(actor.hasRandomAnimation() && !actor.isActionsDisabled() && !actor.isMoving && !actor.isInCombat() && chance(RndAnimationRate))
        {
            setIsInRandomAnimation(3000);
            actor.onRandomAnimation();
            return true;
        }
        return false;
    }
    protected boolean randomWalk() {
        NpcInstance actor = getActor();
        return !actor.getParameter("noRandomWalk", false) && !actor.isMoving && maybeMoveToHome();
    }
    /**
     * @return true если действие выполнено, false если нет
     */
    protected boolean thinkActive() {
        NpcInstance actor = getActor();
        if (actor.isActionsDisabled())
            return true;
        if (getRandomAnimationEnd() > currentTimeMillis())
            return true;
        if (isDef_think()) {
            if (doTask())
                clearTasks();
            return true;
        }
        long now = currentTimeMillis();
        if (now - getCheckAggroTimestamp() > AggroCheckInterval) {
            setCheckAggroTimestamp(now);
            boolean aggressive = chance(actor.getParameter("SelfAggressive", actor.isAggressive() ? 100 : 0));
            if (!actor.getAggroList().isEmpty() || aggressive) {
                List<Creature> chars = getAroundCharacters(actor);
                eqSort(chars, getNearestTargetComparator());
                for (Creature cha : chars) {
                    if (aggressive || actor.getAggroList().get(cha) != null)
                        if (checkAggression(cha))
                            return true;
                }
            }
        }
        if (actor.isMinion()) {
            MonsterInstance leader = ((MinionInstance) actor).getLeader();
            if (leader != null) {
                double distance = actor.getDistance(leader.getX(), leader.getY());
                if (distance > 1000)
                    actor.teleToLocation(leader.getMinionPosition());
                else if (distance > 200)
                    addTaskMove(leader.getMinionPosition(), false);
                return true;
            }
        }
        return randomAnimation() || randomWalk();
    }
    @Override
    protected void onIntentionIdle()
    {
        NpcInstance actor = getActor();
        // Удаляем все задания
        clearTasks();
        actor.stopMove();
        actor.getAggroList().clear(true);
        setAttackTimeout(MAX_VALUE);
        setAttackTarget(null);
        changeIntention(AI_INTENTION_IDLE, null, null);
    }
    @Override
    protected void onIntentionActive()
    {
        NpcInstance actor = getActor();
        actor.stopMove();
        setAttackTimeout(MAX_VALUE);
        if(getIntention() != AI_INTENTION_ACTIVE)
        {
            switchAITask(getAI_TASK_ACTIVE_DELAY());
            changeIntention(AI_INTENTION_ACTIVE, null, null);
        }
        onEvtThink();
    }
    @Override
    protected void onIntentionAttack(Creature target)
    {
        NpcInstance actor = getActor();
        // Удаляем все задания
        clearTasks();
        actor.stopMove();
        setAttackTarget(target);
        setAttackTimeout(getMaxAttackTimeout() + currentTimeMillis());
        setGlobalAggro(0);
        if(getIntention() != AI_INTENTION_ATTACK)
        {
            changeIntention(AI_INTENTION_ATTACK, target, null);
            switchAITask(getAI_TASK_ATTACK_DELAY());
        }
        onEvtThink();
    }
    protected boolean canAttackCharacter(Creature target)
    {
        return target.isPlayable();
    }
    protected boolean checkTarget(Creature target, int range)
    {
        NpcInstance actor = getActor();
        if(target == null || target.isAlikeDead() || !actor.isInRangeZ(target, range))
            return false;
        // если не видим чаров в хайде - не атакуем их
        final boolean hided = target.isPlayable() && !canSeeInHide((Playable)target);
        if(!hided && actor.isConfused())
            return true;
        //В состоянии атаки атакуем всех, на кого у нас есть хейт
        if(getIntention() == AI_INTENTION_ATTACK)
        {
            AggroInfo ai = actor.getAggroList().get(target);
            if (ai != null)
            {
                if (hided)
                {
                    ai.hate = 0; // очищаем хейт
                    return false;
                }
                return ai.hate > 0;
            }
            return false;
        }
        return canAttackCharacter(target);
    }
    public void setAttackTimeout(long time)
    {
        _attackTimeout = time;
    }
    protected long getAttackTimeout()
    {
        return _attackTimeout;
    }
    protected void thinkAttack()
    {
        NpcInstance actor = getActor();
        if(actor.isDead())
            return;
        Location loc = actor.getSpawnedLoc();
        if(!actor.isInRange(loc, MAX_PURSUE_RANGE))
        {
            returnHome();
            return;
        }
        if(doTask() && !actor.isAttackingNow() && !actor.isCastingNow())
        {
            if(!createNewTask())
            {
                if(currentTimeMillis() > getAttackTimeout())
                    returnHome();
            }
        }
    }
    @Override
    protected void onEvtSpawn()
    {
        setGlobalAggro(currentTimeMillis() + getActor().getParameter("globalAggro", 10000L));
        setIntention(AI_INTENTION_ACTIVE);
    }
    @Override
    protected void onEvtReadyToAct()
    {
        onEvtThink();
    }
    @Override
    protected void onEvtArrivedTarget()
    {
        onEvtThink();
    }
    @Override
    protected void onEvtArrived()
    {
        onEvtThink();
    }
    protected boolean tryMoveToTarget(Creature target)
    {
        NpcInstance actor = getActor();
        if(target.isInvisible())
        {
            notifyEvent(EVT_THINK);
            return false;
        }
        if(!actor.followToCharacter(target, actor.getPhysicalAttackRange(), true))
            setPathfindFails(getPathfindFails() + 1);
        if(getPathfindFails() >= getMaxPathfindFails() && (currentTimeMillis() > getAttackTimeout() - getMaxAttackTimeout() + getTeleportTimeout()) && actor.isInRange(target, getMAX_PURSUE_RANGE()))
        {
            setPathfindFails(0);
            if(target.isPlayable())
            {
                AggroInfo hate = actor.getAggroList().get(target);
                if(hate == null || hate.hate < 100)
                {
                    returnHome();
                    return false;
                }
            }
            Location loc = moveCheckForAI(target.getLoc(), actor.getLoc(), actor.getGeoIndex());
            if(!canMoveToCoord(actor.getX(), actor.getY(), actor.getZ(), loc.x, loc.y, loc.z, actor.getGeoIndex())) // Для подстраховки
                loc = target.getLoc();
            actor.teleToLocation(loc);
        }
        return true;
    }
    protected boolean maybeNextTask(Task currentTask)
    {
        // Следующее задание
        getTasks().remove(currentTask);
        // Если заданий больше нет - определить новое
        return getTasks().isEmpty();
    }
    protected boolean doTask()
    {
        NpcInstance actor = getActor();
        if(!isDef_think())
            return true;
        Task currentTask = getTasks().pollFirst();
        if(currentTask == null)
        {
            clearTasks();
            return true;
        }
        if(actor.isDead() || actor.isAttackingNow() || actor.isCastingNow())
            return false;
        switch(currentTask.getType())
        {
            // Задание "прибежать в заданные координаты"
            case MOVE:
            {
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                if(actor.isInRange(currentTask.getLoc(), 100))
                    return maybeNextTask(currentTask);
                if(actor.isMoving)
                    return false;
                if(!actor.moveToLocation(currentTask.loc, 0, currentTask.pathfind))
                {
                    clientStopMoving();
                    setPathfindFails(0);
                    actor.teleToLocation(currentTask.getLoc());
                    //actor.broadcastPacketToOthers(new MagicSkillUse(actor, actor, 2036, 1, 500, 600000));
                    //ThreadPoolManager.getInstance().scheduleAi(new Teleport(currentTask.loc), 500, false);
                    return maybeNextTask(currentTask);
                }
            }
                break;
            // Задание "добежать - ударить"
            case ATTACK:
            {
                Creature target = currentTask.getTarget().get();
                if(!checkTarget(target, MAX_PURSUE_RANGE))
                    return true;
                setAttackTarget(target);
                if(actor.isMoving)
                    return chance(25);
                if(actor.getRealDistance3D(target) <= actor.getPhysicalAttackRange() + 40 && canSeeTarget(actor, target, false))
                {
                    clientStopMoving();
                    setPathfindFails(0);
                    setAttackTimeout(getMaxAttackTimeout() + currentTimeMillis());
                    actor.doAttack(target);
                    return maybeNextTask(currentTask);
                }
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                tryMoveToTarget(target);
            }
                break;
            // Задание "добежать - атаковать скиллом"
            case CAST:
            {
                Creature target = currentTask.getTarget().get();
                if(actor.isMuted(currentTask.getSkill()) || actor.isSkillDisabled(currentTask.getSkill()) || actor.isUnActiveSkill(currentTask.getSkill().getId()))
                    return true;
                boolean isAoE = currentTask.getSkill().getTargetType() == TARGET_AURA;
                int castRange = currentTask.getSkill().getAOECastRange();
                if(!checkTarget(target, MAX_PURSUE_RANGE + castRange))
                    return true;
                setAttackTarget(target);
                if(actor.getRealDistance3D(target) <= castRange + 60 && canSeeTarget(actor, target, false))
                {
                    clientStopMoving();
                    setPathfindFails(0);
                    setAttackTimeout(getMaxAttackTimeout() + currentTimeMillis());
                    actor.doCast(currentTask.getSkill(), isAoE ? actor : target, !target.isPlayable());
                    return maybeNextTask(currentTask);
                }
                if(actor.isMoving)
                    return chance(10);
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                tryMoveToTarget(target);
            }
                break;
            // Задание "добежать - применить скилл"
            case BUFF:
            {
                Creature target = currentTask.getTarget().get();
                if(actor.isMuted(currentTask.getSkill()) || actor.isSkillDisabled(currentTask.getSkill()) || actor.isUnActiveSkill(currentTask.getSkill().getId()))
                    return true;
                if(target == null || target.isAlikeDead() || !actor.isInRange(target, 2000))
                    return true;
                boolean isAoE = currentTask.getSkill().getTargetType() == TARGET_AURA;
                int castRange = currentTask.getSkill().getAOECastRange();
                if(actor.isMoving)
                    return chance(10);
                if(actor.getRealDistance3D(target) <= castRange + 60 && canSeeTarget(actor, target, false))
                {
                    clientStopMoving();
                    setPathfindFails(0);
                    actor.doCast(currentTask.getSkill(), isAoE ? actor : target, !target.isPlayable());
                    return maybeNextTask(currentTask);
                }
                if(actor.isMovementDisabled() || !getIsMobile())
                    return true;
                tryMoveToTarget(target);
            }
                break;
        }
        return false;
    }
    protected boolean createNewTask()
    {
        return false;
    }
    protected boolean defaultNewTask()
    {
        clearTasks();
        NpcInstance actor = getActor();
        Creature target;
        if(actor == null || (target = prepareTarget()) == null)
            return false;
        double distance = actor.getDistance(target);
        return chooseTaskAndTargets(null, target, distance);
    }
    @Override
    protected void onEvtThink()
    {
        NpcInstance actor = getActor();
        if(isThinking() || actor == null || actor.isActionsDisabled() || actor.isAfraid())
            return;
        if(getRandomAnimationEnd() > currentTimeMillis())
            return;
        if(actor.isRaid() && (actor.isInZonePeace() || actor.isInZoneBattle() || actor.isInZone(SIEGE)))
        {
            teleportHome();
            return;
        }
        setThinking(true);
        try
        {
            if(!BlockActiveTasks && getIntention() == AI_INTENTION_ACTIVE)
                thinkActive();
            else if(getIntention() == AI_INTENTION_ATTACK)
                thinkAttack();
        }
        finally
        {
            setThinking(false);
        }
    }
    @Override
    protected void onEvtDead(Creature killer)
    {
        NpcInstance actor = getActor();
        int transformer = actor.getParameter("transformOnDead", 0);
        int chance = actor.getParameter("transformChance", 100);
        if(transformer > 0 && chance(chance))
        {
            NpcInstance npc = spawnSingle(transformer, actor.getLoc(), actor.getReflection()) ;
            if(killer != null && killer.isPlayable())
            {
                npc.getAI().notifyEvent(EVT_AGGRESSION, killer, 100);
                killer.setTarget(npc);
                killer.sendPacket(npc.makeStatusUpdate(CUR_HP, MAX_HP));
            }
        }
        super.onEvtDead(killer);
    }
    @Override
    protected void onEvtClanAttacked(Creature attacked, Creature attacker, int damage)
    {
        if(getIntention() != AI_INTENTION_ACTIVE || !isGlobalAggro())
            return;
        notifyEvent(EVT_AGGRESSION, attacker, 2);
    }
    @Override
    protected void onEvtAttacked(Creature attacker, int damage)
    {
        NpcInstance actor = getActor();
        if(attacker == null || actor.isDead())
            return;
        int transformer = actor.getParameter("transformOnUnderAttack", 0);
        if(transformer > 0)
        {
            int chance = actor.getParameter("transformChance", 5);
            if(chance == 100 || ((MonsterInstance) actor).getChampion() == 0 && actor.getCurrentHpPercents() > 50 && chance(chance))
            {
                MonsterInstance npc = (MonsterInstance) getInstance().getTemplate(transformer).getNewInstance();
                npc.setSpawnedLoc(actor.getLoc());
                npc.setReflection(actor.getReflection());
                npc.setChampion(((MonsterInstance) actor).getChampion());
                npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp(), true);
                npc.spawnMe(npc.getSpawnedLoc());
                npc.getAI().notifyEvent(EVT_AGGRESSION, attacker, 100);
                actor.doDie(actor);
                actor.decayMe();
                attacker.setTarget(npc);
                attacker.sendPacket(npc.makeStatusUpdate(CUR_HP, MAX_HP));
                return;
            }
        }
        Player player = attacker.getPlayer();
        if(player != null)
        {
            //FIXME [G1ta0] затычка для 7 печатей, при атаке монстра 7 печатей телепортирует персонажа в ближайший город
            if((getInstance().isSealValidationPeriod() || getInstance().isCompResultsPeriod()) && actor.isSevenSignsMonster())
            {
                int pcabal = getInstance().getPlayerCabal(player);
                int wcabal = getInstance().getCabalHighestScore();
                if(pcabal != wcabal && wcabal != CABAL_NULL)
                {
                    player.sendMessage("You have been teleported to the nearest town because you not signed for winning cabal.");
                    player.teleToClosestTown();
                    return;
                }
            }
            List<QuestState> quests = player.getQuestsForEvent(actor, ATTACKED_WITH_QUEST);
            if(quests != null)
                for(QuestState qs : quests)
                    qs.getQuest().notifyAttack(actor, qs);
        }
        //Добавляем только хейт, урон, если атакующий - игровой персонаж, будет добавлен в L2NpcInstance.onReduceCurrentHp
        actor.getAggroList().addDamageHate(attacker, 0, damage);
        // Обычно 1 хейт добавляется хозяину суммона, чтобы после смерти суммона моб накинулся на хозяина.
        if(damage > 0 && (attacker.isSummon() || attacker.isPet()))
            actor.getAggroList().addDamageHate(attacker.getPlayer(), 0, actor.getParameter("searchingMaster", false) ? damage : 1);
        if(getIntention() != AI_INTENTION_ATTACK)
        {
            if(!actor.isRunning())
                startRunningTask(getAI_TASK_ATTACK_DELAY());
            setIntention(AI_INTENTION_ATTACK, attacker);
        }
        notifyFriends(attacker, damage);
    }
    @Override
    protected void onEvtAggression(Creature attacker, int aggro)
    {
        NpcInstance actor = getActor();
        if(attacker == null || actor.isDead())
            return;
        actor.getAggroList().addDamageHate(attacker, 0, aggro);
        // Обычно 1 хейт добавляется хозяину суммона, чтобы после смерти суммона моб накинулся на хозяина.
        if(aggro > 0 && (attacker.isSummon() || attacker.isPet()))
            actor.getAggroList().addDamageHate(attacker.getPlayer(), 0, actor.getParameter("searchingMaster", false) ? aggro : 1);
        if(getIntention() != AI_INTENTION_ATTACK)
        {
            if(!actor.isRunning())
                startRunningTask(getAI_TASK_ATTACK_DELAY());
            setIntention(AI_INTENTION_ATTACK, attacker);
        }
    }
    protected boolean maybeMoveToHome()
    {
        NpcInstance actor = getActor();
        if(actor.isDead())
            return false;
        boolean randomWalk = actor.hasRandomWalk();
        Location sloc = actor.getSpawnedLoc();
        // Random walk or not?
        if(randomWalk && (!RndWalk || !chance(RndWalkRate)))
            return false;
        boolean isInRange = actor.isInRangeZ(sloc, MaxDriftRange);
        if(!randomWalk && isInRange)
            return false;
        Location pos = findPointToStay(actor, sloc, 0, MaxDriftRange);
        actor.setWalking();
        // Телепортируемся домой, только если далеко от дома
        if(!actor.moveToLocation(pos.x, pos.y, pos.z, 0, true) && !isInRange)
            teleportHome();
        return true;
    }
    protected void returnHome()
    {
        returnHome(true, false);
    }
    protected void teleportHome()
    {
        returnHome(true, true);
    }
    protected void returnHome(boolean clearAggro, boolean teleport)
    {
        NpcInstance actor = getActor();
        Location sloc = actor.getSpawnedLoc();
        // Удаляем все задания
        clearTasks();
        actor.stopMove();
        if(clearAggro)
            actor.getAggroList().clear(true);
        setAttackTimeout(MAX_VALUE);
        setAttackTarget(null);
        changeIntention(AI_INTENTION_ACTIVE, null, null);
        if(teleport)
        {
            actor.broadcastPacketToOthers(new MagicSkillUse(actor, actor, 2036, 1, 500, 0));
            actor.teleToLocation(sloc.x, sloc.y, getHeight(sloc, actor.getGeoIndex()));
        }
        else
        {
            if(!clearAggro)
                actor.setRunning();
            else
                actor.setWalking();
            addTaskMove(sloc, false);
        }
    }
    protected Creature prepareTarget()
    {
        NpcInstance actor = getActor();
        if(actor.isConfused())
            return getAttackTarget();
        // Для "двинутых" боссов, иногда, выбираем случайную цель
        if(chance(actor.getParameter("isMadness", 0)))
        {
            Creature randomHated = actor.getAggroList().getRandomHated();
            if(randomHated != null)
            {
                setAttackTarget(randomHated);
                if(getMadnessTask() == null && !actor.isConfused())
                {
                    actor.startConfused();
                    setMadnessTask(getInstance().schedule(new MadnessTask(), 10000));
                }
                return randomHated;
            }
        }
        // Новая цель исходя из агрессивности
        List<Creature> hateList = actor.getAggroList().getHateList();
        Creature hated = null;
        for(Creature cha : hateList)
        {
            // Если у Монстра есть скилл "Searching Master" он должен атаковать хозяина пета в первую очередь.
            if((cha.isPet() || cha.isSummon()) && cha.getPlayer() != null)
                if(getActor().getSkillLevel(6019) == 1 && checkTarget(cha.getPlayer(), getMAX_PURSUE_RANGE()))
                    cha = cha.getPlayer();
            //Не подходит, очищаем хейт
            if(!checkTarget(cha, MAX_PURSUE_RANGE))
            {
                actor.getAggroList().remove(cha, true);
                continue;
            }
            hated = cha;
            break;
        }
        if(hated != null)
        {
            setAttackTarget(hated);
            return hated;
        }
        return null;
    }
    protected boolean canUseSkill(Skill skill, Creature target, double distance)
    {
        NpcInstance actor = getActor();
        if(skill == null || skill.isNotUsedByAI())
            return false;
        if(skill.getTargetType() == TARGET_SELF && target != actor)
            return false;
        int castRange = skill.getAOECastRange();
        if(castRange <= 200 && distance > 200)
            return false;
        if(actor.isSkillDisabled(skill) || actor.isMuted(skill) || actor.isUnActiveSkill(skill.getId()))
            return false;
        double mpConsume2 = skill.getMpConsume2();
        if(skill.isMagic())
            mpConsume2 = actor.calcStat(MP_MAGIC_SKILL_CONSUME, mpConsume2, target, skill);
        else
            mpConsume2 = actor.calcStat(MP_PHYSICAL_SKILL_CONSUME, mpConsume2, target, skill);
        if(actor.getCurrentMp() < mpConsume2)
            return false;
        return target.getEffectList().getEffectsCountForSkill(skill.getId()) == 0;
    }
    protected boolean canUseSkill(Skill sk, Creature target)
    {
        return canUseSkill(sk, target, 0);
    }
    protected Skill[] selectUsableSkills(Creature target, double distance, Skill[] skills)
    {
        if(skills == null || skills.length == 0 || target == null)
            return null;
        Skill[] ret = null;
        int usable = 0;
        for(Skill skill : skills)
            if(canUseSkill(skill, target, distance))
            {
                if(ret == null)
                    ret = new Skill[skills.length];
                ret[usable++] = skill;
            }
        if(ret == null || usable == skills.length)
            return ret;
        if(usable == 0)
            return null;
        ret = copyOf(ret, usable);
        return ret;
    }
    protected static Skill selectTopSkillByDamage(Creature actor, Creature target, double distance, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            weight = skill.getSimpleDamage(actor, target) * skill.getAOECastRange() / distance;
            if(weight < 1.)
                weight = 1.;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected static Skill selectTopSkillByDebuff(Creature target, double distance, Skill[] skills) //FIXME
    {
        if(skills == null || skills.length == 0)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            if(skill.getSameByStackType(target) != null)
                continue;
            if((weight = 100. * skill.getAOECastRange() / distance) <= 0)
                weight = 1;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected static Skill selectTopSkillByBuff(Creature target, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            if(skill.getSameByStackType(target) != null)
                continue;
            if((weight = skill.getPower()) <= 0)
                weight = 1;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected static Skill selectTopSkillByHeal(Creature target, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return null;
        double hpReduced = target.getMaxHp() - target.getCurrentHp();
        if(hpReduced < 1)
            return null;
        if(skills.length == 1)
            return skills[0];
        RndSelector<Skill> rnd = new RndSelector<>(skills.length);
        double weight;
        for(Skill skill : skills)
        {
            if((weight = abs(skill.getPower() - hpReduced)) <= 0)
                weight = 1;
            rnd.add(skill, (int) weight);
        }
        return rnd.select();
    }
    protected void addDesiredSkill(Map<Skill, Integer> skillMap, Creature target, double distance, Skill[] skills)
    {
        if(skills == null || skills.length == 0 || target == null)
            return;
        for(Skill sk : skills)
            addDesiredSkill(skillMap, target, distance, sk);
    }
    protected void addDesiredSkill(Map<Skill, Integer> skillMap, Creature target, double distance, Skill skill)
    {
        if(skill == null || target == null || !canUseSkill(skill, target))
            return;
        int weight = (int) -abs(skill.getAOECastRange() - distance);
        if(skill.getAOECastRange() >= distance)
            weight += 1000000;
        else if(skill.isNotTargetAoE() && skill.getTargets(getActor(), target, false).isEmpty())
            return;
        skillMap.put(skill, weight);
    }
    protected void addDesiredHeal(Map<Skill, Integer> skillMap, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return;
        NpcInstance actor = getActor();
        double hpReduced = actor.getMaxHp() - actor.getCurrentHp();
        double hpPercent = actor.getCurrentHpPercents();
        if(hpReduced < 1)
            return;
        int weight;
        for(Skill sk : skills)
            if(canUseSkill(sk, actor) && sk.getPower() <= hpReduced)
            {
                weight = (int) sk.getPower();
                if(hpPercent < 50)
                    weight += 1000000;
                skillMap.put(sk, weight);
            }
    }
    protected void addDesiredBuff(Map<Skill, Integer> skillMap, Skill[] skills)
    {
        if(skills == null || skills.length == 0)
            return;
        NpcInstance actor = getActor();
        for(Skill sk : skills)
            if(canUseSkill(sk, actor))
                skillMap.put(sk, 1000000);
    }
    protected Skill selectTopSkill(Map<Skill, Integer> skillMap)
    {
        if(skillMap == null || skillMap.isEmpty())
            return null;
        int nWeight, topWeight = MIN_VALUE;
        for(Skill next : skillMap.keySet())
            if((nWeight = skillMap.get(next)) > topWeight)
                topWeight = nWeight;
        if(topWeight == MIN_VALUE)
            return null;
        Skill[] skills = new Skill[skillMap.size()];
        nWeight = 0;
        for(Map.Entry<Skill, Integer> e : skillMap.entrySet())
        {
            if(e.getValue() < topWeight)
                continue;
            skills[nWeight++] = e.getKey();
        }
        return skills[get(nWeight)];
    }
    protected boolean chooseTaskAndTargets(Skill skill, Creature target, double distance)
    {
        NpcInstance actor = getActor();
        // Использовать скилл если можно, иначе атаковать
        if(skill != null)
        {
            // Проверка цели, и смена если необходимо
            if(actor.isMovementDisabled() && distance > skill.getAOECastRange() + 60)
            {
                target = null;
                if(skill.isOffensive())
                {
                    LazyArrayList<Creature> targets = newInstance();
                    for(Creature cha : actor.getAggroList().getHateList())
                    {
                        if(!checkTarget(cha, skill.getAOECastRange() + 60) || !canUseSkill(skill, cha))
                            continue;
                        targets.add(cha);
                    }
                    if(!targets.isEmpty())
                        target = targets.get(get(targets.size()));
                    recycle(targets);
                }
            }
            if(target == null)
                return false;
            // Добавить новое задание
            if(skill.isOffensive())
                addTaskCast(target, skill);
            else
                addTaskBuff(target, skill);
            return true;
        }
        // Смена цели, если необходимо
        if(actor.isMovementDisabled() && distance > actor.getPhysicalAttackRange() + 40)
        {
            target = null;
            LazyArrayList<Creature> targets = newInstance();
            for(Creature cha : actor.getAggroList().getHateList())
            {
                if(!checkTarget(cha, actor.getPhysicalAttackRange() + 40))
                    continue;
                targets.add(cha);
            }
            if(!targets.isEmpty())
                target = targets.get(get(targets.size()));
            recycle(targets);
        }
        if(target == null)
            return false;
        // Добавить новое задание
        addTaskAttack(target);
        return true;
    }
    @Override
    public boolean isActive()
    {
        return getAiTask() != null;
    }
    protected void clearTasks()
    {
        setDef_think(false);
        getTasks().clear();
    }
    /** переход в режим бега через определенный интервал времени
     * @param interval */
    protected void startRunningTask(long interval)
    {
        NpcInstance actor = getActor();
        if(actor != null && getRunningTask() == null && !actor.isRunning())
            setRunningTask(getInstance().schedule(new RunningTask(), (int) interval));
    }
    protected boolean isGlobalAggro()
    {
        if(getGlobalAggro() == 0)
            return true;
        if(getGlobalAggro() <= currentTimeMillis())
        {
            setGlobalAggro(0);
            return true;
        }
        return false;
    }
    public void setGlobalAggro(long value)
    {
        _globalAggro = value;
    }
    @Override
    public NpcInstance getActor()
    {
        return (NpcInstance) super.getActor();
    }
    protected boolean defaultThinkBuff(int rateSelf)
    {
        return defaultThinkBuff(rateSelf, 0);
    }
    /**
     * Оповестить дружественные цели об атаке.
     * @param attacker
     * @param damage
     */
    protected void notifyFriends(Creature attacker, int damage)
    {
        NpcInstance actor = getActor();
        if(currentTimeMillis() - getLastFactionNotifyTime() > getMinFactionNotifyInterval())
        {
            setLastFactionNotifyTime(currentTimeMillis());
            if(actor.isMinion())
            {
                //Оповестить лидера об атаке
                MonsterInstance master = ((MinionInstance) actor).getLeader();
                if(master != null)
                {
                    if(!master.isDead() && master.isVisible())
                        master.getAI().notifyEvent(EVT_AGGRESSION, attacker, damage);
                    //Оповестить минионов лидера об атаке
                    MinionList minionList = master.getMinionList();
                    if(minionList != null)
                        minionList.getAliveMinions().stream().filter(minion -> minion != actor).forEach(minion -> minion.getAI().notifyEvent(EVT_AGGRESSION, attacker, damage));
                }
            }
            //Оповестить своих минионов об атаке
            MinionList minionList = actor.getMinionList();
            if(minionList != null && minionList.hasAliveMinions())
                for(MinionInstance minion : minionList.getAliveMinions())
                    minion.getAI().notifyEvent(EVT_AGGRESSION, attacker, damage);
            //Оповестить социальных мобов
            for(NpcInstance npc : activeFactionTargets())
                npc.getAI().notifyEvent(EVT_CLAN_ATTACKED, new Object[] { actor, attacker, damage });
        }
    }
    protected List<NpcInstance> activeFactionTargets()
    {
        NpcInstance actor = getActor();
        if(actor.getFaction().isNone())
            return emptyList();
        return getAroundNpc(actor).stream().filter(npc -> !npc.isDead()).filter(npc -> npc.isInFaction(actor)).filter(npc -> npc.isInRangeZ(actor, npc.getFaction().getRange())).filter(npc -> canSeeTarget(npc, actor, false)).collect(Collectors.toCollection(() -> new LazyArrayList<>()));
    }
    protected boolean defaultThinkBuff(int rateSelf, int rateFriends)
    {
        NpcInstance actor = getActor();
        if(actor.isDead())
            return true;
        //TODO сделать более разумный выбор баффа, сначала выбирать подходящие а потом уже рандомно 1 из них
        if(chance(rateSelf))
        {
            double actorHp = actor.getCurrentHpPercents();
            Skill[] skills = actorHp < 50 ? selectUsableSkills(actor, 0, getHealSkills()) : selectUsableSkills(actor, 0, getBuffSkills());
            if(skills == null || skills.length == 0)
                return false;
            Skill skill = skills[get(skills.length)];
            addTaskBuff(actor, skill);
            return true;
        }
        if(chance(rateFriends))
        {
            for(NpcInstance npc : activeFactionTargets())
            {
                double targetHp = npc.getCurrentHpPercents();
                Skill[] skills = targetHp < 50 ? selectUsableSkills(actor, 0, getHealSkills()) : selectUsableSkills(actor, 0, getBuffSkills());
                if(skills == null || skills.length == 0)
                    continue;
                Skill skill = skills[get(skills.length)];
                addTaskBuff(actor, skill);
                return true;
            }
        }
        return false;
    }
    protected boolean defaultFightTask()
    {
        clearTasks();
        NpcInstance actor = getActor();
        if(actor.isDead() || actor.isAMuted())
            return false;
        Creature target;
        if((target = prepareTarget()) == null)
            return false;
        double distance = actor.getDistance(target);
        double targetHp = target.getCurrentHpPercents();
        double actorHp = actor.getCurrentHpPercents();
        Skill[] dam = chance(getRateDAM()) ? selectUsableSkills(target, distance, getDamSkills()) : null;
        Skill[] dot = chance(getRateDOT()) ? selectUsableSkills(target, distance, getDotSkills()) : null;
        Skill[] debuff = targetHp > 10 ? chance(getRateDEBUFF()) ? selectUsableSkills(target, distance, getDebuffSkills()) : null : null;
        Skill[] stun = chance(getRateSTUN()) ? selectUsableSkills(target, distance, getStunSkills()) : null;
        Skill[] heal = actorHp < 50 ? chance(getRateHEAL()) ? selectUsableSkills(actor, 0, getHealSkills()) : null : null;
        Skill[] buff = chance(getRateBUFF()) ? selectUsableSkills(actor, 0, getBuffSkills()) : null;
        RndSelector<Skill[]> rnd = new RndSelector<>();
        if(!actor.isAMuted())
            rnd.add(null, getRatePHYS());
        rnd.add(dam, getRateDAM());
        rnd.add(dot, getRateDOT());
        rnd.add(debuff, getRateDEBUFF());
        rnd.add(heal, getRateHEAL());
        rnd.add(buff, getRateBUFF());
        rnd.add(stun, getRateSTUN());
        Skill[] selected = rnd.select();
        if(selected != null)
        {
            if(selected == dam || selected == dot)
                return chooseTaskAndTargets(selectTopSkillByDamage(actor, target, distance, selected), target, distance);
            if(selected == debuff || selected == stun)
                return chooseTaskAndTargets(selectTopSkillByDebuff(target, distance, selected), target, distance);
            if(selected == buff)
                return chooseTaskAndTargets(selectTopSkillByBuff(actor, selected), actor, distance);
            if(selected == heal)
                return chooseTaskAndTargets(selectTopSkillByHeal(actor, selected), actor, distance);
        }
        // TODO сделать лечение и баф дружественных целей
        return chooseTaskAndTargets(null, target, distance);
    }
    public int getRatePHYS()
    {
        return 100;
    }
    public int getRateDOT()
    {
        return 0;
    }
    public int getRateDEBUFF()
    {
        return 0;
    }
    public int getRateDAM()
    {
        return 0;
    }
    public int getRateSTUN()
    {
        return 0;
    }
    public int getRateBUFF()
    {
        return 0;
    }
    public int getRateHEAL()
    {
        return 0;
    }
    public boolean getIsMobile()
    {
        return !getActor().getParameter("isImmobilized", false);
    }
    public int getMaxPathfindFails()
    {
        return 3;
    }
    /**
     * Задержка, перед переключением в активный режим после атаки, если цель не найдена (вне зоны досягаемости, убита, очищен хейт)
     * @return
     */
    public int getMaxAttackTimeout()
    {
        return 15000;
    }
    /**
     * Задержка, перед телепортом к цели, если не удается дойти
     * @return
     */
    public int getTeleportTimeout()
    {
        return 10000;
    }
	
			
				Последнее редактирование: 
			
		
	
								
								
									
	
		
			
		
	
								
							
							
	























  А что, проски запрещены?

 за то что посчитали за этого пи**аса обидно...