Chronicle 4 branch.

This commit is contained in:
MobiusDev
2017-07-19 21:24:06 +00:00
parent 9a69bec286
commit 3a0bf3539a
13496 changed files with 641683 additions and 0 deletions

View File

@@ -0,0 +1,859 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2CharPosition;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2Skill;
import com.l2jmobius.gameserver.model.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.network.serverpackets.ActionFailed;
import com.l2jmobius.gameserver.network.serverpackets.AutoAttackStart;
import com.l2jmobius.gameserver.network.serverpackets.AutoAttackStop;
import com.l2jmobius.gameserver.network.serverpackets.CharMoveToLocation;
import com.l2jmobius.gameserver.network.serverpackets.Die;
import com.l2jmobius.gameserver.network.serverpackets.MoveToPawn;
import com.l2jmobius.gameserver.network.serverpackets.StopMove;
import com.l2jmobius.gameserver.network.serverpackets.StopRotation;
import com.l2jmobius.gameserver.taskmanager.AttackStanceTaskManager;
/**
* Mother class of all objects AI in the world.<BR>
* <BR>
* AbastractAI :<BR>
* <BR>
* <li>L2CharacterAI</li><BR>
* <BR>
*/
abstract class AbstractAI implements Ctrl
{
protected static final Logger _log = Logger.getLogger(AbstractAI.class.getName());
class FollowTask implements Runnable
{
int _range = 70;
public FollowTask()
{
}
public FollowTask(int range)
{
_range = range;
}
@Override
public void run()
{
try
{
if (_follow_task == null)
{
return;
}
if (_follow_target == null)
{
if (_actor instanceof L2Summon)
{
((L2Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
if (!_actor.isInsideRadius(_follow_target, _range, true, false))
{
if (!_actor.isInsideRadius(_follow_target, 3000, true, false))
{
// if the target is too far (maybe also teleported)
if (_actor instanceof L2Summon)
{
((L2Summon) _actor).setFollowStatus(false);
}
setIntention(AI_INTENTION_IDLE);
return;
}
moveToPawn(_follow_target, _range);
}
}
catch (final Throwable t)
{
_log.log(Level.WARNING, "", t);
}
}
}
/** The character that this AI manages */
final L2Character _actor;
/** An accessor for private methods of the actor */
final L2Character.AIAccessor _accessor;
/** Current long-term intention */
protected CtrlIntention _intention = AI_INTENTION_IDLE;
/** Current long-term intention parameter */
protected Object _intention_arg0 = null;
/** Current long-term intention parameter */
protected Object _intention_arg1 = null;
/** Flags about client's state, in order to know which messages to send */
protected boolean _client_moving;
/** Flags about client's state, in order to know which messages to send */
protected boolean _client_auto_attacking;
/** Flags about client's state, in order to know which messages to send */
protected int _client_moving_to_pawn_offset;
/** Different targets this AI maintains */
private L2Object _target;
private L2Character _cast_target;
protected L2Character _attack_target;
protected L2Character _follow_target;
/** The skill we are curently casting by INTENTION_CAST */
L2Skill _skill;
/** Diferent internal state flags */
private int _move_to_pawn_timeout;
protected Future<?> _follow_task = null;
private static final int FOLLOW_INTERVAL = 1000;
private static final int ATTACK_FOLLOW_INTERVAL = 500;
/**
* Constructor of AbstractAI.<BR>
* <BR>
* @param accessor The AI accessor of the L2Character
*/
protected AbstractAI(L2Character.AIAccessor accessor)
{
_accessor = accessor;
// Get the L2Character managed by this Accessor AI
_actor = accessor.getActor();
}
/**
* Return the L2Character managed by this Accessor AI.<BR>
* <BR>
*/
@Override
public L2Character getActor()
{
return _actor;
}
/**
* Return the current Intention.<BR>
* <BR>
*/
@Override
public CtrlIntention getIntention()
{
return _intention;
}
protected synchronized void setCastTarget(L2Character target)
{
_cast_target = target;
}
/**
* Return the current cast target.<BR>
* <BR>
* @return
*/
public L2Character getCastTarget()
{
return _cast_target;
}
protected synchronized void setAttackTarget(L2Character target)
{
_attack_target = target;
}
/**
* Return current attack target.<BR>
* <BR>
*/
@Override
public L2Character getAttackTarget()
{
return _attack_target;
}
/**
* Set the Intention of this AbstractAI.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is USED by AI classes</B></FONT><BR>
* <BR>
* <B><U> Overriden in </U> : </B><BR>
* <B>L2AttackableAI</B> : Create an AI Task executed every 1s (if necessary)<BR>
* <B>L2PlayerAI</B> : Stores the current AI intention parameters to later restore it if necessary<BR>
* <BR>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
/*
* if (Config.DEBUG) _log.warning("AbstractAI: changeIntention -> " + intention + " " + arg0 + " " + arg1);
*/
_intention = intention;
_intention_arg0 = arg0;
_intention_arg1 = arg1;
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT><BR>
* <BR>
* @param intention The new Intention to set to the AI
*/
@Override
public final void setIntention(CtrlIntention intention)
{
setIntention(intention, null, null);
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT><BR>
* <BR>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention (optional target)
*/
@Override
public final void setIntention(CtrlIntention intention, Object arg0)
{
setIntention(intention, arg0, null);
}
/**
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT><BR>
* <BR>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention (optional target)
* @param arg1 The second parameter of the Intention (optional target)
*/
@Override
public final void setIntention(CtrlIntention intention, Object arg0, Object arg1)
{
if (_actor instanceof L2PcInstance)
{
if (((L2PcInstance) _actor).isPendingSitting())
{
((L2PcInstance) _actor).setIsPendingSitting(false);
}
}
// Stop the follow mode if necessary
if ((intention != AI_INTENTION_FOLLOW) && (intention != AI_INTENTION_ATTACK))
{
stopFollow();
}
// Launch the onIntention method of the L2CharacterAI corresponding to the new Intention
switch (intention)
{
case AI_INTENTION_IDLE:
onIntentionIdle();
break;
case AI_INTENTION_ACTIVE:
onIntentionActive();
break;
case AI_INTENTION_REST:
onIntentionRest();
break;
case AI_INTENTION_ATTACK:
onIntentionAttack((L2Character) arg0);
break;
case AI_INTENTION_CAST:
onIntentionCast((L2Skill) arg0, (L2Object) arg1);
break;
case AI_INTENTION_MOVE_TO:
onIntentionMoveTo((L2CharPosition) arg0);
break;
case AI_INTENTION_FOLLOW:
onIntentionFollow((L2Character) arg0);
break;
case AI_INTENTION_PICK_UP:
onIntentionPickUp((L2Object) arg0);
break;
case AI_INTENTION_INTERACT:
onIntentionInteract((L2Object) arg0);
break;
}
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned periode)</B></FONT><BR>
* <BR>
* @param evt The event whose the AI must be notified
*/
@Override
public final void notifyEvent(CtrlEvent evt)
{
notifyEvent(evt, null, null);
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned periode)</B></FONT><BR>
* <BR>
* @param evt The event whose the AI must be notified
* @param arg0 The first parameter of the Event (optional target)
*/
@Override
public final void notifyEvent(CtrlEvent evt, Object arg0)
{
notifyEvent(evt, arg0, null);
}
/**
* Launch the L2CharacterAI onEvt method corresponding to the Event.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : The current general intention won't be change (ex : If the character attack and is stunned, he will attack again after the stunned periode)</B></FONT><BR>
* <BR>
* @param evt The event whose the AI must be notified
* @param arg0 The first parameter of the Event (optional target)
* @param arg1 The second parameter of the Event (optional target)
*/
@Override
public final void notifyEvent(CtrlEvent evt, Object arg0, Object arg1)
{
if (!_actor.isVisible() || !_actor.hasAI())
{
return;
}
switch (evt)
{
case EVT_THINK:
onEvtThink();
break;
case EVT_ATTACKED:
onEvtAttacked((L2Character) arg0);
break;
case EVT_AGGRESSION:
onEvtAggression((L2Character) arg0, ((Number) arg1).intValue());
break;
case EVT_STUNNED:
onEvtStunned((L2Character) arg0);
break;
case EVT_PARALYZED:
onEvtParalyzed((L2Character) arg0);
break;
case EVT_SLEEPING:
onEvtSleeping((L2Character) arg0);
break;
case EVT_ROOTED:
onEvtRooted((L2Character) arg0);
break;
case EVT_CONFUSED:
onEvtConfused((L2Character) arg0);
break;
case EVT_MUTED:
onEvtMuted((L2Character) arg0);
break;
case EVT_READY_TO_ACT:
onEvtReadyToAct();
break;
case EVT_USER_CMD:
onEvtUserCmd(arg0, arg1);
break;
case EVT_ARRIVED:
onEvtArrived();
break;
case EVT_ARRIVED_REVALIDATE:
onEvtArrivedRevalidate();
break;
case EVT_ARRIVED_BLOCKED:
onEvtArrivedBlocked((L2CharPosition) arg0);
break;
case EVT_FORGET_OBJECT:
onEvtForgetObject((L2Object) arg0);
break;
case EVT_CANCEL:
onEvtCancel();
break;
case EVT_DEAD:
onEvtDead();
break;
case EVT_FAKE_DEATH:
onEvtFakeDeath();
break;
case EVT_FINISH_CASTING:
onEvtFinishCasting();
break;
}
}
protected abstract void onIntentionIdle();
protected abstract void onIntentionActive();
protected abstract void onIntentionRest();
protected abstract void onIntentionAttack(L2Character target);
protected abstract void onIntentionCast(L2Skill skill, L2Object target);
protected abstract void onIntentionMoveTo(L2CharPosition destination);
protected abstract void onIntentionFollow(L2Character target);
protected abstract void onIntentionPickUp(L2Object item);
protected abstract void onIntentionInteract(L2Object object);
protected abstract void onEvtThink();
protected abstract void onEvtAttacked(L2Character attacker);
protected abstract void onEvtAggression(L2Character target, int aggro);
protected abstract void onEvtStunned(L2Character attacker);
protected abstract void onEvtParalyzed(L2Character attacker);
protected abstract void onEvtSleeping(L2Character attacker);
protected abstract void onEvtRooted(L2Character attacker);
protected abstract void onEvtConfused(L2Character attacker);
protected abstract void onEvtMuted(L2Character attacker);
protected abstract void onEvtReadyToAct();
protected abstract void onEvtUserCmd(Object arg0, Object arg1);
protected abstract void onEvtArrived();
protected abstract void onEvtArrivedRevalidate();
protected abstract void onEvtArrivedBlocked(L2CharPosition blocked_at_pos);
protected abstract void onEvtForgetObject(L2Object object);
protected abstract void onEvtCancel();
protected abstract void onEvtDead();
protected abstract void onEvtFakeDeath();
protected abstract void onEvtFinishCasting();
/**
* Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
*/
protected void clientActionFailed()
{
if (_actor instanceof L2PcInstance)
{
_actor.sendPacket(new ActionFailed());
}
}
/**
* Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn <I>(broadcast)</I>.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
* @param pawn
* @param offset
*/
protected void moveToPawn(L2Object pawn, int offset)
{
// Chek if actor can move
if (!_actor.isMovementDisabled())
{
if (offset < 10)
{
offset = 10;
}
// prevent possible extra calls to this function (there is none?),
// also don't send movetopawn packets too often
boolean sendPacket = true;
if (_client_moving && (_target == pawn))
{
if (_client_moving_to_pawn_offset == offset)
{
if (GameTimeController.getGameTicks() < _move_to_pawn_timeout)
{
return;
}
sendPacket = false;
}
else if (_actor.isOnGeodataPath())
{
// minimum time to calculate new route is 2 seconds
if (GameTimeController.getGameTicks() < (_move_to_pawn_timeout + 10))
{
return;
}
}
}
// Set AI movement data
_client_moving = true;
_client_moving_to_pawn_offset = offset;
_target = pawn;
_move_to_pawn_timeout = GameTimeController.getGameTicks();
_move_to_pawn_timeout += 1000 / GameTimeController.MILLIS_IN_TICK;
if ((pawn == null) || (_accessor == null))
{
return;
}
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_accessor.moveTo(pawn.getX(), pawn.getY(), pawn.getZ(), offset);
if (!_actor.isMoving())
{
_actor.sendPacket(new ActionFailed());
return;
}
// Send a Server->Client packet MoveToPawn/CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
if (pawn instanceof L2Character)
{
if (_actor.isOnGeodataPath())
{
_actor.broadcastPacket(new CharMoveToLocation(_actor));
_client_moving_to_pawn_offset = 0;
}
else if (sendPacket)
{
_actor.broadcastPacket(new MoveToPawn(_actor, (L2Character) pawn, offset));
}
}
else
{
_actor.broadcastPacket(new CharMoveToLocation(_actor));
}
}
else
{
_actor.sendPacket(new ActionFailed());
}
}
/**
* Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation <I>(broadcast)</I>.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
* @param x
* @param y
* @param z
*/
protected void moveTo(int x, int y, int z)
{
// Chek if actor can move
if (!_actor.isMovementDisabled())
{
// Set AI movement data
_client_moving = true;
_client_moving_to_pawn_offset = 0;
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
_accessor.moveTo(x, y, z);
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
final CharMoveToLocation msg = new CharMoveToLocation(_actor);
_actor.broadcastPacket(msg);
}
else
{
_actor.sendPacket(new ActionFailed());
}
}
/**
* Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation <I>(broadcast)</I>.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
* @param pos
*/
protected void clientStopMoving(L2CharPosition pos)
{
// Stop movement of the L2Character
if (_actor.isMoving())
{
_accessor.stopMove(pos);
}
_client_moving_to_pawn_offset = 0;
if (_client_moving || (pos != null))
{
_client_moving = false;
// Send a Server->Client packet StopMove to the actor and all L2PcInstance in its _knownPlayers
final StopMove msg = new StopMove(_actor);
_actor.broadcastPacket(msg);
if (pos != null)
{
// Send a Server->Client packet StopRotation to the actor and all L2PcInstance in its _knownPlayers
final StopRotation sr = new StopRotation(_actor.getObjectId(), pos.heading, 0);
_actor.sendPacket(sr);
_actor.broadcastPacket(sr);
}
}
}
// Client has already arrived to target, no need to force StopMove packet
protected void clientStoppedMoving()
{
if (_client_moving_to_pawn_offset > 0)
{
_client_moving_to_pawn_offset = 0;
_actor.broadcastPacket(new StopMove(_actor));
}
_client_moving = false;
}
public boolean isAutoAttacking()
{
return _client_auto_attacking;
}
public void setAutoAttacking(boolean isAutoAttacking)
{
if (_actor instanceof L2Summon)
{
final L2Summon summon = (L2Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().setAutoAttacking(isAutoAttacking);
}
return;
}
_client_auto_attacking = isAutoAttacking;
}
/**
* Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart <I>(broadcast)</I>.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
*/
public void clientStartAutoAttack()
{
if (_actor instanceof L2Summon)
{
final L2Summon summon = (L2Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStartAutoAttack();
}
return;
}
if (!isAutoAttacking())
{
if ((_actor instanceof L2PcInstance) && (((L2PcInstance) _actor).getPet() != null))
{
((L2PcInstance) _actor).getPet().broadcastPacket(new AutoAttackStart(((L2PcInstance) _actor).getPet().getObjectId()));
}
// Send a Server->Client packet AutoAttackStart to the actor and all L2PcInstance in its _knownPlayers
_actor.broadcastPacket(new AutoAttackStart(_actor.getObjectId()));
setAutoAttacking(true);
}
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
/**
* Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop <I>(broadcast)</I>.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
*/
public void clientStopAutoAttack()
{
if (_actor instanceof L2Summon)
{
final L2Summon summon = (L2Summon) _actor;
if (summon.getOwner() != null)
{
summon.getOwner().getAI().clientStopAutoAttack();
}
return;
}
if (_actor instanceof L2PcInstance)
{
if (!AttackStanceTaskManager.getInstance().getAttackStanceTask(_actor) && isAutoAttacking())
{
AttackStanceTaskManager.getInstance().addAttackStanceTask(_actor);
}
}
else if (isAutoAttacking())
{
_actor.broadcastPacket(new AutoAttackStop(_actor.getObjectId()));
setAutoAttacking(false);
}
}
/**
* Kill the actor client side by sending Server->Client packet AutoAttackStop, StopMove/StopRotation, Die <I>(broadcast)</I>.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
*/
protected void clientNotifyDead()
{
// Send a Server->Client packet Die to the actor and all L2PcInstance in its _knownPlayers
final Die msg = new Die(_actor);
_actor.broadcastPacket(msg);
// Init AI
_intention = AI_INTENTION_IDLE;
_target = null;
_cast_target = null;
_attack_target = null;
// Cancel the follow task if necessary
stopFollow();
}
/**
* Update the state of this actor client side by sending Server->Client packet MoveToPawn/CharMoveToLocation and AutoAttackStart to the L2PcInstance player.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT><BR>
* <BR>
* @param player The L2PcIstance to notify with state of this L2Character
*/
public void describeStateToPlayer(L2PcInstance player)
{
if (_client_moving)
{
if ((_client_moving_to_pawn_offset != 0) && (_follow_target != null))
{
// Send a Server->Client packet MoveToPawn to the actor and all L2PcInstance in its _knownPlayers
final MoveToPawn msg = new MoveToPawn(_actor, _follow_target, _client_moving_to_pawn_offset);
player.sendPacket(msg);
}
else
{
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
final CharMoveToLocation msg = new CharMoveToLocation(_actor);
player.sendPacket(msg);
}
}
}
/**
* Create and Launch an AI Follow Task to execute every 1s.<BR>
* <BR>
* @param target The L2Character to follow
*/
public synchronized void startFollow(L2Character target)
{
if (_follow_task != null)
{
_follow_task.cancel(false);
_follow_task = null;
}
// Create and Launch an AI Follow Task to execute every 1s
_follow_target = target;
_follow_task = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new FollowTask(), 5, FOLLOW_INTERVAL);
}
/**
* Create and Launch an AI Follow Task to execute every 0.5s, following at specified range.<BR>
* <BR>
* @param target The L2Character to follow
* @param range
*/
public synchronized void startFollow(L2Character target, int range)
{
if (_follow_task != null)
{
_follow_task.cancel(false);
_follow_task = null;
}
_follow_target = target;
_follow_task = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(new FollowTask(range), 5, ATTACK_FOLLOW_INTERVAL);
}
/**
* Stop an AI Follow Task.<BR>
* <BR>
*/
public synchronized void stopFollow()
{
if (_follow_task != null)
{
// Stop the Follow Task
_follow_task.cancel(false);
_follow_task = null;
}
_follow_target = null;
}
protected L2Character getFollowTarget()
{
return _follow_target;
}
protected L2Object getTarget()
{
return _target;
}
protected synchronized void setTarget(L2Object target)
{
_target = target;
}
}

View File

@@ -0,0 +1,69 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.L2Character;
/**
* Interface of AI and client state. To correctly send messages to client we need it's state. For example, if we've sent 'StartAutoAttack' message, we need to send 'StopAutoAttack' message before any other action. Or if we've sent 'MoveToPawn', we need to send 'StopMove' when the movement of a
* character is canceled (by Root spell or any other reason). Thus, we need to know the state of client, i.e. which messages we've sent and how the client will show the scene. Close to this task is the task of AI. If a player's character is attacking a mob, his ATTACK may be iterrupted by an event,
* that temporary disable attacking. But when the possibility to ATTACK will be enabled, the character must continue the ATTACK. For mobs it may be more complex, since we want them to decide when to use magic, or when to follow the player for physical combat, or when to escape, to help another mob,
* etc. This interface is hiding complexity of server<->client interaction and multiple states of a character. It allows to set a desired, simple "wish" of a character, and the implementation of this interface will take care about the rest. The goal of a character may be like "ATTACK", "random walk"
* and so on. To reach the goal inplementation will split it into several small actions, several steps (possibly repeatable). Like "run to target" then "hit it", then if target is not dead - repeat. This flow of simplier steps may be interrupted by incoming events. Like a character's movement was
* disabled (by Root spell, for instance). Depending on character's ability AI may choose to wait, or to use magic ATTACK and so on. Additionally incoming events are compared with client's state of the character, and required network messages are sent to client's, i.e. if we have incoming event that
* character's movement was disabled, it causes changing if its behavour, and if client's state for the character is "moving" we send messages to clients to stop the avatar/mob.
*/
public interface Ctrl
{
/**
* the character this AI serves
* @return
*/
L2Character getActor();
/**
* get current intention
* @return
*/
CtrlIntention getIntention();
/**
* get current ATTACK target
* @return
*/
L2Character getAttackTarget();
/**
* Set general state/intention for AI, with optional data
* @param intention
*/
void setIntention(CtrlIntention intention);
void setIntention(CtrlIntention intention, Object arg0);
void setIntention(CtrlIntention intention, Object arg0, Object arg1);
/**
* Event, that notifies about previous step result, or user command, that does not change current general intention
* @param evt
*/
void notifyEvent(CtrlEvent evt);
void notifyEvent(CtrlEvent evt, Object arg0);
void notifyEvent(CtrlEvent evt, Object arg0, Object arg1);
}

View File

@@ -0,0 +1,80 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
/**
* This class contains an enum of each possibles evenements that can happen on an AI character.
*/
public enum CtrlEvent
{
/**
* Something has changed, usually a previous step has being completed or maybe was completed, the AI must thing on next action
*/
EVT_THINK,
/**
* The actor was attacked. This event comes each time a physical or magical attack was done on the actor. NPC may start attack in responce, or ignore this event if they already attack someone, or change target and so on.
*/
EVT_ATTACKED,
/** Increase/decrease aggression towards a target, or reduce global aggression if target is null */
EVT_AGGRESSION,
/** Actor is in stun state */
EVT_STUNNED,
/** Actor is paralyzed or petrified */
EVT_PARALYZED,
/** Actor starts/stops sleeping */
EVT_SLEEPING,
/** Actor is in rooted state (cannot move) */
EVT_ROOTED,
/**
* An event that previous action was completed. The action may be an attempt to physically/magically hit an enemy, or an action that discarded attack attempt has finished.
*/
EVT_READY_TO_ACT,
/**
* User's command, like using a combat magic or changing weapon, etc. The command is not intended to change final goal
*/
EVT_USER_CMD,
/**
* The actor arrived to assigned location, or it's a time to modify movement destination (follow, interact, random move and others intentions).
*/
EVT_ARRIVED,
/**
* The actor arrived to an intermidiate point, and needs revalidate destination. This is sent when follow/move to pawn if destination is far away.
*/
EVT_ARRIVED_REVALIDATE,
/** The actor cannot move anymore. */
EVT_ARRIVED_BLOCKED,
/** Forgets an object (if it's used as attack target, follow target and so on */
EVT_FORGET_OBJECT,
/**
* Attempt to cancel current step execution, but not change the intention. For example, the actor was putted into a stun, so it's current attack or movement has to be canceled. But after the stun state expired, the actor may try to attack again. Another usage for CANCEL is a user's attempt to
* cancel a cast/bow attack and so on.
*/
EVT_CANCEL,
/** The character is dead */
EVT_DEAD,
/** The character looks like dead */
EVT_FAKE_DEATH,
/** The character attack anyone randomly **/
EVT_CONFUSED,
/** The character cannot cast spells anymore **/
EVT_MUTED,
/** The character flee in randoms directions **/
EVT_AFRAID,
/** The character finish casting **/
EVT_FINISH_CASTING
}

View File

@@ -0,0 +1,45 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
/**
* Enumaration of generic intentions of an NPC/PC, an intention may require several steps to be completed
*/
public enum CtrlIntention
{
/** Do nothing, disconnect AI of NPC if no players around */
AI_INTENTION_IDLE,
/** Alerted state without goal : scan attackable targets, random walk, etc */
AI_INTENTION_ACTIVE,
/** Rest (sit until attacked) */
AI_INTENTION_REST,
/**
* Attack target (cast combat magic, go to target, combat), may be ignored, if target is locked on another character or a peacefull zone and so on
*/
AI_INTENTION_ATTACK,
/** Cast a spell, depending on the spell - may start or stop attacking */
AI_INTENTION_CAST,
/** Just move to another location */
AI_INTENTION_MOVE_TO,
/** Like move, but check target's movement and follow it */
AI_INTENTION_FOLLOW,
/** PickUp and item, (got to item, pickup it, become idle */
AI_INTENTION_PICK_UP,
/** Move to target, then interact */
AI_INTENTION_INTERACT;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,168 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.model.L2CharPosition;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2Skill;
import com.l2jmobius.gameserver.model.actor.instance.L2BoatInstance;
import com.l2jmobius.gameserver.network.serverpackets.VehicleDeparture;
import com.l2jmobius.gameserver.network.serverpackets.VehicleInfo;
import com.l2jmobius.gameserver.network.serverpackets.VehicleStarted;
/**
* @author DS
*/
public class L2BoatAI extends L2CharacterAI
{
public L2BoatAI(L2BoatInstance.AIAccessor accessor)
{
super(accessor);
}
@Override
protected void onIntentionAttack(L2Character target)
{
}
@Override
protected void onIntentionCast(L2Skill skill, L2Object target)
{
}
@Override
protected void onIntentionFollow(L2Character target)
{
}
@Override
protected void onIntentionPickUp(L2Object item)
{
}
@Override
protected void onIntentionInteract(L2Object object)
{
}
@Override
protected void onEvtAttacked(L2Character attacker)
{
}
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
}
@Override
protected void onEvtStunned(L2Character attacker)
{
}
@Override
protected void onEvtSleeping(L2Character attacker)
{
}
@Override
protected void onEvtRooted(L2Character attacker)
{
}
@Override
protected void onEvtForgetObject(L2Object object)
{
}
@Override
protected void onEvtCancel()
{
}
@Override
protected void onEvtDead()
{
}
@Override
protected void onEvtFakeDeath()
{
}
@Override
protected void onEvtFinishCasting()
{
}
@Override
protected void clientActionFailed()
{
}
@Override
protected void moveToPawn(L2Object pawn, int offset)
{
}
@Override
protected void moveTo(int x, int y, int z)
{
if (!_actor.isMovementDisabled())
{
if (!_client_moving)
{
_actor.broadcastPacket(new VehicleStarted(_actor.getObjectId(), 1));
}
_client_moving = true;
_accessor.moveTo(x, y, z);
_actor.broadcastPacket(new VehicleDeparture(getActor()));
}
}
@Override
protected void clientStoppedMoving()
{
_client_moving = false;
_actor.broadcastPacket(new VehicleStarted(_actor.getObjectId(), 0));
_actor.broadcastPacket(new VehicleInfo(getActor()));
}
@Override
protected void clientStopMoving(L2CharPosition pos)
{
if (_actor.isMoving())
{
_accessor.stopMove(pos);
}
if (_client_moving || (pos != null))
{
_client_moving = false;
_actor.broadcastPacket(new VehicleStarted(_actor.getObjectId(), 0));
_actor.broadcastPacket(new VehicleInfo(getActor()));
}
}
@Override
public L2BoatInstance getActor()
{
return (L2BoatInstance) _actor;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,643 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import java.util.List;
import com.l2jmobius.gameserver.model.L2Attackable;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2Character.AIAccessor;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2Skill;
import com.l2jmobius.gameserver.model.MobGroup;
import com.l2jmobius.gameserver.model.MobGroupTable;
import com.l2jmobius.gameserver.model.actor.instance.L2ControllableMobInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2FolkInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PlayableInstance;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.Rnd;
import javolution.util.FastList;
/**
* @author littlecrow AI for controllable mobs
*/
public class L2ControllableMobAI extends L2AttackableAI
{
public static final int AI_IDLE = 1;
public static final int AI_NORMAL = 2;
public static final int AI_FORCEATTACK = 3;
public static final int AI_FOLLOW = 4;
public static final int AI_CAST = 5;
public static final int AI_ATTACK_GROUP = 6;
private int _alternateAI;
private boolean _isThinking; // to prevent thinking recursively
private boolean _isNotMoving;
private L2Character _forcedTarget;
private MobGroup _targetGroup;
protected void thinkFollow()
{
final L2Attackable me = (L2Attackable) _actor;
if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, me, getForcedTarget(), true))
{
final int signX = (Rnd.nextInt(2) == 0) ? -1 : 1;
final int signY = (Rnd.nextInt(2) == 0) ? -1 : 1;
final int randX = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
final int randY = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
moveTo(getForcedTarget().getX() + (signX * randX), getForcedTarget().getY() + (signY * randY), getForcedTarget().getZ());
}
}
@Override
protected void onEvtThink()
{
if (isThinking() || _actor.isAllSkillsDisabled())
{
return;
}
setThinking(true);
try
{
switch (getAlternateAI())
{
case AI_IDLE:
if (getIntention() != CtrlIntention.AI_INTENTION_ACTIVE)
{
setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
}
break;
case AI_FOLLOW:
thinkFollow();
break;
case AI_CAST:
thinkCast();
break;
case AI_FORCEATTACK:
thinkForceAttack();
break;
case AI_ATTACK_GROUP:
thinkAttackGroup();
break;
default:
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
break;
}
}
finally
{
setThinking(false);
}
}
protected void thinkCast()
{
final L2Attackable npc = (L2Attackable) _actor;
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
{
setAttackTarget(findNextRndTarget());
clientStopMoving(null);
}
if (getAttackTarget() == null)
{
return;
}
npc.setTarget(getAttackTarget());
L2Skill[] skills = null;
try
{
skills = _actor.getAllSkills();
}
catch (final NullPointerException e)
{
_log.warning("Encountered Null Value.");
e.printStackTrace();
}
if (!_actor.isMuted())
{
int max_range = 0;
// check distant skills
if (skills != null)
{
for (final L2Skill sk : skills)
{
if (Util.checkIfInRange(sk.getCastRange(), _actor, getAttackTarget(), true) && !_actor.isSkillDisabled(sk.getId()) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
max_range = Math.max(max_range, sk.getCastRange());
}
}
if (!isNotMoving())
{
moveToPawn(getAttackTarget(), max_range);
}
return;
}
}
protected void thinkAttackGroup()
{
final L2Character target = getForcedTarget();
if ((target == null) || target.isAlikeDead())
{
// try to get next group target
setForcedTarget(findNextGroupTarget());
clientStopMoving(null);
}
if (target == null)
{
return;
}
L2Skill[] skills = null;
double dist2 = 0;
int range = 0;
int max_range = 0;
_actor.setTarget(target);
// as a response, we put the target in a forcedattack mode
final L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target;
final L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI();
ctrlAi.forceAttack(_actor);
try
{
skills = _actor.getAllSkills();
dist2 = _actor.getPlanDistanceSq(target.getX(), target.getY());
range = _actor.getPhysicalAttackRange() + (int) (_actor.getTemplate().collisionRadius + target.getTemplate().collisionRadius);
max_range = range;
}
catch (final NullPointerException e)
{
_log.warning("Encountered Null Value.");
e.printStackTrace();
}
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
if (skills != null)
{
for (final L2Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk.getId()) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
}
if (!isNotMoving())
{
moveToPawn(target, range);
}
return;
}
_accessor.doAttack(target);
}
protected void thinkForceAttack()
{
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
{
clientStopMoving(null);
setIntention(AI_INTENTION_ACTIVE);
setAlternateAI(AI_IDLE);
}
L2Skill[] skills = null;
double dist2 = 0;
int range = 0;
int max_range = 0;
try
{
_actor.setTarget(getForcedTarget());
skills = _actor.getAllSkills();
dist2 = _actor.getPlanDistanceSq(getForcedTarget().getX(), getForcedTarget().getY());
range = _actor.getPhysicalAttackRange() + (int) (_actor.getTemplate().collisionRadius + getForcedTarget().getTemplate().collisionRadius);
max_range = range;
}
catch (final NullPointerException e)
{
_log.warning("Encountered Null Value.");
e.printStackTrace();
}
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
if (skills != null)
{
for (final L2Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk.getId()) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
}
if (!isNotMoving())
{
moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange());
}
return;
}
_accessor.doAttack(getForcedTarget());
}
protected void thinkAttack()
{
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
{
if (getAttackTarget() != null)
{
// stop hating
final L2Attackable npc = (L2Attackable) _actor;
npc.stopHating(getAttackTarget());
}
setIntention(AI_INTENTION_ACTIVE);
}
else
{
// notify aggression
if ((((L2NpcInstance) _actor).getFactionId() != null) && !(getAttackTarget() instanceof L2Attackable))
{
final String faction_id = ((L2NpcInstance) _actor).getFactionId();
for (final L2Object obj : _actor.getKnownList().getKnownObjects().values())
{
if (!(obj instanceof L2NpcInstance))
{
continue;
}
final L2NpcInstance npc = (L2NpcInstance) obj;
if (!faction_id.equals(npc.getFactionId()))
{
continue;
}
if (_actor.isInsideRadius(npc, (npc.getFactionRange() + npc.getAggroRange()), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 600) && _actor.getAttackByList().contains(getAttackTarget()) && (npc.getAI() != null) && (npc.getAI()._intention != CtrlIntention.AI_INTENTION_ATTACK))
{
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
}
}
}
L2Skill[] skills = null;
double dist2 = 0;
int range = 0;
int max_range = 0;
try
{
_actor.setTarget(getAttackTarget());
skills = _actor.getAllSkills();
dist2 = _actor.getPlanDistanceSq(getAttackTarget().getX(), getAttackTarget().getY());
range = _actor.getPhysicalAttackRange() + (int) (_actor.getTemplate().collisionRadius + getAttackTarget().getTemplate().collisionRadius);
max_range = range;
}
catch (final NullPointerException e)
{
_log.warning("Encountered Null Value.");
e.printStackTrace();
}
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
{
// check distant skills
if (skills != null)
{
for (final L2Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk.getId()) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
max_range = Math.max(max_range, castRange);
}
}
moveToPawn(getAttackTarget(), range);
return;
}
// Force mobs to attack anybody if confused.
L2Character hated;
if (_actor.isConfused())
{
hated = findNextRndTarget();
}
else
{
hated = getAttackTarget();
}
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE);
return;
}
if (hated != getAttackTarget())
{
setAttackTarget(hated);
}
if (!_actor.isMuted() && (skills != null) && (skills.length > 0) && (Rnd.nextInt(5) == 3))
{
for (final L2Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk.getId()) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)))
{
_accessor.doCast(sk);
return;
}
}
}
_accessor.doAttack(getAttackTarget());
}
}
private void thinkActive()
{
setAttackTarget(findNextRndTarget());
L2Character hated;
if (_actor.isConfused())
{
hated = findNextRndTarget();
}
else
{
hated = getAttackTarget();
}
if (hated != null)
{
_actor.setRunning();
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
}
}
private boolean autoAttackCondition(L2Character target)
{
if ((target == null) || !(_actor instanceof L2Attackable))
{
return false;
}
final L2Attackable me = (L2Attackable) _actor;
if ((target instanceof L2FolkInstance) || (target instanceof L2DoorInstance))
{
return false;
}
if (target.isAlikeDead() || !me.isInsideRadius(target, me.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100))
{
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul())
{
return false;
}
// Check if the target is a L2PlayableInstance
if (target instanceof L2PlayableInstance)
{
// Check if the target isn't in silent move mode
if (((L2PlayableInstance) target).isSilentMoving())
{
return false;
}
}
if (target instanceof L2NpcInstance)
{
return false;
}
return me.isAggressive();
}
private L2Character findNextRndTarget()
{
if (getAttackTarget() == null)
{
return null;
}
final int aggroRange = ((L2Attackable) _actor).getAggroRange();
final L2Attackable npc = (L2Attackable) _actor;
int npcX, npcY, targetX, targetY;
double dy, dx;
final double dblAggroRange = aggroRange * aggroRange;
final List<L2Character> potentialTarget = new FastList<>();
for (final L2Object obj : npc.getKnownList().getKnownObjects().values())
{
if (!(obj instanceof L2Character))
{
continue;
}
npcX = npc.getX();
npcY = npc.getY();
targetX = obj.getX();
targetY = obj.getY();
dx = npcX - targetX;
dy = npcY - targetY;
if (((dx * dx) + (dy * dy)) > dblAggroRange)
{
continue;
}
final L2Character target = (L2Character) obj;
if (autoAttackCondition(target))
{
potentialTarget.add(target);
}
}
if (potentialTarget.size() == 0)
{
return null;
}
// we choose a random target
final int choice = Rnd.nextInt(potentialTarget.size());
final L2Character target = potentialTarget.get(choice);
return target;
}
private L2ControllableMobInstance findNextGroupTarget()
{
return getGroupTarget().getRandomMob();
}
public L2ControllableMobAI(AIAccessor accessor)
{
super(accessor);
setAlternateAI(AI_IDLE);
}
public int getAlternateAI()
{
return _alternateAI;
}
public void setAlternateAI(int _alternateai)
{
_alternateAI = _alternateai;
}
public void forceAttack(L2Character target)
{
setAlternateAI(AI_FORCEATTACK);
setForcedTarget(target);
}
public void forceAttackGroup(MobGroup group)
{
setForcedTarget(null);
setGroupTarget(group);
setAlternateAI(AI_ATTACK_GROUP);
}
public void stop()
{
setAlternateAI(AI_IDLE);
clientStopMoving(null);
}
public void move(int x, int y, int z)
{
moveTo(x, y, z);
}
public void follow(L2Character target)
{
setAlternateAI(AI_FOLLOW);
setForcedTarget(target);
}
public boolean isThinking()
{
return _isThinking;
}
public boolean isNotMoving()
{
return _isNotMoving;
}
public void setNotMoving(boolean isNotMoving)
{
_isNotMoving = isNotMoving;
}
public void setThinking(boolean isThinking)
{
_isThinking = isThinking;
}
private L2Character getForcedTarget()
{
return _forcedTarget;
}
private MobGroup getGroupTarget()
{
return _targetGroup;
}
private void setForcedTarget(L2Character forcedTarget)
{
_forcedTarget = forcedTarget;
}
private void setGroupTarget(MobGroup targetGroup)
{
_targetGroup = targetGroup;
}
}

