Partial move siege guard AI to datapack.

This commit is contained in:
MobiusDev
2017-11-11 21:31:02 +00:00
parent 223a342f3a
commit f9afb225c7
35 changed files with 915 additions and 9460 deletions

View File

@@ -0,0 +1,147 @@
/*
* 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 ai.others;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Castle;
import com.l2jmobius.gameserver.model.entity.Fort;
import ai.AbstractNpcAI;
/**
* @author Mobius
*/
public class SiegeGuards extends AbstractNpcAI
{
//@formatter:off
// NPCs
private static final int[] CASTLE_GUARDS =
{
35064, 35065, 35066, 35067, 35068, 35069, 35071, 35072, 35079, 35080, 35081, 35082, 35083, 35084, 35085, // Gludio
35106, 35107, 35108, 35109, 35110, 35111, 35113, 35114, 35121, 35122, 35123,35124, 35125, 35126, 35127, // Dion
35150, 35151, 35152, 35153, 35155, 35156, 35163, 35164, 35165, 35166, 35167, 35168, 35169, // Giran
35192, 35193, 35194, 35195, 35197, 35198, 35205, 35206, 35207, 35208, 35209, 35210, 35211, // Oren
35234, 35239, 35240, 35248, 35249, 35250, 35251, 35252, 35253, 35254, // Aden
35280, 35281, 35282, 35283, 35284, 35285, 35287, 35288, 35295, 35296, 35297, 35298, 35299, 35300, 35301, // Innadril
35324, 35325, 35326, 35327, 35328, 35330, 35339, 35340, 35341, 35343, 35350, 35351, // Goddard
35475, 35477, 35480, 35484, 35486, 35487, 35488, 35489, 35490, // Rune
35516, 35517, 35518, 35519, 35520, 35522, 35531, 35532, 35533, 35535, 35542, 35543, // Schuttgart
};
private static final int[] FORT_GUARDS =
{
35670, 35671, 35672, 35673, 35674, 35678, 35679, 35681, 35682, 35684, 35685, // Shanty
35702, 35703, 35704, 35705, 35706, 35707, 35708, 35709, 35710, 35711, 35712, 35714, 35715, 35717, 35718, 35720, 35722, 35723, // Southern
35739, 35740, 35741, 35742, 35743, 35747, 35748, 35750, 35751, 35753, 35754, // Hive
35758, 35767, 35771, 35772, 35773, 35774, 35775, 35776, 35777, 35778, 35779, 35780, 35781, 35783, 35784, 35786, 35787, 35789, 35791, 35792, // Valley
35808, 35809, 35810, 35811, 35812, 35816, 35817, 35819, 35820, 35822, 35823, // Ivory
35839, 35840, 35841, 35842, 35843, 35847, 35848, 35850, 35851, 35853, 35854, // Narsell
35868, 35869, 35871, 35872, 35873, 35874, 35875, 35876, 35877, 35878, 35879, 35880, 35881, 35883, 35884, 35886, 35887, 35889, 35891, 35892, // Bayou
35908, 35909, 35910, 35911, 35912, 35916, 35917, 35919, 35920, 35922, 35923, // White Sands
35940, 35941, 35942, 35943, 35944, 35945, 35946, 35947, 35948, 35949, 35950, 35952, 35953, 35955, 35956, 35958, 35960, 35961, // Borderland
35978, 35979, 35980, 35981, 35982, 35983, 35984, 35985, 35986, 35987, 35988, 35990, 35991, 35993, 35994, 35996, 35998, 35999, // Swamp
36015, 36016, 36017, 36018, 36019, 36023, 36024, 36026, 36027, 36029, 36030, // Archaic
36047, 36048, 36049, 36050, 36051, 36052, 36053, 36054, 36055, 36056, 36057, 36059, 36060, 36062, 36063, 36065, 36067, 36068, 36468, // Floran
36079, 36085, 36086, 36087, 36088, 36089, 36090, 36091, 36092, 36093, 36094, 36095, 36097, 36098, 36100, 36101, 36103, 36105, 36106, // Removed on Helios (113)
36122, 36123, 36124, 36125, 36126, 36130, 36131, 36133, 36134, 36136, 36137, // Tanor
36153, 36154, 36155, 36156, 36157, 36161, 36162, 36164, 36165, 36167, 36168, // Dragonspine
36185, 36186, 36187, 36188, 36189, 36190, 36191, 36192, 36193, 36194, 36195, 36197, 36198, 36200, 36201, 36203, 36205, 36206, // Antharas
36223, 36224, 36225, 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, 36235, 36236, 36238, 36239, 36241, 36243, 36244, // Western
36261, 36262, 36263, 36264, 36265, 36266, 36267, 36268, 36269, 36270, 36271, 36273, 36274, 36276, 36277, 36279, 36281, 36282, // Hunters
36298, 36299, 36300, 36301, 36302, 36306, 36307, 36309, 36310, 36312, 36313, // Aaru
36330, 36331, 36332, 36333, 36334, 36342, 36343, 36345, 36346, 36348, 36351, // Demon
36368, 36369, 36370, 36371, 36372, 36380, 36381, 36383, 36384, 36386, 36389 // Monastic
};
private static final int[] MERCENARIES =
{
35015, 35016, 35017, 35018, 35019, 35025, 35026, 35027, 35028, 35029, 35035, 35036, 35037, 35038, 35039, 35045, 35046, 35047, 35048, 35049, 35055, 35056, 35057, 35058, 35059, 35060, 35061
};
private static final int[] STATIONARY_MERCENARIES =
{
35010, 35011, 35012, 35013, 35014, 35020, 35021, 35022, 35023, 35024, 35030, 35031, 35032, 35033, 35034, 35040, 35041, 35042, 35043, 35044, 35050, 35051, 35052, 35053, 35054, 35092, 35093, 35094,
35134, 35135, 35136, 35176, 35177, 35178, 35218, 35219, 35220, 35261, 35262, 35263, 35264, 35265, 35308, 35309, 35310, 35352, 35353, 35354, 35497, 35498, 35499, 35500, 35501, 35544, 35545, 35546
};
//@formatter:on
public SiegeGuards()
{
addAttackId(CASTLE_GUARDS);
addAttackId(FORT_GUARDS);
addAttackId(MERCENARIES);
addAttackId(STATIONARY_MERCENARIES);
addSeeCreatureId(CASTLE_GUARDS);
addSeeCreatureId(FORT_GUARDS);
addSeeCreatureId(MERCENARIES);
addSeeCreatureId(STATIONARY_MERCENARIES);
addSpawnId(CASTLE_GUARDS);
addSpawnId(FORT_GUARDS);
addSpawnId(MERCENARIES);
addSpawnId(STATIONARY_MERCENARIES);
}
@Override
public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon)
{
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((attacker.getSiegeState() == 2) && !attacker.isRegisteredOnThisSiegeField(activeSiegeId)) || (attacker.getSiegeState() == 0)) && (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
{
((L2Attackable) npc).stopHating(attacker);
return null;
}
return super.onAttack(npc, attacker, damage, isSummon);
}
@Override
public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon)
{
if (creature.isPlayable() && (npc.calculateDirectionTo(creature) < npc.getAggroRange()) && GeoEngine.getInstance().canSeeTarget(npc, creature))
{
final L2Summon summon = isSummon ? creature.getServitors().values().stream().findFirst().orElse(creature.getPet()) : null;
final L2PcInstance cha = summon == null ? (L2PcInstance) creature : summon.getOwner();
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((cha.getSiegeState() != 2) || cha.isRegisteredOnThisSiegeField(activeSiegeId)) && (cha.getSiegeState() != 0)) || (npc.getAI().getIntention() != CtrlIntention.AI_INTENTION_IDLE))
{
if (!cha.isInvisible()) // skip invisible players
{
addAttackPlayerDesire(npc, cha);
}
}
}
return super.onSeeCreature(npc, creature, isSummon);
}
@Override
public String onSpawn(L2Npc npc)
{
npc.setRandomWalking(false);
return super.onSpawn(npc);
}
public static void main(String[] args)
{
new SiegeGuards();
}
}

