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

外挂编程

开发平台:

Windows_Unix

  1. package AI::Slave;
  2. use strict;
  3. use base qw/Actor::Slave/;
  4. use Globals;
  5. use Log qw/message warning error debug/;
  6. use AI;
  7. use Utils;
  8. use Misc;
  9. use Translation;
  10. use AI::Slave::Homunculus;
  11. use AI::Slave::Mercenary;
  12. # homunculus commands/skills can only be used
  13. # if the homunculus is within this range
  14. use constant MAX_DISTANCE => 17;
  15. sub action {
  16. my $slave = shift;
  17. my $i = (defined $_[0] ? $_[0] : 0);
  18. return $slave->{slave_ai_seq}[$i];
  19. }
  20. sub args {
  21. my $slave = shift;
  22. my $i = (defined $_[0] ? $_[0] : 0);
  23. return %{$slave->{slave_ai_seq_args}[$i]};
  24. }
  25. sub dequeue {
  26. my $slave = shift;
  27. shift @{$slave->{slave_ai_seq}};
  28. shift @{$slave->{slave_ai_seq_args}};
  29. }
  30. sub queue {
  31. my $slave = shift;
  32. unshift @{$slave->{slave_ai_seq}}, shift;
  33. my $args = shift;
  34. unshift @{$slave->{slave_ai_seq_args}}, ((defined $args) ? $args : {});
  35. }
  36. sub clear {
  37. my $slave = shift;
  38. if (@_) {
  39. my $changed;
  40. for (my $i = 0; $i < @{$slave->{slave_ai_seq}}; $i++) {
  41. if (defined binFind(@_, $slave->{slave_ai_seq}[$i])) {
  42. delete $slave->{slave_ai_seq}[$i];
  43. delete $slave->{slave_ai_seq_args}[$i];
  44. $changed = 1;
  45. }
  46. }
  47. if ($changed) {
  48. my (@new_seq, @new_args);
  49. for (my $i = 0; $i < @{$slave->{slave_ai_seq}}; $i++) {
  50. if (defined $slave->{slave_ai_seq}[$i]) {
  51. push @new_seq, $slave->{slave_ai_seq}[$i];
  52. push @new_args, $slave->{slave_ai_seq_args}[$i];
  53. }
  54. }
  55. @{$slave->{slave_ai_seq}} = @new_seq;
  56. @{$slave->{slave_ai_seq_args}} = @new_args;
  57. }
  58. } else {
  59. undef @{$slave->{slave_ai_seq}};
  60. undef @{$slave->{slave_ai_seq_args}};
  61. }
  62. }
  63. sub suspend {
  64. my $slave = shift;
  65. my $i = (defined $_[0] ? $_[0] : 0);
  66. $slave->{slave_ai_seq_args}[$i]{suspended} = time if $i < @{$slave->{slave_ai_seq_args}};
  67. }
  68. sub mapChanged {
  69. my $slave = shift;
  70. my $i = (defined $_[0] ? $_[0] : 0);
  71. $slave->{slave_ai_seq_args}[$i]{mapChanged} = time if $i < @{$slave->{slave_ai_seq_args}};
  72. }
  73. sub findAction {
  74. my $slave = shift;
  75. return binFind(@{$slave->{slave_ai_seq}}, $_[0]);
  76. }
  77. sub inQueue {
  78. my $slave = shift;
  79. foreach (@_) {
  80. # Apparently using a loop is faster than calling
  81. # binFind() (which is optimized in C), because
  82. # of function call overhead.
  83. #return 1 if defined binFind(@homun_ai_seq, $_);
  84. foreach my $seq (@{$slave->{slave_ai_seq}}) {
  85. return 1 if ($_ eq $seq);
  86. }
  87. }
  88. return 0;
  89. }
  90. sub isIdle {
  91. my $slave = shift;
  92. return $slave->{slave_ai_seq}[0] eq "";
  93. }
  94. sub is {
  95. my $slave = shift;
  96. foreach (@_) {
  97. return 1 if ($slave->{slave_ai_seq}[0] eq $_);
  98. }
  99. return 0;
  100. }
  101. sub iterate {
  102. my $slave = shift;
  103. if ($slave->{appear_time} && $field{name} eq $slave->{map}) {
  104. my $slave_dist = blockDistance ($slave->position, $char->position);
  105. # auto-follow
  106. if (
  107. $slave->{slave_AI} == 2
  108. && AI::action eq "move"
  109. && !$char->{sitting}
  110. && !AI::args->{mapChanged}
  111. && !AI::args->{time_move} != $char->{time_move}
  112. && !timeOut(AI::args->{ai_move_giveup})
  113. && $slave_dist < MAX_DISTANCE
  114. && ($slave->isIdle
  115. || blockDistance(AI::args->{move_to}, $slave->{pos_to}) >= MAX_DISTANCE)
  116. && (!defined $slave->findAction('route') || !$slave->args($slave->findAction('route'))->{follow_route})
  117. ) {
  118. $slave->clear('move', 'route');
  119. if (!checkLineWalkable($slave->{pos_to}, $char->{pos_to})) {
  120. $slave->slave_route($char->{pos_to}{x}, $char->{pos_to}{y});
  121. $slave->args->{follow_route} = 1 if $slave->action eq 'route';
  122. debug sprintf("Slave follow route (distance: %.2f)n", $slave->distance()), 'homunculus';
  123. } elsif (timeOut($slave->{slave_move_retry}, 0.5)) {
  124. # No update yet, send move request again.
  125. # We do this every 0.5 secs
  126. $slave->{slave_move_retry} = time;
  127. # NOTE:
  128. # The default LUA uses sendHomunculusStandBy() for the follow AI
  129. # however, the server-side routing is very inefficient
  130. # (e.g. can't route properly around obstacles and corners)
  131. # so we make use of the sendHomunculusMove() to make up for a more efficient routing
  132. $slave->sendMove ($char->{pos_to}{x}, $char->{pos_to}{y});
  133. debug sprintf("Slave follow move (distance: %.2f)n", $slave->distance()), 'homunculus';
  134. }
  135. # homunculus is found
  136. } elsif ($slave->{slave_lost}) {
  137. if ($slave_dist < MAX_DISTANCE) {
  138. delete $slave->{slave_lost};
  139. delete $slave->{lostRoute};
  140. my $action = $slave->findAction('route');
  141. if (defined $action && $slave->args($action)->{lost_route}) {
  142. for (my $i = 0; $i <= $action; $i++) {
  143. $slave->dequeue
  144. }
  145. }
  146. if (timeOut($slave->{standby_time}, 1)) {
  147. $slave->sendStandBy;
  148. $slave->{standby_time} = time;
  149. }
  150. message T("Found your Slave!n"), 'homunculus';
  151. # attempt to find homunculus on it's last known coordinates
  152. } elsif ($AI == 2 && !$slave->{lostRoute}) {
  153. if ($config{teleportAuto_lostHomunculus}) {
  154. message T("Teleporting to get slave backn"), 'teleport';
  155. useTeleport(1);
  156. } else {
  157. my $x = $slave->{pos_to}{x};
  158. my $y = $slave->{pos_to}{y};
  159. my $distFromGoal = $config{$slave->{slave_configPrefix}.'followDistanceMax'};
  160. $distFromGoal = MAX_DISTANCE if ($distFromGoal > MAX_DISTANCE);
  161. main::ai_route($field{name}, $x, $y, distFromGoal => $distFromGoal, attackOnRoute => 1, noSitAuto => 1);
  162. $slave->args->{lost_route} = 1 if $slave->action eq 'route';
  163. message TF("Trying to find your slave at location %d, %d (you are currently at %d, %d)n", $x, $y, $char->{pos_to}{x}, $char->{pos_to}{y}), 'homunculus';
  164. }
  165. $slave->{lostRoute} = 1;
  166. }
  167. # homunculus is lost
  168. } elsif ($slave->{actorType} eq 'Homunculus' && $slave_dist >= MAX_DISTANCE && !$slave->{slave_lost}) {
  169. $slave->{slave_lost} = 1;
  170. message T("You lost your Homunculus!n"), 'homunculus';
  171.  
  172. # if your homunculus is idle, make it move near you
  173. } elsif (
  174. $slave->{slave_AI} == 2
  175. && $slave->isIdle
  176. && $slave_dist > ($config{$slave->{slave_configPrefix}.'followDistanceMin'} || 3)
  177. && $slave_dist < MAX_DISTANCE
  178. && timeOut($slave->{standby_time}, 2)
  179. ) {
  180. $slave->sendStandBy;
  181. $slave->{standby_time} = time;
  182. debug sprintf("Slave standby (distance: %.2f)n", $slave->distance());
  183. # if you are idle, move near the homunculus
  184. } elsif (
  185. $slave->{actorType} eq 'Homunculus' &&
  186. $AI == 2 && AI::isIdle && !$slave->isIdle
  187. && $config{$slave->{slave_configPrefix}.'followDistanceMax'}
  188. && $slave_dist > $config{$slave->{slave_configPrefix}.'followDistanceMax'}
  189. ) {
  190. main::ai_route($field{name}, $slave->{pos_to}{x}, $slave->{pos_to}{y}, distFromGoal => ($config{$slave->{slave_configPrefix}.'followDistanceMin'} || 3), attackOnRoute => 1, noSitAuto => 1);
  191. message TF("Your Slave moves too far (distance: %.2f) - Moving near your Slaven", $slave->distance()), 'homunculus';
  192. # Main Homunculus AI
  193. } else {
  194. return unless $slave->{slave_AI};
  195. return if $slave->processClientSuspend;
  196. $slave->processAttack;
  197. $slave->processRouteAI;
  198. $slave->processMove;
  199. return unless $slave->{slave_AI} == 2;
  200. $slave->processAutoAttack;
  201. }
  202. }
  203. }
  204. ##
  205. # ai_clientSuspend(packet_switch, duration, args...)
  206. # initTimeout: a number of seconds.
  207. #
  208. # Freeze the AI for $duration seconds. $packet_switch and @args are only
  209. # used internally and are ignored unless XKore mode is turned on.
  210. sub slave_clientSuspend {
  211. my ($slave, $type, $duration, @args) = @_;
  212. my %args;
  213. $args{type} = $type;
  214. $args{time} = time;
  215. $args{timeout} = $duration;
  216. @{$args{args}} = @args;
  217. $slave->queue("clientSuspend", %args);
  218. debug "Slave AI suspended by clientSuspend for $args{timeout} secondsn";
  219. }
  220. sub slave_setSuspend {
  221. my ($slave, $index) = @_;
  222. $index = 0 if ($index eq "");
  223. if ($index < @{$slave->{slave_ai_seq_args}}) {
  224. $slave->{slave_ai_seq_args}[$index]{'suspended'} = time;
  225. }
  226. }
  227. sub slave_setMapChanged {
  228. my ($slave, $index) = @_;
  229. $index = 0 if ($index eq "");
  230. if ($index < @{$slave->{slave_seq_args}}) {
  231. $slave->{slave_seq_args}[$index]{'mapChanged'} = time;
  232. }
  233. }
  234. sub slave_stopAttack {
  235. my $slave = shift;
  236. #$messageSender->sendHomunculusStandBy($char->{homunculus}{ID});
  237. my $pos = calcPosition($slave);
  238. $slave->sendMove ($pos->{x}, $pos->{y});
  239. }
  240. sub slave_attack {
  241. my ($slave, $ID) = @_;
  242. #my $priorityAttack = shift;
  243. my %args;
  244. my $target = Actor::get($ID);
  245. $args{'ai_attack_giveup'}{'time'} = time;
  246. $args{'ai_attack_giveup'}{'timeout'} = $timeout{'ai_attack_giveup'}{'timeout'};
  247. $args{'ID'} = $ID;
  248. $args{'unstuck'}{'timeout'} = ($timeout{'ai_attack_unstuck'}{'timeout'} || 1.5);
  249. %{$args{'pos_to'}} = %{$target->{'pos_to'}};
  250. %{$args{'pos'}} = %{$target->{'pos'}};
  251. $slave->queue("attack", %args);
  252. #if ($priorityAttack) {
  253. # message TF("Priority Attacking: %sn", $target);
  254. #} else {
  255. message TF("Slave attacking: %sn", $target), 'homunculus_attack';
  256. #}
  257. }
  258. sub slave_move {
  259. my $slave = shift;
  260. my $x = shift;
  261. my $y = shift;
  262. my $attackID = shift;
  263. my %args;
  264. my $dist;
  265. $args{move_to}{x} = $x;
  266. $args{move_to}{y} = $y;
  267. $args{attackID} = $attackID;
  268. $args{time_move} = $slave->{time_move};
  269. $dist = distance($slave->{pos}, $args{move_to});
  270. $args{ai_move_giveup}{timeout} = $timeout{ai_move_giveup}{timeout};
  271. if ($x == 0 && $y == 0) {
  272. error "BUG: move(0, 0) called!n";
  273. return;
  274. }
  275. debug sprintf("Sending slave move from (%d,%d) to (%d,%d) - distance %.2fn",
  276. $slave->{pos}{x}, $slave->{pos}{y}, $x, $y, $dist), "ai_move";
  277. $slave->queue("move", %args);
  278. }
  279. sub slave_route {
  280. my $slave = shift;
  281. my $map = $field{name};
  282. my $x = shift;
  283. my $y = shift;
  284. my %param = @_;
  285. debug "Slave on route to: $maps_lut{$map.'.rsw'}($map): $x, $yn", "route";
  286. my %args;
  287. $x = int($x) if ($x ne "");
  288. $y = int($y) if ($y ne "");
  289. $args{'dest'}{'map'} = $map;
  290. $args{'dest'}{'pos'}{'x'} = $x;
  291. $args{'dest'}{'pos'}{'y'} = $y;
  292. $args{'maxRouteDistance'} = $param{maxRouteDistance} if exists $param{maxRouteDistance};
  293. $args{'maxRouteTime'} = $param{maxRouteTime} if exists $param{maxRouteTime};
  294. $args{'attackOnRoute'} = $param{attackOnRoute} if exists $param{attackOnRoute};
  295. $args{'distFromGoal'} = $param{distFromGoal} if exists $param{distFromGoal};
  296. $args{'pyDistFromGoal'} = $param{pyDistFromGoal} if exists $param{pyDistFromGoal};
  297. $args{'attackID'} = $param{attackID} if exists $param{attackID};
  298. $args{'noSitAuto'} = $param{noSitAuto} if exists $param{noSitAuto};
  299. $args{'noAvoidWalls'} = $param{noAvoidWalls} if exists $param{noAvoidWalls};
  300. $args{notifyUponArrival} = $param{notifyUponArrival} if exists $param{notifyUponArrival};
  301. $args{'tags'} = $param{tags} if exists $param{tags};
  302. $args{'time_start'} = time;
  303. if (!$param{'_internal'}) {
  304. $args{'solution'} = [];
  305. $args{'mapSolution'} = [];
  306. } elsif (exists $param{'_solution'}) {
  307. $args{'solution'} = $param{'_solution'};
  308. }
  309. # Destination is same map and isn't blocked by walls/water/whatever
  310. my $pos = calcPosition($slave);
  311. require Task::Route;
  312. if ($param{'_internal'} || (Task::Route->getRoute(@{$args{solution}}, $field, $pos, $args{dest}{pos}, !$args{noAvoidWalls}))) {
  313. # Since the solution array is here, we can start in "Route Solution Ready"
  314. $args{'stage'} = 'Route Solution Ready';
  315. debug "Slave route Solution Readyn", "route";
  316. $slave->queue("route", %args);
  317. }
  318. }
  319. ##### ATTACK #####
  320. sub processAttack {
  321. my $slave = shift;
  322. #Benchmark::begin("ai_homunculus_attack") if DEBUG;
  323. if ($slave->action eq "attack" && $slave->args->{suspended}) {
  324. $slave->args->{ai_attack_giveup}{time} += time - $slave->args->{suspended};
  325. delete $slave->args->{suspended};
  326. }
  327. if ($slave->action eq "attack" && $slave->args->{move_start}) {
  328. # We've just finished moving to the monster.
  329. # Don't count the time we spent on moving
  330. $slave->args->{ai_attack_giveup}{time} += time - $slave->args->{move_start};
  331. undef $slave->args->{unstuck}{time};
  332. undef $slave->args->{move_start};
  333. } elsif ($slave->action eq "attack" && $slave->args->{avoiding} && $slave->args->{attackID}) {
  334. my $target = Actor::get($slave->args->{attackID});
  335. $slave->args->{ai_attack_giveup}{time} = time + $target->{time_move_calc} + 3;
  336. undef $slave->args->{avoiding};
  337. } elsif ((($slave->action eq "route" && $slave->action (1) eq "attack") || ($slave->action eq "move" && $slave->action (2) eq "attack"))
  338.    && $slave->args->{attackID} && timeOut($slave->{slave_attack_route_adjust}, 1)) {
  339. # We're on route to the monster; check whether the monster has moved
  340. my $ID = $slave->args->{attackID};
  341. my $attackSeq = ($slave->action eq "route") ? $slave->args (1) : $slave->args (2);
  342. my $target = Actor::get($ID);
  343. if ($target->{type} ne 'Unknown' && $attackSeq->{monsterPos} && %{$attackSeq->{monsterPos}}
  344.  && distance(calcPosition($target), $attackSeq->{monsterPos}) > $attackSeq->{attackMethod}{maxDistance}) {
  345. # Monster has moved; stop moving and let the attack AI readjust route
  346. $slave->dequeue;
  347. $slave->dequeue if $slave->action eq "route";
  348. $attackSeq->{ai_attack_giveup}{time} = time;
  349. debug "Slave target has moved more than $attackSeq->{attackMethod}{maxDistance} blocks; readjusting routen", "ai_attack";
  350. } elsif ($target->{type} ne 'Unknown' && $attackSeq->{monsterPos} && %{$attackSeq->{monsterPos}}
  351.  && distance(calcPosition($target), calcPosition($slave)) <= $attackSeq->{attackMethod}{maxDistance}) {
  352. # Monster is within attack range; stop moving
  353. $slave->dequeue;
  354. $slave->dequeue if $slave->action eq "route";
  355. $attackSeq->{ai_attack_giveup}{time} = time;
  356. debug "Slave target at ($attackSeq->{monsterPos}{x},$attackSeq->{monsterPos}{y}) is now within " .
  357. "$attackSeq->{attackMethod}{maxDistance} blocks; stop movingn", "ai_attack";
  358. }
  359. $slave->{slave_attack_route_adjust} = time;
  360. }
  361. if ($slave->action eq "attack" &&
  362.     (timeOut($slave->args->{ai_attack_giveup}) ||
  363.  $slave->args->{unstuck}{count} > 5) &&
  364. !$config{$slave->{slave_configPrefix}.'attackNoGiveup'}) {
  365. my $ID = $slave->args->{ID};
  366. my $target = Actor::get($ID);
  367. $target->{homunculus_attack_failed} = time if $monsters{$ID};
  368. $slave->dequeue;
  369. message T("Slave can't reach or damage target, dropping targetn"), 'homunculus_attack';
  370. if ($config{$slave->{slave_configPrefix}.'teleportAuto_dropTarget'}) {
  371. message T("Teleport due to dropping slave attack targetn"), 'teleport';
  372. useTeleport(1);
  373. }
  374. } elsif ($slave->action eq "attack" && !$monsters{$slave->args->{ID}} && (!$players{$slave->args->{ID}} || $players{$slave->args->{ID}}{dead})) {
  375. # Monster died or disappeared
  376. $timeout{'ai_homunculus_attack'}{'time'} -= $timeout{'ai_homunculus_attack'}{'timeout'};
  377. my $ID = $slave->args->{ID};
  378. $slave->dequeue;
  379. if ($monsters_old{$ID} && $monsters_old{$ID}{dead}) {
  380. message T("Slave target diedn"), 'homunculus_attack';
  381. Plugins::callHook("homonulus_target_died");
  382. monKilled();
  383. # Pickup loot when monster's dead
  384. if ($AI == 2 && $config{itemsTakeAuto} && $monsters_old{$ID}{dmgFromPlayer}{$slave->{ID}} > 0 && !$monsters_old{$ID}{homunculus_ignore}) {
  385. AI::clear("items_take");
  386. ai_items_take($monsters_old{$ID}{pos}{x}, $monsters_old{$ID}{pos}{y},
  387. $monsters_old{$ID}{pos_to}{x}, $monsters_old{$ID}{pos_to}{y});
  388. } else {
  389. # Cheap way to suspend all movement to make it look real
  390. $slave->slave_clientSuspend(0, $timeout{'ai_attack_waitAfterKill'}{'timeout'});
  391. }
  392. ## kokal start
  393. ## mosters counting
  394. my $i = 0;
  395. my $found = 0;
  396. while ($monsters_Killed[$i]) {
  397. if ($monsters_Killed[$i]{'nameID'} eq $monsters_old{$ID}{'nameID'}) {
  398. $monsters_Killed[$i]{'count'}++;
  399. monsterLog($monsters_Killed[$i]{'name'});
  400. $found = 1;
  401. last;
  402. }
  403. $i++;
  404. }
  405. if (!$found) {
  406. $monsters_Killed[$i]{'nameID'} = $monsters_old{$ID}{'nameID'};
  407. $monsters_Killed[$i]{'name'} = $monsters_old{$ID}{'name'};
  408. $monsters_Killed[$i]{'count'} = 1;
  409. monsterLog($monsters_Killed[$i]{'name'})
  410. }
  411. ## kokal end
  412. } else {
  413. message T("Slave target lostn"), 'homunculus_attack';
  414. }
  415. } elsif ($slave->action eq "attack") {
  416. # The attack sequence hasn't timed out and the monster is on screen
  417. # Update information about the monster and the current situation
  418. my $args = $slave->args;
  419. my $ID = $args->{ID};
  420. my $target = Actor::get($ID);
  421. my $myPos = $slave->{pos_to};
  422. my $monsterPos = $target->{pos_to};
  423. my $monsterDist = distance($myPos, $monsterPos);
  424. my ($realMyPos, $realMonsterPos, $realMonsterDist, $hitYou);
  425. my $realMyPos = calcPosition($slave);
  426. my $realMonsterPos = calcPosition($target);
  427. my $realMonsterDist = distance($realMyPos, $realMonsterPos);
  428. if (!$config{$slave->{slave_configPrefix}.'runFromTarget'}) {
  429. $myPos = $realMyPos;
  430. $monsterPos = $realMonsterPos;
  431. }
  432. my $cleanMonster = checkMonsterCleanness($ID);
  433. # If the damage numbers have changed, update the giveup time so we don't timeout
  434. if ($args->{dmgToYou_last}   != $target->{dmgToPlayer}{$slave->{ID}}
  435.  || $args->{missedYou_last}  != $target->{missedToPlayer}{$slave->{ID}}
  436.  || $args->{dmgFromYou_last} != $target->{dmgFromPlayer}{$slave->{ID}}) {
  437. $args->{ai_attack_giveup}{time} = time;
  438. debug "Update slave attack giveup timen", "ai_attack", 2;
  439. }
  440. $hitYou = ($args->{dmgToYou_last} != $target->{dmgToPlayer}{$slave->{ID}}
  441. || $args->{missedYou_last} != $target->{missedToPlayer}{$slave->{ID}});
  442. $args->{dmgToYou_last} = $target->{dmgToPlayer}{$slave->{ID}};
  443. $args->{missedYou_last} = $target->{missedToPlayer}{$slave->{ID}};
  444. $args->{dmgFromYou_last} = $target->{dmgFromPlayer}{$slave->{ID}};
  445. $args->{missedFromYou_last} = $target->{missedFromPlayer}{$slave->{ID}};
  446. $args->{attackMethod}{type} = "weapon";
  447. $args->{attackMethod}{maxDistance} = $config{$slave->{slave_configPrefix}.'attackMaxDistance'};
  448. $args->{attackMethod}{distance} = ($config{$slave->{slave_configPrefix}.'runFromTarget'} && $config{$slave->{slave_configPrefix}.'runFromTarget_dist'} > $config{$slave->{slave_configPrefix}.'attackDistance'}) ? $config{$slave->{slave_configPrefix}.'runFromTarget_dist'} : $config{$slave->{slave_configPrefix}.'attackDistance'};
  449. if ($args->{attackMethod}{maxDistance} < $args->{attackMethod}{distance}) {
  450. $args->{attackMethod}{maxDistance} = $args->{attackMethod}{distance};
  451. }
  452. if (!$cleanMonster) {
  453. # Drop target if it's already attacked by someone else
  454. $target->{homunculus_attack_failed} = time if $monsters{$ID};
  455. message T("Dropping target - slave will not kill steal othersn"), 'homunculus_attack';
  456. $slave->sendMove ($realMyPos->{x}, $realMyPos->{y});
  457. $slave->dequeue;
  458. if ($config{$slave->{slave_configPrefix}.'teleportAuto_dropTargetKS'}) {
  459. message T("Teleporting due to dropping slave attack targetn"), 'teleport';
  460. useTeleport(1);
  461. }
  462. } elsif ($config{$slave->{slave_configPrefix}.'attackCheckLOS'} &&
  463.  $args->{attackMethod}{distance} > 2 &&
  464.  !checkLineSnipable($realMyPos, $realMonsterPos)) {
  465. # We are a ranged attacker without LOS
  466. # Calculate squares around monster within shooting range, but not
  467. # closer than runFromTarget_dist
  468. my @stand = calcRectArea2($realMonsterPos->{x}, $realMonsterPos->{y},
  469.   $args->{attackMethod}{distance},
  470.   $config{$slave->{slave_configPrefix}.'runFromTarget'} ? $config{$slave->{slave_configPrefix}.'runFromTarget_dist'} : 0);
  471. # Determine which of these spots are snipable
  472. my $best_spot;
  473. my $best_dist;
  474. for my $spot (@stand) {
  475. # Is this spot acceptable?
  476. # 1. It must have LOS to the target ($realMonsterPos).
  477. # 2. It must be within $config{followDistanceMax} of
  478. #    $masterPos, if we have a master.
  479. if (checkLineSnipable($spot, $realMonsterPos) &&
  480.     (distance($spot, $char->{pos_to}) <= 15)) {
  481. # FIXME: use route distance, not pythagorean distance
  482. my $dist = distance($realMyPos, $spot);
  483. if (!defined($best_dist) || $dist < $best_dist) {
  484. $best_dist = $dist;
  485. $best_spot = $spot;
  486. }
  487. }
  488. }
  489. # Move to the closest spot
  490. my $msg = "Slave has no LOS from ($realMyPos->{x}, $realMyPos->{y}) to target ($realMonsterPos->{x}, $realMonsterPos->{y})";
  491. if ($best_spot) {
  492. message TF("%s; moving to (%s, %s)n", $msg, $best_spot->{x}, $best_spot->{y}), 'homunculus_attack';
  493. $slave->slave_route($best_spot->{x}, $best_spot->{y});
  494. } else {
  495. warning TF("%s; no acceptable place for slave to standn", $msg);
  496. $slave->dequeue;
  497. }
  498. } elsif ($config{$slave->{slave_configPrefix}.'runFromTarget'} && ($monsterDist < $config{$slave->{slave_configPrefix}.'runFromTarget_dist'} || $hitYou)) {
  499. #my $begin = time;
  500. # Get a list of blocks that we can run to
  501. my @blocks = calcRectArea($myPos->{x}, $myPos->{y},
  502. # If the monster hit you while you're running, then your recorded
  503. # location may be out of date. So we use a smaller distance so we can still move.
  504. ($hitYou) ? $config{$slave->{slave_configPrefix}.'runFromTarget_dist'} / 2 : $config{$slave->{slave_configPrefix}.'runFromTarget_dist'});
  505. # Find the distance value of the block that's farthest away from a wall
  506. my $highest;
  507. foreach (@blocks) {
  508. my $dist = ord(substr($field{dstMap}, $_->{y} * $field{width} + $_->{x}));
  509. if (!defined $highest || $dist > $highest) {
  510. $highest = $dist;
  511. }
  512. }
  513. # Get rid of rediculously large route distances (such as spots that are on a hill)
  514. # Get rid of blocks that are near a wall
  515. my $pathfinding = new PathFinding;
  516. use constant AVOID_WALLS => 4;
  517. for (my $i = 0; $i < @blocks; $i++) {
  518. # We want to avoid walls (so we don't get cornered), if possible
  519. my $dist = ord(substr($field{dstMap}, $blocks[$i]{y} * $field{width} + $blocks[$i]{x}));
  520. if ($highest >= AVOID_WALLS && $dist < AVOID_WALLS) {
  521. delete $blocks[$i];
  522. next;
  523. }
  524. $pathfinding->reset(
  525. field => $field,
  526. start => $myPos,
  527. dest => $blocks[$i]);
  528. my $ret = $pathfinding->runcount;
  529. if ($ret <= 0 || $ret > $config{$slave->{slave_configPrefix}.'runFromTarget_dist'} * 2) {
  530. delete $blocks[$i];
  531. next;
  532. }
  533. }
  534. # Find the block that's farthest to us
  535. my $largestDist;
  536. my $bestBlock;
  537. foreach (@blocks) {
  538. next unless defined $_;
  539. my $dist = distance($monsterPos, $_);
  540. if (!defined $largestDist || $dist > $largestDist) {
  541. $largestDist = $dist;
  542. $bestBlock = $_;
  543. }
  544. }
  545. #message "Time spent: " . (time - $begin) . "n";
  546. #debug_showSpots('runFromTarget', @blocks, $bestBlock);
  547. $slave->args->{avoiding} = 1;
  548. $slave->slave_move($bestBlock->{x}, $bestBlock->{y}, $ID);
  549. } elsif (!$config{$slave->{slave_configPrefix}.'runFromTarget'} && $monsterDist > $args->{attackMethod}{maxDistance}
  550.   && timeOut($args->{ai_attack_giveup}, 0.5)) {
  551. # The target monster moved; move to target
  552. $args->{move_start} = time;
  553. $args->{monsterPos} = {%{$monsterPos}};
  554. # Calculate how long it would take to reach the monster.
  555. # Calculate where the monster would be when you've reached its
  556. # previous position.
  557. my $time_needed;
  558. if (objectIsMovingTowards($target, $slave, 45)) {
  559. $time_needed = $monsterDist * $slave->{walk_speed};
  560. } else {
  561. # If monster is not moving towards you, then you need more time to walk
  562. $time_needed = $monsterDist * $slave->{walk_speed} + 2;
  563. }
  564. my $pos = calcPosition($target, $time_needed);
  565. my $dist = sprintf("%.1f", $monsterDist);
  566. debug "Slave target distance $dist is >$args->{attackMethod}{maxDistance}; moving to target: " .
  567. "from ($myPos->{x},$myPos->{y}) to ($pos->{x},$pos->{y})n", "ai_attack";
  568. my $result = $slave->slave_route($pos->{x}, $pos->{y},
  569. distFromGoal => $args->{attackMethod}{distance},
  570. maxRouteTime => $config{$slave->{slave_configPrefix}.'attackMaxRouteTime'},
  571. attackID => $ID,
  572. noMapRoute => 1,
  573. noAvoidWalls => 1);
  574. if (!$result) {
  575. # Unable to calculate a route to target
  576. $target->{homunculus_attack_failed} = time;
  577. $slave->dequeue;
  578.   message T("Unable to calculate a route to slave target, dropping targetn"), 'homunculus_attack';
  579. if ($config{$slave->{slave_configPrefix}.'teleportAuto_dropTarget'}) {
  580. message T("Teleport due to dropping slave attack targetn"), 'teleport';
  581. useTeleport(1);
  582. }
  583. }
  584. } elsif ((!$config{$slave->{slave_configPrefix}.'runFromTarget'} || $realMonsterDist >= $config{$slave->{slave_configPrefix}.'runFromTarget_dist'})
  585.  && (!$config{$slave->{slave_configPrefix}.'tankMode'} || !$target->{dmgFromPlayer}{$slave->{ID}})) {
  586. # Attack the target. In case of tanking, only attack if it hasn't been hit once.
  587. if (!$slave->args->{firstAttack}) {
  588. $slave->args->{firstAttack} = 1;
  589. my $dist = sprintf("%.1f", $monsterDist);
  590. my $pos = "$myPos->{x},$myPos->{y}";
  591. debug "Slave is ready to attack target (which is $dist blocks away); we're at ($pos)n", "ai_attack";
  592. }
  593. $args->{unstuck}{time} = time if (!$args->{unstuck}{time});
  594. if (!$target->{dmgFromPlayer}{$slave->{ID}} && timeOut($args->{unstuck})) {
  595. # We are close enough to the target, and we're trying to attack it,
  596. # but some time has passed and we still haven't dealed any damage.
  597. # Our recorded position might be out of sync, so try to unstuck
  598. $args->{unstuck}{time} = time;
  599. debug("Slave attack - trying to unstuckn", "ai_attack");
  600. $slave->slave_move($myPos->{x}, $myPos->{y});
  601. $args->{unstuck}{count}++;
  602. }
  603. if ($args->{attackMethod}{type} eq "weapon" && timeOut($timeout{ai_homunculus_attack})) {
  604. $slave->sendAttack ($ID);#,
  605. #($config{homunculus_tankMode}) ? 0 : 7);
  606. $timeout{ai_homunculus_attack}{time} = time;
  607. delete $args->{attackMethod};
  608. }
  609. } elsif ($config{$slave->{slave_configPrefix}.'tankMode'}) {
  610. if ($args->{'dmgTo_last'} != $target->{dmgFromPlayer}{$slave->{ID}}) {
  611. $args->{'ai_attack_giveup'}{'time'} = time;
  612. $slave->slave_stopAttack ();
  613. }
  614. $args->{'dmgTo_last'} = $target->{dmgFromPlayer}{$slave->{ID}};
  615. }
  616. }
  617. # Check for kill steal while moving
  618. if ($slave->is("move", "route") && $slave->args->{attackID} && $slave->inQueue("attack")) {
  619. my $ID = $slave->args->{attackID};
  620. if ((my $target = $monsters{$ID}) && !checkMonsterCleanness($ID)) {
  621. $target->{homunculus_attack_failed} = time;
  622. message T("Dropping target - slave will not kill steal othersn"), 'homunculus_attack';
  623. $slave->slave_stopAttack();
  624. $monsters{$ID}{homunculus_ignore} = 1;
  625. # Right now, the queue is either
  626. #   move, route, attack
  627. # -or-
  628. #   route, attack
  629. $slave->dequeue;
  630. $slave->dequeue;
  631. $slave->dequeue if ($slave->action eq "attack");
  632. if ($config{$slave->{slave_configPrefix}.'teleportAuto_dropTargetKS'}) {
  633. message T("Teleport due to dropping slave attack targetn"), 'teleport';
  634. useTeleport(1);
  635. }
  636. }
  637. }
  638. #Benchmark::end("ai_homunculus_attack") if DEBUG;
  639. }
  640. ####### ROUTE #######
  641. sub processRouteAI {
  642. my $slave = shift;
  643. if ($slave->action eq "route" && $slave->args->{suspended}) {
  644. $slave->args->{time_start} += time - $slave->args->{suspended};
  645. $slave->args->{time_step} += time - $slave->args->{suspended};
  646. delete $slave->args->{suspended};
  647. }
  648. if ($slave->action eq "route" && $field{'name'} && $slave->{pos_to}{x} ne '' && $slave->{pos_to}{y} ne '') {
  649. my $args = $slave->args;
  650. if ( $args->{maxRouteTime} && timeOut($args->{time_start}, $args->{maxRouteTime})) {
  651. # We spent too much time
  652. debug "Slave route - we spent too much time; bailing out.n", "route";
  653. $slave->dequeue;
  654. } elsif ($field{name} ne $args->{dest}{map} || $args->{mapChanged}) {
  655. debug "Slave map changed: $field{name} $args->{dest}{map}n", "route";
  656. $slave->dequeue;
  657. } elsif ($args->{stage} eq '') {
  658. my $pos = calcPosition($slave);
  659. $args->{solution} = [];
  660. if (Task::Route->getRoute($args->{solution}, $field, $pos, $args->{dest}{pos})) {
  661. $args->{stage} = 'Route Solution Ready';
  662. debug "Slave route Solution Readyn", "route";
  663. } else {
  664. debug "Something's wrong; there is no path to $field{name}($args->{dest}{pos}{x},$args->{dest}{pos}{y}).n", "debug";
  665. $slave->dequeue;
  666. }
  667. } elsif ($args->{stage} eq 'Route Solution Ready') {
  668. my $solution = $args->{solution};
  669. if ($args->{maxRouteDistance} > 0 && $args->{maxRouteDistance} < 1) {
  670. # Fractional route motion
  671. $args->{maxRouteDistance} = int($args->{maxRouteDistance} * scalar(@{$solution}));
  672. }
  673. splice(@{$solution}, 1 + $args->{maxRouteDistance}) if $args->{maxRouteDistance} && $args->{maxRouteDistance} < @{$solution};
  674. # Trim down solution tree for pyDistFromGoal or distFromGoal
  675. if ($args->{pyDistFromGoal}) {
  676. my $trimsteps = 0;
  677. $trimsteps++ while ($trimsteps < @{$solution}
  678.  && distance($solution->[@{$solution} - 1 - $trimsteps], $solution->[@{$solution} - 1]) < $args->{pyDistFromGoal}
  679. );
  680. debug "Slave route - trimming down solution by $trimsteps steps for pyDistFromGoal $args->{'pyDistFromGoal'}n", "route";
  681. splice(@{$args->{'solution'}}, -$trimsteps) if ($trimsteps);
  682. } elsif ($args->{distFromGoal}) {
  683. my $trimsteps = $args->{distFromGoal};
  684. $trimsteps = @{$args->{'solution'}} if $trimsteps > @{$args->{'solution'}};
  685. debug "Slave route - trimming down solution by $trimsteps steps for distFromGoal $args->{'distFromGoal'}n", "route";
  686. splice(@{$args->{solution}}, -$trimsteps) if ($trimsteps);
  687. }
  688. undef $args->{mapChanged};
  689. undef $args->{index};
  690. undef $args->{old_x};
  691. undef $args->{old_y};
  692. undef $args->{new_x};
  693. undef $args->{new_y};
  694. $args->{time_step} = time;
  695. $args->{stage} = 'Walk the Route Solution';
  696. } elsif ($args->{stage} eq 'Walk the Route Solution') {
  697. my $pos = calcPosition($slave);
  698. my ($cur_x, $cur_y) = ($pos->{x}, $pos->{y});
  699. unless (@{$args->{solution}}) {
  700. # No more points to cover; we've arrived at the destination
  701. if ($args->{notifyUponArrival}) {
  702.   message T("Slave destination reached.n"), "success";
  703. } else {
  704. debug "Slave destination reached.n", "route";
  705. }
  706. $slave->dequeue;
  707. } elsif ($args->{old_x} == $cur_x && $args->{old_y} == $cur_y && timeOut($args->{time_step}, 3)) {
  708. # We tried to move for 3 seconds, but we are still on the same spot,
  709. # decrease step size.
  710. # However, if $args->{index} was already 0, then that means
  711. # we were almost at the destination (only 1 more step is needed).
  712. # But we got interrupted (by auto-attack for example). Don't count that
  713. # as stuck.
  714. my $wasZero = $args->{index} == 0;
  715. $args->{index} = int($args->{index} * 0.8);
  716. if ($args->{index}) {
  717. debug "Slave route - not moving, decreasing step size to $args->{index}n", "route";
  718. if (@{$args->{solution}}) {
  719. # If we still have more points to cover, walk to next point
  720. $args->{index} = @{$args->{solution}} - 1 if $args->{index} >= @{$args->{solution}};
  721. $args->{new_x} = $args->{solution}[$args->{index}]{x};
  722. $args->{new_y} = $args->{solution}[$args->{index}]{y};
  723. $args->{time_step} = time;
  724. $slave->slave_move($args->{new_x}, $args->{new_y}, $args->{attackID});
  725. }
  726. } elsif (!$wasZero) {
  727. # We're stuck
  728. my $msg = TF("Slave is stuck at %s (%d,%d), while walking from (%d,%d) to (%d,%d).", 
  729. $field{name}, $slave->{pos_to}{x}, $slave->{pos_to}{y}, $cur_x, $cur_y, $args->{dest}{pos}{x}, $args->{dest}{pos}{y});
  730. $msg .= T(" Teleporting to unstuck.") if $config{$slave->{slave_configPrefix}.'teleportAuto_unstuck'};
  731. $msg .= "n";
  732. warning $msg, "route";
  733. useTeleport(1) if $config{$slave->{slave_configPrefix}.'teleportAuto_unstuck'};
  734. $slave->dequeue;
  735. } else {
  736. $args->{time_step} = time;
  737. }
  738. } else {
  739. # We're either starting to move or already moving, so send out more
  740. # move commands periodically to keep moving and updating our position
  741. my $solution = $args->{solution};
  742. $args->{index} = $config{$slave->{slave_configPrefix}.'route_step'} unless $args->{index};
  743. $args->{index}++ if ($args->{index} < $config{$slave->{slave_configPrefix}.'route_step'});
  744. if (defined($args->{old_x}) && defined($args->{old_y})) {
  745. # See how far we've walked since the last move command and
  746. # trim down the soultion tree by this distance.
  747. # Only remove the last step if we reached the destination
  748. my $trimsteps = 0;
  749. # If position has changed, we must have walked at least one step
  750. $trimsteps++ if ($cur_x != $args->{'old_x'} || $cur_y != $args->{'old_y'});
  751. # Search the best matching entry for our position in the solution
  752. while ($trimsteps < @{$solution}
  753.  && distance( { x => $cur_x, y => $cur_y }, $solution->[$trimsteps + 1])
  754.     < distance( { x => $cur_x, y => $cur_y }, $solution->[$trimsteps])
  755. ) {
  756. $trimsteps++;
  757. }
  758. # Remove the last step also if we reached the destination
  759. $trimsteps = @{$solution} - 1 if ($trimsteps >= @{$solution});
  760. #$trimsteps = @{$solution} if ($trimsteps <= $args->{'index'} && $args->{'new_x'} == $cur_x && $args->{'new_y'} == $cur_y);
  761. $trimsteps = @{$solution} if ($cur_x == $solution->[$#{$solution}]{x} && $cur_y == $solution->[$#{$solution}]{y});
  762. debug "Slave route - trimming down solution (" . @{$solution} . ") by $trimsteps stepsn", "route";
  763. splice(@{$solution}, 0, $trimsteps) if ($trimsteps > 0);
  764. }
  765. my $stepsleft = @{$solution};
  766. if ($stepsleft > 0) {
  767. # If we still have more points to cover, walk to next point
  768. $args->{index} = $stepsleft - 1 if ($args->{index} >= $stepsleft);
  769. $args->{new_x} = $args->{solution}[$args->{index}]{x};
  770. $args->{new_y} = $args->{solution}[$args->{index}]{y};
  771. # But first, check whether the distance of the next point isn't abnormally large.
  772. # If it is, then we've moved to an unexpected place. This could be caused by auto-attack,
  773. # for example.
  774. my %nextPos = (x => $args->{new_x}, y => $args->{new_y});
  775. if (distance(%nextPos, $pos) > $config{$slave->{slave_configPrefix}.'route_step'}) {
  776. debug "Slave route - movement interrupted: reset routen", "route";
  777. $args->{stage} = '';
  778. } else {
  779. $args->{old_x} = $cur_x;
  780. $args->{old_y} = $cur_y;
  781. $args->{time_step} = time if ($cur_x != $args->{old_x} || $cur_y != $args->{old_y});
  782. debug "Slave route - next step moving to ($args->{new_x}, $args->{new_y}), index $args->{index}, $stepsleft steps leftn", "route";
  783. $slave->slave_move($args->{new_x}, $args->{new_y}, $args->{attackID});
  784. }
  785. } else {
  786. # No more points to cover
  787. if ($args->{notifyUponArrival}) {
  788.   message T("Slave destination reached.n"), "success";
  789. } else {
  790. debug "Slave destination reached.n", "route";
  791. }
  792. $slave->dequeue;
  793. }
  794. }
  795. } else {
  796. debug "Unexpected slave route stage [$args->{stage}] occured.n", "route";
  797. $slave->dequeue;
  798. }
  799. }
  800. }
  801. ##### MOVE #####
  802. sub processMove {
  803. my $slave = shift;
  804. if ($slave->action eq "move") {
  805. my $args = $slave->args;
  806. $args->{ai_move_giveup}{time} = time unless $args->{ai_move_giveup}{time};
  807. # Stop if the map changed
  808. if ($args->{mapChanged}) {
  809. debug "Slave move - map change detectedn", "ai_move";
  810. $slave->dequeue;
  811. # Stop if we've moved
  812. } elsif ($args->{time_move} != $slave->{time_move}) {
  813. debug "Slave move - movingn", "ai_move";
  814. $slave->dequeue;
  815. # Stop if we've timed out
  816. } elsif (timeOut($args->{ai_move_giveup})) {
  817. debug "Slave move - timeoutn", "ai_move";
  818. $slave->dequeue;
  819. #} elsif (timeOut($slave->{slave_move_retry}, 0.5)) {
  820. } elsif ($slave->{slave_move_retry} + 0.5 <= time) {
  821. # No update yet, send move request again.
  822. # We do this every 0.5 secs
  823. $slave->{slave_move_retry} = time;
  824. $slave->sendMove ($args->{move_to}{x}, $args->{move_to}{y});
  825. }
  826. }
  827. }
  828. sub processClientSuspend {
  829. my $slave = shift;
  830. ##### CLIENT SUSPEND #####
  831. # The clientSuspend AI sequence is used to freeze all other AI activity
  832. # for a certain period of time.
  833. if ($slave->action eq 'clientSuspend' && timeOut($slave->args)) {
  834. debug "Slave AI suspend by clientSuspend dequeuedn";
  835. $slave->dequeue;
  836. } elsif ($slave->action eq "clientSuspend" && $net->clientAlive()) {
  837. # When XKore mode is turned on, clientSuspend will increase it's timeout
  838. # every time the user tries to do something manually.
  839. my $args = $slave->args;
  840. if ($args->{'type'} eq "0089") {
  841. # Player's manually attacking
  842. if ($args->{'args'}[0] == 2) {
  843. if ($chars[$config{'char'}]{'sitting'}) {
  844. $args->{'time'} = time;
  845. }
  846. } elsif ($args->{'args'}[0] == 3) {
  847. $args->{'timeout'} = 6;
  848. } else {
  849. my $ID = $args->{args}[1];
  850. my $monster = $monstersList->getByID($ID);
  851. if (!$args->{'forceGiveup'}{'timeout'}) {
  852. $args->{'forceGiveup'}{'timeout'} = 6;
  853. $args->{'forceGiveup'}{'time'} = time;
  854. }
  855. if ($monster) {
  856. $args->{time} = time;
  857. $args->{dmgFromYou_last} = $monster->{dmgFromYou};
  858. $args->{missedFromYou_last} = $monster->{missedFromYou};
  859. if ($args->{dmgFromYou_last} != $monster->{dmgFromYou}) {
  860. $args->{forceGiveup}{time} = time;
  861. }
  862. } else {
  863. $args->{time} -= $args->{'timeout'};
  864. }
  865. if (timeOut($args->{forceGiveup})) {
  866. $args->{time} -= $args->{timeout};
  867. }
  868. }
  869. } elsif ($args->{'type'} eq "009F") {
  870. # Player's manually picking up an item
  871. if (!$args->{'forceGiveup'}{'timeout'}) {
  872. $args->{'forceGiveup'}{'timeout'} = 4;
  873. $args->{'forceGiveup'}{'time'} = time;
  874. }
  875. if ($items{$args->{'args'}[0]}) {
  876. $args->{'time'} = time;
  877. } else {
  878. $args->{'time'} -= $args->{'timeout'};
  879. }
  880. if (timeOut($args->{'forceGiveup'})) {
  881. $args->{'time'} -= $args->{'timeout'};
  882. }
  883. }
  884. # Client suspended, do not continue with AI
  885. return 1;
  886. }
  887. }
  888. ##### AUTO-ATTACK #####
  889. sub processAutoAttack {
  890. my $slave = shift;
  891. # The auto-attack logic is as follows:
  892. # 1. Generate a list of monsters that we are allowed to attack.
  893. # 2. Pick the "best" monster out of that list, and attack it.
  894. #Benchmark::begin("ai_homunculus_autoAttack") if DEBUG;
  895. if ((($slave->isIdle || $slave->action eq 'route') && (AI::isIdle || AI::is(qw(follow sitAuto take items_gather items_take attack skill_use))))
  896.      # Don't auto-attack monsters while taking loot, and itemsTake/GatherAuto >= 2
  897.   && timeOut($timeout{ai_homunculus_attack_auto})
  898.   && (!$config{$slave->{slave_configPrefix}.'attackAuto_notInTown'} || !$cities_lut{$field{name}.'.rsw'})) {
  899. # If we're in tanking mode, only attack something if the person we're tanking for is on screen.
  900. my $foundTankee;
  901. if ($config{$slave->{slave_configPrefix}.'tankMode'}) {
  902. if ($config{$slave->{slave_configPrefix}.'tankModeTarget'} eq $char->{name}) {
  903. $foundTankee = 1;
  904. } else {
  905. foreach (@playersID) {
  906. next if (!$_);
  907. if ($config{$slave->{slave_configPrefix}.'tankModeTarget'} eq $players{$_}{'name'}) {
  908. $foundTankee = 1;
  909. last;
  910. }
  911. }
  912. }
  913. }
  914. my $attackTarget;
  915. my $priorityAttack;
  916. if (!$config{$slave->{slave_configPrefix}.'tankMode'} || $foundTankee) {
  917. # This variable controls how far monsters must be away from portals and players.
  918. my $portalDist = $config{'attackMinPortalDistance'} || 4;
  919. my $playerDist = $config{'attackMinPlayerDistance'};
  920. $playerDist = 1 if ($playerDist < 1);
  921. my $routeIndex = $slave->findAction("route");
  922. my $attackOnRoute;
  923. if (defined $routeIndex) {
  924. $attackOnRoute = $slave->args($routeIndex)->{attackOnRoute};
  925. } else {
  926. $attackOnRoute = 2;
  927. }
  928. ### Step 1: Generate a list of all monsters that we are allowed to attack. ###
  929. my @aggressives;
  930. my @partyMonsters;
  931. my @cleanMonsters;
  932. # List aggressive monsters
  933. @aggressives = ai_getPlayerAggressives($slave->{ID}) if ($config{$slave->{slave_configPrefix}.'attackAuto'} && $attackOnRoute);
  934. # There are two types of non-aggressive monsters. We generate two lists:
  935. foreach (@monstersID) {
  936. next if (!$_ || !checkMonsterCleanness($_));
  937. my $monster = $monsters{$_};
  938. # Ignore ignored monsters in mon_control.txt
  939. if ((my $control = mon_control($monster->{name},$monster->{nameID}))) {
  940. next if ( ($control->{attack_auto} ne "" && $control->{attack_auto} <= 0)
  941. || ($control->{attack_lvl} ne "" && $control->{attack_lvl} > $char->{lv})
  942. || ($control->{attack_jlvl} ne "" && $control->{attack_jlvl} > $char->{lv_job})
  943. || ($control->{attack_hp}  ne "" && $control->{attack_hp} > $char->{hp})
  944. || ($control->{attack_sp}  ne "" && $control->{attack_sp} > $char->{sp})
  945. );
  946. }
  947. my $pos = calcPosition($monster);
  948. # List monsters that party members are attacking
  949. if ($config{$slave->{slave_configPrefix}.'attackAuto_party'} && $attackOnRoute
  950.  && ((($monster->{dmgFromYou} || $monster->{dmgFromParty}) && $config{$slave->{slave_configPrefix}.'attackAuto_party'} != 2) ||
  951.      $monster->{dmgToYou} || $monster->{dmgToParty} || $monster->{missedYou} || $monster->{missedToParty})
  952.  && timeOut($monster->{homunculus_attack_failed}, $timeout{ai_attack_unfail}{timeout})) {
  953. push @partyMonsters, $_;
  954. next;
  955. }
  956. ### List normal, non-aggressive monsters. ###
  957. # Ignore monsters that
  958. # - Have a status (such as poisoned), because there's a high chance
  959. #   they're being attacked by other players
  960. # - Are inside others' area spells (this includes being trapped).
  961. # - Are moving towards other players.
  962. # - Are behind a wall
  963. next if (( $monster->{statuses} && scalar(keys %{$monster->{statuses}}) )
  964. || objectInsideSpell($monster)
  965. || objectIsMovingTowardsPlayer($monster));
  966. if ($config{$slave->{slave_configPrefix}.'attackCanSnipe'}) {
  967. next if (!checkLineSnipable($slave->{pos_to}, $pos));
  968. } else {
  969. next if (!checkLineWalkable($slave->{pos_to}, $pos));
  970. }
  971. my $safe = 1;
  972. if ($config{$slave->{slave_configPrefix}.'attackAuto_onlyWhenSafe'}) {
  973. foreach (@playersID) {
  974. next if ($_ eq $slave->{ID});
  975. if ($_ && !$char->{party}{users}{$_}) {
  976. $safe = 0;
  977. last;
  978. }
  979. }
  980. }
  981. if ($config{$slave->{slave_configPrefix}.'attackAuto'} >= 2
  982.  && $attackOnRoute >= 2 && !$monster->{dmgFromYou} && $safe
  983.  && !positionNearPlayer($pos, $playerDist) && !positionNearPortal($pos, $portalDist)
  984.  && timeOut($monster->{homunculus_attack_failed}, $timeout{ai_attack_unfail}{timeout})) {
  985. push @cleanMonsters, $_;
  986. }
  987. }
  988. ### Step 2: Pick out the "best" monster ###
  989. my $myPos = calcPosition($slave);
  990. my $highestPri;
  991. # Look for the aggressive monster that has the highest priority
  992. foreach (@aggressives) {
  993. my $monster = $monsters{$_};
  994. my $pos = calcPosition($monster);
  995. # Don't attack monsters near portals
  996. next if (positionNearPortal($pos, $portalDist));
  997. # Don't attack ignored monsters
  998. if ((my $control = mon_control($monster->{name},$monster->{nameID}))) {
  999. next if ( ($control->{attack_auto} == -1)
  1000. || ($control->{attack_lvl} ne "" && $control->{attack_lvl} > $char->{lv})
  1001. || ($control->{attack_jlvl} ne "" && $control->{attack_jlvl} > $char->{lv_job})
  1002. || ($control->{attack_hp}  ne "" && $control->{attack_hp} > $char->{hp})
  1003. || ($control->{attack_sp}  ne "" && $control->{attack_sp} > $char->{sp})
  1004. );
  1005. }
  1006. my $name = lc $monster->{name};
  1007. if (defined($priority{$name}) && $priority{$name} > $highestPri) {
  1008. $highestPri = $priority{$name};
  1009. }
  1010. }
  1011. my $smallestDist;
  1012. if (!defined $highestPri) {
  1013. # If not found, look for the closest aggressive monster (without priority)
  1014. foreach (@aggressives) {
  1015. my $monster = $monsters{$_};
  1016. next if !timeOut($monster->{homunculus_attack_failed}, $timeout{ai_attack_unfail}{timeout});
  1017. my $pos = calcPosition($monster);
  1018. # Don't attack monsters near portals
  1019. next if (positionNearPortal($pos, $portalDist));
  1020. # Don't attack ignored monsters
  1021. if ((my $control = mon_control($monster->{name},$monster->{nameID}))) {
  1022. next if ( ($control->{attack_auto} == -1)
  1023. || ($control->{attack_lvl} ne "" && $control->{attack_lvl} > $char->{lv})
  1024. || ($control->{attack_jlvl} ne "" && $control->{attack_jlvl} > $char->{lv_job})
  1025. || ($control->{attack_hp}  ne "" && $control->{attack_hp} > $char->{hp})
  1026. || ($control->{attack_sp}  ne "" && $control->{attack_sp} > $char->{sp})
  1027. );
  1028. }
  1029. if (!defined($smallestDist) || (my $dist = distance($myPos, $pos)) < $smallestDist) {
  1030. $smallestDist = $dist;
  1031. $attackTarget = $_;
  1032. }
  1033. }
  1034. } else {
  1035. # If found, look for the closest aggressive monster with the highest priority
  1036. foreach (@aggressives) {
  1037. my $monster = $monsters{$_};
  1038. my $pos = calcPosition($monster);
  1039. # Don't attack monsters near portals
  1040. next if (positionNearPortal($pos, $portalDist));
  1041. # Don't attack ignored monsters
  1042. if ((my $control = mon_control($monster->{name},$monster->{nameID}))) {
  1043. next if ( ($control->{attack_auto} == -1)
  1044. || ($control->{attack_lvl} ne "" && $control->{attack_lvl} > $char->{lv})
  1045. || ($control->{attack_jlvl} ne "" && $control->{attack_jlvl} > $char->{lv_job})
  1046. || ($control->{attack_hp}  ne "" && $control->{attack_hp} > $char->{hp})
  1047. || ($control->{attack_sp}  ne "" && $control->{attack_sp} > $char->{sp})
  1048. );
  1049. }
  1050. my $name = lc $monster->{name};
  1051. if ((!defined($smallestDist) || (my $dist = distance($myPos, $pos)) < $smallestDist)
  1052.   && $priority{$name} == $highestPri) {
  1053. $smallestDist = $dist;
  1054. $attackTarget = $_;
  1055. $priorityAttack = 1;
  1056. }
  1057. }
  1058. }
  1059. if (!$attackTarget) {
  1060. undef $smallestDist;
  1061. # There are no aggressive monsters; look for the closest monster that a party member/master is attacking
  1062. foreach (@partyMonsters) {
  1063. my $monster = $monsters{$_};
  1064. my $pos = calcPosition($monster);
  1065. if (!defined($smallestDist) || (my $dist = distance($myPos, $pos)) < $smallestDist) {
  1066. $smallestDist = $dist;
  1067. $attackTarget = $_;
  1068. }
  1069. }
  1070. }
  1071. if (!$attackTarget) {
  1072. # No party monsters either; look for the closest, non-aggressive monster that:
  1073. # 1) nobody's attacking
  1074. # 2) has the highest priority
  1075. undef $smallestDist;
  1076. foreach (@cleanMonsters) {
  1077. my $monster = $monsters{$_};
  1078. next unless $monster;
  1079. my $pos = calcPosition($monster);
  1080. my $dist = distance($myPos, $pos);
  1081. my $name = lc $monster->{name};
  1082. if (!defined($smallestDist) || $priority{$name} > $highestPri
  1083.   || ( $priority{$name} == $highestPri && $dist < $smallestDist )) {
  1084. $smallestDist = $dist;
  1085. $attackTarget = $_;
  1086. $highestPri = $priority{$monster};
  1087. }
  1088. }
  1089. }
  1090. }
  1091. # If an appropriate monster's found, attack it. If not, wait ai_attack_auto secs before searching again.
  1092. if ($attackTarget) {
  1093. $slave->slave_setSuspend(0);
  1094. $slave->slave_attack($attackTarget, $priorityAttack);
  1095. } else {
  1096. $timeout{'ai_homunculus_attack_auto'}{'time'} = time;
  1097. }
  1098. }
  1099. #Benchmark::end("ai_homunculus_autoAttack") if DEBUG;
  1100. }
  1101. sub sendAttack {
  1102. my ($slave, $targetID) = @_;
  1103. $messageSender->sendHomunculusAttack ($slave->{ID}, $targetID);
  1104. }
  1105. sub sendMove {
  1106. my ($slave, $x, $y) = @_;
  1107. $messageSender->sendHomunculusMove ($slave->{ID}, $x, $y);
  1108. }
  1109. sub sendStandBy {
  1110. my ($slave) = @_;
  1111. $messageSender->sendHomunculusStandBy ($slave->{ID});
  1112. }
  1113. 1;