ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.7
Committed: Fri Feb 2 12:05:28 2007 UTC (17 years, 4 months ago) by elmex
Branch: MAIN
Changes since 1.6: +11 -0 lines
Log Message:
added attachment to jeweler extension for the jeweler quest

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     $ob->set_animation (cf::rndm $ob->num_animations);
232     $ob->flag (cf::FLAG_IDENTIFIED, 1);
233     }
234 root 1.1
235     my $xp_sum = ($xp_gain * $nrof);
236    
237     if ($xp_sum) {
238     $pl->ob->message ("You got $xp_sum xp by making $nrof ${outarch}s");
239 elmex 1.4 $pl->ob->change_exp ($xp_sum, "jeweler", cf::SK_EXP_SKILL_ONLY);
240 root 1.1 }
241 elmex 1.4 } else {
242     $pl->ob->message ("You fail to make something, propably you used not enough source material?");
243 root 1.1 }
244     }
245    
246    
247     package Jeweler::CauldronHandler;
248    
249     use strict;
250    
251     =head2 CauldronHandler
252    
253     The Jeweler::CauldronHandler package, that helps you with handling the
254     cauldron stuff. Can also be used for other skills.
255    
256     =cut
257    
258     sub new {
259     my ($class, %arg) = @_;
260    
261     my $self = bless {
262     %arg,
263     }, $class;
264    
265     $self;
266     }
267    
268     =over 4
269    
270     =item find_cauldron ($arch_name, @map_stack)
271    
272     This finds the cauldron with C<$arch_name> on the C<@map_stack> and initalises the CauldronHandler.
273     It takes the topmost cauldron that is found. Returns undef if no cauldron was found.
274     Returns the cauldron object if it was found.
275    
276     =cut
277    
278     sub find_cauldron {
279     my ($self, $arch_name, @map_stack) = @_;
280    
281     my @c =
282     grep {
283     $_->flag (cf::FLAG_IS_CAULDRON)
284     and $_->arch->name eq $arch_name
285     } @map_stack;
286    
287     $self->{cauldron} = $c[0];
288     }
289    
290     =item grep_by_type (@types)
291    
292     Finds all objects in the cauldron that have the type of one of C<@types>.
293    
294     =cut
295    
296     sub grep_by_type {
297     my ($self, @types) = @_;
298    
299     return () unless $self->{cauldron};
300    
301     my @res = grep {
302     my $ob = $_;
303     (grep { $ob->type == $_ } @types) > 0
304     } $self->{cauldron}->inv;
305    
306     return @res
307     }
308    
309     =item extract_jeweler_ingredients
310    
311     Extracts the ingredients that matter for the Jeweler skill
312     and returns a Jeweler::Ingredients object.
313    
314     =cut
315    
316     sub extract_jeweler_ingredients {
317     my ($self) = @_;
318    
319     return () unless $self->{cauldron};
320    
321     my $ingreds = {};
322    
323     my %type_to_key = (
324     cf::RING => 'rings',
325     cf::AMULET => 'ammys',
326     cf::INORGANIC => 'mets_and_mins',
327     cf::GEM => 'gems',
328     cf::POTION => 'potions',
329     cf::SCROLL => 'scrolls',
330     );
331    
332     for ($self->{cauldron}->inv) {
333 elmex 1.6 if (!$_->flag (cf::FLAG_IDENTIFIED) && $_->need_identify) {
334     die "unidentified";
335     } elsif ($_->flag (cf::FLAG_CURSED) || $_->flag (cf::FLAG_DAMNED)) {
336 elmex 1.5 die "cursed";
337     }
338 root 1.1
339     if (my $k = $type_to_key{$_->type}) {
340     push @{$ingreds->{$k}}, $_;
341 elmex 1.4 } else {
342     push @{$ingreds->{other}}, $_;
343 root 1.1 }
344     }
345    
346     return Jeweler::Ingredients->new (ingredients => $ingreds, cauldron_helper => $self)
347     }
348    
349     =item put ($object)
350    
351     Just puts the C<$object> into the cauldron.
352    
353     =cut
354    
355     sub put {
356     my ($self, $obj) = @_;
357    
358     return undef unless $self->{cauldron};
359     $obj->insert_ob_in_ob ($self->{cauldron});
360     }
361    
362     =back
363    
364     =cut
365    
366     package Jeweler::Ingredients;
367     use Storable qw/dclone/;
368     use strict;
369    
370     =head2 Ingredients
371    
372     This class handles the ingredients.
373    
374     =over 4
375    
376     =item new (ingredients => $ingred_hash)
377    
378     This is called from the CauldronHandler that gives you the ingredients.
379    
380     =cut
381    
382     sub new {
383     my ($class, %arg) = @_;
384    
385     my $self = bless {
386     %arg,
387     }, $class;
388    
389     $self;
390     }
391    
392     =item value ($group, $archname)
393    
394     Returns the value of the ingredients in C<$group> with the archetypename C<$archname>.
395    
396     =cut
397    
398     sub value {
399     my ($self, $group, $archname) = @_;
400    
401     my @objs = grep {
402     $_->arch->name eq $archname
403     } @{$self->{ingredients}->{$group} || []};
404    
405     my $sum = 0;
406     for (@objs) {
407     $sum += ($_->nrof || 1) * $_->value;
408     }
409    
410     return $sum;
411     }
412    
413     =item remove ($group, $archname)
414    
415     Removes the ingredients in C<$group> with archname C<$archname>.
416     It removes all in C<$group> if archname is undef.
417    
418     =cut
419    
420     sub remove {
421     my ($self, $group, $archname) = @_;
422    
423     my $ingred = $self->{ingredients};
424    
425     my @out;
426    
427     for (@{$ingred->{$group}}) {
428     if (defined $archname) {
429     if ($_->arch->name eq $archname) {
430     Jeweler::Util::remove ($_);
431     } else {
432     push @out, $_;
433     }
434     } else {
435     Jeweler::Util::remove ($_);
436     }
437     }
438    
439     @{$ingred->{$group}} = @out;
440     }
441    
442     sub get_plan {
443     my ($self) = @_;
444    
445     my $ingred = $self->{ingredients};
446    
447 elmex 1.4 for my $plan (keys %{$Jeweler::CFG->{plans}}) {
448     my $plg = $Jeweler::CFG->{plans}->{$plan};
449     my @plga = ();
450     unless (ref $plg eq 'ARRAY') {
451     push @plga, $plg;
452     } else {
453     @plga = @$plg;
454     }
455     next unless @plga > 0;
456     if (Jeweler::Util::grep_for_match ($ingred, @plga)) {
457     return $plan;
458 root 1.1 }
459     }
460     }
461    
462     sub get_ring {
463     my ($self) = @_;
464     return (
465     @{$self->{ingredients}->{ammys} || []},
466     @{$self->{ingredients}->{rings} || []}
467     );
468     }
469    
470 elmex 1.4 sub improve_max {
471     my ($stat, $impro) = @_;
472     if ($stat >= 0) {
473     $stat = $impro > $stat ? $impro : $stat;
474     }
475     $stat
476     }
477    
478 root 1.1 sub improve_ring_by_plan {
479     my ($self, $plan, $ring) = @_;
480    
481     $ring = dclone ($ring);
482    
483     my $ingred = $self->{ingredients};
484     my $impr = {};
485    
486     if ($plan =~ m/^stat_(\S+)$/) {
487     my $statname = $1;
488     my $plingred = Jeweler::getcfg (plans => $plan)
489     or die "ingredients for plan '$plan' not defined!";
490    
491     my $cnt = 0;
492 elmex 1.4 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
493     $cnt += $pot->nrof;
494 root 1.1 }
495    
496     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
497     for my $x (1..$maxstat) {
498 elmex 1.4 my $y = Jeweler::Object::fx ($x, 'stat_items');
499 root 1.1
500     if ($cnt <= $y->[0]) {
501 elmex 1.4 $ring->{hash}->{stat}->{$statname} =
502     improve_max $ring->{hash}->{stat}->{$statname}, $x;
503 root 1.1 last;
504     }
505     }
506    
507 elmex 1.4 } elsif ($plan =~ m/^spec_(\S+)$/) {
508     my $specname = $1;
509     my $plingred = Jeweler::getcfg (plans => $plan)
510     or die "ingredients for plan '$plan' not defined!";
511    
512     my $cnt = 0;
513     if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
514     $cnt += $pot->nrof;
515     }
516    
517     my $maxspec = Jeweler::getcfg (maximprovements => 'specials');
518     for my $x (1..$maxspec) {
519     my $y = Jeweler::Object::fx ($x, 'spec_items');
520    
521     if ($cnt <= $y->[0]) {
522     $ring->{hash}->{spec}->{$specname} =
523     improve_max $ring->{hash}->{spec}->{$specname}, $x;
524     last;
525     }
526     }
527 root 1.1
528     } elsif ($plan =~ m/^resist_(\S+)$/) {
529 elmex 1.4 my $resname = $1;
530     my $resnum = $REV_RESMAP{$resname};
531     my $plingred = Jeweler::getcfg (plans => $plan)
532     or die "ingredients for plan '$plan' not defined!";
533    
534     my $cnt = 0;
535     if (my $it = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
536     $cnt += $it->nrof;
537     }
538     my $resist_item_nr = 0;
539     $self->do_grep (sub { $resist_item_nr += ($_[0]->nrof || 1); 0 }, @$plingred);
540    
541     my $maximprovname = (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS)
542     ? 'effect_resistances'
543     : 'attack_resistances';
544    
545     my $maxres = Jeweler::getcfg (maximprovements => $maximprovname);
546     $resist_item_nr = $maxres if ($resist_item_nr > $maxres);
547     $ring->{hash}->{resist}->{$resnum} =
548     improve_max $ring->{hash}->{resist}->{$resnum}, $resist_item_nr;
549 root 1.1 }
550    
551     return $ring;
552     }
553    
554     sub do_grep {
555 elmex 1.4 my ($self, $cb, $cat, @grepar) = @_;
556 root 1.1
557     my $ingred = $self->{ingredients};
558    
559 elmex 1.4 my @rem;
560     for my $ing (@{$ingred->{$cat}}) {
561     if (Jeweler::Util::check_for_match ($ing, @grepar)) {
562     unless ($cb->($ing)) {
563 root 1.1 push @rem, $ing;
564     }
565 elmex 1.4 } else {
566     push @rem, $ing;
567 root 1.1 }
568     }
569 elmex 1.4 @{$ingred->{$cat}} = @rem;
570 root 1.1 }
571    
572     sub check_costs {
573     my ($self, $costs, $do_remove) = @_;
574    
575     my $costs = dclone ($costs);
576    
577     for my $key (keys %$costs) {
578     my @grepar;
579 elmex 1.4 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
580 root 1.1 @grepar = @{Jeweler::getcfg (plans => $key) || []};
581 elmex 1.4 } else { # check the gems
582     @grepar = ('gems', undef, undef, $key);
583 root 1.1 }
584    
585     if ($do_remove) {
586     my $rem = $costs->{$key};
587     $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
588     if ($rem > 0) {
589     warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
590     }
591     } else {
592     my $nr;
593 elmex 1.4 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
594 root 1.1 $costs->{$key} -= $nr;
595     }
596 elmex 1.4
597 root 1.1 }
598    
599     return $costs;
600     }
601    
602     =back
603    
604     =cut
605    
606     sub put_to_bench {
607     my ($self, $bench) = @_;
608    
609     my $ingred = $self->{ingredients};
610    
611     for my $ik (keys %$ingred) {
612     for (@{$ingred->{$ik} || []}) {
613     $bench->put ($_);
614     }
615     }
616     }
617    
618     package Jeweler::Object;
619     use strict;
620     use POSIX;
621     use List::Util qw/max min sum/;
622    
623     sub new {
624     my ($class, %arg) = @_;
625    
626     my $self = bless { }, $class;
627    
628     $self->ring_or_ammy_to_hash ($arg{object});
629    
630     $self;
631     }
632    
633 elmex 1.7 sub has_resist {
634     my ($self, $resistnam, $resistval) = @_;
635     my $resnum = $REV_RESMAP{uc $resistnam};
636     if (defined ($resistval)) {
637     return 1 if $self->{hash}->{resist}->{$resnum} == $resistval;
638     } else {
639     return 1 if $self->{hash}->{resist}->{$resnum};
640     }
641     return undef;
642     }
643    
644 root 1.1 sub analyze {
645     my ($self, $sk, $pl) = @_;
646    
647     my $sklvl = cf::exp_to_level ($sk->stats->exp);
648     my $ringlvl = $self->power_to_level;
649    
650     my $tmpl;
651     if ($pl->flag (cf::FLAG_WIZ)) {
652     $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
653     } else {
654     $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
655     }
656     my $msg = sprintf "Projected success rate: %s", $tmpl;
657     return $msg;
658     }
659    
660     sub wiz_analyze {
661     my ($self, $pl) = @_;
662     my $costs = $self->calc_costs;
663     my $desc = "";
664     my $lvl = $self->power_to_level (\$desc);
665 elmex 1.5 my $emarch = cf::arch::find 'emerald';
666     my $saarch = cf::arch::find 'sapphire';
667     my $pearch = cf::arch::find 'pearl';
668     my $ruarch = cf::arch::find 'ruby';
669     my $diarch = cf::arch::find 'gem';
670     my $scosts = $emarch->clone->value * $costs->{emerald}
671     + $saarch->clone->value * $costs->{sapphire}
672     + $pearch->clone->value * $costs->{pearl}
673     + $ruarch->clone->value * $costs->{ruby}
674     + $diarch->clone->value * $costs->{gem};
675    
676     $pl->message ("costs: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
677 root 1.1 $pl->message ("level: " . $desc);
678     }
679    
680    
681     sub get_chance_perc {
682     my ($self, $sk) = @_;
683     my $sklvl = cf::exp_to_level ($sk->stats->exp);
684     my $ringlvl = $self->power_to_level;
685     return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
686     }
687    
688     sub fx {
689     my ($res, $cfg) = @_;
690     my $or = $res;
691     my $ar = $Jeweler::CFG->{functions}->{$cfg};
692     if (ref $ar->[0] eq 'ARRAY') {
693     $res = $res - 1;
694     } else {
695     $res = ceil ($res / 5) - 1;
696     }
697     $ar->[max (min ($res, @$ar - 1), 0)];
698     }
699    
700     sub improve_by_ring {
701     my ($self, @rings) = @_;
702     my $ring = $self;
703     for my $iring (@rings) {
704     for my $cat (qw/stat spec resist/) {
705     for my $k (keys %{$iring->{hash}->{$cat}}) {
706     if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
707     $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
708     }
709     }
710     }
711     }
712     }
713    
714     sub negate {
715     my ($self) = @_;
716     for my $cat (qw/stat spec resist/) {
717     for my $k (keys %{$self->{hash}->{$cat}}) {
718     if ($self->{hash}->{$cat}->{$k} > 0) {
719     $self->{hash}->{$cat}->{$k} *= -1;
720     }
721     }
722     }
723     }
724    
725     sub to_string {
726     my ($self) = @_;
727     my $r = $self->{hash};
728     return
729     $r->{arch} . " " .
730     join ("",
731     grep { $_ ne "" }
732     join ("",
733     (map {
734     my $rv = $r->{resist}->{$_};
735     "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
736     } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
737     (map {
738     my $rv = $r->{stat}->{$_};
739     "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
740     } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
741     (map {
742     my $rv = $r->{spec}->{$_};
743     "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
744     } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
745     }
746    
747     sub ring_or_ammy_to_hash {
748     my ($self, $thing) = @_;
749    
750     my $obj = {};
751    
752     for (@Jeweler::RESISTS) {
753 root 1.3 $obj->{resist}->{$_} = $thing->resist ($_);
754 root 1.1 }
755    
756     my $stats = $thing->stats;
757    
758     for (qw/Str Dex Con Wis Cha Int Pow/) {
759     $obj->{stat}->{lc $_} = $stats->$_;
760     }
761    
762     $obj->{spec}{regen} = $stats->hp;
763     $obj->{spec}{magic} = $stats->sp;
764     $obj->{spec}{wc} = $stats->wc;
765     $obj->{spec}{dam} = $stats->dam;
766     $obj->{spec}{ac} = $stats->ac;
767     $obj->{spec}{speed} = $stats->exp;
768     $obj->{spec}{food} = $stats->food;
769    
770     $obj->{name} = $thing->name;
771     $obj->{arch} = $thing->arch->name;
772     $obj->{face} = $thing->face;
773    
774     $self->{hash} = $obj
775     }
776    
777     sub to_object {
778     my ($self) = @_;
779    
780     my $obj = cf::object::new $self->{hash}->{arch};
781    
782 elmex 1.6 $obj->item_power ($self->power_to_level); # there have to be strings attached!
783    
784 root 1.1 $obj->face ($self->{hash}{face});
785    
786     my $stats = $obj->stats;
787    
788     $stats->hp ($self->{hash}{spec}{regen});
789     $stats->sp ($self->{hash}{spec}{magic});
790     $stats->wc ($self->{hash}{spec}{wc});
791     $stats->dam ($self->{hash}{spec}{dam});
792     $stats->ac ($self->{hash}{spec}{ac});
793     $stats->exp ($self->{hash}{spec}{speed});
794     $stats->food ($self->{hash}{spec}{food});
795    
796     $stats->$_ ($self->{hash}{stat}{lc $_})
797     for qw/Str Dex Con Wis Cha Int Pow/;
798    
799     for (@Jeweler::RESISTS) {
800 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
801 root 1.1 }
802    
803     $obj->flag (cf::FLAG_IDENTIFIED, 1);
804    
805     return $obj;
806     }
807    
808 elmex 1.4 sub is_better_than {
809     my ($self, $other) = @_;
810    
811     for my $type (qw/spec stat resist/) {
812     for my $stat (keys %{$self->{hash}->{$type}}) {
813     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
814     return 1;
815     }
816     }
817     }
818    
819     return 0;
820     }
821    
822 root 1.1 sub stat_level {
823     my ($self) = @_;
824     my $stats = $self->{hash}->{stat} || {};
825    
826     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
827     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
828    
829     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
830     my $stat_sum = sum (values %$stats); # also count the negative stats!
831     my $level = int (($maxlevel / $maxstat) * $stat_sum);
832    
833     ($level, $stat_cnt)
834     }
835    
836     sub resist_level {
837     my ($self) = @_;
838    
839     my $resists = $self->{hash}->{resist} || {};
840    
841     my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
842     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
843     my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
844     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
845     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
846    
847     my $ressum = 0;
848     my $rescnt = 0;
849     my @reslevels;
850    
851     for my $resnam (keys %$resists) {
852     my $res = $resists->{$resnam};
853    
854     $rescnt++
855     if $res > 0; # negative resistancies are not an improvement
856    
857     $ressum += $res; # note: negative resistancies lower the sum
858    
859     next unless $res > 0;
860    
861     my $level = 0;
862     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
863     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
864     } else {
865     $level = ceil (($att_res_lvl / $max_att_res) * $res);
866     }
867     push @reslevels, $level;
868     }
869    
870     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
871    
872     (max (@reslevels, $overall_lvl), $rescnt);
873     }
874    
875     sub special_level {
876     my ($self) = @_;
877    
878     my $specials = $self->{hash}->{spec} || {};
879    
880     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
881     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
882    
883     my @speclvls;
884     my $specsum = 0;
885     my $imprs = 0;
886    
887     for my $spcnam (keys %$specials) {
888     my $spc = $specials->{$spcnam};
889     next unless $spc > 0;
890    
891     $specsum += $spc;
892     $imprs++;
893    
894     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
895    
896     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
897     push @speclvls, $lvl;
898     }
899    
900     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
901    
902     (max (@speclvls, $sumlvl), $imprs)
903     }
904    
905    
906     # this function calculated the 'level' of an amulet or a ring
907     sub power_to_level {
908     my ($self, $lvldescr) = @_;
909    
910     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
911     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
912    
913     my ($stat_lvl, $stat_imprs) = $self->stat_level;
914     my ($resist_lvl, $res_imprs) = $self->resist_level;
915     my ($spec_lvl, $spec_imprs) = $self->special_level;
916    
917     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
918    
919     my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
920    
921     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
922    
923     if ($lvldescr) {
924     $$lvldescr =
925     sprintf "%3d: %s\n", $levl,
926     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
927     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
928     }
929    
930     $levl
931     }
932    
933     sub add_stat_costs {
934     my ($self, $cost) = @_;
935    
936     my $stats = $self->{hash}->{stat};
937    
938     for my $stat (keys %$stats) {
939     my $sum = $stats->{$stat};
940    
941     next unless $sum > 0;
942    
943 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
944 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
945     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
946     }
947     }
948    
949     sub add_special_costs {
950     my ($self, $cost) = @_;
951    
952     my $specials = $self->{hash}->{spec};
953    
954     for my $spec (keys %$specials) {
955     my $sum = $specials->{$spec};
956    
957     next unless $sum > 0;
958    
959 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
960 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
961     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
962     }
963     }
964    
965     sub calc_costs {
966     my ($self) = @_;
967    
968     my $costs = {};
969    
970     my $ring = $self->{hash};
971    
972 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
973 root 1.1
974 elmex 1.4 my $res = $ring->{resist}->{$resnum};
975 root 1.1
976     next unless $res > 0;
977    
978 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
979 root 1.1
980     my $diamonds;
981 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
982 root 1.1 $diamonds += fx ($res, 'effect_resist_diamonds');
983     } else {
984     $diamonds += fx ($res, 'attack_resist_diamonds');
985     }
986    
987 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
988 root 1.1 }
989    
990     $self->add_stat_costs ($costs);
991     $self->add_special_costs ($costs);
992    
993     return $costs;
994     }
995    
996     sub split_diamonds {
997     my ($cost, $diamonds, $category) = @_;
998    
999     my $stat_split = Jeweler::getcfg (diamond_split => $category);
1000    
1001     my $sum = sum (@$stat_split);
1002     if ($sum < (1 - 0.0001)) {
1003     warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
1004     }
1005    
1006     my $emarch = cf::arch::find 'emerald';
1007     my $saarch = cf::arch::find 'sapphire';
1008     my $pearch = cf::arch::find 'pearl';
1009     my $ruarch = cf::arch::find 'ruby';
1010     my $diarch = cf::arch::find 'gem';
1011    
1012     my $sumvalue = $diarch->clone->value * $diamonds;
1013    
1014     $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
1015     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
1016     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
1017     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
1018     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
1019     }
1020    
1021     package Jeweler::Util;
1022    
1023     use strict;
1024    
1025     =head2 Util
1026    
1027     Some utility functions for the Jeweler skill.
1028    
1029     =over 4
1030    
1031     =item remove ($object[, $nrof])
1032    
1033     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1034     The returnvalue is the number of 'single' objects that couldn't be removed.
1035    
1036     =cut
1037    
1038     sub remove {
1039     my ($obj, $nrof) = @_;
1040    
1041     my $cnt;
1042    
1043     if (defined $nrof) {
1044 elmex 1.4 # TODO: Check tihis line:
1045     return 0 if ($nrof * 1) == 0; #XXX: ???
1046 root 1.1 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1047    
1048     if ($cnt > 0) {
1049     $obj->nrof ($cnt);
1050     return 0;
1051     }
1052     }
1053    
1054     remove ($_) for $obj->inv;
1055     $obj->destroy;
1056     return $cnt;
1057     }
1058    
1059 elmex 1.4 sub check_for_match {
1060 root 1.1 my ($thing, @matchar) = @_;
1061    
1062     my $i = 0;
1063     for my $match (@matchar) {
1064     if ($match =~ m/^\s*$/) {
1065     $i++;
1066     next;
1067     }
1068    
1069     if ($i % 3 == 0) {
1070     $thing->name eq $match
1071     and return 1;
1072     } elsif ($i % 3 == 1) {
1073     $thing->title eq $match
1074     and return 1;
1075     } else { # $i % 3 == 2
1076     $thing->arch->name eq $match
1077     and return 1;
1078     }
1079     $i++;
1080     }
1081     return 0;
1082     }
1083    
1084 elmex 1.4 sub grep_for_match {
1085     my ($ingred, $group, @matchar) = @_;
1086    
1087     for my $thing (@{$ingred->{$group} || []}) {
1088     warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->name, "@matchar"; #d#
1089     if (check_for_match ($thing, @matchar)) {
1090     return $thing;
1091     }
1092     }
1093     return undef;
1094     }
1095    
1096 root 1.1 =back
1097    
1098     1