/* * 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 . */ 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 java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastList; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.L2Skill; 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.util.Util; import com.l2jserver.util.Rnd; /** * AI for controllable mobs * @author littlecrow */ public class L2ControllableMobAI extends L2AttackableAI { private static final Logger _log = Logger.getLogger(L2AttackableAI.class.getName()); 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); } } protected void thinkCast() { L2Attackable npc = (L2Attackable) _actor; if (getAttackTarget() == null || getAttackTarget().isAlikeDead()) { setAttackTarget(findNextRndTarget()); clientStopMoving(null); } if (getAttackTarget() == null) return; npc.setTarget(getAttackTarget()); L2Skill[] skills = null; //double dist2 = 0; try { skills = _actor.getAllSkills(); // dist2 = _actor.getPlanDistanceSq(getAttackTarget().getX(), getAttackTarget().getY()); } catch (NullPointerException e) { _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e); } if (!_actor.isMuted()) { int max_range = 0; // check distant skills for (L2Skill sk : skills) { 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; L2Skill[] skills = null; double dist2 = 0; int range = 0; int max_range = 0; _actor.setTarget(target); // as a response, we put the target in a forcedattack mode L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target; L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI(); ctrlAi.forceAttack(_actor); try { skills = _actor.getAllSkills(); dist2 = _actor.getPlanDistanceSq(target.getX(), target.getY()); range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius(); max_range = range; } catch (NullPointerException e) { _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e); } if (!_actor.isMuted() && dist2 > (range + 20) * (range + 20)) { // check distant skills for (L2Skill sk : skills) { 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); } L2Skill[] skills = null; double dist2 = 0; int range = 0; int max_range = 0; try { _actor.setTarget(getForcedTarget()); skills = _actor.getAllSkills(); dist2 = _actor.getPlanDistanceSq(getForcedTarget().getX(), getForcedTarget().getY()); range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius(); max_range = range; } catch (NullPointerException e) { _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e); } if (!_actor.isMuted() && dist2 > (range + 20) * (range + 20)) { // check distant skills for (L2Skill sk : skills) { 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()); } 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).getFactionId() != null) { String faction_id = ((L2Npc) _actor).getFactionId(); Collection objs = _actor.getKnownList().getKnownObjects().values(); for (L2Object obj : objs) { if (!(obj instanceof L2Npc)) continue; L2Npc npc = (L2Npc) obj; if (!faction_id.equals(npc.getFactionId())) continue; if (_actor.isInsideRadius(npc, npc.getFactionRange(), false, true) && Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200) { npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1); } } } L2Skill[] skills = null; double dist2 = 0; int range = 0; int max_range = 0; try { _actor.setTarget(getAttackTarget()); skills = _actor.getAllSkills(); dist2 = _actor.getPlanDistanceSq(getAttackTarget().getX(), getAttackTarget().getY()); range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius(); max_range = range; } catch (NullPointerException e) { _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e); } if (!_actor.isMuted() && dist2 > (range + 20) * (range + 20)) { // check distant skills for (L2Skill sk : skills) { 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() && skills.length > 0 && Rnd.nextInt(5) == 3) { for (L2Skill sk : skills) { int castRange = sk.getCastRange(); if (castRange * castRange >= dist2 && !_actor.isSkillDisabled(sk) && _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)) { _accessor.doCast(sk); return; } } } _accessor.doAttack(getAttackTarget()); } } private void thinkActive() { setAttackTarget(findNextRndTarget()); L2Character hated; if (_actor.isConfused()) hated = findNextRndTarget(); else hated = getAttackTarget(); if (hated != null) { _actor.setRunning(); setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated); } } private boolean autoAttackCondition(L2Character target) { if (target == null || !(_actor instanceof L2Attackable)) return false; 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 L2PlayableInstance if (target instanceof L2Playable) { // Check if the target isn't in silent move mode if (((L2Playable) target).isSilentMoving()) 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 potentialTarget = new FastList(); Collection 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 (autoAttackCondition(target)) // check aggression potentialTarget.add(target); } if (potentialTarget.isEmpty()) // nothing to do 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; } }