ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.15
Committed: Sat May 19 00:11:11 2007 UTC (17 years ago) by root
Branch: MAIN
CVS Tags: rel-2_1
Changes since 1.14: +1 -1 lines
Log Message:
fix typoe

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