Renamed trunk folder.
This commit is contained in:
@@ -0,0 +1,805 @@
|
||||
/*
|
||||
* 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.Logger;
|
||||
|
||||
import com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.GameTimeController;
|
||||
import com.l2jmobius.gameserver.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Summon;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
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.Die;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.MoveToLocation;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.MoveToPawn;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.StopMove;
|
||||
import com.l2jmobius.gameserver.taskmanager.AttackStanceTaskManager;
|
||||
|
||||
/**
|
||||
* Mother class of all objects AI in the world.<br>
|
||||
* AbastractAI :<br>
|
||||
* <li>L2CharacterAI</li>
|
||||
*/
|
||||
public abstract class AbstractAI implements Ctrl
|
||||
{
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractAI.class.getName());
|
||||
|
||||
private NextAction _nextAction;
|
||||
|
||||
/**
|
||||
* @return the _nextAction
|
||||
*/
|
||||
public NextAction getNextAction()
|
||||
{
|
||||
return _nextAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nextAction the next action to set.
|
||||
*/
|
||||
public void setNextAction(NextAction nextAction)
|
||||
{
|
||||
_nextAction = nextAction;
|
||||
}
|
||||
|
||||
/** The character that this AI manages */
|
||||
protected final L2Character _actor;
|
||||
|
||||
/** Current long-term intention */
|
||||
protected CtrlIntention _intention = AI_INTENTION_IDLE;
|
||||
/** Current long-term intention parameter */
|
||||
protected Object[] _intentionArgs = null;
|
||||
|
||||
/** Flags about client's state, in order to know which messages to send */
|
||||
protected volatile boolean _clientMoving;
|
||||
/** Flags about client's state, in order to know which messages to send */
|
||||
protected volatile boolean _clientAutoAttacking;
|
||||
/** Flags about client's state, in order to know which messages to send */
|
||||
protected int _clientMovingToPawnOffset;
|
||||
|
||||
/** Different targets this AI maintains */
|
||||
private L2Object _target;
|
||||
|
||||
/** The skill we are currently casting by INTENTION_CAST */
|
||||
Skill _skill;
|
||||
L2ItemInstance _item;
|
||||
boolean _forceUse;
|
||||
boolean _dontMove;
|
||||
|
||||
/** Different internal state flags */
|
||||
protected int _moveToPawnTimeout;
|
||||
|
||||
protected Future<?> _followTask = null;
|
||||
private static final int FOLLOW_INTERVAL = 1000;
|
||||
private static final int ATTACK_FOLLOW_INTERVAL = 500;
|
||||
|
||||
protected AbstractAI(L2Character creature)
|
||||
{
|
||||
_actor = creature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the L2Character managed by this Accessor AI.
|
||||
*/
|
||||
@Override
|
||||
public L2Character getActor()
|
||||
{
|
||||
return _actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current Intention.
|
||||
*/
|
||||
@Override
|
||||
public CtrlIntention getIntention()
|
||||
{
|
||||
return _intention;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Intention of this AbstractAI.<br>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : This method is USED by AI classes</B></FONT><B><U><br>
|
||||
* Overridden 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.
|
||||
* @param intention The new Intention to set to the AI
|
||||
* @param args The first parameter of the Intention
|
||||
*/
|
||||
synchronized void changeIntention(CtrlIntention intention, Object... args)
|
||||
{
|
||||
_intention = intention;
|
||||
_intentionArgs = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the L2CharacterAI onIntention method corresponding to the new Intention.<br>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT>
|
||||
* @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>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Stop the FOLLOW mode if necessary</B></FONT>
|
||||
* @param intention The new Intention to set to the AI
|
||||
* @param args The first parameters of the Intention (optional target)
|
||||
*/
|
||||
@Override
|
||||
@SafeVarargs
|
||||
public final void setIntention(CtrlIntention intention, Object... args)
|
||||
{
|
||||
// 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) args[0]);
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_CAST:
|
||||
{
|
||||
onIntentionCast((Skill) args[0], (L2Object) args[1], args.length > 2 ? (L2ItemInstance) args[2] : null, args.length > 3 ? (boolean) args[3] : false, args.length > 4 ? (boolean) args[4] : false);
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_MOVE_TO:
|
||||
{
|
||||
onIntentionMoveTo((Location) args[0]);
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_FOLLOW:
|
||||
{
|
||||
onIntentionFollow((L2Character) args[0]);
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_PICK_UP:
|
||||
{
|
||||
onIntentionPickUp((L2Object) args[0]);
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_INTERACT:
|
||||
{
|
||||
onIntentionInteract((L2Object) args[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If do move or follow intention drop next action.
|
||||
if ((_nextAction != null) && _nextAction.getIntentions().contains(intention))
|
||||
{
|
||||
_nextAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the L2CharacterAI onEvt method corresponding to the Event.<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 period)</B></FONT>
|
||||
* @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. <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 period)</B></FONT>
|
||||
* @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. <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 period)</B></FONT>
|
||||
* @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.isSpawned() && !_actor.isTeleporting()) || !_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_ACTION_BLOCKED:
|
||||
{
|
||||
onEvtActionBlocked((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_EVADED:
|
||||
{
|
||||
onEvtEvaded((L2Character) arg0);
|
||||
break;
|
||||
}
|
||||
case EVT_READY_TO_ACT:
|
||||
{
|
||||
if (!_actor.isCastingNow())
|
||||
{
|
||||
onEvtReadyToAct();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVT_ARRIVED:
|
||||
{
|
||||
// happens e.g. from stopmove but we don't process it if we're casting
|
||||
if (!_actor.isCastingNow())
|
||||
{
|
||||
onEvtArrived();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVT_ARRIVED_REVALIDATE:
|
||||
{
|
||||
// this is disregarded if the char is not moving any more
|
||||
if (_actor.isMoving())
|
||||
{
|
||||
onEvtArrivedRevalidate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EVT_ARRIVED_BLOCKED:
|
||||
{
|
||||
onEvtArrivedBlocked((Location) 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Do next action.
|
||||
if ((_nextAction != null) && _nextAction.getEvents().contains(evt))
|
||||
{
|
||||
_nextAction.doAction();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onIntentionIdle();
|
||||
|
||||
protected abstract void onIntentionActive();
|
||||
|
||||
protected abstract void onIntentionRest();
|
||||
|
||||
protected abstract void onIntentionAttack(L2Character target);
|
||||
|
||||
protected abstract void onIntentionCast(Skill skill, L2Object target, L2ItemInstance item, boolean forceUse, boolean dontMove);
|
||||
|
||||
protected abstract void onIntentionMoveTo(Location 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 onEvtActionBlocked(L2Character attacker);
|
||||
|
||||
protected abstract void onEvtRooted(L2Character attacker);
|
||||
|
||||
protected abstract void onEvtConfused(L2Character attacker);
|
||||
|
||||
protected abstract void onEvtMuted(L2Character attacker);
|
||||
|
||||
protected abstract void onEvtEvaded(L2Character attacker);
|
||||
|
||||
protected abstract void onEvtReadyToAct();
|
||||
|
||||
protected abstract void onEvtArrived();
|
||||
|
||||
protected abstract void onEvtArrivedRevalidate();
|
||||
|
||||
protected abstract void onEvtArrivedBlocked(Location 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. <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
*/
|
||||
protected void clientActionFailed()
|
||||
{
|
||||
if (_actor instanceof L2PcInstance)
|
||||
{
|
||||
_actor.sendPacket(ActionFailed.STATIC_PACKET);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn <I>(broadcast)</I>.<br>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
* @param pawn
|
||||
* @param offset
|
||||
*/
|
||||
protected void moveToPawn(L2Object pawn, int offset)
|
||||
{
|
||||
// Check 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
|
||||
if (_clientMoving && (_target == pawn))
|
||||
{
|
||||
if (_clientMovingToPawnOffset == offset)
|
||||
{
|
||||
if (GameTimeController.getInstance().getGameTicks() < _moveToPawnTimeout)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (_actor.isOnGeodataPath())
|
||||
{
|
||||
// minimum time to calculate new route is 2 seconds
|
||||
if (GameTimeController.getInstance().getGameTicks() < (_moveToPawnTimeout + 10))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set AI movement data
|
||||
_clientMoving = true;
|
||||
_clientMovingToPawnOffset = offset;
|
||||
_target = pawn;
|
||||
_moveToPawnTimeout = GameTimeController.getInstance().getGameTicks();
|
||||
_moveToPawnTimeout += 1000 / GameTimeController.MILLIS_IN_TICK;
|
||||
|
||||
if (pawn == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
|
||||
_actor.moveToLocation(pawn.getX(), pawn.getY(), pawn.getZ(), offset);
|
||||
|
||||
if (!_actor.isMoving())
|
||||
{
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a Server->Client packet MoveToPawn/CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
|
||||
if (pawn.isCharacter())
|
||||
{
|
||||
if (_actor.isOnGeodataPath())
|
||||
{
|
||||
_actor.broadcastPacket(new MoveToLocation(_actor));
|
||||
_clientMovingToPawnOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_actor.broadcastPacket(new MoveToPawn(_actor, pawn, offset));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_actor.broadcastPacket(new MoveToLocation(_actor));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientActionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation <I>(broadcast)</I>.<br>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
* @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
|
||||
_clientMoving = true;
|
||||
_clientMovingToPawnOffset = 0;
|
||||
|
||||
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
|
||||
_actor.moveToLocation(x, y, z, 0);
|
||||
|
||||
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
|
||||
_actor.broadcastPacket(new MoveToLocation(_actor));
|
||||
}
|
||||
else
|
||||
{
|
||||
clientActionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the actor movement server side AND client side by sending Server->Client packet StopMove/StopRotation <I>(broadcast)</I>.<br>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
* @param loc
|
||||
*/
|
||||
public void clientStopMoving(Location loc)
|
||||
{
|
||||
// Stop movement of the L2Character
|
||||
if (_actor.isMoving())
|
||||
{
|
||||
_actor.stopMove(loc);
|
||||
}
|
||||
|
||||
_clientMovingToPawnOffset = 0;
|
||||
_clientMoving = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Client has already arrived to target, no need to force StopMove packet.
|
||||
*/
|
||||
protected void clientStoppedMoving()
|
||||
{
|
||||
if (_clientMovingToPawnOffset > 0) // movetoPawn needs to be stopped
|
||||
{
|
||||
_clientMovingToPawnOffset = 0;
|
||||
_actor.broadcastPacket(new StopMove(_actor));
|
||||
}
|
||||
_clientMoving = false;
|
||||
}
|
||||
|
||||
public boolean isAutoAttacking()
|
||||
{
|
||||
return _clientAutoAttacking;
|
||||
}
|
||||
|
||||
public void setAutoAttacking(boolean isAutoAttacking)
|
||||
{
|
||||
if (_actor.isSummon())
|
||||
{
|
||||
final L2Summon summon = (L2Summon) _actor;
|
||||
if (summon.getOwner() != null)
|
||||
{
|
||||
summon.getOwner().getAI().setAutoAttacking(isAutoAttacking);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_clientAutoAttacking = isAutoAttacking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the actor Auto Attack client side by sending Server->Client packet AutoAttackStart <I>(broadcast)</I>.<br>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
*/
|
||||
public void clientStartAutoAttack()
|
||||
{
|
||||
if (_actor.isSummon())
|
||||
{
|
||||
final L2Summon summon = (L2Summon) _actor;
|
||||
if (summon.getOwner() != null)
|
||||
{
|
||||
summon.getOwner().getAI().clientStartAutoAttack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isAutoAttacking())
|
||||
{
|
||||
if (_actor.isPlayer() && _actor.hasSummon())
|
||||
{
|
||||
final L2Summon pet = _actor.getPet();
|
||||
if (pet != null)
|
||||
{
|
||||
pet.broadcastPacket(new AutoAttackStart(pet.getObjectId()));
|
||||
}
|
||||
_actor.getServitors().values().forEach(s -> s.broadcastPacket(new AutoAttackStart(s.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>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
*/
|
||||
public void clientStopAutoAttack()
|
||||
{
|
||||
if (_actor.isSummon())
|
||||
{
|
||||
final L2Summon summon = (L2Summon) _actor;
|
||||
if (summon.getOwner() != null)
|
||||
{
|
||||
summon.getOwner().getAI().clientStopAutoAttack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_actor instanceof L2PcInstance)
|
||||
{
|
||||
if (!AttackStanceTaskManager.getInstance().hasAttackStanceTask(_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>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
*/
|
||||
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;
|
||||
|
||||
// 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>
|
||||
* <FONT COLOR=#FF0000><B> <U>Caution</U> : Low level function, used by AI subclasses</B></FONT>
|
||||
* @param player The L2PcIstance to notify with state of this L2Character
|
||||
*/
|
||||
public void describeStateToPlayer(L2PcInstance player)
|
||||
{
|
||||
if (getActor().isVisibleFor(player))
|
||||
{
|
||||
if (_clientMoving)
|
||||
{
|
||||
if ((_clientMovingToPawnOffset != 0) && isFollowing())
|
||||
{
|
||||
// Send a Server->Client packet MoveToPawn to the actor and all L2PcInstance in its _knownPlayers
|
||||
player.sendPacket(new MoveToPawn(_actor, _target, _clientMovingToPawnOffset));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a Server->Client packet CharMoveToLocation to the actor and all L2PcInstance in its _knownPlayers
|
||||
player.sendPacket(new MoveToLocation(_actor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFollowing()
|
||||
{
|
||||
return (getTarget() instanceof L2Character) && (getIntention() == CtrlIntention.AI_INTENTION_FOLLOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and Launch an AI Follow Task to execute every 1s.
|
||||
* @param target The L2Character to follow
|
||||
*/
|
||||
public synchronized void startFollow(L2Character target)
|
||||
{
|
||||
startFollow(target, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and Launch an AI Follow Task to execute every 0.5s, following at specified range.
|
||||
* @param target The L2Character to follow
|
||||
* @param range
|
||||
*/
|
||||
public synchronized void startFollow(L2Character target, int range)
|
||||
{
|
||||
if (_followTask != null)
|
||||
{
|
||||
_followTask.cancel(false);
|
||||
_followTask = null;
|
||||
}
|
||||
|
||||
setTarget(target);
|
||||
|
||||
final int followRange = range == -1 ? Rnd.get(50, 100) : range;
|
||||
_followTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_followTask == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final L2Object followTarget = getTarget(); // copy to prevent NPE
|
||||
if (followTarget == null)
|
||||
{
|
||||
if (_actor.isSummon())
|
||||
{
|
||||
((L2Summon) _actor).setFollowStatus(false);
|
||||
}
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_actor.isInsideRadius(followTarget, followRange, true, false))
|
||||
{
|
||||
if (!_actor.isInsideRadius(followTarget, 3000, true, false))
|
||||
{
|
||||
// if the target is too far (maybe also teleported)
|
||||
if (_actor.isSummon())
|
||||
{
|
||||
((L2Summon) _actor).setFollowStatus(false);
|
||||
}
|
||||
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
return;
|
||||
}
|
||||
|
||||
moveToPawn(followTarget, followRange);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.warning("Error: " + e.getMessage());
|
||||
}
|
||||
}, 5, range == -1 ? FOLLOW_INTERVAL : ATTACK_FOLLOW_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop an AI Follow Task.
|
||||
*/
|
||||
public synchronized void stopFollow()
|
||||
{
|
||||
if (_followTask != null)
|
||||
{
|
||||
// Stop the Follow Task
|
||||
_followTask.cancel(false);
|
||||
_followTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTarget(L2Object target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
public L2Object getTarget()
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all Ai tasks and futures.
|
||||
*/
|
||||
public void stopAITask()
|
||||
{
|
||||
stopFollow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Actor: " + _actor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.actor.L2Character;
|
||||
|
||||
/**
|
||||
* Interface of AI and client state.<br>
|
||||
* To correctly send messages to client we need it's state.<br>
|
||||
* For example, if we've sent 'StartAutoAttack' message, we need to send 'StopAutoAttack' message before any other action.<br>
|
||||
* 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).<br>
|
||||
* Thus, we need to know the state of client, i.e. which messages we've sent and how the client will show the scene.<br>
|
||||
* Close to this task is the task of AI.<br>
|
||||
* If a player's character is attacking a mob, his ATTACK may be interrupted by an event, that temporary disable attacking.<br>
|
||||
* But when the possibility to ATTACK will be enabled, the character must continue the ATTACK.<br>
|
||||
* 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.<br>
|
||||
* This interface is hiding complexity of server<->client interaction and multiple states of a character.<br>
|
||||
* It allows to set a desired, simple "wish" of a character, and the implementation of this interface will take care about the rest.<br>
|
||||
* The goal of a character may be like "ATTACK", "random walk" and so on.<br>
|
||||
* To reach the goal implementation will split it into several small actions, several steps (possibly repeatable).<br>
|
||||
* Like "run to target" then "hit it", then if target is not dead - repeat.<br>
|
||||
* This flow of simpler steps may be interrupted by incoming events.<br>
|
||||
* Like a character's movement was disabled (by Root spell, for instance).<br>
|
||||
* Depending on character's ability AI may choose to wait, or to use magic ATTACK and so on.<br>
|
||||
* Additionally incoming events are compared with client's state of the character,<br>
|
||||
* 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 behavior,<br>
|
||||
* and if client's state for the character is "moving" we send messages to clients to stop the avatar/mob.
|
||||
*/
|
||||
public interface Ctrl
|
||||
{
|
||||
/**
|
||||
* Gets the actor.
|
||||
* @return the actor
|
||||
*/
|
||||
L2Character getActor();
|
||||
|
||||
/**
|
||||
* Gets the intention.
|
||||
* @return the intention
|
||||
*/
|
||||
CtrlIntention getIntention();
|
||||
|
||||
/**
|
||||
* Set general state/intention for AI, with optional data.
|
||||
* @param intention the new intention
|
||||
*/
|
||||
void setIntention(CtrlIntention intention);
|
||||
|
||||
/**
|
||||
* Sets the intention.
|
||||
* @param intention the intention
|
||||
* @param args
|
||||
*/
|
||||
void setIntention(CtrlIntention intention, Object... args);
|
||||
|
||||
/**
|
||||
* Event, that notifies about previous step result, or user command, that does not change current general intention.
|
||||
* @param evt the event
|
||||
*/
|
||||
void notifyEvent(CtrlEvent evt);
|
||||
|
||||
/**
|
||||
* Notify an event.
|
||||
* @param evt the event
|
||||
* @param arg0 the arg0
|
||||
*/
|
||||
void notifyEvent(CtrlEvent evt, Object arg0);
|
||||
|
||||
/**
|
||||
* Notify an event.
|
||||
* @param evt the event
|
||||
* @param arg0 the arg0
|
||||
* @param arg1 the arg1
|
||||
*/
|
||||
void notifyEvent(CtrlEvent evt, Object arg0, Object arg1);
|
||||
}
|
@@ -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 events 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<br>
|
||||
* attack was done on the actor. NPC may start attack in response, or ignore<br>
|
||||
* 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_ACTION_BLOCKED,
|
||||
/** Actor is in rooted state (cannot move) */
|
||||
EVT_ROOTED,
|
||||
/** Actor evaded hit **/
|
||||
EVT_EVADED,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* 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 intermediate point, and needs to 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.<br>
|
||||
* For example, the actor was put into a stun, so it's current attack<br>
|
||||
* or movement has to be canceled. But after the stun state expired,<br>
|
||||
* the actor may try to attack again. Another usage for CANCEL is a user's<br>
|
||||
* 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 random directions **/
|
||||
EVT_AFRAID,
|
||||
/** The character finish casting **/
|
||||
EVT_FINISH_CASTING,
|
||||
/** The character betrayed its master */
|
||||
EVT_BETRAYED
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Enumeration 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 peaceful 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;
|
||||
}
|
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 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 com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.GameTimeController;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.DoppelgangerInstance;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.model.skills.SkillCaster;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.MoveToLocation;
|
||||
|
||||
public class DoppelgangerAI extends L2CharacterAI
|
||||
{
|
||||
private volatile boolean _thinking; // to prevent recursive thinking
|
||||
private volatile boolean _startFollow;
|
||||
private L2Character _lastAttack = null;
|
||||
|
||||
public DoppelgangerAI(DoppelgangerInstance clone)
|
||||
{
|
||||
super(clone);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionIdle()
|
||||
{
|
||||
stopFollow();
|
||||
_startFollow = false;
|
||||
onIntentionActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionActive()
|
||||
{
|
||||
if (_startFollow)
|
||||
{
|
||||
setIntention(AI_INTENTION_FOLLOW, getActor().getSummoner());
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onIntentionActive();
|
||||
}
|
||||
}
|
||||
|
||||
private void thinkAttack()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
final L2Character attackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
|
||||
if (checkTargetLostOrDead(attackTarget))
|
||||
{
|
||||
setTarget(null);
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
clientStopMoving(null);
|
||||
_actor.doAttack(attackTarget);
|
||||
}
|
||||
|
||||
private void thinkCast()
|
||||
{
|
||||
if (_actor.isCastingNow(SkillCaster::isAnyNormalType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final L2Object target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
|
||||
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
setTarget(null);
|
||||
return;
|
||||
}
|
||||
final boolean val = _startFollow;
|
||||
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
getActor().followSummoner(false);
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
_startFollow = val;
|
||||
_actor.doCast(_skill, _item, _forceUse, _dontMove);
|
||||
}
|
||||
|
||||
private void thinkInteract()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, 36))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtThink()
|
||||
{
|
||||
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_thinking = true;
|
||||
try
|
||||
{
|
||||
switch (getIntention())
|
||||
{
|
||||
case AI_INTENTION_ATTACK:
|
||||
thinkAttack();
|
||||
break;
|
||||
case AI_INTENTION_CAST:
|
||||
thinkCast();
|
||||
break;
|
||||
case AI_INTENTION_INTERACT:
|
||||
thinkInteract();
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_thinking = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtFinishCasting()
|
||||
{
|
||||
if (_lastAttack == null)
|
||||
{
|
||||
getActor().followSummoner(_startFollow);
|
||||
}
|
||||
else
|
||||
{
|
||||
setIntention(CtrlIntention.AI_INTENTION_ATTACK, _lastAttack);
|
||||
_lastAttack = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyFollowStatusChange()
|
||||
{
|
||||
_startFollow = !_startFollow;
|
||||
switch (getIntention())
|
||||
{
|
||||
case AI_INTENTION_ACTIVE:
|
||||
case AI_INTENTION_FOLLOW:
|
||||
case AI_INTENTION_IDLE:
|
||||
case AI_INTENTION_MOVE_TO:
|
||||
case AI_INTENTION_PICK_UP:
|
||||
getActor().followSummoner(_startFollow);
|
||||
}
|
||||
}
|
||||
|
||||
public void setStartFollowController(boolean val)
|
||||
{
|
||||
_startFollow = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionCast(Skill skill, L2Object target, L2ItemInstance item, boolean forceUse, boolean dontMove)
|
||||
{
|
||||
if (getIntention() == AI_INTENTION_ATTACK)
|
||||
{
|
||||
_lastAttack = (getTarget() != null) && getTarget().isCharacter() ? (L2Character) getTarget() : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastAttack = null;
|
||||
}
|
||||
super.onIntentionCast(skill, target, item, forceUse, dontMove);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void moveToPawn(L2Object pawn, int offset)
|
||||
{
|
||||
// Check if actor can move
|
||||
if (!_actor.isMovementDisabled() && (_actor.getMoveSpeed() > 0))
|
||||
{
|
||||
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 (_clientMoving && (getTarget() == pawn))
|
||||
{
|
||||
if (_clientMovingToPawnOffset == offset)
|
||||
{
|
||||
if (GameTimeController.getInstance().getGameTicks() < _moveToPawnTimeout)
|
||||
{
|
||||
return;
|
||||
}
|
||||
sendPacket = false;
|
||||
}
|
||||
else if (_actor.isOnGeodataPath())
|
||||
{
|
||||
// minimum time to calculate new route is 2 seconds
|
||||
if (GameTimeController.getInstance().getGameTicks() < (_moveToPawnTimeout + 10))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set AI movement data
|
||||
_clientMoving = true;
|
||||
_clientMovingToPawnOffset = offset;
|
||||
setTarget(pawn);
|
||||
_moveToPawnTimeout = GameTimeController.getInstance().getGameTicks();
|
||||
_moveToPawnTimeout += 1000 / GameTimeController.MILLIS_IN_TICK;
|
||||
|
||||
if (pawn == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate movement data for a move to location action and add the actor to movingObjects of GameTimeController
|
||||
// _actor.moveToLocation(pawn.getX(), pawn.getY(), pawn.getZ(), offset);
|
||||
final Location loc = new Location(pawn.getX() + Rnd.get(-offset, offset), pawn.getY() + Rnd.get(-offset, offset), pawn.getZ());
|
||||
_actor.moveToLocation(loc.getX(), loc.getY(), loc.getZ(), 0);
|
||||
|
||||
if (!_actor.isMoving())
|
||||
{
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Doppelgangers always send MoveToLocation packet.
|
||||
if (sendPacket)
|
||||
{
|
||||
_actor.broadcastPacket(new MoveToLocation(_actor));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clientActionFailed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DoppelgangerInstance getActor()
|
||||
{
|
||||
return (DoppelgangerInstance) super.getActor();
|
||||
}
|
||||
}
|
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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_REST;
|
||||
|
||||
import com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.GeoData;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.L2World;
|
||||
import com.l2jmobius.gameserver.model.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Attackable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
|
||||
/**
|
||||
* @author Sdw
|
||||
*/
|
||||
public class FriendlyNpcAI extends L2AttackableAI
|
||||
{
|
||||
public FriendlyNpcAI(L2Attackable attackable)
|
||||
{
|
||||
super(attackable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkActive()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtAttacked(L2Character attacker)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtAggression(L2Character target, int aggro)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionAttack(L2Character target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (getIntention() == AI_INTENTION_REST)
|
||||
{
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isControlBlocked())
|
||||
{
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the Intention of this AbstractAI to AI_INTENTION_ATTACK
|
||||
changeIntention(AI_INTENTION_ATTACK, target);
|
||||
|
||||
// Set the AI attack target
|
||||
setTarget(target);
|
||||
|
||||
stopFollow();
|
||||
|
||||
// Launch the Think Event
|
||||
notifyEvent(CtrlEvent.EVT_THINK, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkAttack()
|
||||
{
|
||||
final L2Attackable npc = getActiveChar();
|
||||
if (npc.isCastingNow() || npc.isCoreAIDisabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final L2Object target = getTarget();
|
||||
final L2Character originalAttackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
// Check if target is dead or if timeout is expired to stop this attack
|
||||
if ((originalAttackTarget == null) || originalAttackTarget.isAlikeDead())
|
||||
{
|
||||
// Stop hating this target after the attack timeout or if target is dead
|
||||
if (originalAttackTarget != null)
|
||||
{
|
||||
npc.stopHating(originalAttackTarget);
|
||||
}
|
||||
|
||||
// Set the AI Intention to AI_INTENTION_ACTIVE
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
|
||||
npc.setWalking();
|
||||
return;
|
||||
}
|
||||
|
||||
final int collision = npc.getTemplate().getCollisionRadius();
|
||||
|
||||
setTarget(originalAttackTarget);
|
||||
|
||||
final int combinedCollision = collision + originalAttackTarget.getTemplate().getCollisionRadius();
|
||||
|
||||
if (!npc.isMovementDisabled() && (Rnd.nextInt(100) <= 3))
|
||||
{
|
||||
for (L2Attackable nearby : L2World.getInstance().getVisibleObjects(npc, L2Attackable.class))
|
||||
{
|
||||
if (npc.isInsideRadius(nearby, collision, false, false) && (nearby != originalAttackTarget))
|
||||
{
|
||||
int newX = combinedCollision + Rnd.get(40);
|
||||
if (Rnd.nextBoolean())
|
||||
{
|
||||
newX = originalAttackTarget.getX() + newX;
|
||||
}
|
||||
else
|
||||
{
|
||||
newX = originalAttackTarget.getX() - newX;
|
||||
}
|
||||
int newY = combinedCollision + Rnd.get(40);
|
||||
if (Rnd.nextBoolean())
|
||||
{
|
||||
newY = originalAttackTarget.getY() + newY;
|
||||
}
|
||||
else
|
||||
{
|
||||
newY = originalAttackTarget.getY() - newY;
|
||||
}
|
||||
|
||||
if (!npc.isInsideRadius(newX, newY, 0, collision, false, false))
|
||||
{
|
||||
final int newZ = npc.getZ() + 30;
|
||||
if (GeoData.getInstance().canMove(npc.getX(), npc.getY(), npc.getZ(), newX, newY, newZ, npc.getInstanceWorld()))
|
||||
{
|
||||
moveTo(newX, newY, newZ);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Dodge if its needed
|
||||
if (!npc.isMovementDisabled() && (npc.getTemplate().getDodge() > 0))
|
||||
{
|
||||
if (Rnd.get(100) <= npc.getTemplate().getDodge())
|
||||
{
|
||||
final double distance2 = npc.calculateDistance(originalAttackTarget, false, true);
|
||||
if (Math.sqrt(distance2) <= (60 + combinedCollision))
|
||||
{
|
||||
int posX = npc.getX();
|
||||
int posY = npc.getY();
|
||||
final int posZ = npc.getZ() + 30;
|
||||
|
||||
if (originalAttackTarget.getX() < posX)
|
||||
{
|
||||
posX = posX + 300;
|
||||
}
|
||||
else
|
||||
{
|
||||
posX = posX - 300;
|
||||
}
|
||||
|
||||
if (originalAttackTarget.getY() < posY)
|
||||
{
|
||||
posY = posY + 300;
|
||||
}
|
||||
else
|
||||
{
|
||||
posY = posY - 300;
|
||||
}
|
||||
|
||||
if (GeoData.getInstance().canMove(npc.getX(), npc.getY(), npc.getZ(), posX, posY, posZ, npc.getInstanceWorld()))
|
||||
{
|
||||
setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new Location(posX, posY, posZ, 0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final double dist = npc.calculateDistance(originalAttackTarget, false, false);
|
||||
final int dist2 = (int) dist - collision;
|
||||
int range = npc.getPhysicalAttackRange() + combinedCollision;
|
||||
if (originalAttackTarget.isMoving())
|
||||
{
|
||||
range = range + 50;
|
||||
if (npc.isMoving())
|
||||
{
|
||||
range = range + 50;
|
||||
}
|
||||
}
|
||||
|
||||
if ((dist2 > range) || !GeoData.getInstance().canSeeTarget(npc, originalAttackTarget))
|
||||
{
|
||||
if (originalAttackTarget.isMoving())
|
||||
{
|
||||
range -= 100;
|
||||
}
|
||||
if (range < 5)
|
||||
{
|
||||
range = 5;
|
||||
}
|
||||
moveToPawn(originalAttackTarget, range);
|
||||
return;
|
||||
}
|
||||
|
||||
_actor.doAttack(originalAttackTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkCast()
|
||||
{
|
||||
final L2Object target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
setTarget(null);
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_actor.doCast(_skill, _item, _forceUse, _dontMove);
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2AirShipInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.ExMoveToLocationAirShip;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.ExStopMoveAirShip;
|
||||
|
||||
/**
|
||||
* @author DS
|
||||
*/
|
||||
public class L2AirShipAI extends L2VehicleAI
|
||||
{
|
||||
public L2AirShipAI(L2AirShipInstance airShip)
|
||||
{
|
||||
super(airShip);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void moveTo(int x, int y, int z)
|
||||
{
|
||||
if (!_actor.isMovementDisabled())
|
||||
{
|
||||
_clientMoving = true;
|
||||
_actor.moveToLocation(x, y, z, 0);
|
||||
_actor.broadcastPacket(new ExMoveToLocationAirShip(getActor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientStopMoving(Location loc)
|
||||
{
|
||||
if (_actor.isMoving())
|
||||
{
|
||||
_actor.stopMove(loc);
|
||||
}
|
||||
|
||||
if (_clientMoving || (loc != null))
|
||||
{
|
||||
_clientMoving = false;
|
||||
_actor.broadcastPacket(new ExStopMoveAirShip(getActor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeStateToPlayer(L2PcInstance player)
|
||||
{
|
||||
if (_clientMoving)
|
||||
{
|
||||
player.sendPacket(new ExMoveToLocationAirShip(getActor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public L2AirShipInstance getActor()
|
||||
{
|
||||
return (L2AirShipInstance) _actor;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2BoatInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
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 L2VehicleAI
|
||||
{
|
||||
public L2BoatAI(L2BoatInstance boat)
|
||||
{
|
||||
super(boat);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void moveTo(int x, int y, int z)
|
||||
{
|
||||
if (!_actor.isMovementDisabled())
|
||||
{
|
||||
if (!_clientMoving)
|
||||
{
|
||||
_actor.broadcastPacket(new VehicleStarted(getActor(), 1));
|
||||
}
|
||||
|
||||
_clientMoving = true;
|
||||
_actor.moveToLocation(x, y, z, 0);
|
||||
_actor.broadcastPacket(new VehicleDeparture(getActor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientStopMoving(Location loc)
|
||||
{
|
||||
if (_actor.isMoving())
|
||||
{
|
||||
_actor.stopMove(loc);
|
||||
}
|
||||
|
||||
if (_clientMoving || (loc != null))
|
||||
{
|
||||
_clientMoving = false;
|
||||
_actor.broadcastPacket(new VehicleStarted(getActor(), 0));
|
||||
_actor.broadcastPacket(new VehicleInfo(getActor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeStateToPlayer(L2PcInstance player)
|
||||
{
|
||||
if (_clientMoving)
|
||||
{
|
||||
player.sendPacket(new VehicleDeparture(getActor()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public L2BoatInstance getActor()
|
||||
{
|
||||
return (L2BoatInstance) _actor;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.L2World;
|
||||
import com.l2jmobius.gameserver.model.MobGroup;
|
||||
import com.l2jmobius.gameserver.model.MobGroupTable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Attackable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Npc;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Playable;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2ControllableMobInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.util.Util;
|
||||
|
||||
/**
|
||||
* AI for controllable mobs
|
||||
* @author littlecrow
|
||||
*/
|
||||
public final 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())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkCast()
|
||||
{
|
||||
L2Object target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
|
||||
if ((target == null) || !target.isCharacter() || ((L2Character) target).isAlikeDead())
|
||||
{
|
||||
target = _skill.getTarget(_actor, findNextRndTarget(), _forceUse, _dontMove, false);
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setTarget(target);
|
||||
|
||||
if (!_actor.isMuted())
|
||||
{
|
||||
int max_range = 0;
|
||||
// check distant skills
|
||||
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
if (Util.checkIfInRange(sk.getCastRange(), _actor, target, true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_actor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, sk.getCastRange());
|
||||
}
|
||||
|
||||
if (!isNotMoving())
|
||||
{
|
||||
moveToPawn(target, 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
final double dist2 = _actor.calculateDistance(target, false, true);
|
||||
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
|
||||
int max_range = range;
|
||||
|
||||
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
|
||||
{
|
||||
// check distant skills
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_actor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, castRange);
|
||||
}
|
||||
|
||||
if (!isNotMoving())
|
||||
{
|
||||
moveToPawn(target, range);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
_actor.doAttack(target);
|
||||
}
|
||||
|
||||
protected void thinkForceAttack()
|
||||
{
|
||||
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
|
||||
{
|
||||
clientStopMoving(null);
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
setAlternateAI(AI_IDLE);
|
||||
}
|
||||
|
||||
setTarget(getForcedTarget());
|
||||
final double dist2 = _actor.calculateDistance(getForcedTarget(), false, true);
|
||||
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius();
|
||||
int max_range = range;
|
||||
|
||||
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
|
||||
{
|
||||
// check distant skills
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_actor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, castRange);
|
||||
}
|
||||
|
||||
if (!isNotMoving())
|
||||
{
|
||||
moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_actor.doAttack(getForcedTarget());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkAttack()
|
||||
{
|
||||
L2Character target = getForcedTarget();
|
||||
if ((target == null) || target.isAlikeDead())
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
// stop hating
|
||||
final L2Attackable npc = (L2Attackable) _actor;
|
||||
npc.stopHating(target);
|
||||
}
|
||||
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// notify aggression
|
||||
final L2Character finalTarget = target;
|
||||
if (((L2Npc) _actor).getTemplate().getClans() != null)
|
||||
{
|
||||
L2World.getInstance().forEachVisibleObject(_actor, L2Npc.class, npc ->
|
||||
{
|
||||
if (!npc.isInMyClan((L2Npc) _actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actor.isInsideRadius(npc, npc.getTemplate().getClanHelpRange(), true, true))
|
||||
{
|
||||
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, finalTarget, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTarget(target);
|
||||
final double dist2 = _actor.calculateDistance(target, false, true);
|
||||
final int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
|
||||
int max_range = range;
|
||||
|
||||
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
|
||||
{
|
||||
// check distant skills
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_actor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, castRange);
|
||||
}
|
||||
|
||||
moveToPawn(target, range);
|
||||
return;
|
||||
}
|
||||
|
||||
// Force mobs to attack anybody if confused.
|
||||
L2Character hated;
|
||||
|
||||
if (_actor.isConfused())
|
||||
{
|
||||
hated = findNextRndTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = target;
|
||||
}
|
||||
|
||||
if (hated == null)
|
||||
{
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hated != target)
|
||||
{
|
||||
target = hated;
|
||||
}
|
||||
|
||||
if (!_actor.isMuted() && (Rnd.nextInt(5) == 3))
|
||||
{
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_actor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_actor.doAttack(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkActive()
|
||||
{
|
||||
L2Character hated;
|
||||
|
||||
if (_actor.isConfused())
|
||||
{
|
||||
hated = findNextRndTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
final L2Object target = _actor.getTarget();
|
||||
hated = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
}
|
||||
|
||||
if (hated != null)
|
||||
{
|
||||
_actor.setRunning();
|
||||
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkAutoAttackCondition(L2Character target)
|
||||
{
|
||||
if ((target == null) || !_actor.isAttackable())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final L2Attackable me = (L2Attackable) _actor;
|
||||
|
||||
if (target.isNpc() || target.isDoor())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// Spawn protection (only against mobs)
|
||||
if (target.isPlayer() && ((L2PcInstance) target).isSpawnProtected())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the target is a L2Playable
|
||||
if (target.isPlayable())
|
||||
{
|
||||
// Check if the target isn't in silent move mode
|
||||
if (((L2Playable) target).isSilentMovingAffected())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.isNpc())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return me.isAggressive();
|
||||
}
|
||||
|
||||
private L2Character findNextRndTarget()
|
||||
{
|
||||
final List<L2Character> potentialTarget = new ArrayList<>();
|
||||
L2World.getInstance().forEachVisibleObject(_actor, L2Character.class, target ->
|
||||
{
|
||||
if (Util.checkIfInShortRange(((L2Attackable) _actor).getAggroRange(), _actor, target, true) && checkAutoAttackCondition(target))
|
||||
{
|
||||
potentialTarget.add(target);
|
||||
}
|
||||
});
|
||||
|
||||
return !potentialTarget.isEmpty() ? potentialTarget.get(Rnd.nextInt(potentialTarget.size())) : null;
|
||||
}
|
||||
|
||||
private L2ControllableMobInstance findNextGroupTarget()
|
||||
{
|
||||
return getGroupTarget().getRandomMob();
|
||||
}
|
||||
|
||||
public L2ControllableMobAI(L2ControllableMobInstance controllableMob)
|
||||
{
|
||||
super(controllableMob);
|
||||
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;
|
||||
}
|
||||
}
|
@@ -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.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.L2World;
|
||||
import com.l2jmobius.gameserver.model.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2DoorInstance;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
|
||||
/**
|
||||
* @author mkizub
|
||||
*/
|
||||
public class L2DoorAI extends L2CharacterAI
|
||||
{
|
||||
public L2DoorAI(L2DoorInstance door)
|
||||
{
|
||||
super(door);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionIdle()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionActive()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionRest()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionAttack(L2Character target)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionCast(Skill skill, L2Object target, L2ItemInstance item, boolean forceUse, boolean dontMove)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionMoveTo(Location 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)
|
||||
{
|
||||
ThreadPoolManager.getInstance().executeGeneral(new onEventAttackedDoorTask((L2DoorInstance) _actor, attacker));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtAggression(L2Character target, int aggro)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtActionBlocked(L2Character attacker)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtRooted(L2Character attacker)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtReadyToAct()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtArrived()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtArrivedRevalidate()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtArrivedBlocked(Location blocked_at_loc)
|
||||
{
|
||||
}
|
||||
|
||||
@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()
|
||||
{
|
||||
L2World.getInstance().forEachVisibleObject(_door, L2DefenderInstance.class, guard ->
|
||||
{
|
||||
if (_actor.isInsideRadius(guard, guard.getTemplate().getClanHelpRange(), true, true))
|
||||
{
|
||||
guard.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, _attacker, 15);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,931 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.GameTimeController;
|
||||
import com.l2jmobius.gameserver.GeoData;
|
||||
import com.l2jmobius.gameserver.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.L2World;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Attackable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Npc;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Playable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Summon;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2FortCommanderInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.effects.L2EffectType;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.util.Util;
|
||||
|
||||
/**
|
||||
* This class manages AI of L2Attackable.
|
||||
*/
|
||||
public class L2FortSiegeGuardAI extends L2CharacterAI implements Runnable
|
||||
{
|
||||
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 attacked 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;
|
||||
|
||||
public L2FortSiegeGuardAI(L2Character accessor)
|
||||
{
|
||||
super(accessor);
|
||||
_selfAnalysis.init();
|
||||
_attackTimeout = Integer.MAX_VALUE;
|
||||
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
|
||||
_attackRange = _actor.getPhysicalAttackRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Launch actions corresponding to the Event Think
|
||||
onEvtThink();
|
||||
}
|
||||
|
||||
/**
|
||||
* <B><U> Actor is a L2GuardInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* <B><U> Actor is a L2MonsterInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* @param target The targeted L2Object
|
||||
* @return True if the target is autoattackable (depends on the actor type).
|
||||
*/
|
||||
private boolean autoAttackCondition(L2Character target)
|
||||
{
|
||||
// Check if the target isn't another guard, folk or a door
|
||||
if ((target == null) || (target instanceof L2DefenderInstance) || target.isNpc() || target.isDoor() || target.isAlikeDead() || (target instanceof L2FortCommanderInstance) || target.isPlayable())
|
||||
{
|
||||
L2PcInstance player = null;
|
||||
if (target instanceof L2PcInstance)
|
||||
{
|
||||
player = ((L2PcInstance) target);
|
||||
}
|
||||
else if (target instanceof L2Summon)
|
||||
{
|
||||
player = ((L2Summon) target).getOwner();
|
||||
}
|
||||
if ((player == null) || ((player.getClan() != null) && (player.getClan().getFortId() == ((L2Npc) _actor).getFort().getResidenceId())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the target isn't invulnerable
|
||||
if ((target != null) && target.isInvul())
|
||||
{
|
||||
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 L2Playable)
|
||||
{
|
||||
// Check if the target isn't in silent move mode AND too far (>100)
|
||||
if (((L2Playable) target).isSilentMovingAffected() && !_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>
|
||||
* <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>
|
||||
* @param intention The new Intention to set to the AI
|
||||
* @param args The first parameter of the Intention
|
||||
*/
|
||||
@Override
|
||||
synchronized void changeIntention(CtrlIntention intention, Object... args)
|
||||
{
|
||||
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 (!L2World.getInstance().getVisibleObjects(npc, L2PcInstance.class).isEmpty())
|
||||
{
|
||||
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);
|
||||
|
||||
// Stop AI task and detach AI from NPC
|
||||
if (_aiTask != null)
|
||||
{
|
||||
_aiTask.cancel(true);
|
||||
_aiTask = null;
|
||||
}
|
||||
|
||||
// Cancel the AI
|
||||
_actor.detachAI();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the Intention of this L2AttackableAI to intention
|
||||
super.changeIntention(intention, args);
|
||||
|
||||
// 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.
|
||||
* @param target The L2Character to attack
|
||||
*/
|
||||
@Override
|
||||
protected void onIntentionAttack(L2Character target)
|
||||
{
|
||||
// Calculate the attack timeout
|
||||
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
*/
|
||||
private void thinkActive()
|
||||
{
|
||||
final L2Attackable npc = (L2Attackable) _actor;
|
||||
final L2Object target = getTarget();
|
||||
// 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)
|
||||
{
|
||||
L2World.getInstance().forEachVisibleObjectInRange(npc, L2Character.class, _attackRange, t ->
|
||||
{
|
||||
if (autoAttackCondition(t)) // check aggression
|
||||
{
|
||||
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
|
||||
final int hating = npc.getHating(t);
|
||||
|
||||
// Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
|
||||
if (hating == 0)
|
||||
{
|
||||
npc.addDamageHate(t, 0, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Chose a target from its aggroList
|
||||
L2Character hated;
|
||||
if (_actor.isConfused() && (target != null) && target.isCharacter())
|
||||
{
|
||||
hated = (L2Character) target; // Force mobs to attack anybody if confused
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = npc.getMostHated();
|
||||
// _mostHatedAnalysis.Update(hated);
|
||||
}
|
||||
|
||||
// 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
|
||||
if (_actor.getWalkSpeed() >= 0)
|
||||
{
|
||||
if (_actor instanceof L2DefenderInstance)
|
||||
{
|
||||
((L2DefenderInstance) _actor).returnHome();
|
||||
}
|
||||
else
|
||||
{
|
||||
((L2FortCommanderInstance) _actor).returnHome();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<br>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>Chose a target and order to attack it with magic skill or physical attack</li>
|
||||
* </ul>
|
||||
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
|
||||
*/
|
||||
private void thinkAttack()
|
||||
{
|
||||
if (_attackTimeout < GameTimeController.getInstance().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.getInstance().getGameTicks();
|
||||
}
|
||||
}
|
||||
|
||||
final L2Object target = getTarget();
|
||||
final L2Character attackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
// Check if target is dead or if timeout is expired to stop this attack
|
||||
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getInstance().getGameTicks()))
|
||||
{
|
||||
// Stop hating this target after the attack timeout or if target is dead
|
||||
if (attackTarget != null)
|
||||
{
|
||||
final L2Attackable npc = (L2Attackable) _actor;
|
||||
npc.stopHating(attackTarget);
|
||||
}
|
||||
|
||||
// Cancel target and timeout
|
||||
_attackTimeout = Integer.MAX_VALUE;
|
||||
setTarget(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 L2Object target = getTarget();
|
||||
// Call all L2Object of its Faction inside the Faction Range
|
||||
if ((((L2Npc) _actor).getTemplate().getClans() == null) || (target == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.isInvul())
|
||||
{
|
||||
return; // speeding it up for siege guards
|
||||
}
|
||||
|
||||
// Go through all L2Character that belong to its faction
|
||||
// for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(((L2NpcInstance) _actor).getFactionRange()+_actor.getTemplate().collisionRadius))
|
||||
for (L2Character cha : L2World.getInstance().getVisibleObjects(_actor, L2Character.class, 1000))
|
||||
{
|
||||
if (cha == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cha.isNpc())
|
||||
{
|
||||
if (_selfAnalysis.hasHealOrResurrect && cha.isPlayer() && ((L2Npc) _actor).getFort().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 (Skill sk : _selfAnalysis.healSkills)
|
||||
{
|
||||
if (_actor.getCurrentMp() < sk.getMpConsume())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_actor.isSkillDisabled(sk))
|
||||
{
|
||||
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 = getTarget();
|
||||
setTarget(cha);
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final L2Npc npc = (L2Npc) cha;
|
||||
|
||||
if (!npc.isInMyClan((L2Npc) _actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (npc.getAI() != null) // TODO: possibly check not needed
|
||||
{
|
||||
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600)
|
||||
// && _actor.getAttackByList().contains(getTarget())
|
||||
&& ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE))
|
||||
// limiting aggro for siege guards
|
||||
&& npc.isInsideRadius(target, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target))
|
||||
{
|
||||
// Notify the L2Object AI with EVT_AGGRESSION
|
||||
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, target, 1);
|
||||
return;
|
||||
}
|
||||
// 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 (Skill sk : _selfAnalysis.healSkills)
|
||||
{
|
||||
if (_actor.getCurrentMp() < sk.getMpConsume())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_actor.isSkillDisabled(sk))
|
||||
{
|
||||
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 = getTarget();
|
||||
setTarget(npc);
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attackPrepare()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
L2Character attackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
if (attackTarget == null)
|
||||
{
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
return;
|
||||
}
|
||||
// Get all information needed to choose between physical or magical attack
|
||||
Collection<Skill> skills = null;
|
||||
double dist_2 = 0;
|
||||
int range = 0;
|
||||
L2DefenderInstance sGuard;
|
||||
if (_actor instanceof L2FortCommanderInstance)
|
||||
{
|
||||
sGuard = (L2FortCommanderInstance) _actor;
|
||||
}
|
||||
else
|
||||
{
|
||||
sGuard = (L2DefenderInstance) _actor;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
setTarget(attackTarget);
|
||||
skills = _actor.getAllSkills();
|
||||
dist_2 = _actor.calculateDistance(attackTarget, false, true);
|
||||
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
|
||||
if (attackTarget.isMoving())
|
||||
{
|
||||
range += 50;
|
||||
}
|
||||
}
|
||||
catch (NullPointerException e)
|
||||
{
|
||||
// LOGGER.warning("AttackableAI: Attack target is NULL.");
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// never attack defenders
|
||||
if ((attackTarget instanceof L2PcInstance) && sGuard.getFort().getSiege().checkIsDefender(((L2PcInstance) attackTarget).getClan()))
|
||||
{
|
||||
// Cancel the target
|
||||
sGuard.stopHating(attackTarget);
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GeoData.getInstance().canSeeTarget(_actor, attackTarget))
|
||||
{
|
||||
// Siege guards differ from normal mobs currently:
|
||||
// If target cannot seen, don't attack any more
|
||||
sGuard.stopHating(attackTarget);
|
||||
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 (Skill sk : skills)
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
|
||||
{
|
||||
|
||||
final L2Object OldTarget = getTarget();
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HEAL)))
|
||||
{
|
||||
boolean useSkillSelf = true;
|
||||
if ((sk.hasEffectType(L2EffectType.HEAL)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId()))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
}
|
||||
|
||||
if (useSkillSelf)
|
||||
{
|
||||
setTarget(_actor);
|
||||
}
|
||||
}
|
||||
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
|
||||
if (!(_actor.isAttackingNow()) && (_actor.getRunSpeed() == 0) && (_actor.isInSurroundingRegion(attackTarget)))
|
||||
{
|
||||
// Cancel the target
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
final double dx = _actor.getX() - attackTarget.getX();
|
||||
final double dy = _actor.getY() - attackTarget.getY();
|
||||
final double dz = _actor.getZ() - attackTarget.getZ();
|
||||
final double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
|
||||
final double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
|
||||
|
||||
// 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.isInSurroundingRegion(attackTarget)))
|
||||
{
|
||||
// Cancel the target
|
||||
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 (_actor.getWalkSpeed() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (attackTarget.isMoving())
|
||||
{
|
||||
moveToPawn(attackTarget, range - 70);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToPawn(attackTarget, 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() - attackTarget.getZ();
|
||||
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
|
||||
{
|
||||
if (_selfAnalysis.isMage)
|
||||
{
|
||||
range = _selfAnalysis.maxCastRange - 50;
|
||||
}
|
||||
if (_actor.getWalkSpeed() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (attackTarget.isMoving())
|
||||
{
|
||||
moveToPawn(attackTarget, range - 70);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToPawn(attackTarget, 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 = attackTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = ((L2Attackable) _actor).getMostHated();
|
||||
}
|
||||
|
||||
if (hated == null)
|
||||
{
|
||||
setIntention(AI_INTENTION_ACTIVE, null, null);
|
||||
return;
|
||||
}
|
||||
if (hated != attackTarget)
|
||||
{
|
||||
attackTarget = hated;
|
||||
}
|
||||
|
||||
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
|
||||
|
||||
// check for close combat skills && heal/buff skills
|
||||
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5))
|
||||
{
|
||||
for (Skill sk : skills)
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk))
|
||||
{
|
||||
final L2Object OldTarget = getTarget();
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HEAL)))
|
||||
{
|
||||
boolean useSkillSelf = true;
|
||||
if ((sk.hasEffectType(L2EffectType.HEAL)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId()))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
}
|
||||
|
||||
if (useSkillSelf)
|
||||
{
|
||||
setTarget(_actor);
|
||||
}
|
||||
}
|
||||
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, do the physical attack itself
|
||||
_actor.doAttack(attackTarget);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage AI thinking actions of a L2Attackable.
|
||||
*/
|
||||
@Override
|
||||
protected void onEvtThink()
|
||||
{
|
||||
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
|
||||
// setIntention(AI_INTENTION_IDLE);
|
||||
|
||||
// Check if the actor can't use skills and if a thinking action isn't already in progress
|
||||
if (_thinking || _actor.isCastingNow() || _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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* @param attacker The L2Character that attacks the actor
|
||||
*/
|
||||
@Override
|
||||
protected void onEvtAttacked(L2Character attacker)
|
||||
{
|
||||
// Calculate the attack timeout
|
||||
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* @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 L2DefenderInstance sGuard = _actor instanceof L2FortCommanderInstance ? (L2FortCommanderInstance) _actor : (L2DefenderInstance) _actor;
|
||||
final double homeX = target.getX() - sGuard.getSpawn().getX();
|
||||
final double homeY = target.getY() - sGuard.getSpawn().getY();
|
||||
|
||||
// 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 (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
|
||||
public void stopAITask()
|
||||
{
|
||||
if (_aiTask != null)
|
||||
{
|
||||
_aiTask.cancel(false);
|
||||
_aiTask = null;
|
||||
}
|
||||
_actor.detachAI();
|
||||
super.stopAITask();
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.L2Object;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Playable;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.model.zone.ZoneId;
|
||||
import com.l2jmobius.gameserver.network.SystemMessageId;
|
||||
|
||||
/**
|
||||
* This class manages AI of L2Playable.<br>
|
||||
* L2PlayableAI :
|
||||
* <li>L2SummonAI</li>
|
||||
* <li>L2PlayerAI</li>
|
||||
* @author JIV
|
||||
*/
|
||||
public abstract class L2PlayableAI extends L2CharacterAI
|
||||
{
|
||||
public L2PlayableAI(L2Playable playable)
|
||||
{
|
||||
super(playable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionAttack(L2Character target)
|
||||
{
|
||||
if (target instanceof L2Playable)
|
||||
{
|
||||
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getReputation() < 0) && !(target.isInsideZone(ZoneId.PVP)))
|
||||
{
|
||||
// If attacker have karma and have level >= 10 than his target and target have
|
||||
// Newbie Protection Buff,
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actor.getActingPlayer().isProtectionBlessingAffected() && ((target.getActingPlayer().getLevel() - _actor.getActingPlayer().getLevel()) >= 10) && (target.getActingPlayer().getReputation() < 0) && !(target.isInsideZone(ZoneId.PVP)))
|
||||
{
|
||||
// If target have karma and have level >= 10 than his target and actor have
|
||||
// Newbie Protection Buff,
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getActingPlayer().isCursedWeaponEquipped() && (_actor.getActingPlayer().getLevel() <= 20))
|
||||
{
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actor.getActingPlayer().isCursedWeaponEquipped() && (target.getActingPlayer().getLevel() <= 20))
|
||||
{
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onIntentionAttack(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionCast(Skill skill, L2Object target, L2ItemInstance item, boolean forceUse, boolean dontMove)
|
||||
{
|
||||
if ((target.isPlayable()) && skill.isBad())
|
||||
{
|
||||
if (target.getActingPlayer().isProtectionBlessingAffected() && ((_actor.getActingPlayer().getLevel() - target.getActingPlayer().getLevel()) >= 10) && (_actor.getActingPlayer().getReputation() < 0) && !target.isInsideZone(ZoneId.PVP))
|
||||
{
|
||||
// If attacker have karma and have level >= 10 than his target and target have
|
||||
// Newbie Protection Buff,
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actor.getActingPlayer().isProtectionBlessingAffected() && ((target.getActingPlayer().getLevel() - _actor.getActingPlayer().getLevel()) >= 10) && (target.getActingPlayer().getReputation() < 0) && !target.isInsideZone(ZoneId.PVP))
|
||||
{
|
||||
// If target have karma and have level >= 10 than his target and actor have
|
||||
// Newbie Protection Buff,
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.getActingPlayer().isCursedWeaponEquipped() && ((_actor.getActingPlayer().getLevel() <= 20) || (target.getActingPlayer().getLevel() <= 20)))
|
||||
{
|
||||
_actor.getActingPlayer().sendPacket(SystemMessageId.THAT_IS_AN_INCORRECT_TARGET);
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onIntentionCast(skill, target, item, forceUse, dontMove);
|
||||
}
|
||||
}
|
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* 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.L2Object;
|
||||
import com.l2jmobius.gameserver.model.Location;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.model.skills.targets.TargetType;
|
||||
|
||||
public class L2PlayerAI extends L2PlayableAI
|
||||
{
|
||||
private boolean _thinking; // to prevent recursive thinking
|
||||
|
||||
IntentionCommand _nextIntention = null;
|
||||
|
||||
public L2PlayerAI(L2PcInstance player)
|
||||
{
|
||||
super(player);
|
||||
}
|
||||
|
||||
void saveNextIntention(CtrlIntention intention, Object arg0, Object arg1)
|
||||
{
|
||||
_nextIntention = new IntentionCommand(intention, arg0, arg1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntentionCommand getNextIntention()
|
||||
{
|
||||
return _nextIntention;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current Intention for this L2PlayerAI if necessary and calls changeIntention in AbstractAI.
|
||||
* @param intention The new Intention to set to the AI
|
||||
* @param args The first parameter of the Intention
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void changeIntention(CtrlIntention intention, Object... args)
|
||||
{
|
||||
final Object localArg0 = args.length > 0 ? args[0] : null;
|
||||
final Object localArg1 = args.length > 1 ? args[1] : null;
|
||||
|
||||
final Object globalArg0 = (_intentionArgs != null) && (_intentionArgs.length > 0) ? _intentionArgs[0] : null;
|
||||
final Object globalArg1 = (_intentionArgs != null) && (_intentionArgs.length > 1) ? _intentionArgs[1] : null;
|
||||
|
||||
// do nothing unless CAST intention
|
||||
// however, forget interrupted actions when starting to use an offensive skill
|
||||
if ((intention != AI_INTENTION_CAST) || ((Skill) args[0]).isBad())
|
||||
{
|
||||
_nextIntention = null;
|
||||
super.changeIntention(intention, args);
|
||||
return;
|
||||
}
|
||||
|
||||
// do nothing if next intention is same as current one.
|
||||
if ((intention == _intention) && (globalArg0 == localArg0) && (globalArg1 == localArg1))
|
||||
{
|
||||
super.changeIntention(intention, args);
|
||||
return;
|
||||
}
|
||||
|
||||
// save current intention so it can be used after cast
|
||||
saveNextIntention(_intention, globalArg0, globalArg1);
|
||||
super.changeIntention(intention, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch actions corresponding to the Event ReadyToAct.<br>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <li>Launch actions corresponding to the Event Think</li>
|
||||
* </ul>
|
||||
*/
|
||||
@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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <li>Stop an AI Follow Task</li>
|
||||
* <li>Launch actions corresponding to the Event Think</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
protected void onEvtCancel()
|
||||
{
|
||||
_nextIntention = null;
|
||||
super.onEvtCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the casting of a skill. This method overrides L2CharacterAI method.<br>
|
||||
* <B>What it does:</B><br>
|
||||
* 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) // previous state shouldn't be casting
|
||||
{
|
||||
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 onEvtAttacked(L2Character attacker)
|
||||
{
|
||||
super.onEvtAttacked(attacker);
|
||||
|
||||
// Summons in defending mode defend its master when attacked.
|
||||
if (_actor.getActingPlayer().hasServitors())
|
||||
{
|
||||
_actor.getActingPlayer().getServitors().values().stream().filter(summon -> ((L2SummonAI) summon.getAI()).isDefending()).forEach(summon -> ((L2SummonAI) summon.getAI()).defendAttack(attacker));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtEvaded(L2Character attacker)
|
||||
{
|
||||
super.onEvtEvaded(attacker);
|
||||
|
||||
// Summons in defending mode defend its master when attacked.
|
||||
if (_actor.getActingPlayer().hasServitors())
|
||||
{
|
||||
_actor.getActingPlayer().getServitors().values().stream().filter(summon -> ((L2SummonAI) summon.getAI()).isDefending()).forEach(summon -> ((L2SummonAI) summon.getAI()).defendAttack(attacker));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionRest()
|
||||
{
|
||||
if (getIntention() != AI_INTENTION_REST)
|
||||
{
|
||||
changeIntention(AI_INTENTION_REST);
|
||||
setTarget(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>
|
||||
* <B><U> Actions</U> : </B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
protected void onIntentionMoveTo(Location loc)
|
||||
{
|
||||
if (getIntention() == AI_INTENTION_REST)
|
||||
{
|
||||
// Cancel action client side by sending Server->Client packet ActionFailed to the L2PcInstance actor
|
||||
clientActionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow() || _actor.isAttackingNow())
|
||||
{
|
||||
clientActionFailed();
|
||||
saveNextIntention(AI_INTENTION_MOVE_TO, loc, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the Intention of this AbstractAI to AI_INTENTION_MOVE_TO
|
||||
changeIntention(AI_INTENTION_MOVE_TO, loc);
|
||||
|
||||
// Stop the actor auto-attack client side by sending Server->Client packet AutoAttackStop (broadcast)
|
||||
clientStopAutoAttack();
|
||||
|
||||
// Abort the attack of the L2Character and send Server->Client ActionFailed packet
|
||||
_actor.abortAttack();
|
||||
|
||||
// Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
|
||||
moveTo(loc.getX(), loc.getY(), loc.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clientNotifyDead()
|
||||
{
|
||||
_clientMovingToPawnOffset = 0;
|
||||
_clientMoving = false;
|
||||
|
||||
super.clientNotifyDead();
|
||||
}
|
||||
|
||||
private void thinkAttack()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
if ((target == null) || !target.isCharacter())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (checkTargetLostOrDead((L2Character) target))
|
||||
{
|
||||
// Notify the target
|
||||
setTarget(null);
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, _actor.getPhysicalAttackRange()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_actor.doAttack((L2Character) target);
|
||||
}
|
||||
|
||||
private void thinkCast()
|
||||
{
|
||||
final L2Object target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
|
||||
if ((_skill.getTargetType() == TargetType.GROUND) && (_actor instanceof L2PcInstance))
|
||||
{
|
||||
if (maybeMoveToPosition(((L2PcInstance) _actor).getCurrentSkillWorldPosition(), _actor.getMagicalAttackRange(_skill)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
if (_skill.isBad() && (target != null))
|
||||
{
|
||||
// Notify the target
|
||||
setTarget(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((target != null) && maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_actor.doCast(_skill, _item, _forceUse, _dontMove);
|
||||
}
|
||||
|
||||
private void thinkPickUp()
|
||||
{
|
||||
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow())
|
||||
{
|
||||
return;
|
||||
}
|
||||
final L2Object target = getTarget();
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, 36))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
getActor().doPickupItem(target);
|
||||
}
|
||||
|
||||
private void thinkInteract()
|
||||
{
|
||||
if (_actor.isAllSkillsDisabled() || _actor.isCastingNow())
|
||||
{
|
||||
return;
|
||||
}
|
||||
final L2Object target = getTarget();
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, 36))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!(target instanceof L2StaticObjectInstance))
|
||||
{
|
||||
getActor().doInteract((L2Character) target);
|
||||
}
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtThink()
|
||||
{
|
||||
if (_thinking && (getIntention() != AI_INTENTION_CAST))
|
||||
{
|
||||
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
|
||||
public L2PcInstance getActor()
|
||||
{
|
||||
return (L2PcInstance) super.getActor();
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.actor.instance.L2ShuttleInstance;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.shuttle.ExShuttleMove;
|
||||
|
||||
/**
|
||||
* @author UnAfraid
|
||||
*/
|
||||
public class L2ShuttleAI extends L2VehicleAI
|
||||
{
|
||||
public L2ShuttleAI(L2ShuttleInstance shuttle)
|
||||
{
|
||||
super(shuttle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(int x, int y, int z)
|
||||
{
|
||||
if (!_actor.isMovementDisabled())
|
||||
{
|
||||
_clientMoving = true;
|
||||
_actor.moveToLocation(x, y, z, 0);
|
||||
_actor.broadcastPacket(new ExShuttleMove(getActor(), x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public L2ShuttleInstance getActor()
|
||||
{
|
||||
return (L2ShuttleInstance) _actor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,884 @@
|
||||
/*
|
||||
* 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.Collection;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.GameTimeController;
|
||||
import com.l2jmobius.gameserver.GeoData;
|
||||
import com.l2jmobius.gameserver.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.L2World;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Attackable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Npc;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Playable;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Summon;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2DefenderInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.effects.L2EffectType;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.util.Util;
|
||||
|
||||
/**
|
||||
* This class manages AI of L2Attackable.
|
||||
*/
|
||||
public class L2SiegeGuardAI extends L2CharacterAI implements Runnable
|
||||
{
|
||||
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();
|
||||
// private TargetAnalysis _mostHatedAnalysis = new TargetAnalysis();
|
||||
|
||||
/** The delay after which the attacked 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;
|
||||
|
||||
public L2SiegeGuardAI(L2Character creature)
|
||||
{
|
||||
super(creature);
|
||||
_selfAnalysis.init();
|
||||
_attackTimeout = Integer.MAX_VALUE;
|
||||
_globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
|
||||
_attackRange = _actor.getPhysicalAttackRange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Launch actions corresponding to the Event Think
|
||||
onEvtThink();
|
||||
}
|
||||
|
||||
/**
|
||||
* <B><U> Actor is a L2GuardInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* <B><U> Actor is a L2SiegeGuardInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* <B><U> Actor is a L2FriendlyMobInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* <B><U> Actor is a L2MonsterInstance</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* @param target The targeted L2Object
|
||||
* @return True if the target is autoattackable (depends on the actor type).
|
||||
*/
|
||||
protected boolean autoAttackCondition(L2Character target)
|
||||
{
|
||||
// Check if the target isn't another guard, folk or a door
|
||||
if ((target == null) || (target instanceof L2DefenderInstance) || target.isNpc() || target.isDoor() || target.isAlikeDead())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the target isn't invulnerable
|
||||
if (target.isInvul())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the owner if the target is a summon
|
||||
if (target.isSummon())
|
||||
{
|
||||
final L2PcInstance owner = ((L2Summon) target).getOwner();
|
||||
if (_actor.isInsideRadius(owner, 1000, true, false))
|
||||
{
|
||||
target = owner;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the target is a L2PcInstance
|
||||
if (target.isPlayable())
|
||||
{
|
||||
// Check if the target isn't in silent move mode AND too far (>100)
|
||||
if (((L2Playable) target).isSilentMovingAffected() && !_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>
|
||||
* <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>
|
||||
* @param intention The new Intention to set to the AI
|
||||
* @param args The first parameter of the Intention
|
||||
*/
|
||||
@Override
|
||||
synchronized void changeIntention(CtrlIntention intention, Object... args)
|
||||
{
|
||||
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 (!L2World.getInstance().getVisibleObjects(npc, L2PcInstance.class).isEmpty())
|
||||
{
|
||||
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);
|
||||
|
||||
// Stop AI task and detach AI from NPC
|
||||
if (_aiTask != null)
|
||||
{
|
||||
_aiTask.cancel(true);
|
||||
_aiTask = null;
|
||||
}
|
||||
|
||||
// Cancel the AI
|
||||
_actor.detachAI();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the Intention of this L2AttackableAI to intention
|
||||
super.changeIntention(intention, args);
|
||||
|
||||
// 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.
|
||||
* @param target The L2Character to attack
|
||||
*/
|
||||
@Override
|
||||
protected void onIntentionAttack(L2Character target)
|
||||
{
|
||||
// Calculate the attack timeout
|
||||
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
*/
|
||||
private void thinkActive()
|
||||
{
|
||||
final L2Attackable npc = (L2Attackable) _actor;
|
||||
final L2Object target = getTarget();
|
||||
// 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)
|
||||
{
|
||||
L2World.getInstance().forEachVisibleObjectInRange(npc, L2Character.class, _attackRange, t ->
|
||||
{
|
||||
if (autoAttackCondition(t)) // check aggression
|
||||
{
|
||||
// Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
|
||||
final int hating = npc.getHating(t);
|
||||
|
||||
// Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
|
||||
if (hating == 0)
|
||||
{
|
||||
npc.addDamageHate(t, 0, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Chose a target from its aggroList
|
||||
L2Character hated;
|
||||
if (_actor.isConfused() && (target != null) && target.isCharacter())
|
||||
{
|
||||
hated = (L2Character) target; // Force mobs to attack anybody if confused
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = npc.getMostHated();
|
||||
// _mostHatedAnalysis.Update(hated);
|
||||
}
|
||||
|
||||
// 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 L2DefenderInstance to return to its home location because there's no target to attack
|
||||
((L2DefenderInstance) _actor).returnHome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage AI attack thinks of a L2Attackable (called by onEvtThink).<br>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>Chose a target and order to attack it with magic skill or physical attack</li>
|
||||
* </ul>
|
||||
* TODO: Manage casting rules to healer mobs (like Ant Nurses)
|
||||
*/
|
||||
private void thinkAttack()
|
||||
{
|
||||
if (_attackTimeout < GameTimeController.getInstance().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.getInstance().getGameTicks();
|
||||
}
|
||||
}
|
||||
|
||||
final L2Object target = getTarget();
|
||||
final L2Character attackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
// Check if target is dead or if timeout is expired to stop this attack
|
||||
if ((attackTarget == null) || attackTarget.isAlikeDead() || (_attackTimeout < GameTimeController.getInstance().getGameTicks()))
|
||||
{
|
||||
// Stop hating this target after the attack timeout or if target is dead
|
||||
if (attackTarget != null)
|
||||
{
|
||||
final L2Attackable npc = (L2Attackable) _actor;
|
||||
npc.stopHating(attackTarget);
|
||||
}
|
||||
|
||||
// Cancel target and timeout
|
||||
_attackTimeout = Integer.MAX_VALUE;
|
||||
setTarget(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 L2Object target = getTarget();
|
||||
// Call all L2Object of its Faction inside the Faction Range
|
||||
if ((((L2Npc) _actor).getTemplate().getClans() == null) || (target == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.isInvul())
|
||||
{
|
||||
return; // speeding it up for siege guards
|
||||
}
|
||||
|
||||
// Go through all L2Character that belong to its faction
|
||||
// for (L2Character cha : _actor.getKnownList().getKnownCharactersInRadius(((L2NpcInstance) _actor).getFactionRange()+_actor.getTemplate().collisionRadius))
|
||||
for (L2Character cha : L2World.getInstance().getVisibleObjects(_actor, L2Character.class, 1000))
|
||||
{
|
||||
if (!cha.isNpc())
|
||||
{
|
||||
if (_selfAnalysis.hasHealOrResurrect && cha.isPlayer() && (((L2Npc) _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 (Skill sk : _selfAnalysis.healSkills)
|
||||
{
|
||||
if (_actor.getCurrentMp() < sk.getMpConsume())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_actor.isSkillDisabled(sk))
|
||||
{
|
||||
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 = getTarget();
|
||||
setTarget(cha);
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final L2Npc npc = (L2Npc) cha;
|
||||
|
||||
if (!npc.isInMyClan((L2Npc) _actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (npc.getAI() != null) // TODO: possibly check not needed
|
||||
{
|
||||
if (!npc.isDead() && (Math.abs(target.getZ() - npc.getZ()) < 600)
|
||||
// && _actor.getAttackByList().contains(getTarget())
|
||||
&& ((npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE) || (npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE))
|
||||
// limiting aggro for siege guards
|
||||
&& npc.isInsideRadius(target, 1500, true, false) && GeoData.getInstance().canSeeTarget(npc, target))
|
||||
{
|
||||
// Notify the L2Object AI with EVT_AGGRESSION
|
||||
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, target, 1);
|
||||
return;
|
||||
}
|
||||
// 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 (Skill sk : _selfAnalysis.healSkills)
|
||||
{
|
||||
if (_actor.getCurrentMp() < sk.getMpConsume())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_actor.isSkillDisabled(sk))
|
||||
{
|
||||
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 = getTarget();
|
||||
setTarget(npc);
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void attackPrepare()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
L2Character attackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
if (attackTarget == null)
|
||||
{
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all information needed to choose between physical or magical attack
|
||||
Collection<Skill> skills = null;
|
||||
double dist_2 = 0;
|
||||
int range = 0;
|
||||
final L2DefenderInstance sGuard = (L2DefenderInstance) _actor;
|
||||
|
||||
try
|
||||
{
|
||||
setTarget(attackTarget);
|
||||
skills = _actor.getAllSkills();
|
||||
dist_2 = _actor.calculateDistance(attackTarget, false, true);
|
||||
range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + attackTarget.getTemplate().getCollisionRadius();
|
||||
if (attackTarget.isMoving())
|
||||
{
|
||||
range += 50;
|
||||
}
|
||||
}
|
||||
catch (NullPointerException e)
|
||||
{
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GeoData.getInstance().canSeeTarget(_actor, attackTarget))
|
||||
{
|
||||
// Siege guards differ from normal mobs currently:
|
||||
// If target cannot seen, don't attack any more
|
||||
sGuard.stopHating(attackTarget);
|
||||
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 (Skill sk : skills)
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if ((dist_2 <= (castRange * castRange)) && (castRange > 70) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !sk.isPassive())
|
||||
{
|
||||
|
||||
final L2Object OldTarget = getTarget();
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HEAL)))
|
||||
{
|
||||
boolean useSkillSelf = true;
|
||||
if ((sk.hasEffectType(L2EffectType.HEAL)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId()))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
}
|
||||
|
||||
if (useSkillSelf)
|
||||
{
|
||||
setTarget(_actor);
|
||||
}
|
||||
}
|
||||
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the L2SiegeGuardInstance is attacking, knows the target and can't run
|
||||
if (!(_actor.isAttackingNow()) && (_actor.getRunSpeed() == 0) && (_actor.isInSurroundingRegion(attackTarget)))
|
||||
{
|
||||
// Cancel the target
|
||||
setTarget(null);
|
||||
setIntention(AI_INTENTION_IDLE, null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
final double dx = _actor.getX() - attackTarget.getX();
|
||||
final double dy = _actor.getY() - attackTarget.getY();
|
||||
final double dz = _actor.getZ() - attackTarget.getZ();
|
||||
final double homeX = attackTarget.getX() - sGuard.getSpawn().getX();
|
||||
final double homeY = attackTarget.getY() - sGuard.getSpawn().getY();
|
||||
|
||||
// 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.isInSurroundingRegion(attackTarget)))
|
||||
{
|
||||
// Cancel the target
|
||||
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.isHealer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_selfAnalysis.isMage)
|
||||
{
|
||||
range = _selfAnalysis.maxCastRange - 50;
|
||||
}
|
||||
if (attackTarget.isMoving())
|
||||
{
|
||||
moveToPawn(attackTarget, range - 70);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToPawn(attackTarget, range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
// Else, if the actor is muted and far from target, just "move to pawn"
|
||||
else if (_actor.isMuted() && (dist_2 > (range * range)) && !_selfAnalysis.isHealer)
|
||||
{
|
||||
// Temporary hack for preventing guards jumping off towers,
|
||||
// before replacing this with effective geodata checks and AI modification
|
||||
final double dz = _actor.getZ() - attackTarget.getZ();
|
||||
if ((dz * dz) < (170 * 170)) // normally 130 if guard z coordinates correct
|
||||
{
|
||||
if (_selfAnalysis.isMage)
|
||||
{
|
||||
range = _selfAnalysis.maxCastRange - 50;
|
||||
}
|
||||
if (attackTarget.isMoving())
|
||||
{
|
||||
moveToPawn(attackTarget, range - 70);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveToPawn(attackTarget, 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 = attackTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = ((L2Attackable) _actor).getMostHated();
|
||||
}
|
||||
|
||||
if (hated == null)
|
||||
{
|
||||
setIntention(AI_INTENTION_ACTIVE, null, null);
|
||||
return;
|
||||
}
|
||||
if (hated != attackTarget)
|
||||
{
|
||||
attackTarget = hated;
|
||||
}
|
||||
|
||||
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().getGameTicks();
|
||||
|
||||
// check for close combat skills && heal/buff skills
|
||||
if (!_actor.isMuted() && (Rnd.nextInt(100) <= 5))
|
||||
{
|
||||
for (Skill sk : skills)
|
||||
{
|
||||
final int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist_2) && !sk.isPassive() && (_actor.getCurrentMp() >= _actor.getStat().getMpConsume(sk)) && !_actor.isSkillDisabled(sk))
|
||||
{
|
||||
final L2Object OldTarget = getTarget();
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) || (sk.hasEffectType(L2EffectType.HEAL)))
|
||||
{
|
||||
boolean useSkillSelf = true;
|
||||
if ((sk.hasEffectType(L2EffectType.HEAL)) && (_actor.getCurrentHp() > (int) (_actor.getMaxHp() / 1.5)))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((sk.isContinuous() && !sk.isDebuff()) && _actor.isAffectedBySkill(sk.getId()))
|
||||
{
|
||||
useSkillSelf = false;
|
||||
}
|
||||
|
||||
if (useSkillSelf)
|
||||
{
|
||||
setTarget(_actor);
|
||||
}
|
||||
}
|
||||
|
||||
_actor.doCast(sk);
|
||||
setTarget(OldTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, do the physical attack itself
|
||||
if (!_selfAnalysis.isHealer)
|
||||
{
|
||||
_actor.doAttack(attackTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage AI thinking actions of a L2Attackable.
|
||||
*/
|
||||
@Override
|
||||
protected void onEvtThink()
|
||||
{
|
||||
// if(getIntention() != AI_INTENTION_IDLE && (!_actor.isVisible() || !_actor.hasAI() || !_actor.isKnownPlayers()))
|
||||
// setIntention(AI_INTENTION_IDLE);
|
||||
|
||||
// Check if the thinking action is already in progress
|
||||
if (_thinking || _actor.isCastingNow() || _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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* @param attacker The L2Character that attacks the actor
|
||||
*/
|
||||
@Override
|
||||
protected void onEvtAttacked(L2Character attacker)
|
||||
{
|
||||
// Calculate the attack timeout
|
||||
_attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getInstance().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>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <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>
|
||||
* </ul>
|
||||
* @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 L2DefenderInstance sGuard = (L2DefenderInstance) _actor;
|
||||
final double homeX = target.getX() - sGuard.getSpawn().getX();
|
||||
final double homeY = target.getY() - sGuard.getSpawn().getY();
|
||||
|
||||
// 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 (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
|
||||
public void stopAITask()
|
||||
{
|
||||
if (_aiTask != null)
|
||||
{
|
||||
_aiTask.cancel(false);
|
||||
_aiTask = null;
|
||||
}
|
||||
_actor.detachAI();
|
||||
super.stopAITask();
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
|
||||
/**
|
||||
* @author BiggBoss
|
||||
*/
|
||||
public final class L2SpecialSiegeGuardAI extends L2SiegeGuardAI
|
||||
{
|
||||
private final List<Integer> _allied = new ArrayList<>();
|
||||
|
||||
public L2SpecialSiegeGuardAI(L2Character creature)
|
||||
{
|
||||
super(creature);
|
||||
}
|
||||
|
||||
public List<Integer> getAlly()
|
||||
{
|
||||
return _allied;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean autoAttackCondition(L2Character target)
|
||||
{
|
||||
if (_allied.contains(target.getObjectId()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.autoAttackCondition(target);
|
||||
}
|
||||
}
|
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* 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 com.l2jmobius.commons.util.Rnd;
|
||||
import com.l2jmobius.gameserver.GeoData;
|
||||
import com.l2jmobius.gameserver.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Summon;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.model.skills.SkillCaster;
|
||||
|
||||
public class L2SummonAI extends L2PlayableAI implements Runnable
|
||||
{
|
||||
private static final int AVOID_RADIUS = 70;
|
||||
|
||||
private volatile boolean _thinking; // to prevent recursive thinking
|
||||
private volatile boolean _startFollow = ((L2Summon) _actor).getFollowStatus();
|
||||
private L2Character _lastAttack = null;
|
||||
|
||||
private volatile boolean _startAvoid;
|
||||
private volatile boolean _isDefending;
|
||||
private Future<?> _avoidTask = null;
|
||||
|
||||
public L2SummonAI(L2Summon summon)
|
||||
{
|
||||
super(summon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionIdle()
|
||||
{
|
||||
stopFollow();
|
||||
_startFollow = false;
|
||||
onIntentionActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionActive()
|
||||
{
|
||||
final L2Summon summon = (L2Summon) _actor;
|
||||
if (_startFollow)
|
||||
{
|
||||
setIntention(AI_INTENTION_FOLLOW, summon.getOwner());
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onIntentionActive();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized void changeIntention(CtrlIntention intention, Object... args)
|
||||
{
|
||||
switch (intention)
|
||||
{
|
||||
case AI_INTENTION_ACTIVE:
|
||||
case AI_INTENTION_FOLLOW:
|
||||
{
|
||||
startAvoidTask();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
stopAvoidTask();
|
||||
}
|
||||
}
|
||||
|
||||
super.changeIntention(intention, args);
|
||||
}
|
||||
|
||||
private void thinkAttack()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
final L2Character attackTarget = (target != null) && target.isCharacter() ? (L2Character) target : null;
|
||||
|
||||
if (checkTargetLostOrDead(attackTarget))
|
||||
{
|
||||
setTarget(null);
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(attackTarget, _actor.getPhysicalAttackRange()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
clientStopMoving(null);
|
||||
_actor.doAttack(attackTarget);
|
||||
}
|
||||
|
||||
private void thinkCast()
|
||||
{
|
||||
final L2Summon summon = (L2Summon) _actor;
|
||||
if (summon.isCastingNow(SkillCaster::isAnyNormalType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final L2Object target = _skill.getTarget(_actor, _forceUse, _dontMove, false);
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
setTarget(null);
|
||||
return;
|
||||
}
|
||||
final boolean val = _startFollow;
|
||||
if (maybeMoveToPawn(target, _actor.getMagicalAttackRange(_skill)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
summon.setFollowStatus(false);
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
_startFollow = val;
|
||||
_actor.doCast(_skill, _item, _forceUse, _dontMove);
|
||||
}
|
||||
|
||||
private void thinkPickUp()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, 36))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
getActor().doPickupItem(target);
|
||||
}
|
||||
|
||||
private void thinkInteract()
|
||||
{
|
||||
final L2Object target = getTarget();
|
||||
if (checkTargetLost(target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (maybeMoveToPawn(target, 36))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setIntention(AI_INTENTION_IDLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtThink()
|
||||
{
|
||||
if (_thinking || _actor.isCastingNow() || _actor.isAllSkillsDisabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_thinking = true;
|
||||
try
|
||||
{
|
||||
switch (getIntention())
|
||||
{
|
||||
case AI_INTENTION_ATTACK:
|
||||
{
|
||||
thinkAttack();
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_CAST:
|
||||
{
|
||||
thinkCast();
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_PICK_UP:
|
||||
{
|
||||
thinkPickUp();
|
||||
break;
|
||||
}
|
||||
case AI_INTENTION_INTERACT:
|
||||
{
|
||||
thinkInteract();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_thinking = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtFinishCasting()
|
||||
{
|
||||
if (_lastAttack == null)
|
||||
{
|
||||
((L2Summon) _actor).setFollowStatus(_startFollow);
|
||||
}
|
||||
else
|
||||
{
|
||||
setIntention(CtrlIntention.AI_INTENTION_ATTACK, _lastAttack);
|
||||
_lastAttack = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtAttacked(L2Character attacker)
|
||||
{
|
||||
super.onEvtAttacked(attacker);
|
||||
|
||||
if (isDefending())
|
||||
{
|
||||
defendAttack(attacker);
|
||||
}
|
||||
else
|
||||
{
|
||||
avoidAttack(attacker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEvtEvaded(L2Character attacker)
|
||||
{
|
||||
super.onEvtEvaded(attacker);
|
||||
|
||||
if (isDefending())
|
||||
{
|
||||
defendAttack(attacker);
|
||||
}
|
||||
else
|
||||
{
|
||||
avoidAttack(attacker);
|
||||
}
|
||||
}
|
||||
|
||||
private void avoidAttack(L2Character attacker)
|
||||
{
|
||||
// Don't move while casting. It breaks casting animation, but still casts the skill... looks so bugged.
|
||||
if (_actor.isCastingNow())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final L2Character owner = getActor().getOwner();
|
||||
// trying to avoid if summon near owner
|
||||
if ((owner != null) && (owner != attacker) && owner.isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false))
|
||||
{
|
||||
_startAvoid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void defendAttack(L2Character attacker)
|
||||
{
|
||||
// Cannot defend while attacking or casting.
|
||||
if (_actor.isAttackingNow() || _actor.isCastingNow())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final L2Summon summon = getActor();
|
||||
if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false))
|
||||
{
|
||||
summon.doAttack(attacker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (_startAvoid)
|
||||
{
|
||||
_startAvoid = false;
|
||||
|
||||
if (!_clientMoving && !_actor.isDead() && !_actor.isMovementDisabled() && (_actor.getMoveSpeed() > 0))
|
||||
{
|
||||
final int ownerX = ((L2Summon) _actor).getOwner().getX();
|
||||
final int ownerY = ((L2Summon) _actor).getOwner().getY();
|
||||
final double angle = Math.toRadians(Rnd.get(-90, 90)) + Math.atan2(ownerY - _actor.getY(), ownerX - _actor.getX());
|
||||
|
||||
final int targetX = ownerX + (int) (AVOID_RADIUS * Math.cos(angle));
|
||||
final int targetY = ownerY + (int) (AVOID_RADIUS * Math.sin(angle));
|
||||
if (GeoData.getInstance().canMove(_actor, targetX, targetY, _actor.getZ()))
|
||||
{
|
||||
moveTo(targetX, targetY, _actor.getZ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyFollowStatusChange()
|
||||
{
|
||||
_startFollow = !_startFollow;
|
||||
switch (getIntention())
|
||||
{
|
||||
case AI_INTENTION_ACTIVE:
|
||||
case AI_INTENTION_FOLLOW:
|
||||
case AI_INTENTION_IDLE:
|
||||
case AI_INTENTION_MOVE_TO:
|
||||
case AI_INTENTION_PICK_UP:
|
||||
{
|
||||
((L2Summon) _actor).setFollowStatus(_startFollow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setStartFollowController(boolean val)
|
||||
{
|
||||
_startFollow = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionCast(Skill skill, L2Object target, L2ItemInstance item, boolean forceUse, boolean dontMove)
|
||||
{
|
||||
if (getIntention() == AI_INTENTION_ATTACK)
|
||||
{
|
||||
_lastAttack = (getTarget() != null) && getTarget().isCharacter() ? (L2Character) getTarget() : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastAttack = null;
|
||||
}
|
||||
super.onIntentionCast(skill, target, item, forceUse, dontMove);
|
||||
}
|
||||
|
||||
private void startAvoidTask()
|
||||
{
|
||||
if (_avoidTask == null)
|
||||
{
|
||||
_avoidTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 100, 100);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAvoidTask()
|
||||
{
|
||||
if (_avoidTask != null)
|
||||
{
|
||||
_avoidTask.cancel(false);
|
||||
_avoidTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAITask()
|
||||
{
|
||||
stopAvoidTask();
|
||||
super.stopAITask();
|
||||
}
|
||||
|
||||
@Override
|
||||
public L2Summon getActor()
|
||||
{
|
||||
return (L2Summon) super.getActor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the summon is defending itself or master.
|
||||
*/
|
||||
public boolean isDefending()
|
||||
{
|
||||
return _isDefending;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isDefending set the summon to defend itself and master, or be passive and avoid while being attacked.
|
||||
*/
|
||||
public void setDefending(boolean isDefending)
|
||||
{
|
||||
_isDefending = isDefending;
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.L2Object;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Character;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Vehicle;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
|
||||
/**
|
||||
* @author DS
|
||||
*/
|
||||
public abstract class L2VehicleAI extends L2CharacterAI
|
||||
{
|
||||
/**
|
||||
* Simple AI for vehicles
|
||||
* @param vehicle
|
||||
*/
|
||||
public L2VehicleAI(L2Vehicle vehicle)
|
||||
{
|
||||
super(vehicle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionAttack(L2Character target)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onIntentionCast(Skill skill, L2Object target, L2ItemInstance item, boolean forceUse, boolean dontMove)
|
||||
{
|
||||
}
|
||||
|
||||
@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 onEvtActionBlocked(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 clientStoppedMoving()
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class for AI action after some event.<br>
|
||||
* Has 2 array list for "work" and "break".
|
||||
* @author Yaroslav
|
||||
*/
|
||||
public class NextAction
|
||||
{
|
||||
public interface NextActionCallback
|
||||
{
|
||||
void doWork();
|
||||
}
|
||||
|
||||
private List<CtrlEvent> _events;
|
||||
private List<CtrlIntention> _intentions;
|
||||
private NextActionCallback _callback;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
* @param events
|
||||
* @param intentions
|
||||
* @param callback
|
||||
*/
|
||||
public NextAction(List<CtrlEvent> events, List<CtrlIntention> intentions, NextActionCallback callback)
|
||||
{
|
||||
_events = events;
|
||||
_intentions = intentions;
|
||||
setCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Single constructor.
|
||||
* @param event
|
||||
* @param intention
|
||||
* @param callback
|
||||
*/
|
||||
public NextAction(CtrlEvent event, CtrlIntention intention, NextActionCallback callback)
|
||||
{
|
||||
if (_events == null)
|
||||
{
|
||||
_events = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (_intentions == null)
|
||||
{
|
||||
_intentions = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (event != null)
|
||||
{
|
||||
_events.add(event);
|
||||
}
|
||||
|
||||
if (intention != null)
|
||||
{
|
||||
_intentions.add(intention);
|
||||
}
|
||||
setCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do action.
|
||||
*/
|
||||
public void doAction()
|
||||
{
|
||||
if (_callback != null)
|
||||
{
|
||||
_callback.doWork();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the _event
|
||||
*/
|
||||
public List<CtrlEvent> getEvents()
|
||||
{
|
||||
// If null return empty list.
|
||||
if (_events == null)
|
||||
{
|
||||
_events = new ArrayList<>();
|
||||
}
|
||||
return _events;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event the event to set.
|
||||
*/
|
||||
public void setEvents(ArrayList<CtrlEvent> event)
|
||||
{
|
||||
_events = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event
|
||||
*/
|
||||
public void addEvent(CtrlEvent event)
|
||||
{
|
||||
if (_events == null)
|
||||
{
|
||||
_events = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (event != null)
|
||||
{
|
||||
_events.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event
|
||||
*/
|
||||
public void removeEvent(CtrlEvent event)
|
||||
{
|
||||
if (_events == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_events.remove(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the _callback
|
||||
*/
|
||||
public NextActionCallback getCallback()
|
||||
{
|
||||
return _callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callback the callback to set.
|
||||
*/
|
||||
public void setCallback(NextActionCallback callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the _intentions
|
||||
*/
|
||||
public List<CtrlIntention> getIntentions()
|
||||
{
|
||||
// If null return empty list.
|
||||
if (_intentions == null)
|
||||
{
|
||||
_intentions = new ArrayList<>();
|
||||
}
|
||||
return _intentions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intentions the intention to set.
|
||||
*/
|
||||
public void setIntentions(ArrayList<CtrlIntention> intentions)
|
||||
{
|
||||
_intentions = intentions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intention
|
||||
*/
|
||||
public void addIntention(CtrlIntention intention)
|
||||
{
|
||||
if (_intentions == null)
|
||||
{
|
||||
_intentions = new ArrayList<>();
|
||||
}
|
||||
|
||||
if (intention != null)
|
||||
{
|
||||
_intentions.add(intention);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param intention
|
||||
*/
|
||||
public void removeIntention(CtrlIntention intention)
|
||||
{
|
||||
if (_intentions == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_intentions.remove(intention);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user