View File

@@ -42,6 +42,7 @@ 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.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GuardInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
@@ -444,8 +445,8 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
return;
}
// Check if the actor is a L2GuardInstance
if ((npc instanceof L2GuardInstance) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
// Check if the actor is a guard
if (((npc instanceof L2GuardInstance) || (npc instanceof L2DefenderInstance)) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
{
// Order to the L2GuardInstance to return to its home location because there's no target to attack
npc.returnHome();

View File

@@ -1,931 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,884 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,51 +0,0 @@
/*
* 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);
}
}

View File

@@ -38,8 +38,6 @@ import com.l2jmobius.gameserver.ai.CtrlEvent;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2AttackableAI;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2FortSiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.data.xml.impl.ExtendDropData;
import com.l2jmobius.gameserver.datatables.EventDroplist;
import com.l2jmobius.gameserver.datatables.EventDroplist.DateDrop;
@@ -753,15 +751,6 @@ public class L2Attackable extends L2Npc
public void reduceHate(L2Character target, int amount)
{
if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI))
{
// TODO: this just prevents error until siege guards are handled properly
stopHating(target);
setTarget(null);
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
return;
}
if (target == null) // whole aggrolist
{
final L2Character mostHated = getMostHated();

View File

@@ -18,9 +18,6 @@ package com.l2jmobius.gameserver.model.actor.instance;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SpecialSiegeGuardAI;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.instancemanager.CastleManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
@@ -45,16 +42,6 @@ public class L2DefenderInstance extends L2Attackable
setInstanceType(InstanceType.L2DefenderInstance);
}
@Override
protected L2CharacterAI initAI()
{
if (getCastle(10000) != null)
{
return new L2SiegeGuardAI(this);
}
return new L2SpecialSiegeGuardAI(this);
}
@Override
public void addDamage(L2Character attacker, int damage, Skill skill)
{
@@ -189,6 +176,39 @@ public class L2DefenderInstance extends L2Attackable
player.sendPacket(ActionFailed.STATIC_PACKET);
}
@Override
public void useMagic(Skill skill)
{
if (!skill.isBad())
{
L2Character target = this;
for (L2Character nearby : L2World.getInstance().getVisibleObjects(this, L2Character.class, skill.getCastRange()))
{
if (nearby == this)
{
continue;
}
if (nearby instanceof L2DefenderInstance)
{
target = nearby;
}
if (nearby.isPlayer())
{
final L2PcInstance player = (L2PcInstance) nearby;
final Castle castle = getCastle();
final Fort fortress = getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((player.getSiegeState() == 2) && !player.isRegisteredOnThisSiegeField(activeSiegeId))
{
target = nearby;
}
}
}
setTarget(target);
}
super.useMagic(skill);
}
@Override
public void addDamageHate(L2Character attacker, int damage, int aggro)
{

View File

@@ -0,0 +1,147 @@
/*
* 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 ai.others;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Castle;
import com.l2jmobius.gameserver.model.entity.Fort;
import ai.AbstractNpcAI;
/**
* @author Mobius
*/
public class SiegeGuards extends AbstractNpcAI
{
//@formatter:off
// NPCs
private static final int[] CASTLE_GUARDS =
{
35064, 35065, 35066, 35067, 35068, 35069, 35071, 35072, 35079, 35080, 35081, 35082, 35083, 35084, 35085, // Gludio
35106, 35107, 35108, 35109, 35110, 35111, 35113, 35114, 35121, 35122, 35123,35124, 35125, 35126, 35127, // Dion
35150, 35151, 35152, 35153, 35155, 35156, 35163, 35164, 35165, 35166, 35167, 35168, 35169, // Giran
35192, 35193, 35194, 35195, 35197, 35198, 35205, 35206, 35207, 35208, 35209, 35210, 35211, // Oren
35234, 35239, 35240, 35248, 35249, 35250, 35251, 35252, 35253, 35254, // Aden
35280, 35281, 35282, 35283, 35284, 35285, 35287, 35288, 35295, 35296, 35297, 35298, 35299, 35300, 35301, // Innadril
35324, 35325, 35326, 35327, 35328, 35330, 35339, 35340, 35341, 35343, 35350, 35351, // Goddard
35475, 35477, 35480, 35484, 35486, 35487, 35488, 35489, 35490, // Rune
35516, 35517, 35518, 35519, 35520, 35522, 35531, 35532, 35533, 35535, 35542, 35543, // Schuttgart
};
private static final int[] FORT_GUARDS =
{
35670, 35671, 35672, 35673, 35674, 35678, 35679, 35681, 35682, 35684, 35685, // Shanty
35702, 35703, 35704, 35705, 35706, 35707, 35708, 35709, 35710, 35711, 35712, 35714, 35715, 35717, 35718, 35720, 35722, 35723, // Southern
35739, 35740, 35741, 35742, 35743, 35747, 35748, 35750, 35751, 35753, 35754, // Hive
35758, 35767, 35771, 35772, 35773, 35774, 35775, 35776, 35777, 35778, 35779, 35780, 35781, 35783, 35784, 35786, 35787, 35789, 35791, 35792, // Valley
35808, 35809, 35810, 35811, 35812, 35816, 35817, 35819, 35820, 35822, 35823, // Ivory
35839, 35840, 35841, 35842, 35843, 35847, 35848, 35850, 35851, 35853, 35854, // Narsell
35868, 35869, 35871, 35872, 35873, 35874, 35875, 35876, 35877, 35878, 35879, 35880, 35881, 35883, 35884, 35886, 35887, 35889, 35891, 35892, // Bayou
35908, 35909, 35910, 35911, 35912, 35916, 35917, 35919, 35920, 35922, 35923, // White Sands
35940, 35941, 35942, 35943, 35944, 35945, 35946, 35947, 35948, 35949, 35950, 35952, 35953, 35955, 35956, 35958, 35960, 35961, // Borderland
35978, 35979, 35980, 35981, 35982, 35983, 35984, 35985, 35986, 35987, 35988, 35990, 35991, 35993, 35994, 35996, 35998, 35999, // Swamp
36015, 36016, 36017, 36018, 36019, 36023, 36024, 36026, 36027, 36029, 36030, // Archaic
36047, 36048, 36049, 36050, 36051, 36052, 36053, 36054, 36055, 36056, 36057, 36059, 36060, 36062, 36063, 36065, 36067, 36068, 36468, // Floran
36079, 36085, 36086, 36087, 36088, 36089, 36090, 36091, 36092, 36093, 36094, 36095, 36097, 36098, 36100, 36101, 36103, 36105, 36106, // Removed on Helios (113)
36122, 36123, 36124, 36125, 36126, 36130, 36131, 36133, 36134, 36136, 36137, // Tanor
36153, 36154, 36155, 36156, 36157, 36161, 36162, 36164, 36165, 36167, 36168, // Dragonspine
36185, 36186, 36187, 36188, 36189, 36190, 36191, 36192, 36193, 36194, 36195, 36197, 36198, 36200, 36201, 36203, 36205, 36206, // Antharas
36223, 36224, 36225, 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, 36235, 36236, 36238, 36239, 36241, 36243, 36244, // Western
36261, 36262, 36263, 36264, 36265, 36266, 36267, 36268, 36269, 36270, 36271, 36273, 36274, 36276, 36277, 36279, 36281, 36282, // Hunters
36298, 36299, 36300, 36301, 36302, 36306, 36307, 36309, 36310, 36312, 36313, // Aaru
36330, 36331, 36332, 36333, 36334, 36342, 36343, 36345, 36346, 36348, 36351, // Demon
36368, 36369, 36370, 36371, 36372, 36380, 36381, 36383, 36384, 36386, 36389 // Monastic
};
private static final int[] MERCENARIES =
{
35015, 35016, 35017, 35018, 35019, 35025, 35026, 35027, 35028, 35029, 35035, 35036, 35037, 35038, 35039, 35045, 35046, 35047, 35048, 35049, 35055, 35056, 35057, 35058, 35059, 35060, 35061
};
private static final int[] STATIONARY_MERCENARIES =
{
35010, 35011, 35012, 35013, 35014, 35020, 35021, 35022, 35023, 35024, 35030, 35031, 35032, 35033, 35034, 35040, 35041, 35042, 35043, 35044, 35050, 35051, 35052, 35053, 35054, 35092, 35093, 35094,
35134, 35135, 35136, 35176, 35177, 35178, 35218, 35219, 35220, 35261, 35262, 35263, 35264, 35265, 35308, 35309, 35310, 35352, 35353, 35354, 35497, 35498, 35499, 35500, 35501, 35544, 35545, 35546
};
//@formatter:on
public SiegeGuards()
{
addAttackId(CASTLE_GUARDS);
addAttackId(FORT_GUARDS);
addAttackId(MERCENARIES);
addAttackId(STATIONARY_MERCENARIES);
addSeeCreatureId(CASTLE_GUARDS);
addSeeCreatureId(FORT_GUARDS);
addSeeCreatureId(MERCENARIES);
addSeeCreatureId(STATIONARY_MERCENARIES);
addSpawnId(CASTLE_GUARDS);
addSpawnId(FORT_GUARDS);
addSpawnId(MERCENARIES);
addSpawnId(STATIONARY_MERCENARIES);
}
@Override
public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon)
{
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((attacker.getSiegeState() == 2) && !attacker.isRegisteredOnThisSiegeField(activeSiegeId)) || (attacker.getSiegeState() == 0)) && (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
{
((L2Attackable) npc).stopHating(attacker);
return null;
}
return super.onAttack(npc, attacker, damage, isSummon);
}
@Override
public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon)
{
if (creature.isPlayable() && (npc.calculateDirectionTo(creature) < npc.getAggroRange()) && GeoEngine.getInstance().canSeeTarget(npc, creature))
{
final L2Summon summon = isSummon ? creature.getServitors().values().stream().findFirst().orElse(creature.getPet()) : null;
final L2PcInstance cha = summon == null ? (L2PcInstance) creature : summon.getOwner();
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((cha.getSiegeState() != 2) || cha.isRegisteredOnThisSiegeField(activeSiegeId)) && (cha.getSiegeState() != 0)) || (npc.getAI().getIntention() != CtrlIntention.AI_INTENTION_IDLE))
{
if (!cha.isInvisible()) // skip invisible players
{
addAttackPlayerDesire(npc, cha);
}
}
}
return super.onSeeCreature(npc, creature, isSummon);
}
@Override
public String onSpawn(L2Npc npc)
{
npc.setRandomWalking(false);
return super.onSpawn(npc);
}
public static void main(String[] args)
{
new SiegeGuards();
}
}

