ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.29
Committed: Mon Oct 12 14:00:58 2009 UTC (14 years, 7 months ago) by root
Branch: MAIN
CVS Tags: rel-2_82, rel-2_81, rel-2_90, rel-2_92
Changes since 1.28: +1 -2 lines
Log Message:
clarify license

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