L2ControllableMobAI.java 15 KB

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