View File

@@ -0,0 +1,178 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2CharPosition;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2Skill;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2SiegeGuardInstance;
/**
* @author mkizub TODO To change the template for this generated type comment go to Window - Preferences - Java - Code Style - Code Templates
*/
public class L2DoorAI extends L2CharacterAI
{
public L2DoorAI(L2DoorInstance.AIAccessor accessor)
{
super(accessor);
}
// rather stupid AI... well, it's for doors :D
@Override
protected void onIntentionIdle()
{
}
@Override
protected void onIntentionActive()
{
}
@Override
protected void onIntentionRest()
{
}
@Override
protected void onIntentionAttack(L2Character target)
{
}
@Override
protected void onIntentionCast(L2Skill skill, L2Object target)
{
}
@Override
protected void onIntentionMoveTo(L2CharPosition destination)
{
}
@Override
protected void onIntentionFollow(L2Character target)
{
}
@Override
protected void onIntentionPickUp(L2Object item)
{
}
@Override
protected void onIntentionInteract(L2Object object)
{
}
@Override
protected void onEvtThink()
{
}
@Override
protected void onEvtAttacked(L2Character attacker)
{
final L2DoorInstance me = (L2DoorInstance) _actor;
ThreadPoolManager.getInstance().executeTask(new onEventAttackedDoorTask(me, attacker));
}
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
}
@Override
protected void onEvtStunned(L2Character attacker)
{
}
@Override
protected void onEvtSleeping(L2Character attacker)
{
}
@Override
protected void onEvtRooted(L2Character attacker)
{
}
@Override
protected void onEvtReadyToAct()
{
}
@Override
protected void onEvtUserCmd(Object arg0, Object arg1)
{
}
@Override
protected void onEvtArrived()
{
}
@Override
protected void onEvtArrivedRevalidate()
{
}
@Override
protected void onEvtArrivedBlocked(L2CharPosition blocked_at_pos)
{
}
@Override
protected void onEvtForgetObject(L2Object object)
{
}
@Override
protected void onEvtCancel()
{
}
@Override
protected void onEvtDead()
{
}
private class onEventAttackedDoorTask implements Runnable
{
private final L2DoorInstance _door;
private final L2Character _attacker;
public onEventAttackedDoorTask(L2DoorInstance door, L2Character attacker)
{
_door = door;
_attacker = attacker;
}
@Override
public void run()
{
for (final L2SiegeGuardInstance guard : _door.getKnownSiegeGuards())
{
if (_actor.isInsideRadius(guard, guard.getFactionRange(), false, true) && (Math.abs(_attacker.getZ() - guard.getZ()) < 200))
{
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
}
}
}
}
}

