ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.24
Committed: Sun Feb 17 22:37:34 2008 UTC (16 years, 3 months ago) by elmex
Branch: MAIN
CVS Tags: rel-2_43, rel-2_42
Changes since 1.23: +4 -0 lines
Log Message:
fixed a missing output

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: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
725 $pl->message ("level: " . $desc);
726 } else {
727 $pl->message ("level: impossible to make, due to impossible resistancy configuration");
728 }
729 }
730
731 sub get_chance_perc {
732 my ($self, $sk) = @_;
733 my $sklvl = cf::exp_to_level ($sk->stats->exp);
734 my $ringlvl = $self->power_to_level;
735 return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
736 }
737
738 sub fx {
739 my ($res, $cfg) = @_;
740 my $or = $res;
741 my $ar = $Jeweler::CFG->{functions}->{$cfg};
742
743 if (ref $ar && ref $ar->[0] eq 'ARRAY') {
744 $res = $res - 1;
745 return $ar->[max (min ($res, @$ar - 1), 0)];
746
747 } else {
748 # +0.1 is for a jump to the next index when $res / 5 is exactly 1, 2, 3...
749 # old code:
750 #my $idx = ceil (($res / 5) + 0.1) - 1;
751 #my $a = $ar->[max (min ($idx, @$ar - 1), 0)];
752 #my $b = $ar->[max (min ($idx + 1, @$ar - 1), 0)];
753 #my $diff = $b - $a; # use the difference of the cost to the next cost
754 #my $o_cost = $a + ($diff / 5) * ($res % 5); # and do some linear interpolation
755 #return $o_cost;
756 return 0 if $res <= 0;
757 return ($ar / (1 - ($res * 0.01)) - $ar)
758 }
759 }
760
761 sub improve_by_ring {
762 my ($self, @rings) = @_;
763 my $ring = $self;
764 for my $iring (@rings) {
765 for my $cat (qw/stat spec resist/) {
766 for my $k (keys %{$iring->{hash}->{$cat}}) {
767 if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
768 $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
769 }
770 }
771 }
772 }
773 }
774
775 sub negate {
776 my ($self) = @_;
777 for my $cat (qw/stat spec resist/) {
778 for my $k (keys %{$self->{hash}->{$cat}}) {
779 if ($self->{hash}->{$cat}->{$k} > 0) {
780 $self->{hash}->{$cat}->{$k} *= -1;
781 }
782 }
783 }
784 $self->{hash}{value} = 0;
785 }
786
787 sub to_string {
788 my ($self) = @_;
789 my $r = $self->{hash};
790 return
791 $r->{arch} . " " .
792 join ("",
793 grep { $_ ne "" }
794 join ("",
795 (map {
796 my $rv = $r->{resist}->{$_};
797 "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
798 } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
799 (map {
800 my $rv = $r->{stat}->{$_};
801 "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
802 } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
803 (map {
804 my $rv = $r->{spec}->{$_};
805 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
806 } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
807 }
808
809 sub ring_or_ammy_to_hash {
810 my ($self, $thing) = @_;
811
812 my $obj = {};
813
814 for (@Jeweler::RESISTS) {
815 $obj->{resist}->{$_} = $thing->resist ($_);
816 }
817
818 my $stats = $thing->stats;
819
820 for (qw/Str Dex Con Wis Cha Int Pow/) {
821 $obj->{stat}->{lc $_} = $stats->$_;
822 }
823
824 $obj->{spec}{regen} = $stats->hp;
825 $obj->{spec}{magic} = $stats->sp;
826 $obj->{spec}{wc} = $stats->wc;
827 $obj->{spec}{dam} = $stats->dam;
828 $obj->{spec}{ac} = $stats->ac;
829 $obj->{spec}{speed} = $stats->exp;
830 $obj->{spec}{food} = $stats->food;
831
832 $obj->{name} = $thing->name;
833 $obj->{arch} = $thing->arch->archname;
834 $obj->{face} = $thing->face;
835
836 $obj->{value} = $thing->value;
837
838 $self->{hash} = $obj
839 }
840
841 sub to_object {
842 my ($self) = @_;
843
844 my $obj = cf::object::new $self->{hash}->{arch};
845
846 $obj->item_power (floor ($self->power_to_level / 3)); # there have to be strings attached!
847
848 $obj->face ($self->{hash}{face});
849
850 my $stats = $obj->stats;
851
852 $stats->hp ($self->{hash}{spec}{regen});
853 $stats->sp ($self->{hash}{spec}{magic});
854 $stats->wc ($self->{hash}{spec}{wc});
855 $stats->dam ($self->{hash}{spec}{dam});
856 $stats->ac ($self->{hash}{spec}{ac});
857 $stats->exp ($self->{hash}{spec}{speed});
858 $stats->food ($self->{hash}{spec}{food});
859
860 $stats->$_ ($self->{hash}{stat}{lc $_})
861 for qw/Str Dex Con Wis Cha Int Pow/;
862
863 for (@Jeweler::RESISTS) {
864 $obj->resist ($_, $self->{hash}->{resist}->{$_});
865 }
866
867 $obj->flag (cf::FLAG_IDENTIFIED, 1);
868
869 $obj->value ($self->{hash}{value});
870
871 return $obj;
872 }
873
874 sub set_value { $_[0]->{hash}{value} = $_[1] }
875
876 sub is_better_than {
877 my ($self, $other) = @_;
878
879 for my $type (qw/spec stat resist/) {
880 for my $stat (keys %{$self->{hash}->{$type}}) {
881 if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
882 return 1;
883 }
884 }
885 }
886
887 return 0;
888 }
889
890 sub stat_level {
891 my ($self) = @_;
892 my $stats = $self->{hash}->{stat} || {};
893
894 my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
895 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
896
897 my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
898 my $stat_sum = sum (values %$stats); # also count the negative stats!
899 my $level = int (($maxlevel / $maxstat) * $stat_sum);
900
901 ($level, $stat_cnt)
902 }
903
904 sub resist_level {
905 my ($self) = @_;
906
907 my $resists = $self->{hash}->{resist} || {};
908
909 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
910 my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
911 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
912 my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
913 my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
914
915 my $ressum = 0;
916 my $rescnt = 0;
917 my @reslevels;
918
919 for my $resnam (keys %$resists) {
920 my $res = $resists->{$resnam};
921
922 $rescnt++
923 if $res > 0; # negative resistancies are not an improvement
924
925 $ressum += $res; # note: negative resistancies lower the sum
926
927 next unless $res > 0;
928
929 my $level = 0;
930 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
931 $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
932 } else {
933 $level = ceil (($att_res_lvl / $max_att_res) * $res);
934 }
935 push @reslevels, $level;
936 }
937
938 my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
939
940 (max (@reslevels, $overall_lvl), $rescnt);
941 }
942
943 sub special_level {
944 my ($self) = @_;
945
946 my $specials = $self->{hash}->{spec} || {};
947
948 my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
949 my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
950
951 my @speclvls;
952 my $specsum = 0;
953 my $imprs = 0;
954
955 for my $spcnam (keys %$specials) {
956 my $spc = $specials->{$spcnam};
957 next unless $spc > 0;
958
959 $specsum += $spc;
960 $imprs++;
961
962 my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
963
964 my $lvl = ($max_spc_lvl / $max_spc) * $spc;
965 push @speclvls, $lvl;
966 }
967
968 my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
969
970 (max (@speclvls, $sumlvl), $imprs)
971 }
972
973
974 # this function calculated the 'level' of an amulet or a ring
975 sub power_to_level {
976 my ($self, $lvldescr) = @_;
977
978 my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
979 my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
980
981 my ($stat_lvl, $stat_imprs) = $self->stat_level;
982 my ($resist_lvl, $res_imprs) = $self->resist_level;
983 my ($spec_lvl, $spec_imprs) = $self->special_level;
984
985 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
986
987 my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
988
989 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
990
991 if ($lvldescr) {
992 $$lvldescr =
993 sprintf "%3d: %s\n", $levl,
994 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
995 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
996 }
997
998 $levl
999 }
1000
1001 sub add_stat_costs {
1002 my ($self, $cost) = @_;
1003
1004 my $stats = $self->{hash}->{stat};
1005
1006 for my $stat (keys %$stats) {
1007 my $sum = $stats->{$stat};
1008
1009 next unless $sum > 0;
1010
1011 my $statfx = fx ($sum, 'stat_items');
1012 $cost->{"stat_$stat"} += $statfx->[0];
1013 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
1014 }
1015 }
1016
1017 sub add_special_costs {
1018 my ($self, $cost) = @_;
1019
1020 my $specials = $self->{hash}->{spec};
1021
1022 for my $spec (keys %$specials) {
1023 my $sum = $specials->{$spec};
1024
1025 next unless $sum > 0;
1026
1027 my $specfx = fx ($sum, 'spec_items');
1028 $cost->{"spec_$spec"} += $specfx->[0];
1029 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
1030 }
1031 }
1032
1033 sub calc_costs {
1034 my ($self) = @_;
1035
1036 my $costs = {};
1037
1038 my $ring = $self->{hash};
1039
1040 for my $resnum (keys %{$ring->{resist} || {}}) {
1041
1042 my $res = $ring->{resist}->{$resnum};
1043
1044 next unless $res > 0;
1045
1046 return undef if $res == 100;
1047
1048 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
1049
1050 my $diamonds;
1051 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
1052 $diamonds += fx ($res, 'effect_resist_diamonds_x');
1053 } else {
1054 $diamonds += fx ($res, 'attack_resist_diamonds_x');
1055 }
1056
1057 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
1058 }
1059
1060 $self->add_stat_costs ($costs);
1061 $self->add_special_costs ($costs);
1062
1063 return $costs;
1064 }
1065
1066 sub split_diamonds {
1067 my ($cost, $diamonds, $category) = @_;
1068
1069 my $stat_split = Jeweler::getcfg (diamond_split => $category);
1070
1071 my $sum = sum (@$stat_split);
1072
1073 my $emarch = cf::arch::find 'emerald';
1074 my $saarch = cf::arch::find 'sapphire';
1075 my $pearch = cf::arch::find 'pearl';
1076 my $ruarch = cf::arch::find 'ruby';
1077 my $diarch = cf::arch::find 'gem';
1078
1079 my $sumvalue = $diarch->value * $diamonds;
1080
1081 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->value;
1082 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->value;
1083 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->value;
1084 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->value;
1085 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->value;
1086 }
1087
1088 package Jeweler::Util;
1089
1090 use strict;
1091
1092 =head2 Util
1093
1094 Some utility functions for the Jeweler skill.
1095
1096 =over 4
1097
1098 =item remove ($object[, $nrof])
1099
1100 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1101 The returnvalue is the number of 'single' objects that couldn't be removed.
1102
1103 =cut
1104
1105 sub remove {
1106 my ($obj, $nrof) = @_;
1107
1108 my $cnt;
1109
1110 if (defined $nrof) {
1111 # TODO: Check tihis line:
1112 return 0 if ($nrof * 1) == 0; #XXX: ???
1113 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1114
1115 if ($cnt > 0) {
1116 $obj->nrof ($cnt);
1117 return 0;
1118 }
1119 }
1120
1121 remove ($_) for $obj->inv;
1122 $obj->destroy;
1123 return $cnt;
1124 }
1125
1126 sub check_for_match {
1127 my ($thing, @matchar) = @_;
1128
1129 my $i = 0;
1130 my $check_cnts = 0;
1131 my $check_true = 0;
1132 for my $match (@matchar) {
1133 if ($i % 3 == 0) {
1134 return 1 if $check_true && $check_cnts == $check_true;
1135 $check_cnts = 0;
1136 $check_true = 0;
1137 }
1138
1139 if ($match =~ m/^\s*$/) {
1140 $i++;
1141 next;
1142 }
1143
1144 $check_cnts++;
1145 if ($i % 3 == 0) {
1146 $thing->name eq $match
1147 and $check_true++;
1148 } elsif ($i % 3 == 1) {
1149 $thing->title eq $match
1150 and $check_true++;
1151 } else { # $i % 3 == 2
1152 $thing->arch->archname eq $match
1153 and $check_true++;
1154 }
1155 $i++;
1156 }
1157 #d# warn "CHECK $check_true | $check_cnts | [".(join ',', @matchar).":".join (",", ($thing->name, $thing->title, $thing->arch->archname))."\n";
1158 return 1 if $check_true && $check_cnts == $check_true;
1159 return 0;
1160 }
1161
1162 sub grep_for_match {
1163 my ($ingred, $group, @matchar) = @_;
1164
1165 for my $thing (@{$ingred->{$group} || []}) {
1166 #d# warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->archname, "@matchar"; #d#
1167 if (check_for_match ($thing, @matchar)) {
1168 return $thing;
1169 }
1170 }
1171 return undef;
1172 }
1173
1174 =back
1175
1176 1