ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.30
Committed: Tue Jan 26 16:13:47 2010 UTC (14 years, 4 months ago) by elmex
Branch: MAIN
CVS Tags: rel-2_93
Changes since 1.29: +10 -5 lines
Log Message:
limited jeweler skill a bit.

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