ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.5
Committed: Wed Jan 31 15:53:17 2007 UTC (17 years, 4 months ago) by elmex
Branch: MAIN
Changes since 1.4: +15 -2 lines
Log Message:
Changed jeweler balancing and made values affordable.
And also don't allow cursed items in the workbench.

File Contents

# Content
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 our %REV_RESMAP = map { $RESMAP{$_} => $_ } keys %RESMAP;
123
124 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 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
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 $pl->ob->change_exp ($xp_sum, "jeweler", cf::SK_EXP_SKILL_ONLY);
240 }
241 } else {
242 $pl->ob->message ("You fail to make something, propably you used not enough source material?");
243 }
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 if ($_->flag (cf::FLAG_CURSED) || $_->flag (cf::FLAG_DAMNED)) {
334 die "cursed";
335 }
336
337 if (my $k = $type_to_key{$_->type}) {
338 push @{$ingreds->{$k}}, $_;
339 } else {
340 push @{$ingreds->{other}}, $_;
341 }
342 }
343
344 return Jeweler::Ingredients->new (ingredients => $ingreds, cauldron_helper => $self)
345 }
346
347 =item put ($object)
348
349 Just puts the C<$object> into the cauldron.
350
351 =cut
352
353 sub put {
354 my ($self, $obj) = @_;
355
356 return undef unless $self->{cauldron};
357 $obj->insert_ob_in_ob ($self->{cauldron});
358 }
359
360 =back
361
362 =cut
363
364 package Jeweler::Ingredients;
365 use Storable qw/dclone/;
366 use strict;
367
368 =head2 Ingredients
369
370 This class handles the ingredients.
371
372 =over 4
373
374 =item new (ingredients => $ingred_hash)
375
376 This is called from the CauldronHandler that gives you the ingredients.
377
378 =cut
379
380 sub new {
381 my ($class, %arg) = @_;
382
383 my $self = bless {
384 %arg,
385 }, $class;
386
387 $self;
388 }
389
390 =item value ($group, $archname)
391
392 Returns the value of the ingredients in C<$group> with the archetypename C<$archname>.
393
394 =cut
395
396 sub value {
397 my ($self, $group, $archname) = @_;
398
399 my @objs = grep {
400 $_->arch->name eq $archname
401 } @{$self->{ingredients}->{$group} || []};
402
403 my $sum = 0;
404 for (@objs) {
405 $sum += ($_->nrof || 1) * $_->value;
406 }
407
408 return $sum;
409 }
410
411 =item remove ($group, $archname)
412
413 Removes the ingredients in C<$group> with archname C<$archname>.
414 It removes all in C<$group> if archname is undef.
415
416 =cut
417
418 sub remove {
419 my ($self, $group, $archname) = @_;
420
421 my $ingred = $self->{ingredients};
422
423 my @out;
424
425 for (@{$ingred->{$group}}) {
426 if (defined $archname) {
427 if ($_->arch->name eq $archname) {
428 Jeweler::Util::remove ($_);
429 } else {
430 push @out, $_;
431 }
432 } else {
433 Jeweler::Util::remove ($_);
434 }
435 }
436
437 @{$ingred->{$group}} = @out;
438 }
439
440 sub get_plan {
441 my ($self) = @_;
442
443 my $ingred = $self->{ingredients};
444
445 for my $plan (keys %{$Jeweler::CFG->{plans}}) {
446 my $plg = $Jeweler::CFG->{plans}->{$plan};
447 my @plga = ();
448 unless (ref $plg eq 'ARRAY') {
449 push @plga, $plg;
450 } else {
451 @plga = @$plg;
452 }
453 next unless @plga > 0;
454 if (Jeweler::Util::grep_for_match ($ingred, @plga)) {
455 return $plan;
456 }
457 }
458 }
459
460 sub get_ring {
461 my ($self) = @_;
462 return (
463 @{$self->{ingredients}->{ammys} || []},
464 @{$self->{ingredients}->{rings} || []}
465 );
466 }
467
468 sub improve_max {
469 my ($stat, $impro) = @_;
470 if ($stat >= 0) {
471 $stat = $impro > $stat ? $impro : $stat;
472 }
473 $stat
474 }
475
476 sub improve_ring_by_plan {
477 my ($self, $plan, $ring) = @_;
478
479 $ring = dclone ($ring);
480
481 my $ingred = $self->{ingredients};
482 my $impr = {};
483
484 if ($plan =~ m/^stat_(\S+)$/) {
485 my $statname = $1;
486 my $plingred = Jeweler::getcfg (plans => $plan)
487 or die "ingredients for plan '$plan' not defined!";
488
489 my $cnt = 0;
490 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
491 $cnt += $pot->nrof;
492 }
493
494 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
495 for my $x (1..$maxstat) {
496 my $y = Jeweler::Object::fx ($x, 'stat_items');
497
498 if ($cnt <= $y->[0]) {
499 $ring->{hash}->{stat}->{$statname} =
500 improve_max $ring->{hash}->{stat}->{$statname}, $x;
501 last;
502 }
503 }
504
505 } elsif ($plan =~ m/^spec_(\S+)$/) {
506 my $specname = $1;
507 my $plingred = Jeweler::getcfg (plans => $plan)
508 or die "ingredients for plan '$plan' not defined!";
509
510 my $cnt = 0;
511 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
512 $cnt += $pot->nrof;
513 }
514
515 my $maxspec = Jeweler::getcfg (maximprovements => 'specials');
516 for my $x (1..$maxspec) {
517 my $y = Jeweler::Object::fx ($x, 'spec_items');
518
519 if ($cnt <= $y->[0]) {
520 $ring->{hash}->{spec}->{$specname} =
521 improve_max $ring->{hash}->{spec}->{$specname}, $x;
522 last;
523 }
524 }
525
526 } elsif ($plan =~ m/^resist_(\S+)$/) {
527 my $resname = $1;
528 my $resnum = $REV_RESMAP{$resname};
529 my $plingred = Jeweler::getcfg (plans => $plan)
530 or die "ingredients for plan '$plan' not defined!";
531
532 my $cnt = 0;
533 if (my $it = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
534 $cnt += $it->nrof;
535 }
536 my $resist_item_nr = 0;
537 $self->do_grep (sub { $resist_item_nr += ($_[0]->nrof || 1); 0 }, @$plingred);
538
539 my $maximprovname = (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS)
540 ? 'effect_resistances'
541 : 'attack_resistances';
542
543 my $maxres = Jeweler::getcfg (maximprovements => $maximprovname);
544 $resist_item_nr = $maxres if ($resist_item_nr > $maxres);
545 $ring->{hash}->{resist}->{$resnum} =
546 improve_max $ring->{hash}->{resist}->{$resnum}, $resist_item_nr;
547 }
548
549 return $ring;
550 }
551
552 sub do_grep {
553 my ($self, $cb, $cat, @grepar) = @_;
554
555 my $ingred = $self->{ingredients};
556
557 my @rem;
558 for my $ing (@{$ingred->{$cat}}) {
559 if (Jeweler::Util::check_for_match ($ing, @grepar)) {
560 unless ($cb->($ing)) {
561 push @rem, $ing;
562 }
563 } else {
564 push @rem, $ing;
565 }
566 }
567 @{$ingred->{$cat}} = @rem;
568 }
569
570 sub check_costs {
571 my ($self, $costs, $do_remove) = @_;
572
573 my $costs = dclone ($costs);
574
575 for my $key (keys %$costs) {
576 my @grepar;
577 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
578 @grepar = @{Jeweler::getcfg (plans => $key) || []};
579 } else { # check the gems
580 @grepar = ('gems', undef, undef, $key);
581 }
582
583 if ($do_remove) {
584 my $rem = $costs->{$key};
585 $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
586 if ($rem > 0) {
587 warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
588 }
589 } else {
590 my $nr;
591 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
592 $costs->{$key} -= $nr;
593 }
594
595 }
596
597 return $costs;
598 }
599
600 =back
601
602 =cut
603
604 sub put_to_bench {
605 my ($self, $bench) = @_;
606
607 my $ingred = $self->{ingredients};
608
609 for my $ik (keys %$ingred) {
610 for (@{$ingred->{$ik} || []}) {
611 $bench->put ($_);
612 }
613 }
614 }
615
616 package Jeweler::Object;
617 use strict;
618 use POSIX;
619 use List::Util qw/max min sum/;
620
621 sub new {
622 my ($class, %arg) = @_;
623
624 my $self = bless { }, $class;
625
626 $self->ring_or_ammy_to_hash ($arg{object});
627
628 $self;
629 }
630
631 sub analyze {
632 my ($self, $sk, $pl) = @_;
633
634 my $sklvl = cf::exp_to_level ($sk->stats->exp);
635 my $ringlvl = $self->power_to_level;
636
637 my $tmpl;
638 if ($pl->flag (cf::FLAG_WIZ)) {
639 $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
640 } else {
641 $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
642 }
643 my $msg = sprintf "Projected success rate: %s", $tmpl;
644 return $msg;
645 }
646
647 sub wiz_analyze {
648 my ($self, $pl) = @_;
649 my $costs = $self->calc_costs;
650 my $desc = "";
651 my $lvl = $self->power_to_level (\$desc);
652 my $emarch = cf::arch::find 'emerald';
653 my $saarch = cf::arch::find 'sapphire';
654 my $pearch = cf::arch::find 'pearl';
655 my $ruarch = cf::arch::find 'ruby';
656 my $diarch = cf::arch::find 'gem';
657 my $scosts = $emarch->clone->value * $costs->{emerald}
658 + $saarch->clone->value * $costs->{sapphire}
659 + $pearch->clone->value * $costs->{pearl}
660 + $ruarch->clone->value * $costs->{ruby}
661 + $diarch->clone->value * $costs->{gem};
662
663 $pl->message ("costs: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
664 $pl->message ("level: " . $desc);
665 }
666
667
668 sub get_chance_perc {
669 my ($self, $sk) = @_;
670 my $sklvl = cf::exp_to_level ($sk->stats->exp);
671 my $ringlvl = $self->power_to_level;
672 return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
673 }
674
675 sub fx {
676 my ($res, $cfg) = @_;
677 my $or = $res;
678 my $ar = $Jeweler::CFG->{functions}->{$cfg};
679 if (ref $ar->[0] eq 'ARRAY') {
680 $res = $res - 1;
681 } else {
682 $res = ceil ($res / 5) - 1;
683 }
684 $ar->[max (min ($res, @$ar - 1), 0)];
685 }
686
687 sub improve_by_ring {
688 my ($self, @rings) = @_;
689 my $ring = $self;
690 for my $iring (@rings) {
691 for my $cat (qw/stat spec resist/) {
692 for my $k (keys %{$iring->{hash}->{$cat}}) {
693 if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
694 $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
695 }
696 }
697 }
698 }
699 }
700
701 sub negate {
702 my ($self) = @_;
703 for my $cat (qw/stat spec resist/) {
704 for my $k (keys %{$self->{hash}->{$cat}}) {
705 if ($self->{hash}->{$cat}->{$k} > 0) {
706 $self->{hash}->{$cat}->{$k} *= -1;
707 }
708 }
709 }
710 }
711
712 sub to_string {
713 my ($self) = @_;
714 my $r = $self->{hash};
715 return
716 $r->{arch} . " " .
717 join ("",
718 grep { $_ ne "" }
719 join ("",
720 (map {
721 my $rv = $r->{resist}->{$_};
722 "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
723 } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
724 (map {
725 my $rv = $r->{stat}->{$_};
726 "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
727 } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
728 (map {
729 my $rv = $r->{spec}->{$_};
730 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
731 } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
732 }
733
734 sub ring_or_ammy_to_hash {
735 my ($self, $thing) = @_;
736
737 my $obj = {};
738
739 for (@Jeweler::RESISTS) {
740 $obj->{resist}->{$_} = $thing->resist ($_);
741 }
742
743 my $stats = $thing->stats;
744
745 for (qw/Str Dex Con Wis Cha Int Pow/) {
746 $obj->{stat}->{lc $_} = $stats->$_;
747 }
748
749 $obj->{spec}{regen} = $stats->hp;
750 $obj->{spec}{magic} = $stats->sp;
751 $obj->{spec}{wc} = $stats->wc;
752 $obj->{spec}{dam} = $stats->dam;
753 $obj->{spec}{ac} = $stats->ac;
754 $obj->{spec}{speed} = $stats->exp;
755 $obj->{spec}{food} = $stats->food;
756
757 $obj->{name} = $thing->name;
758 $obj->{arch} = $thing->arch->name;
759 $obj->{face} = $thing->face;
760
761 $self->{hash} = $obj
762 }
763
764 sub to_object {
765 my ($self) = @_;
766
767 my $obj = cf::object::new $self->{hash}->{arch};
768
769 $obj->face ($self->{hash}{face});
770
771 my $stats = $obj->stats;
772
773 $stats->hp ($self->{hash}{spec}{regen});
774 $stats->sp ($self->{hash}{spec}{magic});
775 $stats->wc ($self->{hash}{spec}{wc});
776 $stats->dam ($self->{hash}{spec}{dam});
777 $stats->ac ($self->{hash}{spec}{ac});
778 $stats->exp ($self->{hash}{spec}{speed});
779 $stats->food ($self->{hash}{spec}{food});
780
781 $stats->$_ ($self->{hash}{stat}{lc $_})
782 for qw/Str Dex Con Wis Cha Int Pow/;
783
784 for (@Jeweler::RESISTS) {
785 $obj->resist ($_, $self->{hash}->{resist}->{$_});
786 }
787
788 $obj->flag (cf::FLAG_IDENTIFIED, 1);
789
790 return $obj;
791 }
792
793 sub is_better_than {
794 my ($self, $other) = @_;
795
796 for my $type (qw/spec stat resist/) {
797 for my $stat (keys %{$self->{hash}->{$type}}) {
798 if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
799 return 1;
800 }
801 }
802 }
803
804 return 0;
805 }
806
807 sub stat_level {
808 my ($self) = @_;
809 my $stats = $self->{hash}->{stat} || {};
810
811 my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
812 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
813
814 my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
815 my $stat_sum = sum (values %$stats); # also count the negative stats!
816 my $level = int (($maxlevel / $maxstat) * $stat_sum);
817
818 ($level, $stat_cnt)
819 }
820
821 sub resist_level {
822 my ($self) = @_;
823
824 my $resists = $self->{hash}->{resist} || {};
825
826 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
827 my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
828 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
829 my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
830 my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
831
832 my $ressum = 0;
833 my $rescnt = 0;
834 my @reslevels;
835
836 for my $resnam (keys %$resists) {
837 my $res = $resists->{$resnam};
838
839 $rescnt++
840 if $res > 0; # negative resistancies are not an improvement
841
842 $ressum += $res; # note: negative resistancies lower the sum
843
844 next unless $res > 0;
845
846 my $level = 0;
847 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
848 $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
849 } else {
850 $level = ceil (($att_res_lvl / $max_att_res) * $res);
851 }
852 push @reslevels, $level;
853 }
854
855 my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
856
857 (max (@reslevels, $overall_lvl), $rescnt);
858 }
859
860 sub special_level {
861 my ($self) = @_;
862
863 my $specials = $self->{hash}->{spec} || {};
864
865 my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
866 my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
867
868 my @speclvls;
869 my $specsum = 0;
870 my $imprs = 0;
871
872 for my $spcnam (keys %$specials) {
873 my $spc = $specials->{$spcnam};
874 next unless $spc > 0;
875
876 $specsum += $spc;
877 $imprs++;
878
879 my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
880
881 my $lvl = ($max_spc_lvl / $max_spc) * $spc;
882 push @speclvls, $lvl;
883 }
884
885 my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
886
887 (max (@speclvls, $sumlvl), $imprs)
888 }
889
890
891 # this function calculated the 'level' of an amulet or a ring
892 sub power_to_level {
893 my ($self, $lvldescr) = @_;
894
895 my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
896 my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
897
898 my ($stat_lvl, $stat_imprs) = $self->stat_level;
899 my ($resist_lvl, $res_imprs) = $self->resist_level;
900 my ($spec_lvl, $spec_imprs) = $self->special_level;
901
902 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
903
904 my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
905
906 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
907
908 if ($lvldescr) {
909 $$lvldescr =
910 sprintf "%3d: %s\n", $levl,
911 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
912 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
913 }
914
915 $levl
916 }
917
918 sub add_stat_costs {
919 my ($self, $cost) = @_;
920
921 my $stats = $self->{hash}->{stat};
922
923 for my $stat (keys %$stats) {
924 my $sum = $stats->{$stat};
925
926 next unless $sum > 0;
927
928 my $statfx = fx ($sum, 'stat_items');
929 $cost->{"stat_$stat"} += $statfx->[0];
930 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
931 }
932 }
933
934 sub add_special_costs {
935 my ($self, $cost) = @_;
936
937 my $specials = $self->{hash}->{spec};
938
939 for my $spec (keys %$specials) {
940 my $sum = $specials->{$spec};
941
942 next unless $sum > 0;
943
944 my $specfx = fx ($sum, 'spec_items');
945 $cost->{"spec_$spec"} += $specfx->[0];
946 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
947 }
948 }
949
950 sub calc_costs {
951 my ($self) = @_;
952
953 my $costs = {};
954
955 my $ring = $self->{hash};
956
957 for my $resnum (keys %{$ring->{resist} || {}}) {
958
959 my $res = $ring->{resist}->{$resnum};
960
961 next unless $res > 0;
962
963 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
964
965 my $diamonds;
966 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
967 $diamonds += fx ($res, 'effect_resist_diamonds');
968 } else {
969 $diamonds += fx ($res, 'attack_resist_diamonds');
970 }
971
972 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
973 }
974
975 $self->add_stat_costs ($costs);
976 $self->add_special_costs ($costs);
977
978 return $costs;
979 }
980
981 sub split_diamonds {
982 my ($cost, $diamonds, $category) = @_;
983
984 my $stat_split = Jeweler::getcfg (diamond_split => $category);
985
986 my $sum = sum (@$stat_split);
987 if ($sum < (1 - 0.0001)) {
988 warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
989 }
990
991 my $emarch = cf::arch::find 'emerald';
992 my $saarch = cf::arch::find 'sapphire';
993 my $pearch = cf::arch::find 'pearl';
994 my $ruarch = cf::arch::find 'ruby';
995 my $diarch = cf::arch::find 'gem';
996
997 my $sumvalue = $diarch->clone->value * $diamonds;
998
999 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
1000 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
1001 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
1002 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
1003 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
1004 }
1005
1006 package Jeweler::Util;
1007
1008 use strict;
1009
1010 =head2 Util
1011
1012 Some utility functions for the Jeweler skill.
1013
1014 =over 4
1015
1016 =item remove ($object[, $nrof])
1017
1018 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1019 The returnvalue is the number of 'single' objects that couldn't be removed.
1020
1021 =cut
1022
1023 sub remove {
1024 my ($obj, $nrof) = @_;
1025
1026 my $cnt;
1027
1028 if (defined $nrof) {
1029 # TODO: Check tihis line:
1030 return 0 if ($nrof * 1) == 0; #XXX: ???
1031 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1032
1033 if ($cnt > 0) {
1034 $obj->nrof ($cnt);
1035 return 0;
1036 }
1037 }
1038
1039 remove ($_) for $obj->inv;
1040 $obj->destroy;
1041 return $cnt;
1042 }
1043
1044 sub check_for_match {
1045 my ($thing, @matchar) = @_;
1046
1047 my $i = 0;
1048 for my $match (@matchar) {
1049 if ($match =~ m/^\s*$/) {
1050 $i++;
1051 next;
1052 }
1053
1054 if ($i % 3 == 0) {
1055 $thing->name eq $match
1056 and return 1;
1057 } elsif ($i % 3 == 1) {
1058 $thing->title eq $match
1059 and return 1;
1060 } else { # $i % 3 == 2
1061 $thing->arch->name eq $match
1062 and return 1;
1063 }
1064 $i++;
1065 }
1066 return 0;
1067 }
1068
1069 sub grep_for_match {
1070 my ($ingred, $group, @matchar) = @_;
1071
1072 for my $thing (@{$ingred->{$group} || []}) {
1073 warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->name, "@matchar"; #d#
1074 if (check_for_match ($thing, @matchar)) {
1075 return $thing;
1076 }
1077 }
1078 return undef;
1079 }
1080
1081 =back
1082
1083 1