ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.5
Committed: Wed Jan 31 15:53:17 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.4: +15 -2 lines
Log Message:
Changed jeweler balancing and made values affordable.
And also don't allow cursed items in the workbench.

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