ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.33
Committed: Wed Apr 28 21:05:33 2010 UTC (14 years, 1 month ago) by root
Branch: MAIN
Changes since 1.32: +4 -8 lines
Log Message:
treat sound.conf and jeweler.yaml like other data files

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