View File

@@ -0,0 +1,240 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import java.util.logging.Logger;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.datatables.NpcWalkerRoutesTable;
import com.l2jmobius.gameserver.model.L2CharPosition;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2NpcWalkerNode;
import com.l2jmobius.gameserver.model.actor.instance.L2NpcWalkerInstance;
import javolution.util.FastList;
public class L2NpcWalkerAI extends L2CharacterAI implements Runnable
{
protected static final Logger _log = Logger.getLogger(L2NpcWalkerAI.class.getName());
private static final int DEFAULT_MOVE_DELAY = 0;
private long _nextMoveTime;
private boolean _walkingToNextPoint = false;
/**
* home points for xyz
*/
int _homeX, _homeY, _homeZ;
/**
* route of the current npc
*/
private FastList<L2NpcWalkerNode> _route;
/**
* current node
*/
private int _currentPos;
/**
* Constructor of L2CharacterAI.<BR>
* <BR>
* @param accessor The AI accessor of the L2Character
*/
public L2NpcWalkerAI(L2Character.AIAccessor accessor)
{
super(accessor);
if (!Config.ALLOW_NPC_WALKERS)
{
return;
}
_route = NpcWalkerRoutesTable.getInstance().getRouteForNpc(getActor().getNpcId());
// Here we need 1 second initial delay cause getActor().hasAI() will return null...
// Constructor of L2NpcWalkerAI is called faster then ai object is attached in L2NpcWalkerInstance
ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
}
@Override
public void run()
{
onEvtThink();
}
@Override
protected void onEvtThink()
{
if (!Config.ALLOW_NPC_WALKERS)
{
return;
}
if (isWalkingToNextPoint())
{
checkArrived();
return;
}
if (_nextMoveTime < System.currentTimeMillis())
{
walkToLocation();
}
}
/**
* If npc can't walk to it's target then just teleport to next point
* @param blocked_at_pos ignoring it
*/
@Override
protected void onEvtArrivedBlocked(L2CharPosition blocked_at_pos)
{
_log.warning("NpcWalker ID: " + getActor().getNpcId() + ": Blocked at rote position [" + _currentPos + "], coords: " + blocked_at_pos.x + ", " + blocked_at_pos.y + ", " + blocked_at_pos.z + ". Teleporting to next point");
final int destinationX = _route.get(_currentPos).getMoveX();
final int destinationY = _route.get(_currentPos).getMoveY();
final int destinationZ = _route.get(_currentPos).getMoveZ();
getActor().teleToLocation(destinationX, destinationY, destinationZ, false);
super.onEvtArrivedBlocked(blocked_at_pos);
}
private void checkArrived()
{
final int destinationX = _route.get(_currentPos).getMoveX();
final int destinationY = _route.get(_currentPos).getMoveY();
final int destinationZ = _route.get(_currentPos).getMoveZ();
if ((getActor().getX() == destinationX) && (getActor().getY() == destinationY) && (getActor().getZ() == destinationZ))
{
final String chat = _route.get(_currentPos).getChatText();
if ((chat != null) && !chat.isEmpty())
{
try
{
getActor().broadcastChat(chat);
}
catch (final ArrayIndexOutOfBoundsException e)
{
_log.info("L2NpcWalkerInstance: Error, " + e);
}
}
// time in millis
long delay = _route.get(_currentPos).getDelay() * 1000;
// sleeps between each move
if ((delay <= 0) && (delay != DEFAULT_MOVE_DELAY))
{
delay = DEFAULT_MOVE_DELAY;
if (Config.DEVELOPER)
{
_log.warning("Wrong Delay Set in Npc Walker Functions = " + delay + " secs, using default delay: " + DEFAULT_MOVE_DELAY + " secs instead.");
}
}
_nextMoveTime = System.currentTimeMillis() + delay;
setWalkingToNextPoint(false);
}
}
private void walkToLocation()
{
if (_currentPos < (_route.size() - 1))
{
_currentPos++;
}
else
{
_currentPos = 0;
}
final boolean moveType = _route.get(_currentPos).getRunning();
/**
* false - walking true - Running
*/
if (moveType)
{
getActor().setRunning();
}
else
{
getActor().setWalking();
}
// now we define destination
final int destinationX = _route.get(_currentPos).getMoveX();
final int destinationY = _route.get(_currentPos).getMoveY();
final int destinationZ = _route.get(_currentPos).getMoveZ();
// notify AI of MOVE_TO
setWalkingToNextPoint(true);
setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(destinationX, destinationY, destinationZ, 0));
}
@Override
public L2NpcWalkerInstance getActor()
{
return (L2NpcWalkerInstance) super.getActor();
}
public int getHomeX()
{
return _homeX;
}
public int getHomeY()
{
return _homeY;
}
public int getHomeZ()
{
return _homeZ;
}
public void setHomeX(int homeX)
{
_homeX = homeX;
}
public void setHomeY(int homeY)
{
_homeY = homeY;
}
public void setHomeZ(int homeZ)
{
_homeZ = homeZ;
}
public boolean isWalkingToNextPoint()
{
return _walkingToNextPoint;
}
public void setWalkingToNextPoint(boolean value)
{
_walkingToNextPoint = value;
}
}