View File

@@ -42,6 +42,7 @@ 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.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GuardInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
@@ -444,8 +445,8 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
return;
}
// Check if the actor is a L2GuardInstance
if ((npc instanceof L2GuardInstance) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
// Check if the actor is a guard
if (((npc instanceof L2GuardInstance) || (npc instanceof L2DefenderInstance)) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
{
// Order to the L2GuardInstance to return to its home location because there's no target to attack
npc.returnHome();

View File

@@ -1,931 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,884 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,51 +0,0 @@
/*
* 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);
}
}

View File

@@ -38,8 +38,6 @@ import com.l2jmobius.gameserver.ai.CtrlEvent;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2AttackableAI;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2FortSiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.data.xml.impl.ExtendDropData;
import com.l2jmobius.gameserver.datatables.EventDroplist;
import com.l2jmobius.gameserver.datatables.EventDroplist.DateDrop;
@@ -749,15 +747,6 @@ public class L2Attackable extends L2Npc
public void reduceHate(L2Character target, int amount)
{
if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI))
{
// TODO: this just prevents error until siege guards are handled properly
stopHating(target);
setTarget(null);
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
return;
}
if (target == null) // whole aggrolist
{
final L2Character mostHated = getMostHated();

View File

@@ -18,9 +18,6 @@ package com.l2jmobius.gameserver.model.actor.instance;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SpecialSiegeGuardAI;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.instancemanager.CastleManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
@@ -45,16 +42,6 @@ public class L2DefenderInstance extends L2Attackable
setInstanceType(InstanceType.L2DefenderInstance);
}
@Override
protected L2CharacterAI initAI()
{
if (getCastle(10000) != null)
{
return new L2SiegeGuardAI(this);
}
return new L2SpecialSiegeGuardAI(this);
}
@Override
public void addDamage(L2Character attacker, int damage, Skill skill)
{
@@ -189,6 +176,39 @@ public class L2DefenderInstance extends L2Attackable
player.sendPacket(ActionFailed.STATIC_PACKET);
}
@Override
public void useMagic(Skill skill)
{
if (!skill.isBad())
{
L2Character target = this;
for (L2Character nearby : L2World.getInstance().getVisibleObjects(this, L2Character.class, skill.getCastRange()))
{
if (nearby == this)
{
continue;
}
if (nearby instanceof L2DefenderInstance)
{
target = nearby;
}
if (nearby.isPlayer())
{
final L2PcInstance player = (L2PcInstance) nearby;
final Castle castle = getCastle();
final Fort fortress = getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((player.getSiegeState() == 2) && !player.isRegisteredOnThisSiegeField(activeSiegeId))
{
target = nearby;
}
}
}
setTarget(target);
}
super.useMagic(skill);
}
@Override
public void addDamageHate(L2Character attacker, int damage, int aggro)
{

View File

@@ -0,0 +1,147 @@
/*
* 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 ai.others;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Castle;
import com.l2jmobius.gameserver.model.entity.Fort;
import ai.AbstractNpcAI;
/**
* @author Mobius
*/
public class SiegeGuards extends AbstractNpcAI
{
//@formatter:off
// NPCs
private static final int[] CASTLE_GUARDS =
{
35064, 35065, 35066, 35067, 35068, 35069, 35071, 35072, 35079, 35080, 35081, 35082, 35083, 35084, 35085, // Gludio
35106, 35107, 35108, 35109, 35110, 35111, 35113, 35114, 35121, 35122, 35123,35124, 35125, 35126, 35127, // Dion
35150, 35151, 35152, 35153, 35155, 35156, 35163, 35164, 35165, 35166, 35167, 35168, 35169, // Giran
35192, 35193, 35194, 35195, 35197, 35198, 35205, 35206, 35207, 35208, 35209, 35210, 35211, // Oren
35234, 35239, 35240, 35248, 35249, 35250, 35251, 35252, 35253, 35254, // Aden
35280, 35281, 35282, 35283, 35284, 35285, 35287, 35288, 35295, 35296, 35297, 35298, 35299, 35300, 35301, // Innadril
35324, 35325, 35326, 35327, 35328, 35330, 35339, 35340, 35341, 35343, 35350, 35351, // Goddard
35475, 35477, 35480, 35484, 35486, 35487, 35488, 35489, 35490, // Rune
35516, 35517, 35518, 35519, 35520, 35522, 35531, 35532, 35533, 35535, 35542, 35543, // Schuttgart
};
private static final int[] FORT_GUARDS =
{
35670, 35671, 35672, 35673, 35674, 35678, 35679, 35681, 35682, 35684, 35685, // Shanty
35702, 35703, 35704, 35705, 35706, 35707, 35708, 35709, 35710, 35711, 35712, 35714, 35715, 35717, 35718, 35720, 35722, 35723, // Southern
35739, 35740, 35741, 35742, 35743, 35747, 35748, 35750, 35751, 35753, 35754, // Hive
35758, 35767, 35771, 35772, 35773, 35774, 35775, 35776, 35777, 35778, 35779, 35780, 35781, 35783, 35784, 35786, 35787, 35789, 35791, 35792, // Valley
35808, 35809, 35810, 35811, 35812, 35816, 35817, 35819, 35820, 35822, 35823, // Ivory
35839, 35840, 35841, 35842, 35843, 35847, 35848, 35850, 35851, 35853, 35854, // Narsell
35868, 35869, 35871, 35872, 35873, 35874, 35875, 35876, 35877, 35878, 35879, 35880, 35881, 35883, 35884, 35886, 35887, 35889, 35891, 35892, // Bayou
35908, 35909, 35910, 35911, 35912, 35916, 35917, 35919, 35920, 35922, 35923, // White Sands
35940, 35941, 35942, 35943, 35944, 35945, 35946, 35947, 35948, 35949, 35950, 35952, 35953, 35955, 35956, 35958, 35960, 35961, // Borderland
35978, 35979, 35980, 35981, 35982, 35983, 35984, 35985, 35986, 35987, 35988, 35990, 35991, 35993, 35994, 35996, 35998, 35999, // Swamp
36015, 36016, 36017, 36018, 36019, 36023, 36024, 36026, 36027, 36029, 36030, // Archaic
36047, 36048, 36049, 36050, 36051, 36052, 36053, 36054, 36055, 36056, 36057, 36059, 36060, 36062, 36063, 36065, 36067, 36068, 36468, // Floran
36079, 36085, 36086, 36087, 36088, 36089, 36090, 36091, 36092, 36093, 36094, 36095, 36097, 36098, 36100, 36101, 36103, 36105, 36106, // Removed on Helios (113)
36122, 36123, 36124, 36125, 36126, 36130, 36131, 36133, 36134, 36136, 36137, // Tanor
36153, 36154, 36155, 36156, 36157, 36161, 36162, 36164, 36165, 36167, 36168, // Dragonspine
36185, 36186, 36187, 36188, 36189, 36190, 36191, 36192, 36193, 36194, 36195, 36197, 36198, 36200, 36201, 36203, 36205, 36206, // Antharas
36223, 36224, 36225, 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, 36235, 36236, 36238, 36239, 36241, 36243, 36244, // Western
36261, 36262, 36263, 36264, 36265, 36266, 36267, 36268, 36269, 36270, 36271, 36273, 36274, 36276, 36277, 36279, 36281, 36282, // Hunters
36298, 36299, 36300, 36301, 36302, 36306, 36307, 36309, 36310, 36312, 36313, // Aaru
36330, 36331, 36332, 36333, 36334, 36342, 36343, 36345, 36346, 36348, 36351, // Demon
36368, 36369, 36370, 36371, 36372, 36380, 36381, 36383, 36384, 36386, 36389 // Monastic
};
private static final int[] MERCENARIES =
{
35015, 35016, 35017, 35018, 35019, 35025, 35026, 35027, 35028, 35029, 35035, 35036, 35037, 35038, 35039, 35045, 35046, 35047, 35048, 35049, 35055, 35056, 35057, 35058, 35059, 35060, 35061
};
private static final int[] STATIONARY_MERCENARIES =
{
35010, 35011, 35012, 35013, 35014, 35020, 35021, 35022, 35023, 35024, 35030, 35031, 35032, 35033, 35034, 35040, 35041, 35042, 35043, 35044, 35050, 35051, 35052, 35053, 35054, 35092, 35093, 35094,
35134, 35135, 35136, 35176, 35177, 35178, 35218, 35219, 35220, 35261, 35262, 35263, 35264, 35265, 35308, 35309, 35310, 35352, 35353, 35354, 35497, 35498, 35499, 35500, 35501, 35544, 35545, 35546
};
//@formatter:on
public SiegeGuards()
{
addAttackId(CASTLE_GUARDS);
addAttackId(FORT_GUARDS);
addAttackId(MERCENARIES);
addAttackId(STATIONARY_MERCENARIES);
addSeeCreatureId(CASTLE_GUARDS);
addSeeCreatureId(FORT_GUARDS);
addSeeCreatureId(MERCENARIES);
addSeeCreatureId(STATIONARY_MERCENARIES);
addSpawnId(CASTLE_GUARDS);
addSpawnId(FORT_GUARDS);
addSpawnId(MERCENARIES);
addSpawnId(STATIONARY_MERCENARIES);
}
@Override
public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon)
{
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((attacker.getSiegeState() == 2) && !attacker.isRegisteredOnThisSiegeField(activeSiegeId)) || (attacker.getSiegeState() == 0)) && (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
{
((L2Attackable) npc).stopHating(attacker);
return null;
}
return super.onAttack(npc, attacker, damage, isSummon);
}
@Override
public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon)
{
if (creature.isPlayable() && (npc.calculateDirectionTo(creature) < npc.getAggroRange()) && GeoEngine.getInstance().canSeeTarget(npc, creature))
{
final L2Summon summon = isSummon ? creature.getServitors().values().stream().findFirst().orElse(creature.getPet()) : null;
final L2PcInstance cha = summon == null ? (L2PcInstance) creature : summon.getOwner();
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((cha.getSiegeState() != 2) || cha.isRegisteredOnThisSiegeField(activeSiegeId)) && (cha.getSiegeState() != 0)) || (npc.getAI().getIntention() != CtrlIntention.AI_INTENTION_IDLE))
{
if (!cha.isInvisible()) // skip invisible players
{
addAttackPlayerDesire(npc, cha);
}
}
}
return super.onSeeCreature(npc, creature, isSummon);
}
@Override
public String onSpawn(L2Npc npc)
{
npc.setRandomWalking(false);
return super.onSpawn(npc);
}
public static void main(String[] args)
{
new SiegeGuards();
}
}

