ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.8
Committed: Sun Feb 4 00:35:23 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.7: +2 -1 lines
Log Message:
fixed bug with generated amulets :)

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     my ($sk, $chdl, $pl) = @_;
168    
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     my $msg = $r->analyze ($sk, $pl);
177     $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     my $outarchval = cf::arch::find ($outarch)->clone->value;
225    
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     $pl->ob->message ("You fail to make something, propably 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     and $_->arch->name eq $arch_name
286     } @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     $_->arch->name eq $archname
404     } @{$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     if ($_->arch->name eq $archname) {
431     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 root 1.1 @grepar = @{Jeweler::getcfg (plans => $key) || []};
582 elmex 1.4 } else { # check the gems
583     @grepar = ('gems', undef, undef, $key);
584 root 1.1 }
585    
586     if ($do_remove) {
587     my $rem = $costs->{$key};
588     $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
589     if ($rem > 0) {
590     warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
591     }
592     } else {
593     my $nr;
594 elmex 1.4 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
595 root 1.1 $costs->{$key} -= $nr;
596     }
597 elmex 1.4
598 root 1.1 }
599    
600     return $costs;
601     }
602    
603     =back
604    
605     =cut
606    
607     sub put_to_bench {
608     my ($self, $bench) = @_;
609    
610     my $ingred = $self->{ingredients};
611    
612     for my $ik (keys %$ingred) {
613     for (@{$ingred->{$ik} || []}) {
614     $bench->put ($_);
615     }
616     }
617     }
618    
619     package Jeweler::Object;
620     use strict;
621     use POSIX;
622     use List::Util qw/max min sum/;
623    
624     sub new {
625     my ($class, %arg) = @_;
626    
627     my $self = bless { }, $class;
628    
629     $self->ring_or_ammy_to_hash ($arg{object});
630    
631     $self;
632     }
633    
634 elmex 1.7 sub has_resist {
635     my ($self, $resistnam, $resistval) = @_;
636     my $resnum = $REV_RESMAP{uc $resistnam};
637     if (defined ($resistval)) {
638     return 1 if $self->{hash}->{resist}->{$resnum} == $resistval;
639     } else {
640     return 1 if $self->{hash}->{resist}->{$resnum};
641     }
642     return undef;
643     }
644    
645 root 1.1 sub analyze {
646     my ($self, $sk, $pl) = @_;
647    
648     my $sklvl = cf::exp_to_level ($sk->stats->exp);
649     my $ringlvl = $self->power_to_level;
650    
651     my $tmpl;
652     if ($pl->flag (cf::FLAG_WIZ)) {
653     $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
654     } else {
655     $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
656     }
657     my $msg = sprintf "Projected success rate: %s", $tmpl;
658     return $msg;
659     }
660    
661     sub wiz_analyze {
662     my ($self, $pl) = @_;
663     my $costs = $self->calc_costs;
664     my $desc = "";
665     my $lvl = $self->power_to_level (\$desc);
666 elmex 1.5 my $emarch = cf::arch::find 'emerald';
667     my $saarch = cf::arch::find 'sapphire';
668     my $pearch = cf::arch::find 'pearl';
669     my $ruarch = cf::arch::find 'ruby';
670     my $diarch = cf::arch::find 'gem';
671     my $scosts = $emarch->clone->value * $costs->{emerald}
672     + $saarch->clone->value * $costs->{sapphire}
673     + $pearch->clone->value * $costs->{pearl}
674     + $ruarch->clone->value * $costs->{ruby}
675     + $diarch->clone->value * $costs->{gem};
676    
677     $pl->message ("costs: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
678 root 1.1 $pl->message ("level: " . $desc);
679     }
680    
681    
682     sub get_chance_perc {
683     my ($self, $sk) = @_;
684     my $sklvl = cf::exp_to_level ($sk->stats->exp);
685     my $ringlvl = $self->power_to_level;
686     return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
687     }
688    
689     sub fx {
690     my ($res, $cfg) = @_;
691     my $or = $res;
692     my $ar = $Jeweler::CFG->{functions}->{$cfg};
693     if (ref $ar->[0] eq 'ARRAY') {
694     $res = $res - 1;
695     } else {
696     $res = ceil ($res / 5) - 1;
697     }
698     $ar->[max (min ($res, @$ar - 1), 0)];
699     }
700    
701     sub improve_by_ring {
702     my ($self, @rings) = @_;
703     my $ring = $self;
704     for my $iring (@rings) {
705     for my $cat (qw/stat spec resist/) {
706     for my $k (keys %{$iring->{hash}->{$cat}}) {
707     if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
708     $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
709     }
710     }
711     }
712     }
713     }
714    
715     sub negate {
716     my ($self) = @_;
717     for my $cat (qw/stat spec resist/) {
718     for my $k (keys %{$self->{hash}->{$cat}}) {
719     if ($self->{hash}->{$cat}->{$k} > 0) {
720     $self->{hash}->{$cat}->{$k} *= -1;
721     }
722     }
723     }
724     }
725    
726     sub to_string {
727     my ($self) = @_;
728     my $r = $self->{hash};
729     return
730     $r->{arch} . " " .
731     join ("",
732     grep { $_ ne "" }
733     join ("",
734     (map {
735     my $rv = $r->{resist}->{$_};
736     "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
737     } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
738     (map {
739     my $rv = $r->{stat}->{$_};
740     "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
741     } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
742     (map {
743     my $rv = $r->{spec}->{$_};
744     "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
745     } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
746     }
747    
748     sub ring_or_ammy_to_hash {
749     my ($self, $thing) = @_;
750    
751     my $obj = {};
752    
753     for (@Jeweler::RESISTS) {
754 root 1.3 $obj->{resist}->{$_} = $thing->resist ($_);
755 root 1.1 }
756    
757     my $stats = $thing->stats;
758    
759     for (qw/Str Dex Con Wis Cha Int Pow/) {
760     $obj->{stat}->{lc $_} = $stats->$_;
761     }
762    
763     $obj->{spec}{regen} = $stats->hp;
764     $obj->{spec}{magic} = $stats->sp;
765     $obj->{spec}{wc} = $stats->wc;
766     $obj->{spec}{dam} = $stats->dam;
767     $obj->{spec}{ac} = $stats->ac;
768     $obj->{spec}{speed} = $stats->exp;
769     $obj->{spec}{food} = $stats->food;
770    
771     $obj->{name} = $thing->name;
772     $obj->{arch} = $thing->arch->name;
773     $obj->{face} = $thing->face;
774    
775     $self->{hash} = $obj
776     }
777    
778     sub to_object {
779     my ($self) = @_;
780    
781     my $obj = cf::object::new $self->{hash}->{arch};
782    
783 elmex 1.6 $obj->item_power ($self->power_to_level); # there have to be strings attached!
784    
785 root 1.1 $obj->face ($self->{hash}{face});
786    
787     my $stats = $obj->stats;
788    
789     $stats->hp ($self->{hash}{spec}{regen});
790     $stats->sp ($self->{hash}{spec}{magic});
791     $stats->wc ($self->{hash}{spec}{wc});
792     $stats->dam ($self->{hash}{spec}{dam});
793     $stats->ac ($self->{hash}{spec}{ac});
794     $stats->exp ($self->{hash}{spec}{speed});
795     $stats->food ($self->{hash}{spec}{food});
796    
797     $stats->$_ ($self->{hash}{stat}{lc $_})
798     for qw/Str Dex Con Wis Cha Int Pow/;
799    
800     for (@Jeweler::RESISTS) {
801 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
802 root 1.1 }
803    
804     $obj->flag (cf::FLAG_IDENTIFIED, 1);
805    
806     return $obj;
807     }
808    
809 elmex 1.4 sub is_better_than {
810     my ($self, $other) = @_;
811    
812     for my $type (qw/spec stat resist/) {
813     for my $stat (keys %{$self->{hash}->{$type}}) {
814     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
815     return 1;
816     }
817     }
818     }
819    
820     return 0;
821     }
822    
823 root 1.1 sub stat_level {
824     my ($self) = @_;
825     my $stats = $self->{hash}->{stat} || {};
826    
827     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
828     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
829    
830     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
831     my $stat_sum = sum (values %$stats); # also count the negative stats!
832     my $level = int (($maxlevel / $maxstat) * $stat_sum);
833    
834     ($level, $stat_cnt)
835     }
836    
837     sub resist_level {
838     my ($self) = @_;
839    
840     my $resists = $self->{hash}->{resist} || {};
841    
842     my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
843     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
844     my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
845     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
846     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
847    
848     my $ressum = 0;
849     my $rescnt = 0;
850     my @reslevels;
851    
852     for my $resnam (keys %$resists) {
853     my $res = $resists->{$resnam};
854    
855     $rescnt++
856     if $res > 0; # negative resistancies are not an improvement
857    
858     $ressum += $res; # note: negative resistancies lower the sum
859    
860     next unless $res > 0;
861    
862     my $level = 0;
863     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
864     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
865     } else {
866     $level = ceil (($att_res_lvl / $max_att_res) * $res);
867     }
868     push @reslevels, $level;
869     }
870    
871     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
872    
873     (max (@reslevels, $overall_lvl), $rescnt);
874     }
875    
876     sub special_level {
877     my ($self) = @_;
878    
879     my $specials = $self->{hash}->{spec} || {};
880    
881     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
882     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
883    
884     my @speclvls;
885     my $specsum = 0;
886     my $imprs = 0;
887    
888     for my $spcnam (keys %$specials) {
889     my $spc = $specials->{$spcnam};
890     next unless $spc > 0;
891    
892     $specsum += $spc;
893     $imprs++;
894    
895     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
896    
897     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
898     push @speclvls, $lvl;
899     }
900    
901     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
902    
903     (max (@speclvls, $sumlvl), $imprs)
904     }
905    
906    
907     # this function calculated the 'level' of an amulet or a ring
908     sub power_to_level {
909     my ($self, $lvldescr) = @_;
910    
911     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
912     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
913    
914     my ($stat_lvl, $stat_imprs) = $self->stat_level;
915     my ($resist_lvl, $res_imprs) = $self->resist_level;
916     my ($spec_lvl, $spec_imprs) = $self->special_level;
917    
918     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
919    
920     my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
921    
922     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
923    
924     if ($lvldescr) {
925     $$lvldescr =
926     sprintf "%3d: %s\n", $levl,
927     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
928     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
929     }
930    
931     $levl
932     }
933    
934     sub add_stat_costs {
935     my ($self, $cost) = @_;
936    
937     my $stats = $self->{hash}->{stat};
938    
939     for my $stat (keys %$stats) {
940     my $sum = $stats->{$stat};
941    
942     next unless $sum > 0;
943    
944 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
945 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
946     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
947     }
948     }
949    
950     sub add_special_costs {
951     my ($self, $cost) = @_;
952    
953     my $specials = $self->{hash}->{spec};
954    
955     for my $spec (keys %$specials) {
956     my $sum = $specials->{$spec};
957    
958     next unless $sum > 0;
959    
960 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
961 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
962     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
963     }
964     }
965    
966     sub calc_costs {
967     my ($self) = @_;
968    
969     my $costs = {};
970    
971     my $ring = $self->{hash};
972    
973 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
974 root 1.1
975 elmex 1.4 my $res = $ring->{resist}->{$resnum};
976 root 1.1
977     next unless $res > 0;
978    
979 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
980 root 1.1
981     my $diamonds;
982 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
983 root 1.1 $diamonds += fx ($res, 'effect_resist_diamonds');
984     } else {
985     $diamonds += fx ($res, 'attack_resist_diamonds');
986     }
987    
988 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
989 root 1.1 }
990    
991     $self->add_stat_costs ($costs);
992     $self->add_special_costs ($costs);
993    
994     return $costs;
995     }
996    
997     sub split_diamonds {
998     my ($cost, $diamonds, $category) = @_;
999    
1000     my $stat_split = Jeweler::getcfg (diamond_split => $category);
1001    
1002     my $sum = sum (@$stat_split);
1003     if ($sum < (1 - 0.0001)) {
1004     warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
1005     }
1006    
1007     my $emarch = cf::arch::find 'emerald';
1008     my $saarch = cf::arch::find 'sapphire';
1009     my $pearch = cf::arch::find 'pearl';
1010     my $ruarch = cf::arch::find 'ruby';
1011     my $diarch = cf::arch::find 'gem';
1012    
1013     my $sumvalue = $diarch->clone->value * $diamonds;
1014    
1015     $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
1016     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
1017     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
1018     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
1019     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
1020     }
1021    
1022     package Jeweler::Util;
1023    
1024     use strict;
1025    
1026     =head2 Util
1027    
1028     Some utility functions for the Jeweler skill.
1029    
1030     =over 4
1031    
1032     =item remove ($object[, $nrof])
1033    
1034     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1035     The returnvalue is the number of 'single' objects that couldn't be removed.
1036    
1037     =cut
1038    
1039     sub remove {
1040     my ($obj, $nrof) = @_;
1041    
1042     my $cnt;
1043    
1044     if (defined $nrof) {
1045 elmex 1.4 # TODO: Check tihis line:
1046     return 0 if ($nrof * 1) == 0; #XXX: ???
1047 root 1.1 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1048    
1049     if ($cnt > 0) {
1050     $obj->nrof ($cnt);
1051     return 0;
1052     }
1053     }
1054    
1055     remove ($_) for $obj->inv;
1056     $obj->destroy;
1057     return $cnt;
1058     }
1059    
1060 elmex 1.4 sub check_for_match {
1061 root 1.1 my ($thing, @matchar) = @_;
1062    
1063     my $i = 0;
1064     for my $match (@matchar) {
1065     if ($match =~ m/^\s*$/) {
1066     $i++;
1067     next;
1068     }
1069    
1070     if ($i % 3 == 0) {
1071     $thing->name eq $match
1072     and return 1;
1073     } elsif ($i % 3 == 1) {
1074     $thing->title eq $match
1075     and return 1;
1076     } else { # $i % 3 == 2
1077     $thing->arch->name eq $match
1078     and return 1;
1079     }
1080     $i++;
1081     }
1082     return 0;
1083     }
1084    
1085 elmex 1.4 sub grep_for_match {
1086     my ($ingred, $group, @matchar) = @_;
1087    
1088     for my $thing (@{$ingred->{$group} || []}) {
1089     warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->name, "@matchar"; #d#
1090     if (check_for_match ($thing, @matchar)) {
1091     return $thing;
1092     }
1093     }
1094     return undef;
1095     }
1096    
1097 root 1.1 =back
1098    
1099     1