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