ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.24
Committed: Mon May 28 21:28:35 2007 UTC (16 years, 11 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.23: +17 -17 lines
Log Message:
update copyrights in server/*.C

File Contents

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