ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/maps/perl/Jeweler.pm
Revision: 1.3
Committed: Thu Aug 31 12:39:19 2006 UTC (17 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.2: +130 -81 lines
Log Message:
further work on the jeweler skill

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