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

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Inventory list
  3. #
  4. #  Copyright (c) 2007 OpenKore Development Team
  5. #
  6. #  This software is open source, licensed under the GNU General Public
  7. #  License, version 2.
  8. #  Basically, this means that you're allowed to modify and distribute
  9. #  this software. However, if you distribute modified versions, you MUST
  10. #  also distribute the source code.
  11. #  See http://www.gnu.org/licenses/gpl.html for the full license.
  12. #########################################################################
  13. ##
  14. # MODULE DESCRIPTION: Inventory model
  15. #
  16. # <b>Derived from: @CLASS(ObjectList)</b>
  17. #
  18. # The InventoryList class models a character's inventory or a Kapra storage.
  19. #
  20. # <h3>Differences compared to ObjectList</h3>
  21. # All items in Inventory are of the same class, and are all a
  22. # subclass of @CLASS(Actor::Item).
  23. package InventoryList;
  24. use strict;
  25. use Carp::Assert;
  26. use Utils::ObjectList;
  27. use base qw(ObjectList);
  28. ### CATEGORY: Class InventoryList
  29. ##
  30. # InventoryList InventoryList->new()
  31. # Ensures:  $self->size() == 0
  32. #
  33. # Creates a new InventoryList object.
  34. sub new {
  35. my ($class) = @_;
  36. my $self = $class->SUPER::new();
  37. # Hash<String, Array<int>> nameIndex
  38. # Maps an item name to a list of item indices. Used for fast
  39. # case-insensitive lookups of items based on names. Note that
  40. # the key is always in lowercase.
  41. #
  42. # Invariant:
  43. #     defined(nameIndex)
  44. #     scalar(keys nameIndex) <= size()
  45. #     The sum of sizes of all values in nameIndex == size()
  46. #     for all keys $k in nameIndex:
  47. #         lc(getByName($k)->{name}) eq $k
  48. #         lc($k) eq $k
  49. #     for all values $v in nameIndex:
  50. #         defined($v)
  51. #         scalar(@{$v}) > 0
  52. #         for all $i in the array $v:
  53. #             defined($i)
  54. #             defined(get($i))
  55. #             get($i)->{invIndex} == $i
  56. #             $i is unique in the entire nameIndex.
  57. $self->{nameIndex} = {};
  58. # Hash<int, Scalar> nameChangeEvents
  59. # InventoryList watches for name change events in all of its
  60. # items. This variable maps an item index in this list to the
  61. # registered event ID, so that the event watcher can be removed
  62. # later.
  63. #
  64. # Invariant:
  65. #     defined(nameChangeEvents)
  66. #     scalar(keys nameChangeEvents) == size()
  67. $self->{nameChangeEvents} = {};
  68. return $self;
  69. }
  70. sub DESTROY {
  71. my ($self) = @_;
  72. $self->clear();
  73. $self->SUPER::DESTROY();
  74. }
  75. ##
  76. # int $InventoryList->add(Actor::Item item)
  77. # Requires:
  78. #     defined($item)
  79. #     defined($item->{name})
  80. #     $self->find($item) == -1
  81. # Ensures: $item->{invIndex} == result
  82. #
  83. # Adds an item to this InventoryList. $item->{invIndex} will automatically be set
  84. # index in which that item is stored in this list.
  85. #
  86. # This method overloads $ObjectList->add(), and has a stronger precondition.
  87. # See the documentation for that method for more information about this
  88. # method.
  89. sub add {
  90. my ($self, $item) = @_;
  91. assert(defined $item) if DEBUG;
  92. assert($item->isa('Actor::Item')) if DEBUG;
  93. assert(defined $item->{name}) if DEBUG;
  94. assert($self->find($item) == -1) if DEBUG;
  95. my $invIndex = $self->SUPER::add($item);
  96. $item->{invIndex} = $invIndex;
  97. my $indexSlot = $self->getNameIndexSlot($item->{name});
  98. push @{$indexSlot}, $invIndex;
  99. my $eventID = $item->onNameChange->add($self, &onNameChange);
  100. $self->{nameChangeEvents}{$invIndex} = $eventID;
  101. return $invIndex;
  102. }
  103. if (DEBUG) {
  104. eval q{
  105. # Override get() to do more error checking.
  106. sub get {
  107. my ($self, $index) = @_;
  108. my $item = $self->SUPER::get($index);
  109. if ($item) {
  110. assert(defined $item->{invIndex}, "invIndex must be defined");
  111. }
  112. return $item;
  113. }
  114. };
  115. }
  116. ##
  117. # Actor::Item $InventoryList->getByName(String name)
  118. # Returns: An Actor::Item, or undef if there is no item with that name in this list.
  119. # Requires: defined($name)
  120. # Ensures: if defined(result): result->{ID} eq $ID
  121. #
  122. # Looks up an Actor::Item object based on the item name. The name lookup is
  123. # case-insensitive. If there is more than one item with this name, then it
  124. # is unspecified which exact item will be returned.
  125. #
  126. # See also: $Actor->{ID}
  127. sub getByName {
  128. my ($self, $name) = @_;
  129. assert(defined $name) if DEBUG;
  130. my $indexSlot = $self->{nameIndex}{lc($name)};
  131. if ($indexSlot) {
  132. return $self->get($indexSlot->[0]);
  133. } else {
  134. return undef;
  135. }
  136. }
  137. ##
  138. # Actor::Item $InventoryList->getByServerIndex(int serverIndex)
  139. #
  140. # Return the first Actor::Item object, whose 'index' field is equal to $serverIndex.
  141. # If nothing is found, undef is returned.
  142. sub getByServerIndex {
  143. my ($self, $serverIndex) = @_;
  144. foreach my $item (@{$self->getItems()}) {
  145. if ($item->{index} == $serverIndex) {
  146. return $item;
  147. }
  148. }
  149. return undef;
  150. }
  151. ##
  152. # Actor::Item $InventoryList->getByNameID(Bytes nameID)
  153. #
  154. # Return the first Actor::Item object, whose 'nameID' field is equal to $nameID.
  155. # If nothing is found, undef is returned.
  156. sub getByNameID {
  157. my ($self, $nameID) = @_;
  158. foreach my $item (@{$self->getItems()}) {
  159. if ($item->{nameID} eq $nameID) {
  160. return $item;
  161. }
  162. }
  163. return undef;
  164. }
  165. ##
  166. # Actor::Item $InventoryList->getByCondition(Function condition)
  167. #
  168. # Return the first Actor::Item object for which the function $condition returns true.
  169. # If nothing is found, undef is returned.
  170. #
  171. # $condition is called with exactly one parameter, namely the item that is currently
  172. # being checked.
  173. sub getByCondition {
  174. my ($self, $condition) = @_;
  175. foreach my $item (@{$self->getItems()}) {
  176. if ($condition->($item)) {
  177. return $item;
  178. }
  179. }
  180. return undef;
  181. }
  182. ##
  183. # Actor::Item $InventoryList->getByNameList(String nameList)
  184. # nameList: a string containing a comma-separated list of item names.
  185. # Requires: defined($nameList)
  186. # Returns: The found item, or undef if not found.
  187. #
  188. # Lookup an item by using the specified name list. For example, if $nameList
  189. # is "Red Potion,White Potion,Jellopy", then this method will look up
  190. # either Red Potion, White Potion or Jellopy, whichever is found
  191. # first.
  192. sub getByNameList {
  193. my ($self, $lists) = @_;
  194. assert(defined $lists) if DEBUG;
  195. my @items = split / *, */, lc($lists);
  196. foreach my $name (@items) {
  197. next if (!$name);
  198. my $indexSlot = $self->{nameIndex}{$name};
  199. if ($indexSlot) {
  200. return $self->get($indexSlot->[0]);
  201. }
  202. }
  203. return undef;
  204. }
  205. ##
  206. # boolean $InventoryList->remove(Actor::Item item)
  207. # Requires: defined($item) && defined($item->{name})
  208. #
  209. # Removes an item from this InventoryList.
  210. #
  211. # This method overloads $ObjectList->remove(), and has a stronger precondition.
  212. # See the documentation for that method for more information about this
  213. # method.
  214. sub remove {
  215. my ($self, $item) = @_;
  216. assert(defined $item) if DEBUG;
  217. assert(UNIVERSAL::isa($item, 'Actor::Item')) if DEBUG;
  218. assert(defined $item->{name}) if DEBUG;
  219. my $result = $self->SUPER::remove($item);
  220. if ($result) {
  221. my $indexSlot = $self->getNameIndexSlot($item->{name});
  222. for (my $i = 0; $i < @{$indexSlot}; $i++) {
  223. if ($indexSlot->[$i] == $item->{invIndex}) {
  224. splice(@{$indexSlot}, $i, 1);
  225. last;
  226. }
  227. }
  228. if (@{$indexSlot} == 0) {
  229. delete $self->{nameIndex}{lc($item->{name})};
  230. }
  231. my $eventID = $self->{nameChangeEvents}{$item->{invIndex}};
  232. delete $self->{nameChangeEvents}{$item->{invIndex}};
  233. $item->onNameChange->remove($eventID);
  234. }
  235. return $result;
  236. }
  237. ##
  238. # boolean $InventoryList->removeByName(String name)
  239. # name: The name of the item to remove.
  240. # Returns: Whether the item with the specified name was in the list.
  241. # Requires: defined($name)
  242. #
  243. # Removes an item based on the item name. The name lookup is case-insensitive.
  244. # If there is more than one item with this name, then it is unspecified which
  245. # exact item (with that name) is removed.
  246. #
  247. # This will trigger an onRemove event before the item is removed.
  248. sub removeByName {
  249. my ($self, $name) = @_;
  250. my $item = $self->getByName($name);
  251. if (defined $item) {
  252. return $self->remove($item);
  253. } else {
  254. return 0;
  255. }
  256. }
  257. # overloaded
  258. sub doClear {
  259. my ($self) = @_;
  260. foreach my $item (@{$self->getItems()}) {
  261. assert(defined $item->{invIndex}, "invIndex must be defined") if DEBUG;
  262. my $eventID = $self->{nameChangeEvents}{$item->{invIndex}};
  263. delete $self->{nameChangeEvents}{$item->{invIndex}};
  264. $item->onNameChange->remove($eventID);
  265. }
  266. $self->SUPER::doClear();
  267. $self->{nameIndex} = {};
  268. $self->{nameChangeEvents} = {};
  269. }
  270. # overloaded
  271. sub checkValidity {
  272. my ($self) = @_;
  273. $self->SUPER::checkValidity();
  274. assert(defined $self->{nameIndex});
  275. assert(scalar(keys %{$self->{nameIndex}}) <= $self->size());
  276. foreach my $k (keys %{$self->{nameIndex}}) {
  277. should(lc($self->getByName($k)->{name}), $k);
  278. should(lc $k, $k);
  279. }
  280. my $sum = 0;
  281. my %invIndexCount;
  282. foreach my $v (values %{$self->{nameIndex}}) {
  283. assert(defined $v);
  284. assert(@{$v} > 0);
  285. foreach my $i (@{$v}) {
  286. assert(defined $i);
  287. assert(defined $self->get($i));
  288. assert($self->get($i)->{invIndex} == $i);
  289. $invIndexCount{$i}++;
  290. should($invIndexCount{$i}, 1);
  291. }
  292. $sum += @{$v};
  293. }
  294. should($sum, $self->size());
  295. assert(defined $self->{nameChangeEvents});
  296. should(scalar(keys %{$self->{nameChangeEvents}}), $self->size());
  297. }
  298. sub getNameIndexSlot {
  299. my ($self, $name) = @_;
  300. return $self->{nameIndex}{lc($name)} ||= [];
  301. }
  302. sub onNameChange {
  303. my ($self, $item, $args) = @_;
  304. assert(defined($item->{name}), 'An item must have a name.');
  305. my $indexSlot = $self->getNameIndexSlot($args->{oldName});
  306. for (my $i = 0; $i < @{$indexSlot}; $i++) {
  307. if ($indexSlot->[$i] == $item->{invIndex}) {
  308. # Delete from old index slot.
  309. splice(@{$indexSlot}, $i, 1);
  310. if (@{$indexSlot} == 0) {
  311. delete $self->{nameIndex}{lc($args->{oldName})};
  312. }
  313. # Add to new index slot.
  314. $indexSlot = $self->getNameIndexSlot($item->{name});
  315. push @{$indexSlot}, $item->{invIndex};
  316. return;
  317. }
  318. }
  319. assert(0, 'This should never be reached.') if DEBUG;
  320. }
  321. 1;