ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/maps/perl/Jeweler.pm
Revision: 1.5
Committed: Fri Sep 8 15:21:04 2006 UTC (17 years, 8 months ago) by root
Branch: MAIN
Changes since 1.4: +4 -4 lines
Log Message:
get_flag => flag

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 root 1.5 if (!$_->flag (cf::FLAG_IDENTIFIED) && $_->need_identify) {
169 elmex 1.3 $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 root 1.5 if ($pl->flag (cf::FLAG_WIZ)) {
176 elmex 1.4 $r->wiz_analyze ($pl);
177     }
178 elmex 1.3 }
179     if ($hadunid) {
180     $pl->message ("You couldn't identify the other rings and not analyze them!");
181 elmex 1.2 }
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 elmex 1.3 my $archvalsum = $ingred->value ($ingr_grp, $srcarchname);
219     $ingred->remove ($ingr_grp, $srcarchname);
220 elmex 1.2
221 elmex 1.3 my $outarchval = cf::arch::find ($outarch)->clone->value;
222 elmex 1.2
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 elmex 1.1
237    
238     package Jeweler::CauldronHandler;
239 elmex 1.2 use strict;
240 elmex 1.1
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 elmex 1.2 use Storable qw/dclone/;
351     use strict;
352 elmex 1.1
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 elmex 1.3 $sum += ($_->nrof || 1) * $_->value;
391 elmex 1.1 }
392    
393     return $sum;
394     }
395    
396     =item remove ($group, $archname)
397    
398     Removes the ingredients in C<$group> with archname C<$archname>.
399 elmex 1.2 It removes all in C<$group> if archname is undef.
400 elmex 1.1
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 elmex 1.2 if (defined $archname) {
412     if ($_->archetype->name eq $archname) {
413     Jeweler::Util::remove ($_);
414     } else {
415     push @out, $_;
416     }
417     } else {
418 elmex 1.1 Jeweler::Util::remove ($_);
419 elmex 1.2 }
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 elmex 1.3 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
478 elmex 1.2 my $did_impr = 0;
479 elmex 1.3 for my $x (1..$maxstat) {
480 elmex 1.2 my $y = Jeweler::Object::fx ($x, 'stat_potions');
481 elmex 1.3
482     if ($cnt <= $y->[0]) {
483 elmex 1.2 $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 elmex 1.1 } else {
540 elmex 1.2 my $nr;
541 elmex 1.3 $self->do_grep (sub { $nr += $_[0]->nrof; 0 }, @grepar);
542 elmex 1.2 $costs->{$key} -= $nr;
543 elmex 1.1 }
544     }
545    
546 elmex 1.2 return $costs;
547 elmex 1.1 }
548    
549     =back
550    
551     =cut
552    
553 elmex 1.2 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 elmex 1.3 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 root 1.5 if ($pl->flag (cf::FLAG_WIZ)) {
588 elmex 1.3 $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 elmex 1.2 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 elmex 1.3 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 elmex 1.2 sub ring_or_ammy_to_hash {
673     my ($self, $thing) = @_;
674    
675     my $obj = {};
676    
677     for (@Jeweler::RESISTS) {
678     $obj->{resist}->{$_} = $thing->get_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} = $thing->hp;
688     $obj->{spec}->{magic} = $thing->sp;
689     $obj->{spec}->{wc} = $thing->wc;
690     $obj->{spec}->{dam} = $thing->dam;
691     $obj->{spec}->{ac} = $thing->ac;
692     $obj->{spec}->{speed} = $thing->stats->exp;
693     $obj->{spec}->{suste} = $thing->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     my $obj = cf::object::new $self->{hash}->{arch};
705     $obj->set_face ($self->{hash}->{face});
706    
707     $obj->set_hp ($self->{hash}->{spec}->{regen} * 1);
708     $obj->set_sp ($self->{hash}->{spec}->{magic} * 1);
709     $obj->set_wc ($self->{hash}->{spec}->{wc} * 1);
710     $obj->set_dam ($self->{hash}->{spec}->{dam} * 1);
711     $obj->set_ac ($self->{hash}->{spec}->{ac} * 1);
712     $obj->stats->exp ($self->{hash}->{spec}->{speed} * 1);
713     $obj->set_food ($self->{hash}->{spec}->{suste} * 1);
714    
715     for (qw/Str Dex Con Wis Cha Int Pow/) {
716     $obj->stats->$_ ($self->{hash}->{stat}->{lc $_} * 1);
717     }
718    
719     for (@Jeweler::RESISTS) {
720     $obj->set_resistance ($_, $self->{hash}->{resist}->{$_} * 1);
721     }
722    
723 root 1.5 $obj->flag (cf::FLAG_IDENTIFIED, 1);
724 elmex 1.2
725     return $obj;
726     }
727    
728     sub stat_level {
729     my ($self) = @_;
730     my $stats = $self->{hash}->{stat} || {};
731    
732     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
733     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
734    
735     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
736     my $stat_sum = sum (values %$stats);
737     my $level = int (($maxlevel / $maxstat) * $stat_sum);
738    
739     ($level, $stat_cnt)
740     }
741    
742     sub resist_level {
743     my ($self) = @_;
744    
745     my $resists = $self->{hash}->{resist} || {};
746    
747     my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
748     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
749     my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
750     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
751     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
752    
753     my $ressum = 0;
754     my $rescnt = 0;
755     my @reslevels;
756    
757     for my $resnam (keys %$resists) {
758     my $res = $resists->{$resnam};
759    
760     $rescnt++
761     if $res > 0; # negative resistancies are not an improvement
762    
763     $ressum += $res; # note: negative resistancies lower the sum
764    
765     next unless $res > 0;
766    
767     my $level = 0;
768     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
769     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
770     } else {
771     $level = ceil (($att_res_lvl / $max_att_res) * $res);
772     }
773     push @reslevels, $level;
774     }
775    
776     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
777    
778     (max (@reslevels, $overall_lvl), $rescnt);
779     }
780    
781     sub special_level {
782     my ($self) = @_;
783    
784     my $specials = $self->{hash}->{spec} || {};
785    
786     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
787     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
788    
789     my @speclvls;
790     my $specsum = 0;
791     my $imprs = 0;
792    
793     for my $spcnam (keys %$specials) {
794     my $spc = $specials->{$spcnam};
795     next unless $spc > 0;
796    
797     $specsum += $spc;
798     $imprs++;
799    
800     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
801    
802     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
803     push @speclvls, $lvl;
804     }
805    
806     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
807    
808     (max (@speclvls, $sumlvl), $imprs)
809     }
810    
811    
812     # this function calculated the 'level' of an amulet or a ring
813     sub power_to_level {
814 elmex 1.3 my ($self, $lvldescr) = @_;
815 elmex 1.2
816     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
817     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
818    
819     my ($stat_lvl, $stat_imprs) = $self->stat_level;
820     my ($resist_lvl, $res_imprs) = $self->resist_level;
821     my ($spec_lvl, $spec_imprs) = $self->special_level;
822    
823     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
824    
825     my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
826    
827     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
828    
829 elmex 1.3 if ($lvldescr) {
830     $$lvldescr =
831     sprintf "%3d: %s\n", $levl,
832 elmex 1.2 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
833     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
834 elmex 1.3 }
835 elmex 1.2
836     $levl
837     }
838    
839     sub add_stat_costs {
840     my ($self, $cost) = @_;
841    
842     my $stats = $self->{hash}->{stat};
843    
844     for my $stat (keys %$stats) {
845     my $sum = $stats->{$stat};
846    
847     next unless $sum > 0;
848    
849     my $statfx = fx ($sum, 'stat_potions');
850     $cost->{"stat_$stat"} += $statfx->[0];
851     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
852     }
853     }
854    
855     sub add_special_costs {
856     my ($self, $cost) = @_;
857    
858     my $specials = $self->{hash}->{spec};
859    
860     for my $spec (keys %$specials) {
861     my $sum = $specials->{$spec};
862    
863     next unless $sum > 0;
864    
865     my $specfx = fx ($sum, 'spec_potions');
866     $cost->{"spec_$spec"} += $specfx->[0];
867     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
868     }
869     }
870    
871     sub calc_costs {
872     my ($self) = @_;
873    
874     my $costs = {};
875    
876     my $ring = $self->{hash};
877    
878     for my $resnam (keys %{$ring->{resist} || {}}) {
879    
880     my $res = $ring->{resist}->{$resnam};
881    
882     next unless $res > 0;
883    
884     $costs->{"resist_" . $Jeweler::RESMAP{$resnam} . "_item"} += $res;
885    
886     my $diamonds;
887     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
888     $diamonds += fx ($res, 'effect_resist_diamonds');
889     } else {
890     $diamonds += fx ($res, 'attack_resist_diamonds');
891     }
892    
893     split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnam});
894     }
895    
896     $self->add_stat_costs ($costs);
897     $self->add_special_costs ($costs);
898    
899     return $costs;
900     }
901    
902     sub split_diamonds {
903     my ($cost, $diamonds, $category) = @_;
904    
905     my $stat_split = Jeweler::getcfg (diamond_split => $category);
906    
907     my $sum = sum (@$stat_split);
908     if ($sum < (1 - 0.0001)) {
909     warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
910     }
911    
912 elmex 1.3 my $emarch = cf::arch::find ('emerald');
913     my $saarch = cf::arch::find ('sapphire');
914     my $pearch = cf::arch::find ('pearl');
915     my $ruarch = cf::arch::find ('ruby');
916     my $diarch = cf::arch::find ('gem');
917    
918     my $sumvalue = $diarch->clone->value * $diamonds;
919    
920     $cost->{emerald} += ceil (($sumvalue * $stat_split->[0]) / max (1, $emarch->clone->value));
921     $cost->{sapphire} += ceil (($sumvalue * $stat_split->[1]) / max (1, $saarch->clone->value));
922     $cost->{pearl} += ceil (($sumvalue * $stat_split->[2]) / max (1, $pearch->clone->value));
923     $cost->{ruby} += ceil (($sumvalue * $stat_split->[3]) / max (1, $ruarch->clone->value));
924     $cost->{gem} += ceil (($sumvalue * $stat_split->[4]) / max (1, $diarch->clone->value));
925 elmex 1.2 }
926    
927    
928    
929 elmex 1.1 package Jeweler::Util;
930 elmex 1.2 use strict;
931 elmex 1.1
932     =head2 Util
933    
934     Some utility functions for the Jeweler skill.
935    
936     =over 4
937    
938 elmex 1.2 =item remove ($object[, $nrof])
939 elmex 1.1
940 elmex 1.2 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
941     The returnvalue is the number of 'single' objects that couldn't be removed.
942 elmex 1.1
943     =cut
944    
945     sub remove {
946 elmex 1.2 my ($obj, $nrof) = @_;
947    
948 elmex 1.3 my $cnt;
949 elmex 1.2
950 elmex 1.3 if (defined $nrof) {
951     return 0 if ($nrof * 1) == 0;
952     $cnt = int (($obj->nrof || 1) - (1 * $nrof));
953 elmex 1.2
954 elmex 1.3 if ($cnt > 0) {
955     $obj->set_nrof ($cnt);
956     return 0;
957     }
958 elmex 1.2 }
959 elmex 1.3
960     remove ($_) for ($obj->inv);
961     $obj->remove;
962     $obj->free;
963     return $cnt;
964 elmex 1.2 }
965 elmex 1.1
966 elmex 1.2 sub grep_for_match {
967     my ($thing, @matchar) = @_;
968    
969     my $i = 0;
970     for my $match (@matchar) {
971     if ($match =~ m/^\s*$/) {
972     $i++;
973     next;
974     }
975    
976     if ($i % 3 == 0) {
977     $thing->name eq $match
978     and return 1;
979     } elsif ($i % 3 == 1) {
980     $thing->title eq $match
981     and return 1;
982     } else { # $i % 3 == 2
983     $thing->archetype->name eq $match
984     and return 1;
985     }
986     $i++;
987     }
988     return 0;
989 elmex 1.1 }
990    
991     =back
992    
993     =back
994    
995     1