View File

@@ -0,0 +1,392 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_CAST;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_MOVE_TO;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_REST;
import com.l2jmobius.gameserver.model.L2CharPosition;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2Character.AIAccessor;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2Skill;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
public class L2PlayerAI extends L2CharacterAI
{
private boolean thinking; // to prevent recursive thinking
public L2PlayerAI(AIAccessor accessor)
{
super(accessor);
}
/**
* Saves the current Intention for this L2PlayerAI if necessary and calls changeIntention in AbstractAI.<BR>
* <BR>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
public synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
// do nothing unless CAST intention
// however, forget interrupted actions when starting to use an offensive skill
if ((intention != AI_INTENTION_CAST) || ((arg0 != null) && ((L2Skill) arg0).isOffensive()))
{
boolean hasStandIntention = false;
if ((_actor instanceof L2PcInstance) && ((L2PcInstance) _actor).isSitting())
{
hasStandIntention = true;
}
if (!hasStandIntention)
{
_nextIntention = null;
}
super.changeIntention(intention, arg0, arg1);
return;
}
// do nothing if next intention is same as current one.
if ((intention == _intention) && (arg0 == _intention_arg0) && (arg1 == _intention_arg1))
{
super.changeIntention(intention, arg0, arg1);
return;
}
// save current intention so it can be used after cast
saveNextIntention(_intention, _intention_arg0, _intention_arg1);
super.changeIntention(intention, arg0, arg1);
}
/**
* Launch actions corresponding to the Event ReadyToAct.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Launch actions corresponding to the Event Think</li><BR>
* <BR>
*/
@Override
protected void onEvtReadyToAct()
{
// Launch actions corresponding to the Event Think
if (_nextIntention != null)
{
setIntention(_nextIntention._crtlIntention, _nextIntention._arg0, _nextIntention._arg1);
_nextIntention = null;
}
super.onEvtReadyToAct();
}
/**
* Launch actions corresponding to the Event Cancel.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Stop an AI Follow Task</li>
* <li>Launch actions corresponding to the Event Think</li><BR>
* <BR>
*/
@Override
protected void onEvtCancel()
{
_nextIntention = null;
super.onEvtCancel();
}
/**
* Finalize the casting of a skill. This method overrides L2CharacterAI method.<BR>
* <BR>
* <B>What it does:</B> Check if actual intention is set to CAST and, if so, retrieves latest intention before the actual CAST and set it as the current intention for the player
*/
@Override
protected void onEvtFinishCasting()
{
if (getIntention() == AI_INTENTION_CAST)
{
// run interrupted or next intention
final IntentionCommand nextIntention = _nextIntention;
if (nextIntention != null)
{
if (nextIntention._crtlIntention != AI_INTENTION_CAST)
{
setIntention(nextIntention._crtlIntention, nextIntention._arg0, nextIntention._arg1);
}
else
{
setIntention(AI_INTENTION_IDLE);
}
}
else
{
// set intention to idle if skill doesn't change intention.
setIntention(AI_INTENTION_IDLE);
}
}
}
@Override
protected void onIntentionRest()
{
if (getIntention() != AI_INTENTION_REST)
{
changeIntention(AI_INTENTION_REST, null, null);
setTarget(null);
if (getAttackTarget() != null)
{
setAttackTarget(null);
}
clientStopMoving(null);
}
}
@Override
protected void onIntentionActive()
{
setIntention(AI_INTENTION_IDLE);
}
/**
* Manage the Move To Intention : Stop current Attack and Launch a Move to Location Task.<BR>
* <BR>
* <B><U> Actions</U> : </B><BR>
* <BR>
* <li>Stop the actor auto-attack server side AND client side by sending Server->Client packet AutoAttackStop (broadcast)</li>
* <li>Set the Intention of this AI to AI_INTENTION_MOVE_TO</li>
* <li>Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)</li><BR>
* <BR>
*/
@Override
protected void onIntentionMoveTo(L2CharPosition pos)
{
if (getIntention() == AI_INTENTION_REST)
{
// Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor
clientActionFailed();
saveNextIntention(AI_INTENTION_MOVE_TO, pos, null);
return;
}
if (_actor.isAllSkillsDisabled() || (_actor.isAttackingNow() && !_actor.isAfraid()))
{
clientActionFailed();
saveNextIntention(AI_INTENTION_MOVE_TO, pos, null);
return;
}
// Set the Intention of this AbstractAI to AI_INTENTION_MOVE_TO
changeIntention(AI_INTENTION_MOVE_TO, pos, null);
// Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
clientStopAutoAttack();
// Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
moveTo(pos.x, pos.y, pos.z);
}
@Override
protected void clientNotifyDead()
{
_client_moving_to_pawn_offset = 0;
_client_moving = false;
super.clientNotifyDead();
}
private void thinkAttack()
{
final L2Character target = getAttackTarget();
if (target == null)
{
return;
}
if (checkTargetLostOrDead(target))
{
// if (target != null)
// {
// Notify the target
setAttackTarget(null);
// }
return;
}
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
{
return;
}
_accessor.doAttack(target);
}
private void thinkCast()
{
final L2Character target = getCastTarget();
if (checkTargetLost(target))
{
if (_skill.isOffensive() && (getAttackTarget() != null))
{
// Notify the target
setCastTarget(null);
}
return;
}
if (target != null)
{
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
{
return;
}
}
if (_skill.getHitTime() > 50)
{
clientStopMoving(null);
}
final L2Object oldTarget = _actor.getTarget();
if (oldTarget != null)
{
// Replace the current target by the cast target
if ((target != null) && (oldTarget != target))
{
_actor.setTarget(getCastTarget());
}
// Launch the Cast of the skill
_accessor.doCast(_skill);
// Restore the initial target
if ((target != null) && (oldTarget != target))
{
_actor.setTarget(oldTarget);
}
}
else
{
_accessor.doCast(_skill);
}
}
private void thinkPickUp()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
final L2Object target = getTarget();
if (target == null)
{
return;
}
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
((L2PcInstance.AIAccessor) _accessor).doPickupItem(target);
}
private void thinkInteract()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
final L2Object target = getTarget();
if (target == null)
{
return;
}
if (checkTargetLost(target))
{
return;
}
if (maybeMoveToPawn(target, 36))
{
return;
}
if (!(target instanceof L2StaticObjectInstance))
{
((L2PcInstance.AIAccessor) _accessor).doInteract((L2Character) target);
}
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void onEvtThink()
{
if (thinking || _actor.isAllSkillsDisabled())
{
return;
}
thinking = true;
try
{
if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
else if (getIntention() == AI_INTENTION_CAST)
{
thinkCast();
}
else if (getIntention() == AI_INTENTION_PICK_UP)
{
thinkPickUp();
}
else if (getIntention() == AI_INTENTION_INTERACT)
{
thinkInteract();
}
}
finally
{
thinking = false;
}
}
@Override
protected void onEvtArrivedRevalidate()
{
super.onEvtArrivedRevalidate();
}
}

