This commit is contained in:
583
trunk/java/com/l2jserver/gameserver/ai/L2ControllableMobAI.java
Normal file
583
trunk/java/com/l2jserver/gameserver/ai/L2ControllableMobAI.java
Normal file
@ -0,0 +1,583 @@
|
||||
/*
|
||||
* Copyright (C) 2004-2014 L2J Server
|
||||
*
|
||||
* This file is part of L2J Server.
|
||||
*
|
||||
* L2J Server 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.
|
||||
*
|
||||
* L2J Server 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.l2jserver.gameserver.ai;
|
||||
|
||||
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
|
||||
import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javolution.util.FastList;
|
||||
|
||||
import com.l2jserver.gameserver.model.L2Object;
|
||||
import com.l2jserver.gameserver.model.MobGroup;
|
||||
import com.l2jserver.gameserver.model.MobGroupTable;
|
||||
import com.l2jserver.gameserver.model.actor.L2Attackable;
|
||||
import com.l2jserver.gameserver.model.actor.L2Character;
|
||||
import com.l2jserver.gameserver.model.actor.L2Character.AIAccessor;
|
||||
import com.l2jserver.gameserver.model.actor.L2Npc;
|
||||
import com.l2jserver.gameserver.model.actor.L2Playable;
|
||||
import com.l2jserver.gameserver.model.actor.instance.L2ControllableMobInstance;
|
||||
import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
|
||||
import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance;
|
||||
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jserver.gameserver.model.skills.Skill;
|
||||
import com.l2jserver.gameserver.util.Util;
|
||||
import com.l2jserver.util.Rnd;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
L2Attackable me = (L2Attackable) _actor;
|
||||
|
||||
if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, me, getForcedTarget(), true))
|
||||
{
|
||||
int signX = (Rnd.nextInt(2) == 0) ? -1 : 1;
|
||||
int signY = (Rnd.nextInt(2) == 0) ? -1 : 1;
|
||||
int randX = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
|
||||
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()
|
||||
{
|
||||
L2Attackable npc = (L2Attackable) _actor;
|
||||
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
|
||||
{
|
||||
setAttackTarget(findNextRndTarget());
|
||||
clientStopMoving(null);
|
||||
}
|
||||
|
||||
if (getAttackTarget() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
npc.setTarget(getAttackTarget());
|
||||
|
||||
if (!_actor.isMuted())
|
||||
{
|
||||
int max_range = 0;
|
||||
// check distant skills
|
||||
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
if (Util.checkIfInRange(sk.getCastRange(), _actor, getAttackTarget(), true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_accessor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, sk.getCastRange());
|
||||
}
|
||||
|
||||
if (!isNotMoving())
|
||||
{
|
||||
moveToPawn(getAttackTarget(), max_range);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected void thinkAttackGroup()
|
||||
{
|
||||
L2Character target = getForcedTarget();
|
||||
if ((target == null) || target.isAlikeDead())
|
||||
{
|
||||
// try to get next group target
|
||||
setForcedTarget(findNextGroupTarget());
|
||||
clientStopMoving(null);
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_actor.setTarget(target);
|
||||
// as a response, we put the target in a forcedattack mode
|
||||
L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target;
|
||||
L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI();
|
||||
ctrlAi.forceAttack(_actor);
|
||||
|
||||
double dist2 = _actor.calculateDistance(target, false, true);
|
||||
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())
|
||||
{
|
||||
int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_accessor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, castRange);
|
||||
}
|
||||
|
||||
if (!isNotMoving())
|
||||
{
|
||||
moveToPawn(target, range);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
_accessor.doAttack(target);
|
||||
}
|
||||
|
||||
protected void thinkForceAttack()
|
||||
{
|
||||
if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
|
||||
{
|
||||
clientStopMoving(null);
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
setAlternateAI(AI_IDLE);
|
||||
}
|
||||
|
||||
_actor.setTarget(getForcedTarget());
|
||||
double dist2 = _actor.calculateDistance(getForcedTarget(), false, true);
|
||||
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())
|
||||
{
|
||||
int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_accessor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, castRange);
|
||||
}
|
||||
|
||||
if (!isNotMoving())
|
||||
{
|
||||
moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_accessor.doAttack(getForcedTarget());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkAttack()
|
||||
{
|
||||
if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
|
||||
{
|
||||
if (getAttackTarget() != null)
|
||||
{
|
||||
// stop hating
|
||||
L2Attackable npc = (L2Attackable) _actor;
|
||||
npc.stopHating(getAttackTarget());
|
||||
}
|
||||
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// notify aggression
|
||||
if (((L2Npc) _actor).getTemplate().getClans() != null)
|
||||
{
|
||||
Collection<L2Object> objs = _actor.getKnownList().getKnownObjects().values();
|
||||
for (L2Object obj : objs)
|
||||
{
|
||||
if (!(obj instanceof L2Npc))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
L2Npc npc = (L2Npc) obj;
|
||||
|
||||
if (!npc.isInMyClan((L2Npc) _actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_actor.isInsideRadius(npc, npc.getTemplate().getClanHelpRange(), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200))
|
||||
{
|
||||
npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_actor.setTarget(getAttackTarget());
|
||||
double dist2 = _actor.calculateDistance(getAttackTarget(), false, true);
|
||||
int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius();
|
||||
int max_range = range;
|
||||
|
||||
if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
|
||||
{
|
||||
// check distant skills
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_accessor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
|
||||
max_range = Math.max(max_range, castRange);
|
||||
}
|
||||
|
||||
moveToPawn(getAttackTarget(), range);
|
||||
return;
|
||||
}
|
||||
|
||||
// Force mobs to attack anybody if confused.
|
||||
L2Character hated;
|
||||
|
||||
if (_actor.isConfused())
|
||||
{
|
||||
hated = findNextRndTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = getAttackTarget();
|
||||
}
|
||||
|
||||
if (hated == null)
|
||||
{
|
||||
setIntention(AI_INTENTION_ACTIVE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hated != getAttackTarget())
|
||||
{
|
||||
setAttackTarget(hated);
|
||||
}
|
||||
|
||||
if (!_actor.isMuted() && (Rnd.nextInt(5) == 3))
|
||||
{
|
||||
for (Skill sk : _actor.getAllSkills())
|
||||
{
|
||||
int castRange = sk.getCastRange();
|
||||
|
||||
if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)))
|
||||
{
|
||||
_accessor.doCast(sk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_accessor.doAttack(getAttackTarget());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void thinkActive()
|
||||
{
|
||||
setAttackTarget(findNextRndTarget());
|
||||
L2Character hated;
|
||||
|
||||
if (_actor.isConfused())
|
||||
{
|
||||
hated = findNextRndTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
hated = getAttackTarget();
|
||||
}
|
||||
|
||||
if (hated != null)
|
||||
{
|
||||
_actor.setRunning();
|
||||
setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkAutoAttackCondition(L2Character target)
|
||||
{
|
||||
if ((target == null) || !(_actor instanceof L2Attackable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
L2Attackable me = (L2Attackable) _actor;
|
||||
|
||||
if ((target instanceof L2NpcInstance) || (target instanceof L2DoorInstance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.isAlikeDead() || !me.isInsideRadius(target, me.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the target isn't invulnerable
|
||||
if (target.isInvul())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spawn protection (only against mobs)
|
||||
if ((target instanceof L2PcInstance) && ((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 instanceof L2Npc)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return me.isAggressive();
|
||||
}
|
||||
|
||||
private L2Character findNextRndTarget()
|
||||
{
|
||||
int aggroRange = ((L2Attackable) _actor).getAggroRange();
|
||||
L2Attackable npc = (L2Attackable) _actor;
|
||||
int npcX, npcY, targetX, targetY;
|
||||
double dy, dx;
|
||||
double dblAggroRange = aggroRange * aggroRange;
|
||||
|
||||
List<L2Character> potentialTarget = new FastList<>();
|
||||
|
||||
Collection<L2Object> objs = npc.getKnownList().getKnownObjects().values();
|
||||
for (L2Object obj : objs)
|
||||
{
|
||||
if (!(obj instanceof L2Character))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
npcX = npc.getX();
|
||||
npcY = npc.getY();
|
||||
targetX = obj.getX();
|
||||
targetY = obj.getY();
|
||||
|
||||
dx = npcX - targetX;
|
||||
dy = npcY - targetY;
|
||||
|
||||
if (((dx * dx) + (dy * dy)) > dblAggroRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
L2Character target = (L2Character) obj;
|
||||
|
||||
if (checkAutoAttackCondition(target))
|
||||
{
|
||||
potentialTarget.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialTarget.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// we choose a random target
|
||||
int choice = Rnd.nextInt(potentialTarget.size());
|
||||
L2Character target = potentialTarget.get(choice);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private L2ControllableMobInstance findNextGroupTarget()
|
||||
{
|
||||
return getGroupTarget().getRandomMob();
|
||||
}
|
||||
|
||||
public L2ControllableMobAI(AIAccessor accessor)
|
||||
{
|
||||
super(accessor);
|
||||
setAlternateAI(AI_IDLE);
|
||||
}
|
||||
|
||||
public int getAlternateAI()
|
||||
{
|
||||
return _alternateAI;
|
||||
}
|
||||
|
||||
public void setAlternateAI(int _alternateai)
|
||||
{
|
||||
_alternateAI = _alternateai;
|
||||
}
|
||||
|
||||
public void forceAttack(L2Character target)
|
||||
{
|
||||
setAlternateAI(AI_FORCEATTACK);
|
||||
setForcedTarget(target);
|
||||
}
|
||||
|
||||
public void forceAttackGroup(MobGroup group)
|
||||
{
|
||||
setForcedTarget(null);
|
||||
setGroupTarget(group);
|
||||
setAlternateAI(AI_ATTACK_GROUP);
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
setAlternateAI(AI_IDLE);
|
||||
clientStopMoving(null);
|
||||
}
|
||||
|
||||
public void move(int x, int y, int z)
|
||||
{
|
||||
moveTo(x, y, z);
|
||||
}
|
||||
|
||||
public void follow(L2Character target)
|
||||
{
|
||||
setAlternateAI(AI_FOLLOW);
|
||||
setForcedTarget(target);
|
||||
}
|
||||
|
||||
public boolean isThinking()
|
||||
{
|
||||
return _isThinking;
|
||||
}
|
||||
|
||||
public boolean isNotMoving()
|
||||
{
|
||||
return _isNotMoving;
|
||||
}
|
||||
|
||||
public void setNotMoving(boolean isNotMoving)
|
||||
{
|
||||
_isNotMoving = isNotMoving;
|
||||
}
|
||||
|
||||
public void setThinking(boolean isThinking)
|
||||
{
|
||||
_isThinking = isThinking;
|
||||
}
|
||||
|
||||
private L2Character getForcedTarget()
|
||||
{
|
||||
return _forcedTarget;
|
||||
}
|
||||
|
||||
private MobGroup getGroupTarget()
|
||||
{
|
||||
return _targetGroup;
|
||||
}
|
||||
|
||||
private void setForcedTarget(L2Character forcedTarget)
|
||||
{
|
||||
_forcedTarget = forcedTarget;
|
||||
}
|
||||
|
||||
private void setGroupTarget(MobGroup targetGroup)
|
||||
{
|
||||
_targetGroup = targetGroup;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user