ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.4
Committed: Wed Jan 31 14:11:02 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.3: +126 -52 lines
Log Message:
finally finished the last bits of the jeweler skill. now only debugging
and balancing is missing. going to make some maps next and try to sort
out the right balancing.

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