ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/maps/perl/Jeweler.pm
Revision: 1.3
Committed: Thu Aug 31 12:39:19 2006 UTC (17 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.2: +130 -81 lines
Log Message:
further work on the jeweler skill

File Contents

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