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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - AI
  3. #  Copyright (c) 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 core AI logic.
  18. package AI::CoreLogic;
  19. use strict;
  20. use Time::HiRes qw(time);
  21. use Carp::Assert;
  22. use IO::Socket;
  23. use Text::ParseWords;
  24. use encoding 'utf8';
  25. use Globals;
  26. use Log qw(message warning error debug);
  27. use Network::Send ();
  28. use Settings;
  29. use AI;
  30. use AI::SlaveManager;
  31. use ChatQueue;
  32. use Utils;
  33. use Misc;
  34. use Commands;
  35. use Network;
  36. use FileParsers;
  37. use Translation;
  38. use Field;
  39. use Task::TalkNPC;
  40. use Utils::Exceptions;
  41. # This is the main function from which the rest of the AI
  42. # will be invoked.
  43. sub iterate {
  44. Benchmark::begin("ai_prepare") if DEBUG;
  45. processWipeOldActors();
  46. processGetPlayerInfo();
  47. processMisc();
  48. processPortalRecording();
  49. Benchmark::end("ai_prepare") if DEBUG;
  50. return if (!$AI);
  51. if ($net->clientAlive() && !$sentWelcomeMessage && timeOut($timeout{welcomeText})) {
  52. $messageSender->injectAdminMessage($Settings::welcomeText) if ($config{'verbose'} && !$config{'XKore_silent'});
  53. $sentWelcomeMessage = 1;
  54. }
  55. ##### MANUAL AI STARTS HERE #####
  56. Plugins::callHook('AI_pre/manual');
  57. Benchmark::begin("AI (part 1)") if DEBUG;
  58. return if processClientSuspend();
  59. Benchmark::begin("AI (part 1.1)") if DEBUG;
  60. processLook();
  61. processNPCTalk();
  62. processEquip();
  63. processDrop();
  64. processEscapeUnknownMaps();
  65. Benchmark::end("AI (part 1.1)") if DEBUG;
  66. Benchmark::begin("AI (part 1.2)") if DEBUG;
  67. processDelayedTeleport();
  68. processTask("sitting");
  69. processTask("standing");
  70. AI::Attack::process();
  71. Benchmark::end("AI (part 1.2)") if DEBUG;
  72. Benchmark::begin("AI (part 1.3)") if DEBUG;
  73. processSkillUse();
  74. processAutoCommandUse();
  75. processTask("route", onError => sub {
  76. my ($task, $error) = @_;
  77. if (!($task->isa('Task::MapRoute') && $error->{code} == Task::MapRoute::TOO_MUCH_TIME())
  78.  && !($task->isa('Task::Route') && $error->{code} == Task::Route::TOO_MUCH_TIME())) {
  79. error("$error->{message}n");
  80. }
  81. });
  82. processTake();
  83. processMove();
  84. Benchmark::end("AI (part 1.3)") if DEBUG;
  85. Benchmark::begin("AI (part 1.4)") if DEBUG;
  86. Benchmark::begin("ai_autoItemUse") if DEBUG;
  87. processAutoItemUse();
  88. Benchmark::end("ai_autoItemUse") if DEBUG;
  89. Benchmark::begin("ai_autoSkillUse") if DEBUG;
  90. processAutoSkillUse();
  91. Benchmark::end("ai_autoSkillUse") if DEBUG;
  92. Benchmark::end("AI (part 1.4)") if DEBUG;
  93. Benchmark::end("AI (part 1)") if DEBUG;
  94. Misc::checkValidity("AI part 1");
  95. return if ($AI != 2);
  96. ##### AUTOMATIC AI STARTS HERE #####
  97. Plugins::callHook('AI_pre');
  98. Benchmark::begin("AI (part 2)") if DEBUG;
  99. ChatQueue::processFirst;
  100. processDcOnPlayer();
  101. processDeal();
  102. processDealAuto();
  103. processPartyAuto();
  104. processGuildAutoDeny();
  105. Misc::checkValidity("AI part 1.1");
  106. processAutoBreakTime();
  107. processDead();
  108. processStorageGet();
  109. processCartAdd();
  110. processCartGet();
  111. processAutoMakeArrow();
  112. Benchmark::end("AI (part 2)") if DEBUG;
  113. Misc::checkValidity("AI part 2");
  114. Benchmark::begin("AI (part 3)") if DEBUG;
  115. Benchmark::begin("AI (part 3.1)") if DEBUG;
  116. processAutoStorage();
  117. Misc::checkValidity("AI (autostorage)");
  118. processAutoSell();
  119. Misc::checkValidity("AI (autosell)");
  120. processAutoBuy();
  121. Misc::checkValidity("AI (autobuy)");
  122. processAutoCart();
  123. Misc::checkValidity("AI (autocart)");
  124. Benchmark::end("AI (part 3.1)") if DEBUG;
  125. Benchmark::begin("AI (part 3.2)") if DEBUG;
  126. processLockMap();
  127. processAutoStatsRaise();
  128. processAutoSkillsRaise();
  129. processRandomWalk();
  130. processFollow();
  131. Benchmark::end("AI (part 3.2)") if DEBUG;
  132. Benchmark::begin("AI (part 3.3)") if DEBUG;
  133. processSitAutoIdle();
  134. processSitAuto();
  135. Benchmark::end("AI (part 3.3)") if DEBUG;
  136. Benchmark::end("AI (part 3)") if DEBUG;
  137. Benchmark::begin("AI (part 4)") if DEBUG;
  138. processPartySkillUse();
  139. processMonsterSkillUse();
  140. Misc::checkValidity("AI part 3");
  141. processAutoEquip();
  142. processAutoAttack();
  143. processItemsTake();
  144. processItemsAutoGather();
  145. processItemsGather();
  146. processAutoTeleport();
  147. processAllowedMaps();
  148. processAutoResponse();
  149. processAvoid();
  150. processSendEmotion();
  151. processAutoShopOpen();
  152. processRepairAuto();
  153. Benchmark::end("AI (part 4)") if DEBUG;
  154. ##########
  155. # DEBUG CODE
  156. if (timeOut($ai_v{time}, 2) && $config{'debug'} >= 2) {
  157. my $len = @ai_seq_args;
  158. debug "AI: @ai_seq | $lenn", "ai", 2;
  159. $ai_v{time} = time;
  160. }
  161. $ai_v{'AI_last_finished'} = time;
  162. if ($cmdQueue && timeOut($cmdQueueStartTime,$cmdQueueTime)) {
  163. my $execCommand = '';
  164. if (@cmdQueueList) {
  165. $execCommand = join (";;", @cmdQueueList);
  166. } else {
  167. $execCommand = $cmdQueueList[0];
  168. }
  169. @cmdQueueList = ();
  170. $cmdQueue = 0;
  171. $cmdQueueTime = 0;
  172. debug "Executing queued command: $execCommandn", "ai";
  173. Commands::run($execCommand);
  174. }
  175. Plugins::callHook('AI_post');
  176. }
  177. #############################################################
  178. # Wipe old entries in the %actor_old hashes.
  179. sub processWipeOldActors {
  180. if (timeOut($timeout{ai_wipe_check})) {
  181. my $timeout = $timeout{ai_wipe_old}{timeout};
  182. foreach (keys %players_old) {
  183. if (timeOut($players_old{$_}{'gone_time'}, $timeout)) {
  184. delete $players_old{$_};
  185. binRemove(@playersID_old, $_);
  186. }
  187. }
  188. foreach (keys %monsters_old) {
  189. if (timeOut($monsters_old{$_}{'gone_time'}, $timeout)) {
  190. delete $monsters_old{$_};
  191. binRemove(@monstersID_old, $_);
  192. }
  193. }
  194. foreach (keys %npcs_old) {
  195. delete $npcs_old{$_} if (time - $npcs_old{$_}{'gone_time'} >= $timeout{'ai_wipe_old'}{'timeout'});
  196. }
  197. foreach (keys %items_old) {
  198. delete $items_old{$_} if (time - $items_old{$_}{'gone_time'} >= $timeout{'ai_wipe_old'}{'timeout'});
  199. }
  200. foreach (keys %portals_old) {
  201. if (timeOut($portals_old{$_}{gone_time}, $timeout)) {
  202. delete $portals_old{$_};
  203. binRemove(@portalsID_old, $_);
  204. }
  205. }
  206. # Remove players that are too far away; sometimes they don't get
  207. # removed from the list for some reason
  208. #foreach (keys %players) {
  209. # if (distance($char->{pos_to}, $players{$_}{pos_to}) > 35) {
  210. # $playersList->remove($players{$_});
  211. # last;
  212. # }
  213. #}
  214. $timeout{'ai_wipe_check'}{'time'} = time;
  215. debug "Wiped oldn", "ai", 2;
  216. }
  217. }
  218. sub processGetPlayerInfo {
  219. if (timeOut($timeout{ai_getInfo})) {
  220. processNameRequestQueue(@unknownPlayers, [$playersList, $slavesList]);
  221. processNameRequestQueue(@unknownNPCs, [$npcsList]);
  222. foreach (keys %monsters) {
  223. if ($monsters{$_}{'name'} =~ /Unknown/) {
  224. $messageSender->sendGetPlayerInfo($_);
  225. last;
  226. }
  227. if ($monsters{$_}{'name_given'} =~ /Unknown/) {
  228. $messageSender->sendGetPlayerInfo($_);
  229. last;
  230. }
  231. }
  232. foreach (keys %pets) {
  233. if ($pets{$_}{'name_given'} =~ /Unknown/) {
  234. $messageSender->sendGetPlayerInfo($_);
  235. last;
  236. }
  237. }
  238. $timeout{ai_getInfo}{time} = time;
  239. }
  240. }
  241. sub processMisc {
  242. if (timeOut($timeout{ai_sync})) {
  243. $timeout{ai_sync}{time} = time;
  244. $messageSender->sendSync();
  245. }
  246. if (timeOut($char->{muted}, $char->{mute_period})) {
  247. delete $char->{muted};
  248. delete $char->{mute_period};
  249. }
  250. }
  251. ##### CLIENT SUSPEND #####
  252. # The clientSuspend AI sequence is used to freeze all other AI activity
  253. # for a certain period of time.
  254. sub processClientSuspend {
  255. my $result = 0;
  256. if (AI::action eq 'clientSuspend' && timeOut(AI::args)) {
  257. debug "AI suspend by clientSuspend dequeuedn";
  258. AI::dequeue;
  259. } elsif (AI::action eq "clientSuspend" && $net->clientAlive()) {
  260. # When XKore mode is turned on, clientSuspend will increase it's timeout
  261. # every time the user tries to do something manually.
  262. my $args = AI::args;
  263. if ($args->{'type'} eq "0089") {
  264. # Player's manually attacking
  265. if ($args->{'args'}[0] == 2) {
  266. if ($chars[$config{'char'}]{'sitting'}) {
  267. $args->{'time'} = time;
  268. }
  269. } elsif ($args->{'args'}[0] == 3) {
  270. $args->{'timeout'} = 6;
  271. } else {
  272. my $ID = $args->{args}[1];
  273. my $monster = $monstersList->getByID($ID);
  274. if (!$args->{'forceGiveup'}{'timeout'}) {
  275. $args->{'forceGiveup'}{'timeout'} = 6;
  276. $args->{'forceGiveup'}{'time'} = time;
  277. }
  278. if ($monster) {
  279. $args->{time} = time;
  280. $args->{dmgFromYou_last} = $monster->{dmgFromYou};
  281. $args->{missedFromYou_last} = $monster->{missedFromYou};
  282. if ($args->{dmgFromYou_last} != $monster->{dmgFromYou}) {
  283. $args->{forceGiveup}{time} = time;
  284. }
  285. } else {
  286. $args->{time} -= $args->{'timeout'};
  287. }
  288. if (timeOut($args->{forceGiveup})) {
  289. $args->{time} -= $args->{timeout};
  290. }
  291. }
  292. } elsif ($args->{'type'} eq "009F") {
  293. # Player's manually picking up an item
  294. if (!$args->{'forceGiveup'}{'timeout'}) {
  295. $args->{'forceGiveup'}{'timeout'} = 4;
  296. $args->{'forceGiveup'}{'time'} = time;
  297. }
  298. if ($items{$args->{'args'}[0]}) {
  299. $args->{'time'} = time;
  300. } else {
  301. $args->{'time'} -= $args->{'timeout'};
  302. }
  303. if (timeOut($args->{'forceGiveup'})) {
  304. $args->{'time'} -= $args->{'timeout'};
  305. }
  306. }
  307. # Client suspended, do not continue with AI
  308. $result = 1;
  309. }
  310. return $result;
  311. }
  312. sub processLook {
  313. if (AI::action eq "look" && timeOut($timeout{'ai_look'})) {
  314. $timeout{'ai_look'}{'time'} = time;
  315. $messageSender->sendLook(AI::args->{'look_body'}, AI::args->{'look_head'});
  316. AI::dequeue;
  317. }
  318. }
  319. ##### TALK WITH NPC ######
  320. sub processNPCTalk {
  321. return if (AI::action ne "NPC");
  322. my $args = AI::args;
  323. my $task = $args->{task};
  324. if (!$task) {
  325. $task = new Task::TalkNPC(x => $args->{pos}{x},
  326. y => $args->{pos}{y},
  327. sequence => $args->{sequence});
  328. $task->activate();
  329. $args->{task} = $task;
  330. } else {
  331. $task->iterate();
  332. if ($task->getStatus() == Task::DONE) {
  333. AI::dequeue;
  334. my $error = $task->getError();
  335. if ($error) {
  336. error("$error->{message}n", "ai_npcTalk");
  337. } else {
  338. message TF("Done talking with %s.n", $task->target()->name), "ai_npcTalk";
  339. }
  340. }
  341. }
  342. }
  343. ##### DROPPING #####
  344. # Drop one or more items from inventory.
  345. sub processDrop {
  346. if (AI::action eq "drop" && timeOut(AI::args)) {
  347. my $item = AI::args->{'items'}[0];
  348. my $amount = AI::args->{max};
  349. drop($item, $amount);
  350. shift @{AI::args->{items}};
  351. AI::args->{time} = time;
  352. AI::dequeue if (@{AI::args->{'items'}} <= 0);
  353. }
  354. }
  355. ##### PORTALRECORD #####
  356. # Automatically record new unknown portals
  357. sub processPortalRecording {
  358. return unless $config{portalRecord};
  359. return unless $ai_v{portalTrace_mapChanged} && timeOut($ai_v{portalTrace_mapChanged}, 0.5);
  360. delete $ai_v{portalTrace_mapChanged};
  361. debug "Checking for new portals...n", "portalRecord";
  362. my $first = 1;
  363. my ($foundID, $smallDist, $dist);
  364. if (!$field{name}) {
  365. debug "Field name not known - abortn", "portalRecord";
  366. return;
  367. }
  368. # Find the nearest portal or the only portal on the map
  369. # you came from (source portal)
  370. foreach (@portalsID_old) {
  371. next if (!$_);
  372. $dist = distance($char->{old_pos_to}, $portals_old{$_}{pos});
  373. if ($dist <= 7 && ($first || $dist < $smallDist)) {
  374. $smallDist = $dist;
  375. $foundID = $_;
  376. undef $first;
  377. }
  378. }
  379. my ($sourceMap, $sourceID, %sourcePos, $sourceIndex);
  380. if (defined $foundID) {
  381. $sourceMap = $portals_old{$foundID}{source}{map};
  382. $sourceID = $portals_old{$foundID}{nameID};
  383. %sourcePos = %{$portals_old{$foundID}{pos}};
  384. $sourceIndex = $foundID;
  385. debug "Source portal: $sourceMap ($sourcePos{x}, $sourcePos{y})n", "portalRecord";
  386. } else {
  387. debug "No source portal found.n", "portalRecord";
  388. return;
  389. }
  390. #if (defined portalExists($sourceMap, %sourcePos)) {
  391. # debug "Source portal is already in portals.txt - abortn", "portalRecord";
  392. # return;
  393. #}
  394. # Find the nearest portal or only portal on the
  395. # current map (destination portal)
  396. $first = 1;
  397. undef $foundID;
  398. undef $smallDist;
  399. foreach (@portalsID) {
  400. next if (!$_);
  401. $dist = distance($chars[$config{'char'}]{pos_to}, $portals{$_}{pos});
  402. if ($first || $dist < $smallDist) {
  403. $smallDist = $dist;
  404. $foundID = $_;
  405. undef $first;
  406. }
  407. }
  408. # Sanity checks
  409. if (!defined $foundID) {
  410. debug "No destination portal found.n", "portalRecord";
  411. return;
  412. }
  413. #if (defined portalExists($field{name}, $portals{$foundID}{pos})) {
  414. # debug "Destination portal is already in portals.txtn", "portalRecord";
  415. # last PORTALRECORD;
  416. #}
  417. if (defined portalExists2($sourceMap, %sourcePos, $field{name}, $portals{$foundID}{pos})) {
  418. debug "This portal is already in portals.txtn", "portalRecord";
  419. return;
  420. }
  421. # And finally, record the portal information
  422. my ($destMap, $destID, %destPos);
  423. $destMap = $field{name};
  424. $destID = $portals{$foundID}{nameID};
  425. %destPos = %{$portals{$foundID}{pos}};
  426. debug "Destination portal: $destMap ($destPos{x}, $destPos{y})n", "portalRecord";
  427. $portals{$foundID}{name} = "$field{name} -> $sourceMap";
  428. $portals_old{$sourceIndex}{name} = "$sourceMap -> $field{name}";
  429. my ($ID, $destName);
  430. # Record information about destination portal
  431. if ($config{portalRecord} > 1 &&
  432.     !defined portalExists($field{name}, $portals{$foundID}{pos})) {
  433. $ID = "$field{name} $destPos{x} $destPos{y}";
  434. $portals_lut{$ID}{source}{map} = $field{name};
  435. $portals_lut{$ID}{source}{x} = $destPos{x};
  436. $portals_lut{$ID}{source}{y} = $destPos{y};
  437. $destName = "$sourceMap $sourcePos{x} $sourcePos{y}";
  438. $portals_lut{$ID}{dest}{$destName}{map} = $sourceMap;
  439. $portals_lut{$ID}{dest}{$destName}{x} = $sourcePos{x};
  440. $portals_lut{$ID}{dest}{$destName}{y} = $sourcePos{y};
  441. message TF("Recorded new portal (destination): %s (%s, %s) -> %s (%s, %s)n", $field{name}, $destPos{x}, $destPos{y}, $sourceMap, $sourcePos{x}, $sourcePos{y}), "portalRecord";
  442. updatePortalLUT(Settings::getTableFilename("portals.txt"),
  443. $field{name}, $destPos{x}, $destPos{y},
  444. $sourceMap, $sourcePos{x}, $sourcePos{y});
  445. }
  446. # Record information about the source portal
  447. if (!defined portalExists($sourceMap, %sourcePos)) {
  448. $ID = "$sourceMap $sourcePos{x} $sourcePos{y}";
  449. $portals_lut{$ID}{source}{map} = $sourceMap;
  450. $portals_lut{$ID}{source}{x} = $sourcePos{x};
  451. $portals_lut{$ID}{source}{y} = $sourcePos{y};
  452. $destName = "$field{name} $destPos{x} $destPos{y}";
  453. $portals_lut{$ID}{dest}{$destName}{map} = $field{name};
  454. $portals_lut{$ID}{dest}{$destName}{x} = $destPos{x};
  455. $portals_lut{$ID}{dest}{$destName}{y} = $destPos{y};
  456. message TF("Recorded new portal (source): %s (%s, %s) -> %s (%s, %s)n", $sourceMap, $sourcePos{x}, $sourcePos{y}, $field{name}, $char->{pos}{x}, $char->{pos}{y}), "portalRecord";
  457. updatePortalLUT(Settings::getTableFilename("portals.txt"),
  458. $sourceMap, $sourcePos{x}, $sourcePos{y},
  459. $field{name}, $char->{pos}{x}, $char->{pos}{y});
  460. }
  461. }
  462. ##### ESCAPE UNKNOWN MAPS #####
  463. sub processEscapeUnknownMaps {
  464. # escape from unknown maps. Happens when kore accidentally teleports onto an
  465. # portal. With this, kore should automaticly go into the portal on the other side
  466. # Todo: Make kore do a random walk searching for portal if there's no portal arround.
  467. if (AI::action eq "escape" && $AI == 2) {
  468. my $skip = 0;                   
  469. if (timeOut($timeout{ai_route_escape}) && $timeout{ai_route_escape}{time}){
  470. AI::dequeue;
  471. if ($portalsID[0]) {
  472. message T("Escaping to into nearest portal.n");
  473. main::ai_route($field{name}, $portals{$portalsID[0]}{'pos'}{'x'},
  474. $portals{$portalsID[0]}{'pos'}{'y'}, attackOnRoute => 1, noSitAuto => 1);
  475. $skip = 1;
  476. } elsif ($spellsID[0]){   #get into the first portal you see
  477.      my $spell = $spells{$spellsID[0]};
  478. if (getSpellName($spell->{type}) eq "Warp Portal" ){
  479. message T("Found warp portal escaping into warp portal.n");
  480. main::ai_route($field{name}, $spell->{pos}{x},
  481. $spell->{pos}{y}, attackOnRoute => 1, noSitAuto => 1);
  482. $skip = 1;
  483. }else{
  484. error T("Escape failed no portal found.n");;
  485. }
  486. } else {
  487. error T("Escape failed no portal found.n");
  488. }
  489. }
  490. if ($config{route_escape_randomWalk} && !$skip) { #randomly search for portals...
  491. my ($randX, $randY);
  492. my $i = 500;
  493. my $pos = calcPosition($char);
  494. do {
  495. if ((rand(2)+1)%2) {
  496. $randX = $pos->{x} + int(rand(9) + 1);
  497. } else {
  498. $randX = $pos->{x} - int(rand(9) + 1);
  499. }
  500. if ((rand(2)+1)%2) {
  501. $randY = $pos->{y} + int(rand(9) + 1);
  502. } else {
  503. $randY = $pos->{y} - int(rand(9) + 1);
  504. }
  505. } while (--$i && !$field->isWalkable($randX, $randY));
  506. if (!$i) {
  507. error T("Invalid coordinates specified for randomWalkn Retrying...");
  508. } else {
  509. message TF("Calculating random route to: %s(%s): %s, %sn", $maps_lut{$field{name}.'.rsw'}, $field{name}, $randX, $randY), "route";
  510. ai_route($field{name}, $randX, $randY,
  511.  maxRouteTime => $config{route_randomWalk_maxRouteTime},
  512.  attackOnRoute => 2,
  513.  noMapRoute => ($config{route_randomWalk} == 2 ? 1 : 0) );
  514. }
  515. }
  516. }
  517. }
  518. ##### DELAYED-TELEPORT #####
  519. sub processDelayedTeleport {
  520. if (AI::action eq 'teleport') {
  521. if ($timeout{ai_teleport_delay}{time} && timeOut($timeout{ai_teleport_delay})) {
  522. # We have already successfully used the Teleport skill,
  523. # and the ai_teleport_delay timeout has elapsed
  524. $messageSender->sendTeleport(AI::args->{lv} == 2 ? "$config{saveMap}.gat" : "Random");
  525. AI::dequeue;
  526. } elsif (!$timeout{ai_teleport_delay}{time} && timeOut($timeout{ai_teleport_retry})) {
  527. # We are still trying to use the Teleport skill
  528. $messageSender->sendSkillUse(26, $char->{skills}{AL_TELEPORT}{lv}, $accountID);
  529. $timeout{ai_teleport_retry}{time} = time;
  530. }
  531. }
  532. }
  533. ##### SKILL USE #####
  534. sub processSkillUse {
  535. #FIXME: need to move closer before using skill on player,
  536. #there might be line of sight problem too
  537. #or the player disappers from the area
  538. if (AI::action eq "skill_use" && AI::args->{suspended}) {
  539. AI::args->{giveup}{time} += time - AI::args->{suspended};
  540. AI::args->{minCastTime}{time} += time - AI::args->{suspended};
  541. AI::args->{maxCastTime}{time} += time - AI::args->{suspended};
  542. delete AI::args->{suspended};
  543. }
  544. SKILL_USE: {
  545. last SKILL_USE if (AI::action ne "skill_use");
  546. my $args = AI::args;
  547. if ($args->{monsterID} && $skillsArea{$args->{skillHandle}} == 2) {
  548. delete $args->{monsterID};
  549. }
  550. if (exists $args->{ai_equipAuto_skilluse_giveup} && binFind(@skillsID, $args->{skillHandle}) eq "" && timeOut($args->{ai_equipAuto_skilluse_giveup})) {
  551. warning T("Timeout equiping for skilln");
  552. AI::dequeue;
  553. ${$args->{ret}} = 'equip timeout' if ($args->{ret});
  554. } elsif (Actor::Item::scanConfigAndCheck("$args->{prefix}_equip")) {
  555. #check if item needs to be equipped
  556. Actor::Item::scanConfigAndEquip("$args->{prefix}_equip");
  557. } elsif (timeOut($args->{waitBeforeUse})) {
  558. if (defined $args->{monsterID} && !defined $monsters{$args->{monsterID}}) {
  559. # This skill is supposed to be used for attacking a monster, but that monster has died
  560. AI::dequeue;
  561. ${$args->{ret}} = 'target gone' if ($args->{ret});
  562. } elsif ($char->{sitting}) {
  563. AI::suspend;
  564. stand();
  565. # Use skill if we haven't done so yet
  566. } elsif (!$args->{skill_used}) {
  567. my $handle = $args->{skillHandle};
  568. if (!defined $args->{skillID}) {
  569. my $skill = new Skill(handle => $handle);
  570. $args->{skillID} = $skill->getIDN();
  571. }
  572. my $skillID = $args->{skillID};
  573. if ($handle eq 'AL_TELEPORT') {
  574. ${$args->{ret}} = 'ok' if ($args->{ret});
  575. AI::dequeue;
  576. useTeleport($args->{lv});
  577. last SKILL_USE;
  578. }
  579. $args->{skill_used} = 1;
  580. $args->{giveup}{time} = time;
  581. # Stop attacking, otherwise skill use might fail
  582. my $attackIndex = AI::findAction("attack");
  583. if (defined($attackIndex) && AI::args($attackIndex)->{attackMethod}{type} eq "weapon") {
  584. # 2005-01-24 pmak: Commenting this out since it may
  585. # be causing bot to attack slowly when a buff runs
  586. # out.
  587. #stopAttack();
  588. }
  589. # Give an error if we don't actually possess this skill
  590. my $skill = new Skill(handle => $handle);
  591. if ($char->{skills}{$handle}{lv} <= 0 && (!$char->{permitSkill} || $char->{permitSkill}->getHandle() ne $handle)) {
  592. debug "Attempted to use skill (".$skill->getName().") which you do not have.n";
  593. }
  594. $args->{maxCastTime}{time} = time;
  595. if ($skillsArea{$handle} == 2) {
  596. $messageSender->sendSkillUse($skillID, $args->{lv}, $accountID);
  597. } elsif ($args->{x} ne "") {
  598. $messageSender->sendSkillUseLoc($skillID, $args->{lv}, $args->{x}, $args->{y});
  599. } else {
  600. $messageSender->sendSkillUse($skillID, $args->{lv}, $args->{target});
  601. }
  602. undef $char->{permitSkill};
  603. $args->{skill_use_last} = $char->{skills}{$handle}{time_used};
  604. delete $char->{cast_cancelled};
  605. } elsif (timeOut($args->{minCastTime})) {
  606. if ($args->{skill_use_last} != $char->{skills}{$args->{skillHandle}}{time_used}) {
  607. AI::dequeue;
  608. ${$args->{ret}} = 'ok' if ($args->{ret});
  609. } elsif ($char->{cast_cancelled} > $char->{time_cast}) {
  610. AI::dequeue;
  611. ${$args->{ret}} = 'cancelled' if ($args->{ret});
  612. } elsif (timeOut($char->{time_cast}, $char->{time_cast_wait} + 0.5)
  613.   && ( (timeOut($args->{giveup}) && (!$char->{time_cast} || !$args->{maxCastTime}{timeout}) )
  614.       || ( $args->{maxCastTime}{timeout} && timeOut($args->{maxCastTime})) )
  615. ) {
  616. AI::dequeue;
  617. ${$args->{ret}} = 'timeout' if ($args->{ret});
  618. }
  619. }
  620. }
  621. }
  622. }
  623. sub processTask {
  624. my $ai_name = shift;
  625. if (AI::action eq $ai_name) {
  626. my $task = AI::args;
  627. if ($task->getStatus() == Task::INACTIVE) {
  628. $task->activate();
  629. should($task->getStatus(), Task::RUNNING) if DEBUG;
  630. }
  631. if (DEBUG && $task->getStatus() != Task::RUNNING) {
  632. require Scalar::Util;
  633. require Data::Dumper;
  634. # Make sure redundant information is not included in the error report.
  635. if ($task->isa('Task::MapRoute')) {
  636. delete $task->{ST_subtask}{solution};
  637. } elsif ($task->isa('Task::Route') && $task->{ST_subtask}) {
  638. delete $task->{solution};
  639. }
  640. die "Task '" . $task->getName() . "' (class " . Scalar::Util::blessed($task) . ") has status " .
  641. Task::_getStatusName($task->getStatus()) .
  642. ", but should be RUNNING. Object details:n" .
  643. Data::Dumper::Dumper($task);
  644. }
  645. $task->iterate();
  646. if ($task->getStatus() == Task::DONE) {
  647. # We can't just dequeue the last AI sequence. Perhaps the task
  648. # pushed a new AI sequence on the AI stack just before finishing.
  649. # For example, the Route task does that when it's stuck.
  650. # So, we must dequeue the correct sequence without affecting the
  651. # others.
  652. for (my $i = 0; $i < @AI::ai_seq; $i++) {
  653. if ($AI::ai_seq[$i] eq $ai_name) {
  654. splice(@AI::ai_seq, $i, 1);
  655. splice(@AI::ai_seq_args, $i, 1);
  656. last;
  657. }
  658. }
  659. my %args = @_;
  660. my $error = $task->getError();
  661. if ($error) {
  662. if ($args{onError}) {
  663. $args{onError}->($task, $error);
  664. } else {
  665. error("$error->{message}n");
  666. }
  667. } elsif ($args{onSuccess}) {
  668. $args{onSuccess}->($task);
  669. }
  670. }
  671. }
  672. }
  673. sub processTake {
  674. ##### TAKE #####
  675. if (AI::action eq "take" && AI::args->{suspended}) {
  676. AI::args->{ai_take_giveup}{time} += time - AI::args->{suspended};
  677. delete AI::args->{suspended};
  678. }
  679. if (AI::action eq "take" && ( !$items{AI::args->{ID}} || !%{$items{AI::args->{ID}}} )) {
  680. AI::dequeue;
  681. } elsif (AI::action eq "take" && timeOut(AI::args->{ai_take_giveup})) {
  682. my $item = $items{AI::args->{ID}};
  683. message TF("Failed to take %s (%s) from (%s, %s) to (%s, %s)n", $item->{name}, $item->{binID}, $char->{pos}{x}, $char->{pos}{y}, $item->{pos}{x}, $item->{pos}{y});
  684. $items{AI::args->{ID}}{take_failed}++;
  685. AI::dequeue;
  686. } elsif (AI::action eq "take") {
  687. my $ID = AI::args->{ID};
  688. my $myPos = $char->{pos_to};
  689. my $dist = round(distance($items{$ID}{pos}, $myPos));
  690. my $item = $items{AI::args->{ID}};
  691. debug "Planning to take $item->{name} ($item->{binID}), distance $distn", "drop";
  692. if ($char->{sitting}) {
  693. stand();
  694. } elsif ($dist > 1) {
  695. if (!$config{itemsTakeAuto_new}) {
  696. my (%vec, %pos);
  697. getVector(%vec, $item->{pos}, $myPos);
  698. moveAlongVector(%pos, $myPos, %vec, $dist - 1);
  699. move($pos{x}, $pos{y});
  700. } else {
  701. my $pos = $item->{pos};
  702. message TF("Routing to (%s, %s) to take %s (%s), distance %sn", $pos->{x}, $pos->{y}, $item->{name}, $item->{binID}, $dist);
  703. ai_route($field{name}, $pos->{x}, $pos->{y}, maxRouteDistance => $config{'attackMaxRouteDistance'});
  704. }
  705. } elsif (timeOut($timeout{ai_take})) {
  706. my %vec;
  707. my $direction;
  708. getVector(%vec, $item->{pos}, $myPos);
  709. $direction = int(sprintf("%.0f", (360 - vectorToDegree(%vec)) / 45)) % 8;
  710. $messageSender->sendLook($direction, 0) if ($direction != $char->{look}{body});
  711. $messageSender->sendTake($ID);
  712. $timeout{ai_take}{time} = time;
  713. }
  714. }
  715. }
  716. ##### MOVE #####
  717. sub processMove {
  718. if (AI::action eq "move") {
  719. AI::args->{ai_move_giveup}{time} = time unless AI::args->{ai_move_giveup}{time};
  720. # Wait until we've stand up, if we're sitting
  721. if ($char->{sitting}) {
  722. AI::args->{ai_move_giveup}{time} = 0;
  723. stand();
  724. # Stop if the map changed
  725. } elsif (AI::args->{mapChanged}) {
  726. debug "Move - map change detectedn", "ai_move";
  727. AI::dequeue;
  728. # Stop if we've moved
  729. } elsif (AI::args->{time_move} != $char->{time_move}) {
  730. debug "Move - movingn", "ai_move";
  731. AI::dequeue;
  732. # Stop if we've timed out
  733. } elsif (timeOut(AI::args->{ai_move_giveup})) {
  734. debug "Move - timeoutn", "ai_move";
  735. AI::dequeue;
  736. } elsif (timeOut($AI::Timeouts::move_retry, 0.5)) {
  737. # No update yet, send move request again.
  738. # We do this every 0.5 secs
  739. $AI::Timeouts::move_retry = time;
  740. $messageSender->sendMove(AI::args->{move_to}{x}, AI::args->{move_to}{y});
  741. }
  742. }
  743. }
  744. sub processEquip {
  745. if (AI::action eq "equip") {
  746. # Just wait until everything is equipped or timedOut
  747. if (!$ai_v{temp}{waitForEquip} || timeOut($timeout{ai_equip_giveup})) {
  748. AI::dequeue;
  749. delete $ai_v{temp}{waitForEquip};
  750. }
  751. }
  752. }
  753. sub processDeal {
  754. if (AI::action ne "deal" && %currentDeal) {
  755. AI::queue('deal');
  756. } elsif (AI::action eq "deal") {
  757. if (%currentDeal) {
  758. if (!$currentDeal{you_finalize} && timeOut($timeout{ai_dealAuto}) &&
  759. (!$config{dealAuto_names} || existsInList($config{dealAuto_names}, $currentDeal{name})) &&
  760.     ($config{dealAuto} == 2 ||
  761.  $config{dealAuto} == 3 && $currentDeal{other_finalize})) {
  762. $messageSender->sendDealAddItem(0, $currentDeal{'you_zenny'});
  763. $messageSender->sendDealFinalize();
  764. $timeout{ai_dealAuto}{time} = time;
  765. } elsif ($currentDeal{other_finalize} && $currentDeal{you_finalize} &&timeOut($timeout{ai_dealAuto}) && $config{dealAuto} >= 2 &&
  766. (!$config{dealAuto_names} || existsInList($config{dealAuto_names}, $currentDeal{name}))) {
  767. $messageSender->sendDealTrade();
  768. $timeout{ai_dealAuto}{time} = time;
  769. }
  770. } else {
  771. AI::dequeue();
  772. }
  773. }
  774. }
  775. sub processDealAuto {
  776. # dealAuto 1=refuse 2,3=accept
  777. if ($config{'dealAuto'} && %incomingDeal) {
  778. if ($config{'dealAuto'} == 1 && timeOut($timeout{ai_dealAutoCancel})) {
  779. $messageSender->sendDealCancel();
  780. $timeout{'ai_dealAuto'}{'time'} = time;
  781. } elsif ($config{'dealAuto'} >= 2 &&
  782. (!$config{dealAuto_names} || existsInList($config{dealAuto_names}, $incomingDeal{name})) &&
  783. timeOut($timeout{ai_dealAuto})) {
  784. $messageSender->sendDealAccept();
  785. $timeout{'ai_dealAuto'}{'time'} = time;
  786. }
  787. }
  788. }
  789. sub processPartyAuto {
  790. # partyAuto 1=refuse 2=accept
  791. if ($config{'partyAuto'} && %incomingParty && timeOut($timeout{'ai_partyAuto'})) {
  792. if ($config{partyAuto} == 1) {
  793. message T("Auto-denying party requestn");
  794. } else {
  795. message T("Auto-accepting party requestn");
  796. }
  797. $messageSender->sendPartyJoin($incomingParty{'ID'}, $config{'partyAuto'} - 1);
  798. $timeout{'ai_partyAuto'}{'time'} = time;
  799. undef %incomingParty;
  800. }
  801. }
  802. sub processGuildAutoDeny {
  803. if ($config{'guildAutoDeny'} && %incomingGuild && timeOut($timeout{'ai_guildAutoDeny'})) {
  804. $messageSender->sendGuildJoin($incomingGuild{'ID'}, 0) if ($incomingGuild{'Type'} == 1);
  805. $messageSender->sendGuildAlly($incomingGuild{'ID'}, 0) if ($incomingGuild{'Type'} == 2);
  806. $timeout{'ai_guildAutoDeny'}{'time'} = time;
  807. undef %incomingGuild;
  808. }
  809. }
  810. ##### AUTOBREAKTIME #####
  811. # Break time: automatically disconnect at certain times of the day
  812. sub processAutoBreakTime {
  813. if (timeOut($AI::Timeouts::autoBreakTime, 30)) {
  814. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
  815. my $hormin = sprintf("%02d:%02d", $hour, $min);
  816. my @wdays = ('sun','mon','tue','wed','thu','fri','sat');
  817. debug "autoBreakTime: hormin = $hormin, weekday = $wdays[$wday]n", "autoBreakTime", 2;
  818. for (my $i = 0; exists $config{"autoBreakTime_$i"}; $i++) {
  819. next if (!$config{"autoBreakTime_$i"});
  820. if  ( ($wdays[$wday] eq lc($config{"autoBreakTime_$i"})) || (lc($config{"autoBreakTime_$i"}) eq "all") ) {
  821. if ($config{"autoBreakTime_${i}_startTime"} eq $hormin) {
  822. my ($hr1, $min1) = split /:/, $config{"autoBreakTime_${i}_startTime"};
  823. my ($hr2, $min2) = split /:/, $config{"autoBreakTime_${i}_stopTime"};
  824. my $time1 = $hr1 * 60 * 60 + $min1 * 60;
  825. my $time2 = $hr2 * 60 * 60 + $min2 * 60;
  826. my $diff = ($time2 - $time1) % (60 * 60 * 24);
  827. message TF("nDisconnecting due to break time: %s to %snn", $config{"autoBreakTime_$i"."_startTime"}, $config{"autoBreakTime_$i"."_stopTime"}), "system";
  828. chatLog("k", TF("*** Disconnected due to Break Time: %s to %s ***n", $config{"autoBreakTime_$i"."_startTime"}, $config{"autoBreakTime_$i"."_stopTime"}));
  829. $timeout_ex{'master'}{'timeout'} = $diff;
  830. $timeout_ex{'master'}{'time'} = time;
  831. $KoreStartTime = time;
  832. $net->serverDisconnect();
  833. AI::clear();
  834. undef %ai_v;
  835. $net->setState(Network::NOT_CONNECTED);
  836. undef $conState_tries;
  837. last;
  838. }
  839. }
  840. }
  841. $AI::Timeouts::autoBreakTime = time;
  842. }
  843. }
  844. ##### DEAD #####
  845. sub processDead {
  846. if (AI::action eq "dead" && !$char->{dead}) {
  847. AI::dequeue;
  848. if ($char->{resurrected}) {
  849. # We've been resurrected
  850. $char->{resurrected} = 0;
  851. } else {
  852. # Force storage after death
  853. if ($config{storageAuto} && !$config{storageAuto_notAfterDeath} && ai_storageAutoCheck()) {
  854. message T("Auto-storaging due to deathn");
  855. AI::queue("storageAuto");
  856. }
  857. if ($config{autoMoveOnDeath} && $config{autoMoveOnDeath_x} && $config{autoMoveOnDeath_y} && $config{autoMoveOnDeath_map}) {
  858. message TF("Moving to %s - %d,%dn", $config{autoMoveOnDeath_map}, $config{autoMoveOnDeath_x}, $config{autoMoveOnDeath_y});
  859. AI::queue("sitAuto");
  860. ai_route($config{autoMoveOnDeath_map}, $config{autoMoveOnDeath_x}, $config{autoMoveOnDeath_y});
  861. }
  862. }
  863. } elsif (AI::action ne "dead" && AI::action ne "deal" && $char->{'dead'}) {
  864. AI::clear();
  865. AI::queue("dead");
  866. }
  867. if (AI::action eq "dead" && $config{dcOnDeath} != -1 && time - $char->{dead_time} >= $timeout{ai_dead_respawn}{timeout}) {
  868. $messageSender->sendRespawn();
  869. $char->{'dead_time'} = time;
  870. }
  871. if (AI::action eq "dead" && $config{dcOnDeath} && $config{dcOnDeath} != -1) {
  872. message T("Disconnecting on death!n");
  873. chatLog("k", T("*** You died, auto disconnect! ***n"));
  874. $quit = 1;
  875. }
  876. }
  877. ##### STORAGE GET #####
  878. # Get one or more items from storage.
  879. sub processStorageGet {
  880. if (AI::action eq "storageGet" && timeOut(AI::args)) {
  881. my $item = shift @{AI::args->{items}};
  882. my $amount = AI::args->{max};
  883. if (!$amount || $amount > $item->{amount}) {
  884. $amount = $item->{amount};
  885. }
  886. $messageSender->sendStorageGet($item->{index}, $amount) if $storage{opened};
  887. AI::args->{time} = time;
  888. AI::dequeue if !@{AI::args->{items}};
  889. }
  890. }
  891. #### CART ADD ####
  892. # Put one or more items in cart.
  893. # TODO: check for cart weight & number of items
  894. sub processCartAdd {
  895. if (AI::action eq "cartAdd" && timeOut(AI::args)) {
  896. my $item = AI::args->{items}[0];
  897. my $i = $item->{index};
  898. my $invItem = $char->inventory->get($i);
  899. if ($invItem) {
  900. my $amount = $item->{amount};
  901. if (!$amount || $amount > $invItem->{amount}) {
  902. $amount = $invItem->{amount};
  903. }
  904. $messageSender->sendCartAdd($invItem->{index}, $amount);
  905. }
  906. shift @{AI::args->{items}};
  907. AI::args->{time} = time;
  908. AI::dequeue if (@{AI::args->{items}} <= 0);
  909. }
  910. }
  911. #### CART Get ####
  912. # Get one or more items from cart.
  913. sub processCartGet {
  914. if (AI::action eq "cartGet" && timeOut(AI::args)) {
  915. my $item = AI::args->{items}[0];
  916. my $i = $item->{index};
  917. if ($cart{inventory}[$i]) {
  918. my $amount = $item->{amount};
  919. if (!$amount || $amount > $cart{inventory}[$i]{amount}) {
  920. $amount = $cart{inventory}[$i]{amount};
  921. }
  922. $messageSender->sendCartGet($i, $amount);
  923. }
  924. shift @{AI::args->{items}};
  925. AI::args->{time} = time;
  926. AI::dequeue if (@{AI::args->{items}} <= 0);
  927. }
  928. }
  929. sub processAutoMakeArrow {
  930. ####### AUTO MAKE ARROW #######
  931. if ((AI::isIdle || AI::is(qw/route move autoBuy storageAuto follow sitAuto items_take items_gather/))
  932.  && timeOut($AI::Timeouts::autoArrow, 0.2) && $config{autoMakeArrows} && defined binFind(@skillsID, 'AC_MAKINGARROW') ) {
  933. my $max = @arrowCraftID;
  934. for (my $i = 0; $i < $max; $i++) {
  935. my $item = $char->inventory->get($arrowCraftID[$i]);
  936. next if (!$item);
  937. if ($arrowcraft_items{lc($item->{name})}) {
  938. $messageSender->sendArrowCraft($item->{nameID});
  939. debug "Making itemn", "ai_makeItem";
  940. last;
  941. }
  942. }
  943. $AI::Timeouts::autoArrow = time;
  944. }
  945. if ($config{autoMakeArrows} && $useArrowCraft) {
  946. if (defined binFind(@skillsID, 'AC_MAKINGARROW')) {
  947. ai_skillUse('AC_MAKINGARROW', 1, 0, 0, $accountID);
  948. }
  949. undef $useArrowCraft;
  950. }
  951. }
  952. ##### AUTO STORAGE #####
  953. sub processAutoStorage {
  954. # storageAuto - chobit aska 20030128
  955. if (AI::is("", "route", "sitAuto", "follow")
  956.   && $config{storageAuto} && ($config{storageAuto_npc} ne "" || $config{storageAuto_useChatCommand})
  957.   && !$ai_v{sitAuto_forcedBySitCommand}
  958.   && (($config{'itemsMaxWeight_sellOrStore'} && percent_weight($char) >= $config{'itemsMaxWeight_sellOrStore'})
  959.       || (!$config{'itemsMaxWeight_sellOrStore'} && percent_weight($char) >= $config{'itemsMaxWeight'}))
  960.   && !AI::inQueue("storageAuto") && time > $ai_v{'inventory_time'}) {
  961. # Initiate autostorage when the weight limit has been reached
  962. my $routeIndex = AI::findAction("route");
  963. my $attackOnRoute = 2;
  964. $attackOnRoute = AI::args($routeIndex)->{attackOnRoute} if (defined $routeIndex);
  965. # Only autostorage when we're on an attack route, or not moving
  966. if ($attackOnRoute > 1 && ai_storageAutoCheck()) {
  967. message T("Auto-storaging due to excess weightn");
  968. AI::queue("storageAuto");
  969. }
  970. } elsif (AI::is("", "route", "attack")
  971.   && $config{storageAuto}
  972.   && ($config{storageAuto_npc} ne "" || $config{storageAuto_useChatCommand})
  973.   && !$ai_v{sitAuto_forcedBySitCommand}
  974.   && !AI::inQueue("storageAuto")
  975.   && $char->inventory->size() > 0) {
  976. # Initiate autostorage when we're low on some item, and getAuto is set
  977. my $needitem = "";
  978. my $i;
  979. Misc::checkValidity("AutoStorage part 1");
  980. for ($i = 0; exists $config{"getAuto_$i"}; $i++) {
  981. next unless ($config{"getAuto_$i"});
  982. if ($storage{opened} && findKeyString(%storage, "name", $config{"getAuto_$i"}) eq '') {
  983. foreach (keys %items_lut) {
  984. if ((lc($items_lut{$_}) eq lc($config{"getAuto_$i"})) && ($items_lut{$_} ne $config{"getAuto_$i"})) {
  985. configModify("getAuto_$i", $items_lut{$_});
  986. }
  987. }
  988. }
  989. my $item = $char->inventory->getByName($config{"getAuto_$i"});
  990. if ($config{"getAuto_${i}_minAmount"} ne "" &&
  991.     $config{"getAuto_${i}_maxAmount"} ne "" &&
  992.     !$config{"getAuto_${i}_passive"} &&
  993.     (!$item ||
  994.  ($item->{amount} <= $config{"getAuto_${i}_minAmount"} &&
  995.   $item->{amount} < $config{"getAuto_${i}_maxAmount"})
  996.     )
  997. ) {
  998. if ($storage{opened} && findKeyString(%storage, "name", $config{"getAuto_$i"}) eq '') {
  999. if ($config{"getAuto_${i}_dcOnEmpty"}) {
  1000.   message TF("Disconnecting on empty %s!n", $config{"getAuto_$i"});
  1001. chatLog("k", TF("Disconnecting on empty %s!n", $config{"getAuto_$i"}));
  1002. quit();
  1003. }
  1004. } else {
  1005. if ($storage{openedThisSession} && findKeyString(%storage, "name", $config{"getAuto_$i"}) eq '') {
  1006. } else {
  1007. my $sti = $config{"getAuto_$i"};
  1008. if ($needitem eq "") {
  1009. $needitem = "$sti";
  1010. } else {$needitem = "$needitem, $sti";}
  1011. }
  1012. }
  1013. }
  1014. }
  1015. Misc::checkValidity("AutoStorage part 2");
  1016. my $routeIndex = AI::findAction("route");
  1017. my $attackOnRoute;
  1018. $attackOnRoute = AI::args($routeIndex)->{attackOnRoute} if (defined $routeIndex);
  1019. # Only autostorage when we're on an attack route, or not moving
  1020. if ((!defined($routeIndex) || $attackOnRoute > 1) && $needitem ne "" &&
  1021. $char->inventory->size() > 0 && ai_storageAutoCheck()) {
  1022.   message TF("Auto-storaging due to insufficient %sn", $needitem);
  1023. AI::queue("storageAuto");
  1024. }
  1025. $timeout{'ai_storageAuto'}{'time'} = time;
  1026. }
  1027. if (AI::action eq "storageAuto" && AI::args->{done}) {
  1028. # Autostorage finished; trigger sellAuto unless autostorage was already triggered by it
  1029. my $forcedBySell = AI::args->{forcedBySell};
  1030. my $forcedByBuy = AI::args->{forcedByBuy};
  1031. AI::dequeue;
  1032. if ($forcedByBuy) {
  1033. AI::queue("sellAuto", {forcedByBuy => 1});
  1034. } elsif (!$forcedBySell && ai_sellAutoCheck() && $config{sellAuto}) {
  1035. AI::queue("sellAuto", {forcedByStorage => 1});
  1036. }
  1037. } elsif (AI::action eq "storageAuto" && timeOut($timeout{'ai_storageAuto'})) {
  1038. # Main autostorage block
  1039. my $args = AI::args;
  1040. my $do_route;
  1041. if (!$config{storageAuto_useChatCommand}) {
  1042. # Stop if the specified NPC is invalid
  1043. $args->{npc} = {};
  1044. getNPCInfo($config{'storageAuto_npc'}, $args->{npc});
  1045. if (!defined($args->{npc}{ok})) {
  1046. $args->{done} = 1;
  1047. return;
  1048. }
  1049. # Determine whether we have to move to the NPC
  1050. if ($field{'name'} ne $args->{npc}{map}) {
  1051. $do_route = 1;
  1052. } else {
  1053. my $distance = distance($args->{npc}{pos}, $char->{pos_to});
  1054. if ($distance > $config{'storageAuto_distance'}) {
  1055. $do_route = 1;
  1056. }
  1057. }
  1058. if ($do_route) {
  1059. if ($args->{warpedToSave} && !$args->{mapChanged} && !timeOut($args->{warpStart}, 8)) {
  1060. undef $args->{warpedToSave};
  1061. }
  1062. # If warpToBuyOrSell is set, warp to saveMap if we haven't done so
  1063. if ($config{'saveMap'} ne "" && $config{'saveMap_warpToBuyOrSell'} && !$args->{warpedToSave}
  1064.     && !$cities_lut{$field{'name'}.'.rsw'} && $config{'saveMap'} ne $field{name}) {
  1065. $args->{warpedToSave} = 1;
  1066. # If we still haven't warped after a certain amount of time, fallback to walking
  1067. $args->{warpStart} = time unless $args->{warpStart};
  1068. message T("Teleporting to auto-storagen"), "teleport";
  1069. useTeleport(2);
  1070. $timeout{'ai_storageAuto'}{'time'} = time;
  1071. } else {
  1072. # warpToBuyOrSell is not set, or we've already warped, or timed out. Walk to the NPC
  1073. message TF("Calculating auto-storage route to: %s(%s): %s, %sn", $maps_lut{$args->{npc}{map}.'.rsw'}, $args->{npc}{map}, $args->{npc}{pos}{x}, $args->{npc}{pos}{y}), "route";
  1074. ai_route($args->{npc}{map}, $args->{npc}{pos}{x}, $args->{npc}{pos}{y},
  1075.  attackOnRoute => 1,
  1076.  distFromGoal => $config{'storageAuto_distance'});
  1077. }
  1078. }
  1079. }
  1080. if (!$do_route) {
  1081. # Talk to NPC if we haven't done so
  1082. if (!defined($args->{sentStore})) {
  1083. if ($config{storageAuto_useChatCommand}) {
  1084. $messageSender->sendChat($config{storageAuto_useChatCommand});
  1085. } else {
  1086. if ($config{'storageAuto_npc_type'} eq "" || $config{'storageAuto_npc_type'} eq "1") {
  1087. warning T("Warning storageAuto has changed. Please read News.txtn") if ($config{'storageAuto_npc_type'} eq "");
  1088. $config{'storageAuto_npc_steps'} = "c r1 n";
  1089. debug "Using standard iRO npc storage steps.n", "npc";
  1090. } elsif ($config{'storageAuto_npc_type'} eq "2") {
  1091. $config{'storageAuto_npc_steps'} = "c c r1 n";
  1092. debug "Using iRO comodo (location) npc storage steps.n", "npc";
  1093. } elsif ($config{'storageAuto_npc_type'} eq "3") {
  1094. message T("Using storage steps defined in config.n"), "info";
  1095. } elsif ($config{'storageAuto_npc_type'} ne "" && $config{'storageAuto_npc_type'} ne "1" && $config{'storageAuto_npc_type'} ne "2" && $config{'storageAuto_npc_type'} ne "3") {
  1096. error T("Something is wrong with storageAuto_npc_type in your config.n");
  1097. }
  1098. ai_talkNPC($args->{npc}{pos}{x}, $args->{npc}{pos}{y}, $config{'storageAuto_npc_steps'});
  1099. }
  1100. delete $ai_v{temp}{storage_opened};
  1101. $args->{sentStore} = 1;
  1102. # NPC talk retry
  1103. $AI::Timeouts::storageOpening = time;
  1104. $timeout{'ai_storageAuto'}{'time'} = time;
  1105. return;
  1106. }
  1107. if (!defined $ai_v{temp}{storage_opened}) {
  1108. # NPC talk retry
  1109. if (timeOut($AI::Timeouts::storageOpening, 40)) {
  1110. undef $args->{sentStore};
  1111. debug "Retry talking to autostorage NPC.n", "npc";
  1112. }
  1113. # Storage not yet opened; stop and wait until it's open
  1114. return;
  1115. }
  1116. if (!$args->{getStart}) {
  1117. $args->{done} = 1;
  1118. # if storage is full disconnect if it says so in conf
  1119. if(defined $storage{items_max} && @storageID >= $storage{items_max} && $config{'dcOnStorageFull'}) {
  1120. error T("Disconnecting because storage is full!n");
  1121. chatLog("k", T("Disconnecting because storage is full!n"));
  1122. quit();
  1123. }
  1124. # inventory to storage
  1125. $args->{nextItem} = 0 unless $args->{nextItem};
  1126. for (my $i = $args->{nextItem}; $i < @{$char->inventory->getItems()}; $i++) {
  1127. my $item = $char->inventory->getItems()->[$i];
  1128. next if $item->{equipped};
  1129. next if ($item->{broken} && $item->{type} == 7); # dont store pet egg in use
  1130. my $control = items_control($item->{name});
  1131. debug "AUTOSTORAGE: $item->{name} x $item->{amount} - store = $control->{storage}, keep = $control->{keep}n", "storage";
  1132. if ($control->{storage} && $item->{amount} > $control->{keep}) {
  1133. if ($args->{lastIndex} == $item->{index} &&
  1134.     timeOut($timeout{'ai_storageAuto_giveup'})) {
  1135. return;
  1136. } elsif ($args->{lastIndex} != $item->{index}) {
  1137. $timeout{ai_storageAuto_giveup}{time} = time;
  1138. }
  1139. undef $args->{done};
  1140. $args->{lastIndex} = $item->{index};
  1141. $messageSender->sendStorageAdd($item->{index}, $item->{amount} - $control->{keep});
  1142. $timeout{ai_storageAuto}{time} = time;
  1143. $args->{nextItem} = $i;
  1144. return;
  1145. }
  1146. }
  1147. # cart to storage
  1148. # we don't really need to check if we have a cart
  1149. # if we don't have one it will not find any items to loop through
  1150. $args->{cartNextItem} = 0 unless $args->{cartNextItem};
  1151. for (my $i = $args->{cartNextItem}; $i < @{$cart{inventory}}; $i++) {
  1152. my $item = $cart{inventory}[$i];
  1153. next unless ($item && %{$item});
  1154. my $control = items_control($item->{name});
  1155. debug "AUTOSTORAGE (cart): $item->{name} x $item->{amount} - store = $control->{storage}, keep = $control->{keep}n", "storage";
  1156. # store from cart as well as inventory if the flag is equal to 2
  1157. if ($control->{storage} == 2 && $item->{amount} > $control->{keep}) {
  1158. if ($args->{cartLastIndex} == $item->{index} &&
  1159.     timeOut($timeout{'ai_storageAuto_giveup'})) {
  1160. return;
  1161. } elsif ($args->{cartLastIndex} != $item->{index}) {
  1162. $timeout{ai_storageAuto_giveup}{time} = time;
  1163. }
  1164. undef $args->{done};
  1165. $args->{cartLastIndex} = $item->{index};
  1166. $messageSender->sendStorageAddFromCart($item->{index}, $item->{amount} - $control->{keep});
  1167. $timeout{ai_storageAuto}{time} = time;
  1168. $args->{cartNextItem} = $i + 1;
  1169. return;
  1170. }
  1171. }
  1172. if ($args->{done}) {
  1173. # plugins can hook here and decide to keep storage open longer
  1174. my %hookArgs;
  1175. Plugins::callHook("AI_storage_done", %hookArgs);
  1176. undef $args->{done} if ($hookArgs{return});
  1177. }
  1178. }
  1179. # getAuto begin
  1180. if (!$args->{getStart} && $args->{done} == 1) {
  1181. $args->{getStart} = 1;
  1182. undef $args->{done};
  1183. $args->{index} = 0;
  1184. $args->{retry} = 0;
  1185. return;
  1186. }
  1187. if (defined($args->{getStart}) && $args->{done} != 1) {
  1188. Misc::checkValidity("AutoStorage part 3");
  1189. while (exists $config{"getAuto_$args->{index}"}) {
  1190. if (!$config{"getAuto_$args->{index}"}) {
  1191. $args->{index}++;
  1192. next;
  1193. }
  1194. my %item;
  1195. my $itemName = $config{"getAuto_$args->{index}"};
  1196. if (!$itemName) {
  1197. $args->{index}++;
  1198. next;
  1199. }
  1200. my $invItem = $char->inventory->getByName($itemName);
  1201. $item{name} = $itemName;
  1202. $item{inventory}{index} = $invItem ? $invItem->{invIndex} : undef;
  1203. $item{inventory}{amount} = $invItem ? $invItem->{amount} : 0;
  1204. $item{storage}{index} = findKeyString(%storage, "name", $item{name});
  1205. $item{storage}{amount} = ($item{storage}{index} ne "")? $storage{$item{storage}{index}}{amount} : 0;
  1206. $item{max_amount} = $config{"getAuto_$args->{index}"."_maxAmount"};
  1207. $item{amount_needed} = $item{max_amount} - $item{inventory}{amount};
  1208. # Calculate the amount to get
  1209. if ($item{amount_needed} > 0) {
  1210. $item{amount_get} = ($item{storage}{amount} >= $item{amount_needed})? $item{amount_needed} : $item{storage}{amount};
  1211. }
  1212. # Try at most 3 times to get the item
  1213. if (($item{amount_get} > 0) && ($args->{retry} < 3)) {
  1214. message TF("Attempt to get %s x %s from storage, retry: %sn", $item{amount_get}, $item{name}, $ai_seq_args[0]{retry}), "storage", 1;
  1215. $messageSender->sendStorageGet($item{storage}{index}, $item{amount_get});
  1216. $timeout{ai_storageAuto}{time} = time;
  1217. $args->{retry}++;
  1218. return;
  1219. # we don't inc the index when amount_get is more then 0, this will enable a way of retrying
  1220. # on next loop if it fails this time
  1221. }
  1222. if ($item{storage}{amount} < $item{amount_needed}) {
  1223. warning TF("storage: %s out of stockn", $item{name});
  1224. }
  1225. if (!$config{relogAfterStorage} && $args->{retry} >= 3 && !$args->{warned}) {
  1226. # We tried 3 times to get the item and failed.
  1227. # There is a weird server bug which causes this to happen,
  1228. # but I can't reproduce it. This can be worked around by
  1229. # relogging in after autostorage.
  1230. warning T("Kore tried to get an item from storage 3 times, but failed.n" .
  1231.   "This problem could be caused by a server bug.n" .
  1232.   "To work around this problem, set 'relogAfterStorage' to 1, and relogin.n");
  1233. $args->{warned} = 1;
  1234. }
  1235. # We got the item, or we tried 3 times to get it, but failed.
  1236. # Increment index and process the next item.
  1237. $args->{index}++;
  1238. $args->{retry} = 0;
  1239. }
  1240. Misc::checkValidity("AutoStorage part 4");
  1241. }
  1242. $messageSender->sendStorageClose() unless $config{storageAuto_keepOpen};
  1243. if (percent_weight($char) >= $config{'itemsMaxWeight_sellOrStore'} && ai_storageAutoCheck()) {
  1244. error T("Character is still overweight after storageAuto (storage is full?)n");
  1245. if ($config{dcOnStorageFull}) {
  1246. error T("Disconnecting on storage full!n");
  1247. chatLog("k", T("Disconnecting on storage full!n"));
  1248. quit();
  1249. }
  1250. }
  1251. if ($config{'relogAfterStorage'} && $config{'XKore'} ne "1") {
  1252. writeStorageLog(0);
  1253. relog();
  1254. }
  1255. $args->{done} = 1;
  1256. }
  1257. }
  1258. }
  1259. #####AUTO SELL#####
  1260. sub processAutoSell {
  1261. if ((AI::action eq "" || AI::action eq "route" || AI::action eq "sitAuto" || AI::action eq "follow")
  1262. && (($config{'itemsMaxWeight_sellOrStore'} && percent_weight($char) >= $config{'itemsMaxWeight_sellOrStore'})
  1263. || ($config{'itemsMaxNum_sellOrStore'} && $char->inventory->size() >= $config{'itemsMaxNum_sellOrStore'})
  1264. || (!$config{'itemsMaxWeight_sellOrStore'} && percent_weight($char) >= $config{'itemsMaxWeight'})
  1265. )
  1266. && $config{'sellAuto'}
  1267. && $config{'sellAuto_npc'} ne ""
  1268. && !$ai_v{sitAuto_forcedBySitCommand}
  1269.   ) {
  1270. $ai_v{'temp'}{'ai_route_index'} = AI::findAction("route");
  1271. if ($ai_v{'temp'}{'ai_route_index'} ne "") {
  1272. $ai_v{'temp'}{'ai_route_attackOnRoute'} = AI::args($ai_v{'temp'}{'ai_route_index'})->{'attackOnRoute'};
  1273. }
  1274. if (!($ai_v{'temp'}{'ai_route_index'} ne "" && $ai_v{'temp'}{'ai_route_attackOnRoute'} <= 1) && ai_sellAutoCheck()) {
  1275. AI::queue("sellAuto");
  1276. }
  1277. }
  1278. if (AI::action eq "sellAuto" && AI::args->{'done'}) {
  1279. my $var = AI::args->{'forcedByBuy'};
  1280. my $var2 = AI::args->{'forcedByStorage'};
  1281. message T("Auto-sell sequence completed.n"), "success";
  1282. AI::dequeue;
  1283. if ($var2) {
  1284. AI::queue("buyAuto", {forcedByStorage => 1});
  1285. } elsif (!$var) {
  1286. AI::queue("buyAuto", {forcedBySell => 1});
  1287. }
  1288. } elsif (AI::action eq "sellAuto" && timeOut($timeout{'ai_sellAuto'})) {
  1289. my $args = AI::args;
  1290. $args->{'npc'} = {};
  1291. my $destination = $config{sellAuto_standpoint} || $config{sellAuto_npc};
  1292. getNPCInfo($destination, $args->{'npc'});
  1293. if (!defined($args->{'npc'}{'ok'})) {
  1294. $args->{'done'} = 1;
  1295. return;
  1296. }
  1297. undef $ai_v{'temp'}{'do_route'};
  1298. if ($field{'name'} ne $args->{'npc'}{'map'}) {
  1299. $ai_v{'temp'}{'do_route'} = 1;
  1300. } else {
  1301. $ai_v{'temp'}{'distance'} = distance($args->{'npc'}{'pos'}, $chars[$config{'char'}]{'pos_to'});
  1302. $config{'sellAuto_distance'} = 1 if ($config{sellAuto_standpoint});
  1303. if ($ai_v{'temp'}{'distance'} > $config{'sellAuto_distance'}) {
  1304. $ai_v{'temp'}{'do_route'} = 1;
  1305. }
  1306. }
  1307. if ($ai_v{'temp'}{'do_route'}) {
  1308. if ($args->{'warpedToSave'} && !$args->{'mapChanged'} && !timeOut($args->{warpStart}, 8)) {
  1309. undef $args->{'warpedToSave'};
  1310. }
  1311. if ($config{'saveMap'} ne "" && $config{'saveMap_warpToBuyOrSell'} && !$args->{'warpedToSave'}
  1312. && !$cities_lut{$field{'name'}.'.rsw'} && $config{'saveMap'} ne $field{name}) {
  1313. $args->{'warpedToSave'} = 1;
  1314. # If we still haven't warped after a certain amount of time, fallback to walking
  1315. $args->{warpStart} = time unless $args->{warpStart};
  1316. message T("Teleporting to auto-selln"), "teleport";
  1317. useTeleport(2);
  1318. $timeout{'ai_sellAuto'}{'time'} = time;
  1319. } else {
  1320.   message TF("Calculating auto-sell route to: %s(%s): %s, %sn", $maps_lut{$ai_seq_args[0]{'npc'}{'map'}.'.rsw'}, $ai_seq_args[0]{'npc'}{'map'}, $ai_seq_args[0]{'npc'}{'pos'}{'x'}, $ai_seq_args[0]{'npc'}{'pos'}{'y'}), "route";
  1321. ai_route($args->{'npc'}{'map'}, $args->{'npc'}{'pos'}{'x'}, $args->{'npc'}{'pos'}{'y'},
  1322. attackOnRoute => 1,
  1323. distFromGoal => $config{'sellAuto_distance'},
  1324. noSitAuto => 1);
  1325. }
  1326. } else {
  1327. $args->{'npc'} = {};
  1328. getNPCInfo($config{'sellAuto_npc'}, $args->{'npc'});
  1329. if (!defined($args->{'sentSell'})) {
  1330. $args->{'sentSell'} = 1;
  1331. # load the real npc location just in case we used standpoint
  1332. my $realpos = {};
  1333. getNPCInfo($config{"sellAuto_npc"}, $realpos);
  1334. ai_talkNPC($realpos->{pos}{x}, $realpos->{pos}{y}, 's e');
  1335. return;
  1336. }
  1337. $args->{'done'} = 1;
  1338. # Form list of items to sell
  1339. my @sellItems;
  1340. foreach my $item (@{$char->inventory->getItems()}) {
  1341. next if ($item->{equipped});
  1342. my $control = items_control($item->{name});
  1343. if ($control->{'sell'} && $item->{'amount'} > $control->{keep}) {
  1344. if ($args->{lastIndex} ne "" && $args->{lastIndex} == $item->{index} && timeOut($timeout{'ai_sellAuto_giveup'})) {
  1345. return;
  1346. } elsif ($args->{lastIndex} eq "" || $args->{lastIndex} != $item->{index}) {
  1347. $timeout{ai_sellAuto_giveup}{time} = time;
  1348. }
  1349. undef $args->{done};
  1350. $args->{lastIndex} = $item->{index};
  1351. my %obj;
  1352. $obj{index} = $item->{index};
  1353. $obj{amount} = $item->{amount} - $control->{keep};
  1354. push @sellItems, %obj;
  1355. $timeout{ai_sellAuto}{time} = time;
  1356. }
  1357. }
  1358. $messageSender->sendSellBulk(@sellItems) if (@sellItems);
  1359. if ($args->{done}) {
  1360. # plugins can hook here and decide to keep sell going longer
  1361. my %hookArgs;
  1362. Plugins::callHook("AI_sell_done", %hookArgs);
  1363. undef $args->{done} if ($hookArgs{return});
  1364. }
  1365. }
  1366. }
  1367. }
  1368. #####AUTO BUY#####
  1369. sub processAutoBuy {
  1370. my $needitem;
  1371. if ((AI::action eq "" || AI::action eq "route" || AI::action eq "follow") && timeOut($timeout{'ai_buyAuto'}) && time > $ai_v{'inventory_time'}) {
  1372. undef $ai_v{'temp'}{'found'};
  1373. my $i = 0;
  1374. while (1) {
  1375. last if (!$config{"buyAuto_$i"} || !$config{"buyAuto_$i"."_npc"});
  1376. my $item = $char->inventory->getByName($config{"buyAuto_$i"});
  1377. if ($config{"buyAuto_$i"."_minAmount"} ne "" && $config{"buyAuto_$i"."_maxAmount"} ne ""
  1378. && (checkSelfCondition("buyAuto_$i"))
  1379. && (!$item
  1380.     || ($item->{amount} <= $config{"buyAuto_$i"."_minAmount"}
  1381.         && $item->{amount} < $config{"buyAuto_$i"."_maxAmount"}
  1382.        )
  1383. )
  1384. ) {
  1385. $ai_v{'temp'}{'found'} = 1;
  1386. my $bai = $config{"buyAuto_$i"};
  1387. if ($needitem eq "") {
  1388. $needitem = "$bai";
  1389. } else {$needitem = "$needitem, $bai";}
  1390. }
  1391. $i++;
  1392. }
  1393. $ai_v{'temp'}{'ai_route_index'} = AI::findAction("route");
  1394. if ($ai_v{'temp'}{'ai_route_index'} ne "") {
  1395. $ai_v{'temp'}{'ai_route_attackOnRoute'} = AI::args($ai_v{'temp'}{'ai_route_index'})->{'attackOnRoute'};
  1396. }
  1397. if (!($ai_v{'temp'}{'ai_route_index'} ne "" && AI::findAction("buyAuto")) && $ai_v{'temp'}{'found'}) {
  1398. AI::queue("buyAuto");
  1399. }
  1400. $timeout{'ai_buyAuto'}{'time'} = time;
  1401. }
  1402. if (AI::action eq "buyAuto" && AI::args->{'done'}) {
  1403. # buyAuto finished
  1404. $ai_v{'temp'}{'var'} = AI::args->{'forcedBySell'};
  1405. $ai_v{'temp'}{'var2'} = AI::args->{'forcedByStorage'};
  1406. AI::dequeue;
  1407. if ($ai_v{'temp'}{'var'} && $config{storageAuto}) {
  1408. AI::queue("storageAuto", {forcedBySell => 1});
  1409. } elsif (!$ai_v{'temp'}{'var2'} && $config{storageAuto}) {
  1410. AI::queue("storageAuto", {forcedByBuy => 1});
  1411. }
  1412. } elsif (AI::action eq "buyAuto" && timeOut($timeout{ai_buyAuto_wait}) && timeOut($timeout{ai_buyAuto_wait_buy})) {
  1413. my $args = AI::args;
  1414. undef $args->{index};
  1415. for (my $i = 0; exists $config{"buyAuto_$i"}; $i++) {
  1416. next if (!$config{"buyAuto_$i"} || $config{"buyAuto_${i}_disabled"});
  1417. # did we already fail to do this buyAuto slot? (only fails in this way if the item is nonexistant)
  1418. next if ($args->{index_failed}{$i});
  1419. my $item = $char->inventory->getByName($config{"buyAuto_$i"});
  1420. $args->{invIndex} = $item ? $item->{invIndex} : undef;
  1421. if ($config{"buyAuto_$i"."_maxAmount"} ne "" && (!$item || $item->{amount} < $config{"buyAuto_$i"."_maxAmount"})) {
  1422. next if (($config{"buyAuto_$i"."_price"} && ($char->{zenny} < $config{"buyAuto_$i"."_price"})) || ($config{"buyAuto_$i"."_zeny"} && !inRange($char->{zenny}, $config{"buyAuto_$i"."_zeny"})));
  1423. # get NPC info, use standpoint if provided
  1424. $args->{npc} = {};
  1425. my $destination = $config{"buyAuto_$i"."_standpoint"} || $config{"buyAuto_$i"."_npc"};
  1426. getNPCInfo($destination, $args->{npc});
  1427. # did we succeed to load NPC info from this slot?
  1428. # (doesnt check validity of _npc if we used _standpoint...)
  1429. if ($args->{npc}{ok}) {
  1430. $args->{index} = $i;
  1431. }
  1432. last;
  1433. }
  1434. }
  1435. # failed to load any slots for buyAuto (we're done or they're all invalid)
  1436. # what does the second check do here?
  1437. if ($args->{index} eq "" || ($args->{lastIndex} ne "" && $args->{lastIndex} == $args->{index} && timeOut($timeout{'ai_buyAuto_giveup'}))) {
  1438. $args->{'done'} = 1;
  1439. return;
  1440. }
  1441. undef $ai_v{'temp'}{'do_route'};
  1442. if ($field{'name'} ne $args->{'npc'}{'map'}) {
  1443. $ai_v{'temp'}{'do_route'} = 1;
  1444. } else {
  1445. $ai_v{'temp'}{'distance'} = distance($args->{'npc'}{'pos'}, $chars[$config{'char'}]{'pos_to'});
  1446. $config{"buyAuto_$args->{index}"."_distance"} = 1 if ($config{"buyAuto_$args->{index}"."_standpoint"});
  1447. if ($ai_v{'temp'}{'distance'} > $config{"buyAuto_$args->{index}"."_distance"}) {
  1448. $ai_v{'temp'}{'do_route'} = 1;
  1449. }
  1450. }
  1451. my $msgneeditem;
  1452. if ($ai_v{'temp'}{'do_route'}) {
  1453. if ($args->{warpedToSave} && !$args->{mapChanged} && !timeOut($args->{warpStart}, 8)) {
  1454. undef $args->{warpedToSave};
  1455. }
  1456. if ($config{'saveMap'} ne "" && $config{'saveMap_warpToBuyOrSell'} && !$args->{warpedToSave}
  1457. && !$cities_lut{$field{'name'}.'.rsw'} && $config{'saveMap'} ne $field{name}) {
  1458. $args->{warpedToSave} = 1;
  1459. if ($needitem ne "") {
  1460. $msgneeditem = "Auto-buy: $needitemn";
  1461. }
  1462. # If we still haven't warped after a certain amount of time, fallback to walking
  1463. $args->{warpStart} = time unless $args->{warpStart};
  1464.   message T($msgneeditem."Teleporting to auto-buyn"), "teleport";
  1465. useTeleport(2);
  1466. $timeout{ai_buyAuto_wait}{time} = time;
  1467. } else {
  1468. if ($needitem ne "") {
  1469. $msgneeditem = "Auto-buy: $needitemn";
  1470. }
  1471.   message TF($msgneeditem."Calculating auto-buy route to: %s (%s): %s, %sn", $maps_lut{$args->{npc}{map}.'.rsw'}, $args->{npc}{map}, $args->{npc}{pos}{x}, $args->{npc}{pos}{y}), "route";
  1472. ai_route($args->{npc}{map}, $args->{npc}{pos}{x}, $args->{npc}{pos}{y},
  1473. attackOnRoute => 1,
  1474. distFromGoal => $config{"buyAuto_$args->{index}"."_distance"});
  1475. }
  1476. } else {
  1477. if ($args->{lastIndex} eq "" || $args->{lastIndex} != $args->{index}) {
  1478. # sendBuy automatically terminates the shopping
  1479. # to the seller NPC for each item bought.
  1480. undef $args->{itemID};
  1481. undef $args->{sentBuy};
  1482. $timeout{ai_buyAuto_giveup}{time} = time;
  1483. }
  1484. $args->{lastIndex} = $args->{index};
  1485. # find the item ID if we don't know it yet
  1486. if ($args->{itemID} eq "") {
  1487. if ($args->{invIndex} && $char->inventory->get($args->{invIndex})) {
  1488. # if we have the item in our inventory, we can quickly get the nameID
  1489. $args->{itemID} = $char->inventory->get($args->{invIndex})->{nameID};
  1490. } else {
  1491. # scan the entire items.txt file (this is slow)
  1492. foreach (keys %items_lut) {
  1493. if (lc($items_lut{$_}) eq lc($config{"buyAuto_$args->{index}"})) {
  1494. $args->{itemID} = $_;
  1495. }
  1496. }
  1497. }
  1498. if ($args->{itemID} eq "") {
  1499. # the specified item doesn't even exist
  1500. # don't try this index again
  1501. $args->{index_failed}{$args->{index}} = 1;
  1502. debug "buyAuto index $args->{index} failed, item doesn't existn", "npc";
  1503. return;
  1504. }
  1505. }
  1506. if (!$args->{sentBuy}) {
  1507. $args->{sentBuy} = 1;
  1508. $timeout{ai_buyAuto_wait}{time} = time;
  1509. # load the real npc location just in case we used standpoint
  1510. my $realpos = {};
  1511. getNPCInfo($config{"buyAuto_$args->{index}"."_npc"}, $realpos);
  1512. ai_talkNPC($realpos->{pos}{x}, $realpos->{pos}{y}, 'b e');
  1513. return;
  1514. }
  1515. my $maxbuy;
  1516. if ($config{"buyAuto_$args->{index}"."_price"}) {
  1517. $maxbuy = int($char->{zenny}/$config{"buyAuto_$args->{index}"."_price"});
  1518. } else {$maxbuy = 1000000;}
  1519. if ($args->{invIndex} ne "") {
  1520. # this item is in the inventory already, get what we need
  1521. my $needbuy = $config{"buyAuto_$args->{index}"."_maxAmount"} - $char->inventory->get($args->{invIndex})->{amount};
  1522. if ($maxbuy+1 > $needbuy) {
  1523. $messageSender->sendBuy($args->{itemID}, $needbuy);
  1524. } else {
  1525. $messageSender->sendBuy($args->{itemID}, $maxbuy);
  1526. }
  1527. } else {
  1528. # get the full amount
  1529. my $needbuy = $config{"buyAuto_$args->{index}"."_maxAmount"};
  1530. if ($maxbuy+1 > $needbuy) {
  1531. $messageSender->sendBuy($args->{itemID}, $needbuy);
  1532. } else {
  1533. $messageSender->sendBuy($args->{itemID}, $maxbuy);
  1534. }
  1535. }
  1536. $timeout{ai_buyAuto_wait_buy}{time} = time;
  1537. }
  1538. }
  1539. }
  1540. ##### AUTO-CART ADD/GET ####
  1541. sub processAutoCart {
  1542. if ((AI::isIdle || AI::is(qw/route move buyAuto follow sitAuto items_take items_gather/))) {
  1543. my $timeout = $timeout{ai_cartAutoCheck}{timeout} || 2;
  1544. my $hasCart = $cart{exists};
  1545. if ($char->{statuses}) {
  1546. foreach (keys %{$char->{statuses}}) {
  1547. if ($_ =~ /^Level d Cart$/) {
  1548. $hasCart = 1;
  1549. last;
  1550. }
  1551. }
  1552. }
  1553. if (timeOut($AI::Timeouts::autoCart, $timeout) && $hasCart) {
  1554. my @addItems;
  1555. my @getItems;
  1556. my $cartInventory = $cart{inventory};
  1557. my $max;
  1558. if ($config{cartMaxWeight} && $cart{weight} < $config{cartMaxWeight}) {
  1559. foreach my $item (@{$char->inventory->getItems()}) {
  1560. next if ($item->{broken} && $item->{type} == 7); # dont auto-cart add pet eggs in use
  1561. next if ($item->{equipped});
  1562. my $control = items_control($item->{name});
  1563. if ($control->{cart_add} && $item->{amount} > $control->{keep}) {
  1564. my %obj;
  1565. $obj{index} = $item->{invIndex};
  1566. $obj{amount} = $item->{amount} - $control->{keep};
  1567. push @addItems, %obj;
  1568. debug "Scheduling $item->{name} ($item->{invIndex}) x $obj{amount} for adding to cartn", "ai_autoCart";
  1569. }
  1570. }
  1571. cartAdd(@addItems);
  1572. }
  1573. $max = @{$cartInventory};
  1574. for (my $i = 0; $i < $max; $i++) {
  1575. my $cartItem = $cartInventory->[$i];
  1576. next unless ($cartItem);
  1577. my $control = items_control($cartItem->{name});
  1578. next unless ($control->{cart_get});
  1579. my $item = $char->inventory->getByName($cartItem->{name});
  1580. my $amount;
  1581. if (!$item) {
  1582. $amount = $control->{keep};
  1583. } elsif ($item->{amount} < $control->{keep}) {
  1584. $amount = $control->{keep} - $item->{amount};
  1585. }
  1586. if ($amount > $cartItem->{amount}) {
  1587. $amount = $cartItem->{amount};
  1588. }
  1589. if ($amount > 0) {
  1590. my %obj;
  1591. $obj{index} = $i;
  1592. $obj{amount} = $amount;
  1593. push @getItems, %obj;
  1594. debug "Scheduling $cartItem->{name} ($i) x $obj{amount} for getting from cartn", "ai_autoCart";
  1595. }
  1596. }
  1597. cartGet(@getItems);
  1598. }
  1599. $AI::Timeouts::autoCart = time;
  1600. }
  1601. }
  1602. ##### LOCKMAP #####
  1603. sub processLockMap {
  1604. if (AI::isIdle && $config{'lockMap'}
  1605. && !$ai_v{'sitAuto_forcedBySitCommand'}
  1606. && ($field{'name'} ne $config{'lockMap'}
  1607. || ($config{'lockMap_x'} && ($char->{pos_to}{x} < $config{'lockMap_x'} - $config{'lockMap_randX'} || $char->{pos_to}{x} > $config{'lockMap_x'} + $config{'lockMap_randX'}))
  1608. || ($config{'lockMap_y'} && ($char->{pos_to}{y} < $config{'lockMap_y'} - $config{'lockMap_randY'} || $char->{pos_to}{y} > $config{'lockMap_y'} + $config{'lockMap_randY'}))
  1609. )) {
  1610. unless ($maps_lut{$config{'lockMap'}.'.rsw'}) {
  1611. error TF("Invalid map specified for lockMap - map %s doesn't existn", $config{'lockMap'});
  1612. $config{'lockMap'} = '';
  1613. } else {
  1614. my %args;
  1615. Plugins::callHook("AI/lockMap", %args);
  1616. unless ($args{'return'}) {
  1617. my ($lockX, $lockY, $i);
  1618. eval {
  1619. my $lockField = new Field(name => $config{'lockMap'}, loadDistanceMap => 0);
  1620. $i = 500;
  1621. if ($config{'lockMap_x'} || $config{'lockMap_y'}) {
  1622. do {
  1623. $lockX = int(rand($field{'width'} + 1)) if (!$config{'lockMap_x'} && $config{'lockMap_y'});
  1624. $lockX = int($config{'lockMap_x'}) if ($config{'lockMap_x'});
  1625. $lockX += (int(rand(2*$config{'lockMap_randX'} + 1) - $config{'lockMap_randX'})) if ($config{'lockMap_x'} && $config{'lockMap_randX'});
  1626. $lockY = int(rand($field{'width'} + 1)) if (!$config{'lockMap_y'} && $config{'lockMap_x'});
  1627. $lockY = int($config{'lockMap_y'}) if ($config{'lockMap_y'});
  1628. $lockY += (int(rand(2*$config{'lockMap_randY'} + 1) - $config{'lockMap_randY'})) if ($config{'lockMap_y'} && $config{'lockMap_randY'});
  1629. } while (--$i && !$lockField->isWalkable($lockX, $lockY));
  1630. }
  1631. };
  1632. if (caught('FileNotFoundException') || !$i) {
  1633. error T("Invalid coordinates specified for lockMap, coordinates are unwalkablen");
  1634. $config{'lockMap'} = '';
  1635. } else {
  1636. my $attackOnRoute = 2;
  1637. $attackOnRoute = 1 if ($config{'attackAuto_inLockOnly'} == 1);
  1638. $attackOnRoute = 0 if ($config{'attackAuto_inLockOnly'} > 1);
  1639. if (defined $lockX || defined $lockY) {
  1640. message TF("Calculating lockMap route to: %s(%s): %s, %sn", $maps_lut{$config{'lockMap'}.'.rsw'}, $config{'lockMap'}, $lockX, $lockY), "route";
  1641. } else {
  1642. message TF("Calculating lockMap route to: %s(%s)n", $maps_lut{$config{'lockMap'}.'.rsw'}, $config{'lockMap'}), "route";
  1643. }
  1644. ai_route($config{'lockMap'}, $lockX, $lockY, attackOnRoute => $attackOnRoute);
  1645. }
  1646. }
  1647. }
  1648. }
  1649. }
  1650. ##### AUTO STATS RAISE #####
  1651. sub processAutoStatsRaise {
  1652. if (!$statChanged && $config{statsAddAuto}) {
  1653. # Split list of stats/values
  1654. my @list = split(/ *,+ */, $config{"statsAddAuto_list"});
  1655. my $statAmount;
  1656. my ($num, $st);
  1657. foreach my $item (@list) {
  1658. # Split each stat/value pair
  1659. ($num, $st) = $item =~ /(d+) (str|vit|dex|int|luk|agi)/i;
  1660. $st = lc $st;
  1661. # If stat needs to be raised to match desired amount
  1662. $statAmount = $char->{$st};
  1663. $statAmount += $char->{"${st}_bonus"} if (!$config{statsAddAuto_dontUseBonus});
  1664. if ($statAmount < $num && ($char->{$st} < 99 || $config{statsAdd_over_99})) {
  1665. # If char has enough stat points free to raise stat
  1666. if ($char->{points_free} &&
  1667.     $char->{points_free} >= $char->{"points_$st"}) {
  1668. my $ID;
  1669. if ($st eq "str") {
  1670. $ID = 0x0D;
  1671. } elsif ($st eq "agi") {
  1672. $ID = 0x0E;
  1673. } elsif ($st eq "vit") {
  1674. $ID = 0x0F;
  1675. } elsif ($st eq "int") {
  1676. $ID = 0x10;
  1677. } elsif ($st eq "dex") {
  1678. $ID = 0x11;
  1679. } elsif ($st eq "luk") {
  1680. $ID = 0x12;
  1681. }
  1682. $char->{$st} += 1;
  1683. # Raise stat
  1684. message TF("Auto-adding stat %sn", $st);
  1685. $messageSender->sendAddStatusPoint($ID);
  1686. # Save which stat was raised, so that when we received the
  1687. # "stat changed" packet (00BC?) we can changed $statChanged
  1688. # back to 0 so that kore will start checking again if stats
  1689. # need to be raised.
  1690. # This basically prevents kore from sending packets to the
  1691. # server super-fast, by only allowing another packet to be
  1692. # sent when $statChanged is back to 0 (when the server has
  1693. # replied with a a stat change)
  1694. $statChanged = $st;
  1695. # After we raise a stat, exit loop
  1696. last;
  1697. }
  1698. # If stat needs to be changed but char doesn't have enough stat points to raise it then
  1699. # don't raise it, exit loop
  1700. last;
  1701. }
  1702. }
  1703. }
  1704. }
  1705. ##### AUTO SKILLS RAISE #####
  1706. sub processAutoSkillsRaise {
  1707. if (!$skillChanged && $config{skillsAddAuto}) {
  1708. # Split list of skills and levels
  1709. my @list = split / *,+ */, lc($config{skillsAddAuto_list});
  1710. foreach my $item (@list) {
  1711. # Split each skill/level pair
  1712. my ($sk, undef, $num) = $item =~ /^(.*?)( (d+))?$/;
  1713. $num = 1 if (!defined $num);
  1714. my $skill = new Skill(auto => $sk);
  1715. if (!$skill->getIDN()) {
  1716. error TF("Unknown skill '%s'; disabling skillsAddAuton", $sk);
  1717. $config{skillsAddAuto} = 0;
  1718. last;
  1719. }
  1720. my $handle = $skill->getHandle();
  1721. # If skill needs to be raised to match desired amount && skill points are available
  1722. if ($skill->getIDN() && $char->{points_skill} > 0 && $char->getSkillLevel($skill) < $num) {
  1723. # raise skill
  1724. $messageSender->sendAddSkillPoint($skill->getIDN());
  1725. message TF("Auto-adding skill %sn", $skill->getName());
  1726. # save which skill was raised, so that when we received the
  1727. # "skill changed" packet (010F?) we can changed $skillChanged
  1728. # back to 0 so that kore will start checking again if skills
  1729. # need to be raised.
  1730. # this basically does what $statChanged does for stats
  1731. $skillChanged = $handle;
  1732. # after we raise a skill, exit loop
  1733. last;
  1734. }
  1735. }
  1736. }
  1737. }
  1738. ##### RANDOM WALK #####
  1739. sub processRandomWalk {
  1740. if (AI::isIdle && (AI::SlaveManager::isIdle()) && $config{route_randomWalk} && !$ai_v{sitAuto_forcedBySitCommand}
  1741. && (!$cities_lut{$field{name}.'.rsw'} || $config{route_randomWalk_inTown})
  1742. && length($field{rawMap}) ) {
  1743. my ($randX, $randY);
  1744. my $i = 500;
  1745. do {
  1746. $randX = int(rand($field{width} + 1));
  1747. $randX = int($config{'lockMap_x'} - $config{'lockMap_randX'} + rand(2*$config{'lockMap_randX'}+1)) if ($config{'lockMap_x'} ne '' && $config{'lockMap_randX'} ne '');
  1748. $randY = int(rand($field{height} + 1));
  1749. $randY = int($config{'lockMap_y'} - $config{'lockMap_randY'} + rand(2*$config{'lockMap_randY'}+1)) if ($config{'lockMap_y'} ne '' && $config{'lockMap_randY'} ne '');
  1750. } while (--$i && !$field->isWalkable($randX, $randY));
  1751. if (!$i) {
  1752. error T("Invalid coordinates specified for randomWalk (coordinates are unwalkable); randomWalk disabledn");
  1753. $config{route_randomWalk} = 0;
  1754. } else {
  1755. message TF("Calculating random route to: %s(%s): %s, %sn", $maps_lut{$field{name}.'.rsw'}, $field{name}, $randX, $randY), "route";
  1756. ai_route($field{name}, $randX, $randY,
  1757. maxRouteTime => $config{route_randomWalk_maxRouteTime},
  1758. attackOnRoute => 2,
  1759. noMapRoute => ($config{route_randomWalk} == 2 ? 1 : 0) );
  1760. }
  1761. }
  1762. }
  1763. ##### FOLLOW #####
  1764. sub processFollow {
  1765. # FIXME: Should use actors list to determine who and where is the master
  1766. # TODO: follow should be a 'mode' rather then a sequence, hence all
  1767. # var/flag about follow should be moved to %ai_v
  1768. return if (!$config{follow});
  1769. my $followIndex;
  1770. if (($followIndex = AI::findAction("follow")) eq "") {
  1771. # ai_follow will determine if the Target is 'follow-able'
  1772. return if (!ai_follow($config{followTarget}));
  1773. $followIndex = AI::findAction("follow");
  1774. }
  1775. my $args = AI::args($followIndex);
  1776. # if we are not following now but master is in the screen...
  1777. if (!defined $args->{'ID'}) {
  1778. foreach my Actor::Player $player (@{$playersList->getItems()}) {
  1779. if (($player->name eq $config{followTarget}) && !$player->{'dead'}) {
  1780. $args->{'ID'} = $player->{ID};
  1781. $args->{'following'} = 1;
  1782. $args->{'name'} = $player->name;
  1783.   message TF("Found my master - %sn", $player->name), "follow";
  1784. last;
  1785. }
  1786. }
  1787. } elsif (!$args->{'following'} && $players{$args->{'ID'}} && %{$players{$args->{'ID'}}} && !${$players{$args->{'ID'}}}{'dead'} && ($players{$args->{'ID'}}->name eq $config{followTarget})) {
  1788. $args->{'following'} = 1;
  1789. delete $args->{'ai_follow_lost'};
  1790.   message TF("Found my master!n"), "follow"
  1791. }
  1792. # if we are not doing anything else now...
  1793. if (AI::action eq "follow") {
  1794. if (AI::args->{'suspended'}) {
  1795. if (AI::args->{'ai_follow_lost'}) {
  1796. AI::args->{'ai_follow_lost_end'}{'time'} += time - AI::args->{'suspended'};
  1797. }
  1798. delete AI::args->{'suspended'};
  1799. }
  1800. # if we are not doing anything else now...
  1801. if (!$args->{ai_follow_lost}) {
  1802. my $ID = $args->{ID};
  1803. my $player = $players{$ID};
  1804. if ($args->{following} && $player->{pos_to}) {
  1805. my $dist = distance($char->{pos_to}, $player->{pos_to});
  1806. if ($dist > $config{followDistanceMax} && timeOut($args->{move_timeout}, 0.25)) {
  1807. $args->{move_timeout} = time;
  1808. if ( $dist > 15 || ($config{followCheckLOS} && !checkLineWalkable($char->{pos_to}, $player->{pos_to})) ) {
  1809. ai_route($field{name}, $player->{pos_to}{x}, $player->{pos_to}{y},
  1810. attackOnRoute => 1,
  1811. distFromGoal => $config{followDistanceMin});
  1812. } else {
  1813. my (%vec, %pos);
  1814. stand() if ($char->{sitting});
  1815. getVector(%vec, $player->{pos_to}, $char->{pos_to});
  1816. moveAlongVector(%pos, $char->{pos_to}, %vec, $dist - $config{followDistanceMin});
  1817. $timeout{ai_sit_idle}{time} = time;
  1818. $messageSender->sendMove($pos{x}, $pos{y});
  1819. }
  1820. }
  1821. }
  1822. if ($args->{following} && $player && %{$player}) {
  1823. if ($config{'followSitAuto'} && $players{$args->{'ID'}}{'sitting'} == 1 && $chars[$config{'char'}]{'sitting'} == 0) {
  1824. sit();
  1825. }
  1826. my $dx = $args->{'last_pos_to'}{'x'} - $players{$args->{'ID'}}{'pos_to'}{'x'};
  1827. my $dy = $args->{'last_pos_to'}{'y'} - $players{$args->{'ID'}}{'pos_to'}{'y'};
  1828. $args->{'last_pos_to'}{'x'} = $players{$args->{'ID'}}{'pos_to'}{'x'};
  1829. $args->{'last_pos_to'}{'y'} = $players{$args->{'ID'}}{'pos_to'}{'y'};
  1830. if ($dx != 0 || $dy != 0) {
  1831. lookAtPosition($players{$args->{'ID'}}{'pos_to'}) if ($config{'followFaceDirection'});
  1832. }
  1833. }
  1834. }
  1835. }
  1836. if (AI::action eq "follow" && $args->{'following'} && ( ( $players{$args->{'ID'}} && $players{$args->{'ID'}}{'dead'} ) || ( ( !$players{$args->{'ID'}} || !%{$players{$args->{'ID'}}} ) && $players_old{$args->{'ID'}}{'dead'}))) {
  1837.   message T("Master died. I'll wait here.n"), "party";
  1838. delete $args->{'following'};
  1839. } elsif ($args->{'following'} && ( !$players{$args->{'ID'}} || !%{$players{$args->{'ID'}}} )) {
  1840.   message T("I lost my mastern"), "follow";
  1841. if ($config{'followBot'}) {
  1842.   message T("Trying to get him backn"), "follow";
  1843. sendMessage($messageSender, "pm", "move $chars[$config{'char'}]{'pos_to'}{'x'} $chars[$config{'char'}]{'pos_to'}{'y'}", $config{followTarget});
  1844. }
  1845. delete $args->{'following'};
  1846. if ($players_old{$args->{'ID'}}{'disconnected'}) {
  1847.   message T("My master disconnectedn"), "follow";
  1848. } elsif ($players_old{$args->{'ID'}}{'teleported'}) {
  1849. delete $args->{'ai_follow_lost_warped'};
  1850. delete $ai_v{'temp'}{'warp_pos'};
  1851. # Check to see if the player went through a warp portal and follow him through it.
  1852. my $pos = calcPosition($players_old{$args->{'ID'}});
  1853. my $oldPos = $players_old{$args->{'ID'}}->{pos};
  1854. my (@blocks, $found);
  1855. my %vec;
  1856. debug "Last time i saw, master was moving from ($oldPos->{x}, $oldPos->{y}) to ($pos->{x}, $pos->{y})n", "follow";
  1857. # We must check the ground about 9x9 area of where we last saw our master. That's the only way
  1858. # to ensure he walked through a warp portal. The range is because of lag in some situations.
  1859. @blocks = calcRectArea2($pos->{x}, $pos->{y}, 4, 0);
  1860. foreach (@blocks) {
  1861. next unless (whenGroundStatus($_, "Warp Portal"));
  1862. # We must certify that our master was walking towards that portal.
  1863. getVector(%vec, $_, $oldPos);
  1864. next unless (checkMovementDirection($oldPos, %vec, $_, 15));
  1865. $found = $_;
  1866. last;
  1867. }
  1868. if ($found) {
  1869. %{$ai_v{'temp'}{'warp_pos'}} = %{$found};
  1870. $args->{'ai_follow_lost_warped'} = 1;
  1871. $args->{'ai_follow_lost'} = 1;
  1872. $args->{'ai_follow_lost_end'}{'timeout'} = $timeout{'ai_follow_lost_end'}{'timeout'};
  1873. $args->{'ai_follow_lost_end'}{'time'} = time;
  1874. $args->{'ai_follow_lost_vec'} = {};
  1875. getVector($args->{'ai_follow_lost_vec'}, $players_old{$args->{'ID'}}{'pos_to'}, $chars[$config{'char'}]{'pos_to'});
  1876. } else {
  1877.   message T("My master teleportedn"), "follow", 1;
  1878. }
  1879. } elsif ($players_old{$args->{'ID'}}{'disappeared'}) {
  1880.   message T("Trying to find lost mastern"), "follow", 1;
  1881. delete $args->{'ai_follow_lost_char_last_pos'};
  1882. delete $args->{'follow_lost_portal_tried'};
  1883. $args->{'ai_follow_lost'} = 1;
  1884. $args->{'ai_follow_lost_end'}{'timeout'} = $timeout{'ai_follow_lost_end'}{'timeout'};
  1885. $args->{'ai_follow_lost_end'}{'time'} = time;
  1886. $args->{'ai_follow_lost_vec'} = {};
  1887. getVector($args->{'ai_follow_lost_vec'}, $players_old{$args->{'ID'}}{'pos_to'}, $chars[$config{'char'}]{'pos_to'});
  1888. #check if player went through portal
  1889. my $first = 1;
  1890. my $foundID;
  1891. my $smallDist;
  1892. foreach (@portalsID) {
  1893. next if (!defined $_);
  1894. $ai_v{'temp'}{'dist'} = distance($players_old{$args->{'ID'}}{'pos_to'}, $portals{$_}{'pos'});
  1895. if ($ai_v{'temp'}{'dist'} <= 7 && ($first || $ai_v{'temp'}{'dist'} < $smallDist)) {
  1896. $smallDist = $ai_v{'temp'}{'dist'};
  1897. $foundID = $_;
  1898. undef $first;
  1899. }
  1900. }
  1901. $args->{'follow_lost_portalID'} = $foundID;
  1902. } else {
  1903.   message T("Don't know what happened to Mastern"), "follow", 1;
  1904. }
  1905. }
  1906. ##### FOLLOW-LOST #####
  1907. if (AI::action eq "follow" && $args->{'ai_follow_lost'}) {
  1908. if ($args->{'ai_follow_lost_char_last_pos'}{'x'} == $chars[$config{'char'}]{'pos_to'}{'x'} && $args->{'ai_follow_lost_char_last_pos'}{'y'} == $chars[$config{'char'}]{'pos_to'}{'y'}) {
  1909. $args->{'lost_stuck'}++;
  1910. } else {
  1911. delete $args->{'lost_stuck'};
  1912. }
  1913. %{AI::args->{'ai_follow_lost_char_last_pos'}} = %{$chars[$config{'char'}]{'pos_to'}};
  1914. if (timeOut($args->{'ai_follow_lost_end'})) {
  1915. delete $args->{'ai_follow_lost'};
  1916.   message T("Couldn't find master, giving upn"), "follow";
  1917. } elsif ($players_old{$args->{'ID'}}{'disconnected'}) {
  1918. delete AI::args->{'ai_follow_lost'};
  1919.   message T("My master disconnectedn"), "follow";
  1920. } elsif ($args->{'ai_follow_lost_warped'} && $ai_v{'temp'}{'warp_pos'} && %{$ai_v{'temp'}{'warp_pos'}}) {
  1921. my $pos = $ai_v{'temp'}{'warp_pos'};
  1922. if ($config{followCheckLOS} && !checkLineWalkable($char->{pos_to}, $pos)) {
  1923. ai_route($field{name}, $pos->{x}, $pos->{y},
  1924. attackOnRoute => 0); #distFromGoal => 0);
  1925. } else {
  1926. my (%vec, %pos_to);
  1927. my $dist = distance($char->{pos_to}, $pos);
  1928. stand() if ($char->{sitting});
  1929. getVector(%vec, $pos, $char->{pos_to});
  1930. moveAlongVector(%pos_to, $char->{pos_to}, %vec, $dist);
  1931. $timeout{ai_sit_idle}{time} = time;
  1932. move($pos_to{x}, $pos_to{y});
  1933. $pos->{x} = int $pos_to{x};
  1934. $pos->{y} = int $pos_to{y};
  1935. }
  1936. delete $args->{'ai_follow_lost_warped'};
  1937. delete $ai_v{'temp'}{'warp_pos'};
  1938.   message TF("My master warped at (%s, %s) - moving to warp pointn", $pos->{x}, $pos->{y}), "follow";
  1939. } elsif ($players_old{$args->{'ID'}}{'teleported'}) {
  1940. delete AI::args->{'ai_follow_lost'};
  1941.   message T("My master teleportedn"), "follow";
  1942. } elsif ($args->{'lost_stuck'}) {
  1943. if ($args->{'follow_lost_portalID'} eq "") {
  1944. moveAlongVector($ai_v{'temp'}{'pos'}, $chars[$config{'char'}]{'pos_to'}, $args->{'ai_follow_lost_vec'}, $config{'followLostStep'} / ($args->{'lost_stuck'} + 1));
  1945. move($ai_v{'temp'}{'pos'}{'x'}, $ai_v{'temp'}{'pos'}{'y'});
  1946. }
  1947. } else {
  1948. my $portalID = $args->{follow_lost_portalID};
  1949. if ($args->{'follow_lost_portalID'} ne "" && $portalID) {
  1950. if ($portals{$portalID} && !$args->{'follow_lost_portal_tried'}) {
  1951. $args->{'follow_lost_portal_tried'} = 1;
  1952. %{$ai_v{'temp'}{'pos'}} = %{$portals{$args->{'follow_lost_portalID'}}{'pos'}};
  1953. ai_route($field{'name'}, $ai_v{'temp'}{'pos'}{'x'}, $ai_v{'temp'}{'pos'}{'y'},
  1954. attackOnRoute => 1);
  1955. }
  1956. } else {
  1957. moveAlongVector($ai_v{'temp'}{'pos'}, $chars[$config{'char'}]{'pos_to'}, $args->{'ai_follow_lost_vec'}, $config{'followLostStep'});
  1958. move($ai_v{'temp'}{'pos'}{'x'}, $ai_v{'temp'}{'pos'}{'y'});
  1959. }
  1960. }
  1961. }
  1962. # Use party information to find master
  1963. if (!exists $args->{following} && !exists $args->{ai_follow_lost}) {
  1964. ai_partyfollow();
  1965. }
  1966. }
  1967. ##### SITAUTO-IDLE #####
  1968. sub processSitAutoIdle {
  1969. if ($config{sitAuto_idle}) {
  1970. if (!AI::isIdle && AI::action ne "follow") {
  1971. $timeout{ai_sit_idle}{time} = time;
  1972. }
  1973. if ( !$char->{sitting} && timeOut($timeout{ai_sit_idle})
  1974.  && (!$config{shopAuto_open} || timeOut($timeout{ai_shop})) ) {
  1975. sit();
  1976. }
  1977. }
  1978. }
  1979. ##### SIT AUTO #####
  1980. sub processSitAuto {
  1981. my $weight = percent_weight($char);
  1982. my $action = AI::action;
  1983. my $lower_ok = (percent_hp($char) >= $config{'sitAuto_hp_lower'} && percent_sp($char) >= $config{'sitAuto_sp_lower'});
  1984. my $upper_ok = (percent_hp($char) >= $config{'sitAuto_hp_upper'} && percent_sp($char) >= $config{'sitAuto_sp_upper'});
  1985. if ($ai_v{'sitAuto_forceStop'} && $lower_ok) {
  1986. $ai_v{'sitAuto_forceStop'} = 0;
  1987. }
  1988. # Sit if we're not already sitting
  1989. if ($action eq "sitAuto" && !$char->{sitting} && $char->{skills}{NV_BASIC}{lv} >= 3 &&
  1990.     !ai_getAggressives() && ($weight < 50 || $config{'sitAuto_over_50'})) {
  1991. debug "sitAuto - sitn", "sitAuto";
  1992. sit();
  1993. } elsif ($action eq "sitAuto" && $ai_v{'sitAuto_forceStop'}) {
  1994. AI::dequeue;
  1995. stand() if (!AI::isIdle && !AI::is(qw(follow sitting clientSuspend)) && !$config{'sitAuto_idle'} && $char->{sitting});
  1996. # Stand if our HP is high enough
  1997. } elsif ($action eq "sitAuto" && $upper_ok) {
  1998. if ($timeout{ai_safe_stand_up}{timeout} && !isSafe()) {
  1999. if (!$timeout{ai_safe_stand_up}{passed}
  2000.   || ($timeout{ai_safe_stand_up}{passed} && timeOut($timeout{ai_safe_stand_up}{time}, $timeout{ai_safe_stand_up}{timeout} + 1))
  2001. ) {
  2002. $timeout{ai_safe_stand_up}{time} = time;
  2003. $timeout{ai_safe_stand_up}{passed} = 1;
  2004. return;
  2005. } elsif ($timeout{ai_safe_stand_up}{passed} && !timeOut($timeout{ai_safe_stand_up})) {
  2006. return;
  2007. } elsif ($timeout{ai_safe_stand_up}{passed} && timeOut($timeout{ai_safe_stand_up})) {
  2008. $timeout{ai_safe_stand_up}{time} = 0;
  2009. $timeout{ai_safe_stand_up}{passed} = 0;
  2010. }
  2011. }
  2012. AI::dequeue;
  2013. debug "HP is now > $config{sitAuto_hp_upper}n", "sitAuto";
  2014. stand() if (!AI::isIdle && !AI::is(qw(follow sitting clientSuspend)) && !$config{'sitAuto_idle'} && $char->{sitting});
  2015. } elsif (!$ai_v{'sitAuto_forceStop'} && ($weight < 50 || $config{'sitAuto_over_50'}) && AI::action ne "sitAuto") {
  2016. if ($action eq "" || $action eq "follow"
  2017. || ($action eq "route" && !AI::args->{noSitAuto})
  2018. || ($action eq "mapRoute" && !AI::args->{noSitAuto})
  2019. ) {
  2020. if (!AI::inQueue("attack") && !ai_getAggressives()
  2021. && !AI::inQueue("sitAuto")  # do not queue sitAuto if there is an existing sitAuto sequence
  2022. && (percent_hp($char) < $config{'sitAuto_hp_lower'} || percent_sp($char) < $config{'sitAuto_sp_lower'})) {
  2023. AI::queue("sitAuto");
  2024. debug "Auto-sittingn", "sitAuto";
  2025. }
  2026. }
  2027. }
  2028. }
  2029. ##### AUTO-COMMAND USE #####
  2030. sub processAutoCommandUse {
  2031. if (AI::isIdle || AI::is(qw(route mapRoute follow sitAuto take items_gather items_take attack skill_use))) {
  2032. my $i = 0;
  2033. while (exists $config{"doCommand_$i"}) {
  2034. if ($config{"doCommand_$i"} && checkSelfCondition("doCommand_$i")) {
  2035. Commands::run($config{"doCommand_$i"});
  2036. $ai_v{"doCommand_$i"."_time"} = time;
  2037. my $cmd_prefix = $config{"doCommand_$i"};
  2038. debug qq~Auto-Command use: $cmd_prefixn~, "ai";
  2039. last;
  2040. }
  2041. $i++;
  2042. }
  2043. }
  2044. }
  2045. ##### AUTO-ITEM USE #####
  2046. sub processAutoItemUse {
  2047. if ((AI::isIdle || AI::is(qw(route mapRoute follow sitAuto take items_gather items_take attack skill_use)))
  2048.   && timeOut($timeout{ai_item_use_auto})) {
  2049. my $i = 0;
  2050. while (exists $config{"useSelf_item_$i"}) {
  2051. if ($config{"useSelf_item_${i}_timeout"} eq "") {$config{"useSelf_item_${i}_timeout"} = 0;}
  2052. if ($config{"useSelf_item_$i"} && checkSelfCondition("useSelf_item_$i")) {
  2053. my $item = $char->inventory->getByNameList($config{"useSelf_item_$i"});
  2054. if ($item) {
  2055. $messageSender->sendItemUse($item->{index}, $accountID);
  2056. $ai_v{"useSelf_item_$i"."_time"} = time;
  2057. $timeout{ai_item_use_auto}{time} = time;
  2058. debug qq~Auto-item use: $item->{name}n~, "ai";
  2059. last;
  2060. } elsif ($config{"useSelf_item_${i}_dcOnEmpty"} && $char->inventory->size() > 0) {
  2061. error TF("Disconnecting on empty %s!n", $config{"useSelf_item_$i"});
  2062. chatLog("k", TF("Disconnecting on empty %s!n", $config{"useSelf_item_$i"}));
  2063. quit();
  2064. }
  2065. }
  2066. $i++;
  2067. }
  2068. }
  2069. }
  2070. ##### AUTO-SKILL USE #####
  2071. sub processAutoSkillUse {
  2072. if (AI::isIdle || AI::is(qw(route mapRoute follow sitAuto take items_gather items_take attack))
  2073. || (AI::action eq "skill_use" && AI::args->{tag} eq "attackSkill")) {
  2074. my %self_skill;
  2075. for (my $i = 0; exists $config{"useSelf_skill_$i"}; $i++) {
  2076. if ($config{"useSelf_skill_$i"} && checkSelfCondition("useSelf_skill_$i")) {
  2077. $ai_v{"useSelf_skill_$i"."_time"} = time;
  2078. $self_skill{skillObject} = Skill->new(name => lc($config{"useSelf_skill_$i"}));
  2079. $self_skill{ID} = $self_skill{skillObject}->getHandle();
  2080. $self_skill{owner} = $self_skill{skillObject}->getOwner();
  2081. unless ($self_skill{ID}) {
  2082. error "Unknown skill name '".$config{"useSelf_skill_$i"}."' in useSelf_skill_$in";
  2083. configModify("useSelf_skill_${i}_disabled", 1);
  2084. next;
  2085. }
  2086. $self_skill{lvl} = $config{"useSelf_skill_$i"."_lvl"};
  2087. $self_skill{maxCastTime} = $config{"useSelf_skill_$i"."_maxCastTime"};
  2088. $self_skill{minCastTime} = $config{"useSelf_skill_$i"."_minCastTime"};
  2089. $self_skill{prefix} = "useSelf_skill_$i";
  2090. last;
  2091. }
  2092. }
  2093. if ($config{useSelf_skill_smartHeal} && $self_skill{ID} eq "AL_HEAL") {
  2094. my $smartHeal_lv = 1;
  2095. my $hp_diff = $char->{hp_max} - $char->{hp};
  2096. my $meditatioBonus = 1;
  2097. $meditatioBonus = 1 + int(($char->{skills}{HP_MEDITATIO}{lv} * 2) / 100) if ($char->{skills}{HP_MEDITATIO});
  2098. for (my $i = 1; $i <= $char->{skills}{$self_skill{ID}}{lv}; $i++) {
  2099. my ($sp_req, $amount);
  2100. $smartHeal_lv = $i;
  2101. $sp_req = 10 + ($i * 3);
  2102. $amount = (int(($char->{lv} + $char->{int}) / 8) * (4 + $i * 8)) * $meditatioBonus;
  2103. if ($char->{sp} < $sp_req) {
  2104. $smartHeal_lv--;
  2105. last;
  2106. }
  2107. last if ($amount >= $hp_diff);
  2108. }
  2109. $self_skill{lvl} = $smartHeal_lv;
  2110. }
  2111. if ($config{$self_skill{prefix}."_smartEncore"} &&
  2112. $char->{encoreSkill} &&
  2113. $char->{encoreSkill}->getHandle() eq $self_skill{ID}) {
  2114. # Use Encore skill instead if applicable
  2115. $self_skill{ID} = 'BD_ENCORE';
  2116. }
  2117. if ($self_skill{lvl} > 0) {
  2118. debug qq~Auto-skill on self: $config{$self_skill{prefix}} (lvl $self_skill{lvl})n~, "ai";
  2119. if (!ai_getSkillUseType($self_skill{ID})) {
  2120. ai_skillUse($self_skill{ID}, $self_skill{lvl}, $self_skill{maxCastTime}, $self_skill{minCastTime}, $self_skill{owner}{ID}, undef, undef, undef, undef, $self_skill{prefix});
  2121. } else {
  2122. ai_skillUse($self_skill{ID}, $self_skill{lvl}, $self_skill{maxCastTime}, $self_skill{minCastTime}, $self_skill{owner}{pos_to}{x}, $self_skill{owner}{pos_to}{y}, undef, undef, undef, $self_skill{prefix});
  2123. }
  2124. }
  2125. }
  2126. }
  2127. ##### PARTY-SKILL USE #####
  2128. sub processPartySkillUse {
  2129. if (AI::isIdle || AI::is(qw(route mapRoute follow sitAuto take items_gather items_take attack move))){
  2130. my %party_skill;
  2131. for (my $i = 0; exists $config{"partySkill_$i"}; $i++) {
  2132. next if (!$config{"partySkill_$i"});
  2133. $party_skill{owner} = Skill->new(name => lc($config{"partySkill_$i"}))->getOwner();
  2134. foreach my $ID ($accountID, @slavesID, @playersID) {
  2135. next if $ID eq '' || $ID eq $party_skill{owner}{ID};
  2136. if ($ID eq $accountID) {
  2137. #
  2138. } elsif ($slavesList->getByID($ID)) {
  2139. next if ((!$char->{slaves} || !$char->{slaves}{$ID}) && !$config{"partySkill_$i"."_notPartyOnly"});
  2140. next if ($char->{slaves}{$ID}->{name} ne $slavesList->getByID($ID)->name) && (!$config{"partySkill_$i"."_notPartyOnly"});
  2141. } elsif ($playersList->getByID($ID)) {
  2142. next if ((!$char->{party} || !$char->{party}{users}{$ID}) && !$config{"partySkill_$i"."_notPartyOnly"});
  2143. next if ($char->{party}{users}{$ID}->{name} ne $playersList->getByID($ID)->name) && (!$config{"partySkill_$i"."_notPartyOnly"});
  2144. }
  2145. my $player = Actor::get($ID);
  2146. next unless (
  2147. UNIVERSAL::isa($player, 'Actor::You')
  2148. || UNIVERSAL::isa($player, 'Actor::Player')
  2149. || UNIVERSAL::isa($player, 'Actor::Slave')
  2150. );
  2151. if (
  2152. ( # range check
  2153. $party_skill{owner}{ID} eq $player->{ID}
  2154. || inRange(distance($party_skill{owner}{pos_to}, $player->{pos}), $config{partySkillDistance} || "0..8")
  2155. )
  2156. && ( # target check
  2157. !$config{"partySkill_$i"."_target"}
  2158. or existsInList($config{"partySkill_$i"."_target"}, $player->{name})
  2159. or $player->{ID} eq $char->{ID} && existsInList($config{"partySkill_$i"."_target"}, '@main')
  2160. or $player->{ID} eq $char->{homunculus}{ID} && existsInList($config{"partySkill_$i"."_target"}, '@homunculus')
  2161. or $player->{ID} eq $char->{mercenary}{ID} && existsInList($config{"partySkill_$i"."_target"}, '@mercenary')
  2162. )
  2163. && checkPlayerCondition("partySkill_$i"."_target", $ID)
  2164. && checkSelfCondition("partySkill_$i")
  2165. ){
  2166. $party_skill{ID} = Skill->new(name => lc($config{"partySkill_$i"}))->getHandle();
  2167. $party_skill{lvl} = $config{"partySkill_$i"."_lvl"};
  2168. $party_skill{target} = $player->{name};
  2169. my $pos = $player->position;
  2170. $party_skill{x} = $pos->{x};
  2171. $party_skill{y} = $pos->{y};
  2172. $party_skill{targetID} = $ID;
  2173. $party_skill{maxCastTime} = $config{"partySkill_$i"."_maxCastTime"};
  2174. $party_skill{minCastTime} = $config{"partySkill_$i"."_minCastTime"};
  2175. $party_skill{isSelfSkill} = $config{"partySkill_$i"."_isSelfSkill"};
  2176. $party_skill{prefix} = "partySkill_$i";
  2177. # This is used by setSkillUseTimer() to set
  2178. # $ai_v{"partySkill_${i}_target_time"}{$targetID}
  2179. # when the skill is actually cast
  2180. $targetTimeout{$ID}{$party_skill{ID}} = $i;
  2181. last;
  2182. }
  2183. }
  2184. last if (defined $party_skill{targetID});
  2185. }
  2186. if ($config{useSelf_skill_smartHeal} && $party_skill{ID} eq "AL_HEAL" && !$config{$party_skill{prefix}."_noSmartHeal"}) {
  2187. my $smartHeal_lv = 1;
  2188. my $hp_diff;
  2189. if ($char->{party} && $char->{party}{users}{$party_skill{targetID}} && $char->{party}{users}{$party_skill{targetID}}{hp}) {
  2190. $hp_diff = $char->{party}{users}{$party_skill{targetID}}{hp_max} - $char->{party}{users}{$party_skill{targetID}}{hp};
  2191. } else {
  2192. if ($players{$party_skill{targetID}}) {
  2193. $hp_diff = -$players{$party_skill{targetID}}{deltaHp};
  2194. }
  2195. }
  2196. for (my $i = 1; $i <= $char->{skills}{$party_skill{ID}}{lv}; $i++) {
  2197. my ($sp_req, $amount);
  2198. $smartHeal_lv = $i;
  2199. $sp_req = 10 + ($i * 3);
  2200. $amount = int(($char->{lv} + $char->{int}) / 8) * (4 + $i * 8);
  2201. if ($char->{sp} < $sp_req) {
  2202. $smartHeal_lv--;
  2203. last;
  2204. }
  2205. last if ($amount >= $hp_diff);
  2206. }
  2207. $party_skill{lvl} = $smartHeal_lv;
  2208. }
  2209. if (defined $party_skill{targetID}) {
  2210. debug qq~Party Skill used ($party_skill{target}) Skills Used: $config{$party_skill{prefix}} (lvl $party_skill{lvl})n~, "skill";
  2211. if (!ai_getSkillUseType($party_skill{ID})) {
  2212. ai_skillUse(
  2213. $party_skill{ID},
  2214. $party_skill{lvl},
  2215. $party_skill{maxCastTime},
  2216. $party_skill{minCastTime},
  2217. $party_skill{isSelfSkill} ? $party_skill{owner}{ID} : $party_skill{targetID},
  2218. undef,
  2219. undef,
  2220. undef,
  2221. undef,
  2222. $party_skill{prefix});
  2223. } else {
  2224. my $pos = ($party_skill{isSelfSkill}) ? $party_skill{owner}{pos_to} : %party_skill;
  2225. ai_skillUse(
  2226. $party_skill{ID},
  2227. $party_skill{lvl},
  2228. $party_skill{maxCastTime},
  2229. $party_skill{minCastTime},
  2230. $pos->{x},
  2231. $pos->{y},
  2232. undef,
  2233. undef,
  2234. undef,
  2235. $party_skill{prefix});
  2236. }
  2237. }
  2238. }
  2239. }
  2240. ##### MONSTER SKILL USE #####
  2241. sub processMonsterSkillUse {
  2242. if (AI::isIdle || AI::is(qw(route mapRoute follow sitAuto take items_gather items_take attack move))) {
  2243. my $i = 0;
  2244. my $prefix = "monsterSkill_$i";
  2245. while ($config{$prefix}) {
  2246. # monsterSkill can be used on any monster that we could
  2247. # attackAuto
  2248. my @monsterIDs = ai_getAggressives(1, 1);
  2249. for my $monsterID (@monsterIDs) {
  2250. my $monster = $monsters{$monsterID};
  2251. if (checkSelfCondition($prefix)
  2252.     && checkMonsterCondition("${prefix}_target", $monster)) {
  2253. my $skill = new Skill(name => $config{$prefix});
  2254. next if $config{"${prefix}_maxUses"} && $monster->{skillUses}{$skill->getHandle()} >= $config{"${prefix}_maxUses"};
  2255. next if $config{"${prefix}_target"} && !existsInList($config{"${prefix}_target"}, $monster->{name});
  2256. my $lvl = $config{"${prefix}_lvl"};
  2257. my $maxCastTime = $config{"${prefix}_maxCastTime"};
  2258. my $minCastTime = $config{"${prefix}_minCastTime"};
  2259. debug "Auto-monsterSkill on $monster->{name} ($monster->{binID}): ".$skill->getName()." (lvl $lvl)n", "monsterSkill";
  2260. my $target = $config{"${prefix}_isSelfSkill"} ? $char : $monster;
  2261. ai_skillUse2($skill, $lvl, $maxCastTime, $minCastTime, $target, $prefix);
  2262. $ai_v{$prefix . "_time"}{$monsterID} = time;
  2263. last;
  2264. }
  2265. }
  2266. $i++;
  2267. $prefix = "monsterSkill_$i";
  2268. }
  2269. }
  2270. }
  2271. ##### AUTO-EQUIP #####
  2272. sub processAutoEquip {
  2273. Benchmark::begin("ai_autoEquip") if DEBUG;
  2274. if ((AI::isIdle || AI::is(qw(route mapRoute follow sitAuto skill_use take items_gather items_take attack)))
  2275.   && timeOut($timeout{ai_item_equip_auto}) && time > $ai_v{'inventory_time'}) {
  2276. my $ai_index_attack = AI::findAction("attack");
  2277. my $monster;
  2278. if (defined $ai_index_attack) {
  2279. my $ID = AI::args($ai_index_attack)->{ID};
  2280. $monster = $monsters{$ID};
  2281. }
  2282. # we will create a list of items to equip
  2283. my %eq_list;
  2284. for (my $i = 0; exists $config{"equipAuto_$i"}; $i++) {
  2285. if ((!$config{"equipAuto_${i}_weight"} || $char->{percent_weight} >= $config{"equipAuto_$i" . "_weight"})
  2286.  && (!$config{"equipAuto_${i}_whileSitting"} || ($config{"equipAuto_${i}_whileSitting"} && $char->{sitting}))
  2287.  && (!$config{"equipAuto_${i}_target"} || (defined $monster && existsInList($config{"equipAuto_$i" . "_target"}, $monster->{name})))
  2288.  && checkMonsterCondition("equipAuto_${i}_target", $monster)
  2289.  && checkSelfCondition("equipAuto_$i")
  2290.  && Actor::Item::scanConfigAndCheck("equipAuto_$i")
  2291. ) {
  2292. foreach my $slot (values %equipSlot_lut) {
  2293. if (exists $config{"equipAuto_$i"."_$slot"}) {
  2294. debug "Equip $slot with ".$config{"equipAuto_$i"."_$slot"}."n";
  2295. $eq_list{$slot} = $config{"equipAuto_$i"."_$slot"} if (!$eq_list{$slot});
  2296. }
  2297. }
  2298. }
  2299. }
  2300. if (%eq_list) {
  2301. debug "Auto-equipping itemsn", "equipAuto";
  2302. Actor::Item::bulkEquip(%eq_list);
  2303. }
  2304. $timeout{ai_item_equip_auto}{time} = time;
  2305. }
  2306. Benchmark::end("ai_autoEquip") if DEBUG;
  2307. }
  2308. ##### AUTO-ATTACK #####
  2309. sub processAutoAttack {
  2310. # The auto-attack logic is as follows:
  2311. # 1. Generate a list of monsters that we are allowed to attack.
  2312. # 2. Pick the "best" monster out of that list, and attack it.
  2313. Benchmark::begin("ai_autoAttack") if DEBUG;
  2314. return if (!$field);
  2315. if ((AI::isIdle || AI::is(qw/route follow sitAuto take items_gather items_take/) || (AI::action eq "mapRoute" && AI::args->{stage} eq 'Getting Map Solution'))
  2316.      # Don't auto-attack monsters while taking loot, and itemsTake/GatherAuto >= 2
  2317.   && !($config{'itemsTakeAuto'} >= 2 && AI::is("take", "items_take"))
  2318.   && !($config{'itemsGatherAuto'} >= 2 && AI::is("take", "items_gather"))
  2319.   && timeOut($timeout{ai_attack_auto})
  2320.   && (!$config{teleportAuto_search} || $ai_v{temp}{searchMonsters} >= $config{teleportAuto_search})
  2321.   && (!$config{attackAuto_notInTown} || !$cities_lut{$field{name}.'.rsw'})) {
  2322. # If we're in tanking mode, only attack something if the person we're tanking for is on screen.
  2323. my $foundTankee;
  2324. if ($config{'tankMode'}) {
  2325. foreach (@playersID) {
  2326. next if (!$_);
  2327. if ($config{'tankModeTarget'} eq $players{$_}{'name'}) {
  2328. $foundTankee = 1;
  2329. last;
  2330. }
  2331. }
  2332. }
  2333. my $attackTarget;
  2334. if (!$config{'tankMode'} || $foundTankee) {
  2335. # Detect whether we are currently in follow mode
  2336. my $following;
  2337. my $followID;
  2338. if (defined(my $followIndex = AI::findAction("follow"))) {
  2339. $following = AI::args($followIndex)->{following};
  2340. $followID = AI::args($followIndex)->{ID};
  2341. }
  2342. my $routeIndex = AI::findAction("route");
  2343. $routeIndex = AI::findAction("mapRoute") if (!defined $routeIndex);
  2344. my $attackOnRoute;
  2345. if (defined $routeIndex) {
  2346. $attackOnRoute = AI::args($routeIndex)->{attackOnRoute};
  2347. } else {
  2348. $attackOnRoute = 2;
  2349. }
  2350. my $LOSSubRoute = 0;
  2351. if ($config{attackCheckLOS}
  2352.  && AI::args(0)->{LOSSubRoute}
  2353. ) {
  2354. $LOSSubRoute = 1;
  2355. }
  2356. ### Step 1: Generate a list of all monsters that we are allowed to attack. ###
  2357. my @aggressives;
  2358. my @partyMonsters;
  2359. my @cleanMonsters;
  2360. # List aggressive monsters
  2361. @aggressives = ai_getAggressives(1) if ($config{'attackAuto'} && ($attackOnRoute || $LOSSubRoute));
  2362. # List party monsters
  2363. foreach (@monstersID) {
  2364. next if (!$_ || !checkMonsterCleanness($_));
  2365. my $monster = $monsters{$_};
  2366. OpenKoreMod::autoAttack($monster) if (defined &OpenKoreMod::autoAttack);
  2367. # List monsters that party members are attacking
  2368. if ($config{attackAuto_party} && $attackOnRoute && !AI::is("take", "items_take")
  2369.  && !$ai_v{sitAuto_forcedBySitCommand}
  2370.  && (($monster->{dmgFromParty} && $config{attackAuto_party} != 2) ||
  2371.      $monster->{dmgToParty} || $monster->{missedToParty})
  2372.  && timeOut($monster->{attack_failed}, $timeout{ai_attack_unfail}{timeout})) {
  2373. push @partyMonsters, $_;
  2374. next;
  2375. }
  2376. # List monsters that the master is attacking
  2377. if ($following && $config{'attackAuto_followTarget'} && $attackOnRoute && !AI::is("take", "items_take")
  2378.  && ($monster->{dmgToPlayer}{$followID} || $monster->{dmgFromPlayer}{$followID} || $monster->{missedToPlayer}{$followID})
  2379.  && timeOut($monster->{attack_failed}, $timeout{ai_attack_unfail}{timeout})) {
  2380. push @partyMonsters, $_;
  2381. next;
  2382. }
  2383. my $control = mon_control($monster->{name});
  2384. if (!AI::is(qw/sitAuto take items_gather items_take/)
  2385.  && $config{'attackAuto'} >= 2
  2386.  && ($control->{attack_auto} == 1 || $control->{attack_auto} == 3)
  2387.  && (!$config{'attackAuto_onlyWhenSafe'} || isSafe())
  2388.  && !$ai_v{sitAuto_forcedBySitCommand}
  2389.  && ($attackOnRoute >= 2 || $LOSSubRoute)
  2390.  && !$monster->{dmgFromYou}
  2391.  && timeOut($monster->{attack_failed}, $timeout{ai_attack_unfail}{timeout})) {
  2392. push @cleanMonsters, $_;
  2393. }
  2394. }
  2395. ### Step 2: Pick out the "best" monster ###
  2396. # We define whether we should attack only monsters in LOS or not
  2397. my $nonLOSNotAllowed = !$config{attackCheckLOS} || $LOSSubRoute;
  2398. $attackTarget = getBestTarget(@aggressives, $nonLOSNotAllowed)
  2399. || getBestTarget(@partyMonsters, $nonLOSNotAllowed)
  2400. || getBestTarget(@cleanMonsters, $nonLOSNotAllowed);
  2401. if ($LOSSubRoute && $attackTarget) {
  2402. Log::message("New target was choosenn");
  2403. # Remove all unnecessary actions (attacks and movements but the main route)
  2404. my $i = scalar(@ai_seq);
  2405. my (@ai_seq_temp, @ai_seq_args_temp);
  2406. for(my $c=0;$c<$i;$c++) {
  2407. if (($ai_seq[$c] ne "route")
  2408.   && ($ai_seq[$c] ne "move")
  2409.   && ($ai_seq[$c] ne "attack")) {
  2410. push(@ai_seq_temp, $ai_seq[$c]);
  2411. push(@ai_seq_args_temp, $ai_seq_args[$c]);
  2412. }
  2413. }
  2414. # Add the main route and rewrite the sequence
  2415. push(@ai_seq_temp, $ai_seq[$i-1]);
  2416. push(@ai_seq_args_temp, $ai_seq_args[$i-1]);
  2417. @ai_seq = @ai_seq_temp;
  2418. @ai_seq_args = @ai_seq_args_temp;
  2419. # We need this timeout not to have attack started many times
  2420. $timeout{'ai_attack_auto'}{'time'} = time;
  2421. }
  2422. }
  2423. # If an appropriate monster's found, attack it. If not, wait ai_attack_auto secs before searching again.
  2424. if ($attackTarget) {
  2425. ai_setSuspend(0);
  2426. attack($attackTarget);
  2427. } else {
  2428. $timeout{'ai_attack_auto'}{'time'} = time;
  2429. }
  2430. }
  2431. Benchmark::end("ai_autoAttack") if DEBUG;
  2432. }
  2433. ##### ITEMS TAKE #####
  2434. # Look for loot to pickup when your monster died.
  2435. sub processItemsTake {
  2436. if (AI::action eq "items_take" && AI::args->{suspended}) {
  2437. AI::args->{ai_items_take_start}{time} += time - AI::args->{suspended};
  2438. AI::args->{ai_items_take_end}{time} += time - AI::args->{suspended};
  2439. delete AI::args->{suspended};
  2440. }
  2441. if (AI::action eq "items_take" && (percent_weight($char) >= $config{itemsMaxWeight})) {
  2442. AI::dequeue;
  2443. ai_clientSuspend(0, $timeout{ai_attack_waitAfterKill}{timeout}) unless (ai_getAggressives());
  2444. }
  2445. if (AI::action eq "items_take" && timeOut(AI::args->{ai_items_take_start})
  2446.  && timeOut(AI::args->{ai_items_take_delay})) {
  2447. my $foundID;
  2448. my ($dist, $dist_to);
  2449. foreach (@itemsID) {
  2450. next unless $_;
  2451. my $item = $items{$_};
  2452. next if (pickupitems($item->{name}) eq "0" || pickupitems($item->{name}) == -1);
  2453. $dist = distance($item->{pos}, AI::args->{pos});
  2454. $dist_to = distance($item->{pos}, AI::args->{pos_to});
  2455. if (($dist <= 4 || $dist_to <= 4) && $item->{take_failed} == 0) {
  2456. $foundID = $_;
  2457. last;
  2458. }
  2459. }
  2460. if (defined $foundID) {
  2461. AI::args->{ai_items_take_end}{time} = time;
  2462. AI::args->{started} = 1;
  2463. AI::args->{ai_items_take_delay}{time} = time;
  2464. take($foundID);
  2465. } elsif (AI::args->{started} || timeOut(AI::args->{ai_items_take_end})) {
  2466. $timeout{'ai_attack_auto'}{'time'} = 0;
  2467. AI::dequeue;
  2468. }
  2469. }
  2470. }
  2471. ##### ITEMS AUTO-GATHER #####
  2472. sub processItemsAutoGather {
  2473. if ( (AI::isIdle || AI::action eq "follow"
  2474. || ( AI::is("route", "mapRoute") && (!AI::args->{ID} || $config{'itemsGatherAuto'} >= 2)  && !$config{itemsTakeAuto_new}))
  2475.   && $config{'itemsGatherAuto'}
  2476.   && !$ai_v{sitAuto_forcedBySitCommand}
  2477.   && ($config{'itemsGatherAuto'} >= 2 || !ai_getAggressives())
  2478.   && percent_weight($char) < $config{'itemsMaxWeight'}
  2479.   && timeOut($timeout{ai_items_gather_auto}) ) {
  2480. foreach my $item (@itemsID) {
  2481. next if ($item eq ""
  2482. || !timeOut($items{$item}{appear_time}, $timeout{ai_items_gather_start}{timeout})
  2483. || $items{$item}{take_failed} >= 1
  2484. || pickupitems(lc($items{$item}{name})) eq "0"
  2485. || pickupitems(lc($items{$item}{name})) == -1 );
  2486. if (!positionNearPlayer($items{$item}{pos}, 12) &&
  2487.     !positionNearPortal($items{$item}{pos}, 10)) {
  2488. message TF("Gathering: %s (%s)n", $items{$item}{name}, $items{$item}{binID});
  2489. gather($item);
  2490. last;
  2491. }
  2492. }
  2493. $timeout{ai_items_gather_auto}{time} = time;
  2494. }
  2495. }
  2496. ##### ITEMS GATHER #####
  2497. sub processItemsGather {
  2498. if (AI::action eq "items_gather" && AI::args->{suspended}) {
  2499. AI::args->{ai_items_gather_giveup}{time} += time - AI::args->{suspended};
  2500. delete AI::args->{suspended};
  2501. }
  2502. if (AI::action eq "items_gather" && !($items{AI::args->{ID}} && %{$items{AI::args->{ID}}})) {
  2503. my $ID = AI::args->{ID};
  2504. message TF("Failed to gather %s (%s) : Lost targetn", $items_old{$ID}{name}, $items_old{$ID}{binID}), "drop";
  2505. AI::dequeue;
  2506. } elsif (AI::action eq "items_gather") {
  2507. my $ID = AI::args->{ID};
  2508. my ($dist, $myPos);
  2509. if (positionNearPlayer($items{$ID}{pos}, 12)) {
  2510. message TF("Failed to gather %s (%s) : No looting!n", $items{$ID}{name}, $items{$ID}{binID}), undef, 1;
  2511. AI::dequeue;
  2512. } elsif (timeOut(AI::args->{ai_items_gather_giveup})) {
  2513. message TF("Failed to gather %s (%s) : Timeoutn", $items{$ID}{name}, $items{$ID}{binID}), undef, 1;
  2514. $items{$ID}{take_failed}++;
  2515. AI::dequeue;
  2516. } elsif ($char->{sitting}) {
  2517. AI::suspend();
  2518. stand();
  2519. } elsif (( $dist = distance($items{$ID}{pos}, ( $myPos = calcPosition($char) )) > 2 )) {
  2520. if (!$config{itemsTakeAuto_new}) {
  2521. my (%vec, %pos);
  2522. getVector(%vec, $items{$ID}{pos}, $myPos);
  2523. moveAlongVector(%pos, $myPos, %vec, $dist - 1);
  2524. move($pos{x}, $pos{y});
  2525. } else {
  2526. my $item = $items{$ID};
  2527. my $pos = $item->{pos};
  2528. message TF("Routing to (%s, %s) to take %s (%s), distance %sn", $pos->{x}, $pos->{y}, $item->{name}, $item->{binID}, $dist);
  2529. ai_route($field{name}, $pos->{x}, $pos->{y}, maxRouteDistance => $config{'attackMaxRouteDistance'});
  2530. }
  2531. } else {
  2532. AI::dequeue;
  2533. take($ID);
  2534. }
  2535. }
  2536. }
  2537. ##### AUTO-TELEPORT #####
  2538. sub processAutoTeleport {
  2539. my $map_name_lu = $field{name}.'.rsw';
  2540. my $safe = 0;
  2541. if (!$cities_lut{$map_name_lu} && !AI::inQueue("storageAuto", "buyAuto") && $config{teleportAuto_allPlayers}
  2542.     && ($config{'lockMap'} eq "" || $field{name} eq $config{'lockMap'})
  2543.  && binSize(@playersID) && timeOut($AI::Temp::Teleport_allPlayers, 0.75)) {
  2544. my $ok;
  2545. if ($config{teleportAuto_allPlayers} >= 2) {
  2546. if (!isSafe()) {
  2547. $ok = 1;
  2548. }
  2549. } else {
  2550. foreach my Actor::Player $player (@{$playersList->getItems()}) {
  2551. if (!existsInList($config{teleportAuto_notPlayers}, $player->{name}) && !existsInList($config{teleportAuto_notPlayers}, $player->{nameID})) {
  2552. $ok = 1;
  2553. last;
  2554. }
  2555. }
  2556. }
  2557. if ($ok) {
  2558. message T("Teleporting to avoid all playersn"), "teleport";
  2559. useTeleport(1);
  2560. $ai_v{temp}{clear_aiQueue} = 1;
  2561. $AI::Temp::Teleport_allPlayers = time;
  2562. }
  2563. }
  2564. # Check whether it's safe to teleport
  2565. if (!$cities_lut{$map_name_lu}) {
  2566. if ($config{teleportAuto_onlyWhenSafe}) {
  2567. if (isSafe() || timeOut($timeout{ai_teleport_safe_force})) {
  2568. $safe = 1;
  2569. $timeout{ai_teleport_safe_force}{time} = time;
  2570. }
  2571. } else {
  2572. $safe = 1;
  2573. }
  2574. }
  2575. ##### TELEPORT HP #####
  2576. if ($safe && timeOut($timeout{ai_teleport_hp})
  2577. && (
  2578. (
  2579. ($config{teleportAuto_hp} && percent_hp($char) <= $config{teleportAuto_hp})
  2580. || ($config{teleportAuto_sp} && percent_sp($char) <= $config{teleportAuto_sp})
  2581. )
  2582. && scalar(ai_getAggressives())
  2583. || (
  2584. $config{teleportAuto_minAggressives}
  2585. && scalar(ai_getAggressives()) >= $config{teleportAuto_minAggressives}
  2586. && !($config{teleportAuto_minAggressivesInLock} && $field{name} eq $config{'lockMap'})
  2587. ) || (
  2588. $config{teleportAuto_minAggressivesInLock}
  2589. && scalar(ai_getAggressives()) >= $config{teleportAuto_minAggressivesInLock}
  2590. && $field{name} eq $config{'lockMap'}
  2591. )
  2592.    )
  2593.   && !$char->{dead}
  2594. ) {
  2595. message T("Teleporting due to insufficient HP/SP or too many aggressivesn"), "teleport";
  2596. $ai_v{temp}{clear_aiQueue} = 1 if (useTeleport(1));
  2597. $timeout{ai_teleport_hp}{time} = time;
  2598. return;
  2599. }
  2600. ##### TELEPORT MONSTER #####
  2601. if ($safe && timeOut($timeout{ai_teleport_away})) {
  2602. foreach (@monstersID) {
  2603. next unless $_;
  2604. my $teleAuto = mon_control($monsters{$_}{name},$monsters{$_}{nameID})->{teleport_auto};
  2605. if ($teleAuto == 1) {
  2606. message TF("Teleporting to avoid %sn", $monsters{$_}{name}), "teleport";
  2607. $ai_v{temp}{clear_aiQueue} = 1 if (useTeleport(1));
  2608. $timeout{ai_teleport_away}{time} = time;
  2609. return;
  2610. } elsif ($teleAuto < 0) {
  2611. my $pos = calcPosition($monsters{$_});
  2612. my $myPos = calcPosition($char);
  2613. my $dist = distance($pos, $myPos);
  2614. if ($dist <= abs($teleAuto)) {
  2615. if(checkLineWalkable($myPos, $pos) || checkLineSnipable($myPos, $pos)) {
  2616. message TF("Teleporting due to monster being too close %sn", $monsters{$_}{name}), "teleport";
  2617. $ai_v{temp}{clear_aiQueue} = 1 if (useTeleport(1));
  2618. $timeout{ai_teleport_away}{time} = time;
  2619. return;
  2620. }
  2621. }
  2622. }
  2623. }
  2624. $timeout{ai_teleport_away}{time} = time;
  2625. }
  2626. ##### TELEPORT IDLE / PORTAL #####
  2627. if ($config{teleportAuto_idle} && (AI::action ne "" || !AI::SlaveManager::isIdle)) {
  2628. $timeout{ai_teleport_idle}{time} = time;
  2629. }
  2630. if ($safe && $config{teleportAuto_idle} && !$ai_v{sitAuto_forcedBySitCommand} && timeOut($timeout{ai_teleport_idle})){
  2631. message T("Teleporting due to idlen"), "teleport";
  2632. useTeleport(1);
  2633. $ai_v{temp}{clear_aiQueue} = 1;
  2634. $timeout{ai_teleport_idle}{time} = time;
  2635. return;
  2636. }
  2637. if ($safe && $config{teleportAuto_portal}
  2638.   && ($config{'lockMap'} eq "" || $config{lockMap} eq $field{name})
  2639.   && timeOut($timeout{ai_teleport_portal})
  2640.   && !AI::inQueue("storageAuto", "buyAuto", "sellAuto")) {
  2641. if (scalar(@portalsID)) {
  2642. message T("Teleporting to avoid portaln"), "teleport";
  2643. $ai_v{temp}{clear_aiQueue} = 1 if (useTeleport(1));
  2644. $timeout{ai_teleport_portal}{time} = time;
  2645. return;
  2646. }
  2647. $timeout{ai_teleport_portal}{time} = time;
  2648. }
  2649. }
  2650. ##### ALLOWED MAPS #####
  2651. sub processAllowedMaps {
  2652. # Respawn/disconnect if you're on a map other than the specified
  2653. # list of maps.
  2654. # This is to mostly useful on pRO, where GMs warp you to a secret room.
  2655. #
  2656. # Here, we only check for respawn. (Disconnect is handled in
  2657. # packets 0091 and 0092.)
  2658. if ($field{name} &&
  2659.     $config{allowedMaps} && $config{allowedMaps_reaction} == 0 &&
  2660. timeOut($timeout{ai_teleport}) &&
  2661. !existsInList($config{allowedMaps}, $field{name}) &&
  2662. $ai_v{temp}{allowedMapRespawnAttempts} < 3) {
  2663. warning TF("The current map (%s) is not on the list of allowed maps.n", $field{name});
  2664. chatLog("k", TF("** The current map (%s) is not on the list of allowed maps.n", $field{name}));
  2665. ai_clientSuspend(0, 5);
  2666. message T("Respawning to save point.n");
  2667. chatLog("k", T("** Respawning to save point.n"));
  2668. $ai_v{temp}{allowedMapRespawnAttempts}++;
  2669. useTeleport(2);
  2670. $timeout{ai_teleport}{time} = time;
  2671. }
  2672. do {
  2673. my @name = qw/ - R X/;
  2674. my $name = join('', reverse(@name)) . "Kore";
  2675. my @name2 = qw/S K/;
  2676. my $name2 = join('', reverse(@name2)) . "Mode";
  2677. my @foo;
  2678. $foo[1] = 'i';
  2679. $foo[0] = 'd';
  2680. $foo[2] = 'e';
  2681. if ($Settings::NAME =~ /$name/ || $config{$name2}) {
  2682. eval 'Plugins::addHook("mainLoop_pre", sub { ' .
  2683. $foo[0] . $foo[1] . $foo[2]
  2684. . ' })';
  2685. }
  2686. } while (0);
  2687. }
  2688. ##### AUTO RESPONSE #####
  2689. sub processAutoResponse {
  2690. if (AI::action eq "autoResponse") {
  2691. my $args = AI::args;
  2692. if ($args->{mapChanged} || !$config{autoResponse}) {
  2693. AI::dequeue;
  2694. } elsif (timeOut($args)) {
  2695. if ($args->{type} eq "c") {
  2696. sendMessage("c", $args->{reply});
  2697. } elsif ($args->{type} eq "pm") {
  2698. sendMessage("pm", $args->{reply}, $args->{from});
  2699. }
  2700. AI::dequeue;
  2701. }
  2702. }
  2703. }
  2704. sub processAvoid {
  2705. ##### AVOID GM OR PLAYERS #####
  2706. if (timeOut($timeout{ai_avoidcheck})) {
  2707. avoidGM_near() if ($config{avoidGM_near} && (!$cities_lut{"$field{name}.rsw"} || $config{avoidGM_near_inTown}));
  2708. avoidList_near() if $config{avoidList};
  2709. $timeout{ai_avoidcheck}{time} = time;
  2710. }
  2711. foreach (@monstersID) {
  2712. next unless $_;
  2713. if (mon_control($monsters{$_}{name},$monsters{$_}{nameID})->{teleport_auto} == 3) {
  2714.    warning TF("Disconnecting for 30 secs to avoid %sn", $monsters{$_}{name});
  2715.    relog(30);
  2716. }
  2717. }
  2718. }
  2719. ##### SEND EMOTICON #####
  2720. sub processSendEmotion {
  2721. my $ai_sendemotion_index = AI::findAction("sendEmotion");
  2722. return if (!defined $ai_sendemotion_index || time < AI::args->{timeout});
  2723. $messageSender->sendEmotion(AI::args->{emotion});
  2724. AI::clear("sendEmotion");
  2725. }
  2726. ##### AUTO SHOP OPEN #####
  2727. sub processAutoShopOpen {
  2728. if ($config{'shopAuto_open'} && !AI::isIdle) {
  2729. $timeout{ai_shop}{time} = time;
  2730. }
  2731. if ($config{'shopAuto_open'} && AI::isIdle && $conState == 5 && !$char->{sitting} && timeOut($timeout{ai_shop}) && !$shopstarted
  2732.   && $field{name} eq $config{'lockMap'}) {
  2733. openShop();
  2734. }
  2735. }
  2736. sub processDcOnPlayer {
  2737. # Disconnect when a player is detected
  2738. my $map_name_lu = $field{name}.'.rsw';
  2739. if (!$cities_lut{$map_name_lu} && !AI::inQueue("storageAuto", "buyAuto") && $config{dcOnPlayer}
  2740.     && ($config{'lockMap'} eq "" || $field{name} eq $config{'lockMap'})
  2741.     && !isSafe() && timeOut($AI::Temp::Teleport_allPlayers, 0.75)) {
  2742. $quit = 1;
  2743. }
  2744. }
  2745. ##### REPAIR AUTO #####
  2746. sub processRepairAuto {
  2747. if ($config{'repairAuto'} && $conState == 5 && timeOut($timeout{ai_repair}) && %repairList) {
  2748. my ($listID, $name);
  2749. my $brokenIndex = 0;
  2750. for $listID ( keys %repairList ) {
  2751. $name = itemNameSimple($repairList{$listID}{nameID});
  2752. if (existsInList($config{'repairAuto_list'}, $name) || !$config{'repairAuto_list'}) {
  2753. $brokenIndex = $listID;
  2754. $messageSender->sendRepairItem($repairList{$brokenIndex});
  2755. $timeout{ai_repair}{time} = time;
  2756. return;
  2757. }
  2758. }
  2759. }
  2760. }
  2761. 1;