ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.34
Committed: Wed Apr 28 21:07:41 2010 UTC (14 years, 1 month ago) by root
Branch: MAIN
Changes since 1.33: +2 -0 lines
Log Message:
*** empty log message ***

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