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