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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Skill usage task
  3. #  Copyright (c) 2007 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: Skill usage task.
  14. #
  15. # This task is specialized in using a single skill. It will:
  16. # - Execute necessary preparation actions, such as standing up.
  17. # - Retry to use the skill if it doesn't start within a time limit.
  18. # - Handle errors gracefully.
  19. package Task::UseSkill;
  20. use strict;
  21. use Modules 'register';
  22. use Time::HiRes qw(time);
  23. use Scalar::Util;
  24. use Carp::Assert;
  25. use Task::WithSubtask;
  26. use base qw(Task::WithSubtask);
  27. use Task::Chained;
  28. use Task::Function;
  29. use Task::SitStand;
  30. use Globals qw($net $char %skillsArea $messageSender $accountID %timeout);
  31. use Network;
  32. use Plugins;
  33. use Skill;
  34. use Log qw(debug);
  35. use Translation qw(T TF);
  36. use Utils qw(timeOut);
  37. use Utils::Exceptions;
  38. # States
  39. use enum qw(
  40. PREPARING
  41. WAITING_FOR_CAST_TO_START
  42. WAITING_FOR_CAST_TO_FINISH
  43. );
  44. # Errors
  45. use enum qw(
  46. ERROR_PREPARATION_FAILED
  47. ERROR_TARGET_LOST
  48. ERROR_MAX_TRIES
  49. ERROR_CASTING_CANCELLED
  50. ERROR_CASTING_FAILED
  51. ERROR_CASTING_TIMEOUT
  52. ERROR_NO_SKILL
  53. );
  54. use constant {
  55. DEFAULT_MAX_CAST_TRIES => 3,
  56. DEFAULT_CAST_TIMEOUT   => 3
  57. };
  58. ##
  59. # Task::UseSkill->new(options...);
  60. #
  61. # Create a new Task::UseSkill object.
  62. #
  63. # The following options are allowed:
  64. # `l
  65. # - All options allowed for Task->new(), except 'mutexes'.
  66. # - skill (required) - A Skill object, which represents the skill to be used.
  67. #       The level property must be set. If not set, the maximum available level will
  68. #       be used.
  69. # - target - Specifies the target to use this skill on. If the skill is to be
  70. #       used on an actor (such as a monster), then this argument must be an
  71. #       Actor object. If the skill is to be used on a location (as is the case
  72. #       for area spells), then this argument must be a hash containing an 'x' and
  73. #       a 'y' item, which specifies the location.
  74. # - actorList - If _target_ is an Actor object, but not of the class 'Actor::You',
  75. #       then this argument must be set to the ActorList object which contains _target_.
  76. #       This is used to check whether the target actor is still on screen.
  77. # - stopWhenHit - Specifies whether you want to stop using this skill if casting has
  78. #       been cancelled because you've been hit. The default is true.
  79. # `l`
  80. sub new {
  81. my $class = shift;
  82. my %args = @_;
  83. my $self = $class->SUPER::new(@_, manageMutexes => 1, mutexes => ['movement', 'skill']);
  84. if (!$args{skill}) {
  85. ArgumentException->throw("No skill argument given.");
  86. }
  87. $self->{skill} = $args{skill};
  88. $self->{stopWhenHit} = defined($args{stopWhenHit}) ? $args{stopWhenHit} : 1;
  89. if ($args{target}) {
  90. if (UNIVERSAL::isa($args{target}, 'Actor') && !$args{target}->isa('Actor::You') && !$args{actorList}) {
  91. ArgumentException->throw("No actorList argument given.");
  92. }
  93. $self->{target} = $args{target};
  94. $self->{actorList} = $args{actorList};
  95. }
  96. $self->{state} = PREPARING;
  97. # int castTries
  98. # The number of times we've tried to cast the skill.
  99. $self->{castTries} = 0;
  100. # Hash castWaitTimer
  101. # A timer used when waiting for casting to start.
  102. $self->{castWaitTimer}{timeout} = $timeout{ai_skill_use}{timeout};
  103. # int maxCastTries
  104. # The maximum number of times to try to re-cast the skill before
  105. # we give up.
  106. $self->{maxCastTries} = DEFAULT_MAX_CAST_TRIES;
  107. # boolean castingFinished
  108. # Whether casting has finished.
  109. # boolean castingStarted
  110. # Whether casting has started.
  111. # Hash castFinishTimer
  112. # A timer for checking when the casting is supposed to be finished.
  113. # Hash castingError
  114. # If casting has failed, then this member contains error information.
  115. # The hash has the following keys:
  116. # - type - An identifier for the error.
  117. # - message - A human-readable message for the error.
  118. # boolean castingCancelled
  119. # Whether casting has been cancelled.
  120. my @holder = ($self);
  121. Scalar::Util::weaken($holder[0]);
  122. $self->{hooks} = Plugins::addHooks(
  123. ['is_casting',       &onSkillCast, @holder],
  124. ['packet_skilluse',  &onSkillUse,  @holder],
  125. ['packet_skillfail', &onSkillFail, @holder],
  126. ['packet_castCancelled', &onSkillCancelled, @holder]
  127. );
  128. return $self;
  129. }
  130. sub DESTROY {
  131. my ($self) = @_;
  132. Plugins::delHooks($self->{hooks});
  133. $self->SUPER::DESTROY();
  134. }
  135. # Overrided method.
  136. sub interrupt {
  137. my ($self) = @_;
  138. $self->SUPER::interrupt();
  139. $self->{interruptTime} = time;
  140. }
  141. # Overrided method.
  142. sub resume {
  143. my ($self) = @_;
  144. $self->SUPER::resume();
  145. $self->{castWaitTimer}{time} += time - $self->{interruptTime};
  146. delete $self->{interruptTime};
  147. }
  148. # Checks whether the caster of a skill is this character (in case of a character skill)
  149. # or the homunculus (in case of a Homunculus skill).
  150. sub casterIsCorrect {
  151. my ($self, $actorID) = @_;
  152. my $skill = $self->{skill};
  153. return ($skill->getOwnerType() == Skill::OWNER_CHAR && $actorID eq $char->{ID})
  154.     || ($skill->getOwnerType() == Skill::OWNER_HOMUN && $char->{homunculus} && $actorID eq $char->{homunculus}{ID})
  155.     || ($skill->getOwnerType() == Skill::OWNER_MERC && $char->{mercenary} && $actorID eq $char->{mercenary}{ID});
  156. }
  157. # Called when a skill has started casting.
  158. sub onSkillCast {
  159. my (undef, $args, $holder) = @_;
  160. my $self = $holder->[0];
  161. if ($self->getStatus() == Task::RUNNING && $self->casterIsCorrect($args->{sourceID})
  162.  && $self->{skill}->getIDN() == $args->{skillID}) {
  163. $self->{castingStarted} = 1;
  164. $self->{castFinishTimer}{time} = time;
  165. $self->{castFinishTimer}{timeout} = $args->{castTime} + DEFAULT_CAST_TIMEOUT;
  166. }
  167. }
  168. # Called when a skill has been used.
  169. sub onSkillUse {
  170. my (undef, $args, $holder) = @_;
  171. my $self = $holder->[0];
  172. if ($self->getStatus() == Task::RUNNING && $self->casterIsCorrect($args->{sourceID})
  173.  && $self->{skill}->getIDN() == $args->{skillID}) {
  174. $self->{castingFinished} = 1;
  175. }
  176. }
  177. # Called when a skill has failed.
  178. sub onSkillFail {
  179. my (undef, $args, $holder) = @_;
  180. my $self = $holder->[0];
  181. if ($self->getStatus() == Task::RUNNING && $self->{skill}->getIDN() == $args->{skillID}) {
  182. $self->{castingError} = {
  183. type => $args->{failType},
  184. message => $args->{failMessage}
  185. };
  186. }
  187. }
  188. # Called when a skill has been cancelled.
  189. sub onSkillCancelled {
  190. my (undef, $args, $holder) = @_;
  191. my $self = $holder->[0];
  192. if ($self->getStatus() == Task::RUNNING && $self->casterIsCorrect($args->{sourceID})) {
  193. $self->{castingCancelled} = 1;
  194. }
  195. }
  196. # Check whether the target has been lost.
  197. sub targetLost {
  198. my ($self) = @_;
  199. return UNIVERSAL::isa($self->{target}, 'Actor') && !$self->{target}->isa('Actor::You')
  200. && !$self->{actorList}->getByID($self->{target}{ID});
  201. }
  202. # Check whether we've equipped the necessary items (e.g. Vitata Card for Heal)
  203. # to be able to use the skill.
  204. sub hasNecessaryEquipment {
  205. return 1;
  206. }
  207. # Check whether the preparation conditions are satisfied.
  208. sub checkPreparations {
  209. my ($self) = @_;
  210. return !$char->{sitting} && $self->hasNecessaryEquipment();
  211. }
  212. # Cast the skill, reset castWaitTimer and increment castTries.
  213. sub castSkill {
  214. my ($self) = @_;
  215. my $skill = $self->{skill};
  216. my $handle = $skill->getHandle();
  217. my $skillID = $skill->getIDN();
  218. my $level = $skill->getLevel();
  219. if (!defined $level) {
  220. $level = $char->getSkillLevel($skill);
  221. }
  222. if ($skill->getTargetType() == Skill::TARGET_SELF) {
  223. # A skill which is used on the character self.
  224. $messageSender->sendSkillUse($skillID, $level, $accountID);
  225. } elsif (UNIVERSAL::isa($self->{target}, 'Actor')) {
  226. # The skill must be used on an actor.
  227. if ($skill->getTargetType() == Skill::TARGET_LOCATION) {
  228. # This is a location skill.
  229. $messageSender->sendSkillUseLoc($skillID, $level,
  230. $self->{target}{pos_to}{x}, $self->{target}{pos_to}{y});
  231. } else {
  232. $messageSender->sendSkillUse($skillID, $level, $self->{target}{ID});
  233. }
  234. } else {
  235. # A location skill.
  236. $messageSender->sendSkillUseLoc($skillID, $level, $self->{target}{x}, $self->{target}{y});
  237. }
  238. $self->{castTries}++;
  239. $self->{castWaitTimer}{time} = time;
  240. }
  241. # TODO:
  242. # - walk to target if it's too far away?
  243. # - equip necessary items
  244. # - check whether we're silensed
  245. sub iterate {
  246. my ($self) = @_;
  247. return if (!$char || $net->getState() != Network::IN_GAME);
  248. my $handle = $self->{skill}->getHandle();
  249. if ($char->getSkillLevel($self->{skill}) == 0
  250. && !($char->{permitSkill} && $char->{permitSkill}->getHandle() eq $handle)) {
  251. $self->setError(ERROR_NO_SKILL, TF("Skill %s cannot be used because your character has no such skill.",
  252. $self->{skill}->getName()));
  253. debug "UseSkill - No such skill.n", "Task::UseSkill" if DEBUG;
  254. return;
  255. }
  256. if ($self->{state} == PREPARING) {
  257. if (!$self->getSubtask()) {
  258. my $task = new Task::Chained(tasks => [
  259. new Task::SitStand(mode => 'stand')
  260. ]);
  261. $self->setSubtask($task);
  262. $self->{preparationTask} = $task;
  263. debug "UseSkill - Created preparation subtask.n", "Task::UseSkill" if DEBUG;
  264. }
  265. # Iterate preparation subtask
  266. $self->SUPER::iterate();
  267. if (!$self->getSubtask() && !$self->{preparationTask}->getError()) {
  268. # Preparation subtask completed with success.
  269. $self->{state} = WAITING_FOR_CAST_TO_START;
  270. $self->castSkill();
  271. delete $self->{preparationTask};
  272. debug "UseSkill - Preparation subtask completed with success, waiting for cast to start...n", "Task::UseSkill" if DEBUG;
  273. } elsif (!$self->getSubtask() && $self->{preparationTask}->getError()) {
  274. # Preparation subtask completed with error.
  275. my $error = $self->{preparationTask}->getError();
  276. $self->setError(ERROR_PREPARATION_FAILED, $error->{message});
  277. debug "UseSkill - Preparation failed: $error->{message}n", "Task::UseSkill" if DEBUG;
  278. } elsif ($self->targetLost()) {
  279. $self->setError(ERROR_TARGET_LOST, T("Target lost."));
  280. $self->{preparationTask}->stop() if ($self->{preparationTask});
  281. debug "UseSkill - Target lost.n", "Task::UseSkill" if DEBUG;
  282. }
  283. } elsif ($self->{state} == WAITING_FOR_CAST_TO_START) {
  284. if ($self->{castingFinished}) {
  285. $self->setDone();
  286. debug "UseSkill - Done.n", "Task::UseSkill" if DEBUG;
  287. } elsif ($self->{castingStarted}) {
  288. delete $self->{castingStarted};
  289. $self->{state} = WAITING_FOR_CAST_TO_FINISH;
  290. debug "UseSkill - Casting started, waiting for cast to finish...n", "Task::UseSkill" if DEBUG;
  291. } elsif ($self->targetLost()) {
  292. $self->setError(ERROR_TARGET_LOST, T("Target lost."));
  293. debug "UseSkill - Target lost.n", "Task::UseSkill" if DEBUG;
  294. } elsif (timeOut($self->{castWaitTimer})) {
  295. # Nothing happened within a period of time.
  296. if ($self->{castTries} < $self->{maxCastTries}) {
  297. $self->castSkill();
  298. debug "UseSkill - Timeout, recasting skill.n", "Task::UseSkill" if DEBUG;
  299. } else {
  300. $self->setError(ERROR_MAX_TRIES, TF("Unable to cast skill %s in %d tries.",
  301. $self->{skill}->getName(), $self->{maxCastTries}));
  302. debug "UseSkill - Timeout, maximum tries reached.n", "Task::UseSkill" if DEBUG;
  303. }
  304. } elsif ($self->{castingError}) {
  305. $self->setError(ERROR_CASTING_FAILED, TF("Casting failed: %s (%d)",
  306. $self->{castingError}{message}, $self->{castingError}{type}));
  307. debug "UseSkill - Casting failed: $self->{castingError}{message}.n", "Task::UseSkill" if DEBUG;
  308. } elsif (!$self->checkPreparations()) {
  309. # Preparation conditions violated.
  310. $self->{state} = PREPARING;
  311. debug "UseSkill - Preparation conditions violated.n" if DEBUG;
  312. }
  313. } elsif ($self->{state} == WAITING_FOR_CAST_TO_FINISH) {
  314. if ($self->{castingFinished}) {
  315. $self->setDone();
  316. debug "UseSkill - Done.n", "Task::UseSkill" if DEBUG;
  317. } elsif ($self->{castingCancelled}) {
  318. delete $self->{castingCancelled};
  319. if ($self->{stopWhenHit}) {
  320. $self->setError(ERROR_CASTING_CANCELLED, T("Casting has been cancelled."));
  321. debug "UseSkill - Casting cancelled, stopping.n", "Task::UseSkill" if DEBUG;
  322. # Here we also check castTries. This differs from the state diagram.
  323. # The state diagram will become too messy if I add this behavior.
  324. } elsif ($self->{castTries} < $self->{maxCastTries}) {
  325. $self->castSkill();
  326. $self->{state} = WAITING_FOR_CAST_TO_START;
  327. debug "UseSkill - Casting cancelled, retrying.n", "Task::UseSkill" if DEBUG;
  328. } else {
  329. $self->setError(ERROR_MAX_TRIES, TF("Unable to cast skill in %d tries.", $self->{maxCastTries}));
  330. debug "UseSkill - Casting cancelled, maximum tries reached.n", "Task::UseSkill" if DEBUG;
  331. }
  332. } elsif ($self->{castingError}) {
  333. $self->setError(ERROR_CASTING_FAILED, TF("Casting failed: %s (%d)",
  334. $self->{castingError}{message}, $self->{castingError}{type}));
  335. debug "UseSkill - Casting failed: $self->{castingError}{message}.n", "Task::UseSkill" if DEBUG;
  336. } elsif (timeOut($self->{castFinishTimer})) {
  337. $self->setError(ERROR_CASTING_TIMEOUT, T("Casting is supposed to be finished now, but nothing happened."));
  338. debug "UseSkill - Timeout.n", "Task::UseSkill" if DEBUG;
  339. }
  340. }
  341. }
  342. 1;