ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.34
Committed: Wed Apr 28 21:07:41 2010 UTC (14 years ago) by root
Branch: MAIN
Changes since 1.33: +2 -0 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 warn "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 ($r->to_string . ": " . $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_animation (cf::rndm $ob->num_animations)
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 strict;
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 use Storable qw/dclone/;
373 use strict;
374
375 =head2 Ingredients
376
377 This class handles the ingredients.
378
379 =over 4
380
381 =item new (ingredients => $ingred_hash)
382
383 This is called from the CauldronHandler that gives you the ingredients.
384
385 =cut
386
387 sub new {
388 my ($class, %arg) = @_;
389
390 my $self = bless {
391 %arg,
392 }, $class;
393
394 $self;
395 }
396
397 =item value ($group, $archname)
398
399 Returns the value of the ingredients in C<$group> with the archetypename C<$archname>.
400
401 =cut
402
403 sub value {
404 my ($self, $group, $archname) = @_;
405
406 my @objs = grep {
407 $_->arch->archname eq $archname
408 } @{$self->{ingredients}->{$group} || []};
409
410 my $sum = 0;
411 for (@objs) {
412 $sum += ($_->nrof || 1) * $_->value;
413 }
414
415 return $sum;
416 }
417
418 =item remove ($group, $archname)
419
420 Removes the ingredients in C<$group> with archname C<$archname>.
421 It removes all in C<$group> if archname is undef.
422
423 =cut
424
425 sub remove {
426 my ($self, $group, $archname) = @_;
427
428 my $ingred = $self->{ingredients};
429
430 my @out;
431
432 for (@{$ingred->{$group}}) {
433 if (defined $archname) {
434 if ($_->arch->archname eq $archname) {
435 Jeweler::Util::remove ($_);
436 } else {
437 push @out, $_;
438 }
439 } else {
440 Jeweler::Util::remove ($_);
441 }
442 }
443
444 @{$ingred->{$group}} = @out;
445 }
446
447 sub get_plan {
448 my ($self) = @_;
449
450 my $ingred = $self->{ingredients};
451
452 for my $plan (keys %{$Jeweler::CFG->{plans}}) {
453 my $plg = $Jeweler::CFG->{plans}->{$plan};
454 my @plga = ();
455 unless (ref $plg eq 'ARRAY') {
456 push @plga, $plg;
457 } else {
458 @plga = @$plg;
459 }
460 next unless @plga > 0;
461 if (Jeweler::Util::grep_for_match ($ingred, @plga)) {
462 return $plan;
463 }
464 }
465 }
466
467 sub get_ring {
468 my ($self) = @_;
469 return (
470 @{$self->{ingredients}->{ammys} || []},
471 @{$self->{ingredients}->{rings} || []}
472 );
473 }
474
475 sub improve_max {
476 my ($stat, $impro) = @_;
477 if ($stat >= 0) {
478 $stat = $impro > $stat ? $impro : $stat;
479 }
480 $stat
481 }
482
483 sub improve_ring_by_plan {
484 my ($self, $plan, $ring) = @_;
485
486 $ring = dclone $ring;
487
488 my $ingred = $self->{ingredients};
489 my $impr = {};
490
491 if ($plan =~ m/^stat_(\S+)$/) {
492 my $statname = $1;
493 my $plingred = Jeweler::getcfg (plans => $plan)
494 or die "ingredients for plan '$plan' not defined!";
495
496 my $cnt = 0;
497 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
498 $cnt += $pot->nrof;
499 }
500
501 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
502 for my $x (1..$maxstat) {
503 my $y = Jeweler::Object::fx ($x, 'stat_items');
504
505 if ($cnt <= $y->[0]) {
506 $ring->{hash}->{stat}->{$statname} =
507 improve_max $ring->{hash}->{stat}->{$statname}, $x;
508 last;
509 }
510 }
511
512 } elsif ($plan =~ m/^spec_(\S+)$/) {
513 my $specname = $1;
514 my $plingred = Jeweler::getcfg (plans => $plan)
515 or die "ingredients for plan '$plan' not defined!";
516
517 my $cnt = 0;
518 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
519 $cnt += $pot->nrof;
520 }
521
522 my $maxspec = Jeweler::getcfg (maximprovements => 'specials');
523 for my $x (1..$maxspec) {
524 my $y = Jeweler::Object::fx ($x, 'spec_items');
525
526 if ($cnt <= $y->[0]) {
527 $ring->{hash}->{spec}->{$specname} =
528 improve_max $ring->{hash}->{spec}->{$specname}, $x;
529 last;
530 }
531 }
532
533 } elsif ($plan =~ m/^resist_(\S+)$/) {
534 my $resname = $1;
535 my $resnum = $REV_RESMAP{$resname};
536 my $plingred = Jeweler::getcfg (plans => $plan)
537 or die "ingredients for plan '$plan' not defined!";
538
539 my $cnt = 0;
540 if (my $it = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
541 $cnt += $it->nrof;
542 }
543 my $resist_item_nr = 0;
544 $self->do_grep (sub { $resist_item_nr += ($_[0]->nrof || 1); 0 }, @$plingred);
545
546 my $maximprovname = (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS)
547 ? 'effect_resistances'
548 : 'attack_resistances';
549
550 my $maxres = Jeweler::getcfg (maximprovements => $maximprovname);
551 $resist_item_nr = $maxres if ($resist_item_nr > $maxres);
552 $ring->{hash}->{resist}->{$resnum} =
553 improve_max $ring->{hash}->{resist}->{$resnum}, $resist_item_nr;
554 }
555
556 return $ring;
557 }
558
559 sub do_grep {
560 my ($self, $cb, $cat, @grepar) = @_;
561
562 my $ingred = $self->{ingredients};
563
564 my @rem;
565 for my $ing (@{$ingred->{$cat}}) {
566 if (Jeweler::Util::check_for_match ($ing, @grepar)) {
567 unless ($cb->($ing)) {
568 push @rem, $ing;
569 }
570 } else {
571 push @rem, $ing;
572 }
573 }
574 @{$ingred->{$cat}} = @rem;
575 }
576
577 sub check_costs {
578 my ($self, $costs, $do_remove) = @_;
579
580 my $costs = dclone $costs;
581
582 for my $key (keys %$costs) {
583 my @grepar;
584 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
585 eval { @grepar = @{Jeweler::getcfg (plans => $key) || []} };
586 next if $@;
587 } else { # check the gems
588 @grepar = ('gems', undef, undef, $key);
589 }
590
591 if ($do_remove) {
592 my $rem = $costs->{$key};
593 $self->do_grep (sub {
594 if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); }
595 1
596 }, @grepar);
597 if ($rem > 0) {
598 warn "JEWELER BUG: removed ingredients ($key) $rem > 0 after removing!";
599 }
600
601 } else {
602 my $nr;
603 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
604 $costs->{$key} -= $nr;
605 }
606
607 }
608
609 return $costs;
610 }
611
612 =back
613
614 =cut
615
616 sub put_to_bench {
617 my ($self, $bench) = @_;
618
619 my $ingred = $self->{ingredients};
620
621 for my $ik (keys %$ingred) {
622 for (@{$ingred->{$ik} || []}) {
623 $bench->put ($_);
624 }
625 }
626 }
627
628 package Jeweler::Object;
629 use strict;
630 use POSIX;
631 use List::Util qw/max min sum/;
632
633 sub new {
634 my ($class, %arg) = @_;
635
636 my $self = bless { }, $class;
637
638 $self->ring_or_ammy_to_hash ($arg{object});
639
640 $self;
641 }
642
643 sub has_resist {
644 my ($self, $resistnam, $resistval) = @_;
645 my $resnum = $REV_RESMAP{uc $resistnam};
646 if (defined ($resistval)) {
647 return 1 if $self->{hash}->{resist}->{$resnum} == $resistval;
648 } else {
649 return 1 if $self->{hash}->{resist}->{$resnum};
650 }
651 return undef;
652 }
653
654 sub projected_exp {
655 my ($self, $input_level) = @_;
656
657 my $lvl = max ($self->power_to_level, 1);
658 my $exp =
659 (cf::level_to_min_exp ($lvl) - cf::level_to_min_exp ($lvl - 1))
660 / (10 + max ($lvl - 1, 0)); # 10 + level times making such a ring
661 # should get you to the rings level at least.
662
663 if (defined $input_level) {
664 my $subexp =
665 (cf::level_to_min_exp ($input_level)
666 - cf::level_to_min_exp ($input_level - 1))
667 / (10 + max ($input_level - 1, 0)); # see above for comment
668
669 $exp -= $subexp;
670 $exp = max ($exp, 0);
671
672 } else {
673 # the experience bonus here is to make level 1 rings give you at least
674 # 200 exp points when making them. This also makes leveling in the
675 # first few levels a bit easier. (probably until around level 5-6).
676 my $expbonus = cf::level_to_min_exp (2) / 5;
677 # this bonus should also only be given for _new_ rings and not for merged
678 # ones - to prevent infinite exp making.
679 $exp += $expbonus;
680 }
681
682 $exp
683 }
684
685 sub analyze {
686 my ($self, $sk, $pl, $input_level) = @_;
687 my $costs = $self->calc_costs;
688
689 unless (defined $costs) {
690 return "This ring has a resistancy above 99%, you can't make that.";
691 }
692
693 my $sklvl = cf::exp_to_level ($sk->stats->exp);
694 my $ringlvl = $self->power_to_level;
695
696 my $tmpl;
697 if ($pl->flag (cf::FLAG_WIZ)) {
698 $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
699 } else {
700 $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
701 }
702 my $msg = sprintf "Projected success rate: %s, you would get %d exp for this.", $tmpl, $self->projected_exp ($input_level);
703 return $msg;
704 }
705
706 sub calc_value_from_cost {
707 my ($self, $costs) = @_;
708 my $emarch = cf::arch::find 'emerald';
709 my $saarch = cf::arch::find 'sapphire';
710 my $pearch = cf::arch::find 'pearl';
711 my $ruarch = cf::arch::find 'ruby';
712 my $diarch = cf::arch::find 'gem';
713 my $value = $emarch->value * $costs->{emerald}
714 + $saarch->value * $costs->{sapphire}
715 + $pearch->value * $costs->{pearl}
716 + $ruarch->value * $costs->{ruby}
717 + $diarch->value * $costs->{gem};
718
719 $value
720 }
721
722 sub wiz_analyze {
723 my ($self, $pl) = @_;
724 my $costs = $self->calc_costs;
725 if (defined $costs) {
726 my $desc = "";
727 my $lvl = $self->power_to_level (\$desc);
728 my $scosts = $self->calc_value_from_cost ($costs);
729
730 $pl->message ("costs: "
731 . (join ', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)
732 . " ("
733 . ($scosts / "platinacoin"->cf::arch::find->value)
734 . " platinum)");
735 $pl->message ("level: $desc");
736 } else {
737 $pl->message ("level: impossible to make, due to impossible resistancy configuration");
738 }
739 }
740
741 sub get_chance_perc {
742 my ($self, $sk) = @_;
743 my $sklvl = cf::exp_to_level ($sk->stats->exp);
744 my $ringlvl = $self->power_to_level;
745 return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
746 }
747
748 sub fx {
749 my ($res, $cfg) = @_;
750 my $or = $res;
751 my $ar = $Jeweler::CFG->{functions}->{$cfg};
752
753 if (ref $ar && ref $ar->[0] eq 'ARRAY') {
754 $res = $res - 1;
755 return $ar->[max (min ($res, @$ar - 1), 0)];
756
757 } else {
758 # +0.1 is for a jump to the next index when $res / 5 is exactly 1, 2, 3...
759 # old code:
760 #my $idx = ceil (($res / 5) + 0.1) - 1;
761 #my $a = $ar->[max (min ($idx, @$ar - 1), 0)];
762 #my $b = $ar->[max (min ($idx + 1, @$ar - 1), 0)];
763 #my $diff = $b - $a; # use the difference of the cost to the next cost
764 #my $o_cost = $a + ($diff / 5) * ($res % 5); # and do some linear interpolation
765 #return $o_cost;
766 return 0 if $res <= 0;
767 return ($ar / (1 - ($res * 0.01)) - $ar)
768 }
769 }
770
771 sub improve_by_ring {
772 my ($self, @rings) = @_;
773 my $ring = $self;
774 for my $iring (@rings) {
775 for my $cat (qw/stat spec resist/) {
776 for my $k (keys %{$iring->{hash}->{$cat}}) {
777 if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
778 $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
779 }
780 }
781 }
782 }
783 }
784
785 sub negate {
786 my ($self) = @_;
787 for my $cat (qw/stat spec resist/) {
788 for my $k (keys %{$self->{hash}->{$cat}}) {
789 if ($self->{hash}->{$cat}->{$k} > 0) {
790 $self->{hash}->{$cat}->{$k} *= -1;
791 }
792 }
793 }
794 $self->{hash}{value} = 0;
795 }
796
797 sub to_string {
798 my ($self) = @_;
799 my $r = $self->{hash};
800 return
801 $r->{arch} . " " .
802 join ("",
803 grep { $_ ne "" }
804 join ("",
805 (map {
806 my $rv = $r->{resist}->{$_};
807 "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
808 } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
809 (map {
810 my $rv = $r->{stat}->{$_};
811 "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
812 } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
813 (map {
814 my $rv = $r->{spec}->{$_};
815 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
816 } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
817 }
818
819 sub ring_or_ammy_to_hash {
820 my ($self, $thing) = @_;
821
822 my $obj = {};
823
824 for (@Jeweler::RESISTS) {
825 $obj->{resist}->{$_} = $thing->resist ($_);
826 }
827
828 my $stats = $thing->stats;
829
830 for (qw/Str Dex Con Wis Cha Int Pow/) {
831 $obj->{stat}->{lc $_} = $stats->$_;
832 }
833
834 $obj->{spec}{regen} = $stats->hp;
835 $obj->{spec}{magic} = $stats->sp;
836 $obj->{spec}{wc} = $stats->wc;
837 $obj->{spec}{dam} = $stats->dam;
838 $obj->{spec}{ac} = $stats->ac;
839 $obj->{spec}{speed} = $stats->exp;
840 $obj->{spec}{food} = $stats->food;
841
842 $obj->{name} = $thing->name;
843 $obj->{arch} = $thing->arch->archname;
844 $obj->{face} = $thing->face;
845
846 $obj->{value} = $thing->value;
847
848 $obj->{is_ring} = ($thing->type == cf::RING);
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 my $ring_offs = Jeweler::getcfg (maxlevels => 'ring_offset');
993
994 my ($stat_lvl, $stat_imprs) = $self->stat_level;
995 my ($resist_lvl, $res_imprs) = $self->resist_level;
996 my ($spec_lvl, $spec_imprs) = $self->special_level;
997
998 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
999
1000 my $impr_lvl =
1001 ceil (($max_impr_lvl / ($max_imprs + 1))
1002 * ($impr_sum - 1)); # 1 improvemnt bonus
1003
1004 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
1005
1006 if ($self->{hash}->{is_ring}) {
1007 $levl += $ring_offs;
1008 }
1009
1010 $levl = min ($levl, cf::settings->max_level);
1011
1012 if ($lvldescr) {
1013 $$lvldescr =
1014 sprintf "%3d: %s\n", $levl,
1015 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
1016 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
1017 }
1018
1019 $levl
1020 }
1021
1022 sub add_stat_costs {
1023 my ($self, $cost) = @_;
1024
1025 my $stats = $self->{hash}->{stat};
1026
1027 for my $stat (keys %$stats) {
1028 my $sum = $stats->{$stat};
1029
1030 next unless $sum > 0;
1031
1032 my $statfx = fx ($sum, 'stat_items');
1033 $cost->{"stat_$stat"} += $statfx->[0];
1034 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1035 }
1036 }
1037
1038 sub add_special_costs {
1039 my ($self, $cost) = @_;
1040
1041 my $specials = $self->{hash}->{spec};
1042
1043 for my $spec (keys %$specials) {
1044 my $sum = $specials->{$spec};
1045
1046 next unless $sum > 0;
1047
1048 my $specfx = fx ($sum, 'spec_items');
1049 $cost->{"spec_$spec"} += $specfx->[0];
1050 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1051 }
1052 }
1053
1054 sub calc_costs {
1055 my ($self) = @_;
1056
1057 my $costs = {};
1058
1059 my $ring = $self->{hash};
1060
1061 for my $resnum (keys %{$ring->{resist} || {}}) {
1062
1063 my $res = $ring->{resist}->{$resnum};
1064
1065 next unless $res > 0;
1066
1067 return undef if $res == 100;
1068
1069 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1070
1071 my $diamonds;
1072 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1073 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1074 } else {
1075 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1076 }
1077
1078 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1079 }
1080
1081 $self->add_stat_costs ($costs);
1082 $self->add_special_costs ($costs);
1083
1084 return $costs;
1085 }
1086
1087 sub split_diamonds {
1088 my ($cost, $diamonds, $category) = @_;
1089
1090 my $stat_split = Jeweler::getcfg (diamond_split => $category);
1091
1092 my $sum = sum (@$stat_split);
1093
1094 my $emarch = cf::arch::find 'emerald';
1095 my $saarch = cf::arch::find 'sapphire';
1096 my $pearch = cf::arch::find 'pearl';
1097 my $ruarch = cf::arch::find 'ruby';
1098 my $diarch = cf::arch::find 'gem';
1099
1100 my $sumvalue = $diarch->value * $diamonds;
1101
1102 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1103 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1104 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1105 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1106 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1107 }
1108
1109 package Jeweler::Util;
1110
1111 use strict;
1112
1113 =head2 Util
1114
1115 Some utility functions for the Jeweler skill.
1116
1117 =over 4
1118
1119 =item remove ($object[, $nrof])
1120
1121 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1122 The return value is the number of 'single' objects that couldn't be removed.
1123
1124 =cut
1125
1126 sub remove {
1127 my ($obj, $nrof) = @_;
1128
1129 my $c = $obj->nrof || 1;
1130 my $r = $c > $nrof ? 0 : $nrof - $c;
1131 $obj->decrease (defined ($nrof) ? $nrof : ($obj->nrof || 1));
1132
1133 $r
1134 }
1135
1136 sub check_for_match {
1137 my ($thing, @matchar) = @_;
1138
1139 my $i = 0;
1140 my $check_cnts = 0;
1141 my $check_true = 0;
1142 for my $match (@matchar) {
1143 if ($i % 3 == 0) {
1144 return 1 if $check_true && $check_cnts == $check_true;
1145 $check_cnts = 0;
1146 $check_true = 0;
1147 }
1148
1149 if ($match =~ m/^\s*$/) {
1150 $i++;
1151 next;
1152 }
1153
1154 $check_cnts++;
1155 if ($i % 3 == 0) {
1156 $thing->name eq $match
1157 and $check_true++;
1158 } elsif ($i % 3 == 1) {
1159 $thing->title eq $match
1160 and $check_true++;
1161 } else { # $i % 3 == 2
1162 $thing->arch->archname eq $match
1163 and $check_true++;
1164 }
1165 $i++;
1166 }
1167 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1168 return 1 if $check_true && $check_cnts == $check_true;
1169 return 0;
1170 }
1171
1172 sub grep_for_match {
1173 my ($ingred, $group, @matchar) = @_;
1174
1175 for my $thing (@{$ingred->{$group} || []}) {
1176 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1177 if (check_for_match ($thing, @matchar)) {
1178 return $thing;
1179 }
1180 }
1181 return undef;
1182 }
1183
1184 =back
1185
1186 1