ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.39
Committed: Fri Oct 9 23:00:39 2009 UTC (14 years, 8 months ago) by sf-marcmagus
Content type: text/plain
Branch: MAIN
Changes since 1.38: +3 -2 lines
Log Message:
Fix alchemy [and other craft skills] so recipes marked as transmutation will
actually transmute an ingredient when appropriate.

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