AI l2p

Wolfer

Прославленный
Местный
Старожил I степени
Сообщения
227
Розыгрыши
0
Репутация
216
Реакции
238
Баллы
1 408
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;
    }
 
Последнее редактирование:

PlayableAI.java
PHP:
package l2p.gameserver.ai;

import l2p.commons.threading.RunnableImpl;
import l2p.gameserver.ThreadPoolManager;
import l2p.gameserver.geodata.GeoEngine;
import l2p.gameserver.model.Creature;
import l2p.gameserver.model.GameObject;
import l2p.gameserver.model.Playable;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.Skill;
import l2p.gameserver.model.Skill.NextAction;
import l2p.gameserver.model.Skill.SkillType;
import l2p.gameserver.model.Summon;
import l2p.gameserver.serverpackets.MyTargetSelected;
import l2p.gameserver.serverpackets.components.SystemMsg;
import l2p.gameserver.utils.Location;

import java.util.concurrent.ScheduledFuture;

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_CAST;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;


public class PlayableAI extends CharacterAI
{
    private volatile int thinking = 0; // to prevent recursive thinking

    protected Object _intention_arg0 = null, _intention_arg1 = null;
    protected Skill _skill;

    private nextAction _nextAction;
    private Object _nextAction_arg0;
    private Object _nextAction_arg1;
    private boolean _nextAction_arg2;
    private boolean _nextAction_arg3;

    protected boolean _forceUse;
    private boolean _dontMove;

    private ScheduledFuture<?> _followTask;

    public PlayableAI(Playable actor)
    {
        super(actor);
    }

    public enum nextAction
    {
        ATTACK,
        CAST,
        MOVE,
        REST,
        PICKUP,
        INTERACT
    }

    @Override
    public void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
    {
        super.changeIntention(intention, arg0, arg1);
        _intention_arg0 = arg0;
        _intention_arg1 = arg1;
    }

    @Override
    public void setIntention(CtrlIntention intention, Object arg0, Object arg1)
    {
        _intention_arg0 = null;
        _intention_arg1 = null;
        super.setIntention(intention, arg0, arg1);
    }

    @Override
    protected void onIntentionCast(Skill skill, Creature target)
    {
        _skill = skill;
        super.onIntentionCast(skill, target);
    }

    @Override
    public void setNextAction(nextAction action, Object arg0, Object arg1, boolean arg2, boolean arg3)
    {
        _nextAction = action;
        _nextAction_arg0 = arg0;
        _nextAction_arg1 = arg1;
        _nextAction_arg2 = arg2;
        _nextAction_arg3 = arg3;
    }

    public boolean setNextIntention()
    {
        nextAction nextAction = _nextAction;
        Object nextAction_arg0 = _nextAction_arg0;
        Object nextAction_arg1 = _nextAction_arg1;
        boolean nextAction_arg2 = _nextAction_arg2;
        boolean nextAction_arg3 = _nextAction_arg3;

        Playable actor = getActor();
        if(nextAction == null || actor.isActionsDisabled())
            return false;

        Skill skill;
        Creature target;
        GameObject object;

        switch(nextAction)
        {
            case ATTACK:
                if(nextAction_arg0 == null)
                    return false;
                target = (Creature) nextAction_arg0;
                _forceUse = nextAction_arg2;
                _dontMove = nextAction_arg3;
                clearNextAction();
                setIntention(AI_INTENTION_ATTACK, target);
                break;
            case CAST:
                if(nextAction_arg0 == null || nextAction_arg1 == null)
                    return false;
                skill = (Skill) nextAction_arg0;
                target = (Creature) nextAction_arg1;
                _forceUse = nextAction_arg2;
                _dontMove = nextAction_arg3;
                clearNextAction();
                if(!skill.checkCondition(actor, target, _forceUse, _dontMove, true))
                {
                    if(skill.getNextAction() == NextAction.ATTACK && !actor.equals(target))
                    {
                        setNextAction(PlayableAI.nextAction.ATTACK, target, null, _forceUse, false);
                        return setNextIntention();
                    }
                    return false;
                }
                setIntention(AI_INTENTION_CAST, skill, target);
                break;
            case MOVE:
                if(nextAction_arg0 == null || nextAction_arg1 == null)
                    return false;
                Location loc = (Location) nextAction_arg0;
                Integer offset = (Integer) nextAction_arg1;
                clearNextAction();
                actor.moveToLocation(loc, offset, nextAction_arg2);
                break;
            case REST:
                actor.sitDown(null);
                break;
            case INTERACT:
                if(nextAction_arg0 == null)
                    return false;
                object = (GameObject) nextAction_arg0;
                clearNextAction();
                onIntentionInteract(object);
                break;
            case PICKUP:
                if(nextAction_arg0 == null)
                    return false;
                object = (GameObject) nextAction_arg0;
                clearNextAction();
                onIntentionPickUp(object);
                break;
            default:
                return false;
        }
        return true;
    }

    @Override
    public void clearNextAction()
    {
        _nextAction = null;
        _nextAction_arg0 = null;
        _nextAction_arg1 = null;
        _nextAction_arg2 = false;
        _nextAction_arg3 = false;
    }

    @Override
    protected void onEvtFinishCasting()
    {
        if(!setNextIntention())
            setIntention(AI_INTENTION_ACTIVE);
    }

    @Override
    protected void onEvtReadyToAct()
    {
        if(!setNextIntention())
            onEvtThink();
    }

    @Override
    protected void onEvtArrived()
    {
        if(!setNextIntention())
            if(getIntention() == AI_INTENTION_INTERACT || getIntention() == AI_INTENTION_PICK_UP)
                onEvtThink();
            else
                changeIntention(AI_INTENTION_ACTIVE, null, null);
    }

    @Override
    protected void onEvtArrivedTarget()
    {
        switch(getIntention())
        {
            case AI_INTENTION_ATTACK:
                thinkAttack(false);
                break;
            case AI_INTENTION_CAST:
                thinkCast(false);
                break;
            case AI_INTENTION_FOLLOW:
                thinkFollow();
                break;
            default:
                onEvtThink();
                break;
        }
    }

