ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/maps/perl/Jeweler.pm
Revision: 1.6
Committed: Fri Sep 8 16:22:14 2006 UTC (17 years, 8 months ago) by root
Branch: MAIN
Changes since 1.5: +35 -32 lines
Log Message:
new accessor methods

File Contents

# Content
1 =head1 NAME
2
3 Jeweler
4
5 =head1 DESCRIPTION
6
7 The Jeweler skill helper module.
8
9 =cut
10
11 package Jeweler;
12 use strict;
13 use YAML;
14
15 =over 4
16
17 =item @RESISTS
18
19 List of all resistancies that can occur on rings and amulets.
20
21 =cut
22
23 our $CFG;
24
25 sub read_config {
26 my ($filename) = @_;
27
28 unless (-e $filename) {
29 warn "$filename doesn't exists! no config for jeweler skill loaded!\n";
30 $CFG = {};
31 return
32 }
33
34 $CFG = YAML::LoadFile $filename;
35 }
36
37 sub getcfg {
38 my ($sect, $key) = @_;
39 return $CFG->{$sect} unless defined $key;
40
41 my $cfg = $CFG->{$sect}->{$key}
42 or die "Couldn't find $sect/$key in configuration!";
43
44 $cfg
45 }
46
47 our @RESISTS = (
48 cf::ATNR_PHYSICAL,
49 cf::ATNR_MAGIC,
50 cf::ATNR_FIRE,
51 cf::ATNR_ELECTRICITY,
52 cf::ATNR_COLD,
53 cf::ATNR_CONFUSION,
54
55 cf::ATNR_ACID,
56 cf::ATNR_DRAIN,
57 cf::ATNR_GHOSTHIT,
58 cf::ATNR_POISON,
59 cf::ATNR_SLOW,
60 cf::ATNR_PARALYZE,
61
62 cf::ATNR_TURN_UNDEAD,
63 cf::ATNR_FEAR,
64 cf::ATNR_DEPLETE,
65 cf::ATNR_DEATH,
66 cf::ATNR_HOLYWORD,
67 cf::ATNR_LIFE_STEALING,
68
69 cf::ATNR_BLIND,
70 cf::ATNR_DISEASE,
71 );
72
73 =item @EFFECT_RESISTS
74
75 List of all effect resistancies that occur on rings and amulets.
76 The difference is made because effect resistancies are less effective at lower levels.
77
78 =back
79
80 =cut
81
82 our @EFFECT_RESISTS = (
83 cf::ATNR_CONFUSION,
84 cf::ATNR_DRAIN,
85 cf::ATNR_POISON,
86 cf::ATNR_SLOW,
87 cf::ATNR_PARALYZE,
88 cf::ATNR_TURN_UNDEAD,
89 cf::ATNR_FEAR,
90 cf::ATNR_DEPLETE,
91 cf::ATNR_DEATH,
92 cf::ATNR_BLIND,
93 cf::ATNR_DISEASE,
94 );
95
96 our %RESMAP = (
97 cf::ATNR_PHYSICAL => "PHYSICAL",
98 cf::ATNR_MAGIC => "MAGIC",
99 cf::ATNR_FIRE => "FIRE",
100 cf::ATNR_ELECTRICITY => "ELECTRICITY",
101 cf::ATNR_COLD => "COLD",
102 cf::ATNR_CONFUSION => "CONFUSION",
103 cf::ATNR_ACID => "ACID",
104
105 cf::ATNR_DRAIN => "DRAIN",
106 cf::ATNR_GHOSTHIT => "GHOSTHIT",
107 cf::ATNR_POISON => "POISON",
108 cf::ATNR_SLOW => "SLOW",
109 cf::ATNR_PARALYZE => "PARALYZE",
110 cf::ATNR_TURN_UNDEAD => "TURN_UNDEAD",
111
112 cf::ATNR_FEAR => "FEAR",
113 cf::ATNR_DEPLETE => "DEPLETE",
114 cf::ATNR_DEATH => "DEATH",
115 cf::ATNR_HOLYWORD => "HOLYWORD",
116 cf::ATNR_LIFE_STEALING => "LIFE_STEALING",
117 cf::ATNR_BLIND => "BLIND",
118 cf::ATNR_DISEASE => "DISEASE",
119 );
120
121 our %LVL_DIFF_CHANCES = (
122 +5 => 100,
123 +4 => 95,
124 +3 => 85,
125 +2 => 75,
126 +1 => 65,
127 0 => 50,
128 -1 => 45,
129 -2 => 35,
130 -3 => 25,
131 -4 => 10,
132 -5 => 0
133 );
134
135 our %LVL_DIFF_MSG = (
136 -5 => 'Way above your skill',
137 -4 => 'Very low',
138 -3 => 'Slight chance',
139 -2 => 'Low',
140 -1 => 'Nearly 50:50',
141 0 => '50:50',
142 1 => 'Slightly above 50:50',
143 2 => 'Good',
144 3 => 'High',
145 4 => 'Nearly confident',
146 5 => '100%',
147 );
148
149 sub level_diff_to_str {
150 my ($delta) = @_;
151 $delta = -5 if $delta < -5;
152 $delta = 5 if $delta > 5;
153 return $LVL_DIFF_MSG{$delta}
154 }
155
156 sub level_diff_to_chance_perc {
157 my ($delta) = @_;
158 $delta = -5 if $delta < -5;
159 $delta = 5 if $delta > 5;
160 return $LVL_DIFF_CHANCES{$delta}
161 }
162
163 sub analyze {
164 my ($sk, $chdl, $pl) = @_;
165
166 my $hadunid = 0;
167 for ($chdl->grep_by_type (cf::RING, cf::AMULET)) {
168 if (!$_->flag (cf::FLAG_IDENTIFIED) && $_->need_identify) {
169 $hadunid = 1;
170 next;
171 }
172 my $r = Jeweler::Object->new (object => $_);
173 my $msg = $r->analyze ($sk, $pl);
174 $pl->message ($r->to_string . ": " . $msg);
175 if ($pl->flag (cf::FLAG_WIZ)) {
176 $r->wiz_analyze ($pl);
177 }
178 }
179 if ($hadunid) {
180 $pl->message ("You couldn't identify the other rings and not analyze them!");
181 }
182 }
183
184 # this function converts metals/minerals into a raw ring (of adornment)
185 sub simple_converter {
186 my ($pl, $ingred, $chdl, $conv) = @_;
187
188 $conv = lc $conv;
189 my $cnvs = $CFG->{conversions};
190
191 return unless $cnvs->{$conv};
192
193 my %ingred_groups;
194
195 my @conv_cfg = @{$cnvs->{$conv}};
196 my $outarch = $conv;
197 my ($ingr_grp, $outarchvalfact, $srcarchname, $xp_gain) = @conv_cfg;
198
199 unless (@conv_cfg <= 4) {
200 warn "ERROR: Conversion for '$outarch' has only " . (@conv_cfg) . " arguments!";
201 return;
202 }
203
204 unless ($xp_gain > 0) {
205 warn "WARNING: xp gain isn't > 0 in convesion '$outarch'\n";
206 return;
207 }
208
209 unless ($outarchvalfact) {
210 warn "ERROR: source-arch-value-multiplier == 0 in convesion '$outarch'\n";
211 return;
212 }
213
214 unless ($outarchvalfact >= 1) {
215 warn "WARNING: source-arch-value-multiplier < 1 in convesion '$outarch', results in more valuable output!\n";
216 }
217
218 my $archvalsum = $ingred->value ($ingr_grp, $srcarchname);
219 $ingred->remove ($ingr_grp, $srcarchname);
220
221 my $outarchval = cf::arch::find ($outarch)->clone->value;
222
223 my $nrof = int ($archvalsum / (($outarchval || 1000) * $outarchvalfact));
224 if ($nrof) {
225 # XXX: yes, i know what i'm doing here, i don't set nrof, but it didn't work somehow (pls. chek sometimes)
226 $chdl->put (cf::object::new $outarch) for 1..$nrof;
227
228 my $xp_sum = ($xp_gain * $nrof);
229
230 if ($xp_sum) {
231 $pl->ob->message ("You got $xp_sum xp by making $nrof ${outarch}s");
232 $pl->ob->change_exp ($xp_sum, "jeweler", cf::SK_EXP_ADD_SKILL);
233 }
234 }
235 }
236
237
238 package Jeweler::CauldronHandler;
239 use strict;
240
241 =head2 CauldronHandler
242
243 The Jeweler::CauldronHandler package, that helps you with handling the
244 cauldron stuff. Can also be used for other skills.
245
246 =cut
247
248 sub new {
249 my ($class, %arg) = @_;
250
251 my $self = bless {
252 %arg,
253 }, $class;
254
255 $self;
256 }
257
258 =over 4
259
260 =item find_cauldron ($arch_name, @map_stack)
261
262 This finds the cauldron with C<$arch_name> on the C<@map_stack> and initalises the CauldronHandler.
263 It takes the topmost cauldron that is found. Returns undef if no cauldron was found.
264 Returns the cauldron object if it was found.
265
266 =cut
267
268 sub find_cauldron {
269 my ($self, $arch_name, @map_stack) = @_;
270
271 my @c =
272 grep {
273 $_->flag (cf::FLAG_IS_CAULDRON)
274 and $_->archetype->name eq $arch_name
275 } @map_stack;
276
277 $self->{cauldron} = $c[0];
278 }
279
280 =item grep_by_type (@types)
281
282 Finds all objects in the cauldron that have the type of one of C<@types>.
283
284 =cut
285
286 sub grep_by_type {
287 my ($self, @types) = @_;
288
289 return () unless $self->{cauldron};
290
291 my @res = grep {
292 my $ob = $_;
293 (grep { $ob->type == $_ } @types) > 0
294 } $self->{cauldron}->inv;
295
296 return @res
297 }
298
299 =item extract_jeweler_ingredients
300
301 Extracts the ingredients that matter for the Jeweler skill
302 and returns a Jeweler::Ingredients object.
303
304 =cut
305
306 sub extract_jeweler_ingredients {
307 my ($self) = @_;
308
309 return () unless $self->{cauldron};
310
311 my $ingreds = {};
312
313 my %type_to_key = (
314 cf::RING => 'rings',
315 cf::AMULET => 'ammys',
316 cf::INORGANIC => 'mets_and_mins',
317 cf::GEM => 'gems',
318 cf::POTION => 'potions',
319 cf::SCROLL => 'scrolls',
320 );
321
322 for ($self->{cauldron}->inv) {
323
324 if (my $k = $type_to_key{$_->type}) {
325 push @{$ingreds->{$k}}, $_;
326 }
327 }
328
329 return Jeweler::Ingredients->new (ingredients => $ingreds, cauldron_helper => $self)
330 }
331
332 =item put ($object)
333
334 Just puts the C<$object> into the cauldron.
335
336 =cut
337
338 sub put {
339 my ($self, $obj) = @_;
340
341 return undef unless $self->{cauldron};
342 $obj->insert_ob_in_ob ($self->{cauldron});
343 }
344
345 =back
346
347 =cut
348
349 package Jeweler::Ingredients;
350 use Storable qw/dclone/;
351 use strict;
352
353 =head2 Ingredients
354
355 This class handles the ingredients.
356
357 =over 4
358
359 =item new (ingredients => $ingred_hash)
360
361 This is called from the CauldronHandler that gives you the ingredients.
362
363 =cut
364
365 sub new {
366 my ($class, %arg) = @_;
367
368 my $self = bless {
369 %arg,
370 }, $class;
371
372 $self;
373 }
374
375 =item value ($group, $archname)
376
377 Returns the value of the ingredients in C<$group> with the archetypename C<$archname>.
378
379 =cut
380
381 sub value {
382 my ($self, $group, $archname) = @_;
383
384 my @objs = grep {
385 $_->archetype->name eq $archname
386 } @{$self->{ingredients}->{$group} || []};
387
388 my $sum = 0;
389 for (@objs) {
390 $sum += ($_->nrof || 1) * $_->value;
391 }
392
393 return $sum;
394 }
395
396 =item remove ($group, $archname)
397
398 Removes the ingredients in C<$group> with archname C<$archname>.
399 It removes all in C<$group> if archname is undef.
400
401 =cut
402
403 sub remove {
404 my ($self, $group, $archname) = @_;
405
406 my $ingred = $self->{ingredients};
407
408 my @out;
409
410 for (@{$ingred->{$group}}) {
411 if (defined $archname) {
412 if ($_->archetype->name eq $archname) {
413 Jeweler::Util::remove ($_);
414 } else {
415 push @out, $_;
416 }
417 } else {
418 Jeweler::Util::remove ($_);
419 }
420 }
421
422 @{$ingred->{$group}} = @out;
423 }
424
425 sub get_plan {
426 my ($self) = @_;
427
428 my $ingred = $self->{ingredients};
429
430 for my $grp (keys %$ingred) {
431 for my $pot (@{$ingred->{$grp}}) {
432 for my $plan (keys %{$Jeweler::CFG->{plans}}) {
433 my $plg = $Jeweler::CFG->{plans}->{$plan};
434 my @plga = ();
435 unless (ref $plg eq 'ARRAY') {
436 push @plga, $plg;
437 } else {
438 @plga = @$plg;
439 }
440 next unless @plga > 0;
441 if (Jeweler::Util::grep_for_match ($pot, @plga)) {
442 return $plan;
443 }
444 }
445 }
446 }
447 }
448
449 sub get_ring {
450 my ($self) = @_;
451 return (
452 @{$self->{ingredients}->{ammys} || []},
453 @{$self->{ingredients}->{rings} || []}
454 );
455 }
456
457 sub improve_ring_by_plan {
458 my ($self, $plan, $ring) = @_;
459
460 $ring = dclone ($ring);
461
462 my $ingred = $self->{ingredients};
463 my $impr = {};
464
465 if ($plan =~ m/^stat_(\S+)$/) {
466 my $statname = $1;
467 my $plingred = Jeweler::getcfg (plans => $plan)
468 or die "ingredients for plan '$plan' not defined!";
469
470 my $cnt = 0;
471 for my $pot (@{$ingred->{potions}}) {
472 if (Jeweler::Util::grep_for_match ($pot, @$plingred)) {
473 $cnt += $pot->nrof;
474 }
475 }
476
477 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
478 my $did_impr = 0;
479 for my $x (1..$maxstat) {
480 my $y = Jeweler::Object::fx ($x, 'stat_potions');
481
482 if ($cnt <= $y->[0]) {
483 $ring->{hash}->{stat}->{$statname} += $x;
484 $did_impr = 1;
485 last;
486 }
487 }
488
489 # we want at least this improvement if we have a plan...
490 $ring->{hash}->{stat}->{$statname} += 1 unless $did_impr;
491
492 } elsif ($plan =~ m/^spec_(\S+)$/) {
493 } elsif ($plan =~ m/^resist_(\S+)$/) {
494 }
495
496 return $ring;
497 }
498
499 sub do_grep {
500 my ($self, $cb, @grepar) = @_;
501
502 my $ingred = $self->{ingredients};
503
504
505 for my $cat (keys %$ingred) {
506 my @rem;
507 for my $ing (@{$ingred->{$cat}}) {
508 if (Jeweler::Util::grep_for_match ($ing, @grepar)) {
509 unless ($cb->($ing)) {
510 push @rem, $ing;
511 }
512 } else {
513 push @rem, $ing;
514 }
515 }
516 @{$ingred->{$cat}} = @rem;
517 }
518 }
519
520 sub check_costs {
521 my ($self, $costs, $do_remove) = @_;
522
523 my $costs = dclone ($costs);
524
525 for my $key (keys %$costs) {
526 my @grepar;
527 if ($key =~ m/^stat_(\S+)$/) {
528 @grepar = @{Jeweler::getcfg (plans => $key) || []};
529 } else {
530 @grepar = (undef, undef, $key);
531 }
532
533 if ($do_remove) {
534 my $rem = $costs->{$key};
535 $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
536 if ($rem > 0) {
537 warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
538 }
539 } else {
540 my $nr;
541 $self->do_grep (sub { $nr += $_[0]->nrof; 0 }, @grepar);
542 $costs->{$key} -= $nr;
543 }
544 }
545
546 return $costs;
547 }
548
549 =back
550
551 =cut
552
553 sub put_to_bench {
554 my ($self, $bench) = @_;
555
556 my $ingred = $self->{ingredients};
557
558 for my $ik (keys %$ingred) {
559 for (@{$ingred->{$ik} || []}) {
560 $bench->put ($_);
561 }
562 }
563 }
564
565 package Jeweler::Object;
566 use strict;
567 use POSIX;
568 use List::Util qw/max min sum/;
569
570 sub new {
571 my ($class, %arg) = @_;
572
573 my $self = bless { }, $class;
574
575 $self->ring_or_ammy_to_hash ($arg{object});
576
577 $self;
578 }
579
580 sub analyze {
581 my ($self, $sk, $pl) = @_;
582
583 my $sklvl = cf::exp_to_level ($sk->stats->exp);
584 my $ringlvl = $self->power_to_level;
585
586 my $tmpl;
587 if ($pl->flag (cf::FLAG_WIZ)) {
588 $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
589 } else {
590 $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
591 }
592 my $msg = sprintf "Projected success rate: %s", $tmpl;
593 return $msg;
594 }
595
596 sub wiz_analyze {
597 my ($self, $pl) = @_;
598 my $costs = $self->calc_costs;
599 my $desc = "";
600 my $lvl = $self->power_to_level (\$desc);
601 $pl->message ("costs: " . join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs));
602 $pl->message ("level: " . $desc);
603 }
604
605
606 sub get_chance_perc {
607 my ($self, $sk) = @_;
608 my $sklvl = cf::exp_to_level ($sk->stats->exp);
609 my $ringlvl = $self->power_to_level;
610 return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
611 }
612
613 sub fx {
614 my ($res, $cfg) = @_;
615 my $or = $res;
616 my $ar = $Jeweler::CFG->{functions}->{$cfg};
617 if (ref $ar->[0] eq 'ARRAY') {
618 $res = $res - 1;
619 } else {
620 $res = ceil ($res / 5) - 1;
621 }
622 $ar->[max (min ($res, @$ar - 1), 0)];
623 }
624
625 sub improve_by_ring {
626 my ($self, @rings) = @_;
627 my $ring = $self;
628 for my $iring (@rings) {
629 for my $cat (qw/stat spec resist/) {
630 for my $k (keys %{$iring->{hash}->{$cat}}) {
631 if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
632 $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
633 }
634 }
635 }
636 }
637 }
638
639 sub negate {
640 my ($self) = @_;
641 for my $cat (qw/stat spec resist/) {
642 for my $k (keys %{$self->{hash}->{$cat}}) {
643 if ($self->{hash}->{$cat}->{$k} > 0) {
644 $self->{hash}->{$cat}->{$k} *= -1;
645 }
646 }
647 }
648 }
649
650 sub to_string {
651 my ($self) = @_;
652 my $r = $self->{hash};
653 return
654 $r->{arch} . " " .
655 join ("",
656 grep { $_ ne "" }
657 join ("",
658 (map {
659 my $rv = $r->{resist}->{$_};
660 "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
661 } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
662 (map {
663 my $rv = $r->{stat}->{$_};
664 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
665 } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
666 (map {
667 my $rv = $r->{spec}->{$_};
668 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
669 } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
670 }
671
672 sub ring_or_ammy_to_hash {
673 my ($self, $thing) = @_;
674
675 my $obj = {};
676
677 for (@Jeweler::RESISTS) {
678 $obj->{resist}->{$_} = $thing->resistance ($_);
679 }
680
681 my $stats = $thing->stats;
682
683 for (qw/Str Dex Con Wis Cha Int Pow/) {
684 $obj->{stat}->{lc $_} = $stats->$_;
685 }
686
687 $obj->{spec}{regen} = $stats->hp;
688 $obj->{spec}{magic} = $stats->sp;
689 $obj->{spec}{wc} = $stats->wc;
690 $obj->{spec}{dam} = $stats->dam;
691 $obj->{spec}{ac} = $stats->ac;
692 $obj->{spec}{speed} = $stats->exp;
693 $obj->{spec}{food} = $stats->food;
694
695 $obj->{name} = $thing->name;
696 $obj->{arch} = $thing->archetype->name;
697 $obj->{face} = $thing->face;
698
699 $self->{hash} = $obj
700 }
701
702 sub to_object {
703 my ($self) = @_;
704
705 my $obj = cf::object::new $self->{hash}->{arch};
706
707 $obj->face ($self->{hash}{face});
708
709 my $stats = $obj->stats;
710
711 $stats->hp ($self->{hash}{spec}{regen});
712 $stats->sp ($self->{hash}{spec}{magic});
713 $stats->wc ($self->{hash}{spec}{wc});
714 $stats->dam ($self->{hash}{spec}{dam});
715 $stats->ac ($self->{hash}{spec}{ac});
716 $stats->exp ($self->{hash}{spec}{speed});
717 $stats->food ($self->{hash}{spec}{food});
718
719 $stats->$_ ($self->{hash}{stat}{lc $_})
720 for qw/Str Dex Con Wis Cha Int Pow/;
721
722 for (@Jeweler::RESISTS) {
723 $obj->resistance ($_, $self->{hash}->{resist}->{$_});
724 }
725
726 $obj->flag (cf::FLAG_IDENTIFIED, 1);
727
728 return $obj;
729 }
730
731 sub stat_level {
732 my ($self) = @_;
733 my $stats = $self->{hash}->{stat} || {};
734
735 my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
736 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
737
738 my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
739 my $stat_sum = sum (values %$stats);
740 my $level = int (($maxlevel / $maxstat) * $stat_sum);
741
742 ($level, $stat_cnt)
743 }
744
745 sub resist_level {
746 my ($self) = @_;
747
748 my $resists = $self->{hash}->{resist} || {};
749
750 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
751 my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
752 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
753 my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
754 my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
755
756 my $ressum = 0;
757 my $rescnt = 0;
758 my @reslevels;
759
760 for my $resnam (keys %$resists) {
761 my $res = $resists->{$resnam};
762
763 $rescnt++
764 if $res > 0; # negative resistancies are not an improvement
765
766 $ressum += $res; # note: negative resistancies lower the sum
767
768 next unless $res > 0;
769
770 my $level = 0;
771 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
772 $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
773 } else {
774 $level = ceil (($att_res_lvl / $max_att_res) * $res);
775 }
776 push @reslevels, $level;
777 }
778
779 my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
780
781 (max (@reslevels, $overall_lvl), $rescnt);
782 }
783
784 sub special_level {
785 my ($self) = @_;
786
787 my $specials = $self->{hash}->{spec} || {};
788
789 my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
790 my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
791
792 my @speclvls;
793 my $specsum = 0;
794 my $imprs = 0;
795
796 for my $spcnam (keys %$specials) {
797 my $spc = $specials->{$spcnam};
798 next unless $spc > 0;
799
800 $specsum += $spc;
801 $imprs++;
802
803 my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
804
805 my $lvl = ($max_spc_lvl / $max_spc) * $spc;
806 push @speclvls, $lvl;
807 }
808
809 my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
810
811 (max (@speclvls, $sumlvl), $imprs)
812 }
813
814
815 # this function calculated the 'level' of an amulet or a ring
816 sub power_to_level {
817 my ($self, $lvldescr) = @_;
818
819 my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
820 my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
821
822 my ($stat_lvl, $stat_imprs) = $self->stat_level;
823 my ($resist_lvl, $res_imprs) = $self->resist_level;
824 my ($spec_lvl, $spec_imprs) = $self->special_level;
825
826 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
827
828 my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
829
830 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
831
832 if ($lvldescr) {
833 $$lvldescr =
834 sprintf "%3d: %s\n", $levl,
835 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
836 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
837 }
838
839 $levl
840 }
841
842 sub add_stat_costs {
843 my ($self, $cost) = @_;
844
845 my $stats = $self->{hash}->{stat};
846
847 for my $stat (keys %$stats) {
848 my $sum = $stats->{$stat};
849
850 next unless $sum > 0;
851
852 my $statfx = fx ($sum, 'stat_potions');
853 $cost->{"stat_$stat"} += $statfx->[0];
854 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
855 }
856 }
857
858 sub add_special_costs {
859 my ($self, $cost) = @_;
860
861 my $specials = $self->{hash}->{spec};
862
863 for my $spec (keys %$specials) {
864 my $sum = $specials->{$spec};
865
866 next unless $sum > 0;
867
868 my $specfx = fx ($sum, 'spec_potions');
869 $cost->{"spec_$spec"} += $specfx->[0];
870 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
871 }
872 }
873
874 sub calc_costs {
875 my ($self) = @_;
876
877 my $costs = {};
878
879 my $ring = $self->{hash};
880
881 for my $resnam (keys %{$ring->{resist} || {}}) {
882
883 my $res = $ring->{resist}->{$resnam};
884
885 next unless $res > 0;
886
887 $costs->{"resist_" . $Jeweler::RESMAP{$resnam} . "_item"} += $res;
888
889 my $diamonds;
890 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
891 $diamonds += fx ($res, 'effect_resist_diamonds');
892 } else {
893 $diamonds += fx ($res, 'attack_resist_diamonds');
894 }
895
896 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnam});
897 }
898
899 $self->add_stat_costs ($costs);
900 $self->add_special_costs ($costs);
901
902 return $costs;
903 }
904
905 sub split_diamonds {
906 my ($cost, $diamonds, $category) = @_;
907
908 my $stat_split = Jeweler::getcfg (diamond_split => $category);
909
910 my $sum = sum (@$stat_split);
911 if ($sum < (1 - 0.0001)) {
912 warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
913 }
914
915 my $emarch = cf::arch::find 'emerald';
916 my $saarch = cf::arch::find 'sapphire';
917 my $pearch = cf::arch::find 'pearl';
918 my $ruarch = cf::arch::find 'ruby';
919 my $diarch = cf::arch::find 'gem';
920
921 my $sumvalue = $diarch->clone->value * $diamonds;
922
923 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
924 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
925 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
926 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
927 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
928 }
929
930
931
932 package Jeweler::Util;
933 use strict;
934
935 =head2 Util
936
937 Some utility functions for the Jeweler skill.
938
939 =over 4
940
941 =item remove ($object[, $nrof])
942
943 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
944 The returnvalue is the number of 'single' objects that couldn't be removed.
945
946 =cut
947
948 sub remove {
949 my ($obj, $nrof) = @_;
950
951 my $cnt;
952
953 if (defined $nrof) {
954 return 0 if ($nrof * 1) == 0;
955 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
956
957 if ($cnt > 0) {
958 $obj->nrof ($cnt);
959 return 0;
960 }
961 }
962
963 remove ($_) for $obj->inv;
964 $obj->remove;
965 $obj->free;
966 return $cnt;
967 }
968
969 sub grep_for_match {
970 my ($thing, @matchar) = @_;
971
972 my $i = 0;
973 for my $match (@matchar) {
974 if ($match =~ m/^\s*$/) {
975 $i++;
976 next;
977 }
978
979 if ($i % 3 == 0) {
980 $thing->name eq $match
981 and return 1;
982 } elsif ($i % 3 == 1) {
983 $thing->title eq $match
984 and return 1;
985 } else { # $i % 3 == 2
986 $thing->archetype->name eq $match
987 and return 1;
988 }
989 $i++;
990 }
991 return 0;
992 }
993
994 =back
995
996 =back
997
998 1