Attack.pm.svn-base
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:23k
源码类别:

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Attack AI
  3. #  Copyright (c) 2006 OpenKore Team
  4. #
  5. #  This software is open source, licensed under the GNU General Public
  6. #  License, version 2.
  7. #  Basically, this means that you're allowed to modify and distribute
  8. #  this software. However, if you distribute modified versions, you MUST
  9. #  also distribute the source code.
  10. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  11. #
  12. #  $Revision: 4286 $
  13. #  $Id: Commands.pm 4286 2006-04-17 14:02:27Z illusion_kore $
  14. #
  15. #########################################################################
  16. #
  17. # This module contains the attack AI's code.
  18. package AI::Attack;
  19. use strict;
  20. use Carp::Assert;
  21. use Time::HiRes qw(time);
  22. use Globals;
  23. use AI;
  24. use Actor;
  25. use Field;
  26. use Log qw(message debug warning);
  27. use Translation qw(T TF);
  28. use Network::Send ();
  29. use Skill;
  30. use Misc;
  31. use Utils;
  32. use Utils::Benchmark;
  33. use Utils::PathFinding;
  34. sub process {
  35. Benchmark::begin("ai_attack") if DEBUG;
  36. my $args = AI::args;
  37. if (AI::action eq "attack" && AI::args->{suspended}) {
  38. $args->{ai_attack_giveup}{time} += time - $args->{suspended};
  39. delete $args->{suspended};
  40. }
  41. if (AI::action eq "attack" && $args->{move_start}) {
  42. # We've just finished moving to the monster.
  43. # Don't count the time we spent on moving
  44. $args->{ai_attack_giveup}{time} += time - $args->{move_start};
  45. undef $args->{unstuck}{time};
  46. undef $args->{move_start};
  47. } elsif (AI::action eq "attack" && $args->{avoiding} && $args->{attackID}) {
  48. my $target = Actor::get($args->{attackID});
  49. $args->{ai_attack_giveup}{time} = time + $target->{time_move_calc} + 3;
  50. undef $args->{avoiding};
  51. } elsif (((AI::action eq "route" && AI::action(1) eq "attack") || (AI::action eq "move" && AI::action(2) eq "attack"))
  52.    && $args->{attackID} && timeOut($AI::Temp::attack_route_adjust, 1)) {
  53. # We're on route to the monster; check whether the monster has moved
  54. my $ID = $args->{attackID};
  55. my $attackSeq = (AI::action eq "route") ? AI::args(1) : AI::args(2);
  56. my $target = Actor::get($ID);
  57. if ($target->{type} ne 'Unknown' && $attackSeq->{monsterPos} && %{$attackSeq->{monsterPos}}
  58.  && round(distance(calcPosition($target), $attackSeq->{monsterPos})) > $attackSeq->{attackMethod}{maxDistance}) {
  59. # Monster has moved; stop moving and let the attack AI readjust route
  60. AI::dequeue;
  61. AI::dequeue if (AI::action eq "route");
  62. $attackSeq->{ai_attack_giveup}{time} = time;
  63. debug "Target has moved more than $attackSeq->{attackMethod}{maxDistance} blocks; readjusting routen", "ai_attack";
  64. } elsif ($target->{type} ne 'Unknown' && $attackSeq->{monsterPos} && %{$attackSeq->{monsterPos}}
  65.  && round(distance(calcPosition($target), calcPosition($char))) <= $attackSeq->{attackMethod}{maxDistance}) {
  66. # Monster is within attack range; stop moving
  67. AI::dequeue;
  68. AI::dequeue if (AI::action eq "route");
  69. $attackSeq->{ai_attack_giveup}{time} = time;
  70. debug "Target at ($attackSeq->{monsterPos}{x},$attackSeq->{monsterPos}{y}) is now within " .
  71. "$attackSeq->{attackMethod}{maxDistance} blocks; stop movingn", "ai_attack";
  72. }
  73. $AI::Temp::attack_route_adjust = time;
  74. }
  75. if (AI::action eq "attack") {
  76. my $ID = $args->{ID};
  77. if (targetGone()) {
  78. finishAttacking();
  79. } elsif (shouldGiveUp()) {
  80. giveUp();
  81. } else {
  82. if (timeOut($args->{attackMainTimeout}, 0.1)) {
  83. $args->{attackMainTimeout} = time;
  84. main();
  85. }
  86. }
  87. }
  88. # Check for kill steal and mob-training while moving
  89. if ((AI::is("move", "route") && $args->{attackID} && AI::inQueue("attack")
  90. && timeOut($args->{movingWhileAttackingTimeout}, 0.2))) {
  91. my $ID = AI::args->{attackID};
  92. my $monster = $monsters{$ID};
  93. # Check for kill steal while moving
  94. if ($monster && !Misc::checkMonsterCleanness($ID)) {
  95. message T("Dropping target - you will not kill steal othersn");
  96. stopAttack();
  97. $monster->{ignore} = 1;
  98. # Right now, the queue is either
  99. #   move, route, attack
  100. # -or-
  101. #   route, attack
  102. AI::dequeue;
  103. AI::dequeue;
  104. AI::dequeue if (AI::action eq "attack");
  105. if ($config{teleportAuto_dropTargetKS}) {
  106. message T("Teleport due to dropping attack targetn");
  107. useTeleport(1);
  108. }
  109. }
  110. # Mob-training, stop attacking the monster if it is already aggressive
  111. if ((my $control = mon_control($monster->{name},$monster->{nameID}))) {
  112. if ($control->{attack_auto} == 3
  113. && ($monster->{dmgToYou} || $monster->{missedYou} || $monster->{dmgFromYou})) {
  114. message TF("Dropping target - %s (%s) has been provokedn", $monster->{name}, $monster->{binID});
  115. stopAttack();
  116. $monster->{ignore} = 1;
  117. # Right now, the queue is either
  118. #   move, route, attack
  119. # -or-
  120. #   route, attack
  121. AI::dequeue;
  122. AI::dequeue;
  123. AI::dequeue if (AI::action eq "attack");
  124. }
  125. }
  126. $args->{movingWhileAttackingTimeout} = time;
  127. }
  128. Benchmark::end("ai_attack") if DEBUG;
  129. }
  130. sub shouldGiveUp {
  131. my $args = AI::args;
  132. return !$config{attackNoGiveup} && (timeOut($args->{ai_attack_giveup}) || $args->{unstuck}{count} > 5);
  133. }
  134. sub giveUp {
  135. my $ID = AI::args->{ID};
  136. my $target = Actor::get($ID);
  137. $target->{attack_failed} = time if ($monsters{$ID});
  138. AI::dequeue;
  139. message T("Can't reach or damage target, dropping targetn"), "ai_attack";
  140. if ($config{'teleportAuto_dropTarget'}) {
  141. message T("Teleport due to dropping attack targetn");
  142. useTeleport(1);
  143. }
  144. }
  145. sub targetGone {
  146. my $args = AI::args;
  147. return !$monsters{$args->{ID}} && (!$players{$args->{ID}} || $players{$args->{ID}}{dead});
  148. }
  149. sub finishAttacking {
  150. my $args = AI::args;
  151. $timeout{'ai_attack'}{'time'} -= $timeout{'ai_attack'}{'timeout'};
  152. my $ID = $args->{ID};
  153. AI::dequeue;
  154. if ($monsters_old{$ID} && $monsters_old{$ID}{dead}) {
  155. message T("Target diedn"), "ai_attack";
  156. Plugins::callHook("target_died");
  157. monKilled();
  158. # Pickup loot when monster's dead
  159. if ($AI == 2 && $config{'itemsTakeAuto'} && $monsters_old{$ID}{dmgFromYou} > 0 && !$monsters_old{$ID}{ignore}) {
  160. AI::clear("items_take");
  161. ai_items_take($monsters_old{$ID}{pos}{x}, $monsters_old{$ID}{pos}{y},
  162.       $monsters_old{$ID}{pos_to}{x}, $monsters_old{$ID}{pos_to}{y});
  163. } else {
  164. # Cheap way to suspend all movement to make it look real
  165. ai_clientSuspend(0, $timeout{'ai_attack_waitAfterKill'}{'timeout'});
  166. }
  167. ## kokal start
  168. ## mosters counting
  169. my $i = 0;
  170. my $found = 0;
  171. while ($monsters_Killed[$i]) {
  172. if ($monsters_Killed[$i]{'nameID'} eq $monsters_old{$ID}{'nameID'}) {
  173. $monsters_Killed[$i]{'count'}++;
  174. monsterLog($monsters_Killed[$i]{'name'});
  175. $found = 1;
  176. last;
  177. }
  178. $i++;
  179. }
  180. if (!$found) {
  181. $monsters_Killed[$i]{'nameID'} = $monsters_old{$ID}{'nameID'};
  182. $monsters_Killed[$i]{'name'} = $monsters_old{$ID}{'name'};
  183. $monsters_Killed[$i]{'count'} = 1;
  184. monsterLog($monsters_Killed[$i]{'name'})
  185. }
  186. ## kokal end
  187. } elsif ($config{teleportAuto_lostTarget}) {
  188. message T("Target lost, teleporting.n"), "ai_attack";
  189. useTeleport(1);
  190. } else {
  191. message T("Target lostn"), "ai_attack";
  192. }
  193. Plugins::callHook('attack_end', {ID => $ID})
  194. }
  195. sub dropTargetWhileMoving {
  196. my $ID = AI::args->{attackID};
  197. message T("Dropping target - you will not kill steal othersn");
  198. stopAttack();
  199. $monsters{$ID}{ignore} = 1;
  200. # Right now, the queue is either
  201. #   move, route, attack
  202. # -or-
  203. #   route, attack
  204. AI::dequeue;
  205. AI::dequeue;
  206. AI::dequeue if (AI::action eq "attack");
  207. if ($config{teleportAuto_dropTargetKS}) {
  208. message T("Teleport due to dropping attack targetn");
  209. useTeleport(1);
  210. }
  211. }
  212. sub main {
  213. my $args = AI::args;
  214. Benchmark::begin("ai_attack (part 1)") if DEBUG;
  215. Benchmark::begin("ai_attack (part 1.1)") if DEBUG;
  216. # The attack sequence hasn't timed out and the monster is on screen
  217. # Update information about the monster and the current situation
  218. my $args = AI::args;
  219. my $followIndex = AI::findAction("follow");
  220. my $following;
  221. my $followID;
  222. if (defined $followIndex) {
  223. $following = AI::args($followIndex)->{following};
  224. $followID = AI::args($followIndex)->{ID};
  225. }
  226. my $ID = $args->{ID};
  227. my $target = Actor::get($ID);
  228. my $myPos = $char->{pos_to};
  229. my $monsterPos = $target->{pos_to};
  230. my $monsterDist = round(distance($myPos, $monsterPos));
  231. my ($realMyPos, $realMonsterPos, $realMonsterDist, $hitYou);
  232. my $realMyPos = calcPosition($char);
  233. my $realMonsterPos = calcPosition($target);
  234. my $realMonsterDist = round(distance($realMyPos, $realMonsterPos));
  235. if (!$config{'runFromTarget'}) {
  236. $myPos = $realMyPos;
  237. $monsterPos = $realMonsterPos;
  238. }
  239. my $cleanMonster = checkMonsterCleanness($ID);
  240. # If the damage numbers have changed, update the giveup time so we don't timeout
  241. if ($args->{dmgToYou_last}   != $target->{dmgToYou}
  242.  || $args->{missedYou_last}  != $target->{missedYou}
  243.  || $args->{dmgFromYou_last} != $target->{dmgFromYou}
  244.  || $args->{lastSkillTime} != $char->{last_skill_time}) {
  245. $args->{ai_attack_giveup}{time} = time;
  246. debug "Update attack giveup timen", "ai_attack", 2;
  247. }
  248. $hitYou = ($args->{dmgToYou_last} != $target->{dmgToYou}
  249. || $args->{missedYou_last} != $target->{missedYou});
  250. $args->{dmgToYou_last} = $target->{dmgToYou};
  251. $args->{missedYou_last} = $target->{missedYou};
  252. $args->{dmgFromYou_last} = $target->{dmgFromYou};
  253. $args->{missedFromYou_last} = $target->{missedFromYou};
  254. $args->{lastSkillTime} = $char->{last_skill_time};
  255. Benchmark::end("ai_attack (part 1.1)") if DEBUG;
  256. Benchmark::begin("ai_attack (part 1.2)") if DEBUG;
  257. # Determine what combo skill to use
  258. delete $args->{attackMethod};
  259. my $lastSkill;
  260. if ($char->{last_skill_used}) {
  261. $lastSkill = Skill->new(idn => $char->{last_skill_used})->getName();
  262. }
  263. my $i = 0;
  264. while (exists $config{"attackComboSlot_$i"}) {
  265. if (!$config{"attackComboSlot_$i"}) {
  266. $i++;
  267. next;
  268. }
  269. if ($config{"attackComboSlot_${i}_afterSkill"} eq $lastSkill
  270.  && ( !$config{"attackComboSlot_${i}_maxUses"} || $args->{attackComboSlot_uses}{$i} < $config{"attackComboSlot_${i}_maxUses"} )
  271.  && ( !$config{"attackComboSlot_${i}_autoCombo"} || ($char->{combo_packet} && $config{"attackComboSlot_${i}_autoCombo"}) )
  272.  && ( !defined($args->{ID}) || $args->{ID} eq $char->{last_skill_target} || !$config{"attackComboSlot_${i}_isSelfSkill"})
  273.  && checkSelfCondition("attackComboSlot_$i")
  274.  && (!$config{"attackComboSlot_${i}_monsters"} || existsInList($config{"attackComboSlot_${i}_monsters"}, $target->{name}))
  275.  && (!$config{"attackComboSlot_${i}_notMonsters"} || !existsInList($config{"attackComboSlot_${i}_notMonsters"}, $target->{name}))
  276.  && checkMonsterCondition("attackComboSlot_${i}_target", $target)) {
  277. $args->{attackComboSlot_uses}{$i}++;
  278. delete $char->{last_skill_used};
  279. if ($config{"attackComboSlot_${i}_autoCombo"}) {
  280. $char->{combo_packet} = 1500 if ($char->{combo_packet} > 1500);
  281. # eAthena seems to have a bug where the combo_packet overflows and gives an
  282. # abnormally high number. This causes kore to get stuck in a waitBeforeUse timeout.
  283. $config{"attackComboSlot_${i}_waitBeforeUse"} = ($char->{combo_packet} / 1000);
  284. }
  285. delete $char->{combo_packet};
  286. $args->{attackMethod}{type} = "combo";
  287. $args->{attackMethod}{comboSlot} = $i;
  288. $args->{attackMethod}{distance} = $config{"attackComboSlot_${i}_dist"};
  289. $args->{attackMethod}{maxDistance} = $config{"attackComboSlot_${i}_dist"};
  290. $args->{attackMethod}{isSelfSkill} = $config{"attackComboSlot_${i}_isSelfSkill"};
  291. last;
  292. }
  293. $i++;
  294. }
  295. # Determine what skill to use to attack
  296. if (!$args->{attackMethod}{type}) {
  297. if ($config{'attackUseWeapon'}) {
  298. $args->{attackMethod}{distance} = $config{'attackDistance'};
  299. $args->{attackMethod}{maxDistance} = $config{'attackMaxDistance'};
  300. $args->{attackMethod}{type} = "weapon";
  301. } else {
  302. $args->{attackMethod}{distance} = 30;
  303. $args->{attackMethod}{maxDistance} = 30;
  304. undef $args->{attackMethod}{type};
  305. }
  306. $i = 0;
  307. while (exists $config{"attackSkillSlot_$i"}) {
  308. if (!$config{"attackSkillSlot_$i"}) {
  309. $i++;
  310. next;
  311. }
  312. my $skill = new Skill(name => $config{"attackSkillSlot_$i"});
  313. if (checkSelfCondition("attackSkillSlot_$i")
  314. && (!$config{"attackSkillSlot_$i"."_maxUses"} ||
  315.     $target->{skillUses}{$skill->getHandle()} < $config{"attackSkillSlot_$i"."_maxUses"})
  316. && (!$config{"attackSkillSlot_$i"."_maxAttempts"} || $args->{attackSkillSlot_attempts}{$i} < $config{"attackSkillSlot_$i"."_maxAttempts"})
  317. && (!$config{"attackSkillSlot_$i"."_monsters"} || existsInList($config{"attackSkillSlot_$i"."_monsters"}, $target->{'name'}))
  318. && (!$config{"attackSkillSlot_$i"."_notMonsters"} || !existsInList($config{"attackSkillSlot_$i"."_notMonsters"}, $target->{'name'}))
  319. && (!$config{"attackSkillSlot_$i"."_previousDamage"} || inRange($target->{dmgTo}, $config{"attackSkillSlot_$i"."_previousDamage"}))
  320. && checkMonsterCondition("attackSkillSlot_${i}_target", $target)
  321. ) {
  322. $args->{attackSkillSlot_attempts}{$i}++;
  323. $args->{attackMethod}{distance} = $config{"attackSkillSlot_$i"."_dist"};
  324. $args->{attackMethod}{maxDistance} = $config{"attackSkillSlot_$i"."_dist"};
  325. $args->{attackMethod}{type} = "skill";
  326. $args->{attackMethod}{skillSlot} = $i;
  327. last;
  328. }
  329. $i++;
  330. }
  331. if ($config{'runFromTarget'} && $config{'runFromTarget_dist'} > $args->{attackMethod}{distance}) {
  332. $args->{attackMethod}{distance} = $config{'runFromTarget_dist'};
  333. }
  334. }
  335. $args->{attackMethod}{maxDistance} ||= $config{attackMaxDistance};
  336. $args->{attackMethod}{distance} ||= $config{attackDistance};
  337. if ($args->{attackMethod}{maxDistance} < $args->{attackMethod}{distance}) {
  338. $args->{attackMethod}{maxDistance} = $args->{attackMethod}{distance};
  339. }
  340. Benchmark::end("ai_attack (part 1.2)") if DEBUG;
  341. Benchmark::end("ai_attack (part 1)") if DEBUG;
  342. if ($char->{sitting}) {
  343. ai_setSuspend(0);
  344. stand();
  345. } elsif (!$cleanMonster) {
  346. # Drop target if it's already attacked by someone else
  347. message T("Dropping target - you will not kill steal othersn"), "ai_attack";
  348. $messageSender->sendMove($realMyPos->{x}, $realMyPos->{y});
  349. AI::dequeue;
  350. if ($config{teleportAuto_dropTargetKS}) {
  351. message T("Teleporting due to dropping attack targetn"), "teleport";
  352. useTeleport(1);
  353. }
  354. } elsif (
  355. $config{attackCheckLOS} && $args->{attackMethod}{distance} > 2
  356. && (($config{attackCanSnipe} && !checkLineSnipable($realMyPos, $realMonsterPos))
  357. || (!$config{attackCanSnipe} && $realMonsterDist < $args->{attackMethod}{maxDistance} && !checkLineWalkable($realMyPos, $realMonsterPos)))
  358. ) {
  359. # We are a ranged attacker without LOS
  360. # Calculate squares around monster within shooting range, but not
  361. # closer than runFromTarget_dist
  362. my @stand = calcRectArea2($realMonsterPos->{x}, $realMonsterPos->{y},
  363.   $args->{attackMethod}{distance},
  364.   $config{runFromTarget} ? $config{runFromTarget_dist} : 0);
  365. my ($master, $masterPos);
  366. if ($config{follow}) {
  367. foreach (keys %players) {
  368. if ($players{$_}{name} eq $config{followTarget}) {
  369. $master = $players{$_};
  370. last;
  371. }
  372. }
  373. $masterPos = calcPosition($master) if $master;
  374. }
  375. # Determine which of these spots are snipable
  376. my $best_spot;
  377. my $best_dist;
  378. for my $spot (@stand) {
  379. # Is this spot acceptable?
  380. # 1. It must have LOS to the target ($realMonsterPos).
  381. # 2. It must be within $config{followDistanceMax} of
  382. #    $masterPos, if we have a master.
  383. if (
  384.     (($config{attackCanSnipe} && checkLineSnipable($spot, $realMonsterPos))
  385. && checkLineWalkable($spot, $realMonsterPos))
  386. && $field->isWalkable($spot->{x}, $spot->{y})
  387. && ($realMyPos->{x} != $spot->{x} && $realMyPos->{y} != $spot->{y})
  388. && (!$master || round(distance($spot, $masterPos)) <= $config{followDistanceMax})
  389. ) {
  390. my $dist = distance($realMyPos, $spot);
  391. if (!defined($best_dist) || $dist < $best_dist) {
  392. $best_dist = $dist;
  393. $best_spot = $spot;
  394. }
  395. }
  396. }
  397. # Move to the closest spot
  398. my $msg = "No LOS from ($realMyPos->{x}, $realMyPos->{y}) to target ($realMonsterPos->{x}, $realMonsterPos->{y})";
  399. if ($best_spot) {
  400. message TF("%s; moving to (%s, %s)n", $msg, $best_spot->{x}, $best_spot->{y});
  401. if ($config{attackChangeTarget} == 1) {
  402. # Restart attack from processAutoAttack
  403. AI::dequeue;
  404. ai_route($field{name}, $best_spot->{x}, $best_spot->{y}, LOSSubRoute => 1);
  405. } else {
  406. ai_route($field{name}, $best_spot->{x}, $best_spot->{y});
  407. }
  408. } else {
  409. warning TF("%s; no acceptable place to standn", $msg);
  410. $target->{attack_failedLOS} = time;
  411. AI::dequeue;
  412. AI::dequeue;
  413. AI::dequeue if (AI::action eq "attack");
  414. }
  415. } elsif ($config{'runFromTarget'} && ($realMonsterDist < $config{'runFromTarget_dist'} || $hitYou)) {
  416. #my $begin = time;
  417. # Get a list of blocks that we can run to
  418. my @blocks = calcRectArea($myPos->{x}, $myPos->{y},
  419. # If the monster hit you while you're running, then your recorded
  420. # location may be out of date. So we use a smaller distance so we can still move.
  421. ($hitYou) ? $config{'runFromTarget_dist'} / 2 : $config{'runFromTarget_dist'});
  422. # Find the distance value of the block that's farthest away from a wall
  423. my $highest;
  424. foreach (@blocks) {
  425. my $dist = ord(substr($field{dstMap}, $_->{y} * $field{width} + $_->{x}));
  426. if (!defined $highest || $dist > $highest) {
  427. $highest = $dist;
  428. }
  429. }
  430. # Get rid of rediculously large route distances (such as spots that are on a hill)
  431. # Get rid of blocks that are near a wall
  432. my $pathfinding = new PathFinding;
  433. use constant AVOID_WALLS => 4;
  434. for (my $i = 0; $i < @blocks; $i++) {
  435. # We want to avoid walls (so we don't get cornered), if possible
  436. my $dist = ord(substr($field{dstMap}, $blocks[$i]{y} * $field{width} + $blocks[$i]{x}));
  437. if ($highest >= AVOID_WALLS && $dist < AVOID_WALLS) {
  438. delete $blocks[$i];
  439. next;
  440. }
  441. $pathfinding->reset(
  442. field => %field,
  443. start => $myPos,
  444. dest => $blocks[$i]);
  445. my $ret = $pathfinding->runcount;
  446. if ($ret <= 0 || $ret > $config{'runFromTarget_dist'} * 2) {
  447. delete $blocks[$i];
  448. next;
  449. }
  450. }
  451. # Find the block that's farthest to us
  452. my $largestDist;
  453. my $bestBlock;
  454. foreach (@blocks) {
  455. next unless defined $_;
  456. my $dist = distance($monsterPos, $_);
  457. if (!defined $largestDist || $dist > $largestDist) {
  458. $largestDist = $dist;
  459. $bestBlock = $_;
  460. }
  461. }
  462. #message "Time spent: " . (time - $begin) . "n";
  463. #debug_showSpots('runFromTarget', @blocks, $bestBlock);
  464. $args->{avoiding} = 1;
  465. move($bestBlock->{x}, $bestBlock->{y}, $ID);
  466. } elsif ($realMonsterDist > $args->{attackMethod}{maxDistance}
  467.   && timeOut($args->{ai_attack_giveup}, 0.5)) {
  468. # The target monster moved; move to target
  469. $args->{move_start} = time;
  470. $args->{monsterPos} = {%{$monsterPos}};
  471. my $pos = meetingPosition($target, $args->{attackMethod}{maxDistance});
  472. my $dist = sprintf("%.1f", $monsterDist);
  473. debug "Target distance $dist is >$args->{attackMethod}{maxDistance}; moving to target: " .
  474. "from ($myPos->{x},$myPos->{y}) to ($pos->{x},$pos->{y})n", "ai_attack";
  475. my $result = ai_route($field{'name'}, $pos->{x}, $pos->{y},
  476. maxRouteTime => $config{'attackMaxRouteTime'},
  477. attackID => $ID,
  478. noMapRoute => 1,
  479. noAvoidWalls => 1);
  480. if (!$result) {
  481. # Unable to calculate a route to target
  482. $target->{attack_failed} = time;
  483. AI::dequeue;
  484. message T("Unable to calculate a route to target, dropping targetn"), "ai_attack";
  485. if ($config{'teleportAuto_dropTarget'}) {
  486. message T("Teleport due to dropping attack targetn");
  487. useTeleport(1);
  488. }
  489. }
  490. } elsif ((!$config{'runFromTarget'} || $realMonsterDist >= $config{'runFromTarget_dist'})
  491.  && (!$config{'tankMode'} || !$target->{dmgFromYou})) {
  492. # Attack the target. In case of tanking, only attack if it hasn't been hit once.
  493. if (!$args->{firstAttack}) {
  494. $args->{firstAttack} = 1;
  495. my $pos = "$myPos->{x},$myPos->{y}";
  496. debug "Ready to attack target (which is $realMonsterDist blocks away); we're at ($pos)n", "ai_attack";
  497. }
  498. $args->{unstuck}{time} = time if (!$args->{unstuck}{time});
  499. if (!$target->{dmgFromYou} && timeOut($args->{unstuck})) {
  500. # We are close enough to the target, and we're trying to attack it,
  501. # but some time has passed and we still haven't dealed any damage.
  502. # Our recorded position might be out of sync, so try to unstuck
  503. $args->{unstuck}{time} = time;
  504. debug("Attack - trying to unstuckn", "ai_attack");
  505. move($myPos->{x}, $myPos->{y});
  506. $args->{unstuck}{count}++;
  507. }
  508. if ($args->{attackMethod}{type} eq "weapon" && timeOut($timeout{ai_attack})) {
  509. if (Actor::Item::scanConfigAndCheck("attackEquip")) {
  510. #check if item needs to be equipped
  511. Actor::Item::scanConfigAndEquip("attackEquip");
  512. } else {
  513. $messageSender->sendAttack($ID,
  514. ($config{'tankMode'}) ? 0 : 7);
  515. $timeout{ai_attack}{time} = time;
  516. delete $args->{attackMethod};
  517. }
  518. } elsif ($args->{attackMethod}{type} eq "skill") {
  519. my $slot = $args->{attackMethod}{skillSlot};
  520. delete $args->{attackMethod};
  521. ai_setSuspend(0);
  522. my $skill = new Skill(name => lc($config{"attackSkillSlot_$slot"}));
  523. if (!ai_getSkillUseType($skill->getHandle())) {
  524. ai_skillUse(
  525. $skill->getHandle(),
  526. $config{"attackSkillSlot_${slot}_lvl"},
  527. $config{"attackSkillSlot_${slot}_maxCastTime"},
  528. $config{"attackSkillSlot_${slot}_minCastTime"},
  529. $config{"attackSkillSlot_${slot}_isSelfSkill"} ? $accountID : $ID,
  530. undef,
  531. "attackSkill",
  532. undef,
  533. undef,
  534. "attackSkillSlot_${slot}");
  535. } else {
  536. my $pos = calcPosition($config{"attackSkillSlot_${slot}_isSelfSkill"} ? $char : $target);
  537. ai_skillUse(
  538. $skill->getHandle(),
  539. $config{"attackSkillSlot_${slot}_lvl"},
  540. $config{"attackSkillSlot_${slot}_maxCastTime"},
  541. $config{"attackSkillSlot_${slot}_minCastTime"},
  542. $pos->{x},
  543. $pos->{y},
  544. "attackSkill",
  545. undef,
  546. undef,
  547. "attackSkillSlot_${slot}");
  548. }
  549. $args->{monsterID} = $ID;
  550. debug "Auto-skill on monster ".getActorName($ID).": ".qq~$config{"attackSkillSlot_$slot"} (lvl $config{"attackSkillSlot_${slot}_lvl"})n~, "ai_attack";
  551. } elsif ($args->{attackMethod}{type} eq "combo") {
  552. my $slot = $args->{attackMethod}{comboSlot};
  553. my $isSelfSkill = $args->{attackMethod}{isSelfSkill};
  554. my $skill = Skill->new(name => $config{"attackComboSlot_$slot"})->getHandle();
  555. delete $args->{attackMethod};
  556. if (!ai_getSkillUseType($skill)) {
  557. my $targetID = ($isSelfSkill) ? $accountID : $ID;
  558. ai_skillUse(
  559. $skill,
  560. $config{"attackComboSlot_${slot}_lvl"},
  561. $config{"attackComboSlot_${slot}_maxCastTime"},
  562. $config{"attackComboSlot_${slot}_minCastTime"},
  563. $targetID,
  564. undef,
  565. undef,
  566. undef,
  567. $config{"attackComboSlot_${slot}_waitBeforeUse"});
  568. } else {
  569. my $pos = ($isSelfSkill) ? $char->{pos_to} : $target->{pos_to};
  570. ai_skillUse(
  571. $skill,
  572. $config{"attackComboSlot_${slot}_lvl"},
  573. $config{"attackComboSlot_${slot}_maxCastTime"},
  574. $config{"attackComboSlot_${slot}_minCastTime"},
  575. $pos->{x},
  576. $pos->{y},
  577. undef,
  578. undef,
  579. $config{"attackComboSlot_${slot}_waitBeforeUse"});
  580. }
  581. $args->{monsterID} = $ID;
  582. }
  583. } elsif ($config{tankMode}) {
  584. if ($args->{dmgTo_last} != $target->{dmgTo}) {
  585. $args->{ai_attack_giveup}{time} = time;
  586. }
  587. $args->{dmgTo_last} = $target->{dmgTo};
  588. }
  589. }
  590. 1;