ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.24
Committed: Sun Feb 17 22:37:34 2008 UTC (16 years, 3 months ago) by elmex
Branch: MAIN
CVS Tags: rel-2_43, rel-2_42
Changes since 1.23: +4 -0 lines
Log Message:
fixed a missing output

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