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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - NPC talking task
  3. #  Copyright (c) 2004-2006 OpenKore Developers
  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. # This task is responsible for automatically talking to NPCs, using a
  13. # pre-defined NPC talking sequence.
  14. package Task::TalkNPC;
  15. use strict;
  16. use Time::HiRes qw(time);
  17. use Scalar::Util;
  18. use encoding 'utf8';
  19. use Modules 'register';
  20. use Task;
  21. use base qw(Task);
  22. use Globals qw($char %timeout $npcsList $monstersList %ai_v $messageSender %config @storeList $net %talk);
  23. use Log qw(debug);
  24. use Utils;
  25. use Commands;
  26. use Network;
  27. use Misc;
  28. use Plugins;
  29. use Translation qw(T TF);
  30. # Error codes:
  31. use enum qw(
  32. NPC_NOT_FOUND
  33. NPC_NO_RESPONSE
  34. NO_SHOP_ITEM
  35. WRONG_NPC_INSTRUCTIONS
  36. );
  37. # Mutexes used by this task.
  38. use constant MUTEXES => ['npc'];
  39. ##
  40. # Task::TalkNPC->new(options...)
  41. #
  42. # Create a new Task::TalkNPC object. The following options are allowed:
  43. # `l
  44. # - All options allowed in Task->new(), except 'mutexes'.
  45. # - <tt>x</tt> (required): The X-coordinate of the NPC to talk to.
  46. # - <tt>y</tt> (required): The Y-coordinate of the NPC to talk to.
  47. # - <tt>sequence</tt> (required): A string which describes how to talk to the NPC.
  48. # `l`
  49. # Note that the NPC is assumed to be on the same map as where the character currently is.
  50. #
  51. # <tt>sequence</tt> is a string of whitespace-separated instructions:
  52. # ~l
  53. # - c       : Continue
  54. # - r#      : Select option # from menu.
  55. # - n       : Stop talking to NPC.
  56. # - b       : Send the "Show shop item list" (Buy) packet.
  57. # - w#      : Wait # seconds.
  58. # - x       : Initialize conversation with NPC. Useful to perform multiple transaction with a single NPC.
  59. # - t="str" : send the text str to NPC, double quote is needed only if the string contains space
  60. # ~l~
  61. sub new {
  62. my $class = shift;
  63. my %args = @_;
  64. my $self = $class->SUPER::new(@_, mutexes => MUTEXES);
  65. $self->{x} = $args{x};
  66. $self->{y} = $args{y};
  67. $self->{sequence} = $args{sequence};
  68. $self->{sequence} =~ s/^ +| +$//g;
  69. # Watch for map change events. Pass a weak reference to ourselves in order
  70. # to avoid circular references (memory leaks).
  71. my @holder = ($self);
  72. Scalar::Util::weaken($holder[0]);
  73. $self->{mapChangedHook} = Plugins::addHook('Network::Receive::map_changed', &mapChanged, @holder);
  74. return $self;
  75. }
  76. sub DESTROY {
  77. my ($self) = @_;
  78. Plugins::delHook($self->{mapChangedHook});
  79. }
  80. # Overrided method.
  81. sub activate {
  82. my ($self) = @_;
  83. $self->SUPER::activate(); # Do not forget to call this!
  84. $self->{time} = time;
  85. $self->{stage} = 'Not Started';
  86. $self->{mapChanged} = 0;
  87. }
  88. # Overrided method.
  89. sub iterate {
  90. my ($self) = @_;
  91. $self->SUPER::iterate(); # Do not forget to call this!
  92. return unless ($net->getState() == Network::IN_GAME);
  93. my $timeResponse = ($config{npcTimeResponse} >= 5) ? $config{npcTimeResponse}:5;
  94. if ($self->{stage} eq 'Not Started') {
  95. if (!timeOut($char->{time_move}, $char->{time_move_calc} + 0.2)) {
  96. # Wait for us to stop moving before talking.
  97. return;
  98. } elsif (timeOut($self->{time}, $timeResponse)) {
  99. $self->setError(NPC_NOT_FOUND, TF("Could not find an NPC at location (%d,%d).",
  100. $self->{x}, $self->{y}));
  101. } else {
  102. my $target = $self->findTarget($npcsList);
  103. if ($target) {
  104. debug "Target NPC " . $target->name() . " at ($self->{pos}{x},$self->{pos}{y}) found.n", "ai_npcTalk";
  105. } else {
  106. $target = $self->findTarget($monstersList);
  107. if ($target) {
  108. debug "Target Monster-NPC " . $target->name() . " at ($self->{pos}{x},$self->{pos}{y}) found.n", "ai_npcTalk";
  109. }
  110. }
  111. if ($target) {
  112. $self->{target} = $target;
  113. $self->{ID} = $target->{ID};
  114. $self->{stage} = 'Talking to NPC';
  115. $self->{steps} = [parseArgs("x $self->{sequence}")];
  116. $self->{time} = time;
  117. undef $ai_v{npc_talk}{time};
  118. undef $ai_v{npc_talk}{talk};
  119. lookAtPosition($self);
  120. }
  121. }
  122. } elsif ($self->{mapChanged} || ($ai_v{npc_talk}{talk} eq 'close' && $self->{steps}[0] !~ /x/i)) {
  123. # Cancel conversation only if NPC is still around; otherwise
  124. # we could get disconnected.
  125. #$messageSender->sendTalkCancel($self->{ID}) if ($npcsList->getByID($self->{ID}));
  126. $self->setDone();
  127. } elsif (timeOut($self->{time}, $timeResponse)) {
  128. # If NPC does not respond before timing out, then by default, it's
  129. # a failure.
  130. $messageSender->sendTalkCancel($self->{ID});
  131. $self->setError(NPC_NO_RESPONSE, T("The NPC did not respond."));
  132. } elsif (timeOut($ai_v{npc_talk}{time}, 0.25)) {
  133. # 0.25 seconds have passed since we last talked to the NPC.
  134. if ($ai_v{npc_talk}{talk} eq 'close' && $self->{steps}[0] =~ /x/i) {
  135. undef $ai_v{npc_talk}{talk};
  136. }
  137. $self->{time} = time;
  138. # We give the NPC some time to respond. This time will be reset once
  139. # the NPC responds.
  140. $ai_v{npc_talk}{time} = time + $timeResponse;
  141. if ($config{autoTalkCont}) {
  142. while ($self->{steps}[0] =~ /^c$/i) {
  143. shift @{$self->{steps}};
  144. }
  145. }
  146. my $step = $self->{steps}[0];
  147. my $npcTalkType = $ai_v{npc_talk}{talk};
  148. if ($step =~ /^w(d+)/i) {
  149. # Wait x seconds.
  150. my $time = $1;
  151. $ai_v{npc_talk}{time} = time + $time;
  152. $self->{time} = time + $time;
  153. } elsif ( $step =~ /^t=(.*)/i ) {
  154. # Send NPC talk text.
  155. $messageSender->sendTalkText($self->{ID}, $1);
  156. } elsif ( $step =~ /^a=(.*)/i ) {
  157. # Run a command.
  158. my $command = $1;
  159. $ai_v{npc_talk}{time} = time + $timeResponse - 4;
  160. $self->{time} = time + $timeResponse - 4;
  161. Commands::run($command);
  162. } elsif ( $step =~ /d(d+)/i ) {
  163. # Send NPC talk number.
  164. $messageSender->sendTalkNumber($self->{ID}, $1);
  165. } elsif ( $step =~ /x/i ) {
  166. # Initiate NPC conversation.
  167. if (!$self->{target}->isa('Actor::Monster')) {
  168. $messageSender->sendTalk($self->{ID});
  169. } else {
  170. $messageSender->sendAttack($self->{ID}, 0);
  171. }
  172. } elsif ( $step =~ /c/i ) {
  173. # Click Next.
  174. if ($npcTalkType eq 'next') {
  175. $messageSender->sendTalkContinue($self->{ID});
  176. } else {
  177. $self->setError(WRONG_NPC_INSTRUCTIONS,
  178. T("According to the given NPC instructions, the Next button " .
  179. "must now be clicked on, but that's not possible."));
  180. $self->cancelTalk();
  181. }
  182. } elsif ( $step =~ /r(d+)/i ) {
  183. # Choose a menu item.
  184. my $choice = $1;
  185. if ($npcTalkType eq 'select') {
  186. if ($choice < @{$talk{responses}} - 1) {
  187. $messageSender->sendTalkResponse($self->{ID}, $choice + 1);
  188. } else {
  189. $self->setError(WRONG_NPC_INSTRUCTIONS,
  190. TF("According to the given NPC instructions, menu item %d must " .
  191. "now be selected, but there are only %d menu items.",
  192. $choice, @{$talk{responses}} - 1));
  193. $self->cancelTalk();
  194. }
  195. } else {
  196. $self->setError(WRONG_NPC_INSTRUCTIONS,
  197. T("According to the given NPC instructions, a menu item " .
  198. "must now be selected, but that's not possible."));
  199. $self->cancelTalk();
  200. }
  201. } elsif ( $step =~ /n/i ) {
  202. # Click Close or Cancel.
  203. $self->cancelTalk();
  204. $ai_v{npc_talk}{time} = time;
  205. $self->{time} = time;
  206. } elsif ( $step =~ /^b(d+),(d+)/i ) {
  207. # Buy an shop item.
  208. my $index = $1;
  209. my $amount = $2;
  210. if ($storeList[$index]) {
  211. my $itemID = $storeList[$index]{nameID};
  212. $ai_v{npc_talk}{itemID} = $itemID;
  213. $messageSender->sendBuy($itemID, $amount);
  214. } else {
  215. $self->setError(NO_SHOP_ITEM, TF("Shop item %d not found.", $index));
  216. }
  217. } elsif ( $step =~ /b/i ) {
  218. # Get the shop's item list.
  219. $messageSender->sendGetStoreList($self->{ID});
  220. } elsif ( $step =~ /s/i ) {
  221. # Get the sell list in a shop.
  222. $messageSender->sendGetSellList($self->{ID});
  223. } elsif ( $step =~ /e/i ) {
  224. # ? Pretend like the conversation was stopped by the NPC?
  225. $ai_v{npc_talk}{talk} = 'close';
  226. }
  227. shift @{$self->{steps}};
  228. }
  229. }
  230. ##
  231. # Actor $Task_TalkNPC->target()
  232. # Requires: $self->getStatus() == Task::DONE && !defined($self->getError())
  233. # Ensures: defined(result)
  234. #
  235. # Returns the target Actor object.
  236. sub target {
  237. my ($self) = @_;
  238. return $self->{target};
  239. }
  240. sub cancelTalk {
  241. my ($self) = @_;
  242. if ($ai_v{npc_talk}{talk} eq 'select') {
  243. $messageSender->sendTalkResponse($self->{ID}, 255);
  244. } elsif ($ai_v{npc_talk}{talk} ne 'close' && !$talk{canceled}) {
  245. $messageSender->sendTalkCancel($self->{ID});
  246. $talk{canceled} = 1;
  247. }
  248. }
  249. sub mapChanged {
  250. my (undef, undef, $holder) = @_;
  251. my $self = $holder->[0];
  252. $self->{mapChanged} = 1;
  253. }
  254. # Actor findTarget(ActorList actorList)
  255. #
  256. # Check whether the target as specified in $self->{x} and $self->{y} is in the given
  257. # actor list. Returns the actor object if it's currently on screen and has a name,
  258. # undef otherwise.
  259. #
  260. # Note: we require that the NPC's name is known, because otherwise talking
  261. # may fail.
  262. sub findTarget {
  263. my ($self, $actorList) = @_;
  264. foreach my $actor (@{$actorList->getItems()}) {
  265. my $pos = ($actor->isa('Actor::NPC')) ? $actor->{pos} : $actor->{pos_to};
  266. if ($pos->{x} == $self->{x} && $pos->{y} == $self->{y}) {
  267. if (defined $actor->{name}) {
  268. return $actor;
  269. } else {
  270. return undef;
  271. }
  272. }
  273. }
  274. return undef;
  275. }
  276. 1;