ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/maps/perl/Jeweler.pm
Revision: 1.5
Committed: Fri Sep 8 15:21:04 2006 UTC (17 years, 8 months ago) by root
Branch: MAIN
Changes since 1.4: +4 -4 lines
Log Message:
get_flag => flag

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