View File

@@ -42,6 +42,7 @@ 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.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GuardInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
@@ -444,8 +445,8 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
return;
}
// Check if the actor is a L2GuardInstance
if ((npc instanceof L2GuardInstance) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
// Check if the actor is a guard
if (((npc instanceof L2GuardInstance) || (npc instanceof L2DefenderInstance)) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
{
// Order to the L2GuardInstance to return to its home location because there's no target to attack
npc.returnHome();

View File

@@ -1,931 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,884 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,51 +0,0 @@
/*
* 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);
}
}

View File

@@ -38,8 +38,6 @@ import com.l2jmobius.gameserver.ai.CtrlEvent;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2AttackableAI;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2FortSiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.data.xml.impl.ExtendDropData;
import com.l2jmobius.gameserver.datatables.EventDroplist;
import com.l2jmobius.gameserver.datatables.EventDroplist.DateDrop;
@@ -749,15 +747,6 @@ public class L2Attackable extends L2Npc
public void reduceHate(L2Character target, int amount)
{
if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI))
{
// TODO: this just prevents error until siege guards are handled properly
stopHating(target);
setTarget(null);
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
return;
}
if (target == null) // whole aggrolist
{
final L2Character mostHated = getMostHated();

View File

@@ -18,9 +18,6 @@ package com.l2jmobius.gameserver.model.actor.instance;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SpecialSiegeGuardAI;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.instancemanager.CastleManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
@@ -45,16 +42,6 @@ public class L2DefenderInstance extends L2Attackable
setInstanceType(InstanceType.L2DefenderInstance);
}
@Override
protected L2CharacterAI initAI()
{
if (getCastle(10000) != null)
{
return new L2SiegeGuardAI(this);
}
return new L2SpecialSiegeGuardAI(this);
}
@Override
public void addDamage(L2Character attacker, int damage, Skill skill)
{
@@ -189,6 +176,39 @@ public class L2DefenderInstance extends L2Attackable
player.sendPacket(ActionFailed.STATIC_PACKET);
}
@Override
public void useMagic(Skill skill)
{
if (!skill.isBad())
{
L2Character target = this;
for (L2Character nearby : L2World.getInstance().getVisibleObjects(this, L2Character.class, skill.getCastRange()))
{
if (nearby == this)
{
continue;
}
if (nearby instanceof L2DefenderInstance)
{
target = nearby;
}
if (nearby.isPlayer())
{
final L2PcInstance player = (L2PcInstance) nearby;
final Castle castle = getCastle();
final Fort fortress = getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((player.getSiegeState() == 2) && !player.isRegisteredOnThisSiegeField(activeSiegeId))
{
target = nearby;
}
}
}
setTarget(target);
}
super.useMagic(skill);
}
@Override
public void addDamageHate(L2Character attacker, int damage, int aggro)
{

View File

@@ -0,0 +1,147 @@
/*
* 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 ai.others;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Castle;
import com.l2jmobius.gameserver.model.entity.Fort;
import ai.AbstractNpcAI;
/**
* @author Mobius
*/
public class SiegeGuards extends AbstractNpcAI
{
//@formatter:off
// NPCs
private static final int[] CASTLE_GUARDS =
{
35064, 35065, 35066, 35067, 35068, 35069, 35071, 35072, 35079, 35080, 35081, 35082, 35083, 35084, 35085, // Gludio
35106, 35107, 35108, 35109, 35110, 35111, 35113, 35114, 35121, 35122, 35123,35124, 35125, 35126, 35127, // Dion
35150, 35151, 35152, 35153, 35155, 35156, 35163, 35164, 35165, 35166, 35167, 35168, 35169, // Giran
35192, 35193, 35194, 35195, 35197, 35198, 35205, 35206, 35207, 35208, 35209, 35210, 35211, // Oren
35234, 35239, 35240, 35248, 35249, 35250, 35251, 35252, 35253, 35254, // Aden
35280, 35281, 35282, 35283, 35284, 35285, 35287, 35288, 35295, 35296, 35297, 35298, 35299, 35300, 35301, // Innadril
35324, 35325, 35326, 35327, 35328, 35330, 35339, 35340, 35341, 35343, 35350, 35351, // Goddard
35475, 35477, 35480, 35484, 35486, 35487, 35488, 35489, 35490, // Rune
35516, 35517, 35518, 35519, 35520, 35522, 35531, 35532, 35533, 35535, 35542, 35543, // Schuttgart
};
private static final int[] FORT_GUARDS =
{
35670, 35671, 35672, 35673, 35674, 35678, 35679, 35681, 35682, 35684, 35685, // Shanty
35702, 35703, 35704, 35705, 35706, 35707, 35708, 35709, 35710, 35711, 35712, 35714, 35715, 35717, 35718, 35720, 35722, 35723, // Southern
35739, 35740, 35741, 35742, 35743, 35747, 35748, 35750, 35751, 35753, 35754, // Hive
35758, 35767, 35771, 35772, 35773, 35774, 35775, 35776, 35777, 35778, 35779, 35780, 35781, 35783, 35784, 35786, 35787, 35789, 35791, 35792, // Valley
35808, 35809, 35810, 35811, 35812, 35816, 35817, 35819, 35820, 35822, 35823, // Ivory
35839, 35840, 35841, 35842, 35843, 35847, 35848, 35850, 35851, 35853, 35854, // Narsell
35868, 35869, 35871, 35872, 35873, 35874, 35875, 35876, 35877, 35878, 35879, 35880, 35881, 35883, 35884, 35886, 35887, 35889, 35891, 35892, // Bayou
35908, 35909, 35910, 35911, 35912, 35916, 35917, 35919, 35920, 35922, 35923, // White Sands
35940, 35941, 35942, 35943, 35944, 35945, 35946, 35947, 35948, 35949, 35950, 35952, 35953, 35955, 35956, 35958, 35960, 35961, // Borderland
35978, 35979, 35980, 35981, 35982, 35983, 35984, 35985, 35986, 35987, 35988, 35990, 35991, 35993, 35994, 35996, 35998, 35999, // Swamp
36015, 36016, 36017, 36018, 36019, 36023, 36024, 36026, 36027, 36029, 36030, // Archaic
36047, 36048, 36049, 36050, 36051, 36052, 36053, 36054, 36055, 36056, 36057, 36059, 36060, 36062, 36063, 36065, 36067, 36068, 36468, // Floran
36079, 36085, 36086, 36087, 36088, 36089, 36090, 36091, 36092, 36093, 36094, 36095, 36097, 36098, 36100, 36101, 36103, 36105, 36106, // Removed on Helios (113)
36122, 36123, 36124, 36125, 36126, 36130, 36131, 36133, 36134, 36136, 36137, // Tanor
36153, 36154, 36155, 36156, 36157, 36161, 36162, 36164, 36165, 36167, 36168, // Dragonspine
36185, 36186, 36187, 36188, 36189, 36190, 36191, 36192, 36193, 36194, 36195, 36197, 36198, 36200, 36201, 36203, 36205, 36206, // Antharas
36223, 36224, 36225, 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, 36235, 36236, 36238, 36239, 36241, 36243, 36244, // Western
36261, 36262, 36263, 36264, 36265, 36266, 36267, 36268, 36269, 36270, 36271, 36273, 36274, 36276, 36277, 36279, 36281, 36282, // Hunters
36298, 36299, 36300, 36301, 36302, 36306, 36307, 36309, 36310, 36312, 36313, // Aaru
36330, 36331, 36332, 36333, 36334, 36342, 36343, 36345, 36346, 36348, 36351, // Demon
36368, 36369, 36370, 36371, 36372, 36380, 36381, 36383, 36384, 36386, 36389 // Monastic
};
private static final int[] MERCENARIES =
{
35015, 35016, 35017, 35018, 35019, 35025, 35026, 35027, 35028, 35029, 35035, 35036, 35037, 35038, 35039, 35045, 35046, 35047, 35048, 35049, 35055, 35056, 35057, 35058, 35059, 35060, 35061
};
private static final int[] STATIONARY_MERCENARIES =
{
35010, 35011, 35012, 35013, 35014, 35020, 35021, 35022, 35023, 35024, 35030, 35031, 35032, 35033, 35034, 35040, 35041, 35042, 35043, 35044, 35050, 35051, 35052, 35053, 35054, 35092, 35093, 35094,
35134, 35135, 35136, 35176, 35177, 35178, 35218, 35219, 35220, 35261, 35262, 35263, 35264, 35265, 35308, 35309, 35310, 35352, 35353, 35354, 35497, 35498, 35499, 35500, 35501, 35544, 35545, 35546
};
//@formatter:on
public SiegeGuards()
{
addAttackId(CASTLE_GUARDS);
addAttackId(FORT_GUARDS);
addAttackId(MERCENARIES);
addAttackId(STATIONARY_MERCENARIES);
addSeeCreatureId(CASTLE_GUARDS);
addSeeCreatureId(FORT_GUARDS);
addSeeCreatureId(MERCENARIES);
addSeeCreatureId(STATIONARY_MERCENARIES);
addSpawnId(CASTLE_GUARDS);
addSpawnId(FORT_GUARDS);
addSpawnId(MERCENARIES);
addSpawnId(STATIONARY_MERCENARIES);
}
@Override
public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon)
{
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((attacker.getSiegeState() == 2) && !attacker.isRegisteredOnThisSiegeField(activeSiegeId)) || (attacker.getSiegeState() == 0)) && (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
{
((L2Attackable) npc).stopHating(attacker);
return null;
}
return super.onAttack(npc, attacker, damage, isSummon);
}
@Override
public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon)
{
if (creature.isPlayable() && (npc.calculateDirectionTo(creature) < npc.getAggroRange()) && GeoEngine.getInstance().canSeeTarget(npc, creature))
{
final L2Summon summon = isSummon ? creature.getServitors().values().stream().findFirst().orElse(creature.getPet()) : null;
final L2PcInstance cha = summon == null ? (L2PcInstance) creature : summon.getOwner();
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((cha.getSiegeState() != 2) || cha.isRegisteredOnThisSiegeField(activeSiegeId)) && (cha.getSiegeState() != 0)) || (npc.getAI().getIntention() != CtrlIntention.AI_INTENTION_IDLE))
{
if (!cha.isInvisible()) // skip invisible players
{
addAttackPlayerDesire(npc, cha);
}
}
}
return super.onSeeCreature(npc, creature, isSummon);
}
@Override
public String onSpawn(L2Npc npc)
{
npc.setRandomWalking(false);
return super.onSpawn(npc);
}
public static void main(String[] args)
{
new SiegeGuards();
}
}