View File

@@ -0,0 +1,957 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import java.util.concurrent.Future;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.GameTimeController;
import com.l2jmobius.gameserver.GeoData;
import com.l2jmobius.gameserver.ThreadPoolManager;
import com.l2jmobius.gameserver.model.L2Attackable;
import com.l2jmobius.gameserver.model.L2Character;
import com.l2jmobius.gameserver.model.L2Effect;
import com.l2jmobius.gameserver.model.L2Object;
import com.l2jmobius.gameserver.model.L2Skill;
import com.l2jmobius.gameserver.model.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2FolkInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2NpcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2PlayableInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2SiegeGuardInstance;
import com.l2jmobius.gameserver.util.Util;
import com.l2jmobius.util.Rnd;
/**
* This class manages AI of L2Attackable.<BR>
* <BR>
*/
public class L2SiegeGuardAI extends L2CharacterAI implements Runnable
{
// protected static final Logger _log = Logger.getLogger(L2SiegeGuardAI.class.getName());
private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
/** The L2Attackable AI task executed every 1s (call onEvtThink method) */
private Future<?> aiTask;
/** For attack AI, analysis of mob and its targets */
private final SelfAnalysis _selfAnalysis = new SelfAnalysis();
/** The delay after which the attack is stopped */
private int _attackTimeout;
/** The L2Attackable aggro counter */
private int _globalAggro;
/** The flag used to indicate that a thinking action is in progress */
private boolean thinking; // to prevent recursive thinking
private final int attackRange;
/**
* Constructor of L2AttackableAI.<BR>
* <BR>
* @param accessor The AI accessor of the L2Character
*/
public L2SiegeGuardAI(L2Character.AIAccessor accessor)
{
super(accessor);
_selfAnalysis.Init();
_attackTimeout = Integer.MAX_VALUE;
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
attackRange = ((L2Attackable) _actor).getPhysicalAttackRange();
}
@Override
public void run()
{
// Launch actions corresponding to the Event Think
onEvtThink();
}
/**
* Return True if the target is autoattackable (depends on the actor type).<BR>
* <BR>
* <B><U> Actor is a L2GuardInstance</U> :</B><BR>
* <BR>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li>
* <li>The L2MonsterInstance target is aggressive</li><BR>
* <BR>
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B><BR>
* <BR>
* <li>The target isn't a Folk or a Door</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>A siege is in progress</li>
* <li>The L2PcInstance target isn't a Defender</li><BR>
* <BR>
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B><BR>
* <BR>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The L2PcInstance target has karma (=PK)</li><BR>
* <BR>
* <B><U> Actor is a L2MonsterInstance</U> :</B><BR>
* <BR>
* <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
* <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
* <li>The target is in the actor Aggro range and is at the same height</li>
* <li>The actor is Aggressive</li><BR>
* <BR>
* @param target The targeted L2Object
* @return
*/
private boolean autoAttackCondition(L2Character target)
{
// Check if the target isn't another guard, folk or a door
if ((target == null) || (target instanceof L2SiegeGuardInstance) || (target instanceof L2FolkInstance) || (target instanceof L2DoorInstance))
{
return false;
}
// Check if the target isn't dead
if (target.isAlikeDead())
{
return false;
}
// Check if the target isn't invulnerable
if (target.isInvul())
{
if ((target.getActingPlayer() != null) && target.getActingPlayer().isGM())
{
return false;
}
}
// Get the owner if the target is a summon
if (target instanceof L2Summon)
{
final L2PcInstance owner = ((L2Summon) target).getOwner();
if (_actor.isInsideRadius(owner, 1000, true, false))
{
target = owner;
}
}
// Check if the target is a L2PcInstance
if (target instanceof L2PlayableInstance)
{
// Check if the target isn't in silent move mode AND too far (>250)
if (((L2PlayableInstance) target).isSilentMoving() && !_actor.isInsideRadius(target, 250, false, false))
{
return false;
}
}
// Los Check Here
return (_actor.isAutoAttackable(target) && GeoData.getInstance().canSeeTarget(_actor, target));
}
/**
* Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<BR>
* <BR>
* <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT><BR>
* <BR>
* @param intention The new Intention to set to the AI
* @param arg0 The first parameter of the Intention
* @param arg1 The second parameter of the Intention
*/
@Override
synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
{
if (Config.DEBUG)
{
_log.info("L2SiegeAI.changeIntention(" + intention + ", " + arg0 + ", " + arg1 + ")");
}
if (intention == AI_INTENTION_IDLE /* || intention == AI_INTENTION_ACTIVE */) // active becomes idle if only a summon is present
{
// Check if actor is not dead
if (!_actor.isAlikeDead())
{
final L2Attackable npc = (L2Attackable) _actor;
// If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
if (npc.getKnownList().getKnownPlayers().size() > 0)
{
intention = AI_INTENTION_ACTIVE;
}
else
{
intention = AI_INTENTION_IDLE;
}
}
if (intention == AI_INTENTION_IDLE)
{
// Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
super.changeIntention(AI_INTENTION_IDLE, null, null);
// Stop AI task and detach AI from NPC
if (aiTask != null)
{
aiTask.cancel(true);
aiTask = null;
}
// Cancel the AI
_accessor.detachAI();
return;
}
}
// Set the Intention of this L2AttackableAI to intention
super.changeIntention(intention, arg0, arg1);
// If not idle - create an AI task (schedule onEvtThink repeatedly)
if (aiTask == null)
{
aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
}
}
/**
* Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.<BR>
* <BR>
* @param target The L2Character to attack
*/
@Override
protected void onIntentionAttack(L2Character target)
{
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
// if (_actor.getTarget() != null)
super.onIntentionAttack(target);
}
/**
* Manage AI standard thinks of a L2Attackable (called by onEvtThink).<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Update every 1s the _globalAggro counter to come close to 0</li>
* <li>If the actor is Aggressive and can attack, add all autoAttackable L2Character in its Aggro Range to its _aggroList, chose a target and order to attack it</li>
* <li>If the actor can't attack, order to it to return to its home location</li>
*/
private void thinkActive()
{
final L2Attackable npc = (L2Attackable) _actor;
// Update every 1s the _globalAggro counter to come close to 0
if (_globalAggro != 0)
{
if (_globalAggro < 0)
{
_globalAggro++;
}
else
{
_globalAggro--;
}
}
// Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
// A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
if (_globalAggro >= 0)
{
for (final L2Character target : npc.getKnownList().getKnownCharactersInRadius(attackRange))
{
if (target == null)
{
continue;
}
if (autoAttackCondition(target)) // check aggression
{
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
final int hating = npc.getHating(target);
// Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
if (hating == 0)
{
npc.addDamageHate(target, 0, 1);
}
}
}
// Chose a target from its aggroList
L2Character hated;
if (_actor.isConfused())
{
hated = _attack_target; // Force mobs to attack anybody if confused
}
else
{
hated = npc.getMostHated();
}
// Order to the L2Attackable to attack the target
if (hated != null)
{
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
final int aggro = npc.getHating(hated);
if ((aggro + _globalAggro) > 0)
{
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the AI Intention to AI_INTENTION_ATTACK
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated, null);
}
return;
}
}
// Order to the L2SiegeGuardInstance to return to its home location because there's no target to attack
((L2SiegeGuardInstance) _actor).returnHome();
}
/**
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Update the attack timeout if actor is running</li>
* <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
* <li>Call all L2Object of its Faction inside the Faction Range</li>
* <li>Choose a target and order to attack it with magic skill or physical attack</li><BR>
* <BR>
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
*/
private void thinkAttack()
{
if (Config.DEBUG)
{
_log.info("L2SiegeGuardAI.thinkAttack(); timeout=" + (_attackTimeout - GameTimeController.getGameTicks()));
}
if (_attackTimeout < GameTimeController.getGameTicks())
{
// Check if the actor is running
if (_actor.isRunning())
{
// Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance
_actor.setWalking();
// Calculate a new attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
}
}
// Check if target is dead or if timeout is expired to stop this attack
if ((_attack_target == null) || _attack_target.isAlikeDead() || (_attackTimeout < GameTimeController.getGameTicks()))
{
// Stop hating this target after the attack timeout or if target is dead
if (_attack_target != null)
{
final L2Attackable npc = (L2Attackable) _actor;
npc.stopHating(_attack_target);
}
// Cancel target and timeout
_attackTimeout = Integer.MAX_VALUE;
_attack_target = null;
// Set the AI Intention to AI_INTENTION_ACTIVE
setIntention(AI_INTENTION_ACTIVE, null, null);
_actor.setWalking();
return;
}
factionNotifyAndSupport();
attackPrepare();
}
private final void factionNotifyAndSupport()
{
final L2Character target = getAttackTarget();
// Call all L2Object of its Faction inside the Faction Range
if ((((L2NpcInstance) _actor).getFactionId() == null) || (target == null) || (_actor == null))
{
return;
}
if (target.isInvul())
{
return; // speeding it up for siege guards
}
final String faction_id = ((L2NpcInstance) _actor).getFactionId();
// Go through all L2Character that belong to its faction
for (final L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(1000))
{
if (cha == null)
{
continue;
}
if (!(cha instanceof L2NpcInstance))
{
if (_selfAnalysis.hasHealOrResurrect && (cha instanceof L2PcInstance) && ((L2NpcInstance) _actor).getCastle().getSiege().checkIsDefender(((L2PcInstance) cha).getClan()))
{
// heal friends
if (!_actor.isAttackingDisabled() && (cha.getCurrentHp() < (cha.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && cha.isInCombat())
{
for (final L2Skill sk : _selfAnalysis.healSkills)
{
if (_actor.getCurrentMp() < sk.getMpConsume())
{
continue;
}
if (_actor.isSkillDisabled(sk.getId()))
{
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, cha, true))
{
continue;
}
final int chance = 5;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, cha))
{
break;
}
final L2Object OldTarget = _actor.getTarget();
_actor.setTarget(cha);
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
continue;
}
final L2NpcInstance npc = (L2NpcInstance) cha;
if (faction_id != npc.getFactionId())
{
continue;
}
if (npc.getAI() != null) // TODO: possibly check not needed
{
if ((Math.abs(target.getZ() - npc.getZ()) < 600) && ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE))
// limiting aggro for siege guards
&& target.isInsideRadius(npc, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target))
{
// Notify the L2Object AI with EVT_AGGRESSION
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
}
// heal friends
if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackingDisabled() && (npc.getCurrentHp() < (npc.getMaxHp() * 0.6)) && (_actor.getCurrentHp() > (_actor.getMaxHp() / 2)) && (_actor.getCurrentMp() > (_actor.getMaxMp() / 2)) && npc.isInCombat())
{
for (final L2Skill sk : _selfAnalysis.healSkills)
{
if (_actor.getCurrentMp() < sk.getMpConsume())
{
continue;
}
if (_actor.isSkillDisabled(sk.getId()))
{
continue;
}
if (!Util.checkIfInRange(sk.getCastRange(), _actor, npc, true))
{
continue;
}
final int chance = 4;
if (chance >= Rnd.get(100))
{
continue;
}
if (!GeoData.getInstance().canSeeTarget(_actor, npc))
{
break;
}
final L2Object OldTarget = _actor.getTarget();
_actor.setTarget(npc);
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
}
}
private void attackPrepare()
{
// Get all information needed to choose between physical or magical attack
L2Skill[] skills = null;
double dist_2 = 0;
int range = 0;
final L2SiegeGuardInstance sGuard = (L2SiegeGuardInstance) _actor;
try
{
_actor.setTarget(_attack_target);
skills = _actor.getAllSkills();
dist_2 = _actor.getPlanDistanceSq(_attack_target.getX(), _attack_target.getY());
range = _actor.getPhysicalAttackRange() + (int) (_actor.getTemplate().collisionRadius + _attack_target.getTemplate().collisionRadius);
if (_attack_target.isMoving())
{
range += 50;
}
}
catch (final NullPointerException e)
{
// _log.warning("AttackableAI: Attack target is NULL.");
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// never attack defenders
if ((_attack_target instanceof L2PcInstance) && sGuard.getCastle().getSiege().checkIsDefender(((L2PcInstance) _attack_target).getClan()))
{
// Cancel the target
sGuard.stopHating(_attack_target);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
if (!GeoData.getInstance().canSeeTarget(_actor, _attack_target))
{
// Siege guards differ from normal mobs currently:
// If target cannot be seen, don't attack any more
sGuard.stopHating(_attack_target);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
return;
}
// Check if the actor isn't muted and if it is far from target
if (!_actor.isMuted() && (dist_2 > (range * range)))
{
// check for long ranged skills and heal/buff skills
for (final L2Skill sk : skills)
{
final int castRange = sk.getCastRange();
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk.getId()) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
{
final L2Object OldTarget = _actor.getTarget();
if ((sk.getSkillType() == L2Skill.SkillType.BUFF) || (sk.getSkillType() == L2Skill.SkillType.HEAL))
{
boolean useSkillSelf = true;
if ((sk.getSkillType() == L2Skill.SkillType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.getSkillType() == L2Skill.SkillType.BUFF)
{
final L2Effect[] effects = _actor.getAllEffects();
for (int i = 0; (effects != null) && (i < effects.length); i++)
{
final L2Effect effect = effects[i];
if (effect.getSkill() == sk)
{
useSkillSelf = false;
break;
}
}
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
if (!(_actor.isAttackingNow()) && (_actor.getRunSpeed() == 0) && (_actor.getKnownList().knowsObject(_attack_target)))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(_attack_target);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else
{
final double dx = _actor.getX() - _attack_target.getX();
final double dy = _actor.getY() - _attack_target.getY();
final double dz = _actor.getZ() - _attack_target.getZ();
final double homeX = _attack_target.getX() - sGuard.getSpawn().getLocx();
final double homeY = _attack_target.getY() - sGuard.getSpawn().getLocy();
// Check if the L2SiegeGuardInstance isn't too far from it's home location
if ((((dx * dx) + (dy * dy)) > 10000) && (((homeX * homeX) + (homeY * homeY)) > 3240000) // 1800 * 1800
&& (_actor.getKnownList().knowsObject(_attack_target)))
{
// Cancel the target
_actor.getKnownList().removeKnownObject(_attack_target);
_actor.setTarget(null);
setIntention(AI_INTENTION_IDLE, null, null);
}
else // Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage)
{
range = _selfAnalysis.maxCastRange - 50;
}
if (_attack_target.isMoving())
{
moveToPawn(_attack_target, range - 70);
}
else
{
moveToPawn(_attack_target, range);
}
}
}
}
return;
}
// Else, if the actor is muted and far from target, just "move to pawn"
else if (_actor.isMuted() && (dist_2 > (range * range)))
{
// Temporary hack for preventing guards jumping off towers,
// before replacing this with effective geodata checks and AI modification
final double dz = _actor.getZ() - _attack_target.getZ();
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
{
if (_selfAnalysis.isMage)
{
range = _selfAnalysis.maxCastRange - 50;
}
if (_attack_target.isMoving())
{
moveToPawn(_attack_target, range - 70);
}
else
{
moveToPawn(_attack_target, range);
}
}
return;
}
// Else, if this is close enough to attack
else if (dist_2 <= (range * range))
{
// Force mobs to attack anybody if confused
L2Character hated = null;
if (_actor.isConfused())
{
hated = _attack_target;
}
else
{
hated = ((L2Attackable) _actor).getMostHated();
}
if (hated == null)
{
setIntention(AI_INTENTION_ACTIVE, null, null);
return;
}
if (hated != _attack_target)
{
_attack_target = hated;
}
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// check for close combat skills && heal/buff skills
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5))
{
for (final L2Skill sk : skills)
{
final int castRange = sk.getCastRange();
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk.getId()))
{
final L2Object OldTarget = _actor.getTarget();
if ((sk.getSkillType() == L2Skill.SkillType.BUFF) || (sk.getSkillType() == L2Skill.SkillType.HEAL))
{
boolean useSkillSelf = true;
if ((sk.getSkillType() == L2Skill.SkillType.HEAL) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
{
useSkillSelf = false;
break;
}
if (sk.getSkillType() == L2Skill.SkillType.BUFF)
{
final L2Effect[] effects = _actor.getAllEffects();
for (int i = 0; (effects != null) && (i < effects.length); i++)
{
final L2Effect effect = effects[i];
if (effect.getSkill() == sk)
{
useSkillSelf = false;
break;
}
}
}
if (useSkillSelf)
{
_actor.setTarget(_actor);
}
}
clientStopMoving(null);
_accessor.doCast(sk);
_actor.setTarget(OldTarget);
return;
}
}
}
// Finally, do the physical attack itself
_accessor.doAttack(_attack_target);
}
}
/**
* Manage AI thinking actions of a L2Attackable.<BR>
* <BR>
*/
@Override
protected void onEvtThink()
{
// Check if the actor can't use skills and if a thinking action isn't already in progress
if (thinking || _actor.isAllSkillsDisabled())
{
return;
}
// Start thinking action
thinking = true;
try
{
// Manage AI thinks of a L2Attackable
if (getIntention() == AI_INTENTION_ACTIVE)
{
thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
}
finally
{
// Stop thinking action
thinking = false;
}
}
/**
* Launch actions corresponding to the Event Attacked.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
* <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li>
* <li>Set the Intention to AI_INTENTION_ATTACK</li><BR>
* <BR>
* @param attacker The L2Character that attacks the actor
*/
@Override
protected void onEvtAttacked(L2Character attacker)
{
if (attacker == null)
{
return;
}
// Calculate the attack timeout
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
// Set the _globalAggro to 0 to permit attack even just after spawn
if (_globalAggro < 0)
{
_globalAggro = 0;
}
// Add the attacker to the _aggroList of the actor
((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
// Set the Intention to AI_INTENTION_ATTACK
if (getIntention() != AI_INTENTION_ATTACK)
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker, null);
}
super.onEvtAttacked(attacker);
}
/**
* Launch actions corresponding to the Event Aggression.<BR>
* <BR>
* <B><U> Actions</U> :</B><BR>
* <BR>
* <li>Add the target to the actor _aggroList or update hate if already present</li>
* <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li><BR>
* <BR>
* @param target The L2Character that attacks
* @param aggro The value of hate to add to the actor against the target
*/
@Override
protected void onEvtAggression(L2Character target, int aggro)
{
if (_actor == null)
{
return;
}
final L2Attackable me = (L2Attackable) _actor;
if (target != null)
{
// Add the target to the actor _aggroList or update hate if already present
me.addDamageHate(target, 0, aggro);
// Get the hate of the actor against the target
aggro = me.getHating(target);
if (aggro <= 0)
{
if (me.getMostHated() == null)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
return;
}
// Set the actor AI Intention to AI_INTENTION_ATTACK
if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
{
// Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
if (!_actor.isRunning())
{
_actor.setRunning();
}
final L2SiegeGuardInstance sGuard = (L2SiegeGuardInstance) _actor;
final double homeX = target.getX() - sGuard.getSpawn().getLocx();
final double homeY = target.getY() - sGuard.getSpawn().getLocy();
// Check if the L2SiegeGuardInstance is not too far from its home location
if (((homeX * homeX) + (homeY * homeY)) < 3240000)
{
setIntention(CtrlIntention.AI_INTENTION_ATTACK, target, null);
}
}
}
else
{
// currently only for setting lower general aggro
if (aggro >= 0)
{
return;
}
final L2Character mostHated = me.getMostHated();
if (mostHated == null)
{
_globalAggro = -25;
return;
}
for (final L2Character aggroed : me.getAggroList().keySet())
{
me.addDamageHate(aggroed, 0, aggro);
}
aggro = me.getHating(mostHated);
if (aggro <= 0)
{
_globalAggro = -25;
me.clearAggroList();
setIntention(AI_INTENTION_IDLE, null, null);
}
}
}
@Override
protected void onEvtDead()
{
stopAITask();
super.onEvtDead();
}
public void stopAITask()
{
if (aiTask != null)
{
aiTask.cancel(false);
aiTask = null;
}
_accessor.detachAI();
}
}

View File

@@ -0,0 +1,195 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.ai;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_CAST;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_FOLLOW;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_INTERACT;
import static com.l2jmobius.gameserver.ai.CtrlIntention.AI_INTENTION_PICK_UP;
import com.l2jmobius.gameserver.model.L2Character.AIAccessor;
import com.l2jmobius.gameserver.model.L2Summon;
public class L2SummonAI extends L2CharacterAI
{
private boolean thinking; // to prevent recursive thinking
private boolean _previousFollowStatus = ((L2Summon) _actor).getFollowStatus();
public L2SummonAI(AIAccessor accessor)
{
super(accessor);
}
@Override
protected void onIntentionIdle()
{
stopFollow();
_previousFollowStatus = false;
onIntentionActive();
}
@Override
protected void onIntentionActive()
{
final L2Summon summon = (L2Summon) _actor;
if (_previousFollowStatus)
{
setIntention(AI_INTENTION_FOLLOW, summon.getOwner());
}
else
{
super.onIntentionActive();
}
}
private void thinkAttack()
{
if (checkTargetLostOrDead(getAttackTarget()))
{
setAttackTarget(null);
return;
}
if (maybeMoveToPawn(getAttackTarget(), _actor.getPhysicalAttackRange()))
{
return;
}
clientStopMoving(null);
_accessor.doAttack(getAttackTarget());
}
private void thinkCast()
{
final L2Summon summon = (L2Summon) _actor;
if (checkTargetLost(getCastTarget()))
{
setCastTarget(null);
return;
}
final boolean val = _previousFollowStatus;
if (maybeMoveToPawn(getCastTarget(), _actor.getMagicalAttackRange(_skill)))
{
return;
}
clientStopMoving(null);
summon.setFollowStatus(false);
setIntention(AI_INTENTION_IDLE);
_previousFollowStatus = val;
_accessor.doCast(_skill);
}
private void thinkPickUp()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
if (checkTargetLost(getTarget()))
{
return;
}
if (maybeMoveToPawn(getTarget(), 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
((L2Summon.AIAccessor) _accessor).doPickupItem(getTarget());
}
private void thinkInteract()
{
if (_actor.isAllSkillsDisabled())
{
return;
}
if (checkTargetLost(getTarget()))
{
return;
}
if (maybeMoveToPawn(getTarget(), 36))
{
return;
}
setIntention(AI_INTENTION_IDLE);
}
@Override
protected void onEvtThink()
{
if (thinking || _actor.isAllSkillsDisabled())
{
return;
}
thinking = true;
try
{
if (getIntention() == AI_INTENTION_ATTACK)
{
thinkAttack();
}
else if (getIntention() == AI_INTENTION_CAST)
{
thinkCast();
}
else if (getIntention() == AI_INTENTION_PICK_UP)
{
thinkPickUp();
}
else if (getIntention() == AI_INTENTION_INTERACT)
{
thinkInteract();
}
}
finally
{
thinking = false;
}
}
@Override
protected void onEvtFinishCasting()
{
if (_actor.getAI().getIntention() != AI_INTENTION_ATTACK)
{
((L2Summon) _actor).setFollowStatus(_previousFollowStatus);
}
}
public void notifyFollowStatusChange()
{
_previousFollowStatus = !_previousFollowStatus;
switch (getIntention())
{
case AI_INTENTION_ACTIVE:
case AI_INTENTION_FOLLOW:
case AI_INTENTION_IDLE:
((L2Summon) _actor).setFollowStatus(_previousFollowStatus);
break;
}
}
public void setStartFollowController(boolean val)
{
_previousFollowStatus = val;
}
}