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;
}
Последнее редактирование: