ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.32
Committed: Tue Apr 27 17:08:09 2010 UTC (14 years, 1 month ago) by elmex
Branch: MAIN
Changes since 1.31: +12 -1 lines
Log Message:
some jeweler changes i have to test.

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 $obj->{is_ring} = ($thing->type == cf::RING);
851
852 $self->{hash} = $obj
853 }
854
855 sub to_object {
856 my ($self) = @_;
857
858 my $obj = cf::object::new $self->{hash}->{arch};
859
860 $obj->item_power (floor ($self->power_to_level / 3)); # there have to be strings attached!
861
862 $obj->face ($self->{hash}{face});
863
864 my $stats = $obj->stats;
865
866 $stats->hp ($self->{hash}{spec}{regen});
867 $stats->sp ($self->{hash}{spec}{magic});
868 $stats->wc ($self->{hash}{spec}{wc});
869 $stats->dam ($self->{hash}{spec}{dam});
870 $stats->ac ($self->{hash}{spec}{ac});
871 $stats->exp ($self->{hash}{spec}{speed});
872 $stats->food ($self->{hash}{spec}{food});
873
874 $stats->$_ ($self->{hash}{stat}{lc $_})
875 for qw/Str Dex Con Wis Cha Int Pow/;
876
877 for (@Jeweler::RESISTS) {
878 $obj->resist ($_, $self->{hash}->{resist}->{$_});
879 }
880
881 $obj->flag (cf::FLAG_IDENTIFIED, 1);
882
883 $obj->value ($self->{hash}{value});
884
885 return $obj;
886 }
887
888 sub set_value { $_[0]->{hash}{value} = $_[1] }
889
890 sub is_better_than {
891 my ($self, $other) = @_;
892
893 for my $type (qw/spec stat resist/) {
894 for my $stat (keys %{$self->{hash}->{$type}}) {
895 if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
896 return 1;
897 }
898 }
899 }
900
901 return 0;
902 }
903
904 sub stat_level {
905 my ($self) = @_;
906 my $stats = $self->{hash}->{stat} || {};
907
908 my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
909 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
910
911 my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
912 my $stat_sum = sum (values %$stats); # also count the negative stats!
913 my $level = int (($maxlevel / $maxstat) * $stat_sum);
914
915 ($level, $stat_cnt)
916 }
917
918 sub resist_level {
919 my ($self) = @_;
920
921 my $resists = $self->{hash}->{resist} || {};
922
923 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
924 my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
925 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
926 my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
927 my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
928
929 my $ressum = 0;
930 my $rescnt = 0;
931 my @reslevels;
932
933 for my $resnam (keys %$resists) {
934 my $res = $resists->{$resnam};
935
936 $rescnt++
937 if $res > 0; # negative resistancies are not an improvement
938
939 $ressum += $res; # note: negative resistancies lower the sum
940
941 next unless $res > 0;
942
943 my $level = 0;
944 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
945 $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
946 } else {
947 $level = ceil (($att_res_lvl / $max_att_res) * $res);
948 }
949 push @reslevels, $level;
950 }
951
952 my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
953
954 (max (@reslevels, $overall_lvl), $rescnt);
955 }
956
957 sub special_level {
958 my ($self) = @_;
959
960 my $specials = $self->{hash}->{spec} || {};
961
962 my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
963 my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
964
965 my @speclvls;
966 my $specsum = 0;
967 my $imprs = 0;
968
969 for my $spcnam (keys %$specials) {
970 my $spc = $specials->{$spcnam};
971 next unless $spc > 0;
972
973 $specsum += $spc;
974 $imprs++;
975
976 my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
977
978 my $lvl = ($max_spc_lvl / $max_spc) * $spc;
979 push @speclvls, $lvl;
980 }
981
982 my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
983
984 (max (@speclvls, $sumlvl), $imprs)
985 }
986
987
988 # this function calculated the 'level' of an amulet or a ring
989 sub power_to_level {
990 my ($self, $lvldescr) = @_;
991
992 my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
993 my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
994 my $ring_offs = Jeweler::getcfg (maxlevels => 'ring_offset');
995
996 my ($stat_lvl, $stat_imprs) = $self->stat_level;
997 my ($resist_lvl, $res_imprs) = $self->resist_level;
998 my ($spec_lvl, $spec_imprs) = $self->special_level;
999
1000 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
1001
1002 my $impr_lvl =
1003 ceil (($max_impr_lvl / ($max_imprs + 1))
1004 * ($impr_sum - 1)); # 1 improvemnt bonus
1005
1006 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
1007
1008 if ($self->{hash}->{is_ring}) {
1009 $levl += $ring_offs;
1010 }
1011
1012 $levl = min ($levl, cf::settings->max_level);
1013
1014 if ($lvldescr) {
1015 $$lvldescr =
1016 sprintf "%3d: %s\n", $levl,
1017 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
1018 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
1019 }
1020
1021 $levl
1022 }
1023
1024 sub add_stat_costs {
1025 my ($self, $cost) = @_;
1026
1027 my $stats = $self->{hash}->{stat};
1028
1029 for my $stat (keys %$stats) {
1030 my $sum = $stats->{$stat};
1031
1032 next unless $sum > 0;
1033
1034 my $statfx = fx ($sum, 'stat_items');
1035 $cost->{"stat_$stat"} += $statfx->[0];
1036 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1037 }
1038 }
1039
1040 sub add_special_costs {
1041 my ($self, $cost) = @_;
1042
1043 my $specials = $self->{hash}->{spec};
1044
1045 for my $spec (keys %$specials) {
1046 my $sum = $specials->{$spec};
1047
1048 next unless $sum > 0;
1049
1050 my $specfx = fx ($sum, 'spec_items');
1051 $cost->{"spec_$spec"} += $specfx->[0];
1052 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1053 }
1054 }
1055
1056 sub calc_costs {
1057 my ($self) = @_;
1058
1059 my $costs = {};
1060
1061 my $ring = $self->{hash};
1062
1063 for my $resnum (keys %{$ring->{resist} || {}}) {
1064
1065 my $res = $ring->{resist}->{$resnum};
1066
1067 next unless $res > 0;
1068
1069 return undef if $res == 100;
1070
1071 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1072
1073 my $diamonds;
1074 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1075 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1076 } else {
1077 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1078 }
1079
1080 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1081 }
1082
1083 $self->add_stat_costs ($costs);
1084 $self->add_special_costs ($costs);
1085
1086 return $costs;
1087 }
1088
1089 sub split_diamonds {
1090 my ($cost, $diamonds, $category) = @_;
1091
1092 my $stat_split = Jeweler::getcfg (diamond_split => $category);
1093
1094 my $sum = sum (@$stat_split);
1095
1096 my $emarch = cf::arch::find 'emerald';
1097 my $saarch = cf::arch::find 'sapphire';
1098 my $pearch = cf::arch::find 'pearl';
1099 my $ruarch = cf::arch::find 'ruby';
1100 my $diarch = cf::arch::find 'gem';
1101
1102 my $sumvalue = $diarch->value * $diamonds;
1103
1104 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1105 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1106 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1107 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1108 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1109 }
1110
1111 package Jeweler::Util;
1112
1113 use strict;
1114
1115 =head2 Util
1116
1117 Some utility functions for the Jeweler skill.
1118
1119 =over 4
1120
1121 =item remove ($object[, $nrof])
1122
1123 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1124 The return value is the number of 'single' objects that couldn't be removed.
1125
1126 =cut
1127
1128 sub remove {
1129 my ($obj, $nrof) = @_;
1130
1131 my $c = $obj->nrof || 1;
1132 my $r = $c > $nrof ? 0 : $nrof - $c;
1133 $obj->decrease (defined ($nrof) ? $nrof : ($obj->nrof || 1));
1134
1135 $r
1136 }
1137
1138 sub check_for_match {
1139 my ($thing, @matchar) = @_;
1140
1141 my $i = 0;
1142 my $check_cnts = 0;
1143 my $check_true = 0;
1144 for my $match (@matchar) {
1145 if ($i % 3 == 0) {
1146 return 1 if $check_true && $check_cnts == $check_true;
1147 $check_cnts = 0;
1148 $check_true = 0;
1149 }
1150
1151 if ($match =~ m/^\s*$/) {
1152 $i++;
1153 next;
1154 }
1155
1156 $check_cnts++;
1157 if ($i % 3 == 0) {
1158 $thing->name eq $match
1159 and $check_true++;
1160 } elsif ($i % 3 == 1) {
1161 $thing->title eq $match
1162 and $check_true++;
1163 } else { # $i % 3 == 2
1164 $thing->arch->archname eq $match
1165 and $check_true++;
1166 }
1167 $i++;
1168 }
1169 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1170 return 1 if $check_true && $check_cnts == $check_true;
1171 return 0;
1172 }
1173
1174 sub grep_for_match {
1175 my ($ingred, $group, @matchar) = @_;
1176
1177 for my $thing (@{$ingred->{$group} || []}) {
1178 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1179 if (check_for_match ($thing, @matchar)) {
1180 return $thing;
1181 }
1182 }
1183 return undef;
1184 }
1185
1186 =back
1187
1188 1