ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.22
Committed: Mon Apr 16 06:23:42 2007 UTC (17 years, 1 month ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.21: +2 -1 lines
Log Message:
VERY EXPERIMENTAL

- change the way archetypes and treasurelists are being loaded:
  - referring to a nonexisting treasurelist will create an empty one
  - referring to a nonexisting archetype will create an empty one
  - archetypes/treasurelists will overwrite any existing object
    of the same name.

- net effect should be to allow reloading of archetypes and treasurelists
  at runtime at a later stage.

File Contents

# User Rev Content
1 elmex 1.1 /*
2 pippijn 1.18 * CrossFire, A Multiplayer game for X-windows
3     *
4     * Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team
5     * Copyright (C) 2002 Mark Wedel & Crossfire Development Team
6     * Copyright (C) 1992 Frank Tore Johansen
7     *
8     * This program 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 2 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, write to the Free Software
20     * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21     *
22     * The authors can be reached via e-mail at <crossfire@schmorp.de>
23     */
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     /**
736     *"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
746     int
747     calc_alch_danger (object *caster, object *cauldron, recipe *rp)
748     {
749     object *item;
750     char name[MAX_BUF];
751     int danger = 0, nrofi = 0;
752    
753     /* Knowing alchemy skill reduces yer risk */
754     danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
755    
756     /* better cauldrons reduce risk */
757     danger -= cauldron->magic;
758    
759     /* Higher Int, lower the risk */
760     danger -= 3 * (caster->stats.Int - 15);
761    
762     /* Ingredients. Longer names usually mean rarer stuff.
763     * Thus the backfire is worse. Also, more ingredients
764     * means we are attempting a more powerfull potion,
765     * and thus the backfire will be worse. */
766     for (item = cauldron->inv; item; item = item->below)
767     {
768 root 1.19 assign (name, item->name);
769 root 1.7 if (item->title)
770     sprintf (name, "%s %s", &item->name, &item->title);
771     danger += (strtoint (name) / 1000) + 3;
772     nrofi++;
773     }
774     if (rp == NULL)
775     danger += 110;
776     else
777     danger += rp->diff * 3;
778    
779     /* Using a bad device is *majorly* stupid */
780     if (QUERY_FLAG (cauldron, FLAG_CURSED))
781     danger += 80;
782     if (QUERY_FLAG (cauldron, FLAG_DAMNED))
783     danger += 200;
784 elmex 1.1
785     #ifdef ALCHEMY_DEBUG
786 root 1.7 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
787 elmex 1.1 #endif
788    
789 root 1.7 return danger;
790 elmex 1.1 }
791    
792     /**
793     * Determines if ingredients in a container match the
794     * proper ingredients for a recipe.
795     *
796     * rp is the recipe to check
797     * cauldron is the container that holds the ingredients
798     * returns 1 if the ingredients match the recipe, 0 if not
799     *
800     * This functions tries to find each defined ingredient in the container. It is
801     * the defined recipe iff
802     * - the number of ingredients of the recipe and in the container is equal
803     * - all ingredients of the recipe are found in the container
804     * - the number of batches is the same for all ingredients
805     */
806 root 1.7 static int
807     is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
808 elmex 1.1 {
809 root 1.7 uint32 batches_in_cauldron;
810     const linked_char *ingredient;
811     int number;
812     const object *ob;
813    
814     /* check for matching number of ingredients */
815     number = 0;
816     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
817     number++;
818     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
819     number--;
820     if (number != 0)
821     return 0;
822    
823     /* check for matching ingredients */
824     batches_in_cauldron = 0;
825     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
826     {
827     uint32 nrof;
828     const char *name;
829     int ok;
830    
831     /* determine and remove nrof from name */
832     name = ingredient->name;
833     nrof = 0;
834     while (isdigit (*name))
835     {
836     nrof = 10 * nrof + (*name - '0');
837     name++;
838     }
839     if (nrof == 0)
840     nrof = 1;
841     while (*name == ' ')
842     name++;
843    
844     /* find the current ingredient in the cauldron */
845     ok = 0;
846     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
847     {
848     char name_ob[MAX_BUF];
849     const char *name2;
850    
851     if (ob->title == NULL)
852     name2 = ob->name;
853     else
854     {
855     snprintf (name_ob, sizeof (name_ob), "%s %s", &ob->name, &ob->title);
856     name2 = name_ob;
857     }
858    
859     if (strcmp (name2, name) == 0)
860     {
861     if (ob->nrof % nrof == 0)
862     {
863     uint32 batches;
864    
865     batches = ob->nrof / nrof;
866     if (batches_in_cauldron == 0)
867     {
868     batches_in_cauldron = batches;
869     ok = 1;
870     }
871     else if (batches_in_cauldron == batches)
872     ok = 1;
873 root 1.4 }
874 root 1.7 break;
875 root 1.4 }
876     }
877 root 1.7 if (!ok)
878     return (0);
879 elmex 1.1 }
880    
881 root 1.7 return (1);
882 elmex 1.1 }
883    
884     /**
885     * Find a recipe from a recipe list that matches the given formula. If there
886     * is more than one matching recipe, it selects a random one. If at least one
887     * transmuting recipe matches, it only considers matching transmuting recipes.
888     *
889     * @return one matching recipe, or NULL if no recipe matches
890     */
891 root 1.7 static recipe *
892     find_recipe (recipelist * fl, int formula, object *ingredients)
893 elmex 1.1 {
894 root 1.7 recipe *rp;
895     recipe *result; /* winning recipe, or NULL if no recipe found */
896     int recipes_matching; /* total number of matching recipes so far */
897     int transmute_found; /* records whether a transmuting recipe was found so far */
898     size_t rp_arch_index;
899 elmex 1.1
900     #ifdef EXTREME_ALCHEMY_DEBUG
901 root 1.7 LOG (llevDebug, "looking for formula %d:\n", formula);
902 elmex 1.1 #endif
903 root 1.7 result = NULL;
904     recipes_matching = 0;
905     transmute_found = 0;
906 root 1.22
907     for (rp = fl->items; rp; rp = rp->next)
908 root 1.7 {
909     /* check if recipe matches at all */
910     if (formula % rp->index != 0)
911     {
912 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
913 root 1.7 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
914 elmex 1.1 #endif
915 root 1.7 continue;
916 elmex 1.1 }
917    
918 root 1.7 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
919     {
920 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
921 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);
922 elmex 1.1 #endif
923 root 1.7 /* transmution recipe with matching base ingredient */
924     if (!transmute_found)
925     {
926     transmute_found = 1;
927     recipes_matching = 0;
928 elmex 1.1 }
929 root 1.7 }
930     else if (transmute_found)
931     {
932 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
933 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,
934     rp->index);
935 elmex 1.1 #endif
936 root 1.7 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
937     continue;
938 elmex 1.1 }
939     #ifdef EXTREME_ALCHEMY_DEBUG
940 root 1.7 else
941     {
942     LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
943 elmex 1.1 }
944     #endif
945    
946 root 1.7 if (rndm (0, recipes_matching) == 0)
947     result = rp;
948 elmex 1.1
949 root 1.7 recipes_matching++;
950 elmex 1.1 }
951    
952 root 1.7 if (result == NULL)
953     {
954 elmex 1.1 #ifdef ALCHEMY_DEBUG
955 root 1.7 LOG (llevDebug, "couldn't find formula for ingredients.\n");
956 elmex 1.1 #endif
957 root 1.7 return NULL;
958 elmex 1.1 }
959    
960     #ifdef ALCHEMY_DEBUG
961 root 1.7 if (strcmp (result->title, "NONE") != 0)
962     LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula / result->index);
963     else
964     LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
965 elmex 1.1 #endif
966 root 1.7 return result;
967 elmex 1.1 }