ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.6
Committed: Thu Feb 1 01:46:45 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.5: +5 -1 lines
Log Message:
fixed the big big big bug in the merging experience concept.
added checks for unidentified items.
added itempower limits to the generated rings!
jeweler skill is finally becoming a bit more balanced.

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 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 $obj->insert_ob_in_ob ($self->{cauldron});
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->name 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->name 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 = 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 = dclone ($costs);
576
577 for my $key (keys %$costs) {
578 my @grepar;
579 if ($key =~ m/^(resist_|spec_|stat_)/) { # check the special items
580 @grepar = @{Jeweler::getcfg (plans => $key) || []};
581 } else { # check the gems
582 @grepar = ('gems', undef, undef, $key);
583 }
584
585 if ($do_remove) {
586 my $rem = $costs->{$key};
587 $self->do_grep (sub { if ($rem) { $rem = Jeweler::Util::remove ($_[0], $rem); } 1 }, @grepar);
588 if ($rem > 0) {
589 warn "JEWELER BUG: removed ingredients $rem > 0 after removing!";
590 }
591 } else {
592 my $nr;
593 $self->do_grep (sub { $nr += ($_[0]->nrof || 1); 0 }, @grepar);
594 $costs->{$key} -= $nr;
595 }
596
597 }
598
599 return $costs;
600 }
601
602 =back
603
604 =cut
605
606 sub put_to_bench {
607 my ($self, $bench) = @_;
608
609 my $ingred = $self->{ingredients};
610
611 for my $ik (keys %$ingred) {
612 for (@{$ingred->{$ik} || []}) {
613 $bench->put ($_);
614 }
615 }
616 }
617
618 package Jeweler::Object;
619 use strict;
620 use POSIX;
621 use List::Util qw/max min sum/;
622
623 sub new {
624 my ($class, %arg) = @_;
625
626 my $self = bless { }, $class;
627
628 $self->ring_or_ammy_to_hash ($arg{object});
629
630 $self;
631 }
632
633 sub analyze {
634 my ($self, $sk, $pl) = @_;
635
636 my $sklvl = cf::exp_to_level ($sk->stats->exp);
637 my $ringlvl = $self->power_to_level;
638
639 my $tmpl;
640 if ($pl->flag (cf::FLAG_WIZ)) {
641 $tmpl = Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
642 } else {
643 $tmpl = Jeweler::level_diff_to_str ($sklvl - $ringlvl);
644 }
645 my $msg = sprintf "Projected success rate: %s", $tmpl;
646 return $msg;
647 }
648
649 sub wiz_analyze {
650 my ($self, $pl) = @_;
651 my $costs = $self->calc_costs;
652 my $desc = "";
653 my $lvl = $self->power_to_level (\$desc);
654 my $emarch = cf::arch::find 'emerald';
655 my $saarch = cf::arch::find 'sapphire';
656 my $pearch = cf::arch::find 'pearl';
657 my $ruarch = cf::arch::find 'ruby';
658 my $diarch = cf::arch::find 'gem';
659 my $scosts = $emarch->clone->value * $costs->{emerald}
660 + $saarch->clone->value * $costs->{sapphire}
661 + $pearch->clone->value * $costs->{pearl}
662 + $ruarch->clone->value * $costs->{ruby}
663 + $diarch->clone->value * $costs->{gem};
664
665 $pl->message ("costs: " . (join (', ', map { "$_: " . sprintf "%.2f", $costs->{$_} } keys %$costs)) . " (".($scosts / 5000)." royalties)");
666 $pl->message ("level: " . $desc);
667 }
668
669
670 sub get_chance_perc {
671 my ($self, $sk) = @_;
672 my $sklvl = cf::exp_to_level ($sk->stats->exp);
673 my $ringlvl = $self->power_to_level;
674 return Jeweler::level_diff_to_chance_perc ($sklvl - $ringlvl);
675 }
676
677 sub fx {
678 my ($res, $cfg) = @_;
679 my $or = $res;
680 my $ar = $Jeweler::CFG->{functions}->{$cfg};
681 if (ref $ar->[0] eq 'ARRAY') {
682 $res = $res - 1;
683 } else {
684 $res = ceil ($res / 5) - 1;
685 }
686 $ar->[max (min ($res, @$ar - 1), 0)];
687 }
688
689 sub improve_by_ring {
690 my ($self, @rings) = @_;
691 my $ring = $self;
692 for my $iring (@rings) {
693 for my $cat (qw/stat spec resist/) {
694 for my $k (keys %{$iring->{hash}->{$cat}}) {
695 if ($ring->{hash}->{$cat}->{$k} < $iring->{hash}->{$cat}->{$k}) {
696 $ring->{hash}->{$cat}->{$k} = $iring->{hash}->{$cat}->{$k};
697 }
698 }
699 }
700 }
701 }
702
703 sub negate {
704 my ($self) = @_;
705 for my $cat (qw/stat spec resist/) {
706 for my $k (keys %{$self->{hash}->{$cat}}) {
707 if ($self->{hash}->{$cat}->{$k} > 0) {
708 $self->{hash}->{$cat}->{$k} *= -1;
709 }
710 }
711 }
712 }
713
714 sub to_string {
715 my ($self) = @_;
716 my $r = $self->{hash};
717 return
718 $r->{arch} . " " .
719 join ("",
720 grep { $_ ne "" }
721 join ("",
722 (map {
723 my $rv = $r->{resist}->{$_};
724 "(resist " . (lc $Jeweler::RESMAP{$_}) . " " . ($rv > 0 ? '+' : '') . $rv . ")"
725 } grep { $r->{resist}->{$_} } @Jeweler::RESISTS),
726 (map {
727 my $rv = $r->{stat}->{$_};
728 "(" . (ucfirst lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
729 } grep { $r->{stat}->{$_} } keys %{$r->{stat}}),
730 (map {
731 my $rv = $r->{spec}->{$_};
732 "(" . (lc $_) . ($rv > 0 ? '+' : '') . $rv . ")"
733 } grep { $r->{spec}->{$_} } keys %{$r->{spec}})))
734 }
735
736 sub ring_or_ammy_to_hash {
737 my ($self, $thing) = @_;
738
739 my $obj = {};
740
741 for (@Jeweler::RESISTS) {
742 $obj->{resist}->{$_} = $thing->resist ($_);
743 }
744
745 my $stats = $thing->stats;
746
747 for (qw/Str Dex Con Wis Cha Int Pow/) {
748 $obj->{stat}->{lc $_} = $stats->$_;
749 }
750
751 $obj->{spec}{regen} = $stats->hp;
752 $obj->{spec}{magic} = $stats->sp;
753 $obj->{spec}{wc} = $stats->wc;
754 $obj->{spec}{dam} = $stats->dam;
755 $obj->{spec}{ac} = $stats->ac;
756 $obj->{spec}{speed} = $stats->exp;
757 $obj->{spec}{food} = $stats->food;
758
759 $obj->{name} = $thing->name;
760 $obj->{arch} = $thing->arch->name;
761 $obj->{face} = $thing->face;
762
763 $self->{hash} = $obj
764 }
765
766 sub to_object {
767 my ($self) = @_;
768
769 my $obj = cf::object::new $self->{hash}->{arch};
770
771 $obj->item_power ($self->power_to_level); # there have to be strings attached!
772
773 $obj->face ($self->{hash}{face});
774
775 my $stats = $obj->stats;
776
777 $stats->hp ($self->{hash}{spec}{regen});
778 $stats->sp ($self->{hash}{spec}{magic});
779 $stats->wc ($self->{hash}{spec}{wc});
780 $stats->dam ($self->{hash}{spec}{dam});
781 $stats->ac ($self->{hash}{spec}{ac});
782 $stats->exp ($self->{hash}{spec}{speed});
783 $stats->food ($self->{hash}{spec}{food});
784
785 $stats->$_ ($self->{hash}{stat}{lc $_})
786 for qw/Str Dex Con Wis Cha Int Pow/;
787
788 for (@Jeweler::RESISTS) {
789 $obj->resist ($_, $self->{hash}->{resist}->{$_});
790 }
791
792 $obj->flag (cf::FLAG_IDENTIFIED, 1);
793
794 return $obj;
795 }
796
797 sub is_better_than {
798 my ($self, $other) = @_;
799
800 for my $type (qw/spec stat resist/) {
801 for my $stat (keys %{$self->{hash}->{$type}}) {
802 if ($self->{hash}->{$type}->{$stat} > $other->{hash}->{$type}->{$stat}) {
803 return 1;
804 }
805 }
806 }
807
808 return 0;
809 }
810
811 sub stat_level {
812 my ($self) = @_;
813 my $stats = $self->{hash}->{stat} || {};
814
815 my $maxlevel = Jeweler::getcfg (maxlevels => 'stat_level');
816 my $maxstat = Jeweler::getcfg (maximprovements => 'stats');
817
818 my $stat_cnt = scalar (grep { $_ > 0 } values %$stats);
819 my $stat_sum = sum (values %$stats); # also count the negative stats!
820 my $level = int (($maxlevel / $maxstat) * $stat_sum);
821
822 ($level, $stat_cnt)
823 }
824
825 sub resist_level {
826 my ($self) = @_;
827
828 my $resists = $self->{hash}->{resist} || {};
829
830 my $att_res_lvl = Jeweler::getcfg (maxlevels => 'resist_level');
831 my $efc_res_lvl = Jeweler::getcfg (maxlevels => 'effect_resist_level');
832 my $max_att_res = Jeweler::getcfg (maximprovements => 'attack_resistances');
833 my $max_efc_res = Jeweler::getcfg (maximprovements => 'effect_resistances');
834 my $max_ovr_res = Jeweler::getcfg (maximprovements => 'resistances');
835
836 my $ressum = 0;
837 my $rescnt = 0;
838 my @reslevels;
839
840 for my $resnam (keys %$resists) {
841 my $res = $resists->{$resnam};
842
843 $rescnt++
844 if $res > 0; # negative resistancies are not an improvement
845
846 $ressum += $res; # note: negative resistancies lower the sum
847
848 next unless $res > 0;
849
850 my $level = 0;
851 if (grep { $resnam eq $_ } @Jeweler::EFFECT_RESISTS) {
852 $level = ceil (($efc_res_lvl / $max_efc_res) * $res);
853 } else {
854 $level = ceil (($att_res_lvl / $max_att_res) * $res);
855 }
856 push @reslevels, $level;
857 }
858
859 my $overall_lvl = ($att_res_lvl / $max_ovr_res) * $ressum;
860
861 (max (@reslevels, $overall_lvl), $rescnt);
862 }
863
864 sub special_level {
865 my ($self) = @_;
866
867 my $specials = $self->{hash}->{spec} || {};
868
869 my $max_spc_lvl = Jeweler::getcfg (maxlevels => 'spec_level');
870 my $max_specials = Jeweler::getcfg (maximprovements => 'specials');
871
872 my @speclvls;
873 my $specsum = 0;
874 my $imprs = 0;
875
876 for my $spcnam (keys %$specials) {
877 my $spc = $specials->{$spcnam};
878 next unless $spc > 0;
879
880 $specsum += $spc;
881 $imprs++;
882
883 my $max_spc = Jeweler::getcfg (maxspecial => $spcnam);
884
885 my $lvl = ($max_spc_lvl / $max_spc) * $spc;
886 push @speclvls, $lvl;
887 }
888
889 my $sumlvl = ($max_spc_lvl / $max_specials) * $specsum;
890
891 (max (@speclvls, $sumlvl), $imprs)
892 }
893
894
895 # this function calculated the 'level' of an amulet or a ring
896 sub power_to_level {
897 my ($self, $lvldescr) = @_;
898
899 my $max_imprs = Jeweler::getcfg (maximprovements => 'improvements');
900 my $max_impr_lvl = Jeweler::getcfg (maxlevels => 'improve_level');
901
902 my ($stat_lvl, $stat_imprs) = $self->stat_level;
903 my ($resist_lvl, $res_imprs) = $self->resist_level;
904 my ($spec_lvl, $spec_imprs) = $self->special_level;
905
906 my $impr_sum = $stat_imprs + $res_imprs + $spec_imprs;
907
908 my $impr_lvl = ceil (($max_impr_lvl / ($max_imprs + 1)) * ($impr_sum - 1)); # 1 improvemnt bonus
909
910 my $levl = int max ($stat_lvl, $resist_lvl, $impr_lvl, $spec_lvl, 0);
911
912 if ($lvldescr) {
913 $$lvldescr =
914 sprintf "%3d: %s\n", $levl,
915 "stat: $stat_lvl, resist: $resist_lvl, improve: $impr_lvl, "
916 ."spec: $spec_lvl (num impr $stat_imprs + $res_imprs + $spec_imprs - 1)";
917 }
918
919 $levl
920 }
921
922 sub add_stat_costs {
923 my ($self, $cost) = @_;
924
925 my $stats = $self->{hash}->{stat};
926
927 for my $stat (keys %$stats) {
928 my $sum = $stats->{$stat};
929
930 next unless $sum > 0;
931
932 my $statfx = fx ($sum, 'stat_items');
933 $cost->{"stat_$stat"} += $statfx->[0];
934 split_diamonds ($cost, $statfx->[1], 'stat_' . $stat);
935 }
936 }
937
938 sub add_special_costs {
939 my ($self, $cost) = @_;
940
941 my $specials = $self->{hash}->{spec};
942
943 for my $spec (keys %$specials) {
944 my $sum = $specials->{$spec};
945
946 next unless $sum > 0;
947
948 my $specfx = fx ($sum, 'spec_items');
949 $cost->{"spec_$spec"} += $specfx->[0];
950 split_diamonds ($cost, $specfx->[1], 'spec_' . $spec);
951 }
952 }
953
954 sub calc_costs {
955 my ($self) = @_;
956
957 my $costs = {};
958
959 my $ring = $self->{hash};
960
961 for my $resnum (keys %{$ring->{resist} || {}}) {
962
963 my $res = $ring->{resist}->{$resnum};
964
965 next unless $res > 0;
966
967 $costs->{"resist_" . $Jeweler::RESMAP{$resnum}} += $res;
968
969 my $diamonds;
970 if (grep { $resnum eq $_ } @Jeweler::EFFECT_RESISTS) {
971 $diamonds += fx ($res, 'effect_resist_diamonds');
972 } else {
973 $diamonds += fx ($res, 'attack_resist_diamonds');
974 }
975
976 split_diamonds ($costs, $diamonds, 'resist_' . $Jeweler::RESMAP{$resnum});
977 }
978
979 $self->add_stat_costs ($costs);
980 $self->add_special_costs ($costs);
981
982 return $costs;
983 }
984
985 sub split_diamonds {
986 my ($cost, $diamonds, $category) = @_;
987
988 my $stat_split = Jeweler::getcfg (diamond_split => $category);
989
990 my $sum = sum (@$stat_split);
991 if ($sum < (1 - 0.0001)) {
992 warn "JEWELER BUG: sum (@$stat_split) = $sum < 1 for $category!";
993 }
994
995 my $emarch = cf::arch::find 'emerald';
996 my $saarch = cf::arch::find 'sapphire';
997 my $pearch = cf::arch::find 'pearl';
998 my $ruarch = cf::arch::find 'ruby';
999 my $diarch = cf::arch::find 'gem';
1000
1001 my $sumvalue = $diarch->clone->value * $diamonds;
1002
1003 $cost->{emerald} += ceil $sumvalue * $stat_split->[0] / max 1, $emarch->clone->value;
1004 $cost->{sapphire} += ceil $sumvalue * $stat_split->[1] / max 1, $saarch->clone->value;
1005 $cost->{pearl} += ceil $sumvalue * $stat_split->[2] / max 1, $pearch->clone->value;
1006 $cost->{ruby} += ceil $sumvalue * $stat_split->[3] / max 1, $ruarch->clone->value;
1007 $cost->{gem} += ceil $sumvalue * $stat_split->[4] / max 1, $diarch->clone->value;
1008 }
1009
1010 package Jeweler::Util;
1011
1012 use strict;
1013
1014 =head2 Util
1015
1016 Some utility functions for the Jeweler skill.
1017
1018 =over 4
1019
1020 =item remove ($object[, $nrof])
1021
1022 Removes the C<$object>. If C<$nrof> is given, remove only C<$nrof> objects.
1023 The returnvalue is the number of 'single' objects that couldn't be removed.
1024
1025 =cut
1026
1027 sub remove {
1028 my ($obj, $nrof) = @_;
1029
1030 my $cnt;
1031
1032 if (defined $nrof) {
1033 # TODO: Check tihis line:
1034 return 0 if ($nrof * 1) == 0; #XXX: ???
1035 $cnt = int (($obj->nrof || 1) - (1 * $nrof));
1036
1037 if ($cnt > 0) {
1038 $obj->nrof ($cnt);
1039 return 0;
1040 }
1041 }
1042
1043 remove ($_) for $obj->inv;
1044 $obj->destroy;
1045 return $cnt;
1046 }
1047
1048 sub check_for_match {
1049 my ($thing, @matchar) = @_;
1050
1051 my $i = 0;
1052 for my $match (@matchar) {
1053 if ($match =~ m/^\s*$/) {
1054 $i++;
1055 next;
1056 }
1057
1058 if ($i % 3 == 0) {
1059 $thing->name eq $match
1060 and return 1;
1061 } elsif ($i % 3 == 1) {
1062 $thing->title eq $match
1063 and return 1;
1064 } else { # $i % 3 == 2
1065 $thing->arch->name eq $match
1066 and return 1;
1067 }
1068 $i++;
1069 }
1070 return 0;
1071 }
1072
1073 sub grep_for_match {
1074 my ($ingred, $group, @matchar) = @_;
1075
1076 for my $thing (@{$ingred->{$group} || []}) {
1077 warn sprintf "DEB:(%s,%s,%s)<->%s\n", $thing->name, $thing->title, $thing->arch->name, "@matchar"; #d#
1078 if (check_for_match ($thing, @matchar)) {
1079 return $thing;
1080 }
1081 }
1082 return undef;
1083 }
1084
1085 =back
1086
1087 1