ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.8
Committed: Sun Feb 4 00:35:23 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.7: +2 -1 lines
Log Message:
fixed bug with generated amulets :)

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