ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.4
Committed: Wed Jan 31 14:11:02 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.3: +126 -52 lines
Log Message:
finally finished the last bits of the jeweler skill. now only debugging
and balancing is missing. going to make some maps next and try to sort
out the right balancing.

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) = @_;
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);
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)->clone->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 $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, propably 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->name 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
334 if (my $k = $type_to_key{$_->type}) {
335 push @{$ingreds->{$k}}, $_;
336 } else {
337 push @{$ingreds->{other}}, $_;
338 }
339 }
340
341 return Jeweler::Ingredients->new (ingredients => $ingreds, cauldron_helper => $self)
342 }
343
344 =item put ($object)
345
346 Just puts the C<$object> into the cauldron.
347
348 =cut
349
350 sub put {
351 my ($self, $obj) = @_;
352
353 return undef unless $self->{cauldron};
354 $obj->insert_ob_in_ob ($self->{cauldron});
355 }
356
357 =back
358
359 =cut
360
361 package Jeweler::Ingredients;
362 use Storable qw/dclone/;
363 use strict;
364
365 =head2 Ingredients
366
367 This class handles the ingredients.
368
369 =over 4
370
371 =item new (ingredients => $ingred_hash)
372
373 This is called from the CauldronHandler that gives you the ingredients.
374
375 =cut
376
377 sub new {
378 my ($class, %arg) = @_;
379
380 my $self = bless {
381 %arg,
382 }, $class;
383
384 $self;
385 }
386
387 =item value ($group, $archname)
388
389 Returns the value of the ingredients in C<$group> with the archetypename C<$archname>.
390
391 =cut
392
393 sub value {
394 my ($self, $group, $archname) = @_;
395
396 my @objs = grep {
397 $_->arch->name eq $archname
398 } @{$self->{ingredients}->{$group} || []};
399
400 my $sum = 0;
401 for (@objs) {
402 $sum += ($_->nrof || 1) * $_->value;
403 }
404
405 return $sum;
406 }
407
408 =item remove ($group, $archname)
409
410 Removes the ingredients in C<$group> with archname C<$archname>.
411 It removes all in C<$group> if archname is undef.
412
413 =cut
414
415 sub remove {
416 my ($self, $group, $archname) = @_;
417
418 my $ingred = $self->{ingredients};
419
420 my @out;
421
422 for (@{$ingred->{$group}}) {
423 if (defined $archname) {
424 if ($_->arch->name eq $archname) {
425 Jeweler::Util::remove ($_);
426 } else {
427 push @out, $_;
428 }
429 } else {
430 Jeweler::Util::remove ($_);
431 }
432 }
433
434 @{$ingred->{$group}} = @out;
435 }
436
437 sub get_plan {
438 my ($self) = @_;
439
440 my $ingred = $self->{ingredients};
441
442 for my $plan (keys %{$Jeweler::CFG->{plans}}) {
443 my $plg = $Jeweler::CFG->{plans}->{$plan};
444 my @plga = ();
445 unless (ref $plg eq 'ARRAY') {
446 push @plga, $plg;
447 } else {
448 @plga = @$plg;
449 }
450 next unless @plga > 0;
451 if (Jeweler::Util::grep_for_match ($ingred, @plga)) {
452 return $plan;
453 }
454 }
455 }
456
457 sub get_ring {
458 my ($self) = @_;
459 return (
460 @{$self->{ingredients}->{ammys} || []},
461 @{$self->{ingredients}->{rings} || []}
462 );
463 }
464
465 sub improve_max {
466 my ($stat, $impro) = @_;
467 if ($stat >= 0) {
468 $stat = $impro > $stat ? $impro : $stat;
469 }
470 $stat
471 }
472
473 sub improve_ring_by_plan {
474 my ($self, $plan, $ring) = @_;
475
476 $ring = dclone ($ring);
477
478 my $ingred = $self->{ingredients};
479 my $impr = {};
480
481 if ($plan =~ m/^stat_(\S+)$/) {
482 my $statname = $1;
483 my $plingred = Jeweler::getcfg (plans => $plan)
484 or die "ingredients for plan '$plan' not defined!";
485
486 my $cnt = 0;
487 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
488 $cnt += $pot->nrof;
489 }
490
491 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
492 for my $x (1..$maxstat) {
493 my $y = Jeweler::Object::fx ($x, 'stat_items');
494
495 if ($cnt <= $y->[0]) {
496 $ring->{hash}->{stat}->{$statname} =
497 improve_max $ring->{hash}->{stat}->{$statname}, $x;
498 last;
499 }
500 }
501
502 } elsif ($plan =~ m/^spec_(\S+)$/) {
503 my $specname = $1;
504 my $plingred = Jeweler::getcfg (plans => $plan)
505 or die "ingredients for plan '$plan' not defined!";
506
507 my $cnt = 0;
508 if (my $pot = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
509 $cnt += $pot->nrof;
510 }
511
512 my $maxspec = Jeweler::getcfg (maximprovements => 'specials');
513 for my $x (1..$maxspec) {
514 my $y = Jeweler::Object::fx ($x, 'spec_items');
515
516 if ($cnt <= $y->[0]) {
517 $ring->{hash}->{spec}->{$specname} =
518 improve_max $ring->{hash}->{spec}->{$specname}, $x;
519 last;
520 }
521 }
522
523 } elsif ($plan =~ m/^resist_(\S+)$/) {
524 my $resname = $1;
525 my $resnum = $REV_RESMAP{$resname};
526 my $plingred = Jeweler::getcfg (plans => $plan)
527 or die "ingredients for plan '$plan' not defined!";
528
529 my $cnt = 0;
530 if (my $it = Jeweler::Util::grep_for_match ($ingred, @$plingred)) {
531 $cnt += $it->nrof;
532 }
533 my $resist_item_nr = 0;
534 $self->do_grep (sub { $resist_item_nr += ($_[0]->nrof || 1); 0 }, @$plingred);
535
536 my $maximprovname = (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS)
537 ? 'effect_resistances'
538 : 'attack_resistances';
539
540 my $maxres = Jeweler::getcfg (maximprovements => $maximprovname);
541 $resist_item_nr = $maxres if ($resist_item_nr > $maxres);
542 $ring->{hash}->{resist}->{$resnum} =
543 improve_max $ring->{hash}->{resist}->{$resnum}, $resist_item_nr;
544 }
545
546 return $ring;
547 }
548
549 sub do_grep {
550 my ($self, $cb, $cat, @grepar) = @_;
551
552 my $ingred = $self->{ingredients};
553
554 my @rem;
555 for my $ing (@{$ingred->{$cat}}) {
556 if (Jeweler::Util::check_for_match ($ing, @grepar)) {
557 unless ($cb->($ing)) {
558 push @rem, $ing;
559 }
560 } else {
561 push @rem, $ing;
562 }
563 }
564 @{$ingred->{$cat}} = @rem;
565 }
566
567 sub check_costs {
568 my ($self, $costs, $do_remove) = @_;
569
570 my $costs = dclone ($costs);
571
572 for my $key (keys %$costs) {
573 my @grepar;
574 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
575 @grepar = @{Jeweler::getcfg (plans => $key) || []};
576 } else { # check the gems
577 @grepar = ('gems', undef, undef, $key);
578 }
579
580 if ($do_remove) {
581 my $rem = $costs->{$key};
582 $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
583 if ($rem > 0) {
584 warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
585 }
586 } else {
587 my $nr;
588 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
589 $costs->{$key} -= $nr;
590 warn "CHECKCOST $key <-> $nr -> $costs->{$key}\n";
591 }
592
593 }
594
595 return $costs;
596 }
597
598 =back
599
600 =cut
601
602 sub put_to_bench {
603 my ($self, $bench) = @_;
604
605 my $ingred = $self->{ingredients};
606
607 for my $ik (keys %$ingred) {
608 for (@{$ingred->{$ik} || []}) {
609 $bench->put ($_);
610 }
611 }
612 }
613
614 package Jeweler::Object;
615 use strict;
616 use POSIX;
617 use List::Util qw/max min sum/;
618
619 sub new {
620 my ($class, %arg) = @_;
621
622 my $self = bless { }, $class;
623
624 $self->ring_or_ammy_to_hash ($arg{object});
625
626 $self;
627 }
628
629 sub analyze {
630 my ($self, $sk, $pl) = @_;
631
632 my $sklvl = cf::exp_to_level ($sk->stats->exp);
633 my $ringlvl = $self->power_to_level;
634
635 my $tmpl;
636 if ($pl->flag (cf::FLAG_WIZ)) {
637 $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
638 } else {
639 $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
640 }
641 my $msg = sprintf "Projected success rate: %s", $tmpl;
642 return $msg;
643 }
644
645 sub wiz_analyze {
646 my ($self, $pl) = @_;
647 my $costs = $self->calc_costs;
648 my $desc = "";
649 my $lvl = $self->power_to_level (\$desc);
650 $pl->message ("costs: " . join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs));
651 $pl->message ("level: " . $desc);
652 }
653
654
655 sub get_chance_perc {
656 my ($self, $sk) = @_;
657 my $sklvl = cf::exp_to_level ($sk->stats->exp);
658 my $ringlvl = $self->power_to_level;
659 return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
660 }
661
662 sub fx {
663 my ($res, $cfg) = @_;
664 my $or = $res;
665 my $ar = $Jeweler::CFG->{functions}->{$cfg};
666 if (ref $ar->[0] eq 'ARRAY') {
667 $res = $res - 1;
668 } else {
669 $res = ceil ($res / 5) - 1;
670 }
671 $ar->[max (min ($res, @$ar - 1), 0)];
672 }
673
674 sub improve_by_ring {
675 my ($self, @rings) = @_;
676 my $ring = $self;
677 for my $iring (@rings) {
678 for my $cat (qw/stat spec resist/) {
679 for my $k (keys %{$iring->{hash}->{$cat}}) {
680 if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
681 $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
682 }
683 }
684 }
685 }
686 }
687
688 sub negate {
689 my ($self) = @_;
690 for my $cat (qw/stat spec resist/) {
691 for my $k (keys %{$self->{hash}->{$cat}}) {
692 if ($self->{hash}->{$cat}->{$k} > 0) {
693 $self->{hash}->{$cat}->{$k} *= -1;
694 }
695 }
696 }
697 }
698
699 sub to_string {
700 my ($self) = @_;
701 my $r = $self->{hash};
702 return
703 $r->{arch} . " " .
704 join ("",
705 grep { $_ ne "" }
706 join ("",
707 (map {
708 my $rv = $r->{resist}->{$_};
709 "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
710 } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
711 (map {
712 my $rv = $r->{stat}->{$_};
713 "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
714 } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
715 (map {
716 my $rv = $r->{spec}->{$_};
717 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
718 } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
719 }
720
721 sub ring_or_ammy_to_hash {
722 my ($self, $thing) = @_;
723
724 my $obj = {};
725
726 for (@Jeweler::RESISTS) {
727 $obj->{resist}->{$_} = $thing->resist ($_);
728 }
729
730 my $stats = $thing->stats;
731
732 for (qw/Str Dex Con Wis Cha Int Pow/) {
733 $obj->{stat}->{lc $_} = $stats->$_;
734 }
735
736 $obj->{spec}{regen} = $stats->hp;
737 $obj->{spec}{magic} = $stats->sp;
738 $obj->{spec}{wc} = $stats->wc;
739 $obj->{spec}{dam} = $stats->dam;
740 $obj->{spec}{ac} = $stats->ac;
741 $obj->{spec}{speed} = $stats->exp;
742 $obj->{spec}{food} = $stats->food;
743
744 $obj->{name} = $thing->name;
745 $obj->{arch} = $thing->arch->name;
746 $obj->{face} = $thing->face;
747
748 $self->{hash} = $obj
749 }
750
751 sub to_object {
752 my ($self) = @_;
753
754 my $obj = cf::object::new $self->{hash}->{arch};
755
756 $obj->face ($self->{hash}{face});
757
758 my $stats = $obj->stats;
759
760 $stats->hp ($self->{hash}{spec}{regen});
761 $stats->sp ($self->{hash}{spec}{magic});
762 $stats->wc ($self->{hash}{spec}{wc});
763 $stats->dam ($self->{hash}{spec}{dam});
764 $stats->ac ($self->{hash}{spec}{ac});
765 $stats->exp ($self->{hash}{spec}{speed});
766 $stats->food ($self->{hash}{spec}{food});
767
768 $stats->$_ ($self->{hash}{stat}{lc $_})
769 for qw/Str Dex Con Wis Cha Int Pow/;
770
771 for (@Jeweler::RESISTS) {
772 $obj->resist ($_, $self->{hash}->{resist}->{$_});
773 }
774
775 $obj->flag (cf::FLAG_IDENTIFIED, 1);
776
777 return $obj;
778 }
779
780 sub is_better_than {
781 my ($self, $other) = @_;
782
783 for my $type (qw/spec stat resist/) {
784 for my $stat (keys %{$self->{hash}->{$type}}) {
785 if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
786 return 1;
787 }
788 }
789 }
790
791 return 0;
792 }
793
794 sub stat_level {
795 my ($self) = @_;
796 my $stats = $self->{hash}->{stat} || {};
797
798 my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
799 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
800
801 my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
802 my $stat_sum = sum (values %$stats); # also count the negative stats!
803 my $level = int (($maxlevel / $maxstat) * $stat_sum);
804
805 ($level, $stat_cnt)
806 }
807
808 sub resist_level {
809 my ($self) = @_;
810
811 my $resists = $self->{hash}->{resist} || {};
812
813 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
814 my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
815 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
816 my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
817 my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
818
819 my $ressum = 0;
820 my $rescnt = 0;
821 my @reslevels;
822
823 for my $resnam (keys %$resists) {
824 my $res = $resists->{$resnam};
825
826 $rescnt++
827 if $res > 0; # negative resistancies are not an improvement
828
829 $ressum += $res; # note: negative resistancies lower the sum
830
831 next unless $res > 0;
832
833 my $level = 0;
834 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
835 $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
836 } else {
837 $level = ceil (($att_res_lvl / $max_att_res) * $res);
838 }
839 push @reslevels, $level;
840 }
841
842 my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
843
844 (max (@reslevels, $overall_lvl), $rescnt);
845 }
846
847 sub special_level {
848 my ($self) = @_;
849
850 my $specials = $self->{hash}->{spec} || {};
851
852 my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
853 my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
854
855 my @speclvls;
856 my $specsum = 0;
857 my $imprs = 0;
858
859 for my $spcnam (keys %$specials) {
860 my $spc = $specials->{$spcnam};
861 next unless $spc > 0;
862
863 $specsum += $spc;
864 $imprs++;
865
866 my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
867
868 my $lvl = ($max_spc_lvl / $max_spc) * $spc;
869 push @speclvls, $lvl;
870 }
871
872 my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
873
874 (max (@speclvls, $sumlvl), $imprs)
875 }
876
877
878 # this function calculated the 'level' of an amulet or a ring
879 sub power_to_level {
880 my ($self, $lvldescr) = @_;
881
882 my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
883 my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
884
885 my ($stat_lvl, $stat_imprs) = $self->stat_level;
886 my ($resist_lvl, $res_imprs) = $self->resist_level;
887 my ($spec_lvl, $spec_imprs) = $self->special_level;
888
889 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
890
891 my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
892
893 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
894
895 if ($lvldescr) {
896 $$lvldescr =
897 sprintf "%3d: %s\n", $levl,
898 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
899 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
900 }
901
902 $levl
903 }
904
905 sub add_stat_costs {
906 my ($self, $cost) = @_;
907
908 my $stats = $self->{hash}->{stat};
909
910 for my $stat (keys %$stats) {
911 my $sum = $stats->{$stat};
912
913 next unless $sum > 0;
914
915 my $statfx = fx ($sum, 'stat_items');
916 $cost->{"stat_$stat"} += $statfx->[0];
917 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
918 }
919 }
920
921 sub add_special_costs {
922 my ($self, $cost) = @_;
923
924 my $specials = $self->{hash}->{spec};
925
926 for my $spec (keys %$specials) {
927 my $sum = $specials->{$spec};
928
929 next unless $sum > 0;
930
931 my $specfx = fx ($sum, 'spec_items');
932 $cost->{"spec_$spec"} += $specfx->[0];
933 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
934 }
935 }
936
937 sub calc_costs {
938 my ($self) = @_;
939
940 my $costs = {};
941
942 my $ring = $self->{hash};
943
944 for my $resnum (keys %{$ring->{resist} || {}}) {
945
946 my $res = $ring->{resist}->{$resnum};
947
948 next unless $res > 0;
949
950 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
951
952 my $diamonds;
953 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
954 $diamonds += fx ($res, 'effect_resist_diamonds');
955 } else {
956 $diamonds += fx ($res, 'attack_resist_diamonds');
957 }
958
959 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
960 }
961
962 $self->add_stat_costs ($costs);
963 $self->add_special_costs ($costs);
964
965 return $costs;
966 }
967
968 sub split_diamonds {
969 my ($cost, $diamonds, $category) = @_;
970
971 my $stat_split = Jeweler::getcfg (diamond_split => $category);
972
973 my $sum = sum (@$stat_split);
974 if ($sum < (1 - 0.0001)) {
975 warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
976 }
977
978 my $emarch = cf::arch::find 'emerald';
979 my $saarch = cf::arch::find 'sapphire';
980 my $pearch = cf::arch::find 'pearl';
981 my $ruarch = cf::arch::find 'ruby';
982 my $diarch = cf::arch::find 'gem';
983
984 my $sumvalue = $diarch->clone->value * $diamonds;
985
986 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
987 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
988 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
989 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
990 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
991 }
992
993 package Jeweler::Util;
994
995 use strict;
996
997 =head2 Util
998
999 Some utility functions for the Jeweler skill.
1000
1001 =over 4
1002
1003 =item remove ($object[, $nrof])
1004
1005 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1006 The returnvalue is the number of 'single' objects that couldn't be removed.
1007
1008 =cut
1009
1010 sub remove {
1011 my ($obj, $nrof) = @_;
1012
1013 my $cnt;
1014
1015 if (defined $nrof) {
1016 # TODO: Check tihis line:
1017 return 0 if ($nrof * 1) == 0; #XXX: ???
1018 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1019
1020 if ($cnt > 0) {
1021 $obj->nrof ($cnt);
1022 return 0;
1023 }
1024 }
1025
1026 remove ($_) for $obj->inv;
1027 $obj->destroy;
1028 return $cnt;
1029 }
1030
1031 sub check_for_match {
1032 my ($thing, @matchar) = @_;
1033
1034 my $i = 0;
1035 for my $match (@matchar) {
1036 if ($match =~ m/^\s*$/) {
1037 $i++;
1038 next;
1039 }
1040
1041 if ($i % 3 == 0) {
1042 $thing->name eq $match
1043 and return 1;
1044 } elsif ($i % 3 == 1) {
1045 $thing->title eq $match
1046 and return 1;
1047 } else { # $i % 3 == 2
1048 $thing->arch->name eq $match
1049 and return 1;
1050 }
1051 $i++;
1052 }
1053 return 0;
1054 }
1055
1056 sub grep_for_match {
1057 my ($ingred, $group, @matchar) = @_;
1058
1059 for my $thing (@{$ingred->{$group} || []}) {
1060 warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->name, "@matchar"; #d#
1061 if (check_for_match ($thing, @matchar)) {
1062 return $thing;
1063 }
1064 }
1065 return undef;
1066 }
1067
1068 =back
1069
1070 1