ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.9
Committed: Sun Feb 4 11:17:52 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.8: +44 -17 lines
Log Message:
implemented more sanity. aliases gem to diamond. fixed
the plan finding algorithm. linear interpolate the costs
for resistancies.

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