ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.27
Committed: Sun Jul 20 17:01:29 2008 UTC (15 years, 10 months ago) by elmex
Branch: MAIN
CVS Tags: rel-2_7, rel-2_72, rel-2_71, rel-2_61
Changes since 1.26: +12 -19 lines
Log Message:
fixed bug in jeweler skill

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