L2AttackableAI.java 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479
  1. /*
  2. * This program is free software: you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation, either version 3 of the License, or (at your option) any later
  5. * version.
  6. *
  7. * This program is distributed in the hope that it will be useful, but WITHOUT
  8. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. * details.
  11. *
  12. * You should have received a copy of the GNU General Public License along with
  13. * this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. package net.sf.l2j.gameserver.ai;
  16. import static net.sf.l2j.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
  17. import static net.sf.l2j.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
  18. import static net.sf.l2j.gameserver.ai.CtrlIntention.AI_INTENTION_IDLE;
  19. import java.util.Collection;
  20. import java.util.List;
  21. import java.util.concurrent.Future;
  22. import javolution.util.FastList;
  23. import net.sf.l2j.Config;
  24. import net.sf.l2j.gameserver.GameTimeController;
  25. import net.sf.l2j.gameserver.GeoData;
  26. import net.sf.l2j.gameserver.Territory;
  27. import net.sf.l2j.gameserver.ThreadPoolManager;
  28. import net.sf.l2j.gameserver.instancemanager.DimensionalRiftManager;
  29. import net.sf.l2j.gameserver.model.L2Attackable;
  30. import net.sf.l2j.gameserver.model.L2CharPosition;
  31. import net.sf.l2j.gameserver.model.L2Character;
  32. import net.sf.l2j.gameserver.model.L2Object;
  33. import net.sf.l2j.gameserver.model.L2Skill;
  34. import net.sf.l2j.gameserver.model.L2Summon;
  35. import net.sf.l2j.gameserver.model.actor.instance.L2DoorInstance;
  36. import net.sf.l2j.gameserver.model.actor.instance.L2FestivalMonsterInstance;
  37. import net.sf.l2j.gameserver.model.actor.instance.L2FolkInstance;
  38. import net.sf.l2j.gameserver.model.actor.instance.L2FriendlyMobInstance;
  39. import net.sf.l2j.gameserver.model.actor.instance.L2GrandBossInstance;
  40. import net.sf.l2j.gameserver.model.actor.instance.L2GuardInstance;
  41. import net.sf.l2j.gameserver.model.actor.instance.L2MinionInstance;
  42. import net.sf.l2j.gameserver.model.actor.instance.L2MonsterInstance;
  43. import net.sf.l2j.gameserver.model.actor.instance.L2NpcInstance;
  44. import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
  45. import net.sf.l2j.gameserver.model.actor.instance.L2PlayableInstance;
  46. import net.sf.l2j.gameserver.model.actor.instance.L2RaidBossInstance;
  47. import net.sf.l2j.gameserver.model.actor.instance.L2RiftInvaderInstance;
  48. import net.sf.l2j.gameserver.model.quest.Quest;
  49. import net.sf.l2j.gameserver.taskmanager.DecayTaskManager;
  50. import net.sf.l2j.gameserver.util.Util;
  51. import net.sf.l2j.util.Rnd;
  52. /**
  53. * This class manages AI of L2Attackable.<BR><BR>
  54. *
  55. */
  56. public class L2AttackableAI extends L2CharacterAI implements Runnable
  57. {
  58. //protected static final Logger _log = Logger.getLogger(L2AttackableAI.class.getName());
  59. private static final int RANDOM_WALK_RATE = 30; // confirmed
  60. // private static final int MAX_DRIFT_RANGE = 300;
  61. private static final int MAX_ATTACK_TIMEOUT = 300; // int ticks, i.e. 30 seconds
  62. /** The L2Attackable AI task executed every 1s (call onEvtThink method)*/
  63. private Future<?> _aiTask;
  64. /** The delay after which the attacked is stopped */
  65. private int _attackTimeout;
  66. /** The L2Attackable aggro counter */
  67. private int _globalAggro;
  68. /** The flag used to indicate that a thinking action is in progress */
  69. private boolean _thinking; // to prevent recursive thinking
  70. /** For attack AI, analysis of mob and its targets */
  71. private SelfAnalysis _selfAnalysis = new SelfAnalysis();
  72. private TargetAnalysis _mostHatedAnalysis = new TargetAnalysis();
  73. private TargetAnalysis _secondMostHatedAnalysis = new TargetAnalysis();
  74. /**
  75. * Constructor of L2AttackableAI.<BR><BR>
  76. *
  77. * @param accessor The AI accessor of the L2Character
  78. *
  79. */
  80. public L2AttackableAI(L2Character.AIAccessor accessor)
  81. {
  82. super(accessor);
  83. _selfAnalysis.init();
  84. _attackTimeout = Integer.MAX_VALUE;
  85. _globalAggro = -10; // 10 seconds timeout of ATTACK after respawn
  86. }
  87. public void run()
  88. {
  89. // Launch actions corresponding to the Event Think
  90. onEvtThink();
  91. }
  92. /**
  93. * Return True if the target is autoattackable (depends on the actor type).<BR><BR>
  94. *
  95. * <B><U> Actor is a L2GuardInstance</U> :</B><BR><BR>
  96. * <li>The target isn't a Folk or a Door</li>
  97. * <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
  98. * <li>The target is in the actor Aggro range and is at the same height</li>
  99. * <li>The L2PcInstance target has karma (=PK)</li>
  100. * <li>The L2MonsterInstance target is aggressive</li><BR><BR>
  101. *
  102. * <B><U> Actor is a L2SiegeGuardInstance</U> :</B><BR><BR>
  103. * <li>The target isn't a Folk or a Door</li>
  104. * <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
  105. * <li>The target is in the actor Aggro range and is at the same height</li>
  106. * <li>A siege is in progress</li>
  107. * <li>The L2PcInstance target isn't a Defender</li><BR><BR>
  108. *
  109. * <B><U> Actor is a L2FriendlyMobInstance</U> :</B><BR><BR>
  110. * <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
  111. * <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
  112. * <li>The target is in the actor Aggro range and is at the same height</li>
  113. * <li>The L2PcInstance target has karma (=PK)</li><BR><BR>
  114. *
  115. * <B><U> Actor is a L2MonsterInstance</U> :</B><BR><BR>
  116. * <li>The target isn't a Folk, a Door or another L2NpcInstance</li>
  117. * <li>The target isn't dead, isn't invulnerable, isn't in silent moving mode AND too far (>100)</li>
  118. * <li>The target is in the actor Aggro range and is at the same height</li>
  119. * <li>The actor is Aggressive</li><BR><BR>
  120. *
  121. * @param target The targeted L2Object
  122. *
  123. */
  124. private boolean autoAttackCondition(L2Character target)
  125. {
  126. if (target == null || !(_actor instanceof L2Attackable))
  127. return false;
  128. L2Attackable me = (L2Attackable) _actor;
  129. // Check if the target isn't invulnerable
  130. if (target.isInvul())
  131. {
  132. // However EffectInvincible requires to check GMs specially
  133. if (target instanceof L2PcInstance && ((L2PcInstance) target).isGM())
  134. return false;
  135. if (target instanceof L2Summon && ((L2Summon) target).getOwner().isGM())
  136. return false;
  137. }
  138. // Check if the target isn't a Folk or a Door
  139. if (target instanceof L2FolkInstance || target instanceof L2DoorInstance)
  140. return false;
  141. // Check if the target isn't dead, is in the Aggro range and is at the same height
  142. if (target.isAlikeDead() || !me.isInsideRadius(target, me.getAggroRange(), false, false) || Math.abs(_actor.getZ() - target.getZ()) > 300)
  143. return false;
  144. if (_selfAnalysis.cannotMoveOnLand && !target.isInsideZone(L2Character.ZONE_WATER))
  145. return false;
  146. // Check if the target is a L2PlayableInstance
  147. if (target instanceof L2PlayableInstance)
  148. {
  149. // Check if the AI isn't a Raid Boss and the target isn't in silent move mode
  150. if (!(me instanceof L2RaidBossInstance) && ((L2PlayableInstance) target).isSilentMoving())
  151. return false;
  152. }
  153. // Check if the target is a L2PcInstance
  154. if (target instanceof L2PcInstance)
  155. {
  156. // Don't take the aggro if the GM has the access level below or equal to GM_DONT_TAKE_AGGRO
  157. if (((L2PcInstance) target).isGM() && !((L2PcInstance) target).getAccessLevel().canTakeAggro())
  158. return false;
  159. // TODO: Ideally, autoattack condition should be called from the AI script. In that case,
  160. // it should only implement the basic behaviors while the script will add more specific
  161. // behaviors (like varka/ketra alliance, etc). Once implemented, remove specialized stuff
  162. // from this location. (Fulminus)
  163. // Check if player is an ally (comparing mem addr)
  164. if (me.getFactionId() == "varka" && ((L2PcInstance) target).isAlliedWithVarka())
  165. return false;
  166. if (me.getFactionId() == "ketra" && ((L2PcInstance) target).isAlliedWithKetra())
  167. return false;
  168. // check if the target is within the grace period for JUST getting up from fake death
  169. if (((L2PcInstance) target).isRecentFakeDeath())
  170. return false;
  171. if (target.isInParty() && target.getParty().isInDimensionalRift())
  172. {
  173. byte riftType = target.getParty().getDimensionalRift().getType();
  174. byte riftRoom = target.getParty().getDimensionalRift().getCurrentRoom();
  175. if (me instanceof L2RiftInvaderInstance && !DimensionalRiftManager.getInstance().getRoom(riftType, riftRoom).checkIfInZone(me.getX(), me.getY(), me.getZ()))
  176. return false;
  177. }
  178. }
  179. // Check if the target is a L2Summon
  180. if (target instanceof L2Summon)
  181. {
  182. L2PcInstance owner = ((L2Summon) target).getOwner();
  183. if (owner != null)
  184. {
  185. // Don't take the aggro if the GM has the access level below or equal to GM_DONT_TAKE_AGGRO
  186. if (owner.isGM() && (owner.isInvul() || !owner.getAccessLevel().canTakeAggro()))
  187. return false;
  188. // Check if player is an ally (comparing mem addr)
  189. if (me.getFactionId() == "varka" && owner.isAlliedWithVarka())
  190. return false;
  191. if (me.getFactionId() == "ketra" && owner.isAlliedWithKetra())
  192. return false;
  193. }
  194. }
  195. // Check if the actor is a L2GuardInstance
  196. if (_actor instanceof L2GuardInstance)
  197. {
  198. // Check if the L2PcInstance target has karma (=PK)
  199. if (target instanceof L2PcInstance && ((L2PcInstance) target).getKarma() > 0)
  200. // Los Check
  201. return GeoData.getInstance().canSeeTarget(me, target);
  202. //if (target instanceof L2Summon)
  203. // return ((L2Summon)target).getKarma() > 0;
  204. // Check if the L2MonsterInstance target is aggressive
  205. if (target instanceof L2MonsterInstance)
  206. return (((L2MonsterInstance) target).isAggressive() && GeoData.getInstance().canSeeTarget(me, target));
  207. return false;
  208. }
  209. else if (_actor instanceof L2FriendlyMobInstance)
  210. { // the actor is a L2FriendlyMobInstance
  211. // Check if the target isn't another L2NpcInstance
  212. if (target instanceof L2NpcInstance)
  213. return false;
  214. // Check if the L2PcInstance target has karma (=PK)
  215. if (target instanceof L2PcInstance && ((L2PcInstance) target).getKarma() > 0)
  216. // Los Check
  217. return GeoData.getInstance().canSeeTarget(me, target);
  218. else
  219. return false;
  220. }
  221. else
  222. { //The actor is a L2MonsterInstance
  223. // Check if the target isn't another L2NpcInstance
  224. if (target instanceof L2NpcInstance)
  225. return false;
  226. // depending on config, do not allow mobs to attack _new_ players in peacezones,
  227. // unless they are already following those players from outside the peacezone.
  228. if (!Config.ALT_MOB_AGRO_IN_PEACEZONE && target.isInsideZone(L2Character.ZONE_PEACE))
  229. return false;
  230. if (me.isChampion() && Config.L2JMOD_CHAMPION_PASSIVE)
  231. return false;
  232. // Check if the actor is Aggressive
  233. return (me.isAggressive() && GeoData.getInstance().canSeeTarget(me, target));
  234. }
  235. }
  236. public void startAITask()
  237. {
  238. // If not idle - create an AI task (schedule onEvtThink repeatedly)
  239. if (_aiTask == null)
  240. {
  241. _aiTask = ThreadPoolManager.getInstance().scheduleAiAtFixedRate(this, 1000, 1000);
  242. }
  243. }
  244. public void stopAITask()
  245. {
  246. if (_aiTask != null)
  247. {
  248. _aiTask.cancel(false);
  249. _aiTask = null;
  250. }
  251. }
  252. @Override
  253. protected void onEvtDead()
  254. {
  255. stopAITask();
  256. super.onEvtDead();
  257. }
  258. /**
  259. * Set the Intention of this L2CharacterAI and create an AI Task executed every 1s (call onEvtThink method) for this L2Attackable.<BR><BR>
  260. *
  261. * <FONT COLOR=#FF0000><B> <U>Caution</U> : If actor _knowPlayer isn't EMPTY, AI_INTENTION_IDLE will be change in AI_INTENTION_ACTIVE</B></FONT><BR><BR>
  262. *
  263. * @param intention The new Intention to set to the AI
  264. * @param arg0 The first parameter of the Intention
  265. * @param arg1 The second parameter of the Intention
  266. *
  267. */
  268. @Override
  269. synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
  270. {
  271. if (intention == AI_INTENTION_IDLE || intention == AI_INTENTION_ACTIVE)
  272. {
  273. // Check if actor is not dead
  274. if (!_actor.isAlikeDead())
  275. {
  276. L2Attackable npc = (L2Attackable) _actor;
  277. // If its _knownPlayer isn't empty set the Intention to AI_INTENTION_ACTIVE
  278. if (npc.getKnownList().getKnownPlayers().size() > 0)
  279. intention = AI_INTENTION_ACTIVE;
  280. }
  281. if (intention == AI_INTENTION_IDLE)
  282. {
  283. // Set the Intention of this L2AttackableAI to AI_INTENTION_IDLE
  284. super.changeIntention(AI_INTENTION_IDLE, null, null);
  285. // Stop AI task and detach AI from NPC
  286. if (_aiTask != null)
  287. {
  288. _aiTask.cancel(true);
  289. _aiTask = null;
  290. }
  291. // Cancel the AI
  292. _accessor.detachAI();
  293. return;
  294. }
  295. }
  296. // Set the Intention of this L2AttackableAI to intention
  297. super.changeIntention(intention, arg0, arg1);
  298. // If not idle - create an AI task (schedule onEvtThink repeatedly)
  299. startAITask();
  300. }
  301. /**
  302. * Manage the Attack Intention : Stop current Attack (if necessary), Calculate attack timeout, Start a new Attack and Launch Think Event.<BR><BR>
  303. *
  304. * @param target The L2Character to attack
  305. *
  306. */
  307. @Override
  308. protected void onIntentionAttack(L2Character target)
  309. {
  310. // Calculate the attack timeout
  311. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  312. // self and buffs
  313. if (_selfAnalysis.lastBuffTick + 100 < GameTimeController.getGameTicks())
  314. {
  315. for (L2Skill sk : _selfAnalysis.buffSkills)
  316. {
  317. if (_actor.getFirstEffect(sk.getId()) == null)
  318. {
  319. if (_actor.getCurrentMp() < sk.getMpConsume())
  320. continue;
  321. if (_actor.isSkillDisabled(sk.getId()))
  322. continue;
  323. // no clan buffs here?
  324. if (sk.getTargetType() == L2Skill.SkillTargetType.TARGET_CLAN)
  325. continue;
  326. L2Object OldTarget = _actor.getTarget();
  327. _actor.setTarget(_actor);
  328. clientStopMoving(null);
  329. _accessor.doCast(sk);
  330. // forcing long reuse delay so if cast get interrupted or there would be several buffs, doesn't cast again
  331. _selfAnalysis.lastBuffTick = GameTimeController.getGameTicks();
  332. _actor.setTarget(OldTarget);
  333. }
  334. }
  335. }
  336. // Manage the Attack Intention : Stop current Attack (if necessary), Start a new Attack and Launch Think Event
  337. super.onIntentionAttack(target);
  338. }
  339. /**
  340. * Manage AI standard thinks of a L2Attackable (called by onEvtThink).<BR><BR>
  341. *
  342. * <B><U> Actions</U> :</B><BR><BR>
  343. * <li>Update every 1s the _globalAggro counter to come close to 0</li>
  344. * <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>
  345. * <li>If the actor is a L2GuardInstance that can't attack, order to it to return to its home location</li>
  346. * <li>If the actor is a L2MonsterInstance that can't attack, order to it to random walk (1/100)</li><BR><BR>
  347. *
  348. */
  349. private void thinkActive()
  350. {
  351. L2Attackable npc = (L2Attackable) _actor;
  352. // Update every 1s the _globalAggro counter to come close to 0
  353. if (_globalAggro != 0)
  354. {
  355. if (_globalAggro < 0)
  356. _globalAggro++;
  357. else
  358. _globalAggro--;
  359. }
  360. // Add all autoAttackable L2Character in L2Attackable Aggro Range to its _aggroList with 0 damage and 1 hate
  361. // A L2Attackable isn't aggressive during 10s after its spawn because _globalAggro is set to -10
  362. if (_globalAggro >= 0)
  363. {
  364. // Get all visible objects inside its Aggro Range
  365. //L2Object[] objects = L2World.getInstance().getVisibleObjects(_actor, ((L2NpcInstance)_actor).getAggroRange());
  366. // Go through visible objects
  367. Collection<L2Object> objs = npc.getKnownList().getKnownObjects().values();
  368. //synchronized (npc.getKnownList().getKnownObjects())
  369. {
  370. for (L2Object obj : objs)
  371. {
  372. if (!(obj instanceof L2Character))
  373. continue;
  374. L2Character target = (L2Character) obj;
  375. /*
  376. * Check to see if this is a festival mob spawn.
  377. * If it is, then check to see if the aggro trigger
  378. * is a festival participant...if so, move to attack it.
  379. */
  380. if ((_actor instanceof L2FestivalMonsterInstance) && obj instanceof L2PcInstance)
  381. {
  382. L2PcInstance targetPlayer = (L2PcInstance) obj;
  383. if (!(targetPlayer.isFestivalParticipant()))
  384. continue;
  385. }
  386. /*
  387. * Temporarily adding this commented code as a concept to be used eventually.
  388. * However, the way it is written below will NOT work correctly. The NPC
  389. * should only notify Aggro Range Enter when someone enters the range from outside.
  390. * Instead, the below code will keep notifying even while someone remains within
  391. * the range. Perhaps we need a short knownlist of range = aggroRange for just
  392. * people who are actively within the npc's aggro range?...(Fulminus)
  393. // notify AI that a playable instance came within aggro range
  394. if ((obj instanceof L2PcInstance) || (obj instanceof L2Summon))
  395. {
  396. if ( !((L2Character)obj).isAlikeDead()
  397. && !npc.isInsideRadius(obj, npc.getAggroRange(), true, false) )
  398. {
  399. L2PcInstance targetPlayer = (obj instanceof L2PcInstance)? (L2PcInstance) obj: ((L2Summon) obj).getOwner();
  400. if (npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_AGGRO_RANGE_ENTER) !=null)
  401. for (Quest quest: npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_AGGRO_RANGE_ENTER))
  402. quest.notifyAggroRangeEnter(npc, targetPlayer, (obj instanceof L2Summon));
  403. }
  404. }
  405. */
  406. // TODO: The AI Script ought to handle aggro behaviors in onSee. Once implemented, aggro behaviors ought
  407. // to be removed from here. (Fulminus)
  408. // For each L2Character check if the target is autoattackable
  409. if (autoAttackCondition(target)) // check aggression
  410. {
  411. // Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
  412. int hating = npc.getHating(target);
  413. // Add the attacker to the L2Attackable _aggroList with 0 damage and 1 hate
  414. if (hating == 0)
  415. npc.addDamageHate(target, 0, 1);
  416. }
  417. }
  418. }
  419. // Chose a target from its aggroList
  420. L2Character hated;
  421. if (_actor.isConfused())
  422. hated = getAttackTarget(); // effect handles selection
  423. else
  424. hated = npc.getMostHated();
  425. // Order to the L2Attackable to attack the target
  426. if (hated != null)
  427. {
  428. // Get the hate level of the L2Attackable against this L2Character target contained in _aggroList
  429. int aggro = npc.getHating(hated);
  430. if (aggro + _globalAggro > 0)
  431. {
  432. // Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
  433. if (!_actor.isRunning())
  434. _actor.setRunning();
  435. // Set the AI Intention to AI_INTENTION_ATTACK
  436. setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
  437. }
  438. return;
  439. }
  440. }
  441. // Check if the actor is a L2GuardInstance
  442. if (_actor instanceof L2GuardInstance)
  443. {
  444. // Order to the L2GuardInstance to return to its home location because there's no target to attack
  445. ((L2GuardInstance) _actor).returnHome();
  446. }
  447. // If this is a festival monster, then it remains in the same location.
  448. if (_actor instanceof L2FestivalMonsterInstance)
  449. return;
  450. // Check if the mob should not return to spawn point
  451. if (!npc.canReturnToSpawnPoint())
  452. return;
  453. // Minions following leader
  454. if (_actor instanceof L2MinionInstance && ((L2MinionInstance) _actor).getLeader() != null)
  455. {
  456. int offset;
  457. if (_actor.isRaid())
  458. offset = 500; // for Raids - need correction
  459. else
  460. offset = 200; // for normal minions - need correction :)
  461. if (((L2MinionInstance) _actor).getLeader().isRunning())
  462. _actor.setRunning();
  463. else
  464. _actor.setWalking();
  465. if (_actor.getPlanDistanceSq(((L2MinionInstance) _actor).getLeader()) > offset * offset)
  466. {
  467. int x1, y1, z1;
  468. x1 = ((L2MinionInstance) _actor).getLeader().getX() + Rnd.nextInt((offset - 30) * 2) - (offset - 30);
  469. y1 = ((L2MinionInstance) _actor).getLeader().getY() + Rnd.nextInt((offset - 30) * 2) - (offset - 30);
  470. z1 = ((L2MinionInstance) _actor).getLeader().getZ();
  471. // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
  472. moveTo(x1, y1, z1);
  473. return;
  474. }
  475. else if (Rnd.nextInt(RANDOM_WALK_RATE) == 0)
  476. {
  477. // self and clan buffs
  478. for (L2Skill sk : _selfAnalysis.buffSkills)
  479. {
  480. if (_actor.getFirstEffect(sk.getId()) == null)
  481. {
  482. // if clan buffs, don't buff every time
  483. if (sk.getTargetType() != L2Skill.SkillTargetType.TARGET_SELF && Rnd.nextInt(2) != 0)
  484. continue;
  485. if (_actor.getCurrentMp() < sk.getMpConsume())
  486. continue;
  487. if (_actor.isSkillDisabled(sk.getId()))
  488. continue;
  489. L2Object OldTarget = _actor.getTarget();
  490. _actor.setTarget(_actor);
  491. clientStopMoving(null);
  492. _accessor.doCast(sk);
  493. _actor.setTarget(OldTarget);
  494. return;
  495. }
  496. }
  497. }
  498. }
  499. // Order to the L2MonsterInstance to random walk (1/100)
  500. else if (npc.getSpawn() != null && Rnd.nextInt(RANDOM_WALK_RATE) == 0 && !(_actor instanceof L2RaidBossInstance || _actor instanceof L2MinionInstance || _actor instanceof L2GrandBossInstance))
  501. {
  502. int x1, y1, z1;
  503. int range = Config.MAX_DRIFT_RANGE;
  504. // self and clan buffs
  505. for (L2Skill sk : _selfAnalysis.buffSkills)
  506. {
  507. if (_actor.getFirstEffect(sk.getId()) == null)
  508. {
  509. // if clan buffs, don't buff every time
  510. if (sk.getTargetType() != L2Skill.SkillTargetType.TARGET_SELF && Rnd.nextInt(2) != 0)
  511. continue;
  512. if (_actor.getCurrentMp() < sk.getMpConsume())
  513. continue;
  514. if (_actor.isSkillDisabled(sk.getId()))
  515. continue;
  516. L2Object OldTarget = _actor.getTarget();
  517. _actor.setTarget(_actor);
  518. clientStopMoving(null);
  519. _accessor.doCast(sk);
  520. _actor.setTarget(OldTarget);
  521. return;
  522. }
  523. }
  524. // If NPC with random coord in territory
  525. if (npc.getSpawn().getLocx() == 0 && npc.getSpawn().getLocy() == 0)
  526. {
  527. // Calculate a destination point in the spawn area
  528. int p[] = Territory.getInstance().getRandomPoint(npc.getSpawn().getLocation());
  529. x1 = p[0];
  530. y1 = p[1];
  531. z1 = p[2];
  532. // Calculate the distance between the current position of the L2Character and the target (x,y)
  533. double distance2 = _actor.getPlanDistanceSq(x1, y1);
  534. if (distance2 > range * range)
  535. {
  536. npc.setisReturningToSpawnPoint(true);
  537. float delay = (float) Math.sqrt(distance2) / range;
  538. x1 = _actor.getX() + (int) ((x1 - _actor.getX()) / delay);
  539. y1 = _actor.getY() + (int) ((y1 - _actor.getY()) / delay);
  540. }
  541. // If NPC with random fixed coord, don't move (unless needs to return to spawnpoint)
  542. if (Territory.getInstance().getProcMax(npc.getSpawn().getLocation()) > 0 && !npc.isReturningToSpawnPoint())
  543. return;
  544. }
  545. else
  546. {
  547. // If NPC with fixed coord
  548. x1 = npc.getSpawn().getLocx() + Rnd.nextInt(range * 2) - range;
  549. y1 = npc.getSpawn().getLocy() + Rnd.nextInt(range * 2) - range;
  550. z1 = npc.getZ();
  551. }
  552. //_log.config("Curent pos ("+getX()+", "+getY()+"), moving to ("+x1+", "+y1+").");
  553. // Move the actor to Location (x,y,z) server side AND client side by sending Server->Client packet CharMoveToLocation (broadcast)
  554. moveTo(x1, y1, z1);
  555. }
  556. }
  557. /**
  558. * Manage AI attack thinks of a L2Attackable (called by onEvtThink).<BR><BR>
  559. *
  560. * <B><U> Actions</U> :</B><BR><BR>
  561. * <li>Update the attack timeout if actor is running</li>
  562. * <li>If target is dead or timeout is expired, stop this attack and set the Intention to AI_INTENTION_ACTIVE</li>
  563. * <li>Call all L2Object of its Faction inside the Faction Range</li>
  564. * <li>Chose a target and order to attack it with magic skill or physical attack</li><BR><BR>
  565. *
  566. * TODO: Manage casting rules to healer mobs (like Ant Nurses)
  567. *
  568. */
  569. private void thinkAttack()
  570. {
  571. if (_attackTimeout < GameTimeController.getGameTicks())
  572. {
  573. // Check if the actor is running
  574. if (_actor.isRunning())
  575. {
  576. // Set the actor movement type to walk and send Server->Client packet ChangeMoveType to all others L2PcInstance
  577. _actor.setWalking();
  578. // Calculate a new attack timeout
  579. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  580. }
  581. }
  582. L2Character originalAttackTarget = getAttackTarget();
  583. // Check if target is dead or if timeout is expired to stop this attack
  584. if (originalAttackTarget == null || originalAttackTarget.isAlikeDead() || _attackTimeout < GameTimeController.getGameTicks())
  585. {
  586. // Stop hating this target after the attack timeout or if target is dead
  587. if (originalAttackTarget != null)
  588. ((L2Attackable) _actor).stopHating(originalAttackTarget);
  589. // Set the AI Intention to AI_INTENTION_ACTIVE
  590. setIntention(AI_INTENTION_ACTIVE);
  591. _actor.setWalking();
  592. return;
  593. }
  594. // Handle all L2Object of its Faction inside the Faction Range
  595. if (((L2NpcInstance) _actor).getFactionId() != null)
  596. {
  597. String faction_id = ((L2NpcInstance) _actor).getFactionId();
  598. // Go through all L2Object that belong to its faction
  599. Collection<L2Object> objs = _actor.getKnownList().getKnownObjects().values();
  600. //synchronized (_actor.getKnownList().getKnownObjects())
  601. {
  602. for (L2Object obj : objs)
  603. {
  604. if (obj instanceof L2NpcInstance)
  605. {
  606. L2NpcInstance npc = (L2NpcInstance) obj;
  607. if (faction_id != npc.getFactionId())
  608. continue;
  609. // Check if the L2Object is inside the Faction Range of
  610. // the actor
  611. if (_actor.isInsideRadius(npc, npc.getFactionRange() + npc.getTemplate().collisionRadius, true, false) && npc.getAI() != null)
  612. {
  613. if (Math.abs(originalAttackTarget.getZ() - npc.getZ()) < 600 && _actor.getAttackByList().contains(originalAttackTarget)
  614. && (npc.getAI()._intention == CtrlIntention.AI_INTENTION_IDLE || npc.getAI()._intention == CtrlIntention.AI_INTENTION_ACTIVE) && GeoData.getInstance().canSeeTarget(_actor, npc))
  615. {
  616. if ((originalAttackTarget instanceof L2PcInstance) || (originalAttackTarget instanceof L2Summon))
  617. {
  618. if (npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_FACTION_CALL) != null)
  619. {
  620. L2PcInstance player = (originalAttackTarget instanceof L2PcInstance) ? (L2PcInstance) originalAttackTarget : ((L2Summon) originalAttackTarget).getOwner();
  621. for (Quest quest : npc.getTemplate().getEventQuests(Quest.QuestEventType.ON_FACTION_CALL))
  622. quest.notifyFactionCall(npc, (L2NpcInstance) _actor, player, (originalAttackTarget instanceof L2Summon));
  623. }
  624. }
  625. }
  626. // heal or resurrect friends
  627. if (_selfAnalysis.hasHealOrResurrect && !_actor.isAttackingDisabled() && npc.getCurrentHp() < npc.getMaxHp() * 0.6 && _actor.getCurrentHp() > _actor.getMaxHp() / 2
  628. && _actor.getCurrentMp() > _actor.getMaxMp() / 2
  629. )
  630. {
  631. if (npc.isDead() && _actor instanceof L2MinionInstance)
  632. {
  633. if (((L2MinionInstance) _actor).getLeader() == npc)
  634. {
  635. for (L2Skill sk : _selfAnalysis.resurrectSkills)
  636. {
  637. if (_actor.getCurrentMp() < sk.getMpConsume())
  638. continue;
  639. if (_actor.isSkillDisabled(sk.getId()))
  640. continue;
  641. if (!Util.checkIfInRange(sk.getCastRange(), _actor, npc, true))
  642. continue;
  643. if (10 >= Rnd.get(100)) // chance
  644. continue;
  645. if (!GeoData.getInstance().canSeeTarget(_actor, npc))
  646. break;
  647. L2Object OldTarget = _actor.getTarget();
  648. _actor.setTarget(npc);
  649. // would this ever be fast enough
  650. // for the decay not to run?
  651. // giving some extra seconds
  652. DecayTaskManager.getInstance().cancelDecayTask(npc);
  653. DecayTaskManager.getInstance().addDecayTask(npc);
  654. clientStopMoving(null);
  655. _accessor.doCast(sk);
  656. _actor.setTarget(OldTarget);
  657. return;
  658. }
  659. }
  660. }
  661. else if (npc.isInCombat())
  662. {
  663. for (L2Skill sk : _selfAnalysis.healSkills)
  664. {
  665. if (_actor.getCurrentMp() < sk.getMpConsume())
  666. continue;
  667. if (_actor.isSkillDisabled(sk.getId()))
  668. continue;
  669. if (!Util.checkIfInRange(sk.getCastRange(), _actor, npc, true))
  670. continue;
  671. int chance = 4;
  672. if (_actor instanceof L2MinionInstance)
  673. {
  674. // minions support boss
  675. if (((L2MinionInstance) _actor).getLeader() == npc)
  676. chance = 6;
  677. else
  678. chance = 3;
  679. }
  680. if (npc instanceof L2GrandBossInstance)
  681. chance = 6;
  682. if (chance >= Rnd.get(100)) // chance
  683. continue;
  684. if (!GeoData.getInstance().canSeeTarget(_actor, npc))
  685. break;
  686. L2Object OldTarget = _actor.getTarget();
  687. _actor.setTarget(npc);
  688. clientStopMoving(null);
  689. _accessor.doCast(sk);
  690. _actor.setTarget(OldTarget);
  691. return;
  692. }
  693. }
  694. }
  695. }
  696. }
  697. }
  698. }
  699. }
  700. if (_actor.isAttackingDisabled())
  701. return;
  702. // Get 2 most hated chars
  703. List<L2Character> hated = ((L2Attackable) _actor).get2MostHated();
  704. if (_actor.isConfused())
  705. {
  706. if (hated != null)
  707. hated.set(0, originalAttackTarget); // effect handles selection
  708. else
  709. {
  710. hated = new FastList<L2Character>();
  711. hated.add(originalAttackTarget);
  712. hated.add(null);
  713. }
  714. }
  715. if (hated == null || hated.get(0) == null)
  716. {
  717. setIntention(AI_INTENTION_ACTIVE);
  718. return;
  719. }
  720. if (hated.get(0) != originalAttackTarget)
  721. {
  722. setAttackTarget(hated.get(0));
  723. }
  724. _mostHatedAnalysis.update(hated.get(0));
  725. _secondMostHatedAnalysis.update(hated.get(1));
  726. // Get all information needed to choose between physical or magical attack
  727. _actor.setTarget(_mostHatedAnalysis.character);
  728. double dist2 = _actor.getPlanDistanceSq(_mostHatedAnalysis.character.getX(), _mostHatedAnalysis.character.getY());
  729. int combinedCollision = _actor.getTemplate().collisionRadius + _mostHatedAnalysis.character.getTemplate().collisionRadius;
  730. int range = _actor.getPhysicalAttackRange() + combinedCollision;
  731. // Reconsider target next round if _actor hasn't got hits in for last 14 seconds
  732. if (!_actor.isMuted() && _attackTimeout - 160 < GameTimeController.getGameTicks() && _secondMostHatedAnalysis.character != null)
  733. {
  734. if (Util.checkIfInRange(900, _actor, hated.get(1), true))
  735. {
  736. // take off 2* the amount the aggro is larger than second most
  737. ((L2Attackable) _actor).reduceHate(hated.get(0), 2 * (((L2Attackable) _actor).getHating(hated.get(0)) - ((L2Attackable) _actor).getHating(hated.get(1))));
  738. // Calculate a new attack timeout
  739. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  740. }
  741. }
  742. // Reconsider target during next round if actor is rooted and cannot reach mostHated but can
  743. // reach secondMostHated
  744. if (_actor.isRooted() && _secondMostHatedAnalysis.character != null)
  745. {
  746. if (_selfAnalysis.isMage && dist2 > _selfAnalysis.maxCastRange * _selfAnalysis.maxCastRange
  747. && _actor.getPlanDistanceSq(_secondMostHatedAnalysis.character.getX(), _secondMostHatedAnalysis.character.getY()) < _selfAnalysis.maxCastRange * _selfAnalysis.maxCastRange)
  748. {
  749. ((L2Attackable) _actor).reduceHate(hated.get(0), 1 + (((L2Attackable) _actor).getHating(hated.get(0)) - ((L2Attackable) _actor).getHating(hated.get(1))));
  750. }
  751. else if (dist2 > range * range && _actor.getPlanDistanceSq(_secondMostHatedAnalysis.character.getX(), _secondMostHatedAnalysis.character.getY()) < range * range)
  752. {
  753. ((L2Attackable) _actor).reduceHate(hated.get(0), 1 + (((L2Attackable) _actor).getHating(hated.get(0)) - ((L2Attackable) _actor).getHating(hated.get(1))));
  754. }
  755. }
  756. // Considering, if bigger range will be attempted
  757. if ((dist2 < 10000 + combinedCollision * combinedCollision) && !_selfAnalysis.isFighter && !_selfAnalysis.isBalanced && (_selfAnalysis.hasLongRangeSkills || _selfAnalysis.isArcher)
  758. && (_mostHatedAnalysis.isBalanced || _mostHatedAnalysis.isFighter) && (_mostHatedAnalysis.character.isRooted() || _mostHatedAnalysis.isSlower) && (Config.GEODATA == 2 ? 20 : 12) >= Rnd.get(100) // chance
  759. )
  760. {
  761. int posX = _actor.getX();
  762. int posY = _actor.getY();
  763. int posZ = _actor.getZ();
  764. double distance = Math.sqrt(dist2); // This way, we only do the sqrt if we need it
  765. int signx = -1;
  766. int signy = -1;
  767. if (_actor.getX() > _mostHatedAnalysis.character.getX())
  768. signx = 1;
  769. if (_actor.getY() > _mostHatedAnalysis.character.getY())
  770. signy = 1;
  771. posX += Math.round((float) ((signx * ((range / 2) + (Rnd.get(range)))) - distance));
  772. posY += Math.round((float) ((signy * ((range / 2) + (Rnd.get(range)))) - distance));
  773. setIntention(CtrlIntention.AI_INTENTION_MOVE_TO, new L2CharPosition(posX, posY, posZ, 0));
  774. return;
  775. }
  776. // Cannot see target, needs to go closer, currently just goes to range 300 if mage
  777. if ((dist2 > 310 * 310 + combinedCollision * combinedCollision) && this._selfAnalysis.hasLongRangeSkills && !GeoData.getInstance().canSeeTarget(_actor, _mostHatedAnalysis.character))
  778. {
  779. if (!(_selfAnalysis.isMage && _actor.isMuted()))
  780. {
  781. moveToPawn(_mostHatedAnalysis.character, 300);
  782. return;
  783. }
  784. }
  785. if (_mostHatedAnalysis.character.isMoving())
  786. range += 50;
  787. // Check if the actor is far from target
  788. if (dist2 > range * range)
  789. {
  790. if (!_actor.isMuted() && (_selfAnalysis.hasLongRangeSkills || !_selfAnalysis.healSkills.isEmpty()))
  791. {
  792. // check for long ranged skills and heal/buff skills
  793. if (!_mostHatedAnalysis.isCanceled)
  794. {
  795. for (L2Skill sk : _selfAnalysis.cancelSkills)
  796. {
  797. int castRange = sk.getCastRange() + combinedCollision;
  798. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  799. continue;
  800. if (Rnd.nextInt(100) <= 8)
  801. {
  802. clientStopMoving(null);
  803. _accessor.doCast(sk);
  804. _mostHatedAnalysis.isCanceled = true;
  805. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  806. return;
  807. }
  808. }
  809. }
  810. if (this._selfAnalysis.lastDebuffTick + 60 < GameTimeController.getGameTicks())
  811. {
  812. for (L2Skill sk : _selfAnalysis.debuffSkills)
  813. {
  814. int castRange = sk.getCastRange() + combinedCollision;
  815. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  816. continue;
  817. int chance = 8;
  818. if (_selfAnalysis.isFighter && _mostHatedAnalysis.isMage)
  819. chance = 3;
  820. if (_selfAnalysis.isFighter && _mostHatedAnalysis.isArcher)
  821. chance = 12;
  822. if (_selfAnalysis.isMage && !_mostHatedAnalysis.isMage)
  823. chance = 10;
  824. if (_mostHatedAnalysis.isMagicResistant)
  825. chance /= 2;
  826. if (Rnd.nextInt(100) <= chance)
  827. {
  828. clientStopMoving(null);
  829. _accessor.doCast(sk);
  830. _selfAnalysis.lastDebuffTick = GameTimeController.getGameTicks();
  831. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  832. return;
  833. }
  834. }
  835. }
  836. if (!_mostHatedAnalysis.character.isMuted())
  837. {
  838. int chance = 8;
  839. if (!(_mostHatedAnalysis.isMage || _mostHatedAnalysis.isBalanced))
  840. chance = 3;
  841. for (L2Skill sk : _selfAnalysis.muteSkills)
  842. {
  843. int castRange = sk.getCastRange() + combinedCollision;
  844. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  845. continue;
  846. if (Rnd.nextInt(100) <= chance)
  847. {
  848. clientStopMoving(null);
  849. _accessor.doCast(sk);
  850. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  851. return;
  852. }
  853. }
  854. }
  855. if (_secondMostHatedAnalysis.character != null && !_secondMostHatedAnalysis.character.isMuted() && (_secondMostHatedAnalysis.isMage || _secondMostHatedAnalysis.isBalanced))
  856. {
  857. double secondHatedDist2 = _actor.getPlanDistanceSq(_secondMostHatedAnalysis.character.getX(), _secondMostHatedAnalysis.character.getY());
  858. for (L2Skill sk : _selfAnalysis.muteSkills)
  859. {
  860. int castRange = sk.getCastRange() + combinedCollision;
  861. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (secondHatedDist2 > castRange * castRange))
  862. continue;
  863. if (Rnd.nextInt(100) <= 2)
  864. {
  865. _actor.setTarget(_secondMostHatedAnalysis.character);
  866. clientStopMoving(null);
  867. _accessor.doCast(sk);
  868. _actor.setTarget(_mostHatedAnalysis.character);
  869. return;
  870. }
  871. }
  872. }
  873. if (!_mostHatedAnalysis.character.isSleeping())
  874. {
  875. for (L2Skill sk : _selfAnalysis.sleepSkills)
  876. {
  877. int castRange = sk.getCastRange() + combinedCollision;
  878. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  879. continue;
  880. if (Rnd.nextInt(100) <= 1)
  881. {
  882. clientStopMoving(null);
  883. _accessor.doCast(sk);
  884. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  885. return;
  886. }
  887. }
  888. }
  889. if (_secondMostHatedAnalysis.character != null && !_secondMostHatedAnalysis.character.isSleeping())
  890. {
  891. double secondHatedDist2 = _actor.getPlanDistanceSq(_secondMostHatedAnalysis.character.getX(), _secondMostHatedAnalysis.character.getY());
  892. for (L2Skill sk : _selfAnalysis.sleepSkills)
  893. {
  894. int castRange = sk.getCastRange() + combinedCollision;
  895. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (secondHatedDist2 > castRange * castRange))
  896. continue;
  897. if (Rnd.nextInt(100) <= 3)
  898. {
  899. _actor.setTarget(_secondMostHatedAnalysis.character);
  900. clientStopMoving(null);
  901. _accessor.doCast(sk);
  902. _actor.setTarget(_mostHatedAnalysis.character);
  903. return;
  904. }
  905. }
  906. }
  907. if (!_mostHatedAnalysis.character.isRooted())
  908. {
  909. for (L2Skill sk : _selfAnalysis.rootSkills)
  910. {
  911. int castRange = sk.getCastRange() + combinedCollision;
  912. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  913. continue;
  914. if (Rnd.nextInt(100) <= (_mostHatedAnalysis.isSlower ? 3 : 8))
  915. {
  916. clientStopMoving(null);
  917. _accessor.doCast(sk);
  918. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  919. return;
  920. }
  921. }
  922. }
  923. if (!_mostHatedAnalysis.character.isAttackingDisabled())
  924. {
  925. for (L2Skill sk : _selfAnalysis.generalDisablers)
  926. {
  927. int castRange = sk.getCastRange() + combinedCollision;
  928. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  929. continue;
  930. if (Rnd.nextInt(100) <= ((_selfAnalysis.isFighter && _actor.isRooted()) ? 15 : 7))
  931. {
  932. clientStopMoving(null);
  933. _accessor.doCast(sk);
  934. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  935. return;
  936. }
  937. }
  938. }
  939. if (_actor.getCurrentHp() < _actor.getMaxHp() * 0.4)
  940. {
  941. for (L2Skill sk : _selfAnalysis.healSkills)
  942. {
  943. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk))
  944. continue;
  945. int chance = 7;
  946. if (_mostHatedAnalysis.character.isAttackingDisabled())
  947. chance += 10;
  948. if (_secondMostHatedAnalysis.character == null || _secondMostHatedAnalysis.character.isAttackingDisabled())
  949. chance += 10;
  950. if (Rnd.nextInt(100) <= chance)
  951. {
  952. _actor.setTarget(_actor);
  953. clientStopMoving(null);
  954. _accessor.doCast(sk);
  955. _actor.setTarget(_mostHatedAnalysis.character);
  956. return;
  957. }
  958. }
  959. }
  960. // chance decision for launching long range skills
  961. int castingChance = 5;
  962. if (_selfAnalysis.isMage)
  963. castingChance = 50; // mages
  964. if (_selfAnalysis.isBalanced)
  965. {
  966. if (!_mostHatedAnalysis.isFighter) // advance to mages
  967. castingChance = 15;
  968. else
  969. castingChance = 25; // stay away from fighters
  970. }
  971. if (_selfAnalysis.isFighter)
  972. {
  973. if (_mostHatedAnalysis.isMage)
  974. castingChance = 3;
  975. else
  976. castingChance = 7;
  977. if (_actor.isRooted())
  978. castingChance = 20; // doesn't matter if no success first round
  979. }
  980. for (L2Skill sk : _selfAnalysis.generalSkills)
  981. {
  982. int castRange = sk.getCastRange() + combinedCollision;
  983. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  984. continue;
  985. if (Rnd.nextInt(100) <= castingChance)
  986. {
  987. clientStopMoving(null);
  988. _accessor.doCast(sk);
  989. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  990. return;
  991. }
  992. }
  993. }
  994. // Move the actor to Pawn server side AND client side by sending Server->Client packet MoveToPawn (broadcast)
  995. if (_selfAnalysis.isMage)
  996. {
  997. if (_actor.isMuted())
  998. return;
  999. range = _selfAnalysis.maxCastRange;
  1000. }
  1001. if (_mostHatedAnalysis.character.isMoving())
  1002. range -= 100;
  1003. if (range < 5)
  1004. range = 5;
  1005. moveToPawn(_mostHatedAnalysis.character, range);
  1006. return;
  1007. }
  1008. // **************************************************
  1009. // Else, if this is close enough for physical attacks
  1010. else
  1011. {
  1012. // In case many mobs are trying to hit from same place, move a bit,
  1013. // circling around the target
  1014. if (Rnd.nextInt(100) <= 33) // check it once per 3 seconds
  1015. {
  1016. for (L2Object nearby : _actor.getKnownList().getKnownCharactersInRadius(10))
  1017. {
  1018. if (nearby instanceof L2Attackable && nearby != _mostHatedAnalysis.character)
  1019. {
  1020. int diffx = Rnd.get(combinedCollision, combinedCollision + 40);
  1021. if (Rnd.get(10) < 5)
  1022. diffx = -diffx;
  1023. int diffy = Rnd.get(combinedCollision, combinedCollision + 40);
  1024. if (Rnd.get(10) < 5)
  1025. diffy = -diffy;
  1026. moveTo(_mostHatedAnalysis.character.getX() + diffx, _mostHatedAnalysis.character.getY() + diffy, _mostHatedAnalysis.character.getZ());
  1027. return;
  1028. }
  1029. }
  1030. }
  1031. // Calculate a new attack timeout.
  1032. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  1033. // check for close combat skills && heal/buff skills
  1034. if (!_mostHatedAnalysis.isCanceled)
  1035. {
  1036. for (L2Skill sk : _selfAnalysis.cancelSkills)
  1037. {
  1038. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1039. continue;
  1040. int castRange = sk.getCastRange() + combinedCollision;
  1041. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  1042. continue;
  1043. if (Rnd.nextInt(100) <= 8)
  1044. {
  1045. clientStopMoving(null);
  1046. _accessor.doCast(sk);
  1047. _mostHatedAnalysis.isCanceled = true;
  1048. return;
  1049. }
  1050. }
  1051. }
  1052. if (this._selfAnalysis.lastDebuffTick + 60 < GameTimeController.getGameTicks())
  1053. {
  1054. for (L2Skill sk : _selfAnalysis.debuffSkills)
  1055. {
  1056. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1057. continue;
  1058. int castRange = sk.getCastRange() + combinedCollision;
  1059. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  1060. continue;
  1061. int chance = 5;
  1062. if (_selfAnalysis.isFighter && _mostHatedAnalysis.isMage)
  1063. chance = 3;
  1064. if (_selfAnalysis.isFighter && _mostHatedAnalysis.isArcher)
  1065. chance = 3;
  1066. if (_selfAnalysis.isMage && !_mostHatedAnalysis.isMage)
  1067. chance = 4;
  1068. if (_mostHatedAnalysis.isMagicResistant)
  1069. chance /= 2;
  1070. if (sk.getCastRange() < 200)
  1071. chance += 3;
  1072. if (Rnd.nextInt(100) <= chance)
  1073. {
  1074. clientStopMoving(null);
  1075. _accessor.doCast(sk);
  1076. _selfAnalysis.lastDebuffTick = GameTimeController.getGameTicks();
  1077. return;
  1078. }
  1079. }
  1080. }
  1081. if (!_mostHatedAnalysis.character.isMuted() && (_mostHatedAnalysis.isMage || _mostHatedAnalysis.isBalanced))
  1082. {
  1083. for (L2Skill sk : _selfAnalysis.muteSkills)
  1084. {
  1085. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1086. continue;
  1087. int castRange = sk.getCastRange() + combinedCollision;
  1088. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  1089. continue;
  1090. if (Rnd.nextInt(100) <= 7)
  1091. {
  1092. clientStopMoving(null);
  1093. _accessor.doCast(sk);
  1094. return;
  1095. }
  1096. }
  1097. }
  1098. if (_secondMostHatedAnalysis.character != null && !_secondMostHatedAnalysis.character.isMuted() && (_secondMostHatedAnalysis.isMage || _secondMostHatedAnalysis.isBalanced))
  1099. {
  1100. double secondHatedDist2 = _actor.getPlanDistanceSq(_secondMostHatedAnalysis.character.getX(), _secondMostHatedAnalysis.character.getY());
  1101. for (L2Skill sk : _selfAnalysis.muteSkills)
  1102. {
  1103. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1104. continue;
  1105. int castRange = sk.getCastRange() + combinedCollision;
  1106. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (secondHatedDist2 > castRange * castRange))
  1107. continue;
  1108. if (Rnd.nextInt(100) <= 3)
  1109. {
  1110. _actor.setTarget(_secondMostHatedAnalysis.character);
  1111. clientStopMoving(null);
  1112. _accessor.doCast(sk);
  1113. _actor.setTarget(_mostHatedAnalysis.character);
  1114. return;
  1115. }
  1116. }
  1117. }
  1118. if (_secondMostHatedAnalysis.character != null && !_secondMostHatedAnalysis.character.isSleeping())
  1119. {
  1120. double secondHatedDist2 = _actor.getPlanDistanceSq(_secondMostHatedAnalysis.character.getX(), _secondMostHatedAnalysis.character.getY());
  1121. for (L2Skill sk : _selfAnalysis.sleepSkills)
  1122. {
  1123. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1124. continue;
  1125. int castRange = sk.getCastRange() + combinedCollision;
  1126. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (secondHatedDist2 > castRange * castRange))
  1127. continue;
  1128. if (Rnd.nextInt(100) <= 4)
  1129. {
  1130. _actor.setTarget(_secondMostHatedAnalysis.character);
  1131. clientStopMoving(null);
  1132. _accessor.doCast(sk);
  1133. _actor.setTarget(_mostHatedAnalysis.character);
  1134. return;
  1135. }
  1136. }
  1137. }
  1138. if (!_mostHatedAnalysis.character.isRooted() && _mostHatedAnalysis.isFighter && !_selfAnalysis.isFighter)
  1139. {
  1140. for (L2Skill sk : _selfAnalysis.rootSkills)
  1141. {
  1142. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1143. continue;
  1144. int castRange = sk.getCastRange() + combinedCollision;
  1145. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  1146. continue;
  1147. if (Rnd.nextInt(100) <= 4)
  1148. {
  1149. clientStopMoving(null);
  1150. _accessor.doCast(sk);
  1151. return;
  1152. }
  1153. }
  1154. }
  1155. if (!_mostHatedAnalysis.character.isAttackingDisabled())
  1156. {
  1157. for (L2Skill sk : _selfAnalysis.generalDisablers)
  1158. {
  1159. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1160. continue;
  1161. int castRange = sk.getCastRange() + combinedCollision;
  1162. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  1163. continue;
  1164. if (Rnd.nextInt(100) <= ((sk.getCastRange() < 200) ? 10 : 7))
  1165. {
  1166. clientStopMoving(null);
  1167. _accessor.doCast(sk);
  1168. return;
  1169. }
  1170. }
  1171. }
  1172. if (_actor.getCurrentHp() < _actor.getMaxHp() * 0.4)
  1173. {
  1174. for (L2Skill sk : _selfAnalysis.healSkills)
  1175. {
  1176. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1177. continue;
  1178. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk))
  1179. continue;
  1180. int chance = 7;
  1181. if (_mostHatedAnalysis.character.isAttackingDisabled())
  1182. chance += 10;
  1183. if (_secondMostHatedAnalysis.character == null || _secondMostHatedAnalysis.character.isAttackingDisabled())
  1184. chance += 10;
  1185. if (Rnd.nextInt(100) <= chance)
  1186. {
  1187. _actor.setTarget(_actor);
  1188. clientStopMoving(null);
  1189. _accessor.doCast(sk);
  1190. _actor.setTarget(_mostHatedAnalysis.character);
  1191. return;
  1192. }
  1193. }
  1194. }
  1195. for (L2Skill sk : _selfAnalysis.generalSkills)
  1196. {
  1197. if ((_actor.isMuted() && sk.isMagic()) || (_actor.isPhysicalMuted() && !sk.isMagic()))
  1198. continue;
  1199. int castRange = sk.getCastRange() + combinedCollision;
  1200. if (_actor.isSkillDisabled(sk.getId()) || _actor.getCurrentMp() < _actor.getStat().getMpConsume(sk) || (dist2 > castRange * castRange))
  1201. continue;
  1202. // chance decision for launching general skills in melee fight
  1203. // close range skills should be higher, long range lower
  1204. int castingChance = 5;
  1205. if (_selfAnalysis.isMage)
  1206. {
  1207. if (sk.getCastRange() < 200)
  1208. castingChance = 35;
  1209. else
  1210. castingChance = 25; // mages
  1211. }
  1212. if (_selfAnalysis.isBalanced)
  1213. {
  1214. if (sk.getCastRange() < 200)
  1215. castingChance = 12;
  1216. else
  1217. {
  1218. if (_mostHatedAnalysis.isMage) // hit mages
  1219. castingChance = 2;
  1220. else
  1221. castingChance = 5;
  1222. }
  1223. }
  1224. if (_selfAnalysis.isFighter)
  1225. {
  1226. if (sk.getCastRange() < 200)
  1227. castingChance = 12;
  1228. else
  1229. {
  1230. if (_mostHatedAnalysis.isMage)
  1231. castingChance = 1;
  1232. else
  1233. castingChance = 3;
  1234. }
  1235. }
  1236. if (Rnd.nextInt(100) <= castingChance)
  1237. {
  1238. clientStopMoving(null);
  1239. _accessor.doCast(sk);
  1240. return;
  1241. }
  1242. }
  1243. // Finally, physical attacks
  1244. clientStopMoving(null);
  1245. _accessor.doAttack(getAttackTarget());
  1246. }
  1247. }
  1248. /**
  1249. * Manage AI thinking actions of a L2Attackable.<BR><BR>
  1250. */
  1251. @Override
  1252. protected void onEvtThink()
  1253. {
  1254. // Check if the actor can't use skills and if a thinking action isn't already in progress
  1255. if (_thinking || _actor.isAllSkillsDisabled())
  1256. return;
  1257. // Start thinking action
  1258. _thinking = true;
  1259. try
  1260. {
  1261. // Manage AI thinks of a L2Attackable
  1262. if (getIntention() == AI_INTENTION_ACTIVE)
  1263. thinkActive();
  1264. else if (getIntention() == AI_INTENTION_ATTACK)
  1265. thinkAttack();
  1266. }
  1267. finally
  1268. {
  1269. // Stop thinking action
  1270. _thinking = false;
  1271. }
  1272. }
  1273. /**
  1274. * Launch actions corresponding to the Event Attacked.<BR><BR>
  1275. *
  1276. * <B><U> Actions</U> :</B><BR><BR>
  1277. * <li>Init the attack : Calculate the attack timeout, Set the _globalAggro to 0, Add the attacker to the actor _aggroList</li>
  1278. * <li>Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance</li>
  1279. * <li>Set the Intention to AI_INTENTION_ATTACK</li><BR><BR>
  1280. *
  1281. * @param attacker The L2Character that attacks the actor
  1282. *
  1283. */
  1284. @Override
  1285. protected void onEvtAttacked(L2Character attacker)
  1286. {
  1287. //if (_actor instanceof L2ChestInstance && !((L2ChestInstance)_actor).isInteracted())
  1288. //{
  1289. //((L2ChestInstance)_actor).deleteMe();
  1290. //((L2ChestInstance)_actor).getSpawn().startRespawn();
  1291. //return;
  1292. //}
  1293. // Calculate the attack timeout
  1294. _attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeController.getGameTicks();
  1295. // Set the _globalAggro to 0 to permit attack even just after spawn
  1296. if (_globalAggro < 0)
  1297. _globalAggro = 0;
  1298. // Add the attacker to the _aggroList of the actor
  1299. ((L2Attackable) _actor).addDamageHate(attacker, 0, 1);
  1300. // Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
  1301. if (!_actor.isRunning())
  1302. _actor.setRunning();
  1303. // Set the Intention to AI_INTENTION_ATTACK
  1304. if (getIntention() != AI_INTENTION_ATTACK)
  1305. {
  1306. setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
  1307. }
  1308. else if (((L2Attackable) _actor).getMostHated() != getAttackTarget())
  1309. {
  1310. setIntention(CtrlIntention.AI_INTENTION_ATTACK, attacker);
  1311. }
  1312. super.onEvtAttacked(attacker);
  1313. }
  1314. /**
  1315. * Launch actions corresponding to the Event Aggression.<BR><BR>
  1316. *
  1317. * <B><U> Actions</U> :</B><BR><BR>
  1318. * <li>Add the target to the actor _aggroList or update hate if already present </li>
  1319. * <li>Set the actor Intention to AI_INTENTION_ATTACK (if actor is L2GuardInstance check if it isn't too far from its home location)</li><BR><BR>
  1320. *
  1321. * @param attacker The L2Character that attacks
  1322. * @param aggro The value of hate to add to the actor against the target
  1323. *
  1324. */
  1325. @Override
  1326. protected void onEvtAggression(L2Character target, int aggro)
  1327. {
  1328. L2Attackable me = (L2Attackable) _actor;
  1329. if (target != null)
  1330. {
  1331. // Add the target to the actor _aggroList or update hate if already present
  1332. me.addDamageHate(target, 0, aggro);
  1333. // Set the actor AI Intention to AI_INTENTION_ATTACK
  1334. if (getIntention() != CtrlIntention.AI_INTENTION_ATTACK)
  1335. {
  1336. // Set the L2Character movement type to run and send Server->Client packet ChangeMoveType to all others L2PcInstance
  1337. if (!_actor.isRunning())
  1338. _actor.setRunning();
  1339. setIntention(CtrlIntention.AI_INTENTION_ATTACK, target);
  1340. }
  1341. }
  1342. }
  1343. @Override
  1344. protected void onIntentionActive()
  1345. {
  1346. // Cancel attack timeout
  1347. _attackTimeout = Integer.MAX_VALUE;
  1348. super.onIntentionActive();
  1349. }
  1350. public void setGlobalAggro(int value)
  1351. {
  1352. _globalAggro = value;
  1353. }
  1354. }