ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.32
Committed: Tue Apr 27 17:08:09 2010 UTC (14 years, 1 month ago) by elmex
Branch: MAIN
Changes since 1.31: +12 -1 lines
Log Message:
some jeweler changes i have to test.

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 elmex 1.32 $obj->{is_ring} = ($thing->type == cf::RING);
851    
852 root 1.1 $self->{hash} = $obj
853     }
854    
855     sub to_object {
856     my ($self) = @_;
857    
858     my $obj = cf::object::new $self->{hash}->{arch};
859    
860 elmex 1.14 $obj->item_power (floor ($self->power_to_level / 3)); # there have to be strings attached!
861 elmex 1.6
862 root 1.1 $obj->face ($self->{hash}{face});
863    
864     my $stats = $obj->stats;
865    
866     $stats->hp ($self->{hash}{spec}{regen});
867     $stats->sp ($self->{hash}{spec}{magic});
868     $stats->wc ($self->{hash}{spec}{wc});
869     $stats->dam ($self->{hash}{spec}{dam});
870     $stats->ac ($self->{hash}{spec}{ac});
871     $stats->exp ($self->{hash}{spec}{speed});
872     $stats->food ($self->{hash}{spec}{food});
873    
874     $stats->$_ ($self->{hash}{stat}{lc $_})
875     for qw/Str Dex Con Wis Cha Int Pow/;
876    
877     for (@Jeweler::RESISTS) {
878 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
879 root 1.1 }
880    
881     $obj->flag (cf::FLAG_IDENTIFIED, 1);
882    
883 elmex 1.9 $obj->value ($self->{hash}{value});
884    
885 root 1.1 return $obj;
886     }
887    
888 elmex 1.9 sub set_value { $_[0]->{hash}{value} = $_[1] }
889    
890 elmex 1.4 sub is_better_than {
891     my ($self, $other) = @_;
892    
893     for my $type (qw/spec stat resist/) {
894     for my $stat (keys %{$self->{hash}->{$type}}) {
895     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
896     return 1;
897     }
898     }
899     }
900    
901     return 0;
902     }
903    
904 root 1.1 sub stat_level {
905     my ($self) = @_;
906     my $stats = $self->{hash}->{stat} || {};
907    
908     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
909     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
910    
911     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
912     my $stat_sum = sum (values %$stats); # also count the negative stats!
913     my $level = int (($maxlevel / $maxstat) * $stat_sum);
914    
915     ($level, $stat_cnt)
916     }
917    
918     sub resist_level {
919     my ($self) = @_;
920    
921     my $resists = $self->{hash}->{resist} || {};
922    
923 elmex 1.31 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
924     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
925 root 1.1 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
926     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
927     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
928    
929     my $ressum = 0;
930     my $rescnt = 0;
931     my @reslevels;
932    
933     for my $resnam (keys %$resists) {
934     my $res = $resists->{$resnam};
935    
936     $rescnt++
937     if $res > 0; # negative resistancies are not an improvement
938    
939     $ressum += $res; # note: negative resistancies lower the sum
940    
941     next unless $res > 0;
942    
943     my $level = 0;
944     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
945     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
946     } else {
947     $level = ceil (($att_res_lvl / $max_att_res) * $res);
948     }
949     push @reslevels, $level;
950     }
951    
952     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
953    
954     (max (@reslevels, $overall_lvl), $rescnt);
955     }
956    
957     sub special_level {
958     my ($self) = @_;
959    
960     my $specials = $self->{hash}->{spec} || {};
961    
962     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
963     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
964    
965     my @speclvls;
966     my $specsum = 0;
967     my $imprs = 0;
968    
969     for my $spcnam (keys %$specials) {
970     my $spc = $specials->{$spcnam};
971     next unless $spc > 0;
972    
973     $specsum += $spc;
974     $imprs++;
975    
976     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
977    
978     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
979     push @speclvls, $lvl;
980     }
981    
982     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
983    
984     (max (@speclvls, $sumlvl), $imprs)
985     }
986    
987    
988     # this function calculated the 'level' of an amulet or a ring
989     sub power_to_level {
990     my ($self, $lvldescr) = @_;
991    
992     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
993     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
994 elmex 1.32 my $ring_offs = Jeweler::getcfg (maxlevels => 'ring_offset');
995 root 1.1
996     my ($stat_lvl, $stat_imprs) = $self->stat_level;
997     my ($resist_lvl, $res_imprs) = $self->resist_level;
998     my ($spec_lvl, $spec_imprs) = $self->special_level;
999    
1000     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
1001    
1002 elmex 1.32 my $impr_lvl =
1003     ceil (($max_impr_lvl / ($max_imprs + 1))
1004     * ($impr_sum - 1)); # 1 improvemnt bonus
1005 root 1.1
1006     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
1007    
1008 elmex 1.32 if ($self->{hash}->{is_ring}) {
1009     $levl += $ring_offs;
1010     }
1011    
1012     $levl = min ($levl, cf::settings->max_level);
1013    
1014 root 1.1 if ($lvldescr) {
1015     $$lvldescr =
1016     sprintf "%3d: %s\n", $levl,
1017     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
1018     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
1019     }
1020    
1021     $levl
1022     }
1023    
1024     sub add_stat_costs {
1025     my ($self, $cost) = @_;
1026    
1027     my $stats = $self->{hash}->{stat};
1028    
1029     for my $stat (keys %$stats) {
1030     my $sum = $stats->{$stat};
1031    
1032     next unless $sum > 0;
1033    
1034 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
1035 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
1036     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1037     }
1038     }
1039    
1040     sub add_special_costs {
1041     my ($self, $cost) = @_;
1042    
1043     my $specials = $self->{hash}->{spec};
1044    
1045     for my $spec (keys %$specials) {
1046     my $sum = $specials->{$spec};
1047    
1048     next unless $sum > 0;
1049    
1050 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
1051 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
1052     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1053     }
1054     }
1055    
1056     sub calc_costs {
1057     my ($self) = @_;
1058    
1059     my $costs = {};
1060    
1061     my $ring = $self->{hash};
1062    
1063 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
1064 root 1.1
1065 elmex 1.4 my $res = $ring->{resist}->{$resnum};
1066 root 1.1
1067     next unless $res > 0;
1068    
1069 elmex 1.18 return undef if $res == 100;
1070    
1071 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1072 root 1.1
1073     my $diamonds;
1074 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1075 elmex 1.18 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1076 root 1.1 } else {
1077 elmex 1.18 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1078 root 1.1 }
1079    
1080 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1081 root 1.1 }
1082    
1083     $self->add_stat_costs ($costs);
1084     $self->add_special_costs ($costs);
1085    
1086     return $costs;
1087     }
1088    
1089     sub split_diamonds {
1090     my ($cost, $diamonds, $category) = @_;
1091    
1092     my $stat_split = Jeweler::getcfg (diamond_split => $category);
1093    
1094     my $sum = sum (@$stat_split);
1095    
1096     my $emarch = cf::arch::find 'emerald';
1097     my $saarch = cf::arch::find 'sapphire';
1098     my $pearch = cf::arch::find 'pearl';
1099     my $ruarch = cf::arch::find 'ruby';
1100     my $diarch = cf::arch::find 'gem';
1101    
1102 root 1.16 my $sumvalue = $diarch->value * $diamonds;
1103 root 1.1
1104 root 1.16 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1105     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1106     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1107     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1108     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1109 root 1.1 }
1110    
1111     package Jeweler::Util;
1112    
1113     use strict;
1114    
1115     =head2 Util
1116    
1117     Some utility functions for the Jeweler skill.
1118    
1119     =over 4
1120    
1121     =item remove ($object[, $nrof])
1122    
1123     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1124 elmex 1.27 The return value is the number of 'single' objects that couldn't be removed.
1125 root 1.1
1126     =cut
1127    
1128     sub remove {
1129     my ($obj, $nrof) = @_;
1130 elmex 1.27
1131     my $c = $obj->nrof || 1;
1132     my $r = $c > $nrof ? 0 : $nrof - $c;
1133     $obj->decrease (defined ($nrof) ? $nrof : ($obj->nrof || 1));
1134 root 1.1
1135 elmex 1.27 $r
1136 root 1.1 }
1137    
1138 elmex 1.4 sub check_for_match {
1139 root 1.1 my ($thing, @matchar) = @_;
1140    
1141     my $i = 0;
1142 elmex 1.9 my $check_cnts = 0;
1143     my $check_true = 0;
1144 root 1.1 for my $match (@matchar) {
1145 elmex 1.11 if ($i % 3 == 0) {
1146 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1147 elmex 1.11 $check_cnts = 0;
1148     $check_true = 0;
1149     }
1150 elmex 1.10
1151 root 1.1 if ($match =~ m/^\s*$/) {
1152     $i++;
1153     next;
1154     }
1155    
1156 elmex 1.9 $check_cnts++;
1157 root 1.1 if ($i % 3 == 0) {
1158     $thing->name eq $match
1159 elmex 1.9 and $check_true++;
1160 root 1.1 } elsif ($i % 3 == 1) {
1161     $thing->title eq $match
1162 elmex 1.9 and $check_true++;
1163 root 1.1 } else { # $i % 3 == 2
1164 elmex 1.17 $thing->arch->archname eq $match
1165 elmex 1.9 and $check_true++;
1166 root 1.1 }
1167     $i++;
1168     }
1169 elmex 1.17 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1170 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1171 root 1.1 return 0;
1172     }
1173    
1174 elmex 1.4 sub grep_for_match {
1175     my ($ingred, $group, @matchar) = @_;
1176    
1177     for my $thing (@{$ingred->{$group} || []}) {
1178 elmex 1.17 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1179 elmex 1.4 if (check_for_match ($thing, @matchar)) {
1180     return $thing;
1181     }
1182     }
1183     return undef;
1184     }
1185    
1186 root 1.1 =back
1187    
1188     1