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

外挂编程

开发平台:

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. # Helper functions for managing @ai_seq.
  13. #
  14. # Eventually, @ai_seq should never be referenced directly, and then it can be
  15. # moved into this package.
  16. package AI;
  17. use strict;
  18. use Globals;
  19. use Utils qw(binFind);
  20. use Log qw(message warning error debug);
  21. use Utils;
  22. use Field;
  23. use Exporter;
  24. use base qw(Exporter);
  25. use Translation;
  26. our @EXPORT = (
  27. qw/
  28. ai_clientSuspend
  29. ai_drop
  30. ai_follow
  31. ai_partyfollow
  32. ai_getAggressives
  33. ai_getPlayerAggressives
  34. ai_getMonstersAttacking
  35. ai_getSkillUseType
  36. ai_mapRoute_searchStep
  37. ai_items_take
  38. ai_route
  39. ai_route_getRoute
  40. ai_sellAutoCheck
  41. ai_setMapChanged
  42. ai_setSuspend
  43. ai_skillUse
  44. ai_skillUse2
  45. ai_storageAutoCheck
  46. cartGet
  47. cartAdd
  48. ai_talkNPC
  49. attack
  50. gather
  51. move
  52. sit
  53. stand
  54. take/
  55. );
  56. sub action {
  57. my $i = (defined $_[0] ? $_[0] : 0);
  58. return $ai_seq[$i];
  59. }
  60. sub args {
  61. my $i = (defined $_[0] ? $_[0] : 0);
  62. return %{$ai_seq_args[$i]};
  63. }
  64. sub dequeue {
  65. shift @ai_seq;
  66. shift @ai_seq_args;
  67. }
  68. sub queue {
  69. unshift @ai_seq, shift;
  70. my $args = shift;
  71. unshift @ai_seq_args, ((defined $args) ? $args : {});
  72. }
  73. sub clear {
  74. if (@_) {
  75. my $changed;
  76. for (my $i = 0; $i < @ai_seq; $i++) {
  77. if (defined binFind(@_, $ai_seq[$i])) {
  78. delete $ai_seq[$i];
  79. delete $ai_seq_args[$i];
  80. $changed = 1;
  81. }
  82. }
  83. if ($changed) {
  84. my (@new_seq, @new_args);
  85. for (my $i = 0; $i < @ai_seq; $i++) {
  86. if (defined $ai_seq[$i]) {
  87. push @new_seq, $ai_seq[$i];
  88. push @new_args, $ai_seq_args[$i];
  89. }
  90. }
  91. @ai_seq = @new_seq;
  92. @ai_seq_args = @new_args;
  93. }
  94. } else {
  95. undef @ai_seq;
  96. undef @ai_seq_args;
  97. }
  98. }
  99. sub suspend {
  100. my $i = (defined $_[0] ? $_[0] : 0);
  101. $ai_seq_args[$i]{suspended} = time if $i < @ai_seq_args;
  102. }
  103. sub mapChanged {
  104. my $i = (defined $_[0] ? $_[0] : 0);
  105. $ai_seq_args[$i]{mapChanged} = time if $i < @ai_seq_args;
  106. }
  107. sub findAction {
  108. return binFind(@ai_seq, $_[0]);
  109. }
  110. sub inQueue {
  111. foreach (@_) {
  112. # Apparently using a loop is faster than calling
  113. # binFind() (which is optimized in C), because
  114. # of function call overhead.
  115. #return 1 if defined binFind(@ai_seq, $_);
  116. foreach my $seq (@ai_seq) {
  117. return 1 if ($_ eq $seq);
  118. }
  119. }
  120. return 0;
  121. }
  122. sub isIdle {
  123. return $ai_seq[0] eq "";
  124. }
  125. sub is {
  126. foreach (@_) {
  127. return 1 if ($ai_seq[0] eq $_);
  128. }
  129. return 0;
  130. }
  131. ##########################################
  132. ##
  133. # ai_clientSuspend(packet_switch, duration, args...)
  134. # initTimeout: a number of seconds.
  135. #
  136. # Freeze the AI for $duration seconds. $packet_switch and @args are only
  137. # used internally and are ignored unless XKore mode is turned on.
  138. sub ai_clientSuspend {
  139. my ($type, $duration, @args) = @_;
  140. my %args;
  141. $args{type} = $type;
  142. $args{time} = time;
  143. $args{timeout} = $duration;
  144. @{$args{args}} = @args;
  145. AI::queue("clientSuspend", %args);
  146. debug "AI suspended by clientSuspend for $args{timeout} secondsn";
  147. }
  148. ##
  149. # ai_drop(items, max)
  150. # items: reference to an array of inventory item numbers.
  151. # max: the maximum amount to drop, for each item, or 0 for unlimited.
  152. #
  153. # Drop one or more items.
  154. #
  155. # Example:
  156. # # Drop inventory items 2 and 5.
  157. # ai_drop([2, 5]);
  158. # # Drop inventory items 2 and 5, but at most 30 of each item.
  159. # ai_drop([2, 5], 30);
  160. sub ai_drop {
  161. my $r_items = shift;
  162. my $max = shift;
  163. my %seq = ();
  164. if (@{$r_items} == 1) {
  165. # Dropping one item; do it immediately
  166. Misc::drop($r_items->[0], $max);
  167. } else {
  168. # Dropping multiple items; queue an AI sequence
  169. $seq{items} = @{$r_items};
  170. $seq{max} = $max;
  171. $seq{timeout} = 1;
  172. AI::queue("drop", %seq);
  173. }
  174. }
  175. sub ai_follow {
  176. my $name = shift;
  177. return 0 if (!$name);
  178. if (binFind(@ai_seq, "follow") eq "") {
  179. my %args;
  180. $args{name} = $name;
  181. push @ai_seq, "follow";
  182. push @ai_seq_args, %args;
  183. }
  184. return 1;
  185. }
  186. sub ai_partyfollow {
  187. # we have to enable re-calc of route based on master's possition regulary, even when it is
  188. # on route and move, otherwise we have finaly moved to the possition and found that the master
  189. # already teleported to another side of the map.
  190. # This however will give problem on few seq such as storageAuto as 'move' and 'route' might
  191. # be triggered to move to the NPC
  192. my %master;
  193. $master{id} = main::findPartyUserID($config{followTarget});
  194. if ($master{id} ne "" && !AI::inQueue("storageAuto","storageGet","sellAuto","buyAuto")) {
  195. $master{x} = $char->{party}{users}{$master{id}}{pos}{x};
  196. $master{y} = $char->{party}{users}{$master{id}}{pos}{y};
  197. ($master{map}) = $char->{party}{users}{$master{id}}{map} =~ /([sS]*).gat/;
  198. if ($master{map} ne $field{name} || $master{x} == 0 || $master{y} == 0) {
  199. delete $master{x};
  200. delete $master{y};
  201. }
  202. return unless ($master{map} ne $field{name} || exists $master{x});
  203. if ((exists $ai_v{master} && distance(%master, $ai_v{master}) > 15)
  204. || $master{map} != $ai_v{master}{map}
  205. || (timeOut($ai_v{master}{time}, 15) && distance(%master, $char->{pos_to}) > $config{followDistanceMax})) {
  206. $ai_v{master}{x} = $master{x};
  207. $ai_v{master}{y} = $master{y};
  208. $ai_v{master}{map} = $master{map};
  209. $ai_v{master}{time} = time;
  210. if ($ai_v{master}{map} ne $field{name}) {
  211. message TF("Calculating route to find master: %sn", $ai_v{master}{map}), "follow";
  212. } elsif (distance(%master, $char->{pos_to}) > $config{followDistanceMax} ) {
  213. message TF("Calculating route to find master: %s (%s,%s)n", $ai_v{master}{map}, $ai_v{master}{x}, $ai_v{master}{y}), "follow";
  214. } else {
  215. return;
  216. }
  217. AI::clear("move", "route", "mapRoute");
  218. ai_route($ai_v{master}{map}, $ai_v{master}{x}, $ai_v{master}{y}, distFromGoal => $config{followDistanceMin});
  219. my $followIndex = AI::findAction("follow");
  220. if (defined $followIndex) {
  221. $ai_seq_args[$followIndex]{ai_follow_lost_end}{timeout} = $timeout{ai_follow_lost_end}{timeout};
  222. }
  223. }
  224. }
  225. }
  226. ##
  227. # ai_getAggressives([check_mon_control], [party])
  228. # Returns: an array of monster IDs, or a number.
  229. #
  230. # Get a list of all aggressive monsters on screen.
  231. # The definition of "aggressive" is: a monster who has hit or missed me.
  232. #
  233. # If $check_mon_control is set, then all monsters in mon_control.txt
  234. # with the 'attack_auto' flag set to 2, will be considered as aggressive.
  235. # See also the manual for more information about this.
  236. #
  237. # If $party is set, then monsters that have fought with party members
  238. # (not just you) will be considered as aggressive.
  239. sub ai_getAggressives {
  240. my ($type, $party) = @_;
  241. my $wantArray = wantarray;
  242. my $num = 0;
  243. my @agMonsters;
  244. foreach my $monster (@{$monstersList->getItems()}) {
  245. my $control = Misc::mon_control($monster->name,$monster->{nameID}) if $type || !$wantArray;
  246. my $ID = $monster->{ID};
  247. next if (!timeOut($monster->{attack_failedLOS}, 6));
  248. if (($type && ($control->{attack_auto} == 2)) ||
  249. (($monster->{dmgToYou} || $monster->{missedYou}) && Misc::checkMonsterCleanness($ID)) ||
  250. ($party && ($monster->{dmgToParty} || $monster->{missedToParty} || $monster->{dmgFromParty})) &&
  251. timeOut($monster->{attack_failed}, $timeout{ai_attack_unfail}{timeout}))
  252. {
  253. # Remove monsters that are considered forced agressive (when set to 2 on Mon_Control)
  254. # but has not yet been damaged or attacked by party AND currently has no LOS
  255. # if this is not done, Kore will keep trying infinitely attack targets set to aggro but who
  256. # has no Line of Sight (ex.: GH Cemitery when on a higher position seeing an aggro monster in lower levels).
  257. # The other parameters are re-checked along, so you can continue to attack a monster who has
  258. # already been hit but lost the line for some reason.
  259. # Also, check if the forced aggressive is a clean target when it has not marked as "yours".
  260. my $myPos = calcPosition($char);
  261. my $pos = calcPosition($monster);
  262. next if (($type && $control->{attack_auto} == 2)
  263. && (($config{'attackCanSnipe'}) ? !Misc::checkLineSnipable($myPos, $pos) : (!Misc::checkLineWalkable($myPos, $pos) || !Misc::checkLineSnipable($myPos, $pos)))
  264. && !$monster->{dmgToYou} && !$monster->{missedYou}
  265. && ($party && (!$monster->{dmgToParty} && !$monster->{missedToParty} && !$monster->{dmgFromParty}))
  266. );
  267. # Continuing, check whether the forced Agro is really a clean monster;
  268. next if (($type && $control->{attack_auto} == 2) && !Misc::checkMonsterCleanness($ID));
  269. if ($wantArray) {
  270. # Function is called in array context
  271. push @agMonsters, $ID;
  272. } else {
  273. # Function is called in scalar context
  274. if ($control->{weight} > 0) {
  275. $num += $control->{weight};
  276. } elsif ($control->{weight} != -1) {
  277. $num++;
  278. }
  279. }
  280. }
  281. }
  282. if ($wantArray) {
  283. return @agMonsters;
  284. } else {
  285. return $num;
  286. }
  287. }
  288. sub ai_getPlayerAggressives {
  289. my $ID = shift;
  290. my @agMonsters;
  291. foreach (@monstersID) {
  292. next if ($_ eq "");
  293. if ($monsters{$_}{dmgToPlayer}{$ID} > 0 || $monsters{$_}{missedToPlayer}{$ID} > 0 || $monsters{$_}{dmgFromPlayer}{$ID} > 0 || $monsters{$_}{missedFromPlayer}{$ID} > 0) {
  294. push @agMonsters, $_;
  295. }
  296. }
  297. return @agMonsters;
  298. }
  299. ##
  300. # ai_getMonstersAttacking($ID)
  301. #
  302. # Get the monsters who are attacking player $ID.
  303. sub ai_getMonstersAttacking {
  304. my $ID = shift;
  305. my @agMonsters;
  306. foreach (@monstersID) {
  307. next unless $_;
  308. my $monster = $monsters{$_};
  309. push @agMonsters, $_ if $monster->{target} eq $ID;
  310. }
  311. return @agMonsters;
  312. }
  313. ##
  314. # ai_getSkillUseType(name)
  315. # name: the internal name of the skill (as found in skills.txt), such as
  316. # WZ_FIREPILLAR.
  317. # Returns: 1 if it's a location skill, 0 if it's an object skill.
  318. #
  319. # Determines whether a skill is a skill that's casted on a location, or one
  320. # that's casted on an object (monster/player/etc).
  321. # For example, Firewall is a location skill, while Cold Bolt is an object
  322. # skill.
  323. sub ai_getSkillUseType {
  324. my $skill = shift;
  325. return 1 if $skillsArea{$skill} == 1;
  326. return 0;
  327. }
  328. sub ai_mapRoute_searchStep {
  329. my $r_args = shift;
  330. unless ($r_args->{openlist} && %{$r_args->{openlist}}) {
  331. $r_args->{done} = 1;
  332. $r_args->{found} = '';
  333. return 0;
  334. }
  335. my $parent = (sort {$$r_args{'openlist'}{$a}{'walk'} <=> $$r_args{'openlist'}{$b}{'walk'}} keys %{$$r_args{'openlist'}})[0];
  336. debug "$parent, $$r_args{'openlist'}{$parent}{'walk'}n", "route/path";
  337. # Uncomment this if you want minimum MAP count. Otherwise use the above for minimum step count
  338. #foreach my $parent (keys %{$$r_args{'openlist'}})
  339. {
  340. my ($portal,$dest) = split /=/, $parent;
  341. if ($$r_args{'budget'} ne '' && $$r_args{'openlist'}{$parent}{'zenny'} > $$r_args{'budget'}) {
  342. #This link is too expensive
  343. delete $$r_args{'openlist'}{$parent};
  344. next;
  345. } else {
  346. #MOVE this entry into the CLOSELIST
  347. $$r_args{'closelist'}{$parent}{'walk'}   = $$r_args{'openlist'}{$parent}{'walk'};
  348. $$r_args{'closelist'}{$parent}{'zenny'}  = $$r_args{'openlist'}{$parent}{'zenny'};
  349. $$r_args{'closelist'}{$parent}{'parent'} = $$r_args{'openlist'}{$parent}{'parent'};
  350. #Then delete in from OPENLIST
  351. delete $$r_args{'openlist'}{$parent};
  352. }
  353. if ($portals_lut{$portal}{'dest'}{$dest}{'map'} eq $$r_args{'dest'}{'map'}) {
  354. if ($$r_args{'dest'}{'pos'}{'x'} eq '' && $$r_args{'dest'}{'pos'}{'y'} eq '') {
  355. $$r_args{'found'} = $parent;
  356. $$r_args{'done'} = 1;
  357. undef @{$$r_args{'mapSolution'}};
  358. my $this = $$r_args{'found'};
  359. while ($this) {
  360. my %arg;
  361. $arg{'portal'} = $this;
  362. my ($from,$to) = split /=/, $this;
  363. ($arg{'map'},$arg{'pos'}{'x'},$arg{'pos'}{'y'}) = split / /,$from;
  364. ($arg{dest_map}, $arg{dest_pos}{x}, $arg{dest_pos}{y}) = split(' ', $to);
  365. $arg{'walk'} = $$r_args{'closelist'}{$this}{'walk'};
  366. $arg{'zenny'} = $$r_args{'closelist'}{$this}{'zenny'};
  367. $arg{'steps'} = $portals_lut{$from}{'dest'}{$to}{'steps'};
  368. unshift @{$$r_args{'mapSolution'}},%arg;
  369. $this = $$r_args{'closelist'}{$this}{'parent'};
  370. }
  371. return;
  372. } elsif ( ai_route_getRoute(@{$$r_args{'solution'}}, $$r_args{'dest'}{'field'}, $portals_lut{$portal}{'dest'}{$dest}, $$r_args{'dest'}{'pos'}) ) {
  373. my $walk = "$$r_args{'dest'}{'map'} $$r_args{'dest'}{'pos'}{'x'} $$r_args{'dest'}{'pos'}{'y'}=$$r_args{'dest'}{'map'} $$r_args{'dest'}{'pos'}{'x'} $$r_args{'dest'}{'pos'}{'y'}";
  374. $$r_args{'closelist'}{$walk}{'walk'} = scalar @{$$r_args{'solution'}} + $$r_args{'closelist'}{$parent}{$dest}{'walk'};
  375. $$r_args{'closelist'}{$walk}{'parent'} = $parent;
  376. $$r_args{'closelist'}{$walk}{'zenny'} = $$r_args{'closelist'}{$parent}{'zenny'};
  377. $$r_args{'found'} = $walk;
  378. $$r_args{'done'} = 1;
  379. undef @{$$r_args{'mapSolution'}};
  380. my $this = $$r_args{'found'};
  381. while ($this) {
  382. my %arg;
  383. $arg{'portal'} = $this;
  384. my ($from,$to) = split /=/, $this;
  385. ($arg{'map'},$arg{'pos'}{'x'},$arg{'pos'}{'y'}) = split / /,$from;
  386. $arg{'walk'} = $$r_args{'closelist'}{$this}{'walk'};
  387. $arg{'zenny'} = $$r_args{'closelist'}{$this}{'zenny'};
  388. $arg{'steps'} = $portals_lut{$from}{'dest'}{$to}{'steps'};
  389. unshift @{$$r_args{'mapSolution'}},%arg;
  390. $this = $$r_args{'closelist'}{$this}{'parent'};
  391. }
  392. return;
  393. }
  394. }
  395. #get all children of each openlist
  396. foreach my $child (keys %{$portals_los{$dest}}) {
  397. next unless $portals_los{$dest}{$child};
  398. foreach my $subchild (keys %{$portals_lut{$child}{'dest'}}) {
  399. my $destID = $subchild;
  400. my $mapName = $portals_lut{$child}{'source'}{'map'};
  401. #############################################################
  402. my $penalty = int($routeWeights{lc($mapName)}) + int(($portals_lut{$child}{'dest'}{$subchild}{'steps'} ne '') ? $routeWeights{'NPC'} : $routeWeights{'PORTAL'});
  403. my $thisWalk = $penalty + $$r_args{'closelist'}{$parent}{'walk'} + $portals_los{$dest}{$child};
  404. if (!exists $$r_args{'closelist'}{"$child=$subchild"}) {
  405. if ( !exists $$r_args{'openlist'}{"$child=$subchild"} || $$r_args{'openlist'}{"$child=$subchild"}{'walk'} > $thisWalk ) {
  406. $$r_args{'openlist'}{"$child=$subchild"}{'parent'} = $parent;
  407. $$r_args{'openlist'}{"$child=$subchild"}{'walk'} = $thisWalk;
  408. $$r_args{'openlist'}{"$child=$subchild"}{'zenny'} = $$r_args{'closelist'}{$parent}{'zenny'} + $portals_lut{$child}{'dest'}{$subchild}{'cost'};
  409. }
  410. }
  411. }
  412. }
  413. }
  414. }
  415. sub ai_items_take {
  416. my ($x1, $y1, $x2, $y2) = @_;
  417. my %args;
  418. $args{pos}{x} = $x1;
  419. $args{pos}{y} = $y1;
  420. $args{pos_to}{x} = $x2;
  421. $args{pos_to}{y} = $y2;
  422. $args{ai_items_take_end}{time} = time;
  423. $args{ai_items_take_end}{timeout} = $timeout{ai_items_take_end}{timeout};
  424. $args{ai_items_take_start}{time} = time;
  425. $args{ai_items_take_start}{timeout} = $timeout{ai_items_take_start}{timeout};
  426. $args{ai_items_take_delay}{timeout} = $timeout{ai_items_take_delay}{timeout};
  427. AI::queue("items_take", %args);
  428. }
  429. sub ai_route {
  430. my $map = shift;
  431. my $x = shift;
  432. my $y = shift;
  433. my %args = @_;
  434. debug "On route to: $maps_lut{$map.'.rsw'}($map): $x, $yn", "route";
  435. # I can't use 'use' because of circular dependencies.
  436. require Task::Route;
  437. require Task::MapRoute;
  438. my $task;
  439. my @params = (
  440. x => $x,
  441. y => $y,
  442. maxDistance => $args{maxRouteDistance},
  443. maxTime => $args{maxRouteTime},
  444. distFromGoal => $args{distFromGoal},
  445. pyDistFromGoal => $args{pyDistFromGoal},
  446. avoidWalls => !$args{noAvoidWalls},
  447. notifyUponArrival => $args{notifyUponArrival}
  448. );
  449. if ($args{noMapRoute}) {
  450. $task = new Task::Route(@params);
  451. } else {
  452. $task = new Task::MapRoute(map => $map, @params);
  453. }
  454. $task->{attackID} = $args{attackID};
  455. $task->{attackOnRoute} = $args{attackOnRoute};
  456. $task->{noSitAuto} = $args{noSitAuto};
  457. $task->{LOSSubRoute} = $args{LOSSubRoute};
  458. AI::queue("route", $task);
  459. }
  460. #sellAuto for items_control - chobit andy 20030210
  461. sub ai_sellAutoCheck {
  462. foreach my $item (@{$char->inventory->getItems()}) {
  463. next if ($item->{equipped});
  464. my $control = Misc::items_control($item->{name});
  465. if ($control->{sell} && $item->{amount} > $control->{keep}) {
  466. return 1;
  467. }
  468. }
  469. }
  470. sub ai_setMapChanged {
  471. my $index = shift;
  472. $index = 0 if ($index eq "");
  473. if ($index < @ai_seq_args) {
  474. $ai_seq_args[$index]{'mapChanged'} = time;
  475. }
  476. }
  477. sub ai_setSuspend {
  478. my $index = shift;
  479. $index = 0 if ($index eq "");
  480. if ($index < @ai_seq_args) {
  481. $ai_seq_args[$index]{'suspended'} = time;
  482. }
  483. }
  484. sub ai_skillUse {
  485. return if ($char->{muted});
  486. my %args = (
  487. skillHandle => shift,
  488. lv => shift,
  489. maxCastTime => { time => time, timeout => shift },
  490. minCastTime => { time => time, timeout => shift },
  491. target => shift,
  492. y => shift,
  493. tag => shift,
  494. ret => shift,
  495. waitBeforeUse => { time => time, timeout => shift },
  496. prefix => shift
  497. );
  498. $args{giveup}{time} = time;
  499. $args{giveup}{timeout} = $timeout{ai_skill_use_giveup}{timeout};
  500. if ($args{y} ne "") {
  501. $args{x} = $args{target};
  502. delete $args{target};
  503. }
  504. AI::queue("skill_use", %args);
  505. }
  506. ##
  507. # ai_skillUse2($skill, $lvl, $maxCastTime, $minCastTime, $target)
  508. #
  509. # Calls ai_skillUse(), resolving $target to ($x, $y) if $skillID is an
  510. # area skill.
  511. #
  512. # FIXME: All code of the following structure:
  513. #
  514. # if (!ai_getSkillUseType(...)) {
  515. #     ai_skillUse(..., $ID);
  516. # } else {
  517. #     ai_skillUse(..., $x, $y);
  518. # }
  519. #
  520. # should be converted to use this helper function. Note that this
  521. # function uses objects instead of IDs for the skill and target.
  522. sub ai_skillUse2 {
  523. my ($skill, $lvl, $maxCastTime, $minCastTime, $target, $prefix) = @_;
  524. if (!ai_getSkillUseType($skill->getHandle())) {
  525. ai_skillUse($skill->getHandle(), $lvl, $maxCastTime, $minCastTime, $target->{ID}, undef, undef, undef, undef, $prefix);
  526. } else {
  527. ai_skillUse($skill->getHandle(), $lvl, $maxCastTime, $minCastTime, $target->{pos_to}{x}, $target->{pos_to}{y}, undef, undef, undef, $prefix);
  528. }
  529. }
  530. ##
  531. # ai_storageAutoCheck()
  532. #
  533. # Returns 1 if it is time to perform storageAuto sequence.
  534. # Returns 0 otherwise.
  535. sub ai_storageAutoCheck {
  536. return 0 if ($char->getSkillLevel(new Skill(handle => 'NV_BASIC')) < 6);
  537. if ($config{minStorageZeny}) {
  538. return 0 if ($char->{zenny} < $config{minStorageZeny});
  539. }
  540. foreach my $item (@{$char->inventory->getItems()}) {
  541. next if ($item->{equipped});
  542. my $control = Misc::items_control($item->{name});
  543. if ($control->{storage} && $item->{amount} > $control->{keep}) {
  544. return 1;
  545. }
  546. }
  547. return 0;
  548. }
  549. ##
  550. # cartGet(items)
  551. # items: a reference to an array of indices.
  552. #
  553. # Get one or more items from cart.
  554. # @items is a list of hashes; each has must have an "index" key, and may optionally have an "amount" key.
  555. # "index" is the index of the cart inventory item number. If "amount" is given, only the given amount of
  556. # items will retrieved from cart.
  557. #
  558. # Example:
  559. # # You want to get 5 Apples (inventory item 2) and all
  560. # # Fly Wings (inventory item 5) from cart.
  561. # my @items;
  562. # push @items, {index => 2, amount => 5};
  563. # push @items, {index => 5};
  564. # cartGet(@items);
  565. sub cartGet {
  566. my $items = shift;
  567. return unless ($items && @{$items});
  568. my %args;
  569. $args{items} = $items;
  570. $args{timeout} = $timeout{ai_cartAuto} ? $timeout{ai_cartAuto}{timeout} : 0.15;
  571. AI::queue("cartGet", %args);
  572. }
  573. ##
  574. # cartAdd(items)
  575. # items: a reference to an array of hashes.
  576. #
  577. # Put one or more items in cart.
  578. # @items is a list of hashes; each has must have an "index" key, and may optionally have an "amount" key.
  579. # "index" is the index of the inventory item number. If "amount" is given, only the given amount of items will be put in cart.
  580. #
  581. # Example:
  582. # # You want to add 5 Apples (inventory item 2) and all
  583. # # Fly Wings (inventory item 5) to cart.
  584. # my @items;
  585. # push @items, {index => 2, amount => 5};
  586. # push @items, {index => 5};
  587. # cartAdd(@items);
  588. sub cartAdd {
  589. my $items = shift;
  590. return unless ($items && @{$items});
  591. my %args;
  592. $args{items} = $items;
  593. $args{timeout} = $timeout{ai_cartAuto} ? $timeout{ai_cartAuto}{timeout} : 0.15;
  594. AI::queue("cartAdd", %args);
  595. }
  596. ##
  597. # ai_talkNPC(x, y, sequence)
  598. # x, y: the position of the NPC to talk to.
  599. # sequence: A string containing the NPC talk sequences.
  600. #
  601. # Talks to an NPC. You can specify an NPC position, or an NPC ID.
  602. #
  603. # $sequence is a list of whitespace-separated commands:
  604. # ~l
  605. # c       : Continue
  606. # r#      : Select option # from menu.
  607. # n       : Stop talking to NPC.
  608. # b       : Send the "Show shop item list" (Buy) packet.
  609. # w#      : Wait # seconds.
  610. # x       : Initialize conversation with NPC. Useful to perform multiple transaction with a single NPC.
  611. # t="str" : send the text str to NPC, double quote is needed only if the string contains space
  612. # ~l~
  613. #
  614. # Example:
  615. # # Sends "Continue", "Select option 0" to the NPC at (102, 300)
  616. # ai_talkNPC(102, 300, "c r0");
  617. sub ai_talkNPC {
  618. my %args;
  619. $args{'pos'}{'x'} = shift;
  620. $args{'pos'}{'y'} = shift;
  621. $args{'sequence'} = shift;
  622. $args{'sequence'} =~ s/^ +| +$//g;
  623. AI::queue("NPC", %args);
  624. }
  625. sub attack {
  626. my $ID = shift;
  627. my %args;
  628. my $target = Actor::get($ID);
  629. return if (!$target->{'pos_to'} || ! $target->{'pos'});
  630. $args{'ai_attack_giveup'}{'time'} = time;
  631. $args{'ai_attack_giveup'}{'timeout'} = $timeout{'ai_attack_giveup'}{'timeout'};
  632. $args{'ID'} = $ID;
  633. $args{'unstuck'}{'timeout'} = ($timeout{'ai_attack_unstuck'}{'timeout'} || 1.5);
  634. %{$args{'pos_to'}} = %{$target->{'pos_to'}};
  635. %{$args{'pos'}} = %{$target->{'pos'}};
  636. AI::queue("attack", %args);
  637. message TF("Attacking: %sn", $target);
  638. $startedattack = 1;
  639. Plugins::callHook('attack_start', {ID => $ID});
  640. #Mod Start
  641. AUTOEQUIP: {
  642. last AUTOEQUIP if (UNIVERSAL::isa($target, 'Actor::Player'));
  643. my $i = 0;
  644. my $Lequip = 0;
  645. my $Runeq =0;
  646. my (%eq_list,$Req,$Leq,$arrow,$j);
  647. while (exists $config{"autoSwitch_$i"}) {
  648. if (!$config{"autoSwitch_$i"}) {
  649. $i++;
  650. next;
  651. }
  652. if (existsInList($config{"autoSwitch_$i"}, $monsters{$ID}{'name'})) {
  653. message TF("Encounter Monster : %sn", $monsters{$ID}{'name'});
  654. if ($config{"autoSwitch_$i"."_rightHand"}) {
  655. if ($config{"autoSwitch_$i"."_rightHand"} eq "[NONE]" && $char->{equipment}{'rightHand'}) {
  656. $Runeq = 1;
  657. message TF("Auto UnEquiping [R]: %sn", $config{"autoSwitch_$i"."_rightHand"}), "equip";
  658. $char->{equipment}{'rightHand'}->unequip();
  659. }
  660. $Req = $char->inventory->getByName($config{"autoSwitch_${i}_rightHand"});
  661. if ($Req && !$Req->{equipped}){
  662. message TF("Auto Equiping [R]: %sn", $config{"autoSwitch_$i"."_rightHand"}), "equip";
  663. %eq_list = (rightHand => $Req->{invIndex});
  664. }
  665. }
  666. if ($config{"autoSwitch_${i}_leftHand"}) {
  667. if ($config{"autoSwitch_${i}_leftHand"} eq "[NONE]" && $char->{equipment}{leftHand}) {
  668. if (!($Runeq && $char->{equipment}{rightHand} == $char->{equipment}{leftHand})) {
  669. message TF("Auto UnEquiping [L]: %sn", $config{"autoSwitch_${i}_rightHand"}), "equip";
  670. $char->{equipment}{leftHand}->unequip();
  671. }
  672. }
  673. $Leq = $char->inventory->getByName($config{"autoSwitch_${i}_leftHand"});
  674. if ($Leq && !$Leq->{equipped}) {
  675. if ($Req == $Leq) {
  676. undef $Leq;
  677. foreach my $item (@{$char->inventory->getItems()}) {
  678. if ($item->{name} eq $config{"autoSwitch_${i}_leftHand"} && $item != $Req) {
  679. $Leq = $item;
  680. last;
  681. }
  682. }
  683. }
  684. if ($Leq) {
  685. message TF("Auto Equiping [L]: %s (%s)n", $config{"autoSwitch_$i"."_leftHand"}, $Leq), "equip";
  686. $eq_list{leftHand} = $Leq->{invIndex};
  687. }
  688. }
  689. }
  690. if (%eq_list) {
  691. Actor::Item::bulkEquip(%eq_list);
  692. }
  693. $arrow = $char->inventory->getByName($config{"autoSwitch_${i}_arrow"}) if ($config{"autoSwitch_${i}_arrow"});
  694. if ($arrow && !$arrow->{equipped}) {
  695. message TF("Auto Equiping [A]: %sn", $config{"autoSwitch_$i"."_arrow"}), "equip";
  696. $arrow->equip();
  697. }
  698. if ($config{"autoSwitch_$i"."_distance"} && $config{"autoSwitch_$i"."_distance"} != $config{'attackDistance'}) {
  699. $ai_v{'attackDistance'} = $config{'attackDistance'};
  700. $config{'attackDistance'} = $config{"autoSwitch_$i"."_distance"};
  701. message TF("Change Attack Distance to : %sn", $config{'attackDistance'}), "equip";
  702. }
  703. if ($config{"autoSwitch_$i"."_useWeapon"} ne "") {
  704. $ai_v{'attackUseWeapon'} = $config{'attackUseWeapon'};
  705. $config{'attackUseWeapon'} = $config{"autoSwitch_$i"."_useWeapon"};
  706. message TF("Change Attack useWeapon to : %sn", $config{'attackUseWeapon'}), "equip";
  707. }
  708. last AUTOEQUIP;
  709. }
  710. $i++;
  711. }
  712. undef $Leq;
  713. undef $Req;
  714. if ($config{"autoSwitch_default_rightHand"}) {
  715. if ($config{"autoSwitch_default_rightHand"} eq "[NONE]" && $char->{equipment}{'rightHand'}) {
  716. $Runeq = 1;
  717. message TF("Auto UnEquiping [R]: %sn", $config{"autoSwitch_default_rightHand"}), "equip";
  718. $char->{equipment}{'rightHand'}->unequip();
  719. }
  720. $Req = $char->inventory->getByName($config{"autoSwitch_default_rightHand"});
  721. if ($Req && !$Req->{equipped}){
  722. message TF("Auto Equiping [R]: %sn", $config{"autoSwitch_default_rightHand"}), "equip";
  723. %eq_list = (rightHand => $Req->{invIndex});
  724. }
  725. }
  726. if ($config{"autoSwitch_default_leftHand"}) {
  727. if ($config{"autoSwitch_default_leftHand"} eq "[NONE]" && $char->{equipment}{'leftHand'}) {
  728. if (!($Runeq && $char->{equipment}{'rightHand'} == $char->{equipment}{'leftHand'})) {
  729. message TF("Auto UnEquiping [L]: %sn", $config{"autoSwitch_default_leftHand"}), "equip";
  730. $char->{equipment}{'leftHand'}->unequip();
  731. }
  732. }
  733. $Leq = $char->inventory->getByName($config{"autoSwitch_default_leftHand"});
  734. if ($Leq && !$Leq->{equipped}) {
  735. if ($Req == $Leq) {
  736. undef $Leq;
  737. foreach my $item (@{$char->inventory->getItems()}) {
  738. if ($item->{name} eq $config{"autoSwitch_default_leftHand"} && $item != $Req) {
  739. $Leq = $item;
  740. last;
  741. }
  742. }
  743. }
  744. if ($Leq) {
  745. message TF("Auto Equiping [L]: %sn", $config{"autoSwitch_default_leftHand"}), "equip";
  746. $eq_list{leftHand} = $Leq->{invIndex};
  747. }
  748. }
  749. }
  750. if (%eq_list) {
  751. Actor::Item::bulkEquip(%eq_list);
  752. }
  753. if ($config{'autoSwitch_default_arrow'}) {
  754. $arrow = $char->inventory->getByName($config{"autoSwitch_default_arrow"});
  755. if ($arrow && !$arrow->{equipped}) {
  756. message TF("Auto equiping default [A]: %sn", $config{'autoSwitch_default_arrow'}), "equip";
  757. $arrow->equip();
  758. }
  759. }
  760. if ($ai_v{'attackDistance'} && $config{'attackDistance'} != $ai_v{'attackDistance'}) {
  761. $config{'attackDistance'} = $ai_v{'attackDistance'};
  762. message TF("Change Attack Distance to Default : %sn", $config{'attackDistance'}), "equip";
  763. }
  764. if ($ai_v{'attackUseWeapon'} ne "" && $config{'attackUseWeapon'} != $ai_v{'attackUseWeapon'}) {
  765. $config{'attackUseWeapon'} = $ai_v{'attackUseWeapon'};
  766. message TF("Change Attack useWeapon to default : %sn", $config{'attackUseWeapon'}), "equip";
  767. }
  768. } #END OF BLOCK AUTOEQUIP
  769. }
  770. sub gather {
  771. my $ID = shift;
  772. my %args;
  773. $args{ai_items_gather_giveup}{time} = time;
  774. $args{ai_items_gather_giveup}{timeout} = $timeout{ai_items_gather_giveup}{timeout};
  775. $args{ID} = $ID;
  776. $args{pos} = { %{$items{$ID}{pos}} };
  777. AI::queue("items_gather", %args);
  778. debug "Targeting for Gather: $items{$ID}{name} ($items{$ID}{binID})n";
  779. }
  780. sub move {
  781. my $x = shift;
  782. my $y = shift;
  783. my $attackID = shift;
  784. my %args;
  785. my $dist;
  786. $args{move_to}{x} = $x;
  787. $args{move_to}{y} = $y;
  788. $args{attackID} = $attackID;
  789. $args{time_move} = $char->{time_move};
  790. $dist = distance($char->{pos}, $args{move_to});
  791. $args{ai_move_giveup}{timeout} = $timeout{ai_move_giveup}{timeout};
  792. if ($x == 0 && $y == 0) {
  793. # die "BUG: move(0, 0) called!n";
  794. error "BUG: move(0, 0) called!n";
  795. return;
  796. }
  797. debug sprintf("Sending move from (%d,%d) to (%d,%d) - distance %.2fn",
  798. $char->{pos}{x}, $char->{pos}{y}, $x, $y, $dist), "ai_move";
  799. AI::queue("move", %args);
  800. }
  801. sub sit {
  802. require Task::SitStand;
  803. my $task = new Task::SitStand(mode => 'sit', wait => $timeout{ai_sit_wait}{timeout});
  804. AI::queue("sitting", $task);
  805. if (defined $config{sitAuto_look} && !$config{sitAuto_look_from_wall}) {
  806. Misc::look($config{sitAuto_look});
  807. } elsif (defined $config{sitAuto_look} && $config{sitAuto_look_from_wall}) {
  808. my $sitAutoLook = $config{sitAuto_look};
  809. my $wallRange = $config{sitAuto_look_from_wall};
  810. for (my $i=1;$i<=$wallRange;$i++) {
  811. if ((!$field->isWalkable($char->{pos}{x},$char->{pos}{y}+$wallRange) && $sitAutoLook == 0)
  812.   || (!$field->isWalkable($char->{pos}{x}-$wallRange,$char->{pos}{y}+$wallRange) && $sitAutoLook == 1)
  813.   || (!$field->isWalkable($char->{pos}{x}-$wallRange,$char->{pos}{y}) && $sitAutoLook == 2)
  814.   || (!$field->isWalkable($char->{pos}{x}-$wallRange,$char->{pos}{y}-$wallRange) && $sitAutoLook == 3)
  815.   ) {
  816. $sitAutoLook += 4;
  817. } elsif ((!$field->isWalkable($char->{pos}{x},$char->{pos}{y}-$wallRange) && $sitAutoLook == 4)
  818.   || (!$field->isWalkable($char->{pos}{x}+$wallRange,$char->{pos}{y}-$wallRange) && $sitAutoLook == 5)
  819.   || (!$field->isWalkable($char->{pos}{x}+$wallRange,$char->{pos}{y}) && $sitAutoLook == 6)
  820.   || (!$field->isWalkable($char->{pos}{x}+$wallRange,$char->{pos}{y}+$wallRange) && $sitAutoLook == 7)
  821.   ) {
  822. $sitAutoLook -= 4;
  823. }
  824. }
  825. Misc::look($sitAutoLook);
  826. }
  827. }
  828. sub stand {
  829. require Task::SitStand;
  830. my $task = new Task::SitStand(mode => 'stand', wait => $timeout{ai_stand_wait}{timeout});
  831. AI::queue("standing", $task);
  832. }
  833. sub take {
  834. my $ID = shift;
  835. my %args;
  836. return if (!$items{$ID});
  837. $args{ai_take_giveup}{time} = time;
  838. $args{ai_take_giveup}{timeout} = $timeout{ai_take_giveup}{timeout};
  839. $args{ID} = $ID;
  840. $args{pos} = {%{$items{$ID}{pos}}};
  841. AI::queue("take", %args);
  842. debug "Picking up: $items{$ID}{name} ($items{$ID}{binID})n";
  843. }
  844. return 1;