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

# 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 root 1.6 $obj->{resist}->{$_} = $thing->resistance ($_);
679 elmex 1.2 }
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 root 1.6 $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 elmex 1.2
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 root 1.6
705 elmex 1.2 my $obj = cf::object::new $self->{hash}->{arch};
706    
707 root 1.6 $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 elmex 1.2
719 root 1.6 $stats->$_ ($self->{hash}{stat}{lc $_})
720     for qw/Str Dex Con Wis Cha Int Pow/;
721 elmex 1.2
722     for (@Jeweler::RESISTS) {
723 root 1.6 $obj->resistance ($_, $self->{hash}->{resist}->{$_});
724 elmex 1.2 }
725    
726 root 1.5 $obj->flag (cf::FLAG_IDENTIFIED, 1);
727 elmex 1.2
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 elmex 1.3 my ($self, $lvldescr) = @_;
818 elmex 1.2
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 elmex 1.3 if ($lvldescr) {
833     $$lvldescr =
834     sprintf "%3d: %s\n", $levl,
835 elmex 1.2 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
836     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
837 elmex 1.3 }
838 elmex 1.2
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 root 1.6 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 elmex 1.3
921     my $sumvalue = $diarch->clone->value * $diamonds;
922    
923 root 1.6 $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 elmex 1.2 }
929    
930    
931    
932 elmex 1.1 package Jeweler::Util;
933 elmex 1.2 use strict;
934 elmex 1.1
935     =head2 Util
936    
937     Some utility functions for the Jeweler skill.
938    
939     =over 4
940    
941 elmex 1.2 =item remove ($object[, $nrof])
942 elmex 1.1
943 elmex 1.2 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 elmex 1.1
946     =cut
947    
948     sub remove {
949 elmex 1.2 my ($obj, $nrof) = @_;
950    
951 elmex 1.3 my $cnt;
952 elmex 1.2
953 elmex 1.3 if (defined $nrof) {
954     return 0 if ($nrof * 1) == 0;
955     $cnt = int (($obj->nrof || 1) - (1 * $nrof));
956 elmex 1.2
957 elmex 1.3 if ($cnt > 0) {
958 root 1.6 $obj->nrof ($cnt);
959 elmex 1.3 return 0;
960     }
961 elmex 1.2 }
962 elmex 1.3
963 root 1.6 remove ($_) for $obj->inv;
964 elmex 1.3 $obj->remove;
965     $obj->free;
966     return $cnt;
967 elmex 1.2 }
968 elmex 1.1
969 elmex 1.2 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 elmex 1.1 }
993    
994     =back
995    
996     =back
997    
998     1