ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.26
Committed: Sun Jul 13 20:15:51 2008 UTC (15 years, 10 months ago) by elmex
Branch: MAIN
CVS Tags: rel-2_6
Changes since 1.25: +4 -4 lines
Log Message:
minor rebalancement of the jeweler skill. and added function to safe environment

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