ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.33
Committed: Wed Apr 28 21:05:33 2010 UTC (14 years, 1 month ago) by root
Branch: MAIN
Changes since 1.32: +4 -8 lines
Log Message:
treat sound.conf and jeweler.yaml like other data files

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 root 1.29 use common::sense;
14 root 1.1
15     =over 4
16    
17     =item @RESISTS
18    
19     List of all resistancies that can occur on rings and amulets.
20    
21     =cut
22    
23     our $CFG;
24    
25 root 1.33 sub load_config {
26     0 < Coro::AIO::aio_load "$cf::DATADIR/jeweler", my $data
27     or die "$cf::DATADIR/jeweler: $!";
28 root 1.1
29 root 1.33 $CFG = cf::decode_json $data;
30 root 1.1 }
31    
32     sub getcfg {
33     my ($sect, $key) = @_;
34     return $CFG->{$sect} unless defined $key;
35    
36     my $cfg = $CFG->{$sect}->{$key}
37     or die "Couldn't find $sect/$key in configuration!";
38    
39     $cfg
40     }
41    
42     our @RESISTS = (
43     cf::ATNR_PHYSICAL,
44     cf::ATNR_MAGIC,
45     cf::ATNR_FIRE,
46     cf::ATNR_ELECTRICITY,
47     cf::ATNR_COLD,
48     cf::ATNR_CONFUSION,
49    
50     cf::ATNR_ACID,
51     cf::ATNR_DRAIN,
52     cf::ATNR_GHOSTHIT,
53     cf::ATNR_POISON,
54     cf::ATNR_SLOW,
55     cf::ATNR_PARALYZE,
56    
57     cf::ATNR_TURN_UNDEAD,
58     cf::ATNR_FEAR,
59     cf::ATNR_DEPLETE,
60     cf::ATNR_DEATH,
61     cf::ATNR_HOLYWORD,
62     cf::ATNR_LIFE_STEALING,
63    
64     cf::ATNR_BLIND,
65     cf::ATNR_DISEASE,
66     );
67    
68     =item @EFFECT_RESISTS
69    
70     List of all effect resistancies that occur on rings and amulets.
71     The difference is made because effect resistancies are less effective at lower levels.
72    
73     =back
74    
75     =cut
76    
77     our @EFFECT_RESISTS = (
78     cf::ATNR_CONFUSION,
79     cf::ATNR_DRAIN,
80     cf::ATNR_POISON,
81     cf::ATNR_SLOW,
82     cf::ATNR_PARALYZE,
83     cf::ATNR_TURN_UNDEAD,
84     cf::ATNR_FEAR,
85     cf::ATNR_DEPLETE,
86     cf::ATNR_DEATH,
87     cf::ATNR_BLIND,
88     cf::ATNR_DISEASE,
89     );
90    
91     our %RESMAP = (
92     cf::ATNR_PHYSICAL => "PHYSICAL",
93     cf::ATNR_MAGIC => "MAGIC",
94     cf::ATNR_FIRE => "FIRE",
95     cf::ATNR_ELECTRICITY => "ELECTRICITY",
96     cf::ATNR_COLD => "COLD",
97     cf::ATNR_CONFUSION => "CONFUSION",
98     cf::ATNR_ACID => "ACID",
99    
100     cf::ATNR_DRAIN => "DRAIN",
101     cf::ATNR_GHOSTHIT => "GHOSTHIT",
102     cf::ATNR_POISON => "POISON",
103     cf::ATNR_SLOW => "SLOW",
104     cf::ATNR_PARALYZE => "PARALYZE",
105     cf::ATNR_TURN_UNDEAD => "TURN_UNDEAD",
106    
107     cf::ATNR_FEAR => "FEAR",
108     cf::ATNR_DEPLETE => "DEPLETE",
109     cf::ATNR_DEATH => "DEATH",
110     cf::ATNR_HOLYWORD => "HOLYWORD",
111     cf::ATNR_LIFE_STEALING => "LIFE_STEALING",
112     cf::ATNR_BLIND => "BLIND",
113     cf::ATNR_DISEASE => "DISEASE",
114     );
115    
116 elmex 1.4 our %REV_RESMAP = map { $RESMAP{$_} => $_ } keys %RESMAP;
117    
118 root 1.1 our %LVL_DIFF_CHANCES = (
119     +5 => 100,
120     +4 => 95,
121     +3 => 85,
122     +2 => 75,
123     +1 => 65,
124     0 => 50,
125     -1 => 45,
126     -2 => 35,
127     -3 => 25,
128     -4 => 10,
129     -5 => 0
130     );
131    
132     our %LVL_DIFF_MSG = (
133     -5 => 'Way above your skill',
134     -4 => 'Very low',
135     -3 => 'Slight chance',
136     -2 => 'Low',
137     -1 => 'Nearly 50:50',
138     0 => '50:50',
139     1 => 'Slightly above 50:50',
140     2 => 'Good',
141     3 => 'High',
142     4 => 'Nearly confident',
143     5 => '100%',
144     );
145    
146     sub level_diff_to_str {
147     my ($delta) = @_;
148     $delta = -5 if $delta < -5;
149     $delta = 5 if $delta > 5;
150     return $LVL_DIFF_MSG{$delta}
151     }
152    
153     sub level_diff_to_chance_perc {
154     my ($delta) = @_;
155     $delta = -5 if $delta < -5;
156     $delta = 5 if $delta > 5;
157     return $LVL_DIFF_CHANCES{$delta}
158     }
159    
160     sub analyze {
161 elmex 1.18 my ($sk, $chdl, $pl, $input_level) = @_;
162 root 1.1
163     my $hadunid = 0;
164 elmex 1.24 my $found = 0;
165 root 1.1 for ($chdl->grep_by_type (cf::RING, cf::AMULET)) {
166     if (!$_->flag (cf::FLAG_IDENTIFIED) && $_->need_identify) {
167     $hadunid = 1;
168     next;
169     }
170 elmex 1.24 $found = 1;
171 root 1.1 my $r = Jeweler::Object->new (object => $_);
172 elmex 1.18 my $msg = $r->analyze ($sk, $pl, $input_level);
173 root 1.1 $pl->message ($r->to_string . ": " . $msg);
174     if ($pl->flag (cf::FLAG_WIZ)) {
175     $r->wiz_analyze ($pl);
176     }
177     }
178 root 1.22 $pl->message ("You couldn't identify the other rings and not analyze them!")
179     if $hadunid;
180 elmex 1.24 $pl->message ("You couldn't find anything in the bench to analyse!")
181     unless $found;
182 root 1.1 }
183    
184     # this function converts metals/minerals into a raw ring (of adornment)
185     sub simple_converter {
186 elmex 1.30 my ($pl, $ingred, $chdl, $conv, $sk_lvl, $low_skill_cb) = @_;
187 root 1.1
188     $conv = lc $conv;
189     my $cnvs = $CFG->{conversions};
190    
191     return unless $cnvs->{$conv};
192    
193     my %ingred_groups;
194    
195     my @conv_cfg = @{$cnvs->{$conv}};
196     my $outarch = $conv;
197     my ($ingr_grp, $outarchvalfact, $srcarchname, $xp_gain) = @conv_cfg;
198    
199     unless (@conv_cfg <= 4) {
200     warn "ERROR: Conversion for '$outarch' has only " . (@conv_cfg) . " arguments!";
201     return;
202     }
203    
204     unless ($xp_gain > 0) {
205 root 1.22 warn "WARNING: xp gain isn't > 0 in conversion '$outarch'\n";
206 root 1.1 return;
207     }
208    
209     unless ($outarchvalfact) {
210     warn "ERROR: source-arch-value-multiplier == 0 in conversion '$outarch'\n";
211     return;
212     }
213    
214     unless ($outarchvalfact >= 1) {
215     warn "WARNING: source-arch-value-multiplier < 1 in conversion '$outarch', results in more valuable output!\n";
216     }
217    
218 elmex 1.30 my $archvalsum = $ingred->value ($ingr_grp, $srcarchname);
219     my $outarchval = cf::arch::find ($outarch)->value;
220     my $nrof = int $archvalsum / (($outarchval || 1000) * $outarchvalfact);
221     my $can_make_nr = int (($sk_lvl / 2) + 10);
222 root 1.1
223 elmex 1.30 if ($nrof > $can_make_nr) {
224     $pl->ob->message ("Your jeweler level is too low to make $nrof rings, you can only make $can_make_nr at your current level.");
225     return;
226     }
227 root 1.1
228     if ($nrof) {
229 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)
230 elmex 1.30 $ingred->remove ($ingr_grp, $srcarchname);
231 root 1.22 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.28 $ring = 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.28 my $costs = 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 elmex 1.32 $obj->{is_ring} = ($thing->type == cf::RING);
847    
848 root 1.1 $self->{hash} = $obj
849     }
850    
851     sub to_object {
852     my ($self) = @_;
853    
854     my $obj = cf::object::new $self->{hash}->{arch};
855    
856 elmex 1.14 $obj->item_power (floor ($self->power_to_level / 3)); # there have to be strings attached!
857 elmex 1.6
858 root 1.1 $obj->face ($self->{hash}{face});
859    
860     my $stats = $obj->stats;
861    
862     $stats->hp ($self->{hash}{spec}{regen});
863     $stats->sp ($self->{hash}{spec}{magic});
864     $stats->wc ($self->{hash}{spec}{wc});
865     $stats->dam ($self->{hash}{spec}{dam});
866     $stats->ac ($self->{hash}{spec}{ac});
867     $stats->exp ($self->{hash}{spec}{speed});
868     $stats->food ($self->{hash}{spec}{food});
869    
870     $stats->$_ ($self->{hash}{stat}{lc $_})
871     for qw/Str Dex Con Wis Cha Int Pow/;
872    
873     for (@Jeweler::RESISTS) {
874 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
875 root 1.1 }
876    
877     $obj->flag (cf::FLAG_IDENTIFIED, 1);
878    
879 elmex 1.9 $obj->value ($self->{hash}{value});
880    
881 root 1.1 return $obj;
882     }
883    
884 elmex 1.9 sub set_value { $_[0]->{hash}{value} = $_[1] }
885    
886 elmex 1.4 sub is_better_than {
887     my ($self, $other) = @_;
888    
889     for my $type (qw/spec stat resist/) {
890     for my $stat (keys %{$self->{hash}->{$type}}) {
891     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
892     return 1;
893     }
894     }
895     }
896    
897     return 0;
898     }
899    
900 root 1.1 sub stat_level {
901     my ($self) = @_;
902     my $stats = $self->{hash}->{stat} || {};
903    
904     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
905     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
906    
907     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
908     my $stat_sum = sum (values %$stats); # also count the negative stats!
909     my $level = int (($maxlevel / $maxstat) * $stat_sum);
910    
911     ($level, $stat_cnt)
912     }
913    
914     sub resist_level {
915     my ($self) = @_;
916    
917     my $resists = $self->{hash}->{resist} || {};
918    
919 elmex 1.31 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
920     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
921 root 1.1 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
922     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
923     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
924    
925     my $ressum = 0;
926     my $rescnt = 0;
927     my @reslevels;
928    
929     for my $resnam (keys %$resists) {
930     my $res = $resists->{$resnam};
931    
932     $rescnt++
933     if $res > 0; # negative resistancies are not an improvement
934    
935     $ressum += $res; # note: negative resistancies lower the sum
936    
937     next unless $res > 0;
938    
939     my $level = 0;
940     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
941     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
942     } else {
943     $level = ceil (($att_res_lvl / $max_att_res) * $res);
944     }
945     push @reslevels, $level;
946     }
947    
948     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
949    
950     (max (@reslevels, $overall_lvl), $rescnt);
951     }
952    
953     sub special_level {
954     my ($self) = @_;
955    
956     my $specials = $self->{hash}->{spec} || {};
957    
958     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
959     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
960    
961     my @speclvls;
962     my $specsum = 0;
963     my $imprs = 0;
964    
965     for my $spcnam (keys %$specials) {
966     my $spc = $specials->{$spcnam};
967     next unless $spc > 0;
968    
969     $specsum += $spc;
970     $imprs++;
971    
972     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
973    
974     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
975     push @speclvls, $lvl;
976     }
977    
978     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
979    
980     (max (@speclvls, $sumlvl), $imprs)
981     }
982    
983    
984     # this function calculated the 'level' of an amulet or a ring
985     sub power_to_level {
986     my ($self, $lvldescr) = @_;
987    
988     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
989     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
990 elmex 1.32 my $ring_offs = Jeweler::getcfg (maxlevels => 'ring_offset');
991 root 1.1
992     my ($stat_lvl, $stat_imprs) = $self->stat_level;
993     my ($resist_lvl, $res_imprs) = $self->resist_level;
994     my ($spec_lvl, $spec_imprs) = $self->special_level;
995    
996     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
997    
998 elmex 1.32 my $impr_lvl =
999     ceil (($max_impr_lvl / ($max_imprs + 1))
1000     * ($impr_sum - 1)); # 1 improvemnt bonus
1001 root 1.1
1002     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
1003    
1004 elmex 1.32 if ($self->{hash}->{is_ring}) {
1005     $levl += $ring_offs;
1006     }
1007    
1008     $levl = min ($levl, cf::settings->max_level);
1009    
1010 root 1.1 if ($lvldescr) {
1011     $$lvldescr =
1012     sprintf "%3d: %s\n", $levl,
1013     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
1014     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
1015     }
1016    
1017     $levl
1018     }
1019    
1020     sub add_stat_costs {
1021     my ($self, $cost) = @_;
1022    
1023     my $stats = $self->{hash}->{stat};
1024    
1025     for my $stat (keys %$stats) {
1026     my $sum = $stats->{$stat};
1027    
1028     next unless $sum > 0;
1029    
1030 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
1031 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
1032     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1033     }
1034     }
1035    
1036     sub add_special_costs {
1037     my ($self, $cost) = @_;
1038    
1039     my $specials = $self->{hash}->{spec};
1040    
1041     for my $spec (keys %$specials) {
1042     my $sum = $specials->{$spec};
1043    
1044     next unless $sum > 0;
1045    
1046 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
1047 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
1048     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1049     }
1050     }
1051    
1052     sub calc_costs {
1053     my ($self) = @_;
1054    
1055     my $costs = {};
1056    
1057     my $ring = $self->{hash};
1058    
1059 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
1060 root 1.1
1061 elmex 1.4 my $res = $ring->{resist}->{$resnum};
1062 root 1.1
1063     next unless $res > 0;
1064    
1065 elmex 1.18 return undef if $res == 100;
1066    
1067 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1068 root 1.1
1069     my $diamonds;
1070 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1071 elmex 1.18 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1072 root 1.1 } else {
1073 elmex 1.18 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1074 root 1.1 }
1075    
1076 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1077 root 1.1 }
1078    
1079     $self->add_stat_costs ($costs);
1080     $self->add_special_costs ($costs);
1081    
1082     return $costs;
1083     }
1084    
1085     sub split_diamonds {
1086     my ($cost, $diamonds, $category) = @_;
1087    
1088     my $stat_split = Jeweler::getcfg (diamond_split => $category);
1089    
1090     my $sum = sum (@$stat_split);
1091    
1092     my $emarch = cf::arch::find 'emerald';
1093     my $saarch = cf::arch::find 'sapphire';
1094     my $pearch = cf::arch::find 'pearl';
1095     my $ruarch = cf::arch::find 'ruby';
1096     my $diarch = cf::arch::find 'gem';
1097    
1098 root 1.16 my $sumvalue = $diarch->value * $diamonds;
1099 root 1.1
1100 root 1.16 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1101     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1102     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1103     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1104     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1105 root 1.1 }
1106    
1107     package Jeweler::Util;
1108    
1109     use strict;
1110    
1111     =head2 Util
1112    
1113     Some utility functions for the Jeweler skill.
1114    
1115     =over 4
1116    
1117     =item remove ($object[, $nrof])
1118    
1119     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1120 elmex 1.27 The return value is the number of 'single' objects that couldn't be removed.
1121 root 1.1
1122     =cut
1123    
1124     sub remove {
1125     my ($obj, $nrof) = @_;
1126 elmex 1.27
1127     my $c = $obj->nrof || 1;
1128     my $r = $c > $nrof ? 0 : $nrof - $c;
1129     $obj->decrease (defined ($nrof) ? $nrof : ($obj->nrof || 1));
1130 root 1.1
1131 elmex 1.27 $r
1132 root 1.1 }
1133    
1134 elmex 1.4 sub check_for_match {
1135 root 1.1 my ($thing, @matchar) = @_;
1136    
1137     my $i = 0;
1138 elmex 1.9 my $check_cnts = 0;
1139     my $check_true = 0;
1140 root 1.1 for my $match (@matchar) {
1141 elmex 1.11 if ($i % 3 == 0) {
1142 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1143 elmex 1.11 $check_cnts = 0;
1144     $check_true = 0;
1145     }
1146 elmex 1.10
1147 root 1.1 if ($match =~ m/^\s*$/) {
1148     $i++;
1149     next;
1150     }
1151    
1152 elmex 1.9 $check_cnts++;
1153 root 1.1 if ($i % 3 == 0) {
1154     $thing->name eq $match
1155 elmex 1.9 and $check_true++;
1156 root 1.1 } elsif ($i % 3 == 1) {
1157     $thing->title eq $match
1158 elmex 1.9 and $check_true++;
1159 root 1.1 } else { # $i % 3 == 2
1160 elmex 1.17 $thing->arch->archname eq $match
1161 elmex 1.9 and $check_true++;
1162 root 1.1 }
1163     $i++;
1164     }
1165 elmex 1.17 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1166 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1167 root 1.1 return 0;
1168     }
1169    
1170 elmex 1.4 sub grep_for_match {
1171     my ($ingred, $group, @matchar) = @_;
1172    
1173     for my $thing (@{$ingred->{$group} || []}) {
1174 elmex 1.17 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1175 elmex 1.4 if (check_for_match ($thing, @matchar)) {
1176     return $thing;
1177     }
1178     }
1179     return undef;
1180     }
1181    
1182 root 1.1 =back
1183    
1184     1