ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.23
Committed: Mon Dec 17 08:03:22 2007 UTC (16 years, 5 months ago) by root
Branch: MAIN
CVS Tags: rel-2_4, rel-2_41
Changes since 1.22: +4 -5 lines
Log Message:
- separate internal (undefined type) and client-exported (else)
  resources.
- new %cf::RESOURCE hash for internal resources
- move jeweler.yaml to archetype ("resource") data
- support resource data filters and implement yaml2json
  (this reduced loading time for the jeweler data by a factor of 1000 :).
- clean up Jeweler.pm a bit.

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 root 1.23 if (my $meta = $cf::RESOURCE{$filename}) {
30     $CFG = cf::decode_json $meta->{data};
31     } else {
32     warn "$filename doesn't exist! no config for jeweler skill loaded!\n";
33 root 1.1 $CFG = {};
34     }
35     }
36    
37     sub getcfg {
38     my ($sect, $key) = @_;
39     return $CFG->{$sect} unless defined $key;
40    
41     my $cfg = $CFG->{$sect}->{$key}
42     or die "Couldn't find $sect/$key in configuration!";
43    
44     $cfg
45     }
46    
47     our @RESISTS = (
48     cf::ATNR_PHYSICAL,
49     cf::ATNR_MAGIC,
50     cf::ATNR_FIRE,
51     cf::ATNR_ELECTRICITY,
52     cf::ATNR_COLD,
53     cf::ATNR_CONFUSION,
54    
55     cf::ATNR_ACID,
56     cf::ATNR_DRAIN,
57     cf::ATNR_GHOSTHIT,
58     cf::ATNR_POISON,
59     cf::ATNR_SLOW,
60     cf::ATNR_PARALYZE,
61    
62     cf::ATNR_TURN_UNDEAD,
63     cf::ATNR_FEAR,
64     cf::ATNR_DEPLETE,
65     cf::ATNR_DEATH,
66     cf::ATNR_HOLYWORD,
67     cf::ATNR_LIFE_STEALING,
68    
69     cf::ATNR_BLIND,
70     cf::ATNR_DISEASE,
71     );
72    
73     =item @EFFECT_RESISTS
74    
75     List of all effect resistancies that occur on rings and amulets.
76     The difference is made because effect resistancies are less effective at lower levels.
77    
78     =back
79    
80     =cut
81    
82     our @EFFECT_RESISTS = (
83     cf::ATNR_CONFUSION,
84     cf::ATNR_DRAIN,
85     cf::ATNR_POISON,
86     cf::ATNR_SLOW,
87     cf::ATNR_PARALYZE,
88     cf::ATNR_TURN_UNDEAD,
89     cf::ATNR_FEAR,
90     cf::ATNR_DEPLETE,
91     cf::ATNR_DEATH,
92     cf::ATNR_BLIND,
93     cf::ATNR_DISEASE,
94     );
95    
96     our %RESMAP = (
97     cf::ATNR_PHYSICAL => "PHYSICAL",
98     cf::ATNR_MAGIC => "MAGIC",
99     cf::ATNR_FIRE => "FIRE",
100     cf::ATNR_ELECTRICITY => "ELECTRICITY",
101     cf::ATNR_COLD => "COLD",
102     cf::ATNR_CONFUSION => "CONFUSION",
103     cf::ATNR_ACID => "ACID",
104    
105     cf::ATNR_DRAIN => "DRAIN",
106     cf::ATNR_GHOSTHIT => "GHOSTHIT",
107     cf::ATNR_POISON => "POISON",
108     cf::ATNR_SLOW => "SLOW",
109     cf::ATNR_PARALYZE => "PARALYZE",
110     cf::ATNR_TURN_UNDEAD => "TURN_UNDEAD",
111    
112     cf::ATNR_FEAR => "FEAR",
113     cf::ATNR_DEPLETE => "DEPLETE",
114     cf::ATNR_DEATH => "DEATH",
115     cf::ATNR_HOLYWORD => "HOLYWORD",
116     cf::ATNR_LIFE_STEALING => "LIFE_STEALING",
117     cf::ATNR_BLIND => "BLIND",
118     cf::ATNR_DISEASE => "DISEASE",
119     );
120    
121 elmex 1.4 our %REV_RESMAP = map { $RESMAP{$_} => $_ } keys %RESMAP;
122    
123 root 1.1 our %LVL_DIFF_CHANCES = (
124     +5 => 100,
125     +4 => 95,
126     +3 => 85,
127     +2 => 75,
128     +1 => 65,
129     0 => 50,
130     -1 => 45,
131     -2 => 35,
132     -3 => 25,
133     -4 => 10,
134     -5 => 0
135     );
136    
137     our %LVL_DIFF_MSG = (
138     -5 => 'Way above your skill',
139     -4 => 'Very low',
140     -3 => 'Slight chance',
141     -2 => 'Low',
142     -1 => 'Nearly 50:50',
143     0 => '50:50',
144     1 => 'Slightly above 50:50',
145     2 => 'Good',
146     3 => 'High',
147     4 => 'Nearly confident',
148     5 => '100%',
149     );
150    
151     sub level_diff_to_str {
152     my ($delta) = @_;
153     $delta = -5 if $delta < -5;
154     $delta = 5 if $delta > 5;
155     return $LVL_DIFF_MSG{$delta}
156     }
157    
158     sub level_diff_to_chance_perc {
159     my ($delta) = @_;
160     $delta = -5 if $delta < -5;
161     $delta = 5 if $delta > 5;
162     return $LVL_DIFF_CHANCES{$delta}
163     }
164    
165     sub analyze {
166 elmex 1.18 my ($sk, $chdl, $pl, $input_level) = @_;
167 root 1.1
168     my $hadunid = 0;
169     for ($chdl->grep_by_type (cf::RING, cf::AMULET)) {
170     if (!$_->flag (cf::FLAG_IDENTIFIED) && $_->need_identify) {
171     $hadunid = 1;
172     next;
173     }
174     my $r = Jeweler::Object->new (object => $_);
175 elmex 1.18 my $msg = $r->analyze ($sk, $pl, $input_level);
176 root 1.1 $pl->message ($r->to_string . ": " . $msg);
177     if ($pl->flag (cf::FLAG_WIZ)) {
178     $r->wiz_analyze ($pl);
179     }
180     }
181 root 1.22 $pl->message ("You couldn't identify the other rings and not analyze them!")
182     if $hadunid;
183 root 1.1 }
184    
185     # this function converts metals/minerals into a raw ring (of adornment)
186     sub simple_converter {
187     my ($pl, $ingred, $chdl, $conv) = @_;
188    
189     $conv = lc $conv;
190     my $cnvs = $CFG->{conversions};
191    
192     return unless $cnvs->{$conv};
193    
194     my %ingred_groups;
195    
196     my @conv_cfg = @{$cnvs->{$conv}};
197     my $outarch = $conv;
198     my ($ingr_grp, $outarchvalfact, $srcarchname, $xp_gain) = @conv_cfg;
199    
200     unless (@conv_cfg <= 4) {
201     warn "ERROR: Conversion for '$outarch' has only " . (@conv_cfg) . " arguments!";
202     return;
203     }
204    
205     unless ($xp_gain > 0) {
206 root 1.22 warn "WARNING: xp gain isn't > 0 in conversion '$outarch'\n";
207 root 1.1 return;
208     }
209    
210     unless ($outarchvalfact) {
211     warn "ERROR: source-arch-value-multiplier == 0 in conversion '$outarch'\n";
212     return;
213     }
214    
215     unless ($outarchvalfact >= 1) {
216     warn "WARNING: source-arch-value-multiplier < 1 in conversion '$outarch', results in more valuable output!\n";
217     }
218    
219     my $archvalsum = $ingred->value ($ingr_grp, $srcarchname);
220     $ingred->remove ($ingr_grp, $srcarchname);
221    
222 root 1.16 my $outarchval = cf::arch::find ($outarch)->value;
223 root 1.1
224 root 1.22 my $nrof = int $archvalsum / (($outarchval || 1000) * $outarchvalfact);
225 root 1.1 if ($nrof) {
226 root 1.22 # XXX: yes, I know what I'm doing here, I don't set nrof, but it didn't work somehow (pls. check sometimes)
227     for (1 .. $nrof) {
228 elmex 1.4 $chdl->put (my $ob = cf::object::new $outarch);
229 elmex 1.8 $ob->set_animation (cf::rndm $ob->num_animations)
230     if ($ob->type == cf::RING);
231 elmex 1.4 $ob->flag (cf::FLAG_IDENTIFIED, 1);
232     }
233 root 1.1
234 root 1.22 my $xp_sum = $xp_gain * $nrof;
235 root 1.1
236     if ($xp_sum) {
237     $pl->ob->message ("You got $xp_sum xp by making $nrof ${outarch}s");
238 elmex 1.4 $pl->ob->change_exp ($xp_sum, "jeweler", cf::SK_EXP_SKILL_ONLY);
239 root 1.1 }
240 elmex 1.4 } else {
241 root 1.15 $pl->ob->message ("You fail to make something, probably you used not enough source material?");
242 root 1.1 }
243     }
244    
245    
246     package Jeweler::CauldronHandler;
247    
248     use strict;
249    
250     =head2 CauldronHandler
251    
252     The Jeweler::CauldronHandler package, that helps you with handling the
253     cauldron stuff. Can also be used for other skills.
254    
255     =cut
256    
257     sub new {
258     my ($class, %arg) = @_;
259    
260     my $self = bless {
261     %arg,
262     }, $class;
263    
264     $self;
265     }
266    
267     =over 4
268    
269     =item find_cauldron ($arch_name, @map_stack)
270    
271     This finds the cauldron with C<$arch_name> on the C<@map_stack> and initalises the CauldronHandler.
272     It takes the topmost cauldron that is found. Returns undef if no cauldron was found.
273     Returns the cauldron object if it was found.
274    
275     =cut
276    
277     sub find_cauldron {
278     my ($self, $arch_name, @map_stack) = @_;
279    
280     my @c =
281     grep {
282     $_->flag (cf::FLAG_IS_CAULDRON)
283 elmex 1.17 and $_->arch->archname eq $arch_name
284 root 1.1 } @map_stack;
285    
286     $self->{cauldron} = $c[0];
287     }
288    
289     =item grep_by_type (@types)
290    
291     Finds all objects in the cauldron that have the type of one of C<@types>.
292    
293     =cut
294    
295     sub grep_by_type {
296     my ($self, @types) = @_;
297    
298     return () unless $self->{cauldron};
299    
300     my @res = grep {
301     my $ob = $_;
302     (grep { $ob->type == $_ } @types) > 0
303     } $self->{cauldron}->inv;
304    
305     return @res
306     }
307    
308     =item extract_jeweler_ingredients
309    
310     Extracts the ingredients that matter for the Jeweler skill
311     and returns a Jeweler::Ingredients object.
312    
313     =cut
314    
315     sub extract_jeweler_ingredients {
316     my ($self) = @_;
317    
318     return () unless $self->{cauldron};
319    
320     my $ingreds = {};
321    
322     my %type_to_key = (
323     cf::RING => 'rings',
324     cf::AMULET => 'ammys',
325     cf::INORGANIC => 'mets_and_mins',
326     cf::GEM => 'gems',
327     cf::POTION => 'potions',
328     cf::SCROLL => 'scrolls',
329     );
330    
331     for ($self->{cauldron}->inv) {
332 elmex 1.6 if (!$_->flag (cf::FLAG_IDENTIFIED) && $_->need_identify) {
333     die "unidentified";
334     } elsif ($_->flag (cf::FLAG_CURSED) || $_->flag (cf::FLAG_DAMNED)) {
335 elmex 1.5 die "cursed";
336     }
337 root 1.1
338     if (my $k = $type_to_key{$_->type}) {
339     push @{$ingreds->{$k}}, $_;
340 elmex 1.4 } else {
341     push @{$ingreds->{other}}, $_;
342 root 1.1 }
343     }
344    
345     return Jeweler::Ingredients->new (ingredients => $ingreds, cauldron_helper => $self)
346     }
347    
348     =item put ($object)
349    
350     Just puts the C<$object> into the cauldron.
351    
352     =cut
353    
354     sub put {
355     my ($self, $obj) = @_;
356    
357     return undef unless $self->{cauldron};
358 root 1.20 $self->{cauldron}->insert ($obj);
359 root 1.1 }
360    
361     =back
362    
363     =cut
364    
365     package Jeweler::Ingredients;
366     use Storable qw/dclone/;
367     use strict;
368    
369     =head2 Ingredients
370    
371     This class handles the ingredients.
372    
373     =over 4
374    
375     =item new (ingredients => $ingred_hash)
376    
377     This is called from the CauldronHandler that gives you the ingredients.
378    
379     =cut
380    
381     sub new {
382     my ($class, %arg) = @_;
383    
384     my $self = bless {
385     %arg,
386     }, $class;
387    
388     $self;
389     }
390    
391     =item value ($group, $archname)
392    
393     Returns the value of the ingredients in C<$group> with the archetypename C<$archname>.
394    
395     =cut
396    
397     sub value {
398     my ($self, $group, $archname) = @_;
399    
400     my @objs = grep {
401 elmex 1.17 $_->arch->archname eq $archname
402 root 1.1 } @{$self->{ingredients}->{$group} || []};
403    
404     my $sum = 0;
405     for (@objs) {
406     $sum += ($_->nrof || 1) * $_->value;
407     }
408    
409     return $sum;
410     }
411    
412     =item remove ($group, $archname)
413    
414     Removes the ingredients in C<$group> with archname C<$archname>.
415     It removes all in C<$group> if archname is undef.
416    
417     =cut
418    
419     sub remove {
420     my ($self, $group, $archname) = @_;
421    
422     my $ingred = $self->{ingredients};
423    
424     my @out;
425    
426     for (@{$ingred->{$group}}) {
427     if (defined $archname) {
428 elmex 1.17 if ($_->arch->archname eq $archname) {
429 root 1.1 Jeweler::Util::remove ($_);
430     } else {
431     push @out, $_;
432     }
433     } else {
434     Jeweler::Util::remove ($_);
435     }
436     }
437    
438     @{$ingred->{$group}} = @out;
439     }
440    
441     sub get_plan {
442     my ($self) = @_;
443    
444     my $ingred = $self->{ingredients};
445    
446 elmex 1.4 for my $plan (keys %{$Jeweler::CFG->{plans}}) {
447     my $plg = $Jeweler::CFG->{plans}->{$plan};
448     my @plga = ();
449     unless (ref $plg eq 'ARRAY') {
450     push @plga, $plg;
451     } else {
452     @plga = @$plg;
453     }
454     next unless @plga > 0;
455     if (Jeweler::Util::grep_for_match ($ingred, @plga)) {
456     return $plan;
457 root 1.1 }
458     }
459     }
460    
461     sub get_ring {
462     my ($self) = @_;
463     return (
464     @{$self->{ingredients}->{ammys} || []},
465     @{$self->{ingredients}->{rings} || []}
466     );
467     }
468    
469 elmex 1.4 sub improve_max {
470     my ($stat, $impro) = @_;
471     if ($stat >= 0) {
472     $stat = $impro > $stat ? $impro : $stat;
473     }
474     $stat
475     }
476    
477 root 1.1 sub improve_ring_by_plan {
478     my ($self, $plan, $ring) = @_;
479    
480 root 1.21 $ring = do { my $guard = Coro::Storable::guard; dclone $ring };
481 root 1.1
482     my $ingred = $self->{ingredients};
483     my $impr = {};
484    
485     if ($plan =~ m/^stat_(\S+)$/) {
486     my $statname = $1;
487     my $plingred = Jeweler::getcfg (plans => $plan)
488     or die "ingredients for plan '$plan' not defined!";
489    
490     my $cnt = 0;
491 elmex 1.4 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
492     $cnt += $pot->nrof;
493 root 1.1 }
494    
495     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
496     for my $x (1..$maxstat) {
497 elmex 1.4 my $y = Jeweler::Object::fx ($x, 'stat_items');
498 root 1.1
499     if ($cnt <= $y->[0]) {
500 elmex 1.4 $ring->{hash}->{stat}->{$statname} =
501     improve_max $ring->{hash}->{stat}->{$statname}, $x;
502 root 1.1 last;
503     }
504     }
505    
506 elmex 1.4 } elsif ($plan =~ m/^spec_(\S+)$/) {
507     my $specname = $1;
508     my $plingred = Jeweler::getcfg (plans => $plan)
509     or die "ingredients for plan '$plan' not defined!";
510    
511     my $cnt = 0;
512     if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
513     $cnt += $pot->nrof;
514     }
515    
516     my $maxspec = Jeweler::getcfg (maximprovements => 'specials');
517     for my $x (1..$maxspec) {
518     my $y = Jeweler::Object::fx ($x, 'spec_items');
519    
520     if ($cnt <= $y->[0]) {
521     $ring->{hash}->{spec}->{$specname} =
522     improve_max $ring->{hash}->{spec}->{$specname}, $x;
523     last;
524     }
525     }
526 root 1.1
527     } elsif ($plan =~ m/^resist_(\S+)$/) {
528 elmex 1.4 my $resname = $1;
529     my $resnum = $REV_RESMAP{$resname};
530     my $plingred = Jeweler::getcfg (plans => $plan)
531     or die "ingredients for plan '$plan' not defined!";
532    
533     my $cnt = 0;
534     if (my $it = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
535     $cnt += $it->nrof;
536     }
537     my $resist_item_nr = 0;
538     $self->do_grep (sub { $resist_item_nr += ($_[0]->nrof || 1); 0 }, @$plingred);
539    
540     my $maximprovname = (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS)
541     ? 'effect_resistances'
542     : 'attack_resistances';
543    
544     my $maxres = Jeweler::getcfg (maximprovements => $maximprovname);
545     $resist_item_nr = $maxres if ($resist_item_nr > $maxres);
546     $ring->{hash}->{resist}->{$resnum} =
547     improve_max $ring->{hash}->{resist}->{$resnum}, $resist_item_nr;
548 root 1.1 }
549    
550     return $ring;
551     }
552    
553     sub do_grep {
554 elmex 1.4 my ($self, $cb, $cat, @grepar) = @_;
555 root 1.1
556     my $ingred = $self->{ingredients};
557    
558 elmex 1.4 my @rem;
559     for my $ing (@{$ingred->{$cat}}) {
560     if (Jeweler::Util::check_for_match ($ing, @grepar)) {
561     unless ($cb->($ing)) {
562 root 1.1 push @rem, $ing;
563     }
564 elmex 1.4 } else {
565     push @rem, $ing;
566 root 1.1 }
567     }
568 elmex 1.4 @{$ingred->{$cat}} = @rem;
569 root 1.1 }
570    
571     sub check_costs {
572     my ($self, $costs, $do_remove) = @_;
573    
574 root 1.21 my $costs = do { my $guard = Coro::Storable::guard; dclone $costs };
575 root 1.1
576     for my $key (keys %$costs) {
577     my @grepar;
578 elmex 1.4 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
579 elmex 1.12 eval { @grepar = @{Jeweler::getcfg (plans => $key) || []} };
580     next if $@;
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 elmex 1.18 sub projected_exp {
645     my ($self, $input_level) = @_;
646    
647     my $lvl = max ($self->power_to_level, 1);
648     my $exp =
649     (cf::level_to_min_exp ($lvl) - cf::level_to_min_exp ($lvl - 1))
650     / (10 + max ($lvl - 1, 0)); # 10 + level times making such a ring
651     # should get you to the rings level at least.
652    
653     if (defined $input_level) {
654     my $subexp =
655     (cf::level_to_min_exp ($input_level)
656     - cf::level_to_min_exp ($input_level - 1))
657     / (10 + max ($input_level - 1, 0)); # see above for comment
658    
659     $exp -= $subexp;
660     $exp = max ($exp, 0);
661    
662     } else {
663     # the experience bonus here is to make level 1 rings give you at least
664     # 100 exp points when making them. This also makes leveling in the
665     # first few levels a bit easier. (probably until around level 5-6).
666     my $expbonus = cf::level_to_min_exp (2) / 10;
667     # this bonus should also only be given for _new_ rings and not for merged
668     # ones - to prevent infinite exp making.
669     $exp += $expbonus;
670     }
671    
672     $exp
673     }
674    
675 root 1.1 sub analyze {
676 elmex 1.18 my ($self, $sk, $pl, $input_level) = @_;
677     my $costs = $self->calc_costs;
678    
679     unless (defined $costs) {
680     return "This ring has a resistancy above 99%, you can't make that.";
681     }
682 root 1.1
683     my $sklvl = cf::exp_to_level ($sk->stats->exp);
684     my $ringlvl = $self->power_to_level;
685    
686     my $tmpl;
687     if ($pl->flag (cf::FLAG_WIZ)) {
688     $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
689     } else {
690     $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
691     }
692 elmex 1.18 my $msg = sprintf "Projected success rate: %s, you would get %d exp for this.", $tmpl, $self->projected_exp ($input_level);
693 root 1.1 return $msg;
694     }
695    
696 elmex 1.9 sub calc_value_from_cost {
697     my ($self, $costs) = @_;
698     my $emarch = cf::arch::find 'emerald';
699     my $saarch = cf::arch::find 'sapphire';
700     my $pearch = cf::arch::find 'pearl';
701     my $ruarch = cf::arch::find 'ruby';
702     my $diarch = cf::arch::find 'gem';
703 root 1.16 my $value = $emarch->value * $costs->{emerald}
704     + $saarch->value * $costs->{sapphire}
705     + $pearch->value * $costs->{pearl}
706     + $ruarch->value * $costs->{ruby}
707     + $diarch->value * $costs->{gem};
708 elmex 1.9
709     $value
710     }
711    
712 root 1.1 sub wiz_analyze {
713     my ($self, $pl) = @_;
714     my $costs = $self->calc_costs;
715 elmex 1.18 if (defined $costs) {
716     my $desc = "";
717     my $lvl = $self->power_to_level (\$desc);
718     my $scosts = $self->calc_value_from_cost ($costs);
719 elmex 1.5
720 elmex 1.18 $pl->message ("costs: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
721     $pl->message ("level: " . $desc);
722     } else {
723     $pl->message ("level: impossible to make, due to impossible resistancy configuration");
724     }
725 root 1.1 }
726    
727     sub get_chance_perc {
728     my ($self, $sk) = @_;
729     my $sklvl = cf::exp_to_level ($sk->stats->exp);
730     my $ringlvl = $self->power_to_level;
731     return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
732     }
733    
734     sub fx {
735     my ($res, $cfg) = @_;
736     my $or = $res;
737     my $ar = $Jeweler::CFG->{functions}->{$cfg};
738 elmex 1.9
739 elmex 1.18 if (ref $ar && ref $ar->[0] eq 'ARRAY') {
740 root 1.1 $res = $res - 1;
741 elmex 1.9 return $ar->[max (min ($res, @$ar - 1), 0)];
742    
743 root 1.1 } else {
744 elmex 1.9 # +0.1 is for a jump to the next index when $res / 5 is exactly 1, 2, 3...
745 elmex 1.18 # old code:
746     #my $idx = ceil (($res / 5) + 0.1) - 1;
747     #my $a = $ar->[max (min ($idx, @$ar - 1), 0)];
748     #my $b = $ar->[max (min ($idx + 1, @$ar - 1), 0)];
749     #my $diff = $b - $a; # use the difference of the cost to the next cost
750     #my $o_cost = $a + ($diff / 5) * ($res % 5); # and do some linear interpolation
751     #return $o_cost;
752     return 0 if $res <= 0;
753     return ($ar / (1 - ($res * 0.01)) - $ar)
754 root 1.1 }
755     }
756    
757     sub improve_by_ring {
758     my ($self, @rings) = @_;
759     my $ring = $self;
760     for my $iring (@rings) {
761     for my $cat (qw/stat spec resist/) {
762     for my $k (keys %{$iring->{hash}->{$cat}}) {
763     if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
764     $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
765     }
766     }
767     }
768     }
769     }
770    
771     sub negate {
772     my ($self) = @_;
773     for my $cat (qw/stat spec resist/) {
774     for my $k (keys %{$self->{hash}->{$cat}}) {
775     if ($self->{hash}->{$cat}->{$k} > 0) {
776     $self->{hash}->{$cat}->{$k} *= -1;
777     }
778     }
779     }
780 elmex 1.9 $self->{hash}{value} = 0;
781 root 1.1 }
782    
783     sub to_string {
784     my ($self) = @_;
785     my $r = $self->{hash};
786     return
787     $r->{arch} . " " .
788     join ("",
789     grep { $_ ne "" }
790     join ("",
791     (map {
792     my $rv = $r->{resist}->{$_};
793     "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
794     } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
795     (map {
796     my $rv = $r->{stat}->{$_};
797     "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
798     } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
799     (map {
800     my $rv = $r->{spec}->{$_};
801     "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
802     } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
803     }
804    
805     sub ring_or_ammy_to_hash {
806     my ($self, $thing) = @_;
807    
808     my $obj = {};
809    
810     for (@Jeweler::RESISTS) {
811 root 1.3 $obj->{resist}->{$_} = $thing->resist ($_);
812 root 1.1 }
813    
814     my $stats = $thing->stats;
815    
816     for (qw/Str Dex Con Wis Cha Int Pow/) {
817     $obj->{stat}->{lc $_} = $stats->$_;
818     }
819    
820     $obj->{spec}{regen} = $stats->hp;
821     $obj->{spec}{magic} = $stats->sp;
822     $obj->{spec}{wc} = $stats->wc;
823     $obj->{spec}{dam} = $stats->dam;
824     $obj->{spec}{ac} = $stats->ac;
825     $obj->{spec}{speed} = $stats->exp;
826     $obj->{spec}{food} = $stats->food;
827    
828     $obj->{name} = $thing->name;
829 elmex 1.17 $obj->{arch} = $thing->arch->archname;
830 root 1.1 $obj->{face} = $thing->face;
831    
832 elmex 1.9 $obj->{value} = $thing->value;
833    
834 root 1.1 $self->{hash} = $obj
835     }
836    
837     sub to_object {
838     my ($self) = @_;
839    
840     my $obj = cf::object::new $self->{hash}->{arch};
841    
842 elmex 1.14 $obj->item_power (floor ($self->power_to_level / 3)); # there have to be strings attached!
843 elmex 1.6
844 root 1.1 $obj->face ($self->{hash}{face});
845    
846     my $stats = $obj->stats;
847    
848     $stats->hp ($self->{hash}{spec}{regen});
849     $stats->sp ($self->{hash}{spec}{magic});
850     $stats->wc ($self->{hash}{spec}{wc});
851     $stats->dam ($self->{hash}{spec}{dam});
852     $stats->ac ($self->{hash}{spec}{ac});
853     $stats->exp ($self->{hash}{spec}{speed});
854     $stats->food ($self->{hash}{spec}{food});
855    
856     $stats->$_ ($self->{hash}{stat}{lc $_})
857     for qw/Str Dex Con Wis Cha Int Pow/;
858    
859     for (@Jeweler::RESISTS) {
860 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
861 root 1.1 }
862    
863     $obj->flag (cf::FLAG_IDENTIFIED, 1);
864    
865 elmex 1.9 $obj->value ($self->{hash}{value});
866    
867 root 1.1 return $obj;
868     }
869    
870 elmex 1.9 sub set_value { $_[0]->{hash}{value} = $_[1] }
871    
872 elmex 1.4 sub is_better_than {
873     my ($self, $other) = @_;
874    
875     for my $type (qw/spec stat resist/) {
876     for my $stat (keys %{$self->{hash}->{$type}}) {
877     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
878     return 1;
879     }
880     }
881     }
882    
883     return 0;
884     }
885    
886 root 1.1 sub stat_level {
887     my ($self) = @_;
888     my $stats = $self->{hash}->{stat} || {};
889    
890     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
891     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
892    
893     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
894     my $stat_sum = sum (values %$stats); # also count the negative stats!
895     my $level = int (($maxlevel / $maxstat) * $stat_sum);
896    
897     ($level, $stat_cnt)
898     }
899    
900     sub resist_level {
901     my ($self) = @_;
902    
903     my $resists = $self->{hash}->{resist} || {};
904    
905     my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
906     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
907     my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
908     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
909     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
910    
911     my $ressum = 0;
912     my $rescnt = 0;
913     my @reslevels;
914    
915     for my $resnam (keys %$resists) {
916     my $res = $resists->{$resnam};
917    
918     $rescnt++
919     if $res > 0; # negative resistancies are not an improvement
920    
921     $ressum += $res; # note: negative resistancies lower the sum
922    
923     next unless $res > 0;
924    
925     my $level = 0;
926     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
927     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
928     } else {
929     $level = ceil (($att_res_lvl / $max_att_res) * $res);
930     }
931     push @reslevels, $level;
932     }
933    
934     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
935    
936     (max (@reslevels, $overall_lvl), $rescnt);
937     }
938    
939     sub special_level {
940     my ($self) = @_;
941    
942     my $specials = $self->{hash}->{spec} || {};
943    
944     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
945     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
946    
947     my @speclvls;
948     my $specsum = 0;
949     my $imprs = 0;
950    
951     for my $spcnam (keys %$specials) {
952     my $spc = $specials->{$spcnam};
953     next unless $spc > 0;
954    
955     $specsum += $spc;
956     $imprs++;
957    
958     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
959    
960     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
961     push @speclvls, $lvl;
962     }
963    
964     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
965    
966     (max (@speclvls, $sumlvl), $imprs)
967     }
968    
969    
970     # this function calculated the 'level' of an amulet or a ring
971     sub power_to_level {
972     my ($self, $lvldescr) = @_;
973    
974     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
975     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
976    
977     my ($stat_lvl, $stat_imprs) = $self->stat_level;
978     my ($resist_lvl, $res_imprs) = $self->resist_level;
979     my ($spec_lvl, $spec_imprs) = $self->special_level;
980    
981     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
982    
983     my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
984    
985     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
986    
987     if ($lvldescr) {
988     $$lvldescr =
989     sprintf "%3d: %s\n", $levl,
990     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
991     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
992     }
993    
994     $levl
995     }
996    
997     sub add_stat_costs {
998     my ($self, $cost) = @_;
999    
1000     my $stats = $self->{hash}->{stat};
1001    
1002     for my $stat (keys %$stats) {
1003     my $sum = $stats->{$stat};
1004    
1005     next unless $sum > 0;
1006    
1007 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
1008 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
1009     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1010     }
1011     }
1012    
1013     sub add_special_costs {
1014     my ($self, $cost) = @_;
1015    
1016     my $specials = $self->{hash}->{spec};
1017    
1018     for my $spec (keys %$specials) {
1019     my $sum = $specials->{$spec};
1020    
1021     next unless $sum > 0;
1022    
1023 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
1024 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
1025     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1026     }
1027     }
1028    
1029     sub calc_costs {
1030     my ($self) = @_;
1031    
1032     my $costs = {};
1033    
1034     my $ring = $self->{hash};
1035    
1036 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
1037 root 1.1
1038 elmex 1.4 my $res = $ring->{resist}->{$resnum};
1039 root 1.1
1040     next unless $res > 0;
1041    
1042 elmex 1.18 return undef if $res == 100;
1043    
1044 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1045 root 1.1
1046     my $diamonds;
1047 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1048 elmex 1.18 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1049 root 1.1 } else {
1050 elmex 1.18 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1051 root 1.1 }
1052    
1053 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1054 root 1.1 }
1055    
1056     $self->add_stat_costs ($costs);
1057     $self->add_special_costs ($costs);
1058    
1059     return $costs;
1060     }
1061    
1062     sub split_diamonds {
1063     my ($cost, $diamonds, $category) = @_;
1064    
1065     my $stat_split = Jeweler::getcfg (diamond_split => $category);
1066    
1067     my $sum = sum (@$stat_split);
1068    
1069     my $emarch = cf::arch::find 'emerald';
1070     my $saarch = cf::arch::find 'sapphire';
1071     my $pearch = cf::arch::find 'pearl';
1072     my $ruarch = cf::arch::find 'ruby';
1073     my $diarch = cf::arch::find 'gem';
1074    
1075 root 1.16 my $sumvalue = $diarch->value * $diamonds;
1076 root 1.1
1077 root 1.16 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1078     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1079     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1080     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1081     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1082 root 1.1 }
1083    
1084     package Jeweler::Util;
1085    
1086     use strict;
1087    
1088     =head2 Util
1089    
1090     Some utility functions for the Jeweler skill.
1091    
1092     =over 4
1093    
1094     =item remove ($object[, $nrof])
1095    
1096     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1097     The returnvalue is the number of 'single' objects that couldn't be removed.
1098    
1099     =cut
1100    
1101     sub remove {
1102     my ($obj, $nrof) = @_;
1103    
1104     my $cnt;
1105    
1106     if (defined $nrof) {
1107 elmex 1.4 # TODO: Check tihis line:
1108     return 0 if ($nrof * 1) == 0; #XXX: ???
1109 root 1.1 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1110    
1111     if ($cnt > 0) {
1112     $obj->nrof ($cnt);
1113     return 0;
1114     }
1115     }
1116    
1117     remove ($_) for $obj->inv;
1118     $obj->destroy;
1119     return $cnt;
1120     }
1121    
1122 elmex 1.4 sub check_for_match {
1123 root 1.1 my ($thing, @matchar) = @_;
1124    
1125     my $i = 0;
1126 elmex 1.9 my $check_cnts = 0;
1127     my $check_true = 0;
1128 root 1.1 for my $match (@matchar) {
1129 elmex 1.11 if ($i % 3 == 0) {
1130 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1131 elmex 1.11 $check_cnts = 0;
1132     $check_true = 0;
1133     }
1134 elmex 1.10
1135 root 1.1 if ($match =~ m/^\s*$/) {
1136     $i++;
1137     next;
1138     }
1139    
1140 elmex 1.9 $check_cnts++;
1141 root 1.1 if ($i % 3 == 0) {
1142     $thing->name eq $match
1143 elmex 1.9 and $check_true++;
1144 root 1.1 } elsif ($i % 3 == 1) {
1145     $thing->title eq $match
1146 elmex 1.9 and $check_true++;
1147 root 1.1 } else { # $i % 3 == 2
1148 elmex 1.17 $thing->arch->archname eq $match
1149 elmex 1.9 and $check_true++;
1150 root 1.1 }
1151     $i++;
1152     }
1153 elmex 1.17 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1154 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1155 root 1.1 return 0;
1156     }
1157    
1158 elmex 1.4 sub grep_for_match {
1159     my ($ingred, $group, @matchar) = @_;
1160    
1161     for my $thing (@{$ingred->{$group} || []}) {
1162 elmex 1.17 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1163 elmex 1.4 if (check_for_match ($thing, @matchar)) {
1164     return $thing;
1165     }
1166     }
1167     return undef;
1168     }
1169    
1170 root 1.1 =back
1171    
1172     1