ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.23
Committed: Mon Dec 17 08:03:22 2007 UTC (16 years, 5 months ago) by root
Branch: MAIN
CVS Tags: rel-2_4, rel-2_41
Changes since 1.22: +4 -5 lines
Log Message:
- separate internal (undefined type) and client-exported (else)
  resources.
- new %cf::RESOURCE hash for internal resources
- move jeweler.yaml to archetype ("resource") data
- support resource data filters and implement yaml2json
  (this reduced loading time for the jeweler data by a factor of 1000 :).
- clean up Jeweler.pm a bit.

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