ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.29
Committed: Mon Oct 12 14:00:58 2009 UTC (14 years, 7 months ago) by root
Branch: MAIN
CVS Tags: rel-2_82, rel-2_81, rel-2_90, rel-2_92
Changes since 1.28: +1 -2 lines
Log Message:
clarify license

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