ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.22
Committed: Sun Oct 14 20:23:48 2007 UTC (16 years, 7 months ago) by root
Branch: MAIN
CVS Tags: rel-2_3, rel-2_32
Changes since 1.21: +7 -8 lines
Log Message:
*** empty log message ***

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