ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.40
Committed: Mon Oct 12 14:00:58 2009 UTC (14 years, 7 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_81
Changes since 1.39: +7 -6 lines
Log Message:
clarify license

File Contents

# Content
1 /*
2 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 *
4 * Copyright (©) 2005,2006,2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
6 * Copyright (©) 1992,2007 Frank Tore Johansen
7 *
8 * Deliantra is free software: you can redistribute it and/or modify it under
9 * the terms of the Affero GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the Affero GNU General Public License
19 * and the GNU General Public License along with this program. If not, see
20 * <http://www.gnu.org/licenses/>.
21 *
22 * The authors can be reached via e-mail to <support@deliantra.net>
23 */
24
25 /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
26
27 #include <global.h>
28 #include <object.h>
29 #include <sproto.h>
30 #include <skills.h>
31 #include <spells.h>
32
33 /** define this for some helpful debuging information */
34 #if 0
35 # define ALCHEMY_DEBUG
36 #endif
37
38 /** define this for loads of (marginal) debuging information */
39 #if 0
40 # define EXTREME_ALCHEMY_DEBUG
41 #endif
42
43 /** Random cauldrons effects */
44 static const char *const cauldron_effect[] = {
45 "vibrates briefly",
46 "produces a cloud of steam",
47 "emits bright flames",
48 "pours forth heavy black smoke",
49 "emits sparks",
50 "shoots out small flames",
51 "whines painfully",
52 "hiccups loudly",
53 "wheezes",
54 "burps",
55 "shakes",
56 "rattles",
57 "makes chugging sounds",
58 "smokes heavily for a while"
59 };
60
61
62 static int is_defined_recipe (const recipe *rp, const object *cauldron, object *caster);
63 static recipe *find_recipe (recipelist * fl, int formula, object *ingredients);
64
65
66 /** Returns a random selection from cauldron_effect[] */
67 static const char *
68 cauldron_sound (void)
69 {
70 int size = sizeof (cauldron_effect) / sizeof (char *);
71
72 return cauldron_effect[rndm (0, size - 1)];
73 }
74
75 /**
76 * Main part of the ALCHEMY code. From this we call fctns
77 * that take a look at the contents of the 'cauldron' and, using these ingredients,
78 * we construct an integer formula value which is referenced (randomly) against a
79 * formula list (the formula list chosen is based on the # contents of the cauldron).
80 *
81 * If we get a match between the recipe indicated in cauldron contents and a
82 * randomly chosen one, an item is created and experience awarded. Otherwise
83 * various failure effects are possible (getting worse and worse w/ # cauldron
84 * ingredients). Note that the 'item' to be made can be *anything* listed on
85 * the artifacts list in lib/artifacts which has a recipe listed in lib/formulae.
86 *
87 * To those wondering why I am using the funky formula index method:
88 * 1) I want to match recipe to ingredients regardless of ordering.
89 * 2) I want a fast search for the 'right' recipe.
90 *
91 * Note: it is just possible that a totally different combination of
92 * ingredients will result in a match with a given recipe. This is not a bug!
93 * There is no good reason (in my mind) why alchemical processes have to be
94 * unique -- such a 'feature' is one reason why players might want to experiment
95 * around. :)
96 * -b.t.
97 */
98
99 void
100 attempt_do_alchemy (object *caster, object *cauldron, object *skill)
101 {
102 recipelist *fl;
103 recipe *rp = NULL;
104 float success_chance;
105 int numb, ability = 1;
106 int formula = 0;
107 float ave_chance;
108 object *item, *skop;
109
110 if (caster->type != PLAYER)
111 return; /* only players for now */
112
113 if (get_map_flags (caster->map, NULL, caster->x, caster->y, NULL, NULL) & P_SAFE)
114 {
115 new_draw_info (NDI_UNIQUE, 0, caster, "This is sacred ground, the gods prevent you from using this device.");
116 return;
117 }
118
119 /* if no ingredients, no formula! lets forget it */
120 if (!(formula = content_recipe_value (cauldron)))
121 return;
122
123 numb = numb_ob_inside (cauldron);
124 if ((fl = get_formulalist (numb)))
125 {
126 if (QUERY_FLAG (caster, FLAG_WIZ))
127 {
128 rp = find_recipe (fl, formula, cauldron->inv);
129 if (rp != NULL)
130 {
131 #ifdef ALCHEMY_DEBUG
132 if (strcmp (rp->title, "NONE"))
133 LOG (llevDebug, "WIZ got formula: %s of %s\n", rp->arch_name[0], &rp->title);
134 else
135 LOG (llevDebug, "WIZ got formula: %s (nbatches:%d)\n", rp->arch_name[0], formula / rp->index);
136 #endif
137 attempt_recipe (caster, cauldron, ability, rp, formula / rp->index);
138 }
139 else
140 LOG (llevDebug, "WIZ couldn't find formula for ingredients.\n");
141 return;
142 } /* End of WIZ alchemy */
143
144 /* find the recipe */
145 rp = find_recipe (fl, formula, cauldron->inv);
146 if (rp
147 && rp->skill == skill->skill
148 && (!rp->cauldron
149 || rp->cauldron == cauldron->arch->archname))
150 {
151 uint64 value_ingredients;
152 uint64 value_item;
153 object *tmp;
154 int attempt_shadow_alchemy;
155
156 ave_chance = fl->total_chance / (float) fl->number;
157
158 ability += (int) (skill->level * ((4.0 + cauldron->magic) / 4.0));
159
160 /* determine value of ingredients */
161 value_ingredients = 0;
162 for (tmp = cauldron->inv; tmp != NULL; tmp = tmp->below)
163 value_ingredients += query_cost (tmp, NULL, F_TRUE);
164
165 attempt_shadow_alchemy = !is_defined_recipe (rp, cauldron, caster);
166
167 /* create the object **FIRST**, then decide whether to keep it. */
168 if ((item = attempt_recipe (caster, cauldron, ability, rp, formula / rp->index)) != NULL)
169 {
170 /* compute base chance of recipe success */
171 success_chance = ((float) ability / (float) (rp->diff * (item->level + 2)));
172 if (ave_chance == 0)
173 ave_chance = 1;
174
175 #ifdef ALCHEMY_DEBUG
176 LOG (llevDebug, "percent success chance = %f ab%d / diff%d*lev%d\n", success_chance, ability, rp->diff, item->level);
177 #endif
178
179 value_item = query_cost (item, NULL, F_TRUE | F_IDENTIFIED | F_NOT_CURSED);
180 if (attempt_shadow_alchemy && value_item > value_ingredients)
181 {
182 #ifdef ALCHEMY_DEBUG
183 LOG (llevDebug,
184 "Forcing failure for shadow alchemy recipe because price of ingredients (%llu) is less than price of result (%llu).\n",
185 value_ingredients, value_item);
186 #endif
187 }
188 /* roll the dice */
189 else if ((float) (random_roll (0, 101, caster, PREFER_LOW)) <= 100.0 * success_chance)
190 {
191 change_exp (caster, rp->exp, rp->skill, SK_EXP_NONE);
192
193 // let alchemy consume some time, so that exploits are less easy
194 caster->speed_left -= 1.0;
195
196 return;
197 }
198 }
199 }
200 }
201
202 /* if we get here, we failed!! */
203 alchemy_failure_effect (caster, cauldron, rp, calc_alch_danger (caster, cauldron, rp));
204 }
205
206 /**
207 * Recipe value of the entire contents of a container.
208 * This appears to just generate a hash value, which I guess for now works
209 * ok, but the possibility of duplicate hashes is certainly possible - msw
210 */
211
212 int
213 content_recipe_value (object *op)
214 {
215 char name[MAX_BUF];
216 object *tmp = op->inv;
217 int tval = 0, formula = 0;
218
219 while (tmp)
220 {
221 tval = 0;
222 assign (name, tmp->name);
223 if (tmp->title)
224 sprintf (name, "%s %s", &tmp->name, &tmp->title);
225 tval = (strtoint (name) * (tmp->nrof ? tmp->nrof : 1));
226 #ifdef ALCHEMY_DEBUG
227 LOG (llevDebug, "Got ingredient %d %s(%d)\n", tmp->nrof ? tmp->nrof : 1, name, tval);
228 #endif
229 formula += tval;
230 tmp = tmp->below;
231 }
232 #ifdef ALCHEMY_DEBUG
233 LOG (llevDebug, " Formula value=%d\n", formula);
234 #endif
235 return formula;
236 }
237
238 /**
239 * Returns total number of items in op
240 */
241
242 int
243 numb_ob_inside (object *op)
244 {
245 object *tmp = op->inv;
246 int number = 0, o_number = 0;
247
248 while (tmp)
249 {
250 if (tmp->nrof)
251 number += tmp->nrof;
252 else
253 number++;
254 o_number++;
255 tmp = tmp->below;
256 }
257 #ifdef ALCHEMY_DEBUG
258 LOG (llevDebug, "numb_ob_inside(%s): found %d ingredients\n", &op->name, o_number);
259 #endif
260 return o_number;
261 }
262
263 /**
264 * Essentially a wrapper for make_item_from_recipe() and
265 * insert_ob_in_ob. If the caster has some alchemy skill, then they might
266 * gain some exp from (successfull) fabrication of the product.
267 * If nbatches==-1, don't give exp for this creation (random generation/
268 * failed recipe)
269 */
270
271 object *
272 attempt_recipe (object *caster, object *cauldron, int ability, recipe *rp, int nbatches)
273 {
274
275 object *item = NULL, *skop;
276
277 /* this should be passed to this fctn, not effiecent cpu use this way */
278 int batches = abs (nbatches);
279
280 /* is the cauldron the right type? */
281 if (rp->cauldron && rp->cauldron != cauldron->arch->archname)
282 return 0;
283
284 skop = find_skill_by_name (caster, rp->skill);
285 /* does the caster have the skill? */
286 if (!skop)
287 return 0;
288
289 /* code required for this recipe, search the caster */
290 if (rp->keycode)
291 {
292 object *tmp;
293
294 for (tmp = caster->inv; tmp; tmp = tmp->below)
295 {
296 if (tmp->type == FORCE && tmp->slaying && rp->keycode == tmp->slaying)
297 break;
298 }
299
300 if (!tmp)
301 { /* failure--no code found */
302 new_draw_info (NDI_UNIQUE, 0, caster, "You know the ingredients, but not the technique. Go learn how to do this recipe.");
303 return 0;
304 }
305 }
306
307 #ifdef EXTREME_ALCHEMY_DEBUG
308 LOG (llevDebug, "attempt_recipe(): got %d nbatches\n", nbatches);
309 LOG (llevDebug, "attempt_recipe(): using recipe %s\n", *rp->title ? &rp->title : "unknown");
310 #endif
311
312 if ((item = make_item_from_recipe (cauldron, rp)) != NULL)
313 {
314 remove_contents (cauldron->inv, item);
315 /* Recalc carrying of the cauldron, in case recipe did not conserve mass */
316 cauldron->update_weight ();
317 /* adj lvl, nrof on caster level */
318 adjust_product (item, ability, rp->yield ? (rp->yield * batches) : batches);
319 if (!item->env && (item = insert_ob_in_ob (item, cauldron)) == NULL)
320 {
321 new_draw_info (NDI_UNIQUE, 0, caster, "Nothing happened.");
322 /* new_draw_info_format(NDI_UNIQUE, 0,caster,
323 "Your spell causes the %s to explode!",&cauldron->name); */
324 /* kaboom_cauldron(); */
325 }
326 else
327 new_draw_info_format (NDI_UNIQUE, 0, caster, "The %s %s.", &cauldron->name, cauldron_sound ());
328 }
329
330 return item;
331 }
332
333
334
335 /**
336 * We adjust the nrof, exp and level of the final product, based
337 * on the item's default parameters, and the relevant caster skill level.
338 */
339 void
340 adjust_product (object *item, int lvl, int yield)
341 {
342 int nrof = 1;
343
344 if (!yield)
345 yield = 1;
346
347 if (lvl <= 0)
348 lvl = 1; /* lets avoid div by zero! */
349
350 if (item->nrof)
351 {
352 nrof = (int) ((1.0 - 1.0 / (lvl / 10.0 + 1.0)) * (rndm (0, yield - 1) + rndm (0, yield - 1) + rndm (0, yield - 1)) + 1);
353
354 if (nrof > yield)
355 nrof = yield;
356
357 item->nrof = nrof;
358 }
359 }
360
361
362 /**
363 * Using a list of items and a recipe to make an artifact.
364 *
365 * @param cauldron the cauldron (including the ingredients) used to make the item
366 *
367 * @param rp the recipe to make the artifact from
368 *
369 * @return the newly created object, NULL if something failed
370 */
371
372 object *
373 make_item_from_recipe (object *cauldron, recipe *rp)
374 {
375 artifact *art = NULL;
376 object *item = NULL;
377 size_t rp_arch_index;
378
379 if (rp == NULL)
380 return (object *) NULL;
381
382 /* Find the appropriate object to transform... */
383 if ((item = find_transmution_ob (cauldron->inv, rp, &rp_arch_index, 1)) == NULL)
384 {
385 LOG (llevDebug, "make_alchemy_item(): failed to create alchemical object.\n");
386 return (object *) NULL;
387 }
388
389 /* Find the appropriate artifact template... */
390 if (rp->title != shstr_NONE)
391 {
392 if ((art = locate_recipe_artifact (rp, rp_arch_index)) == NULL)
393 {
394 LOG (llevError, "make_alchemy_item(): failed to locate recipe artifact.\n");
395 LOG (llevDebug, " --requested recipe: %s of %s.\n", rp->arch_name[0], &rp->title);
396 return (object *) NULL;
397 }
398
399 transmute_materialname (item, art->item);
400 give_artifact_abilities (item, art->item);
401 }
402
403 if (QUERY_FLAG (cauldron, FLAG_CURSED))
404 SET_FLAG (item, FLAG_CURSED);
405 if (QUERY_FLAG (cauldron, FLAG_DAMNED))
406 SET_FLAG (item, FLAG_DAMNED);
407
408 return item;
409 }
410
411
412 /**
413 * Looks through the ingredient list. If we find a
414 * suitable object in it - we will use that to make the requested artifact.
415 * Otherwise the code returns a 'generic' item if create_item is set. -b.t.
416 *
417 * @param rp_arch_index pointer to return value; set to arch index for recipe;
418 * set to zero if not using a transmution formula
419 */
420
421 object *
422 find_transmution_ob (object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item)
423 {
424 object *prod_item = 0;
425 bool found = false;
426 *rp_arch_index = 0;
427
428 if (rp->transmute) /* look for matching ingredient/prod archs */
429 for (object *item = first_ingred; item; item = item->below)
430 {
431 for (int i = 0; i < rp->arch_names; i++)
432 //TODO: should be a shstr comparison
433 if (!strcmp(&item->arch->archname,rp->arch_name[i]))
434 {
435 *rp_arch_index = i;
436 prod_item = item;
437 break;
438 }
439
440 if (prod_item)
441 break;
442 }
443
444 /* failed, create a fresh object. Note no nrof>1 because that would
445 * allow players to create massive amounts of artifacts easily */
446 if (create_item && (!prod_item || prod_item->nrof > 1))
447 {
448 #ifdef ALCHEMY_DEBUG
449 LOG (llevDebug, "creating a new item.\n");
450 if (prod_item != NULL)
451 {
452 LOG (llevDebug, " had item: arch %s(nrof:%d)\n",
453 &prod_item->arch->archname, prod_item->nrof);
454 }
455 #endif
456 if (!prod_item)
457 *rp_arch_index = rndm (rp->arch_names);
458 prod_item = get_archetype (rp->arch_name[*rp_arch_index]);
459 }
460
461 #ifdef ALCHEMY_DEBUG
462 LOG (llevDebug, "recipe calls for%stransmution.\n", rp->transmute ? " " : " no ");
463 if (prod_item != NULL)
464 {
465 LOG (llevDebug, " find_transmutable_ob(): returns arch %s(sp:%d)\n",
466 &prod_item->arch->archname, prod_item->stats.sp);
467 }
468 #endif
469
470 return prod_item;
471 }
472
473
474 /**
475 * Ouch. We didnt get the formula we wanted.
476 * This fctn simulates the backfire effects--worse effects as the level
477 * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
478 * can happen to the would be alchemist. This table probably needs some
479 * adjustment for playbalance. -b.t.
480 */
481
482 void
483 alchemy_failure_effect (object *op, object *cauldron, recipe *rp, int danger)
484 {
485 int level = 0;
486
487 if (!op || !cauldron)
488 return;
489
490 if (danger > 1)
491 level = random_roll (1, danger, op, PREFER_LOW);
492
493 #ifdef ALCHEMY_DEBUG
494 LOG (llevDebug, "Alchemy_failure_effect(): using level=%d\n", level);
495 #endif
496
497 /* possible outcomes based on level */
498 if (level < 25)
499 { /* INGREDIENTS USED/SLAGGED */
500 object *item = NULL;
501
502 if (rndm (0, 2))
503 { /* slag created */
504 object *tmp = cauldron->inv;
505 int weight = 0;
506
507 tmp = get_archetype ("rock");
508 tmp->weight = weight;
509 tmp->value = 0;
510 tmp->materialname = "stone";
511 tmp->name = "slag";
512 tmp->name_pl = "slags";
513 item = insert_ob_in_ob (tmp, cauldron);
514 CLEAR_FLAG (tmp, FLAG_CAN_ROLL);
515 CLEAR_FLAG (tmp, FLAG_NO_PICK);
516 tmp->move_block = 0;
517 }
518
519 remove_contents (cauldron->inv, item);
520 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
521 return;
522 }
523 else if (level < 40)
524 { /* MAKE TAINTED ITEM */
525 object *tmp = NULL;
526
527 if (!rp)
528 if ((rp = get_random_recipe ((recipelist *) NULL)) == NULL)
529 return;
530
531 if ((tmp = attempt_recipe (op, cauldron, 1, rp, -1)))
532 {
533 if (!QUERY_FLAG (tmp, FLAG_CURSED)) /* curse it */
534 SET_FLAG (tmp, FLAG_CURSED);
535
536 /* the apply code for potions already deals with cursed
537 * potions, so any code here is basically ignored.
538 */
539 if (tmp->type == FOOD)
540 {
541 tmp->stats.hp = random_roll (0, 149, op, PREFER_LOW);
542 }
543 tmp->value = 0; /* unsaleable item */
544
545 /* change stats downward */
546 do
547 {
548 change_attr_value (&tmp->stats, rndm (0, 6), -1 * (rndm (1, 3)));
549 }
550 while (rndm (0, 2));
551 }
552 return;
553 }
554
555 if (level == 40)
556 { /* MAKE RANDOM RECIPE */
557 recipelist *fl;
558 int numb = numb_ob_inside (cauldron);
559
560 fl = get_formulalist (numb - 1); /* take a lower recipe list */
561 if (fl && (rp = get_random_recipe (fl)))
562 /* even though random, don't grant user any EXP for it */
563 (void) attempt_recipe (op, cauldron, 1, rp, -1);
564 else
565 alchemy_failure_effect (op, cauldron, rp, level - 1);
566 }
567 else if (level < 45)
568 { /* INFURIATE NPC's */
569 /* this is kind of kludgy I know... */
570 cauldron->enemy = op;
571 npc_call_help (cauldron);
572 cauldron->enemy = NULL;
573
574 alchemy_failure_effect (op, cauldron, rp, level - 5);
575 }
576 else if (level < 50)
577 { /* MINOR EXPLOSION/FIREBALL */
578 object *tmp;
579
580 remove_contents (cauldron->inv, NULL);
581 switch (rndm (0, 2))
582 {
583 case 0:
584 tmp = get_archetype ("bomb");
585 tmp->stats.dam = random_roll (1, level, op, PREFER_LOW);
586 tmp->stats.hp = random_roll (1, level, op, PREFER_LOW);
587 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s creates a bomb!", &cauldron->name);
588 break;
589
590 default:
591 tmp = get_archetype ("fireball");
592 tmp->stats.dam = random_roll (1, level, op, PREFER_LOW) / 5 + 1;
593 tmp->stats.hp = random_roll (1, level, op, PREFER_LOW) / 10 + 2;
594 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
595 break;
596 }
597
598 tmp->insert_at (cauldron);
599 }
600 else if (level < 60)
601 { /* CREATE MONSTER */
602 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
603 remove_contents (cauldron->inv, NULL);
604 }
605 else if (level < 80)
606 { /* MAJOR FIRE */
607 object *fb = get_archetype (SP_MED_FIREBALL);
608
609 remove_contents (cauldron->inv, NULL);
610 fire_arch_from_position (cauldron, cauldron, cauldron->x, cauldron->y, 0, fb);
611 fb->destroy ();
612 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
613 }
614 else if (level < 100)
615 { /* WHAMMY the CAULDRON */
616 if (!QUERY_FLAG (cauldron, FLAG_CURSED))
617 SET_FLAG (cauldron, FLAG_CURSED);
618 else
619 cauldron->magic--;
620
621 cauldron->magic -= random_roll (0, 4, op, PREFER_LOW);
622
623 if (rndm (0, 1))
624 {
625 remove_contents (cauldron->inv, NULL);
626 new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s turns darker then makes a gulping sound!", &cauldron->name);
627 }
628 else
629 new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s becomes darker.", &cauldron->name);
630 }
631 else if (level < 110)
632 { /* SUMMON EVIL MONSTERS */
633 object *tmp = get_random_mon (level / 5);
634
635 remove_contents (cauldron->inv, NULL);
636
637 if (!tmp)
638 alchemy_failure_effect (op, cauldron, rp, level);
639 else if (summon_hostile_monsters (cauldron, random_roll (1, 10, op, PREFER_LOW), tmp->arch->archname))
640 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s and then pours forth monsters!", &cauldron->name, cauldron_sound ());
641 }
642 else if (level < 150)
643 { /* COMBO EFFECT */
644 int roll = rndm (1, 3);
645
646 while (roll)
647 {
648 alchemy_failure_effect (op, cauldron, rp, level - 39);
649 roll--;
650 }
651 }
652 else if (level == 151)
653 { /* CREATE RANDOM ARTIFACT */
654 object *tmp;
655
656 /* this is meant to be better than prior possiblity,
657 * in this one, we allow *any* valid alchemy artifact
658 * to be made (rather than only those on the given
659 * formulalist) */
660 if (!rp)
661 rp = get_random_recipe ((recipelist *) NULL);
662
663 if (rp && (tmp = get_archetype (rp->arch_name [rndm (rp->arch_names)])))
664 {
665 generate_artifact (tmp, random_roll (1, op->level / 2 + 1, op, PREFER_HIGH) + 1);
666 if ((tmp = insert_ob_in_ob (tmp, cauldron)))
667 {
668 remove_contents (cauldron->inv, tmp);
669 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
670 }
671 }
672 }
673 else
674 { /* MANA STORM - watch out!! */
675 object *tmp = get_archetype (LOOSE_MANA);
676
677 new_draw_info (NDI_UNIQUE, 0, op, "You unwisely release potent forces!");
678 remove_contents (cauldron->inv, NULL);
679 cast_magic_storm (op, tmp, level);
680 }
681 }
682
683 /*
684 * All but object "save_item" are elimentated from
685 * the container list. Note we have to becareful to remove the inventories
686 * of objects in the cauldron inventory (ex icecube has stuff in it).
687 */
688 void
689 remove_contents (object *first_ob, object *save_item)
690 {
691 // this cries for a cleaner rewrite, removing save_item first possibly
692 object *next, *tmp = first_ob;
693
694 while (tmp)
695 {
696 next = tmp->below;
697
698 if (tmp == save_item)
699 {
700 if (!(tmp = next))
701 break;
702 else
703 next = next->below;
704 }
705
706 if (tmp->inv)
707 remove_contents (tmp->inv, NULL);
708
709 tmp->destroy ();
710 tmp = next;
711 }
712 }
713
714 /**
715 *"Danger" level, will determine how bad the backfire
716 * could be if the user fails to concoct a recipe properly. Factors include
717 * the number of ingredients, the length of the name of each ingredient,
718 * the user's effective level, the user's Int and the enchantment on the
719 * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
720 * danger. Note that we assume that we have had the caster ready the alchemy
721 * skill *before* this routine is called. (no longer auto-readies that skill)
722 * -b.t.
723 */
724 int
725 calc_alch_danger (object *caster, object *cauldron, recipe *rp)
726 {
727 object *item;
728 char name[MAX_BUF];
729 int danger = 0, nrofi = 0;
730
731 /* Knowing alchemy skill reduces yer risk */
732 danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
733
734 if (!caster->chosen_skill)
735 LOG (llevError | logBacktrace, "calc_alch_danger called without a chosen skill, caster %s, cauldron %s\n",
736 caster->debug_desc (), cauldron->debug_desc ());
737
738 /* better cauldrons reduce risk */
739 danger -= cauldron->magic;
740
741 /* Higher Int, lower the risk */
742 danger -= 3 * (caster->stats.Int - 15);
743
744 /* Ingredients. Longer names usually mean rarer stuff.
745 * Thus the backfire is worse. Also, more ingredients
746 * means we are attempting a more powerfull potion,
747 * and thus the backfire will be worse. */
748 for (item = cauldron->inv; item; item = item->below)
749 {
750 assign (name, item->name);
751 if (item->title)
752 sprintf (name, "%s %s", &item->name, &item->title);
753 danger += (strtoint (name) / 1000) + 3;
754 nrofi++;
755 }
756
757 if (rp == NULL)
758 danger += 110;
759 else
760 danger += rp->diff * 3;
761
762 /* Using a bad device is *majorly* stupid */
763 if (QUERY_FLAG (cauldron, FLAG_CURSED))
764 danger += 80;
765 if (QUERY_FLAG (cauldron, FLAG_DAMNED))
766 danger += 200;
767
768 #ifdef ALCHEMY_DEBUG
769 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
770 #endif
771
772 return danger;
773 }
774
775 /**
776 * Determines if ingredients in a container match the
777 * proper ingredients for a recipe.
778 *
779 * rp is the recipe to check
780 * cauldron is the container that holds the ingredients
781 * returns 1 if the ingredients match the recipe, 0 if not
782 *
783 * This functions tries to find each defined ingredient in the container. It is
784 * the defined recipe iff
785 * - the number of ingredients of the recipe and in the container is equal
786 * - all ingredients of the recipe are found in the container
787 * - the number of batches is the same for all ingredients
788 */
789 static int
790 is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
791 {
792 uint32 batches_in_cauldron;
793 const linked_char *ingredient;
794 int number;
795 const object *ob;
796
797 /* check for matching number of ingredients */
798 number = 0;
799 for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
800 number++;
801 for (ob = cauldron->inv; ob != NULL; ob = ob->below)
802 number--;
803 if (number != 0)
804 return 0;
805
806 /* check for matching ingredients */
807 batches_in_cauldron = 0;
808 for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
809 {
810 uint32 nrof;
811 const char *name;
812 int ok;
813
814 /* determine and remove nrof from name */
815 name = ingredient->name;
816 nrof = 0;
817 while (isdigit (*name))
818 {
819 nrof = 10 * nrof + (*name - '0');
820 name++;
821 }
822 if (nrof == 0)
823 nrof = 1;
824 while (*name == ' ')
825 name++;
826
827 /* find the current ingredient in the cauldron */
828 ok = 0;
829 for (ob = cauldron->inv; ob != NULL; ob = ob->below)
830 {
831 char name_ob[MAX_BUF];
832 const char *name2;
833
834 if (!ob->title)
835 name2 = ob->name;
836 else
837 {
838 snprintf (name_ob, sizeof (name_ob), "%s %s", &ob->name, &ob->title);
839 name2 = name_ob;
840 }
841
842 if (strcmp (name2, name) == 0)
843 {
844 if (ob->nrof % nrof == 0)
845 {
846 uint32 batches;
847
848 batches = ob->nrof / nrof;
849 if (batches_in_cauldron == 0)
850 {
851 batches_in_cauldron = batches;
852 ok = 1;
853 }
854 else if (batches_in_cauldron == batches)
855 ok = 1;
856 }
857 break;
858 }
859 }
860 if (!ok)
861 return (0);
862 }
863
864 return (1);
865 }
866
867 /**
868 * Find a recipe from a recipe list that matches the given formula. If there
869 * is more than one matching recipe, it selects a random one. If at least one
870 * transmuting recipe matches, it only considers matching transmuting recipes.
871 *
872 * @return one matching recipe, or NULL if no recipe matches
873 */
874 static recipe *
875 find_recipe (recipelist * fl, int formula, object *ingredients)
876 {
877 recipe *rp;
878 recipe *result; /* winning recipe, or NULL if no recipe found */
879 int recipes_matching; /* total number of matching recipes so far */
880 int transmute_found; /* records whether a transmuting recipe was found so far */
881 size_t rp_arch_index;
882
883 #ifdef EXTREME_ALCHEMY_DEBUG
884 LOG (llevDebug, "looking for formula %d:\n", formula);
885 #endif
886 result = NULL;
887 recipes_matching = 0;
888 transmute_found = 0;
889
890 for (rp = fl->items; rp; rp = rp->next)
891 {
892 /* check if recipe matches at all */
893 if (formula % rp->index != 0)
894 {
895 #ifdef EXTREME_ALCHEMY_DEBUG
896 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], &rp->title, rp->index);
897 #endif
898 continue;
899 }
900
901 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
902 {
903 #ifdef EXTREME_ALCHEMY_DEBUG
904 LOG (llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], &rp->title, rp->index);
905 #endif
906 /* transmution recipe with matching base ingredient */
907 if (!transmute_found)
908 {
909 transmute_found = 1;
910 recipes_matching = 0;
911 }
912 }
913 else if (transmute_found)
914 {
915 #ifdef EXTREME_ALCHEMY_DEBUG
916 LOG (llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], &rp->title,
917 rp->index);
918 #endif
919 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
920 continue;
921 }
922 #ifdef EXTREME_ALCHEMY_DEBUG
923 else
924 {
925 LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], &rp->title, rp->index);
926 }
927 #endif
928
929 if (rndm (0, recipes_matching) == 0)
930 result = rp;
931
932 recipes_matching++;
933 }
934
935 if (result == NULL)
936 {
937 #ifdef ALCHEMY_DEBUG
938 LOG (llevDebug, "couldn't find formula for ingredients.\n");
939 #endif
940 return NULL;
941 }
942
943 #ifdef ALCHEMY_DEBUG
944 if (strcmp (result->title, "NONE") != 0)
945 LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], &result->title, formula / result->index);
946 else
947 LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
948 #endif
949 return result;
950 }