ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.6
Committed: Thu Feb 1 01:46:45 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.5: +5 -1 lines
Log Message:
fixed the big big big bug in the merging experience concept.
added checks for unidentified items.
added itempower limits to the generated rings!
jeweler skill is finally becoming a bit more balanced.

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     my ($sk, $chdl, $pl) = @_;
168    
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     my $msg = $r->analyze ($sk, $pl);
177     $pl->message ($r->to_string . ": " . $msg);
178     if ($pl->flag (cf::FLAG_WIZ)) {
179     $r->wiz_analyze ($pl);
180     }
181     }
182     if ($hadunid) {
183     $pl->message ("You couldn't identify the other rings and not analyze them!");
184     }
185     }
186    
187     # this function converts metals/minerals into a raw ring (of adornment)
188     sub simple_converter {
189     my ($pl, $ingred, $chdl, $conv) = @_;
190    
191     $conv = lc $conv;
192     my $cnvs = $CFG->{conversions};
193    
194     return unless $cnvs->{$conv};
195    
196     my %ingred_groups;
197    
198     my @conv_cfg = @{$cnvs->{$conv}};
199     my $outarch = $conv;
200     my ($ingr_grp, $outarchvalfact, $srcarchname, $xp_gain) = @conv_cfg;
201    
202     unless (@conv_cfg <= 4) {
203     warn "ERROR: Conversion for '$outarch' has only " . (@conv_cfg) . " arguments!";
204     return;
205     }
206    
207     unless ($xp_gain > 0) {
208     warn "WARNING: xp gain isn't > 0 in convesion '$outarch'\n";
209     return;
210     }
211    
212     unless ($outarchvalfact) {
213     warn "ERROR: source-arch-value-multiplier == 0 in conversion '$outarch'\n";
214     return;
215     }
216    
217     unless ($outarchvalfact >= 1) {
218     warn "WARNING: source-arch-value-multiplier < 1 in conversion '$outarch', results in more valuable output!\n";
219     }
220    
221     my $archvalsum = $ingred->value ($ingr_grp, $srcarchname);
222     $ingred->remove ($ingr_grp, $srcarchname);
223    
224     my $outarchval = cf::arch::find ($outarch)->clone->value;
225    
226     my $nrof = int ($archvalsum / (($outarchval || 1000) * $outarchvalfact));
227     if ($nrof) {
228     # XXX: yes, i know what i'm doing here, i don't set nrof, but it didn't work somehow (pls. chek sometimes)
229 elmex 1.4 for (1..$nrof) {
230     $chdl->put (my $ob = cf::object::new $outarch);
231     $ob->set_animation (cf::rndm $ob->num_animations);
232     $ob->flag (cf::FLAG_IDENTIFIED, 1);
233     }
234 root 1.1
235     my $xp_sum = ($xp_gain * $nrof);
236    
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     $pl->ob->message ("You fail to make something, propably 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     and $_->arch->name eq $arch_name
285     } @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     $obj->insert_ob_in_ob ($self->{cauldron});
360     }
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     $_->arch->name eq $archname
403     } @{$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     if ($_->arch->name eq $archname) {
430     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     $ring = dclone ($ring);
482    
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     my $costs = dclone ($costs);
576    
577     for my $key (keys %$costs) {
578     my @grepar;
579 elmex 1.4 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
580 root 1.1 @grepar = @{Jeweler::getcfg (plans => $key) || []};
581 elmex 1.4 } else { # check the gems
582     @grepar = ('gems', undef, undef, $key);
583 root 1.1 }
584    
585     if ($do_remove) {
586     my $rem = $costs->{$key};
587     $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
588     if ($rem > 0) {
589     warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
590     }
591     } else {
592     my $nr;
593 elmex 1.4 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
594 root 1.1 $costs->{$key} -= $nr;
595     }
596 elmex 1.4
597 root 1.1 }
598    
599     return $costs;
600     }
601    
602     =back
603    
604     =cut
605    
606     sub put_to_bench {
607     my ($self, $bench) = @_;
608    
609     my $ingred = $self->{ingredients};
610    
611     for my $ik (keys %$ingred) {
612     for (@{$ingred->{$ik} || []}) {
613     $bench->put ($_);
614     }
615     }
616     }
617    
618     package Jeweler::Object;
619     use strict;
620     use POSIX;
621     use List::Util qw/max min sum/;
622    
623     sub new {
624     my ($class, %arg) = @_;
625    
626     my $self = bless { }, $class;
627    
628     $self->ring_or_ammy_to_hash ($arg{object});
629    
630     $self;
631     }
632    
633     sub analyze {
634     my ($self, $sk, $pl) = @_;
635    
636     my $sklvl = cf::exp_to_level ($sk->stats->exp);
637     my $ringlvl = $self->power_to_level;
638    
639     my $tmpl;
640     if ($pl->flag (cf::FLAG_WIZ)) {
641     $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
642     } else {
643     $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
644     }
645     my $msg = sprintf "Projected success rate: %s", $tmpl;
646     return $msg;
647     }
648    
649     sub wiz_analyze {
650     my ($self, $pl) = @_;
651     my $costs = $self->calc_costs;
652     my $desc = "";
653     my $lvl = $self->power_to_level (\$desc);
654 elmex 1.5 my $emarch = cf::arch::find 'emerald';
655     my $saarch = cf::arch::find 'sapphire';
656     my $pearch = cf::arch::find 'pearl';
657     my $ruarch = cf::arch::find 'ruby';
658     my $diarch = cf::arch::find 'gem';
659     my $scosts = $emarch->clone->value * $costs->{emerald}
660     + $saarch->clone->value * $costs->{sapphire}
661     + $pearch->clone->value * $costs->{pearl}
662     + $ruarch->clone->value * $costs->{ruby}
663     + $diarch->clone->value * $costs->{gem};
664    
665     $pl->message ("costs: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
666 root 1.1 $pl->message ("level: " . $desc);
667     }
668    
669    
670     sub get_chance_perc {
671     my ($self, $sk) = @_;
672     my $sklvl = cf::exp_to_level ($sk->stats->exp);
673     my $ringlvl = $self->power_to_level;
674     return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
675     }
676    
677     sub fx {
678     my ($res, $cfg) = @_;
679     my $or = $res;
680     my $ar = $Jeweler::CFG->{functions}->{$cfg};
681     if (ref $ar->[0] eq 'ARRAY') {
682     $res = $res - 1;
683     } else {
684     $res = ceil ($res / 5) - 1;
685     }
686     $ar->[max (min ($res, @$ar - 1), 0)];
687     }
688    
689     sub improve_by_ring {
690     my ($self, @rings) = @_;
691     my $ring = $self;
692     for my $iring (@rings) {
693     for my $cat (qw/stat spec resist/) {
694     for my $k (keys %{$iring->{hash}->{$cat}}) {
695     if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
696     $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
697     }
698     }
699     }
700     }
701     }
702    
703     sub negate {
704     my ($self) = @_;
705     for my $cat (qw/stat spec resist/) {
706     for my $k (keys %{$self->{hash}->{$cat}}) {
707     if ($self->{hash}->{$cat}->{$k} > 0) {
708     $self->{hash}->{$cat}->{$k} *= -1;
709     }
710     }
711     }
712     }
713    
714     sub to_string {
715     my ($self) = @_;
716     my $r = $self->{hash};
717     return
718     $r->{arch} . " " .
719     join ("",
720     grep { $_ ne "" }
721     join ("",
722     (map {
723     my $rv = $r->{resist}->{$_};
724     "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
725     } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
726     (map {
727     my $rv = $r->{stat}->{$_};
728     "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
729     } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
730     (map {
731     my $rv = $r->{spec}->{$_};
732     "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
733     } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
734     }
735    
736     sub ring_or_ammy_to_hash {
737     my ($self, $thing) = @_;
738    
739     my $obj = {};
740    
741     for (@Jeweler::RESISTS) {
742 root 1.3 $obj->{resist}->{$_} = $thing->resist ($_);
743 root 1.1 }
744    
745     my $stats = $thing->stats;
746    
747     for (qw/Str Dex Con Wis Cha Int Pow/) {
748     $obj->{stat}->{lc $_} = $stats->$_;
749     }
750    
751     $obj->{spec}{regen} = $stats->hp;
752     $obj->{spec}{magic} = $stats->sp;
753     $obj->{spec}{wc} = $stats->wc;
754     $obj->{spec}{dam} = $stats->dam;
755     $obj->{spec}{ac} = $stats->ac;
756     $obj->{spec}{speed} = $stats->exp;
757     $obj->{spec}{food} = $stats->food;
758    
759     $obj->{name} = $thing->name;
760     $obj->{arch} = $thing->arch->name;
761     $obj->{face} = $thing->face;
762    
763     $self->{hash} = $obj
764     }
765    
766     sub to_object {
767     my ($self) = @_;
768    
769     my $obj = cf::object::new $self->{hash}->{arch};
770    
771 elmex 1.6 $obj->item_power ($self->power_to_level); # there have to be strings attached!
772    
773 root 1.1 $obj->face ($self->{hash}{face});
774    
775     my $stats = $obj->stats;
776    
777     $stats->hp ($self->{hash}{spec}{regen});
778     $stats->sp ($self->{hash}{spec}{magic});
779     $stats->wc ($self->{hash}{spec}{wc});
780     $stats->dam ($self->{hash}{spec}{dam});
781     $stats->ac ($self->{hash}{spec}{ac});
782     $stats->exp ($self->{hash}{spec}{speed});
783     $stats->food ($self->{hash}{spec}{food});
784    
785     $stats->$_ ($self->{hash}{stat}{lc $_})
786     for qw/Str Dex Con Wis Cha Int Pow/;
787    
788     for (@Jeweler::RESISTS) {
789 root 1.3 $obj->resist ($_, $self->{hash}->{resist}->{$_});
790 root 1.1 }
791    
792     $obj->flag (cf::FLAG_IDENTIFIED, 1);
793    
794     return $obj;
795     }
796    
797 elmex 1.4 sub is_better_than {
798     my ($self, $other) = @_;
799    
800     for my $type (qw/spec stat resist/) {
801     for my $stat (keys %{$self->{hash}->{$type}}) {
802     if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
803     return 1;
804     }
805     }
806     }
807    
808     return 0;
809     }
810    
811 root 1.1 sub stat_level {
812     my ($self) = @_;
813     my $stats = $self->{hash}->{stat} || {};
814    
815     my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
816     my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
817    
818     my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
819     my $stat_sum = sum (values %$stats); # also count the negative stats!
820     my $level = int (($maxlevel / $maxstat) * $stat_sum);
821    
822     ($level, $stat_cnt)
823     }
824    
825     sub resist_level {
826     my ($self) = @_;
827    
828     my $resists = $self->{hash}->{resist} || {};
829    
830     my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
831     my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
832     my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
833     my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
834     my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
835    
836     my $ressum = 0;
837     my $rescnt = 0;
838     my @reslevels;
839    
840     for my $resnam (keys %$resists) {
841     my $res = $resists->{$resnam};
842    
843     $rescnt++
844     if $res > 0; # negative resistancies are not an improvement
845    
846     $ressum += $res; # note: negative resistancies lower the sum
847    
848     next unless $res > 0;
849    
850     my $level = 0;
851     if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
852     $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
853     } else {
854     $level = ceil (($att_res_lvl / $max_att_res) * $res);
855     }
856     push @reslevels, $level;
857     }
858    
859     my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
860    
861     (max (@reslevels, $overall_lvl), $rescnt);
862     }
863    
864     sub special_level {
865     my ($self) = @_;
866    
867     my $specials = $self->{hash}->{spec} || {};
868    
869     my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
870     my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
871    
872     my @speclvls;
873     my $specsum = 0;
874     my $imprs = 0;
875    
876     for my $spcnam (keys %$specials) {
877     my $spc = $specials->{$spcnam};
878     next unless $spc > 0;
879    
880     $specsum += $spc;
881     $imprs++;
882    
883     my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
884    
885     my $lvl = ($max_spc_lvl / $max_spc) * $spc;
886     push @speclvls, $lvl;
887     }
888    
889     my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
890    
891     (max (@speclvls, $sumlvl), $imprs)
892     }
893    
894    
895     # this function calculated the 'level' of an amulet or a ring
896     sub power_to_level {
897     my ($self, $lvldescr) = @_;
898    
899     my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
900     my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
901    
902     my ($stat_lvl, $stat_imprs) = $self->stat_level;
903     my ($resist_lvl, $res_imprs) = $self->resist_level;
904     my ($spec_lvl, $spec_imprs) = $self->special_level;
905    
906     my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
907    
908     my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
909    
910     my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
911    
912     if ($lvldescr) {
913     $$lvldescr =
914     sprintf "%3d: %s\n", $levl,
915     "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
916     ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
917     }
918    
919     $levl
920     }
921    
922     sub add_stat_costs {
923     my ($self, $cost) = @_;
924    
925     my $stats = $self->{hash}->{stat};
926    
927     for my $stat (keys %$stats) {
928     my $sum = $stats->{$stat};
929    
930     next unless $sum > 0;
931    
932 elmex 1.4 my $statfx = fx ($sum, 'stat_items');
933 root 1.1 $cost->{"stat_$stat"} += $statfx->[0];
934     split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
935     }
936     }
937    
938     sub add_special_costs {
939     my ($self, $cost) = @_;
940    
941     my $specials = $self->{hash}->{spec};
942    
943     for my $spec (keys %$specials) {
944     my $sum = $specials->{$spec};
945    
946     next unless $sum > 0;
947    
948 elmex 1.4 my $specfx = fx ($sum, 'spec_items');
949 root 1.1 $cost->{"spec_$spec"} += $specfx->[0];
950     split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
951     }
952     }
953    
954     sub calc_costs {
955     my ($self) = @_;
956    
957     my $costs = {};
958    
959     my $ring = $self->{hash};
960    
961 elmex 1.4 for my $resnum (keys %{$ring->{resist} || {}}) {
962 root 1.1
963 elmex 1.4 my $res = $ring->{resist}->{$resnum};
964 root 1.1
965     next unless $res > 0;
966    
967 elmex 1.4 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
968 root 1.1
969     my $diamonds;
970 elmex 1.4 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
971 root 1.1 $diamonds += fx ($res, 'effect_resist_diamonds');
972     } else {
973     $diamonds += fx ($res, 'attack_resist_diamonds');
974     }
975    
976 elmex 1.4 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
977 root 1.1 }
978    
979     $self->add_stat_costs ($costs);
980     $self->add_special_costs ($costs);
981    
982     return $costs;
983     }
984    
985     sub split_diamonds {
986     my ($cost, $diamonds, $category) = @_;
987    
988     my $stat_split = Jeweler::getcfg (diamond_split => $category);
989    
990     my $sum = sum (@$stat_split);
991     if ($sum < (1 - 0.0001)) {
992     warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
993     }
994    
995     my $emarch = cf::arch::find 'emerald';
996     my $saarch = cf::arch::find 'sapphire';
997     my $pearch = cf::arch::find 'pearl';
998     my $ruarch = cf::arch::find 'ruby';
999     my $diarch = cf::arch::find 'gem';
1000    
1001     my $sumvalue = $diarch->clone->value * $diamonds;
1002    
1003     $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
1004     $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
1005     $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
1006     $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
1007     $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
1008     }
1009    
1010     package Jeweler::Util;
1011    
1012     use strict;
1013    
1014     =head2 Util
1015    
1016     Some utility functions for the Jeweler skill.
1017    
1018     =over 4
1019    
1020     =item remove ($object[, $nrof])
1021    
1022     Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1023     The returnvalue is the number of 'single' objects that couldn't be removed.
1024    
1025     =cut
1026    
1027     sub remove {
1028     my ($obj, $nrof) = @_;
1029    
1030     my $cnt;
1031    
1032     if (defined $nrof) {
1033 elmex 1.4 # TODO: Check tihis line:
1034     return 0 if ($nrof * 1) == 0; #XXX: ???
1035 root 1.1 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1036    
1037     if ($cnt > 0) {
1038     $obj->nrof ($cnt);
1039     return 0;
1040     }
1041     }
1042    
1043     remove ($_) for $obj->inv;
1044     $obj->destroy;
1045     return $cnt;
1046     }
1047    
1048 elmex 1.4 sub check_for_match {
1049 root 1.1 my ($thing, @matchar) = @_;
1050    
1051     my $i = 0;
1052     for my $match (@matchar) {
1053     if ($match =~ m/^\s*$/) {
1054     $i++;
1055     next;
1056     }
1057    
1058     if ($i % 3 == 0) {
1059     $thing->name eq $match
1060     and return 1;
1061     } elsif ($i % 3 == 1) {
1062     $thing->title eq $match
1063     and return 1;
1064     } else { # $i % 3 == 2
1065     $thing->arch->name eq $match
1066     and return 1;
1067     }
1068     $i++;
1069     }
1070     return 0;
1071     }
1072    
1073 elmex 1.4 sub grep_for_match {
1074     my ($ingred, $group, @matchar) = @_;
1075    
1076     for my $thing (@{$ingred->{$group} || []}) {
1077     warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->name, "@matchar"; #d#
1078     if (check_for_match ($thing, @matchar)) {
1079     return $thing;
1080     }
1081     }
1082     return undef;
1083     }
1084    
1085 root 1.1 =back
1086    
1087     1