ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.36
Committed: Tue May 4 22:49:21 2010 UTC (14 years ago) by root
Branch: MAIN
Changes since 1.35: +7 -4 lines
Log Message:
more common sense

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