View File

@@ -42,6 +42,7 @@ 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.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GuardInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
@@ -444,8 +445,8 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
return;
}
// Check if the actor is a L2GuardInstance
if ((npc instanceof L2GuardInstance) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
// Check if the actor is a guard
if (((npc instanceof L2GuardInstance) || (npc instanceof L2DefenderInstance)) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
{
// Order to the L2GuardInstance to return to its home location because there's no target to attack
npc.returnHome();

View File

@@ -1,931 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,884 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,51 +0,0 @@
/*
* 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);
}
}

View File

@@ -38,8 +38,6 @@ import com.l2jmobius.gameserver.ai.CtrlEvent;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2AttackableAI;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2FortSiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.data.xml.impl.ExtendDropData;
import com.l2jmobius.gameserver.datatables.EventDroplist;
import com.l2jmobius.gameserver.datatables.EventDroplist.DateDrop;
@@ -749,15 +747,6 @@ public class L2Attackable extends L2Npc
public void reduceHate(L2Character target, int amount)
{
if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI))
{
// TODO: this just prevents error until siege guards are handled properly
stopHating(target);
setTarget(null);
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
return;
}
if (target == null) // whole aggrolist
{
final L2Character mostHated = getMostHated();

View File

@@ -18,9 +18,6 @@ package com.l2jmobius.gameserver.model.actor.instance;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SpecialSiegeGuardAI;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.instancemanager.CastleManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
@@ -45,16 +42,6 @@ public class L2DefenderInstance extends L2Attackable
setInstanceType(InstanceType.L2DefenderInstance);
}
@Override
protected L2CharacterAI initAI()
{
if (getCastle(10000) != null)
{
return new L2SiegeGuardAI(this);
}
return new L2SpecialSiegeGuardAI(this);
}
@Override
public void addDamage(L2Character attacker, int damage, Skill skill)
{
@@ -189,6 +176,39 @@ public class L2DefenderInstance extends L2Attackable
player.sendPacket(ActionFailed.STATIC_PACKET);
}
@Override
public void useMagic(Skill skill)
{
if (!skill.isBad())
{
L2Character target = this;
for (L2Character nearby : L2World.getInstance().getVisibleObjects(this, L2Character.class, skill.getCastRange()))
{
if (nearby == this)
{
continue;
}
if (nearby instanceof L2DefenderInstance)
{
target = nearby;
}
if (nearby.isPlayer())
{
final L2PcInstance player = (L2PcInstance) nearby;
final Castle castle = getCastle();
final Fort fortress = getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((player.getSiegeState() == 2) && !player.isRegisteredOnThisSiegeField(activeSiegeId))
{
target = nearby;
}
}
}
setTarget(target);
}
super.useMagic(skill);
}
@Override
public void addDamageHate(L2Character attacker, int damage, int aggro)
{

View File

@@ -0,0 +1,147 @@
/*
* 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 ai.others;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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.L2Summon;
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
import com.l2jmobius.gameserver.model.entity.Castle;
import com.l2jmobius.gameserver.model.entity.Fort;
import ai.AbstractNpcAI;
/**
* @author Mobius
*/
public class SiegeGuards extends AbstractNpcAI
{
//@formatter:off
// NPCs
private static final int[] CASTLE_GUARDS =
{
35064, 35065, 35066, 35067, 35068, 35069, 35071, 35072, 35079, 35080, 35081, 35082, 35083, 35084, 35085, // Gludio
35106, 35107, 35108, 35109, 35110, 35111, 35113, 35114, 35121, 35122, 35123,35124, 35125, 35126, 35127, // Dion
35150, 35151, 35152, 35153, 35155, 35156, 35163, 35164, 35165, 35166, 35167, 35168, 35169, // Giran
35192, 35193, 35194, 35195, 35197, 35198, 35205, 35206, 35207, 35208, 35209, 35210, 35211, // Oren
35234, 35239, 35240, 35248, 35249, 35250, 35251, 35252, 35253, 35254, // Aden
35280, 35281, 35282, 35283, 35284, 35285, 35287, 35288, 35295, 35296, 35297, 35298, 35299, 35300, 35301, // Innadril
35324, 35325, 35326, 35327, 35328, 35330, 35339, 35340, 35341, 35343, 35350, 35351, // Goddard
35475, 35477, 35480, 35484, 35486, 35487, 35488, 35489, 35490, // Rune
35516, 35517, 35518, 35519, 35520, 35522, 35531, 35532, 35533, 35535, 35542, 35543, // Schuttgart
};
private static final int[] FORT_GUARDS =
{
35670, 35671, 35672, 35673, 35674, 35678, 35679, 35681, 35682, 35684, 35685, // Shanty
35702, 35703, 35704, 35705, 35706, 35707, 35708, 35709, 35710, 35711, 35712, 35714, 35715, 35717, 35718, 35720, 35722, 35723, // Southern
35739, 35740, 35741, 35742, 35743, 35747, 35748, 35750, 35751, 35753, 35754, // Hive
35758, 35767, 35771, 35772, 35773, 35774, 35775, 35776, 35777, 35778, 35779, 35780, 35781, 35783, 35784, 35786, 35787, 35789, 35791, 35792, // Valley
35808, 35809, 35810, 35811, 35812, 35816, 35817, 35819, 35820, 35822, 35823, // Ivory
35839, 35840, 35841, 35842, 35843, 35847, 35848, 35850, 35851, 35853, 35854, // Narsell
35868, 35869, 35871, 35872, 35873, 35874, 35875, 35876, 35877, 35878, 35879, 35880, 35881, 35883, 35884, 35886, 35887, 35889, 35891, 35892, // Bayou
35908, 35909, 35910, 35911, 35912, 35916, 35917, 35919, 35920, 35922, 35923, // White Sands
35940, 35941, 35942, 35943, 35944, 35945, 35946, 35947, 35948, 35949, 35950, 35952, 35953, 35955, 35956, 35958, 35960, 35961, // Borderland
35978, 35979, 35980, 35981, 35982, 35983, 35984, 35985, 35986, 35987, 35988, 35990, 35991, 35993, 35994, 35996, 35998, 35999, // Swamp
36015, 36016, 36017, 36018, 36019, 36023, 36024, 36026, 36027, 36029, 36030, // Archaic
36047, 36048, 36049, 36050, 36051, 36052, 36053, 36054, 36055, 36056, 36057, 36059, 36060, 36062, 36063, 36065, 36067, 36068, 36468, // Floran
36079, 36085, 36086, 36087, 36088, 36089, 36090, 36091, 36092, 36093, 36094, 36095, 36097, 36098, 36100, 36101, 36103, 36105, 36106, // Removed on Helios (113)
36122, 36123, 36124, 36125, 36126, 36130, 36131, 36133, 36134, 36136, 36137, // Tanor
36153, 36154, 36155, 36156, 36157, 36161, 36162, 36164, 36165, 36167, 36168, // Dragonspine
36185, 36186, 36187, 36188, 36189, 36190, 36191, 36192, 36193, 36194, 36195, 36197, 36198, 36200, 36201, 36203, 36205, 36206, // Antharas
36223, 36224, 36225, 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, 36235, 36236, 36238, 36239, 36241, 36243, 36244, // Western
36261, 36262, 36263, 36264, 36265, 36266, 36267, 36268, 36269, 36270, 36271, 36273, 36274, 36276, 36277, 36279, 36281, 36282, // Hunters
36298, 36299, 36300, 36301, 36302, 36306, 36307, 36309, 36310, 36312, 36313, // Aaru
36330, 36331, 36332, 36333, 36334, 36342, 36343, 36345, 36346, 36348, 36351, // Demon
36368, 36369, 36370, 36371, 36372, 36380, 36381, 36383, 36384, 36386, 36389 // Monastic
};
private static final int[] MERCENARIES =
{
35015, 35016, 35017, 35018, 35019, 35025, 35026, 35027, 35028, 35029, 35035, 35036, 35037, 35038, 35039, 35045, 35046, 35047, 35048, 35049, 35055, 35056, 35057, 35058, 35059, 35060, 35061
};
private static final int[] STATIONARY_MERCENARIES =
{
35010, 35011, 35012, 35013, 35014, 35020, 35021, 35022, 35023, 35024, 35030, 35031, 35032, 35033, 35034, 35040, 35041, 35042, 35043, 35044, 35050, 35051, 35052, 35053, 35054, 35092, 35093, 35094,
35134, 35135, 35136, 35176, 35177, 35178, 35218, 35219, 35220, 35261, 35262, 35263, 35264, 35265, 35308, 35309, 35310, 35352, 35353, 35354, 35497, 35498, 35499, 35500, 35501, 35544, 35545, 35546
};
//@formatter:on
public SiegeGuards()
{
addAttackId(CASTLE_GUARDS);
addAttackId(FORT_GUARDS);
addAttackId(MERCENARIES);
addAttackId(STATIONARY_MERCENARIES);
addSeeCreatureId(CASTLE_GUARDS);
addSeeCreatureId(FORT_GUARDS);
addSeeCreatureId(MERCENARIES);
addSeeCreatureId(STATIONARY_MERCENARIES);
addSpawnId(CASTLE_GUARDS);
addSpawnId(FORT_GUARDS);
addSpawnId(MERCENARIES);
addSpawnId(STATIONARY_MERCENARIES);
}
@Override
public String onAttack(L2Npc npc, L2PcInstance attacker, int damage, boolean isSummon)
{
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((attacker.getSiegeState() == 2) && !attacker.isRegisteredOnThisSiegeField(activeSiegeId)) || (attacker.getSiegeState() == 0)) && (npc.getAI().getIntention() == CtrlIntention.AI_INTENTION_IDLE))
{
((L2Attackable) npc).stopHating(attacker);
return null;
}
return super.onAttack(npc, attacker, damage, isSummon);
}
@Override
public String onSeeCreature(L2Npc npc, L2Character creature, boolean isSummon)
{
if (creature.isPlayable() && (npc.calculateDirectionTo(creature) < npc.getAggroRange()) && GeoEngine.getInstance().canSeeTarget(npc, creature))
{
final L2Summon summon = isSummon ? creature.getServitors().values().stream().findFirst().orElse(creature.getPet()) : null;
final L2PcInstance cha = summon == null ? (L2PcInstance) creature : summon.getOwner();
final Castle castle = npc.getCastle();
final Fort fortress = npc.getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((((cha.getSiegeState() != 2) || cha.isRegisteredOnThisSiegeField(activeSiegeId)) && (cha.getSiegeState() != 0)) || (npc.getAI().getIntention() != CtrlIntention.AI_INTENTION_IDLE))
{
if (!cha.isInvisible()) // skip invisible players
{
addAttackPlayerDesire(npc, cha);
}
}
}
return super.onSeeCreature(npc, creature, isSummon);
}
@Override
public String onSpawn(L2Npc npc)
{
npc.setRandomWalking(false);
return super.onSpawn(npc);
}
public static void main(String[] args)
{
new SiegeGuards();
}
}

View File

@@ -42,6 +42,7 @@ 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.L2DefenderInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GrandBossInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2GuardInstance;
import com.l2jmobius.gameserver.model.actor.instance.L2MonsterInstance;
@@ -444,8 +445,8 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
return;
}
// Check if the actor is a L2GuardInstance
if ((npc instanceof L2GuardInstance) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
// Check if the actor is a guard
if (((npc instanceof L2GuardInstance) || (npc instanceof L2DefenderInstance)) && !npc.isWalker() && !npc.isRandomWalkingEnabled())
{
// Order to the L2GuardInstance to return to its home location because there's no target to attack
npc.returnHome();

View File

@@ -1,931 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,884 +0,0 @@
/*
* 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.ThreadPoolManager;
import com.l2jmobius.gameserver.geoengine.GeoEngine;
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) && GeoEngine.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.scheduleAtFixedRate(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 (!GeoEngine.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) && GeoEngine.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 (!GeoEngine.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 (!GeoEngine.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();
}
}

View File

@@ -1,51 +0,0 @@
/*
* 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);
}
}

View File

@@ -38,8 +38,6 @@ import com.l2jmobius.gameserver.ai.CtrlEvent;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2AttackableAI;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2FortSiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.data.xml.impl.ExtendDropData;
import com.l2jmobius.gameserver.datatables.EventDroplist;
import com.l2jmobius.gameserver.datatables.EventDroplist.DateDrop;
@@ -749,15 +747,6 @@ public class L2Attackable extends L2Npc
public void reduceHate(L2Character target, int amount)
{
if ((getAI() instanceof L2SiegeGuardAI) || (getAI() instanceof L2FortSiegeGuardAI))
{
// TODO: this just prevents error until siege guards are handled properly
stopHating(target);
setTarget(null);
getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
return;
}
if (target == null) // whole aggrolist
{
final L2Character mostHated = getMostHated();

View File

@@ -18,9 +18,6 @@ package com.l2jmobius.gameserver.model.actor.instance;
import com.l2jmobius.Config;
import com.l2jmobius.gameserver.ai.CtrlIntention;
import com.l2jmobius.gameserver.ai.L2CharacterAI;
import com.l2jmobius.gameserver.ai.L2SiegeGuardAI;
import com.l2jmobius.gameserver.ai.L2SpecialSiegeGuardAI;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.instancemanager.CastleManager;
import com.l2jmobius.gameserver.instancemanager.FortManager;
@@ -45,16 +42,6 @@ public class L2DefenderInstance extends L2Attackable
setInstanceType(InstanceType.L2DefenderInstance);
}
@Override
protected L2CharacterAI initAI()
{
if (getCastle(10000) != null)
{
return new L2SiegeGuardAI(this);
}
return new L2SpecialSiegeGuardAI(this);
}
@Override
public void addDamage(L2Character attacker, int damage, Skill skill)
{
@@ -189,6 +176,39 @@ public class L2DefenderInstance extends L2Attackable
player.sendPacket(ActionFailed.STATIC_PACKET);
}
@Override
public void useMagic(Skill skill)
{
if (!skill.isBad())
{
L2Character target = this;
for (L2Character nearby : L2World.getInstance().getVisibleObjects(this, L2Character.class, skill.getCastRange()))
{
if (nearby == this)
{
continue;
}
if (nearby instanceof L2DefenderInstance)
{
target = nearby;
}
if (nearby.isPlayer())
{
final L2PcInstance player = (L2PcInstance) nearby;
final Castle castle = getCastle();
final Fort fortress = getFort();
final int activeSiegeId = (fortress != null ? fortress.getResidenceId() : (castle != null ? castle.getResidenceId() : 0));
if ((player.getSiegeState() == 2) && !player.isRegisteredOnThisSiegeField(activeSiegeId))
{
target = nearby;
}
}
}
setTarget(target);
}
super.useMagic(skill);
}
@Override
public void addDamageHate(L2Character attacker, int damage, int aggro)
{