File Coverage

blib/lib/NetHack/PriceID.pm
Criterion Covered Total %
statement 75 79 94.9
branch 46 54 85.1
condition 2 3 66.6
subroutine 12 12 100.0
pod 4 4 100.0
total 139 152 91.4


line stmt bran cond sub pod time code
1             package NetHack::PriceID;
2             BEGIN {
3 8     8   182909 $NetHack::PriceID::AUTHORITY = 'cpan:SARTAK';
4             }
5             {
6             $NetHack::PriceID::VERSION = '0.05';
7             }
8 8     8   69 use strict;
  8         16  
  8         333  
9 8     8   50 use warnings;
  8         16  
  8         262  
10 8     8   7730 use integer;
  8         77  
  8         36  
11             # ABSTRACT: identify NetHack items using shopkeepers
12              
13 8     8   223 use base 'Exporter';
  8         17  
  8         36282  
14             our @EXPORT_OK = qw(priceid priceid_buy priceid_sell priceid_base);
15             our %EXPORT_TAGS = ('all' => \@EXPORT_OK);
16              
17             our %glyph2type = (
18             '"' => 'amulet',
19             '?' => 'scroll',
20             '+' => 'spellbook',
21             '=' => 'ring',
22             '!' => 'potion',
23             '/' => 'wand',
24             '(' => 'tool',
25             '[' => 'armor',
26             );
27              
28             our %item_table = (
29             amulet => {
30             0 => ['cheap plastic imitation of the Amulet of Yendor'],
31             150 => ['change', 'ESP', 'life saving', 'magical breathing',
32             'reflection', 'restful sleep', 'strangulation',
33             'unchanging', 'versus poison'],
34             30000 => ['Amulet of Yendor'],
35             },
36              
37             scroll => {
38             20 => ['identify'],
39             50 => ['light'],
40             60 => ['blank paper', 'enchant weapon'],
41             80 => ['enchant armor', 'remove curse'],
42             100 => ['confuse monster', 'destroy armor', 'fire',
43             'food detection', 'gold detection', 'magic mapping',
44             'scare monster', 'teleportation'],
45             200 => ['amnesia', 'create monster', 'earth', 'taming'],
46             300 => ['charging', 'genocide', 'punishment', 'stinking cloud'],
47             },
48              
49             spellbook => {
50             100 => ['detect monsters', 'force bolt', 'healing', 'jumping',
51             'knock', 'light', 'protection', 'sleep'],
52             200 => ['confuse monster', 'create monster', 'cure blindness',
53             'detect food', 'drain life', 'magic missile',
54             'slow monster', 'wizard lock'],
55             300 => ['cause fear', 'charm monster', 'clairvoyance',
56             'cure sickness', 'detect unseen', 'extra healing',
57             'haste self', 'identify', 'remove curse',
58             'stone to flesh'],
59             400 => ['cone of cold', 'detect treasure', 'fireball',
60             'invisibility', 'levitation', 'restore ability'],
61             500 => ['dig', 'magic mapping'],
62             600 => ['create familiar', 'polymorph', 'teleport away',
63             'turn undead'],
64             700 => ['cancellation', 'finger of death'],
65             },
66              
67             potion => {
68             0 => ['uncursed water'],
69             50 => ['booze', 'fruit juice', 'see invisible', 'sickness'],
70             100 => ['confusion', 'extra healing', 'hallucination', 'healing',
71             'restore ability', 'sleeping', '(un)holy water'],
72             150 => ['blindness', 'gain energy', 'invisibility',
73             'monster detection', 'object detection'],
74             200 => ['enlightenment', 'full healing', 'levitation', 'polymorph',
75             'speed'],
76             250 => ['acid', 'oil'],
77             300 => ['gain ability', 'gain level', 'paralysis'],
78             },
79              
80             ring => {
81             100 => ['adornment', 'hunger', 'protection',
82             'protection from shape changers', 'stealth',
83             'sustain ability', 'warning'],
84             150 => ['aggravate monster', 'cold resistance',
85             'gain constitution', 'gain strength', 'increase accuracy',
86             'increase damage', 'invisibility', 'poison resistance',
87             'see invisible', 'shock resistance'],
88             200 => ['fire resistance', 'free action', 'levitation',
89             'regeneration', 'searching', 'slow digestion',
90             'teleportation'],
91             300 => ['conflict', 'polymorph', 'polymorph control',
92             'teleport control'],
93             },
94              
95             wand => {
96             0 => ['uncharged'],
97             100 => ['light', 'nothing'],
98             150 => ['digging', 'enlightenment', 'locking', 'magic missile',
99             'make invisible', 'opening', 'probing',
100             'secret door detection', 'slow monster', 'speed monster',
101             'striking', 'undead turning'],
102             175 => ['cold', 'fire', 'lightning', 'sleep'],
103             200 => ['cancellation', 'create monster', 'polymorph',
104             'teleportation'],
105             500 => ['death', 'wishing'],
106             },
107              
108             bag => {
109             2 => ['sack'],
110             100 => ['bag of holding', 'oilskin sack', 'bag of tricks'],
111             },
112              
113             lamp => {
114             10 => ['oil lamp'],
115             50 => ['magic lamp'],
116             },
117              
118             flute => {
119             12 => ['wooden flute'],
120             36 => ['magic flute'],
121             },
122              
123             horn => {
124             15 => ['tooled horn'],
125             50 => ['fire horn', 'frost horn', 'horn of plenty'],
126             },
127              
128             # shirt => {
129             # 2 => ['T-shirt'],
130             # 3 => ['Hawaiian shirt'],
131             # },
132              
133             # suit => {
134             # },
135              
136             cloak => {
137             50 => ['cloak of displacement', 'cloak of protection', 'oilskin cloak'],
138             60 => ['cloak of invisibility', 'cloak of magic resistance',
139             'elven cloak'],
140             },
141              
142             helmet => {
143             1 => ['dunce cap'],
144             10 => ['helmet'],
145             50 => ['helm of brilliance', 'helm of opposite alignment',
146             'helm of telepathy'],
147             80 => ['cornuthaum'],
148             },
149              
150             gloves => {
151             8 => ['leather gloves'],
152             50 => ['gauntlets of dexterity', 'gauntlets of fumbling',
153             'gauntlets of power'],
154             },
155              
156             # shield => {
157             # 1 => [],
158             # },
159              
160             boots => {
161             8 => ['elven boots', 'kicking boots'],
162             30 => ['fumble boots', 'levitation boots'],
163             50 => ['jumping boots', 'speed boots', 'water walking boots'],
164             },
165             );
166              
167             # dynamically construct a list of all tools from each tool subtype
168             for my $in (qw/bag lamp flute horn/) {
169             while (my ($price, $items) = each %{ $item_table{$in} }) {
170             @{$item_table{tool}{$price}} = sort @{$item_table{tool}{$price} || []},
171             @$items;
172             }
173             }
174              
175             # dynamically construct a list of all armor from each armor subtype
176             for my $in (qw/shirt suit cloak helmet gloves shield boots/) {
177             # automatically calculate +1 .. +6
178             my @prices = reverse sort keys %{ $item_table{$in} };
179             for my $price (@prices) {
180             for my $enchantment (1 .. 6) {
181             my $newprice = $price + 10 * $enchantment;
182              
183             for my $item (@{ $item_table{$in}{$price} }) {
184             push @{ $item_table{$in}{$newprice} }, "+$enchantment $item";
185             }
186             }
187             }
188              
189             while (my ($price, $items) = each %{ $item_table{$in} }) {
190             @{$item_table{armor}{$price}} = sort @{$item_table{armor}{$price}||[]},
191             @$items;
192             }
193             }
194              
195             sub _croak {
196 4     4   30 require Carp;
197 4         772 Carp::croak(@_);
198             }
199              
200             sub priceid {
201 125     125 1 58804 my %args = _canonicalize_args(@_);
202 122         218 my @base;
203              
204 122 100       369 if ($args{in} eq 'sell') {
    100          
    50          
205 55         185 @base = priceid_sell(%args, out => 'base');
206             }
207             elsif ($args{in} eq 'buy') {
208 54         187 @base = priceid_buy(%args, out => 'base');
209             }
210             elsif ($args{in} eq 'base') {
211 13         53 @base = priceid_base(%args, out => 'base');
212             }
213              
214 121         426 return _canonicalize_output(\%args, @base);
215             }
216              
217             sub priceid_buy {
218 54     54 1 110 my %args = _canonicalize_args(@_);
219 54         96 my @base;
220              
221 54 100       120 _croak "Calculating 'buy' prices requires that you set 'charisma'."
222             if !defined $args{charisma};
223              
224 53         153 for my $base (keys %{ $item_table{ $args{type} } }) {
  53         223  
225 572         603 my $tmp = $base;
226              
227 572 100       884 $tmp = 5 if !$tmp;
228              
229 572         743 my $surcharge = $tmp + $tmp / 3;
230              
231 572         679 for ($tmp, $surcharge) {
232 1102 100       1813 $_ += $_ / 3 if $args{tourist};
233 1102 100       1639 $_ += $_ / 3 if $args{dunce};
234              
235 1102 100       4578 if ($args{charisma} > 18) { $_ /= 2 }
  12 50       11  
    50          
    100          
    50          
    50          
236 0         0 elsif ($args{charisma} > 17) { $_ -= $_ / 3 }
237 0         0 elsif ($args{charisma} > 15) { $_ -= $_ / 4 }
238 11         11 elsif ($args{charisma} < 6) { $_ *= 2 }
239 0         0 elsif ($args{charisma} < 8) { $_ += $_ / 2 }
240 1079         1136 elsif ($args{charisma} < 11) { $_ += $_ / 3 }
241              
242 1102 50       1694 $_ = 1 if $_ <= 0;
243              
244 1102 100       1721 if ($args{angry}) { $_ += ($_ + 2) / 3 }
  13         16  
245              
246 1102 100       2795 if (($_ * $args{quan}) == $args{amount}) {
247 59         75 push @base, $base;
248 59         109 last;
249             }
250             }
251             }
252              
253 53         170 return _canonicalize_output(\%args, @base);
254             }
255              
256             sub priceid_sell {
257 56     56 1 737 my %args = _canonicalize_args(@_);
258 56         107 my @base;
259              
260 56         64 for my $base (keys %{ $item_table{ $args{type} } }) {
  56         258  
261 791         1145 my $tmp = $base * $args{quan};
262              
263 791 100       1561 if ($args{tourist}) { $tmp /= 3 }
  7 50       8  
264 0         0 elsif ($args{dunce}) { $tmp /= 3 }
265 784         768 else { $tmp /= 2 }
266              
267 791         943 my $surcharge = $tmp - $tmp / 4;
268 791 100       1279 $surcharge = $tmp unless $tmp > 1;
269              
270 791         1036 for ($tmp, $surcharge) {
271 1535 100       3438 if ($_ == $args{amount}) {
272 68         88 push @base, $base;
273 68         115 last;
274             }
275             }
276             }
277              
278 56         192 return _canonicalize_output(\%args, @base);
279             }
280              
281             sub priceid_base {
282 15     15 1 1463 my %args = _canonicalize_args(@_);
283 15         46 return _canonicalize_output(\%args, $args{amount});
284             }
285              
286             sub _canonicalize_args {
287 250     250   1300 my %args = (
288             in => 'base',
289             out => 'names',
290             quan => 1,
291             @_,
292             );
293              
294 250 100       616 _croak "Price IDing requires that you set 'amount'"
295             if !defined $args{amount};
296              
297 249 100       470 _croak "Price IDing requires that you set 'type'"
298             if !defined $args{type};
299              
300 248   66     929 $args{type} = $glyph2type{ $args{type} } || $args{type};
301              
302 248 100       554 _croak "Unknown item type: $args{type}"
303             if !exists $item_table{ $args{type} };
304              
305 247         1490 return %args;
306             }
307              
308             sub _canonicalize_output {
309 245     245   294 my $args = shift;
310              
311 245 50       536 return map { [$_, @{ $item_table{ $args->{type} }{ $_ } || [] }] } sort @_
  2 100       4  
  2         23  
312             if $args->{out} eq 'both';
313              
314 244 100       949 return sort @_ if $args->{out} eq 'base';
315 117 100       179 return sort map {@{ $item_table{ $args->{type} }{ $_ } || [] }} @_;
  134         155  
  134         1240  
316             }
317              
318             1;
319              
320             __END__