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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Long intra-map movement task
  3. #  Copyright (c) 2006 OpenKore Team
  4. #
  5. #  This software is open source, licensed under the GNU General Public
  6. #  License, version 2.
  7. #  Basically, this means that you're allowed to modify and distribute
  8. #  this software. However, if you distribute modified versions, you MUST
  9. #  also distribute the source code.
  10. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  11. #########################################################################
  12. ##
  13. # MODULE DESCRIPTION: Long intra-map movement task.
  14. #
  15. # This task is able to move long distances within the same map. Unlike
  16. # the Move task, this task can walk to destinations which are outside the
  17. # character's screen.
  18. package Task::Route;
  19. use strict;
  20. use Time::HiRes qw(time);
  21. use Scalar::Util;
  22. use Carp::Assert;
  23. use Modules 'register';
  24. use Task::WithSubtask;
  25. use base qw(Task::WithSubtask);
  26. use Task::Move;
  27. use Globals qw($char $field $net %config);
  28. use Log qw(message debug warning);
  29. use Network;
  30. use Field;
  31. use Translation qw(T TF);
  32. use Misc;
  33. use Utils qw(timeOut distance calcPosition);
  34. use Utils::Exceptions;
  35. use Utils::Set;
  36. use Utils::PathFinding;
  37. # Error code constants.
  38. use enum qw(
  39. TOO_MUCH_TIME
  40. CANNOT_CALCULATE_ROUTE
  41. STUCK
  42. UNEXPECTED_STATE
  43. );
  44. # TODO: Add Homunculus support
  45. ##
  46. # Task::Route->new(options...)
  47. #
  48. # Create a new Task::Route object. The following options are allowed:
  49. # `l
  50. # - All options allowed by Task::WithSubtask->new(), except 'mutexes', 'autostop' and 'autofail'.
  51. # - x (required) - The X-coordinate that you want to move to.
  52. # - y (required) - The Y-coordinate that you want to move to.
  53. # - maxDistance - The maximum distance (in blocks) that the route may be. If
  54. #                 not specified, then there is no limit.
  55. # - maxTime - The maximum time that may be spent on walking the route. If not
  56. #             specified, then there is no time limit.
  57. # - distFromGoal - Stop walking if we're within the specified distance (in blocks)
  58. #                  from the goal. If not specified, then we'll walk until the
  59. #                  destination is reached.
  60. # - pyDistFromGoal - Same as distFromGoal, but this allows you to specify the
  61. #                    Pythagorian distance instead of block distance.
  62. # - avoidWalls - Whether to avoid walls. The default is yes.
  63. # - notifyUponArrival - Whether to print a message when we've reached the destination.
  64. #                       The default is no.
  65. # `l`
  66. #
  67. # x and y may not be 0 or undef. Otherwise, an ArgumentException will be thrown.
  68. sub new {
  69. my $class = shift;
  70. my %args = @_;
  71. my $self = $class->SUPER::new(@_, autostop => 1, autofail => 0, mutexes => ['movement']);
  72. if ($args{x} == 0 || $args{y} == 0) {
  73. ArgumentException->throw(error => "Invalid arguments.");
  74. }
  75. my $allowed = new Set('maxDistance', 'maxTime', 'distFromGoal', 'pyDistFromGoal',
  76. 'avoidWalls', 'notifyUponArrival');
  77. foreach my $key (keys %args) {
  78. if ($allowed->has($key) && defined($args{$key})) {
  79. $self->{$key} = $args{$key};
  80. }
  81. }
  82. $self->{dest}{map} = $field->name();
  83. $self->{dest}{pos}{x} = $args{x};
  84. $self->{dest}{pos}{y} = $args{y};
  85. if ($config{'route_avoidWalls'}) {
  86. $self->{avoidWalls} = 1 if (!defined $self->{avoidWalls});
  87. } else {$self->{avoidWalls} = 0;}
  88. $self->{solution} = [];
  89. $self->{stage} = '';
  90. # Watch for map change events. Pass a weak reference to ourselves in order
  91. # to avoid circular references (memory leaks).
  92. my @holder = ($self);
  93. Scalar::Util::weaken($holder[0]);
  94. $self->{mapChangedHook} = Plugins::addHook('Network::Receive::map_changed', &mapChanged, @holder);
  95. return $self;
  96. }
  97. sub DESTROY {
  98. my ($self) = @_;
  99. Plugins::delHook($self->{mapChangedHook});
  100. $self->SUPER::DESTROY();
  101. }
  102. ##
  103. # Hash* $Task_Route->destCoords()
  104. #
  105. # Returns the destination coordinates. The result is a hash with the items 'x' and 'y'.
  106. sub destCoords {
  107. return $_[0]->{dest}{pos};
  108. }
  109. # Overrided method.
  110. sub activate {
  111. my ($self) = @_;
  112. $self->SUPER::activate();
  113. $self->{time_start} = time;
  114. }
  115. # Overrided method.
  116. sub interrupt {
  117. my ($self) = @_;
  118. $self->SUPER::interrupt();
  119. $self->{interruptionTime} = time;
  120. }
  121. # Overrided method.
  122. sub resume {
  123. my ($self) = @_;
  124. $self->SUPER::resume();
  125. $self->{time_start} += time - $self->{interruptionTime};
  126. $self->{time_step} += time - $self->{interruptionTime};
  127. }
  128. # Overrided method.
  129. sub iterate {
  130. my ($self) = @_;
  131. return unless ($self->SUPER::iterate() && $net->getState() == Network::IN_GAME);
  132. return unless ($field && defined($char->{pos_to}{x}) && defined($char->{pos_to}{y}));
  133. if ( $self->{maxTime} && timeOut($self->{time_start}, $self->{maxTime})) {
  134. # We spent too much time
  135. debug "Route - we spent too much time; bailing out.n", "route";
  136. $self->setError(TOO_MUCH_TIME, "Too much time spent on walking.");
  137. } elsif ($field->name() ne $self->{dest}{map} || $self->{mapChanged}) {
  138. debug "Map changed: " . $field->name() . " $self->{dest}{map}n", "route";
  139. $self->setDone();
  140. } elsif ($self->{stage} eq '') {
  141. my $pos = calcPosition($char);
  142. my $begin = time;
  143. if ($self->getRoute($self->{solution}, $field, $pos, $self->{dest}{pos}, $self->{avoidWalls})) {
  144. $self->{stage} = 'Route Solution Ready';
  145. debug "Route Solution Ready!n", "route";
  146. if (time - $begin < 0.01) {
  147. # Optimization: immediately go to the next stage if we
  148. # spent neglible time in this step.
  149. $self->iterate();
  150. }
  151. } else {
  152. debug "Something's wrong; there is no path to " . $field->name() . "($self->{dest}{pos}{x},$self->{dest}{pos}{y}).n", "debug";
  153. $self->setError(CANNOT_CALCULATE_ROUTE, "Unable to calculate a route.");
  154. }
  155. } elsif ($self->{stage} eq 'Route Solution Ready') {
  156. my $begin = time;
  157. my $solution = $self->{solution};
  158. if ($self->{maxDistance} > 0 && $self->{maxDistance} < 1) {
  159. # Fractional route motion
  160. $self->{maxDistance} = int($self->{maxDistance} * scalar(@{$solution}));
  161. }
  162. if ($self->{maxDistance} && $self->{maxDistance} < @{$solution}) {
  163. splice(@{$solution}, 1 + $self->{maxDistance});
  164. }
  165. # Trim down solution tree for pyDistFromGoal or distFromGoal
  166. if ($self->{pyDistFromGoal}) {
  167. my $trimsteps = 0;
  168. $trimsteps++ while ($trimsteps < @{$solution}
  169. && distance($solution->[@{$solution} - 1 - $trimsteps], $solution->[@{$solution} - 1]) < $self->{pyDistFromGoal}
  170. );
  171. debug "Route - trimming down solution by $trimsteps steps for pyDistFromGoal $self->{pyDistFromGoal}n", "route";
  172. splice(@{$self->{'solution'}}, -$trimsteps) if ($trimsteps);
  173. } elsif ($self->{distFromGoal}) {
  174. my $trimsteps = $self->{distFromGoal};
  175. $trimsteps = @{$self->{solution}} if ($trimsteps > @{$self->{solution}});
  176. debug "Route - trimming down solution by $trimsteps steps for distFromGoal $self->{distFromGoal}n", "route";
  177. splice(@{$self->{solution}}, -$trimsteps) if ($trimsteps);
  178. }
  179. undef $self->{mapChanged};
  180. undef $self->{index};
  181. undef $self->{old_x};
  182. undef $self->{old_y};
  183. undef $self->{new_x};
  184. undef $self->{new_y};
  185. $self->{time_step} = time;
  186. $self->{stage} = 'Walk the Route Solution';
  187. if (time - $begin < 0.01) {
  188. # Optimization: immediately go to the next stage if we
  189. # spent neglible time in this step.
  190. $self->iterate();
  191. }
  192. } elsif ($self->{stage} eq 'Walk the Route Solution') {
  193. my $pos = calcPosition($char);
  194. my ($cur_x, $cur_y) = ($pos->{x}, $pos->{y});
  195. if (@{$self->{solution}} == 0) {
  196. # No more points to cover; we've arrived at the destination
  197. if ($self->{notifyUponArrival}) {
  198. message T("Destination reached.n"), "success";
  199. } else {
  200. debug "Destination reached.n", "route";
  201. }
  202. $self->setDone();
  203. } elsif ($self->{old_x} == $cur_x && $self->{old_y} == $cur_y && timeOut($self->{time_step}, 3)) {
  204. # We tried to move for 3 seconds, but we are still on the same spot,
  205. # decrease step size.
  206. # However, if $self->{index} was already 0, then that means
  207. # we were almost at the destination (only 1 more step is needed).
  208. # But we got interrupted (by auto-attack for example). Don't count that
  209. # as stuck.
  210. my $wasZero = $self->{index} == 0;
  211. $self->{index} = int($self->{index} * 0.8);
  212. if ($self->{index}) {
  213. debug "Route - not moving, decreasing step size to $self->{index}n", "route";
  214. if (@{$self->{solution}}) {
  215. # If we still have more points to cover, walk to next point
  216. $self->{index} = @{$self->{solution}} - 1 if $self->{index} >= @{$self->{solution}};
  217. $self->{new_x} = $self->{solution}[$self->{index}]{x};
  218. $self->{new_y} = $self->{solution}[$self->{index}]{y};
  219. $self->{time_step} = time;
  220. my $task = new Task::Move(
  221. x => $self->{new_x},
  222. y => $self->{new_y});
  223. $self->setSubtask($task);
  224. }
  225. } elsif (!$wasZero) {
  226. # FIXME: this code looks ugly!
  227. # We're stuck
  228. my $msg = TF("Stuck at %s (%d,%d), while walking from (%d,%d) to (%d,%d).",
  229. $field->name(), $char->{pos_to}{x}, $char->{pos_to}{y},
  230. $cur_x, $cur_y, $self->{dest}{pos}{x}, $self->{dest}{pos}{y});
  231. $msg .= T(" Teleporting to unstuck.") if ($config{teleportAuto_unstuck});
  232. $msg .= "n";
  233. warning $msg, "route";
  234. Misc::useTeleport(1) if $config{teleportAuto_unstuck};
  235. $self->setError(STUCK, T("Stuck during route."));
  236. } else {
  237. $self->{time_step} = time;
  238. }
  239. } else {
  240. # We're either starting to move or already moving, so send out more
  241. # move commands periodically to keep moving and updating our position
  242. my $begin = time;
  243. my $solution = $self->{solution};
  244. $self->{index} = $config{route_step} unless $self->{index};
  245. $self->{index}++ if (($self->{index} < $config{route_step})
  246.   && ($self->{old_x} != $cur_x || $self->{old_y} != $cur_y));
  247. if (defined($self->{old_x}) && defined($self->{old_y})) {
  248. # See how far we've walked since the last move command and
  249. # trim down the soultion tree by this distance.
  250. # Only remove the last step if we reached the destination
  251. my $trimsteps = 0;
  252. # If position has changed, we must have walked at least one step
  253. $trimsteps++ if ($cur_x != $self->{'old_x'} || $cur_y != $self->{'old_y'});
  254. # Search the best matching entry for our position in the solution
  255. while ($trimsteps < @{$solution}
  256. && distance( { x => $cur_x, y => $cur_y }, $solution->[$trimsteps + 1])
  257. < distance( { x => $cur_x, y => $cur_y }, $solution->[$trimsteps])
  258. ) {
  259. $trimsteps++;
  260. }
  261. # Remove the last step also if we reached the destination
  262. $trimsteps = @{$solution} - 1 if ($trimsteps >= @{$solution});
  263. #$trimsteps = @{$solution} if ($trimsteps <= $self->{'index'} && $self->{'new_x'} == $cur_x && $self->{'new_y'} == $cur_y);
  264. $trimsteps = @{$solution} if ($cur_x == $solution->[$#{$solution}]{x} && $cur_y == $solution->[$#{$solution}]{y});
  265. debug "Route - trimming down solution (" . @{$solution} . ") by $trimsteps stepsn", "route";
  266. splice(@{$solution}, 0, $trimsteps) if ($trimsteps > 0);
  267. }
  268. my $stepsleft = @{$solution};
  269. if ($stepsleft > 0) {
  270. # If we still have more points to cover, walk to next point
  271. $self->{index} = $stepsleft - 1 if ($self->{index} >= $stepsleft);
  272. $self->{new_x} = $self->{solution}[$self->{index}]{x};
  273. $self->{new_y} = $self->{solution}[$self->{index}]{y};
  274. # But first, check whether the distance of the next point isn't abnormally large.
  275. # If it is, then we've moved to an unexpected place. This could be caused by auto-attack,
  276. # for example.
  277. my %nextPos = (x => $self->{new_x}, y => $self->{new_y});
  278. if (distance(%nextPos, $pos) > $config{route_step}) {
  279. debug "Route - movement interrupted: reset routen", "route";
  280. $self->{stage} = '';
  281. } else {
  282. $self->{old_x} = $cur_x;
  283. $self->{old_y} = $cur_y;
  284. $self->{time_step} = time if ($cur_x != $self->{old_x} || $cur_y != $self->{old_y});
  285. debug "Route - next step moving to ($self->{new_x}, $self->{new_y}), index $self->{index}, $stepsleft steps leftn", "route";
  286. my $task = new Task::Move(
  287. x => $self->{new_x},
  288. y => $self->{new_y});
  289. $self->setSubtask($task);
  290. if (time - $begin < 0.01) {
  291. # Optimization: immediately begin moving, if we spent neglible
  292. # time in this step.
  293. $self->iterate();
  294. }
  295. }
  296. } else {
  297. # No more points to cover
  298. if ($self->{notifyUponArrival}) {
  299. message T("Destination reached.n"), "success";
  300. } else {
  301. debug "Destination reached.n", "route";
  302. }
  303. $self->setDone();
  304. }
  305. }
  306. } else {
  307. # This statement should never be reached.
  308. debug "Unexpected route stage [$self->{stage}] occured.n", "route";
  309. $self->setError(UNEXPECTED_STATE, "Unexpected route stage [$self->{stage}] occured.n");
  310. }
  311. }
  312. ##
  313. # boolean Task::Route->getRoute(Array* solution, Field field, Hash* start, Hash* dest, [boolean avoidWalls = true])
  314. # $solution: The route solution will be stored in here.
  315. # field: the field on which a route must be calculated.
  316. # start: The is the start coordinate.
  317. # dest: The destination coordinate.
  318. # noAvoidWalls: 1 if you don't want to avoid walls on route.
  319. # Returns: 1 if the calculation succeeded, 0 if not.
  320. #
  321. # Calculate how to walk from $start to $dest on field $field, or check whether there
  322. # is a path from $start to $dest on field $field.
  323. #
  324. # If $solution is given, then the blocks you have to walk on in order to get to $dest
  325. # are stored in there.
  326. #
  327. # This function is a convenience wrapper function for the stuff
  328. # in Utils/PathFinding.pm
  329. sub getRoute {
  330. my ($class, $solution, $field, $start, $dest, $avoidWalls) = @_;
  331. assert(UNIVERSAL::isa($field, 'Field')) if DEBUG;
  332. if (!defined $dest->{x} || $dest->{y} eq '') {
  333. @{$solution} = () if ($solution);
  334. return 1;
  335. }
  336. # The exact destination may not be a spot that we can walk on.
  337. # So we find a nearby spot that is walkable.
  338. my %start = %{$start};
  339. my %dest = %{$dest};
  340. Misc::closestWalkableSpot($field, %start);
  341. Misc::closestWalkableSpot($field, %dest);
  342. # Generate map weights (for wall avoidance)
  343. my $weights;
  344. if ($avoidWalls) {
  345. #$weights = join '', map chr $_, (255, 8, 7, 6, 5, 4, 3, 2, 1);
  346. $weights = join('', (map chr($_), (255, 7, 6, 3, 2, 1)));
  347. $weights .= chr(1) x (256 - length($weights));
  348. } else {
  349. $weights = chr(255) . (chr(1) x 255);
  350. }
  351. # Calculate path
  352. my $pathfinding = new PathFinding(
  353. start => %start,
  354. dest  => %dest,
  355. field => $field,
  356. weights => $weights
  357. );
  358. return undef if (!$pathfinding);
  359. my $ret;
  360. if ($solution) {
  361. $ret = $pathfinding->run($solution);
  362. } else {
  363. $ret = $pathfinding->runcount();
  364. }
  365. return $ret > 0;
  366. }
  367. sub mapChanged {
  368. my (undef, undef, $holder) = @_;
  369. my $self = $holder->[0];
  370. $self->{mapChanged} = 1;
  371. }
  372. 1;