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

# User Rev Content
1 elmex 1.1 /*
2 root 1.27 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 pippijn 1.18 *
4 elmex 1.36 * Copyright (©) 2005,2006,2007,2008,2009 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 root 1.24 * Copyright (©) 2002,2007 Mark Wedel & Crossfire Development Team
6     * Copyright (©) 1992,2007 Frank Tore Johansen
7 pippijn 1.18 *
8 root 1.27 * Deliantra is free software: you can redistribute it and/or modify
9 root 1.26 * 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 pippijn 1.18 *
13 root 1.26 * 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 pippijn 1.18 *
18 root 1.26 * 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 root 1.24 *
21 root 1.27 * The authors can be reached via e-mail to <support@deliantra.net>
22 pippijn 1.18 */
23 elmex 1.1
24     /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
25    
26     #include <global.h>
27     #include <object.h>
28 root 1.14 #include <sproto.h>
29 elmex 1.1 #include <skills.h>
30     #include <spells.h>
31    
32     /** define this for some helpful debuging information */
33     #if 0
34 root 1.7 # define ALCHEMY_DEBUG
35 elmex 1.1 #endif
36    
37     /** define this for loads of (marginal) debuging information */
38     #if 0
39 root 1.7 # define EXTREME_ALCHEMY_DEBUG
40 elmex 1.1 #endif
41    
42     /** Random cauldrons effects */
43 root 1.7 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 elmex 1.1
60    
61 root 1.7 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 elmex 1.1
64    
65     /** Returns a random selection from cauldron_effect[] */
66 root 1.7 static const char *
67     cauldron_sound (void)
68     {
69     int size = sizeof (cauldron_effect) / sizeof (char *);
70 elmex 1.1
71 root 1.7 return cauldron_effect[rndm (0, size - 1)];
72 elmex 1.1 }
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 root 1.7 void
99 elmex 1.35 attempt_do_alchemy (object *caster, object *cauldron, object *skill)
100 root 1.7 {
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 sf-marcmagus 1.38 LOG (llevDebug, "WIZ got formula: %s of %s\n", rp->arch_name[0], &rp->title);
133 root 1.7 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 elmex 1.35 if (rp
146     && rp->skill == skill->skill
147     && (!rp->cauldron
148     || rp->cauldron == cauldron->arch->archname))
149 root 1.7 {
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 root 1.4
157 elmex 1.35 ability += (int) (skill->level * ((4.0 + cauldron->magic) / 4.0));
158 root 1.4
159 root 1.7 /* 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 elmex 1.1 #endif
186 root 1.4 }
187 root 1.7 /* 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 root 1.10
192     // let alchemy consume some time, so that exploits are less easy
193     caster->speed_left -= 1.0;
194    
195 root 1.7 return;
196 root 1.4 }
197     }
198     }
199 elmex 1.1 }
200 root 1.10
201 root 1.7 /* if we get here, we failed!! */
202     alchemy_failure_effect (caster, cauldron, rp, calc_alch_danger (caster, cauldron, rp));
203 elmex 1.1 }
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 root 1.7 int
212     content_recipe_value (object *op)
213     {
214 elmex 1.1 char name[MAX_BUF];
215 root 1.7 object *tmp = op->inv;
216     int tval = 0, formula = 0;
217 elmex 1.1
218 root 1.7 while (tmp)
219     {
220     tval = 0;
221 root 1.19 assign (name, tmp->name);
222 root 1.7 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 elmex 1.1 #ifdef ALCHEMY_DEBUG
232 root 1.7 LOG (llevDebug, " Formula value=%d\n", formula);
233 elmex 1.1 #endif
234 root 1.7 return formula;
235 elmex 1.1 }
236    
237     /**
238     * Returns total number of items in op
239     */
240    
241 root 1.7 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 sf-marcmagus 1.38 LOG (llevDebug, "numb_ob_inside(%s): found %d ingredients\n", &op->name, o_number);
258 root 1.7 #endif
259     return o_number;
260 elmex 1.1 }
261 root 1.7
262 elmex 1.6 /**
263 elmex 1.1 * 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 root 1.7 */
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 elmex 1.35 if (rp->cauldron && rp->cauldron != cauldron->arch->archname)
281     return 0;
282 root 1.7
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 root 1.17 for (tmp = caster->inv; tmp; tmp = tmp->below)
294 root 1.7 {
295 root 1.20 if (tmp->type == FORCE && tmp->slaying && rp->keycode == tmp->slaying)
296 root 1.7 break;
297     }
298 root 1.17
299     if (!tmp)
300 root 1.7 { /* failure--no code found */
301 root 1.28 new_draw_info (NDI_UNIQUE, 0, caster, "You know the ingredients, but not the technique. Go learn how to do this recipe.");
302 root 1.7 return 0;
303 root 1.4 }
304 elmex 1.1 }
305    
306     #ifdef EXTREME_ALCHEMY_DEBUG
307 root 1.7 LOG (llevDebug, "attempt_recipe(): got %d nbatches\n", nbatches);
308 sf-marcmagus 1.38 LOG (llevDebug, "attempt_recipe(): using recipe %s\n", *rp->title ? &rp->title : "unknown");
309 root 1.7 #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 root 1.28 cauldron->update_weight ();
316 root 1.7 /* 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 root 1.17 new_draw_info_format (NDI_UNIQUE, 0, caster, "The %s %s.", &cauldron->name, cauldron_sound ());
327 elmex 1.1 }
328 root 1.17
329 root 1.7 return item;
330 elmex 1.1 }
331    
332    
333    
334 root 1.17 /**
335 elmex 1.1 * 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 root 1.17 void
339 root 1.7 adjust_product (object *item, int lvl, int yield)
340     {
341     int nrof = 1;
342    
343     if (!yield)
344     yield = 1;
345 root 1.17
346 root 1.7 if (lvl <= 0)
347     lvl = 1; /* lets avoid div by zero! */
348 root 1.17
349 root 1.7 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 root 1.17
353 root 1.7 if (nrof > yield)
354     nrof = yield;
355 root 1.17
356 root 1.7 item->nrof = nrof;
357 elmex 1.1 }
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 root 1.7 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 root 1.34 if (rp->title != shstr_NONE)
390 root 1.7 {
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 root 1.34
398 root 1.7 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 elmex 1.1 }
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 root 1.7
420     object *
421 elmex 1.37 find_transmution_ob (object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item)
422 root 1.7 {
423 elmex 1.37 object *prod_item = 0;
424     bool found = false;
425     *rp_arch_index = 0;
426 root 1.7
427 elmex 1.37 if (rp->transmute) /* look for matching ingredient/prod archs */
428     for (object *item = first_ingred; item; item = item->below)
429 root 1.7 {
430 root 1.34 for (int i = 0; i < rp->arch_names; i++)
431     //TODO: should be a shstr comparison
432 sf-marcmagus 1.39 if (!strcmp(&item->arch->archname,rp->arch_name[i]))
433 root 1.34 {
434     *rp_arch_index = i;
435 elmex 1.37 prod_item = item;
436 root 1.34 break;
437     }
438 root 1.20
439 elmex 1.37 if (prod_item)
440 root 1.7 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 elmex 1.37 if (create_item && (!prod_item || prod_item->nrof > 1))
446 root 1.7 {
447 sf-marcmagus 1.38 #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 sf-marcmagus 1.39 if (!prod_item)
456     *rp_arch_index = rndm (rp->arch_names);
457 elmex 1.37 prod_item = get_archetype (rp->arch_name[*rp_arch_index]);
458 elmex 1.1 }
459    
460     #ifdef ALCHEMY_DEBUG
461 root 1.7 LOG (llevDebug, "recipe calls for%stransmution.\n", rp->transmute ? " " : " no ");
462 elmex 1.37 if (prod_item != NULL)
463 root 1.7 {
464 elmex 1.37 LOG (llevDebug, " find_transmutable_ob(): returns arch %s(sp:%d)\n",
465 sf-marcmagus 1.38 &prod_item->arch->archname, prod_item->stats.sp);
466 elmex 1.1 }
467     #endif
468 root 1.7
469 elmex 1.37 return prod_item;
470 elmex 1.1 }
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 root 1.7
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 root 1.21
518 root 1.7 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 root 1.30
554 root 1.7 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 root 1.15 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 root 1.4
589 root 1.15 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 root 1.7 }
596 root 1.15
597 root 1.30 tmp->insert_at (cauldron);
598 root 1.7 }
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 root 1.32 fb->destroy ();
611 root 1.7 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 root 1.30
620 root 1.7 cauldron->magic -= random_roll (0, 4, op, PREFER_LOW);
621 root 1.30
622 root 1.7 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 root 1.30
636 root 1.7 if (!tmp)
637     alchemy_failure_effect (op, cauldron, rp, level);
638 root 1.25 else if (summon_hostile_monsters (cauldron, random_roll (1, 10, op, PREFER_LOW), tmp->arch->archname))
639 root 1.7 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 root 1.33
662     if (rp && (tmp = get_archetype (rp->arch_name [rndm (rp->arch_names)])))
663 root 1.7 {
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 root 1.4 }
671 root 1.7 }
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 elmex 1.1 }
681    
682 root 1.13 /*
683 elmex 1.1 * 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 root 1.7 void
688     remove_contents (object *first_ob, object *save_item)
689     {
690 root 1.31 // this cries for a cleaner rewrite, removing save_item first possibly
691 root 1.7 object *next, *tmp = first_ob;
692    
693     while (tmp)
694     {
695     next = tmp->below;
696 root 1.13
697 root 1.7 if (tmp == save_item)
698     {
699     if (!(tmp = next))
700     break;
701     else
702     next = next->below;
703     }
704 root 1.13
705 root 1.7 if (tmp->inv)
706     remove_contents (tmp->inv, NULL);
707 root 1.13
708 root 1.32 tmp->destroy ();
709 root 1.7 tmp = next;
710 elmex 1.1 }
711     }
712    
713 root 1.23 /**
714 elmex 1.1 *"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 root 1.7 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 root 1.23 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 root 1.7 /* 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 root 1.19 assign (name, item->name);
750 root 1.7 if (item->title)
751     sprintf (name, "%s %s", &item->name, &item->title);
752     danger += (strtoint (name) / 1000) + 3;
753     nrofi++;
754     }
755 root 1.23
756 root 1.7 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 elmex 1.1
767     #ifdef ALCHEMY_DEBUG
768 root 1.7 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
769 elmex 1.1 #endif
770    
771 root 1.7 return danger;
772 elmex 1.1 }
773    
774 root 1.23 /**
775 elmex 1.1 * 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 root 1.7 static int
789     is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
790 elmex 1.1 {
791 root 1.7 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 root 1.34 if (!ob->title)
834 root 1.7 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 root 1.4 }
856 root 1.7 break;
857 root 1.4 }
858     }
859 root 1.7 if (!ok)
860     return (0);
861 elmex 1.1 }
862    
863 root 1.7 return (1);
864 elmex 1.1 }
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 root 1.7 static recipe *
874     find_recipe (recipelist * fl, int formula, object *ingredients)
875 elmex 1.1 {
876 root 1.7 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 elmex 1.1
882     #ifdef EXTREME_ALCHEMY_DEBUG
883 root 1.7 LOG (llevDebug, "looking for formula %d:\n", formula);
884 elmex 1.1 #endif
885 root 1.7 result = NULL;
886     recipes_matching = 0;
887     transmute_found = 0;
888 root 1.22
889     for (rp = fl->items; rp; rp = rp->next)
890 root 1.7 {
891     /* check if recipe matches at all */
892     if (formula % rp->index != 0)
893     {
894 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
895 sf-marcmagus 1.38 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], &rp->title, rp->index);
896 elmex 1.1 #endif
897 root 1.7 continue;
898 elmex 1.1 }
899    
900 root 1.7 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
901     {
902 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
903 sf-marcmagus 1.38 LOG (llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], &rp->title, rp->index);
904 elmex 1.1 #endif
905 root 1.7 /* transmution recipe with matching base ingredient */
906     if (!transmute_found)
907     {
908     transmute_found = 1;
909     recipes_matching = 0;
910 elmex 1.1 }
911 root 1.7 }
912     else if (transmute_found)
913     {
914 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
915 sf-marcmagus 1.38 LOG (llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], &rp->title,
916 root 1.7 rp->index);
917 elmex 1.1 #endif
918 root 1.7 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
919     continue;
920 elmex 1.1 }
921     #ifdef EXTREME_ALCHEMY_DEBUG
922 root 1.7 else
923     {
924 sf-marcmagus 1.38 LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], &rp->title, rp->index);
925 elmex 1.1 }
926     #endif
927    
928 root 1.7 if (rndm (0, recipes_matching) == 0)
929     result = rp;
930 elmex 1.1
931 root 1.7 recipes_matching++;
932 elmex 1.1 }
933    
934 root 1.7 if (result == NULL)
935     {
936 elmex 1.1 #ifdef ALCHEMY_DEBUG
937 root 1.7 LOG (llevDebug, "couldn't find formula for ingredients.\n");
938 elmex 1.1 #endif
939 root 1.7 return NULL;
940 elmex 1.1 }
941    
942     #ifdef ALCHEMY_DEBUG
943 root 1.7 if (strcmp (result->title, "NONE") != 0)
944 sf-marcmagus 1.38 LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], &result->title, formula / result->index);
945 root 1.7 else
946     LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
947 elmex 1.1 #endif
948 root 1.7 return result;
949 elmex 1.1 }