ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.9
Committed: Sun Feb 4 11:17:52 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.8: +44 -17 lines
Log Message:
implemented more sanity. aliases gem to diamond. fixed
the plan finding algorithm. linear interpolate the costs
for resistancies.

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