    @Override
    protected final void onEvtThink()
    {
        Playable actor = getActor();
        if(actor.isActionsDisabled())
            return;

        try
        {
            if(thinking++ > 1)
                return;

            switch(getIntention())
            {
                case AI_INTENTION_ACTIVE:
                    thinkActive();
                    break;
                case AI_INTENTION_ATTACK:
                    thinkAttack(true);
                    break;
                case AI_INTENTION_CAST:
                    thinkCast(true);
                    break;
                case AI_INTENTION_PICK_UP:
                    thinkPickUp();
                    break;
                case AI_INTENTION_INTERACT:
                    thinkInteract();
                    break;
                case AI_INTENTION_FOLLOW:
                    thinkFollow();
                    break;
            }
        }
        catch(Exception e)
        {
            _log.error("Unexpected error in PlayableAI.onEvtThink()", e);
        }
        finally
        {
            thinking--;
        }
    }

    protected void thinkActive()
    {

    }

    protected void thinkFollow()
    {
        Playable actor = getActor();

        Creature target = (Creature) _intention_arg0;
        Integer offset = (Integer) _intention_arg1;

        //Находимся слишком далеко цели, либо цель не пригодна для следования
        if(target == null || target.isAlikeDead() || actor.getDistance(target) > 4000 || offset == null)
        {
            clientActionFailed();
            return;
        }

        //Уже следуем за этой целью
        if(actor.isFollow && actor.getFollowTarget() == target)
        {
            clientActionFailed();
            return;
        }

        //Находимся достаточно близко или не можем двигаться - побежим потом ?
        if(actor.isInRange(target, offset + 20) || actor.isMovementDisabled())
            clientActionFailed();

        if(_followTask != null)
        {
            _followTask.cancel(false);
            _followTask = null;
        }

        _followTask = ThreadPoolManager.getInstance().schedule(new ThinkFollow(), 250L);
    }

    protected class ThinkFollow extends RunnableImpl
    {
        @Override
        public void runImpl() throws Exception
        {
            Playable actor = getActor();

            if(getIntention() != AI_INTENTION_FOLLOW)
            {
                // Если пет прекратил преследование, меняем статус, чтобы не пришлось щелкать на кнопку следования 2 раза.
                if((actor.isPet() || actor.isSummon()) && getIntention() == AI_INTENTION_ACTIVE)
                    ((Summon) actor).setFollowMode(false);
                return;
            }

            Creature target = (Creature) _intention_arg0;
            int offset = _intention_arg1 instanceof Integer ? (Integer) _intention_arg1 : 0;

            if(target == null || !target.isVisible() || target.isAlikeDead() || actor.getDistance(target) > 4000)
            {
                setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
                return;
            }

            Player player = actor.getPlayer();
            if(player == null || player.isLogoutStarted() || (actor.isPet() || actor.isSummon()) && player.getPet() != actor)
            {
                setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
                return;
            }

            if(!actor.isInRange(target, offset + 20) && (!actor.isFollow || actor.getFollowTarget() != target))
                actor.followToCharacter(target, offset, false);
            _followTask = ThreadPoolManager.getInstance().schedule(this, 250L);
        }
    }

    protected class ExecuteFollow extends RunnableImpl
    {
        private Creature _target;
        private int _range;

        public ExecuteFollow(Creature target, int range)
        {
            _target = target;
            _range = range;
        }

        @Override
        public void runImpl()
        {
            if(_target.isDoor())
                _actor.moveToLocation(_target.getLoc(), 40, true);
            else
                _actor.followToCharacter(_target, _range, true);
        }
    }

    @Override
    protected void onIntentionInteract(GameObject object)
    {
        Playable actor = getActor();

        if(actor.isActionsDisabled())
        {
            setNextAction(nextAction.INTERACT, object, null, false, false);
            clientActionFailed();
            return;
        }

        clearNextAction();
        changeIntention(AI_INTENTION_INTERACT, object, null);
        onEvtThink();
    }

    protected void thinkInteract()
    {
        Playable actor = getActor();

        GameObject target = (GameObject) _intention_arg0;

        if(target == null)
        {
            setIntention(AI_INTENTION_ACTIVE);
            return;
        }

        int range = (int) (Math.max(30, actor.getMinDistance(target)) + 20);

        if(actor.isInRangeZ(target, range))
        {
            if(actor.isPlayer())
                ((Player) actor).doInteract(target);
            setIntention(AI_INTENTION_ACTIVE);
        }
        else
        {
            actor.moveToLocation(target.getLoc(), 40, true);
            setNextAction(nextAction.INTERACT, target, null, false, false);
        }
    }

    @Override
    protected void onIntentionPickUp(GameObject object)
    {
        Playable actor = getActor();

        if(actor.isActionsDisabled())
        {
            setNextAction(nextAction.PICKUP, object, null, false, false);
            clientActionFailed();
            return;
        }

        clearNextAction();
        changeIntention(AI_INTENTION_PICK_UP, object, null);
        onEvtThink();
    }

    protected void thinkPickUp()
    {
        final Playable actor = getActor();

        final GameObject target = (GameObject) _intention_arg0;

        if(target == null)
        {
            setIntention(AI_INTENTION_ACTIVE);
            return;
        }

        if(actor.isInRange(target, 30) && Math.abs(actor.getZ() - target.getZ()) < 50)
        {
            if(actor.isPlayer() || actor.isPet())
                actor.doPickupItem(target);
            setIntention(AI_INTENTION_ACTIVE);
        }
        else
            ThreadPoolManager.getInstance().execute(new RunnableImpl()
            {
                @Override
                public void runImpl()
                {
                    actor.moveToLocation(target.getLoc(), 10, true);
                    setNextAction(nextAction.PICKUP, target, null, false, false);
                }
            });
    }

