L2ControllableMobAI.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*
  2. * Copyright © 2004-2023 L2J Server
  3. *
  4. * This file is part of L2J Server.
  5. *
  6. * L2J Server is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * L2J Server is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.l2jserver.gameserver.ai;
  20. import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE;
  21. import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ATTACK;
  22. import java.util.Collection;
  23. import java.util.List;
  24. import java.util.stream.Collectors;
  25. import com.l2jserver.commons.util.Rnd;
  26. import com.l2jserver.gameserver.model.L2Object;
  27. import com.l2jserver.gameserver.model.MobGroup;
  28. import com.l2jserver.gameserver.model.MobGroupTable;
  29. import com.l2jserver.gameserver.model.actor.L2Attackable;
  30. import com.l2jserver.gameserver.model.actor.L2Character;
  31. import com.l2jserver.gameserver.model.actor.L2Npc;
  32. import com.l2jserver.gameserver.model.actor.L2Playable;
  33. import com.l2jserver.gameserver.model.actor.instance.L2ControllableMobInstance;
  34. import com.l2jserver.gameserver.model.actor.instance.L2DoorInstance;
  35. import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance;
  36. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  37. import com.l2jserver.gameserver.model.skills.Skill;
  38. import com.l2jserver.gameserver.util.Util;
  39. /**
  40. * AI for controllable mobs
  41. * @author littlecrow
  42. */
  43. public final class L2ControllableMobAI extends L2AttackableAI {
  44. public static final int AI_IDLE = 1;
  45. public static final int AI_NORMAL = 2;
  46. public static final int AI_FORCE_ATTACK = 3;
  47. public static final int AI_FOLLOW = 4;
  48. public static final int AI_CAST = 5;
  49. public static final int AI_ATTACK_GROUP = 6;
  50. private int _alternateAI;
  51. private boolean _isThinking; // to prevent thinking recursively
  52. private boolean _isNotMoving;
  53. private L2Character _forcedTarget;
  54. private MobGroup _targetGroup;
  55. public L2ControllableMobAI(L2ControllableMobInstance creature) {
  56. super(creature);
  57. setAlternateAI(AI_IDLE);
  58. }
  59. protected void thinkFollow() {
  60. L2Attackable me = (L2Attackable) _actor;
  61. if (!Util.checkIfInRange(MobGroupTable.FOLLOW_RANGE, me, getForcedTarget(), true)) {
  62. int signX = (Rnd.nextInt(2) == 0) ? -1 : 1;
  63. int signY = (Rnd.nextInt(2) == 0) ? -1 : 1;
  64. int randX = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
  65. int randY = Rnd.nextInt(MobGroupTable.FOLLOW_RANGE);
  66. moveTo(getForcedTarget().getX() + (signX * randX), getForcedTarget().getY() + (signY * randY), getForcedTarget().getZ());
  67. }
  68. }
  69. @Override
  70. protected void onEvtThink() {
  71. if (isThinking()) {
  72. return;
  73. }
  74. setThinking(true);
  75. try {
  76. switch (getAlternateAI()) {
  77. case AI_IDLE:
  78. if (getIntention() != CtrlIntention.AI_INTENTION_ACTIVE) {
  79. setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
  80. }
  81. break;
  82. case AI_FOLLOW:
  83. thinkFollow();
  84. break;
  85. case AI_CAST:
  86. thinkCast();
  87. break;
  88. case AI_FORCE_ATTACK:
  89. thinkForceAttack();
  90. break;
  91. case AI_ATTACK_GROUP:
  92. thinkAttackGroup();
  93. break;
  94. default:
  95. if (getIntention() == AI_INTENTION_ACTIVE) {
  96. thinkActive();
  97. } else if (getIntention() == AI_INTENTION_ATTACK) {
  98. thinkAttack();
  99. }
  100. break;
  101. }
  102. } finally {
  103. setThinking(false);
  104. }
  105. }
  106. @Override
  107. protected void thinkCast() {
  108. L2Attackable npc = (L2Attackable) _actor;
  109. if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead()) {
  110. setAttackTarget(findNextRndTarget());
  111. clientStopMoving(null);
  112. }
  113. if (getAttackTarget() == null) {
  114. return;
  115. }
  116. npc.setTarget(getAttackTarget());
  117. if (!_actor.isMuted()) {
  118. int max_range = 0;
  119. // check distant skills
  120. for (Skill sk : _actor.getAllSkills()) {
  121. if (Util.checkIfInRange(sk.getCastRange(), _actor, getAttackTarget(), true) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
  122. _actor.doCast(sk);
  123. return;
  124. }
  125. max_range = Math.max(max_range, sk.getCastRange());
  126. }
  127. if (!isNotMoving()) {
  128. moveToPawn(getAttackTarget(), max_range);
  129. }
  130. }
  131. }
  132. protected void thinkAttackGroup() {
  133. L2Character target = getForcedTarget();
  134. if ((target == null) || target.isAlikeDead()) {
  135. // try to get next group target
  136. setForcedTarget(findNextGroupTarget());
  137. clientStopMoving(null);
  138. }
  139. if (target == null) {
  140. return;
  141. }
  142. _actor.setTarget(target);
  143. // as a response, we put the target in a forced attack mode
  144. L2ControllableMobInstance theTarget = (L2ControllableMobInstance) target;
  145. L2ControllableMobAI ctrlAi = (L2ControllableMobAI) theTarget.getAI();
  146. ctrlAi.forceAttack(_actor);
  147. double dist2 = _actor.calculateDistance(target, false, true);
  148. int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + target.getTemplate().getCollisionRadius();
  149. int max_range = range;
  150. if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20)))) {
  151. // check distant skills
  152. for (Skill sk : _actor.getAllSkills()) {
  153. int castRange = sk.getCastRange();
  154. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
  155. _actor.doCast(sk);
  156. return;
  157. }
  158. max_range = Math.max(max_range, castRange);
  159. }
  160. if (!isNotMoving()) {
  161. moveToPawn(target, range);
  162. }
  163. return;
  164. }
  165. _actor.doAttack(target);
  166. }
  167. protected void thinkForceAttack() {
  168. if ((getForcedTarget() == null) || getForcedTarget().isAlikeDead()) {
  169. clientStopMoving(null);
  170. setIntention(AI_INTENTION_ACTIVE);
  171. setAlternateAI(AI_IDLE);
  172. }
  173. _actor.setTarget(getForcedTarget());
  174. double dist2 = _actor.calculateDistance(getForcedTarget(), false, true);
  175. int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getForcedTarget().getTemplate().getCollisionRadius();
  176. int max_range = range;
  177. if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20)))) {
  178. // check distant skills
  179. for (Skill sk : _actor.getAllSkills()) {
  180. int castRange = sk.getCastRange();
  181. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
  182. _actor.doCast(sk);
  183. return;
  184. }
  185. max_range = Math.max(max_range, castRange);
  186. }
  187. if (!isNotMoving()) {
  188. moveToPawn(getForcedTarget(), _actor.getPhysicalAttackRange()/* range */);
  189. }
  190. return;
  191. }
  192. _actor.doAttack(getForcedTarget());
  193. }
  194. @Override
  195. protected void thinkAttack() {
  196. if ((getAttackTarget() == null) || getAttackTarget().isAlikeDead()) {
  197. if (getAttackTarget() != null) {
  198. // stop hating
  199. L2Attackable npc = (L2Attackable) _actor;
  200. npc.stopHating(getAttackTarget());
  201. }
  202. setIntention(AI_INTENTION_ACTIVE);
  203. } else {
  204. // notify aggression
  205. if (((L2Npc) _actor).getTemplate().getClans() != null) {
  206. Collection<L2Object> objs = _actor.getKnownList().getKnownObjects().values();
  207. for (L2Object obj : objs) {
  208. if (!(obj instanceof L2Npc)) {
  209. continue;
  210. }
  211. L2Npc npc = (L2Npc) obj;
  212. if (!npc.isInMyClan((L2Npc) _actor)) {
  213. continue;
  214. }
  215. if (_actor.isInsideRadius(npc, npc.getTemplate().getClanHelpRange(), false, true) && (Math.abs(getAttackTarget().getZ() - npc.getZ()) < 200)) {
  216. npc.getAI().notifyEvent(CtrlEvent.EVT_AGGRESSION, getAttackTarget(), 1);
  217. }
  218. }
  219. }
  220. _actor.setTarget(getAttackTarget());
  221. double dist2 = _actor.calculateDistance(getAttackTarget(), false, true);
  222. int range = _actor.getPhysicalAttackRange() + _actor.getTemplate().getCollisionRadius() + getAttackTarget().getTemplate().getCollisionRadius();
  223. int max_range = range;
  224. if (!_actor.isMuted() && (dist2 > ((range + 20) * (range + 20)))) {
  225. // check distant skills
  226. for (Skill sk : _actor.getAllSkills()) {
  227. int castRange = sk.getCastRange();
  228. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() > _actor.getStat().getMpConsume2(sk))) {
  229. _actor.doCast(sk);
  230. return;
  231. }
  232. max_range = Math.max(max_range, castRange);
  233. }
  234. moveToPawn(getAttackTarget(), range);
  235. return;
  236. }
  237. // Force mobs to attack anybody if confused.
  238. L2Character hated;
  239. if (_actor.isConfused()) {
  240. hated = findNextRndTarget();
  241. } else {
  242. hated = getAttackTarget();
  243. }
  244. if (hated == null) {
  245. setIntention(AI_INTENTION_ACTIVE);
  246. return;
  247. }
  248. if (hated != getAttackTarget()) {
  249. setAttackTarget(hated);
  250. }
  251. if (!_actor.isMuted() && (Rnd.nextInt(5) == 3)) {
  252. for (Skill sk : _actor.getAllSkills()) {
  253. int castRange = sk.getCastRange();
  254. if (((castRange * castRange) >= dist2) && !_actor.isSkillDisabled(sk) && (_actor.getCurrentMp() < _actor.getStat().getMpConsume2(sk))) {
  255. _actor.doCast(sk);
  256. return;
  257. }
  258. }
  259. }
  260. _actor.doAttack(getAttackTarget());
  261. }
  262. }
  263. @Override
  264. protected void thinkActive() {
  265. setAttackTarget(findNextRndTarget());
  266. L2Character hated;
  267. if (_actor.isConfused()) {
  268. hated = findNextRndTarget();
  269. } else {
  270. hated = getAttackTarget();
  271. }
  272. if (hated != null) {
  273. _actor.setRunning();
  274. setIntention(CtrlIntention.AI_INTENTION_ATTACK, hated);
  275. }
  276. }
  277. private boolean checkAutoAttackCondition(L2Character target) {
  278. if ((target == null) || (target instanceof L2NpcInstance) || (target instanceof L2DoorInstance)) {
  279. return false;
  280. }
  281. // TODO(Zoey76)[#112]: This check must change if summon fall in L2Npc hierarchy.
  282. if (target.isNpc()) {
  283. return false;
  284. }
  285. // Check if the target isn't invulnerable
  286. if (target.isInvul() || target.isAlikeDead()) {
  287. return false;
  288. }
  289. // Spawn protection (only against mobs)
  290. if (target.isPlayer() && ((L2PcInstance) target).isSpawnProtected()) {
  291. return false;
  292. }
  293. final L2Attackable me = getActiveChar();
  294. if (!me.isInsideRadius(target, me.getAggroRange(), false, false) || (Math.abs(_actor.getZ() - target.getZ()) > 100)) {
  295. return false;
  296. }
  297. // Check if the target is a L2Playable
  298. if (target.isPlayable()) {
  299. // Check if the target isn't in silent move mode
  300. if (((L2Playable) target).isSilentMovingAffected()) {
  301. return false;
  302. }
  303. }
  304. return me.isAggressive();
  305. }
  306. private L2Character findNextRndTarget() {
  307. final List<L2Character> potentialTarget = _actor.getKnownList().getKnownCharactersInRadius(getActiveChar().getAggroRange()).stream().filter(this::checkAutoAttackCondition).collect(Collectors.toList());
  308. if (potentialTarget.isEmpty()) {
  309. return null;
  310. }
  311. return potentialTarget.get(Rnd.nextInt(potentialTarget.size()));
  312. }
  313. private L2ControllableMobInstance findNextGroupTarget() {
  314. return getGroupTarget().getRandomMob();
  315. }
  316. public int getAlternateAI() {
  317. return _alternateAI;
  318. }
  319. public void setAlternateAI(int alternateAI) {
  320. _alternateAI = alternateAI;
  321. }
  322. public void forceAttack(L2Character target) {
  323. setAlternateAI(AI_FORCE_ATTACK);
  324. setForcedTarget(target);
  325. }
  326. public void forceAttackGroup(MobGroup group) {
  327. setForcedTarget(null);
  328. setGroupTarget(group);
  329. setAlternateAI(AI_ATTACK_GROUP);
  330. }
  331. public void stop() {
  332. setAlternateAI(AI_IDLE);
  333. clientStopMoving(null);
  334. }
  335. public void move(int x, int y, int z) {
  336. moveTo(x, y, z);
  337. }
  338. public void follow(L2Character target) {
  339. setAlternateAI(AI_FOLLOW);
  340. setForcedTarget(target);
  341. }
  342. public boolean isThinking() {
  343. return _isThinking;
  344. }
  345. public boolean isNotMoving() {
  346. return _isNotMoving;
  347. }
  348. public void setNotMoving(boolean isNotMoving) {
  349. _isNotMoving = isNotMoving;
  350. }
  351. public void setThinking(boolean isThinking) {
  352. _isThinking = isThinking;
  353. }
  354. private L2Character getForcedTarget() {
  355. return _forcedTarget;
  356. }
  357. private MobGroup getGroupTarget() {
  358. return _targetGroup;
  359. }
  360. private void setForcedTarget(L2Character forcedTarget) {
  361. _forcedTarget = forcedTarget;
  362. }
  363. private void setGroupTarget(MobGroup targetGroup) {
  364. _targetGroup = targetGroup;
  365. }
  366. }