ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.21
Committed: Fri Oct 12 19:13:26 2007 UTC (16 years, 7 months ago) by root
Branch: MAIN
Changes since 1.20: +2 -2 lines
Log Message:
slightly better can_merge

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