ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.17
Committed: Sat Jul 21 16:06:22 2007 UTC (16 years, 10 months ago) by elmex
Branch: MAIN
Changes since 1.16: +7 -7 lines
Log Message:
fixed jeweler skill, returnvalue of arch->name changed, needing
to use arch->archname now.

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