ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.39
Committed: Sun Nov 18 15:19:48 2018 UTC (5 years, 5 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.38: +1 -1 lines
Log Message:
*** empty log message ***

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