ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.31
Committed: Sun Apr 18 07:59:55 2010 UTC (14 years, 1 month ago) by elmex
Branch: MAIN
Changes since 1.30: +2 -2 lines
Log Message:
beautified formatting.

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