ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.37
Committed: Wed May 5 08:19:06 2010 UTC (14 years ago) by elmex
Branch: MAIN
CVS Tags: rel-3_0
Changes since 1.36: +16 -14 lines
Log Message:
jeweler skill got harder to level, but the item power of the output is lower.

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.35 cf::trace "loading jeweler config from $cf::DATADIR/jeweler\n";
27 root 1.34
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 root 1.36 use common::sense;
255 root 1.1
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 root 1.36
373     use common::sense;
374    
375 root 1.1 use Storable qw/dclone/;
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 elmex 1.37 if ($rem) {
597     $rem = Jeweler::Util::remove ($_[0], $rem);
598     }
599 elmex 1.27 1
600     }, @grepar);
601 root 1.1 if ($rem > 0) {
602 elmex 1.27 warn "JEWELER BUG: removed ingredients ($key) $rem > 0 after removing!";
603 root 1.1 }
604 elmex 1.27
605 root 1.1 } else {
606     my $nr;
607 elmex 1.4 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
608 root 1.1 $costs->{$key} -= $nr;
609     }
610 elmex 1.4
611 root 1.1 }
612    
613     return $costs;
614     }
615    
616     =back
617    
618     =cut
619    
620     sub put_to_bench {
621     my ($self, $bench) = @_;
622    
623     my $ingred = $self->{ingredients};
624    
625     for my $ik (keys %$ingred) {
626     for (@{$ingred->{$ik} || []}) {
627     $bench->put ($_);
628     }
629     }
630     }
631    
632     package Jeweler::Object;
633 root 1.36
634     use common::sense;
635 root 1.1 use POSIX;
636     use List::Util qw/max min sum/;
637    
638     sub new {
639     my ($class, %arg) = @_;
640    
641     my $self = bless { }, $class;
642    
643     $self->ring_or_ammy_to_hash ($arg{object});
644    
645     $self;
646     }
647    
648 elmex 1.7 sub has_resist {
649     my ($self, $resistnam, $resistval) = @_;
650     my $resnum = $REV_RESMAP{uc $resistnam};
651     if (defined ($resistval)) {
652     return 1 if $self->{hash}->{resist}->{$resnum} == $resistval;
653     } else {
654     return 1 if $self->{hash}->{resist}->{$resnum};
655     }
656     return undef;
657     }
658    
659 elmex 1.37 sub lvl2exp {
660     my $lvl = shift;
661     (cf::level_to_min_exp ($lvl) - cf::level_to_min_exp ($lvl - 1))
662     / (20 + max ($lvl - 1, 0)) # 20 + level times making such a ring
663     # should get you to the rings level at least.
664     }
665    
666 elmex 1.18 sub projected_exp {
667     my ($self, $input_level) = @_;
668    
669     my $lvl = max ($self->power_to_level, 1);
670 elmex 1.37 my $exp = lvl2exp ($lvl);
671 elmex 1.18
672 elmex 1.37 if (defined $input_level) { # in case we merge rings:
673     my $subexp = lvl2exp ($input_level);
674 elmex 1.18 $exp -= $subexp;
675     $exp = max ($exp, 0);
676    
677     } else {
678     # the experience bonus here is to make level 1 rings give you at least
679 elmex 1.26 # 200 exp points when making them. This also makes leveling in the
680 elmex 1.18 # first few levels a bit easier. (probably until around level 5-6).
681 elmex 1.26 my $expbonus = cf::level_to_min_exp (2) / 5;
682 elmex 1.18 # this bonus should also only be given for _new_ rings and not for merged
683     # ones - to prevent infinite exp making.
684     $exp += $expbonus;
685     }
686    
687     $exp
688     }
689    
690 root 1.1 sub analyze {
691 elmex 1.18 my ($self, $sk, $pl, $input_level) = @_;
692     my $costs = $self->calc_costs;
693    
694     unless (defined $costs) {
695     return "This ring has a resistancy above 99%, you can't make that.";
696     }
697 root 1.1
698     my $sklvl = cf::exp_to_level ($sk->stats->exp);
699     my $ringlvl = $self->power_to_level;
700    
701     my $tmpl;
702     if ($pl->flag (cf::FLAG_WIZ)) {
703     $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
704     } else {
705     $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
706     }
707 elmex 1.18 my $msg = sprintf "Projected success rate: %s, you would get %d exp for this.", $tmpl, $self->projected_exp ($input_level);
708 root 1.1 return $msg;
709     }
710    
711 elmex 1.9 sub calc_value_from_cost {
712     my ($self, $costs) = @_;
713     my $emarch = cf::arch::find 'emerald';
714     my $saarch = cf::arch::find 'sapphire';
715     my $pearch = cf::arch::find 'pearl';
716     my $ruarch = cf::arch::find 'ruby';
717     my $diarch = cf::arch::find 'gem';
718 root 1.16 my $value = $emarch->value * $costs->{emerald}
719     + $saarch->value * $costs->{sapphire}
720     + $pearch->value * $costs->{pearl}
721     + $ruarch->value * $costs->{ruby}
722     + $diarch->value * $costs->{gem};
723 elmex 1.9
724     $value
725     }
726    
727 root 1.1 sub wiz_analyze {
728     my ($self, $pl) = @_;
729     my $costs = $self->calc_costs;
730 elmex 1.18 if (defined $costs) {
731     my $desc = "";
732     my $lvl = $self->power_to_level (\$desc);
733     my $scosts = $self->calc_value_from_cost ($costs);
734 elmex 1.5
735 root 1.25 $pl->message ("costs: "
736     . (join ', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)
737     . " ("
738 elmex 1.26 . ($scosts / "platinacoin"->cf::arch::find->value)
739     . " platinum)");
740 root 1.25 $pl->message ("level: $desc");
741 elmex 1.18 } else {
742     $pl->message ("level: impossible to make, due to impossible resistancy configuration");
743     }
744 root 1.1 }
745    
746     sub get_chance_perc {
747     my ($self, $sk) = @_;
748     my $sklvl = cf::exp_to_level ($sk->stats->exp);
749     my $ringlvl = $self->power_to_level;
750     return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
751     }
752    
753     sub fx {
754     my ($res, $cfg) = @_;
755     my $or = $res;
756     my $ar = $Jeweler::CFG->{functions}->{$cfg};
757 elmex 1.9
758 elmex 1.18 if (ref $ar && ref $ar->[0] eq 'ARRAY') {
759 root 1.1 $res = $res - 1;
760 elmex 1.9 return $ar->[max (min ($res, @$ar - 1), 0)];
761    
762 root 1.1 } else {
763 elmex 1.9 # +0.1 is for a jump to the next index when $res / 5 is exactly 1, 2, 3...
764 elmex 1.18 # old code:
765     #my $idx = ceil (($res / 5) + 0.1) - 1;
766     #my $a = $ar->[max (min ($idx, @$ar - 1), 0)];
767     #my $b = $ar->[max (min ($idx + 1, @$ar - 1), 0)];
768     #my $diff = $b - $a; # use the difference of the cost to the next cost
769     #my $o_cost = $a + ($diff / 5) * ($res % 5); # and do some linear interpolation
770     #return $o_cost;
771     return 0 if $res <= 0;
772     return ($ar / (1 - ($res * 0.01)) - $ar)
773 root 1.1 }
774     }
775    
776     sub improve_by_ring {
777     my ($self, @rings) = @_;
778     my $ring = $self;
779     for my $iring (@rings) {
780     for my $cat (qw/stat spec resist/) {
781     for my $k (keys %{$iring->{hash}->{$cat}}) {
782     if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
783     $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
784     }
785     }
786     }
787     }
788     }
789    
790     sub negate {
791     my ($self) = @_;
792     for my $cat (qw/stat spec resist/) {
793     for my $k (keys %{$self->{hash}->{$cat}}) {
794     if ($self->{hash}->{$cat}->{$k} > 0) {
795     $self->{hash}->{$cat}->{$k} *= -1;
796     }
797     }
798     }
799 elmex 1.9 $self->{hash}{value} = 0;
800 root 1.1 }
801    
802     sub to_string {
803     my ($self) = @_;
804     my $r = $self->{hash};
805     return
806     $r->{arch} . " " .
807     join ("",
808     grep { $_ ne "" }
809     join ("",
810     (map {
811     my $rv = $r->{resist}->{$_};
812     "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
813     } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
814     (map {
815     my $rv = $r->{stat}->{$_};
816     "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
817     } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
818     (map {
819     my $rv = $r->{spec}->{$_};
820     "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
821     } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
822     }
823    
824     sub ring_or_ammy_to_hash {
825     my ($self, $thing) = @_;
826    
827     my $obj = {};
828    
829     for (@Jeweler::RESISTS) {
830 root 1.3 $obj->{resist}->{$_} = $thing->resist ($_);
831 root 1.1 }
832    
833     my $stats = $thing->stats;
834    
835     for (qw/Str Dex Con Wis Cha Int Pow/) {
836     $obj->{stat}->{lc $_} = $stats->$_;
837     }
838    
839     $obj->{spec}{regen} = $stats->hp;
840     $obj->{spec}{magic} = $stats->sp;
841     $obj->{spec}{wc} = $stats->wc;
842     $obj->{spec}{dam} = $stats->dam;
843     $obj->{spec}{ac} = $stats->ac;
844     $obj->{spec}{speed} = $stats->exp;
845     $obj->{spec}{food} = $stats->food;
846    
847     $obj->{name} = $thing->name;
848 elmex 1.17 $obj->{arch} = $thing->arch->archname;
849 root 1.1 $obj->{face} = $thing->face;
850    
851 elmex 1.9 $obj->{value} = $thing->value;
852    
853 elmex 1.32 $obj->{is_ring} = ($thing->type == cf::RING);
854    
855 root 1.1 $self->{hash} = $obj
856     }
857    
858     sub to_object {
859     my ($self) = @_;
860    
861     my $obj = cf::object::new $self->{hash}->{arch};
862    
863 elmex 1.37 $obj->item_power (floor ($self->power_to_level / 5)); # there have to be strings attached!
864 elmex 1.6
865 root 1.1 $obj->face ($self->{hash}{face});
866    
867     my $stats = $obj->stats;
868    
869     $stats->hp ($self->{hash}{spec}{regen});
870     $stats->sp ($self->{hash}{spec}{magic});
871     $stats->wc ($self->{hash}{spec}{wc});
872     $stats->dam ($self->{hash}{spec}{dam});
873     $stats->ac ($self->{hash}{spec}{ac});
874     $stats->exp ($self->{hash}{spec}{speed});
875     $stats->food ($self->{hash}{spec}{food});
876    
877     $stats->$_ ($self->{hash}{stat}{lc $_})
878     for qw/Str Dex Con Wis Cha Int Pow/;
879    
880     for (@Jeweler::RESISTS) {
881 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
882 root 1.1 }
883    
884     $obj->flag (cf::FLAG_IDENTIFIED, 1);
885    
886 elmex 1.9 $obj->value ($self->{hash}{value});
887    
888 root 1.1 return $obj;
889     }
890    
891 elmex 1.9 sub set_value { $_[0]->{hash}{value} = $_[1] }
892    
893 elmex 1.4 sub is_better_than {
894     my ($self, $other) = @_;
895    
896     for my $type (qw/spec stat resist/) {
897     for my $stat (keys %{$self->{hash}->{$type}}) {
898     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
899     return 1;
900     }
901     }
902     }
903    
904     return 0;
905     }
906    
907 root 1.1 sub stat_level {
908     my ($self) = @_;
909     my $stats = $self->{hash}->{stat} || {};
910    
911     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
912     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
913    
914     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
915     my $stat_sum = sum (values %$stats); # also count the negative stats!
916     my $level = int (($maxlevel / $maxstat) * $stat_sum);
917    
918     ($level, $stat_cnt)
919     }
920    
921     sub resist_level {
922     my ($self) = @_;
923    
924     my $resists = $self->{hash}->{resist} || {};
925    
926 elmex 1.31 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
927     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
928 root 1.1 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
929     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
930     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
931    
932     my $ressum = 0;
933     my $rescnt = 0;
934     my @reslevels;
935    
936     for my $resnam (keys %$resists) {
937     my $res = $resists->{$resnam};
938    
939     $rescnt++
940     if $res > 0; # negative resistancies are not an improvement
941    
942     $ressum += $res; # note: negative resistancies lower the sum
943    
944     next unless $res > 0;
945    
946     my $level = 0;
947     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
948     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
949     } else {
950     $level = ceil (($att_res_lvl / $max_att_res) * $res);
951     }
952     push @reslevels, $level;
953     }
954    
955     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
956    
957     (max (@reslevels, $overall_lvl), $rescnt);
958     }
959    
960     sub special_level {
961     my ($self) = @_;
962    
963     my $specials = $self->{hash}->{spec} || {};
964    
965     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
966     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
967    
968     my @speclvls;
969     my $specsum = 0;
970     my $imprs = 0;
971    
972     for my $spcnam (keys %$specials) {
973     my $spc = $specials->{$spcnam};
974     next unless $spc > 0;
975    
976     $specsum += $spc;
977     $imprs++;
978    
979     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
980    
981     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
982     push @speclvls, $lvl;
983     }
984    
985     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
986    
987     (max (@speclvls, $sumlvl), $imprs)
988     }
989    
990    
991     # this function calculated the 'level' of an amulet or a ring
992     sub power_to_level {
993     my ($self, $lvldescr) = @_;
994    
995     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
996     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
997 elmex 1.32 my $ring_offs = Jeweler::getcfg (maxlevels => 'ring_offset');
998 root 1.1
999     my ($stat_lvl, $stat_imprs) = $self->stat_level;
1000     my ($resist_lvl, $res_imprs) = $self->resist_level;
1001     my ($spec_lvl, $spec_imprs) = $self->special_level;
1002    
1003     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
1004    
1005 elmex 1.32 my $impr_lvl =
1006     ceil (($max_impr_lvl / ($max_imprs + 1))
1007     * ($impr_sum - 1)); # 1 improvemnt bonus
1008 root 1.1
1009     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
1010    
1011 elmex 1.32 if ($self->{hash}->{is_ring}) {
1012     $levl += $ring_offs;
1013     }
1014    
1015     $levl = min ($levl, cf::settings->max_level);
1016    
1017 root 1.1 if ($lvldescr) {
1018     $$lvldescr =
1019     sprintf "%3d: %s\n", $levl,
1020     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
1021     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
1022     }
1023    
1024     $levl
1025     }
1026    
1027     sub add_stat_costs {
1028     my ($self, $cost) = @_;
1029    
1030     my $stats = $self->{hash}->{stat};
1031    
1032     for my $stat (keys %$stats) {
1033     my $sum = $stats->{$stat};
1034    
1035     next unless $sum > 0;
1036    
1037 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
1038 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
1039     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1040     }
1041     }
1042    
1043     sub add_special_costs {
1044     my ($self, $cost) = @_;
1045    
1046     my $specials = $self->{hash}->{spec};
1047    
1048     for my $spec (keys %$specials) {
1049     my $sum = $specials->{$spec};
1050    
1051     next unless $sum > 0;
1052    
1053 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
1054 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
1055     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1056     }
1057     }
1058    
1059     sub calc_costs {
1060     my ($self) = @_;
1061    
1062     my $costs = {};
1063    
1064     my $ring = $self->{hash};
1065    
1066 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
1067 root 1.1
1068 elmex 1.4 my $res = $ring->{resist}->{$resnum};
1069 root 1.1
1070     next unless $res > 0;
1071    
1072 elmex 1.18 return undef if $res == 100;
1073    
1074 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1075 root 1.1
1076     my $diamonds;
1077 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1078 elmex 1.18 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1079 root 1.1 } else {
1080 elmex 1.18 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1081 root 1.1 }
1082    
1083 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1084 root 1.1 }
1085    
1086     $self->add_stat_costs ($costs);
1087     $self->add_special_costs ($costs);
1088    
1089     return $costs;
1090     }
1091    
1092     sub split_diamonds {
1093     my ($cost, $diamonds, $category) = @_;
1094    
1095     my $stat_split = Jeweler::getcfg (diamond_split => $category);
1096    
1097     my $sum = sum (@$stat_split);
1098    
1099     my $emarch = cf::arch::find 'emerald';
1100     my $saarch = cf::arch::find 'sapphire';
1101     my $pearch = cf::arch::find 'pearl';
1102     my $ruarch = cf::arch::find 'ruby';
1103     my $diarch = cf::arch::find 'gem';
1104    
1105 root 1.16 my $sumvalue = $diarch->value * $diamonds;
1106 root 1.1
1107 root 1.16 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1108     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1109     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1110     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1111     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1112 root 1.1 }
1113    
1114     package Jeweler::Util;
1115    
1116 root 1.36 use common::sense;
1117 root 1.1
1118     =head2 Util
1119    
1120     Some utility functions for the Jeweler skill.
1121    
1122     =over 4
1123    
1124     =item remove ($object[, $nrof])
1125    
1126     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1127 elmex 1.27 The return value is the number of 'single' objects that couldn't be removed.
1128 root 1.1
1129     =cut
1130    
1131     sub remove {
1132     my ($obj, $nrof) = @_;
1133 elmex 1.37
1134     my $c = $obj->number_of;
1135 elmex 1.27 my $r = $c > $nrof ? 0 : $nrof - $c;
1136     $obj->decrease (defined ($nrof) ? $nrof : ($obj->nrof || 1));
1137 root 1.1
1138 elmex 1.27 $r
1139 root 1.1 }
1140    
1141 elmex 1.4 sub check_for_match {
1142 root 1.1 my ($thing, @matchar) = @_;
1143    
1144     my $i = 0;
1145 elmex 1.9 my $check_cnts = 0;
1146     my $check_true = 0;
1147 root 1.1 for my $match (@matchar) {
1148 elmex 1.11 if ($i % 3 == 0) {
1149 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1150 elmex 1.11 $check_cnts = 0;
1151     $check_true = 0;
1152     }
1153 elmex 1.10
1154 root 1.1 if ($match =~ m/^\s*$/) {
1155     $i++;
1156     next;
1157     }
1158    
1159 elmex 1.9 $check_cnts++;
1160 root 1.1 if ($i % 3 == 0) {
1161     $thing->name eq $match
1162 elmex 1.9 and $check_true++;
1163 root 1.1 } elsif ($i % 3 == 1) {
1164     $thing->title eq $match
1165 elmex 1.9 and $check_true++;
1166 root 1.1 } else { # $i % 3 == 2
1167 elmex 1.17 $thing->arch->archname eq $match
1168 elmex 1.9 and $check_true++;
1169 root 1.1 }
1170     $i++;
1171     }
1172 elmex 1.17 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1173 elmex 1.13 return 1 if $check_true && $check_cnts == $check_true;
1174 root 1.1 return 0;
1175     }
1176    
1177 elmex 1.4 sub grep_for_match {
1178     my ($ingred, $group, @matchar) = @_;
1179    
1180     for my $thing (@{$ingred->{$group} || []}) {
1181 elmex 1.17 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1182 elmex 1.4 if (check_for_match ($thing, @matchar)) {
1183     return $thing;
1184     }
1185     }
1186     return undef;
1187     }
1188    
1189 root 1.1 =back
1190    
1191     1