ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.18
Committed: Tue Jul 31 09:40:15 2007 UTC (16 years, 9 months ago) by elmex
Branch: MAIN
Changes since 1.17: +63 -18 lines
Log Message:
fixed up the jeweler skill

File Contents

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