ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/Jeweler.pm
Revision: 1.7
Committed: Fri Feb 2 12:05:28 2007 UTC (17 years, 3 months ago) by elmex
Branch: MAIN
Changes since 1.6: +11 -0 lines
Log Message:
added attachment to jeweler extension for the jeweler quest

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