L2ControllableMobAI.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  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 com.l2jserver.gameserver.ai;
  16. import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
  17. import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
  18. import java.util.Collection;
  19. import java.util.List;
  20. import java.util.logging.Level;
  21. import java.util.logging.Logger;
  22. import javolution.util.FastList;
  23. import com.l2jserver.gameserver.model.L2Object;
  24. import com.l2jserver.gameserver.model.MobGroup;
  25. import com.l2jserver.gameserver.model.MobGroupTable;
  26. import com.l2jserver.gameserver.model.actor.L2Attackable;
  27. import com.l2jserver.gameserver.model.actor.L2Character;
  28. import com.l2jserver.gameserver.model.actor.L2Character.AIAccessor;
  29. import com.l2jserver.gameserver.model.actor.L2Npc;
  30. import com.l2jserver.gameserver.model.actor.L2Playable;
  31. import com.l2jserver.gameserver.model.actor.instance.L2ControllableMobInstance;
  32. import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
  33. import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance;
  34. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  35. import com.l2jserver.gameserver.model.skills.L2Skill;
  36. import com.l2jserver.gameserver.util.Util;
  37. import com.l2jserver.util.Rnd;
  38. /**
  39. * AI for controllable mobs
  40. * @author littlecrow
  41. */
  42. public class L2ControllableMobAI extends L2AttackableAI
  43. {
  44. private static final Logger _log = Logger.getLogger(L2ControllableMobAI.class.getName());
  45. public static final int AI_IDLE = 1;
  46. public static final int AI_NORMAL = 2;
  47. public static final int AI_FORCEATTACK = 3;
  48. public static final int AI_FOLLOW = 4;
  49. public static final int AI_CAST = 5;
  50. public static final int AI_ATTACK_GROUP = 6;
  51. private int _alternateAI;
  52. private boolean _isThinking; // to prevent thinking recursively
  53. private boolean _isNotMoving;
  54. private L2Character _forcedTarget;
  55. private MobGroup _targetGroup;
  56. protected void thinkFollow()
  57. {
  58. L2Attackable me = (L2Attackable) _actor;
  59. if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, me, getForcedTarget(), true))
  60. {
  61. int signX = (Rnd.nextInt(2) == 0) ? -1 : 1;
  62. int signY = (Rnd.nextInt(2) == 0) ? -1 : 1;
  63. int randX = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
  64. int randY = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
  65. moveTo(getForcedTarget().getX() + (signX * randX), getForcedTarget().getY() + (signY * randY), getForcedTarget().getZ());
  66. }
  67. }
  68. @Override
  69. protected void onEvtThink()
  70. {
  71. if (isThinking())
  72. {
  73. return;
  74. }
  75. setThinking(true);
  76. try
  77. {
  78. switch (getAlternateAI())
  79. {
  80. case AI_IDLE:
  81. if (getIntention() != CtrlIntention.AI_INTENTION_ACTIVE)
  82. {
  83. setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  84. }
  85. break;
  86. case AI_FOLLOW:
  87. thinkFollow();
  88. break;
  89. case AI_CAST:
  90. thinkCast();
  91. break;
  92. case AI_FORCEATTACK:
  93. thinkForceAttack();
  94. break;
  95. case AI_ATTACK_GROUP:
  96. thinkAttackGroup();
  97. break;
  98. default:
  99. if (getIntention() == AI_INTENTION_ACTIVE)
  100. {
  101. thinkActive();
  102. }
  103. else if (getIntention() == AI_INTENTION_ATTACK)
  104. {
  105. thinkAttack();
  106. }
  107. break;
  108. }
  109. }
  110. finally
  111. {
  112. setThinking(false);
  113. }
  114. }
  115. protected void thinkCast()
  116. {
  117. L2Attackable npc = (L2Attackable) _actor;
  118. if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
  119. {
  120. setAttackTarget(findNextRndTarget());
  121. clientStopMoving(null);
  122. }
  123. if (getAttackTarget() == null)
  124. {
  125. return;
  126. }
  127. npc.setTarget(getAttackTarget());
  128. L2Skill[] skills = null;
  129. // double dist2 = 0;
  130. try
  131. {
  132. skills = _actor.getAllSkills();
  133. // dist2 = _actor.getPlanDistanceSq(getAttackTarget().getX(), getAttackTarget().getY());
  134. }
  135. catch (NullPointerException e)
  136. {
  137. _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e);
  138. }
  139. if (!_actor.isMuted())
  140. {
  141. int max_range = 0;
  142. // check distant skills
  143. for (L2Skill sk : skills)
  144. {
  145. if (Util.checkIfInRange(sk.getCastRange(), _actor, getAttackTarget(), true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
  146. {
  147. _accessor.doCast(sk);
  148. return;
  149. }
  150. max_range = Math.max(max_range, sk.getCastRange());
  151. }
  152. if (!isNotMoving())
  153. {
  154. moveToPawn(getAttackTarget(), max_range);
  155. }
  156. return;
  157. }
  158. }
  159. protected void thinkAttackGroup()
  160. {
  161. L2Character target = getForcedTarget();
  162. if ((target == null) || target.isAlikeDead())
  163. {
  164. // try to get next group target
  165. setForcedTarget(findNextGroupTarget());
  166. clientStopMoving(null);
  167. }
  168. if (target == null)
  169. {
  170. return;
  171. }
  172. L2Skill[] skills = null;
  173. double dist2 = 0;
  174. int range = 0;
  175. int max_range = 0;
  176. _actor.setTarget(target);
  177. // as a response, we put the target in a forcedattack mode
  178. L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target;
  179. L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI();
  180. ctrlAi.forceAttack(_actor);
  181. try
  182. {
  183. skills = _actor.getAllSkills();
  184. dist2 = _actor.getPlanDistanceSq(target.getX(), target.getY());
  185. range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
  186. max_range = range;
  187. }
  188. catch (NullPointerException e)
  189. {
  190. _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e);
  191. }
  192. if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
  193. {
  194. // check distant skills
  195. for (L2Skill sk : skills)
  196. {
  197. int castRange = sk.getCastRange();
  198. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
  199. {
  200. _accessor.doCast(sk);
  201. return;
  202. }
  203. max_range = Math.max(max_range, castRange);
  204. }
  205. if (!isNotMoving())
  206. {
  207. moveToPawn(target, range);
  208. }
  209. return;
  210. }
  211. _accessor.doAttack(target);
  212. }
  213. protected void thinkForceAttack()
  214. {
  215. if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead())
  216. {
  217. clientStopMoving(null);
  218. setIntention(AI_INTENTION_ACTIVE);
  219. setAlternateAI(AI_IDLE);
  220. }
  221. L2Skill[] skills = null;
  222. double dist2 = 0;
  223. int range = 0;
  224. int max_range = 0;
  225. try
  226. {
  227. _actor.setTarget(getForcedTarget());
  228. skills = _actor.getAllSkills();
  229. dist2 = _actor.getPlanDistanceSq(getForcedTarget().getX(), getForcedTarget().getY());
  230. range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius();
  231. max_range = range;
  232. }
  233. catch (NullPointerException e)
  234. {
  235. _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e);
  236. }
  237. if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
  238. {
  239. // check distant skills
  240. for (L2Skill sk : skills)
  241. {
  242. int castRange = sk.getCastRange();
  243. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
  244. {
  245. _accessor.doCast(sk);
  246. return;
  247. }
  248. max_range = Math.max(max_range, castRange);
  249. }
  250. if (!isNotMoving())
  251. {
  252. moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
  253. }
  254. return;
  255. }
  256. _accessor.doAttack(getForcedTarget());
  257. }
  258. protected void thinkAttack()
  259. {
  260. if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead())
  261. {
  262. if (getAttackTarget() != null)
  263. {
  264. // stop hating
  265. L2Attackable npc = (L2Attackable) _actor;
  266. npc.stopHating(getAttackTarget());
  267. }
  268. setIntention(AI_INTENTION_ACTIVE);
  269. }
  270. else
  271. {
  272. // notify aggression
  273. if (((L2Npc) _actor).getFactionId() != null)
  274. {
  275. String faction_id = ((L2Npc) _actor).getFactionId();
  276. Collection<L2Object> objs = _actor.getKnownList().getKnownObjects().values();
  277. for (L2Object obj : objs)
  278. {
  279. if (!(obj instanceof L2Npc))
  280. {
  281. continue;
  282. }
  283. L2Npc npc = (L2Npc) obj;
  284. if (!faction_id.equals(npc.getFactionId()))
  285. {
  286. continue;
  287. }
  288. if (_actor.isInsideRadius(npc, npc.getFactionRange(), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200))
  289. {
  290. npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
  291. }
  292. }
  293. }
  294. L2Skill[] skills = null;
  295. double dist2 = 0;
  296. int range = 0;
  297. int max_range = 0;
  298. try
  299. {
  300. _actor.setTarget(getAttackTarget());
  301. skills = _actor.getAllSkills();
  302. dist2 = _actor.getPlanDistanceSq(getAttackTarget().getX(), getAttackTarget().getY());
  303. range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius();
  304. max_range = range;
  305. }
  306. catch (NullPointerException e)
  307. {
  308. _log.log(Level.WARNING, "Encountered Null Value: " + e.getMessage(), e);
  309. }
  310. if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20))))
  311. {
  312. // check distant skills
  313. for (L2Skill sk : skills)
  314. {
  315. int castRange = sk.getCastRange();
  316. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume(sk)))
  317. {
  318. _accessor.doCast(sk);
  319. return;
  320. }
  321. max_range = Math.max(max_range, castRange);
  322. }
  323. moveToPawn(getAttackTarget(), range);
  324. return;
  325. }
  326. // Force mobs to attack anybody if confused.
  327. L2Character hated;
  328. if (_actor.isConfused())
  329. {
  330. hated = findNextRndTarget();
  331. }
  332. else
  333. {
  334. hated = getAttackTarget();
  335. }
  336. if (hated == null)
  337. {
  338. setIntention(AI_INTENTION_ACTIVE);
  339. return;
  340. }
  341. if (hated != getAttackTarget())
  342. {
  343. setAttackTarget(hated);
  344. }
  345. if (!_actor.isMuted() && (skills.length > 0) && (Rnd.nextInt(5) == 3))
  346. {
  347. for (L2Skill sk : skills)
  348. {
  349. int castRange = sk.getCastRange();
  350. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume(sk)))
  351. {
  352. _accessor.doCast(sk);
  353. return;
  354. }
  355. }
  356. }
  357. _accessor.doAttack(getAttackTarget());
  358. }
  359. }
  360. private void thinkActive()
  361. {
  362. setAttackTarget(findNextRndTarget());
  363. L2Character hated;
  364. if (_actor.isConfused())
  365. {
  366. hated = findNextRndTarget();
  367. }
  368. else
  369. {
  370. hated = getAttackTarget();
  371. }
  372. if (hated != null)
  373. {
  374. _actor.setRunning();
  375. setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
  376. }
  377. }
  378. private boolean autoAttackCondition(L2Character target)
  379. {
  380. if ((target == null) || !(_actor instanceof L2Attackable))
  381. {
  382. return false;
  383. }
  384. L2Attackable me = (L2Attackable) _actor;
  385. if ((target instanceof L2NpcInstance) || (target instanceof L2DoorInstance))
  386. {
  387. return false;
  388. }
  389. if (target.isAlikeDead() || !me.isInsideRadius(target, me.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100))
  390. {
  391. return false;
  392. }
  393. // Check if the target isn't invulnerable
  394. if (target.isInvul())
  395. {
  396. return false;
  397. }
  398. // Spawn protection (only against mobs)
  399. if ((target instanceof L2PcInstance) && ((L2PcInstance) target).isSpawnProtected())
  400. {
  401. return false;
  402. }
  403. // Check if the target is a L2PlayableInstance
  404. if (target instanceof L2Playable)
  405. {
  406. // Check if the target isn't in silent move mode
  407. if (((L2Playable) target).isSilentMoving())
  408. {
  409. return false;
  410. }
  411. }
  412. if (target instanceof L2Npc)
  413. {
  414. return false;
  415. }
  416. return me.isAggressive();
  417. }
  418. private L2Character findNextRndTarget()
  419. {
  420. int aggroRange = ((L2Attackable) _actor).getAggroRange();
  421. L2Attackable npc = (L2Attackable) _actor;
  422. int npcX, npcY, targetX, targetY;
  423. double dy, dx;
  424. double dblAggroRange = aggroRange * aggroRange;
  425. List<L2Character> potentialTarget = new FastList<L2Character>();
  426. Collection<L2Object> objs = npc.getKnownList().getKnownObjects().values();
  427. for (L2Object obj : objs)
  428. {
  429. if (!(obj instanceof L2Character))
  430. {
  431. continue;
  432. }
  433. npcX = npc.getX();
  434. npcY = npc.getY();
  435. targetX = obj.getX();
  436. targetY = obj.getY();
  437. dx = npcX - targetX;
  438. dy = npcY - targetY;
  439. if (((dx * dx) + (dy * dy)) > dblAggroRange)
  440. {
  441. continue;
  442. }
  443. L2Character target = (L2Character) obj;
  444. if (autoAttackCondition(target))
  445. {
  446. potentialTarget.add(target);
  447. }
  448. }
  449. if (potentialTarget.isEmpty())
  450. {
  451. return null;
  452. }
  453. // we choose a random target
  454. int choice = Rnd.nextInt(potentialTarget.size());
  455. L2Character target = potentialTarget.get(choice);
  456. return target;
  457. }
  458. private L2ControllableMobInstance findNextGroupTarget()
  459. {
  460. return getGroupTarget().getRandomMob();
  461. }
  462. public L2ControllableMobAI(AIAccessor accessor)
  463. {
  464. super(accessor);
  465. setAlternateAI(AI_IDLE);
  466. }
  467. public int getAlternateAI()
  468. {
  469. return _alternateAI;
  470. }
  471. public void setAlternateAI(int _alternateai)
  472. {
  473. _alternateAI = _alternateai;
  474. }
  475. public void forceAttack(L2Character target)
  476. {
  477. setAlternateAI(AI_FORCEATTACK);
  478. setForcedTarget(target);
  479. }
  480. public void forceAttackGroup(MobGroup group)
  481. {
  482. setForcedTarget(null);
  483. setGroupTarget(group);
  484. setAlternateAI(AI_ATTACK_GROUP);
  485. }
  486. public void stop()
  487. {
  488. setAlternateAI(AI_IDLE);
  489. clientStopMoving(null);
  490. }
  491. public void move(int x, int y, int z)
  492. {
  493. moveTo(x, y, z);
  494. }
  495. public void follow(L2Character target)
  496. {
  497. setAlternateAI(AI_FOLLOW);
  498. setForcedTarget(target);
  499. }
  500. public boolean isThinking()
  501. {
  502. return _isThinking;
  503. }
  504. public boolean isNotMoving()
  505. {
  506. return _isNotMoving;
  507. }
  508. public void setNotMoving(boolean isNotMoving)
  509. {
  510. _isNotMoving = isNotMoving;
  511. }
  512. public void setThinking(boolean isThinking)
  513. {
  514. _isThinking = isThinking;
  515. }
  516. private L2Character getForcedTarget()
  517. {
  518. return _forcedTarget;
  519. }
  520. private MobGroup getGroupTarget()
  521. {
  522. return _targetGroup;
  523. }
  524. private void setForcedTarget(L2Character forcedTarget)
  525. {
  526. _forcedTarget = forcedTarget;
  527. }
  528. private void setGroupTarget(MobGroup targetGroup)
  529. {
  530. _targetGroup = targetGroup;
  531. }
  532. }