    protected void thinkAttack(boolean checkRange)
    {
        Playable actor = getActor();

        Player player = actor.getPlayer();
        if(player == null)
        {
            setIntention(AI_INTENTION_ACTIVE);
            return;
        }

        if(actor.isActionsDisabled() || actor.isAttackingDisabled())
        {
            actor.sendActionFailed();
            return;
        }

        boolean isPosessed = actor instanceof Summon && ((Summon) actor).isDepressed();

        Creature attack_target = getAttackTarget();
        if(attack_target == null || attack_target.isDead() || !isPosessed && !(_forceUse ? attack_target.isAttackable(actor) : attack_target.isAutoAttackable(actor)))
        {
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        if(!checkRange)
        {
            clientStopMoving();
            actor.doAttack(attack_target);
            return;
        }

        int range = actor.getPhysicalAttackRange();
        if(range < 10)
            range = 10;

        boolean canSee = GeoEngine.canSeeTarget(actor, attack_target, false);

        if(!canSee && (range > 200 || Math.abs(actor.getZ() - attack_target.getZ()) > 200))
        {
            actor.sendPacket(SystemMsg.CANNOT_SEE_TARGET);
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        range += actor.getMinDistance(attack_target);

        if(actor.isFakeDeath())
            actor.breakFakeDeath();

        if(actor.isInRangeZ(attack_target, range))
        {
            if(!canSee)
            {
                actor.sendPacket(SystemMsg.CANNOT_SEE_TARGET);
                setIntention(AI_INTENTION_ACTIVE);
                actor.sendActionFailed();
                return;
            }

            clientStopMoving(false);
            actor.doAttack(attack_target);
        }
        else if(!_dontMove)
            ThreadPoolManager.getInstance().execute(new ExecuteFollow(attack_target, range - 20));
        else
            actor.sendActionFailed();
    }

    protected void thinkCast(boolean checkRange)
    {
        Playable actor = getActor();

        Creature target = getAttackTarget();

        if(_skill.getSkillType() == SkillType.CRAFT || _skill.isToggle())
        {
            if(_skill.checkCondition(actor, target, _forceUse, _dontMove, true))
                actor.doCast(_skill, target, _forceUse);
            return;
        }

        if(target == null || target.isDead() != _skill.getCorpse() && !_skill.isNotTargetAoE())
        {
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        if(!checkRange)
        {
            // Если скилл имеет следующее действие, назначим это действие после окончания действия скилла
            if(_skill.getNextAction() == NextAction.ATTACK && !actor.equals(target))
                setNextAction(nextAction.ATTACK, target, null, _forceUse, false);
            else
                clearNextAction();

            clientStopMoving();

            if(_skill.checkCondition(actor, target, _forceUse, _dontMove, true))
                actor.doCast(_skill, target, _forceUse);
            else
            {
                setNextIntention();
                if(getIntention() == CtrlIntention.AI_INTENTION_ATTACK)
                    thinkAttack(true);
            }

            return;
        }

        int range = actor.getMagicalAttackRange(_skill);
        if(range < 10)
            range = 10;

        boolean canSee = _skill.getSkillType() == SkillType.TAKECASTLE || _skill.getSkillType() == SkillType.TAKEFORTRESS || GeoEngine.canSeeTarget(actor, target, actor.isFlying());
        boolean noRangeSkill = _skill.getCastRange() == 32767;

        if(!noRangeSkill && !canSee && (range > 200 || Math.abs(actor.getZ() - target.getZ()) > 200))
        {
            actor.sendPacket(SystemMsg.CANNOT_SEE_TARGET);
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        range += actor.getMinDistance(target);

        if(actor.isFakeDeath())
            actor.breakFakeDeath();

        if(actor.isInRangeZ(target, range) || noRangeSkill)
        {
            if(!noRangeSkill && !canSee)
            {
                actor.sendPacket(SystemMsg.CANNOT_SEE_TARGET);
                setIntention(AI_INTENTION_ACTIVE);
                actor.sendActionFailed();
                return;
            }

            // Если скилл имеет следующее действие, назначим это действие после окончания действия скилла
            if(_skill.getNextAction() == NextAction.ATTACK && !actor.equals(target) && !_forceUse)
            {
                //clearNextAction();
                setNextAction(nextAction.ATTACK, target, null, _forceUse, false);
            }
            else
                clearNextAction();

            if(_skill.checkCondition(actor, target, _forceUse, _dontMove, true))
            {
                clientStopMoving(false);
                actor.doCast(_skill, target, _forceUse);
            }
            else
            {
                setNextIntention();
                if(getIntention() == CtrlIntention.AI_INTENTION_ATTACK)
                    thinkAttack(true);
            }
        }
        else if(!_dontMove)
            ThreadPoolManager.getInstance().execute(new ExecuteFollow(target, range - 20));
        else
        {
            actor.sendPacket(SystemMsg.YOUR_TARGET_IS_OUT_OF_RANGE);
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
        }
    }

    @Override
    protected void onEvtDead(Creature killer)
    {
        clearNextAction();
        super.onEvtDead(killer);
    }

    @Override
    protected void onEvtFakeDeath()
    {
        clearNextAction();
        super.onEvtFakeDeath();
    }

    public void lockTarget(Creature target)
    {
        Playable actor = getActor();

        if(target == null || target.isDead())
            actor.setAggressionTarget(null);
        else if(actor.getAggressionTarget() == null)
        {
            GameObject actorStoredTarget = actor.getTarget();
            actor.setAggressionTarget(target);
            actor.setTarget(target);

            clearNextAction();
            // DS: агрессия только перекидывает видимую цель, но не обрывает текущую атаку/каст
            /*if (getIntention() == CtrlIntention.AI_INTENTION_ATTACK)
                setAttackTarget(target);
            switch(getIntention())
            {
                case AI_INTENTION_ATTACK:
                    setAttackTarget(target);
                    break;
                case AI_INTENTION_CAST:
                    L2Skill skill = actor.getCastingSkill();
                    if(skill == null)
                        skill = _skill;
                    if(skill != null && !skill.isUsingWhileCasting())
                        switch(skill.getTargetType())
                        {
                            case TARGET_ONE:
                            case TARGET_AREA:
                            case TARGET_MULTIFACE:
                            case TARGET_TUNNEL:
                                setAttackTarget(target);
                                actor.setCastingTarget(target);
                                break;
                        }
                    break;
            }*/

            if(actorStoredTarget != target)
                actor.sendPacket(new MyTargetSelected(target.getObjectId(), 0));
        }
    }

    @Override
    public void Attack(GameObject target, boolean forceUse, boolean dontMove)
    {
        Playable actor = getActor();

        if(target.isCreature() && (actor.isActionsDisabled() || actor.isAttackingDisabled()))
        {
            // Если не можем атаковать, то атаковать позже
            setNextAction(nextAction.ATTACK, target, null, forceUse, false);
            actor.sendActionFailed();
            return;
        }

        _dontMove = dontMove;
        _forceUse = forceUse;
        clearNextAction();
        setIntention(AI_INTENTION_ATTACK, target);
    }

    @Override
    public void Cast(Skill skill, Creature target, boolean forceUse, boolean dontMove)
    {
        Playable actor = getActor();

        // Если скилл альтернативного типа (например, бутылка на хп),
        // то он может использоваться во время каста других скиллов, или во время атаки, или на бегу.
        // Поэтому пропускаем дополнительные проверки.
        if(skill.altUse() || skill.isToggle())
        {
            if((skill.isToggle() || skill.isHandler()) && (actor.isOutOfControl() || actor.isStunned() || actor.isSleeping() || actor.isParalyzed() || actor.isAlikeDead()))
                clientActionFailed();
            else
                actor.altUseSkill(skill, target);
            return;
        }

        // Если не можем кастовать, то использовать скилл позже
        if(actor.isActionsDisabled())
        {
            //if(!actor.isSkillDisabled(skill.getId()))
            setNextAction(nextAction.CAST, skill, target, forceUse, dontMove);
            clientActionFailed();
            return;
        }

        //_actor.stopMove(null);
        _forceUse = forceUse;
        _dontMove = dontMove;
        clearNextAction();
        setIntention(CtrlIntention.AI_INTENTION_CAST, skill, target);
    }

    @Override
    public Playable getActor()
    {
        return (Playable) super.getActor();
    }
}

PHP:
package l2p.gameserver.ai;

import static java.lang.Math.abs;
import static java.lang.Math.max;
import java.util.concurrent.ScheduledFuture;
import l2p.commons.threading.RunnableImpl;
import static l2p.gameserver.ThreadPoolManager.getInstance;
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_CAST;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static l2p.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;
import static l2p.gameserver.ai.PlayableAI.nextAction.ATTACK;
import static l2p.gameserver.ai.PlayableAI.nextAction.CAST;
import static l2p.gameserver.ai.PlayableAI.nextAction.INTERACT;
import static l2p.gameserver.ai.PlayableAI.nextAction.PICKUP;
import static l2p.gameserver.geodata.GeoEngine.canSeeTarget;
import l2p.gameserver.model.Creature;
import l2p.gameserver.model.GameObject;
import l2p.gameserver.model.Playable;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.Skill;
import static l2p.gameserver.model.Skill.SkillType.CRAFT;
import static l2p.gameserver.model.Skill.SkillType.TAKECASTLE;
import static l2p.gameserver.model.Skill.SkillType.TAKEFORTRESS;
import l2p.gameserver.model.Summon;
import l2p.gameserver.serverpackets.MyTargetSelected;
import static l2p.gameserver.serverpackets.components.SystemMsg.CANNOT_SEE_TARGET;
import static l2p.gameserver.serverpackets.components.SystemMsg.YOUR_TARGET_IS_OUT_OF_RANGE;
import l2p.gameserver.utils.Location;


public class PlayableAI extends CharacterAI
{
    private volatile int thinking = 0; // to prevent recursive thinking
    private Object _intention_arg0 = null;
    private Object _intention_arg1 = null;
    private Skill _skill;

    private nextAction _nextAction;
    private Object _nextAction_arg0;
    private Object _nextAction_arg1;
    private boolean _nextAction_arg2;
    private boolean _nextAction_arg3;

    private boolean _forceUse;
    private boolean _dontMove;

    private ScheduledFuture<?> _followTask;

    public PlayableAI(Playable actor)
    {
        super(actor);
    }

    /**
     * @return the thinking
     */
    public int getThinking() {
        return thinking;
    }

    /**
     * @param thinking the thinking to set
     */
    public void setThinking(int thinking) {
        this.thinking = thinking;
    }

    /**
     * @return the _intention_arg0
     */
    public Object getIntention_arg0() {
        return _intention_arg0;
    }

    /**
     * @param _intention_arg0 the _intention_arg0 to set
     */
    public void setIntention_arg0(Object _intention_arg0) {
        this._intention_arg0 = _intention_arg0;
    }

    /**
     * @return the _intention_arg1
     */
    public Object getIntention_arg1() {
        return _intention_arg1;
    }

    /**
     * @param _intention_arg1 the _intention_arg1 to set
     */
    public void setIntention_arg1(Object _intention_arg1) {
        this._intention_arg1 = _intention_arg1;
    }

    /**
     * @return the _skill
     */
    public Skill getSkill() {
        return _skill;
    }

    /**
     * @param _skill the _skill to set
     */
    public void setSkill(Skill _skill) {
        this._skill = _skill;
    }

    /**
     * @return the _nextAction
     */
    public nextAction getNextAction() {
        return _nextAction;
    }

    /**
     * @param _nextAction the _nextAction to set
     */
    public void setNextAction(nextAction _nextAction) {
        this._nextAction = _nextAction;
    }

    /**
     * @return the _nextAction_arg0
     */
    public Object getNextAction_arg0() {
        return _nextAction_arg0;
    }

    /**
     * @param _nextAction_arg0 the _nextAction_arg0 to set
     */
    public void setNextAction_arg0(Object _nextAction_arg0) {
        this._nextAction_arg0 = _nextAction_arg0;
    }

    /**
     * @return the _nextAction_arg1
     */
    public Object getNextAction_arg1() {
        return _nextAction_arg1;
    }

    /**
     * @param _nextAction_arg1 the _nextAction_arg1 to set
     */
    public void setNextAction_arg1(Object _nextAction_arg1) {
        this._nextAction_arg1 = _nextAction_arg1;
    }

    /**
     * @return the _nextAction_arg2
     */
    public boolean isNextAction_arg2() {
        return _nextAction_arg2;
    }

    /**
     * @param _nextAction_arg2 the _nextAction_arg2 to set
     */
    public void setNextAction_arg2(boolean _nextAction_arg2) {
        this._nextAction_arg2 = _nextAction_arg2;
    }

    /**
     * @return the _nextAction_arg3
     */
    public boolean isNextAction_arg3() {
        return _nextAction_arg3;
    }

    /**
     * @param _nextAction_arg3 the _nextAction_arg3 to set
     */
    public void setNextAction_arg3(boolean _nextAction_arg3) {
        this._nextAction_arg3 = _nextAction_arg3;
    }

    /**
     * @return the _forceUse
     */
    public boolean isForceUse() {
        return _forceUse;
    }

    /**
     * @param _forceUse the _forceUse to set
     */
    public void setForceUse(boolean _forceUse) {
        this._forceUse = _forceUse;
    }

    /**
     * @return the _dontMove
     */
    public boolean isDontMove() {
        return _dontMove;
    }

    /**
     * @param _dontMove the _dontMove to set
     */
    public void setDontMove(boolean _dontMove) {
        this._dontMove = _dontMove;
    }

    /**
     * @return the _followTask
     */
    public ScheduledFuture<?> getFollowTask() {
        return _followTask;
    }

    /**
     * @param _followTask the _followTask to set
     */
    public void setFollowTask(ScheduledFuture<?> _followTask) {
        this._followTask = _followTask;
    }

    public enum nextAction
    {
        ATTACK,
        CAST,
        MOVE,
        REST,
        PICKUP,
        INTERACT
    }

    @Override
    public void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
    {
        super.changeIntention(intention, arg0, arg1);
        setIntention_arg0(arg0);
        setIntention_arg1(arg1);
    }

    @Override
    public void setIntention(CtrlIntention intention, Object arg0, Object arg1)
    {
        setIntention_arg0(null);
        setIntention_arg1(null);
        super.setIntention(intention, arg0, arg1);
    }

    @Override
    protected void onIntentionCast(Skill skill, Creature target)
    {
        setSkill(skill);
        super.onIntentionCast(skill, target);
    }

    @Override
    public void setNextAction(nextAction action, Object arg0, Object arg1, boolean arg2, boolean arg3)
    {
        setNextAction(action);
        setNextAction_arg0(arg0);
        setNextAction_arg1(arg1);
        setNextAction_arg2(arg2);
        setNextAction_arg3(arg3);
    }

    public boolean setNextIntention()
    {
        nextAction nextAction = getNextAction();
        Object nextAction_arg0 = getNextAction_arg0();
        Object nextAction_arg1 = getNextAction_arg1();
        boolean nextAction_arg2 = isNextAction_arg2();
        boolean nextAction_arg3 = isNextAction_arg3();

        Playable actor = getActor();
        if(nextAction == null || actor.isActionsDisabled())
            return false;

        Skill skill;
        Creature target;
        GameObject object;

        switch(nextAction)
        {
            case ATTACK:
                if(nextAction_arg0 == null)
                    return false;
                target = (Creature) nextAction_arg0;
                setForceUse(nextAction_arg2);
                setDontMove(nextAction_arg3);
                clearNextAction();
                setIntention(AI_INTENTION_ATTACK, target);
                break;
            case CAST:
                if(nextAction_arg0 == null || nextAction_arg1 == null)
                    return false;
                skill = (Skill) nextAction_arg0;
                target = (Creature) nextAction_arg1;
                setForceUse(nextAction_arg2);
                setDontMove(nextAction_arg3);
                clearNextAction();
                if(!skill.checkCondition(actor, target, _forceUse, _dontMove, true))
                {
                    if(skill.getNextAction() == ATTACK && !actor.equals(target))
                    {
                        setNextAction(ATTACK, target, null, isForceUse(), false);
                        return setNextIntention();
                    }
                    return false;
                }
                setIntention(AI_INTENTION_CAST, skill, target);
                break;
            case MOVE:
                if(nextAction_arg0 == null || nextAction_arg1 == null)
                    return false;
                Location loc = (Location) nextAction_arg0;
                Integer offset = (Integer) nextAction_arg1;
                clearNextAction();
                actor.moveToLocation(loc, offset, nextAction_arg2);
                break;
            case REST:
                actor.sitDown(null);
                break;
            case INTERACT:
                if(nextAction_arg0 == null)
                    return false;
                object = (GameObject) nextAction_arg0;
                clearNextAction();
                onIntentionInteract(object);
                break;
            case PICKUP:
                if(nextAction_arg0 == null)
                    return false;
                object = (GameObject) nextAction_arg0;
                clearNextAction();
                onIntentionPickUp(object);
                break;
            default:
                return false;
        }
        return true;
    }

    @Override
    public void clearNextAction()
    {
        setNextAction(null);
        setNextAction_arg0(null);
        setNextAction_arg1(null);
        setNextAction_arg2(false);
        setNextAction_arg3(false);
    }

    @Override
    protected void onEvtFinishCasting()
    {
        if(!setNextIntention())
            setIntention(AI_INTENTION_ACTIVE);
    }

    @Override
    protected void onEvtReadyToAct()
    {
        if(!setNextIntention())
            onEvtThink();
    }

    @Override
    protected void onEvtArrived()
    {
        if(!setNextIntention())
            if(getIntention() == AI_INTENTION_INTERACT || getIntention() == AI_INTENTION_PICK_UP)
                onEvtThink();
            else
                changeIntention(AI_INTENTION_ACTIVE, null, null);
    }

    @Override
    protected void onEvtArrivedTarget()
    {
        switch(getIntention())
        {
            case AI_INTENTION_ATTACK:
                thinkAttack(false);
                break;
            case AI_INTENTION_CAST:
                thinkCast(false);
                break;
            case AI_INTENTION_FOLLOW:
                thinkFollow();
                break;
            default:
                onEvtThink();
                break;
        }
    }

    @Override
    protected final void onEvtThink()
    {
        Playable actor = getActor();
        if(actor.isActionsDisabled())
            return;

        try
        {
            if(thinking++ > 1)
                return;

            switch(getIntention())
            {
                case AI_INTENTION_ACTIVE:
                    thinkActive();
                    break;
                case AI_INTENTION_ATTACK:
                    thinkAttack(true);
                    break;
                case AI_INTENTION_CAST:
                    thinkCast(true);
                    break;
                case AI_INTENTION_PICK_UP:
                    thinkPickUp();
                    break;
                case AI_INTENTION_INTERACT:
                    thinkInteract();
                    break;
                case AI_INTENTION_FOLLOW:
                    thinkFollow();
                    break;
            }
        }
        catch(Exception e)
        {
            _log.error("Unexpected error in PlayableAI.onEvtThink()", e);
        }
        finally
        {
            setThinking(getThinking() - 1);
        }
    }

    protected void thinkActive()
    {

    }

    protected void thinkFollow()
    {
        Playable actor = getActor();

        Creature target = (Creature) getIntention_arg0();
        Integer offset = (Integer) getIntention_arg1();

        //Находимся слишком далеко цели, либо цель не пригодна для следования
        if(target == null || target.isAlikeDead() || actor.getDistance(target) > 4000 || offset == null)
        {
            clientActionFailed();
            return;
        }

        //Уже следуем за этой целью
        if(actor.isFollow && actor.getFollowTarget() == target)
        {
            clientActionFailed();
            return;
        }

        //Находимся достаточно близко или не можем двигаться - побежим потом ?
        if(actor.isInRange(target, offset + 20) || actor.isMovementDisabled())
            clientActionFailed();

        if(getFollowTask() != null)
        {
            getFollowTask().cancel(false);
            setFollowTask(null);
        }

        setFollowTask(getInstance().schedule(new ThinkFollow(), 250L));
    }

    protected class ThinkFollow extends RunnableImpl
    {
        @Override
        public void runImpl() throws Exception
        {
            Playable actor = getActor();

            if(getIntention() != AI_INTENTION_FOLLOW)
            {
                // Если пет прекратил преследование, меняем статус, чтобы не пришлось щелкать на кнопку следования 2 раза.
                if((actor.isPet() || actor.isSummon()) && getIntention() == AI_INTENTION_ACTIVE)
                    ((Summon) actor).setFollowMode(false);
                return;
            }

            Creature target = (Creature) getIntention_arg0();
            int offset = getIntention_arg1() instanceof Integer ? (Integer) getIntention_arg1() : 0;

            if(target == null || !target.isVisible() || target.isAlikeDead() || actor.getDistance(target) > 4000)
            {
                setIntention(AI_INTENTION_ACTIVE);
                return;
            }

            Player player = actor.getPlayer();
            if(player == null || player.isLogoutStarted() || (actor.isPet() || actor.isSummon()) && player.getPet() != actor)
            {
                setIntention(AI_INTENTION_ACTIVE);
                return;
            }

            if(!actor.isInRange(target, offset + 20) && (!actor.isFollow || actor.getFollowTarget() != target))
                actor.followToCharacter(target, offset, false);
            setFollowTask(getInstance().schedule(this, 250L));
        }
    }

    protected class ExecuteFollow extends RunnableImpl
    {
        private final Creature _target;
        private final int _range;

        public ExecuteFollow(Creature target, int range)
        {
            _target = target;
            _range = range;
        }

        @Override
        public void runImpl()
        {
            if(_target.isDoor())
                _actor.moveToLocation(_target.getLoc(), 40, true);
            else
                _actor.followToCharacter(_target, _range, true);
        }
    }

    @Override
    protected void onIntentionInteract(GameObject object)
    {
        Playable actor = getActor();

        if(actor.isActionsDisabled())
        {
            setNextAction(INTERACT, object, null, false, false);
            clientActionFailed();
            return;
        }

        clearNextAction();
        changeIntention(AI_INTENTION_INTERACT, object, null);
        onEvtThink();
    }

    protected void thinkInteract()
    {
        Playable actor = getActor();

        GameObject target = (GameObject) getIntention_arg0();

        if(target == null)
        {
            setIntention(AI_INTENTION_ACTIVE);
            return;
        }

        int range = (int) (max(30, actor.getMinDistance(target)) + 20);

        if(actor.isInRangeZ(target, range))
        {
            if(actor.isPlayer())
                ((Player) actor).doInteract(target);
            setIntention(AI_INTENTION_ACTIVE);
        }
        else
        {
            actor.moveToLocation(target.getLoc(), 40, true);
            setNextAction(INTERACT, target, null, false, false);
        }
    }

    @Override
    protected void onIntentionPickUp(GameObject object)
    {
        Playable actor = getActor();

        if(actor.isActionsDisabled())
        {
            setNextAction(PICKUP, object, null, false, false);
            clientActionFailed();
            return;
        }

        clearNextAction();
        changeIntention(AI_INTENTION_PICK_UP, object, null);
        onEvtThink();
    }

    protected void thinkPickUp()
    {
        final Playable actor = getActor();

        final GameObject target = (GameObject) getIntention_arg0();

        if(target == null)
        {
            setIntention(AI_INTENTION_ACTIVE);
            return;
        }

        if(actor.isInRange(target, 30) && abs(actor.getZ() - target.getZ()) < 50)
        {
            if(actor.isPlayer() || actor.isPet())
                actor.doPickupItem(target);
            setIntention(AI_INTENTION_ACTIVE);
        }
        else
            getInstance().execute(new RunnableImpl()
            {
                @Override
                public void runImpl()
                {
                    actor.moveToLocation(target.getLoc(), 10, true);
                    setNextAction(PICKUP, target, null, false, false);
                }
            });
    }

    protected void thinkAttack(boolean checkRange)
    {
        Playable actor = getActor();

        Player player = actor.getPlayer();
        if(player == null)
        {
            setIntention(AI_INTENTION_ACTIVE);
            return;
        }

        if(actor.isActionsDisabled() || actor.isAttackingDisabled())
        {
            actor.sendActionFailed();
            return;
        }

        boolean isPosessed = actor instanceof Summon && ((Summon) actor).isDepressed();

        Creature attack_target = getAttackTarget();
        if(attack_target == null || attack_target.isDead() || !isPosessed && !(_forceUse ? attack_target.isAttackable(actor) : attack_target.isAutoAttackable(actor)))
        {
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        if(!checkRange)
        {
            clientStopMoving();
            actor.doAttack(attack_target);
            return;
        }

        int range = actor.getPhysicalAttackRange();
        if(range < 10)
            range = 10;

        boolean canSee = canSeeTarget(actor, attack_target, false);

        if(!canSee && (range > 200 || abs(actor.getZ() - attack_target.getZ()) > 200))
        {
            actor.sendPacket(CANNOT_SEE_TARGET);
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        range += actor.getMinDistance(attack_target);

        if(actor.isFakeDeath())
            actor.breakFakeDeath();

        if(actor.isInRangeZ(attack_target, range))
        {
            if(!canSee)
            {
                actor.sendPacket(CANNOT_SEE_TARGET);
                setIntention(AI_INTENTION_ACTIVE);
                actor.sendActionFailed();
                return;
            }

            clientStopMoving(false);
            actor.doAttack(attack_target);
        }
        else if(!isDontMove())
            getInstance().execute(new ExecuteFollow(attack_target, range - 20));
        else
            actor.sendActionFailed();
    }

    protected void thinkCast(boolean checkRange)
    {
        Playable actor = getActor();

        Creature target = getAttackTarget();

        if(getSkill().getSkillType() == CRAFT || getSkill().isToggle())
        {
            if(getSkill().checkCondition(actor, target, isForceUse(), isDontMove(), true))
                actor.doCast(getSkill(), target, isForceUse());
            return;
        }

        if(target == null || target.isDead() != getSkill().getCorpse() && !_skill.isNotTargetAoE())
        {
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        if(!checkRange)
        {
            // Если скилл имеет следующее действие, назначим это действие после окончания действия скилла
            if(getSkill().getNextAction() == ATTACK && !actor.equals(target))
                setNextAction(ATTACK, target, null, isForceUse(), false);
            else
                clearNextAction();

            clientStopMoving();

            if(getSkill().checkCondition(actor, target, isForceUse(), isDontMove(), true))
                actor.doCast(getSkill(), target, isForceUse());
            else
            {
                setNextIntention();
                if(getIntention() == AI_INTENTION_ATTACK)
                    thinkAttack(true);
            }

            return;
        }

        int range = actor.getMagicalAttackRange(getSkill());
        if(range < 10)
            range = 10;

        boolean canSee = getSkill().getSkillType() == TAKECASTLE || getSkill().getSkillType() == TAKEFORTRESS || canSeeTarget(actor, target, actor.isFlying());
        boolean noRangeSkill = getSkill().getCastRange() == 32767;

        if(!noRangeSkill && !canSee && (range > 200 || abs(actor.getZ() - target.getZ()) > 200))
        {
            actor.sendPacket(CANNOT_SEE_TARGET);
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
            return;
        }

        range += actor.getMinDistance(target);

        if(actor.isFakeDeath())
            actor.breakFakeDeath();

        if(actor.isInRangeZ(target, range) || noRangeSkill)
        {
            if(!noRangeSkill && !canSee)
            {
                actor.sendPacket(CANNOT_SEE_TARGET);
                setIntention(AI_INTENTION_ACTIVE);
                actor.sendActionFailed();
                return;
            }

            // Если скилл имеет следующее действие, назначим это действие после окончания действия скилла
            if(getSkill().getNextAction() == ATTACK && !actor.equals(target) && !isForceUse())
            {
                //clearNextAction();
                setNextAction(ATTACK, target, null, isForceUse(), false);
            }
            else
                clearNextAction();

            if(getSkill().checkCondition(actor, target, isForceUse(), isDontMove(), true))
            {
                clientStopMoving(false);
                actor.doCast(getSkill(), target, isForceUse());
            }
            else
            {
                setNextIntention();
                if(getIntention() == AI_INTENTION_ATTACK)
                    thinkAttack(true);
            }
        }
        else if(!isDontMove())
            getInstance().execute(new ExecuteFollow(target, range - 20));
        else
        {
            actor.sendPacket(YOUR_TARGET_IS_OUT_OF_RANGE);
            setIntention(AI_INTENTION_ACTIVE);
            actor.sendActionFailed();
        }
    }

    @Override
    protected void onEvtDead(Creature killer)
    {
        clearNextAction();
        super.onEvtDead(killer);
    }

    @Override
    protected void onEvtFakeDeath()
    {
        clearNextAction();
        super.onEvtFakeDeath();
    }

    public void lockTarget(Creature target)
    {
        Playable actor = getActor();

        if(target == null || target.isDead())
            actor.setAggressionTarget(null);
        else if(actor.getAggressionTarget() == null)
        {
            GameObject actorStoredTarget = actor.getTarget();
            actor.setAggressionTarget(target);
            actor.setTarget(target);

            clearNextAction();
            // DS: агрессия только перекидывает видимую цель, но не обрывает текущую атаку/каст
            /*if (getIntention() == CtrlIntention.AI_INTENTION_ATTACK)
                setAttackTarget(target);
            switch(getIntention())
            {
                case AI_INTENTION_ATTACK:
                    setAttackTarget(target);
                    break;
                case AI_INTENTION_CAST:
                    L2Skill skill = actor.getCastingSkill();
                    if(skill == null)
                        skill = _skill;
                    if(skill != null && !skill.isUsingWhileCasting())
                        switch(skill.getTargetType())
                        {
                            case TARGET_ONE:
                            case TARGET_AREA:
                            case TARGET_MULTIFACE:
                            case TARGET_TUNNEL:
                                setAttackTarget(target);
                                actor.setCastingTarget(target);
                                break;
                        }
                    break;
            }*/

            if(actorStoredTarget != target)
                actor.sendPacket(new MyTargetSelected(target.getObjectId(), 0));
        }
    }

    @Override
    public void Attack(GameObject target, boolean forceUse, boolean dontMove)
    {
        Playable actor = getActor();

        if(target.isCreature() && (actor.isActionsDisabled() || actor.isAttackingDisabled()))
        {
            // Если не можем атаковать, то атаковать позже
            setNextAction(ATTACK, target, null, forceUse, false);
            actor.sendActionFailed();
            return;
        }

        setDontMove(dontMove);
        setForceUse(forceUse);
        clearNextAction();
        setIntention(AI_INTENTION_ATTACK, target);
    }

    @Override
    public void Cast(Skill skill, Creature target, boolean forceUse, boolean dontMove)
    {
        Playable actor = getActor();

        // Если скилл альтернативного типа (например, бутылка на хп),
        // то он может использоваться во время каста других скиллов, или во время атаки, или на бегу.
        // Поэтому пропускаем дополнительные проверки.
        if(skill.altUse() || skill.isToggle())
        {
            if((skill.isToggle() || skill.isHandler()) && (actor.isOutOfControl() || actor.isStunned() || actor.isSleeping() || actor.isParalyzed() || actor.isAlikeDead()))
                clientActionFailed();
            else
                actor.altUseSkill(skill, target);
            return;
        }

        // Если не можем кастовать, то использовать скилл позже
        if(actor.isActionsDisabled())
        {
            //if(!actor.isSkillDisabled(skill.getId()))
            setNextAction(CAST, skill, target, forceUse, dontMove);
            clientActionFailed();
            return;
        }

        //_actor.stopMove(null);
        setForceUse(forceUse);
        setDontMove(dontMove);
        clearNextAction();
        setIntention(AI_INTENTION_CAST, skill, target);
    }

    @Override
    public Playable getActor()
    {
        return (Playable) super.getActor();
    }
}
 
PHP:
package ai;

import l2p.commons.util.Rnd;
import l2p.gameserver.ai.CtrlIntention;
import l2p.gameserver.ai.Fighter;
import l2p.gameserver.configs.proprties.ServerConfig;
import l2p.gameserver.geodata.GeoEngine;
import l2p.gameserver.model.Creature;
import l2p.gameserver.model.Playable;
import l2p.gameserver.model.instances.NpcInstance;
import l2p.gameserver.tables.SkillTable;

/**
* Created by STIGMATED
* Date: 12.11.11 Time: 12:43
*/
public class BossRandomAi extends Fighter
{

    // private static final int BossId = 91107;
    private int current_point = -1;
    private long _lastAction;
    // private static final long Teleport_period = 7200000; //30 * 60 * 1000= 30 min
    // private long _lastTeleport = System.currentTimeMillis();
    String[] _attackText = {"Эй, ты офигел чтоли! ", "Эээ.. мне же больно :(", "Папа у Васи осёл в математике!", "Советую вам не следовать моим советам.", "Мы с тревогой смотрим на будущее, а будущее с тревогой смотрит на нас.", "Мне чужого не надо….Но свое я заберу, чье бы оно ни было!!!", "Успокойся и не ной. Всё равно ты будешь мой", "Кстати, а тут все из разных мест? Или из одного?", "Как сервер, стоит играть?", "Ничто так не защищает мои зубы 12 часов днём и 12 часов ночью, как уважительное отношение к окружающим.", "Она: Все, я обиделась, тебе на меня наплевать, пойду в интернет и буду изменять тебе в онлайне! Касперского возьми!", "Есть еще похер в похеровницах.", "Жизнь нужно прожить так, чтобы Боги в восторге предложили еще одну…", "В погоне за зайцем многие подстреливают волка, испытывая минутное удовольствие, но в целом охота в данном случае является бессмысленной, поскольку они лишили цели волка и не достигли своей.", "Неважно, что вам говорят - вам говорят не всю правду", "Все люди братья. Но некоторые сестры", "Начни день весело: улыбни своё лицо!", "Все имеют право на тупость, просто некоторые очень злоупотребляют.", "Никто не знает столько, сколько не знаю я!", "Снимаю. Поpчy.", "Громче голову поворачивай!", "Что вы на меня свое лицо вытаращили?", "Я вас не спрашиваю, где вы были! Я спрашиваю, откуда вы идете?", "Чтоб не киснуть - надо квасить!", "Смерть застала его живым.", "Фаллический символ всегда лучше, чем символический фаллос.", "Неудачи преследуют всех. Но догоняют лишь неудачников.", "Жизнь прекрасна, рефлексы условны, а истина относительна.", "Первая заповедь холостяка: Не трогай пыль и она тебя не тронет.", "Спокойно! Уж в грязь лицом я не промахнусь!", "В Китае нет понятия «изменил». Есть понятие «перепутал».", "Труднее всего человеку дается то, что дается не ему."

    };

    public BossRandomAi(Creature actor)
    {
        super((NpcInstance) actor);
    }

    @Override
    protected boolean thinkActive()
    {

        return true;
    }

    @Override
    protected void onEvtAttacked(Creature attacker, int damage)
    {
        NpcInstance actor = getActor();
        if(attacker == null || attacker.getPlayer() == null)
            return;

        actor.startAttackStanceTask();

        // Ругаемся и кастуем скилл не чаще, чем раз в 3 секунды
        if(System.currentTimeMillis() - _lastAction > 27000)
        {
            int chance = Rnd.get(0, 100);
            if(chance < 2)
            {
                attacker.getPlayer().setKarma(attacker.getPlayer().getKarma() + 5);
                attacker.sendChanges();
            }
            else if(chance < 4)
                actor.doCast(SkillTable.getInstance().getInfo(553, 1), attacker, true);
            else
                actor.doCast(SkillTable.getInstance().getInfo(554, 1), attacker, true);

            actor.Shout(attacker.getName() + ", " + _attackText[Rnd.get(_attackText.length)]);
            _lastAction = System.currentTimeMillis();
        }
    }

    @Override
    public boolean isGlobalAI()
    {
        return true;
    }

    @Override
    public boolean checkAggression(Creature target)
    {
        NpcInstance actor = getActor();
        if((actor == null) || ( !(target instanceof Playable)))
            return false;
        if(getIntention() != CtrlIntention.AI_INTENTION_ACTIVE)
            return false;
        if(this.getGlobalAggro() < 0)
            return false;
        if( !actor.isInRange(target, actor.getAggroRange()))
            return false;
        if(Math.abs(target.getZ() - actor.getZ()) > 400)
            return false;
        // if ((Functions.getItemCount((L2Playable) target, 32100) != 0) || (Functions.getItemCount((L2Playable) target, 700) != 0))
        // return;
        if( !GeoEngine.canSeeTarget(actor, target, actor.isFlying()))
            return false;
        // target.addDamageHate((Creature)actor, 0, 1);
        setIntention(CtrlIntention.AI_INTENTION_ATTACK, target);

        String lang = ServerConfig.DefaultLang;

        if(lang.equalsIgnoreCase("en"))
            actor.Say("Die, noob!");
        else
        {
            actor.Say("Умри, Умри!");
        }
        if((getIntention() != CtrlIntention.AI_INTENTION_ACTIVE) && (this.current_point > -1))
            this.current_point -= 1;
        return true;
    }

    @Override
    protected void onEvtDead(Creature killer)
    {
        NpcInstance actor = getActor();
        if(actor == null)
            return;
        // actor.deleteMe();
        _log.info("убили гада");
        super.onEvtDead(killer);
    }

}
 
ждо за высер?
 
🤦
 
Для основы взял 2 варианта исходного кода L2jserver и Overworld, оба варианта хроник High Five
Сразу удивило различие в конструктивности и построении основ, что я немного запутался т.к. опыта в этой сфере нет...
И щас какую то хрень пишешь.
 
лыжа и овер несравнимы... даже такой наркОмэн как я это понимаю))))
 
:-D спасибо за конструктивную критику
 
Оффтоп:
Анциент, или растаруев.

Очередные прокси недерландов, львова и других стран.
 
Оффтоп:
Анциент, или растаруев.

Очередные прокси недерландов, львова и других стран.
вольфер))) тебя сейчас публично оскорбили... сравнение с яйциентом, это как посыл кое куда
 
:Nani1: А что, проски запрещены?

просто тут есть одно существо, которое плодит примерно подобные твоей бредотемы, сидящее из-под проксей и всефорумно призванное пидарасом.

и ты не поверишь - ты прям подходишь под всё, кроме первого пункта (возможно временно, кто знает?).
 
просто тут есть одно существо, которое плодит примерно подобные твоей бредотемы, сидящее из-под проксей и всефорумно призванное пидарасом.

и ты не поверишь - ты прям подходишь под всё, кроме первого пункта (возможно временно, кто знает?).
:Beaten: за то что посчитали за этого пи**аса обидно...
А по сути тему создал специально в говнокоде, думал мб критику адекватную услышу типо то или так....
P.S. Форум администраторов, а не школьников
 
Последнее редактирование:
тьфу, сорян. не первого пункта, а последнего.
 
Будем разговаривать когда выложите свое написанное двигло, а если хотите критики "до" "после", вылаживайте что было изменено, патчи, патчи.
Думаю тут люди ценят свой время, и смотреть в 2к строк изменения никто в здравом уме не станет. В первую очередь ВЫ, хотите критики, вы спрашиваете комьюнити, оформите тему достойно для начало.
 
Назад
Сверху Снизу