ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.7
Committed: Sun Sep 10 15:59:57 2006 UTC (17 years, 8 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.6: +725 -613 lines
Log Message:
indent

File Contents

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