ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.16
Committed: Sat Jan 6 14:42:30 2007 UTC (17 years, 5 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.15: +1 -0 lines
Log Message:
added some copyrights

File Contents

# User Rev Content
1 elmex 1.1 /*
2     CrossFire, A Multiplayer game for X-windows
3    
4 pippijn 1.16 Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team
5 elmex 1.1 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 root 1.8 The authors can be reached via e-mail at <crossfire@schmorp.de>
23 elmex 1.1 */
24    
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     strcpy (name, tmp->name);
238     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     LOG (llevDebug, "A %s <=> %s\n", &(rp->cauldron), &(cauldron->arch->name));
297     /* is the cauldron the right type? */
298     if (strcmp (rp->cauldron, cauldron->arch->name) != 0)
299     {
300     new_draw_info (NDI_UNIQUE, 0, caster, "You are not using the proper" " facilities for this formula.");
301     return 0;
302     }
303    
304     skop = find_skill_by_name (caster, rp->skill);
305     /* does the caster have the skill? */
306     if (!skop)
307     return 0;
308    
309     /* code required for this recipe, search the caster */
310     if (rp->keycode)
311     {
312     object *tmp;
313    
314     for (tmp = caster->inv; tmp != NULL; tmp = tmp->below)
315     {
316     if (tmp->type == FORCE && tmp->slaying && !strcmp (rp->keycode, tmp->slaying))
317     break;
318     }
319     if (tmp == NULL)
320     { /* 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     {
347     new_draw_info_format (NDI_UNIQUE, 0, caster, "The %s %s.", &cauldron->name, cauldron_sound ());
348 root 1.4 }
349 elmex 1.1 }
350 root 1.7 return item;
351 elmex 1.1 }
352    
353    
354    
355     /**
356     * We adjust the nrof, exp and level of the final product, based
357     * on the item's default parameters, and the relevant caster skill level.
358     */
359 root 1.7 void
360     adjust_product (object *item, int lvl, int yield)
361     {
362     int nrof = 1;
363    
364     if (!yield)
365     yield = 1;
366     if (lvl <= 0)
367     lvl = 1; /* lets avoid div by zero! */
368     if (item->nrof)
369     {
370     nrof = (int) ((1.0 - 1.0 / (lvl / 10.0 + 1.0)) * (rndm (0, yield - 1) + rndm (0, yield - 1) + rndm (0, yield - 1)) + 1);
371     if (nrof > yield)
372     nrof = yield;
373     item->nrof = nrof;
374 elmex 1.1 }
375     }
376    
377    
378     /**
379     * Using a list of items and a recipe to make an artifact.
380     *
381     * @param cauldron the cauldron (including the ingredients) used to make the item
382     *
383     * @param rp the recipe to make the artifact from
384     *
385     * @return the newly created object, NULL if something failed
386     */
387    
388 root 1.7 object *
389     make_item_from_recipe (object *cauldron, recipe *rp)
390     {
391     artifact *art = NULL;
392     object *item = NULL;
393     size_t rp_arch_index;
394    
395     if (rp == NULL)
396     return (object *) NULL;
397    
398     /* Find the appropriate object to transform... */
399     if ((item = find_transmution_ob (cauldron->inv, rp, &rp_arch_index, 1)) == NULL)
400     {
401     LOG (llevDebug, "make_alchemy_item(): failed to create alchemical object.\n");
402     return (object *) NULL;
403     }
404    
405     /* Find the appropriate artifact template... */
406     if (strcmp (rp->title, "NONE"))
407     {
408     if ((art = locate_recipe_artifact (rp, rp_arch_index)) == NULL)
409     {
410     LOG (llevError, "make_alchemy_item(): failed to locate recipe artifact.\n");
411     LOG (llevDebug, " --requested recipe: %s of %s.\n", rp->arch_name[0], &rp->title);
412     return (object *) NULL;
413     }
414     transmute_materialname (item, art->item);
415     give_artifact_abilities (item, art->item);
416     }
417    
418     if (QUERY_FLAG (cauldron, FLAG_CURSED))
419     SET_FLAG (item, FLAG_CURSED);
420     if (QUERY_FLAG (cauldron, FLAG_DAMNED))
421     SET_FLAG (item, FLAG_DAMNED);
422    
423     return item;
424 elmex 1.1 }
425    
426    
427     /**
428     * Looks through the ingredient list. If we find a
429     * suitable object in it - we will use that to make the requested artifact.
430     * Otherwise the code returns a 'generic' item if create_item is set. -b.t.
431     *
432     * @param rp_arch_index pointer to return value; set to arch index for recipe;
433     * set to zero if not using a transmution formula
434     */
435 root 1.7
436     object *
437     find_transmution_ob (object *first_ingred, recipe *rp, size_t * rp_arch_index, int create_item)
438     {
439     object *item = NULL;
440    
441     *rp_arch_index = 0;
442    
443     if (rp->transmute) /* look for matching ingredient/prod archs */
444     for (item = first_ingred; item; item = item->below)
445     {
446     size_t i;
447    
448     for (i = 0; i < rp->arch_names; i++)
449     {
450     if (strcmp (item->arch->name, rp->arch_name[i]) == 0)
451     {
452     *rp_arch_index = i;
453 elmex 1.1 break;
454 root 1.7 }
455     }
456     if (i < rp->arch_names)
457     break;
458     }
459    
460     /* failed, create a fresh object. Note no nrof>1 because that would
461     * allow players to create massive amounts of artifacts easily */
462     if (create_item && (!item || item->nrof > 1))
463     {
464     *rp_arch_index = RANDOM () % rp->arch_names;
465     item = get_archetype (rp->arch_name[*rp_arch_index]);
466 elmex 1.1 }
467    
468     #ifdef ALCHEMY_DEBUG
469 root 1.7 LOG (llevDebug, "recipe calls for%stransmution.\n", rp->transmute ? " " : " no ");
470     if (item != NULL)
471     {
472     LOG (llevDebug, " find_transmutable_ob(): returns arch %s(sp:%d)\n", item->arch->name, item->stats.sp);
473 elmex 1.1 }
474     #endif
475 root 1.7
476     return item;
477 elmex 1.1 }
478    
479    
480     /**
481     * Ouch. We didnt get the formula we wanted.
482     * This fctn simulates the backfire effects--worse effects as the level
483     * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
484     * can happen to the would be alchemist. This table probably needs some
485     * adjustment for playbalance. -b.t.
486     */
487 root 1.7
488     void
489     alchemy_failure_effect (object *op, object *cauldron, recipe *rp, int danger)
490     {
491     int level = 0;
492    
493     if (!op || !cauldron)
494     return;
495    
496     if (danger > 1)
497     level = random_roll (1, danger, op, PREFER_LOW);
498    
499     #ifdef ALCHEMY_DEBUG
500     LOG (llevDebug, "Alchemy_failure_effect(): using level=%d\n", level);
501     #endif
502    
503     /* possible outcomes based on level */
504     if (level < 25)
505     { /* INGREDIENTS USED/SLAGGED */
506     object *item = NULL;
507    
508     if (rndm (0, 2))
509     { /* slag created */
510     object *tmp = cauldron->inv;
511     int weight = 0;
512     uint16 material = M_STONE;
513    
514     while (tmp)
515     { /* slag has coadded ingredient properties */
516     weight += tmp->weight;
517     if (!(material & tmp->material))
518     material |= tmp->material;
519     tmp = tmp->below;
520     }
521     tmp = get_archetype ("rock");
522     tmp->weight = weight;
523     tmp->value = 0;
524     tmp->material = material;
525     tmp->materialname = "stone";
526     tmp->name = "slag";
527     tmp->name_pl = "slags";
528     item = insert_ob_in_ob (tmp, cauldron);
529     CLEAR_FLAG (tmp, FLAG_CAN_ROLL);
530     CLEAR_FLAG (tmp, FLAG_NO_PICK);
531     tmp->move_block = 0;
532     }
533     remove_contents (cauldron->inv, item);
534     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
535     return;
536     }
537     else if (level < 40)
538     { /* MAKE TAINTED ITEM */
539     object *tmp = NULL;
540    
541     if (!rp)
542     if ((rp = get_random_recipe ((recipelist *) NULL)) == NULL)
543     return;
544    
545     if ((tmp = attempt_recipe (op, cauldron, 1, rp, -1)))
546     {
547     if (!QUERY_FLAG (tmp, FLAG_CURSED)) /* curse it */
548     SET_FLAG (tmp, FLAG_CURSED);
549    
550     /* the apply code for potions already deals with cursed
551     * potions, so any code here is basically ignored.
552     */
553     if (tmp->type == FOOD)
554     {
555     tmp->stats.hp = random_roll (0, 149, op, PREFER_LOW);
556     }
557     tmp->value = 0; /* unsaleable item */
558    
559     /* change stats downward */
560     do
561     {
562     change_attr_value (&tmp->stats, rndm (0, 6), -1 * (rndm (1, 3)));
563     }
564     while (rndm (0, 2));
565     }
566     return;
567     }
568     if (level == 40)
569     { /* MAKE RANDOM RECIPE */
570     recipelist *fl;
571     int numb = numb_ob_inside (cauldron);
572    
573     fl = get_formulalist (numb - 1); /* take a lower recipe list */
574     if (fl && (rp = get_random_recipe (fl)))
575     /* even though random, don't grant user any EXP for it */
576     (void) attempt_recipe (op, cauldron, 1, rp, -1);
577     else
578     alchemy_failure_effect (op, cauldron, rp, level - 1);
579     return;
580    
581     }
582     else if (level < 45)
583     { /* INFURIATE NPC's */
584     /* this is kind of kludgy I know... */
585     cauldron->enemy = op;
586     npc_call_help (cauldron);
587     cauldron->enemy = NULL;
588    
589     alchemy_failure_effect (op, cauldron, rp, level - 5);
590     return;
591     }
592     else if (level < 50)
593     { /* MINOR EXPLOSION/FIREBALL */
594     object *tmp;
595    
596     remove_contents (cauldron->inv, NULL);
597     switch (rndm (0, 2))
598     {
599 root 1.15 case 0:
600     tmp = get_archetype ("bomb");
601     tmp->stats.dam = random_roll (1, level, op, PREFER_LOW);
602     tmp->stats.hp = random_roll (1, level, op, PREFER_LOW);
603     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s creates a bomb!", &cauldron->name);
604     break;
605 root 1.4
606 root 1.15 default:
607     tmp = get_archetype ("fireball");
608     tmp->stats.dam = random_roll (1, level, op, PREFER_LOW) / 5 + 1;
609     tmp->stats.hp = random_roll (1, level, op, PREFER_LOW) / 10 + 2;
610     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
611     break;
612 root 1.7 }
613 root 1.15
614     op->insert_at (cauldron);
615 root 1.7 return;
616    
617     }
618     else if (level < 60)
619     { /* CREATE MONSTER */
620     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
621     remove_contents (cauldron->inv, NULL);
622     return;
623     }
624     else if (level < 80)
625     { /* MAJOR FIRE */
626     object *fb = get_archetype (SP_MED_FIREBALL);
627    
628     remove_contents (cauldron->inv, NULL);
629     fire_arch_from_position (cauldron, cauldron, cauldron->x, cauldron->y, 0, fb);
630 root 1.13 fb->destroy ();
631 root 1.7 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
632     return;
633    
634     }
635     else if (level < 100)
636     { /* WHAMMY the CAULDRON */
637     if (!QUERY_FLAG (cauldron, FLAG_CURSED))
638     SET_FLAG (cauldron, FLAG_CURSED);
639     else
640     cauldron->magic--;
641     cauldron->magic -= random_roll (0, 4, op, PREFER_LOW);
642     if (rndm (0, 1))
643     {
644     remove_contents (cauldron->inv, NULL);
645     new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s turns darker then makes a gulping sound!", &cauldron->name);
646     }
647     else
648     new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s becomes darker.", &cauldron->name);
649     return;
650    
651     }
652     else if (level < 110)
653     { /* SUMMON EVIL MONSTERS */
654     object *tmp = get_random_mon (level / 5);
655    
656     remove_contents (cauldron->inv, NULL);
657     if (!tmp)
658     alchemy_failure_effect (op, cauldron, rp, level);
659     else if (summon_hostile_monsters (cauldron, random_roll (1, 10, op, PREFER_LOW), tmp->arch->name))
660     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s and then pours forth monsters!", &cauldron->name, cauldron_sound ());
661     return;
662    
663     }
664     else if (level < 150)
665     { /* COMBO EFFECT */
666     int roll = rndm (1, 3);
667    
668     while (roll)
669     {
670     alchemy_failure_effect (op, cauldron, rp, level - 39);
671     roll--;
672     }
673     return;
674     }
675     else if (level == 151)
676     { /* CREATE RANDOM ARTIFACT */
677     object *tmp;
678    
679     /* this is meant to be better than prior possiblity,
680     * in this one, we allow *any* valid alchemy artifact
681     * to be made (rather than only those on the given
682     * formulalist) */
683     if (!rp)
684     rp = get_random_recipe ((recipelist *) NULL);
685     if (rp && (tmp = get_archetype (rp->arch_name[RANDOM () % rp->arch_names])))
686     {
687     generate_artifact (tmp, random_roll (1, op->level / 2 + 1, op, PREFER_HIGH) + 1);
688     if ((tmp = insert_ob_in_ob (tmp, cauldron)))
689     {
690     remove_contents (cauldron->inv, tmp);
691     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
692     }
693 root 1.4 }
694 root 1.7 return;
695     }
696     else
697     { /* MANA STORM - watch out!! */
698     object *tmp = get_archetype (LOOSE_MANA);
699    
700     new_draw_info (NDI_UNIQUE, 0, op, "You unwisely release potent forces!");
701     remove_contents (cauldron->inv, NULL);
702     cast_magic_storm (op, tmp, level);
703     return;
704     }
705 elmex 1.1 }
706    
707    
708 root 1.13 /*
709 elmex 1.1 * All but object "save_item" are elimentated from
710     * the container list. Note we have to becareful to remove the inventories
711     * of objects in the cauldron inventory (ex icecube has stuff in it).
712     */
713 root 1.7
714     void
715     remove_contents (object *first_ob, object *save_item)
716     {
717     object *next, *tmp = first_ob;
718    
719     while (tmp)
720     {
721     next = tmp->below;
722 root 1.13
723 root 1.7 if (tmp == save_item)
724     {
725     if (!(tmp = next))
726     break;
727     else
728     next = next->below;
729     }
730 root 1.13
731 root 1.7 if (tmp->inv)
732     remove_contents (tmp->inv, NULL);
733 root 1.13
734     tmp->destroy ();
735 root 1.7 tmp = next;
736 elmex 1.1 }
737     }
738    
739     /**
740     *"Danger" level, will determine how bad the backfire
741     * could be if the user fails to concoct a recipe properly. Factors include
742     * the number of ingredients, the length of the name of each ingredient,
743     * the user's effective level, the user's Int and the enchantment on the
744     * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
745     * danger. Note that we assume that we have had the caster ready the alchemy
746     * skill *before* this routine is called. (no longer auto-readies that skill)
747     * -b.t.
748     */
749 root 1.7
750     int
751     calc_alch_danger (object *caster, object *cauldron, recipe *rp)
752     {
753     object *item;
754     char name[MAX_BUF];
755     int danger = 0, nrofi = 0;
756    
757     /* Knowing alchemy skill reduces yer risk */
758     danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
759    
760     /* better cauldrons reduce risk */
761     danger -= cauldron->magic;
762    
763     /* Higher Int, lower the risk */
764     danger -= 3 * (caster->stats.Int - 15);
765    
766     /* Ingredients. Longer names usually mean rarer stuff.
767     * Thus the backfire is worse. Also, more ingredients
768     * means we are attempting a more powerfull potion,
769     * and thus the backfire will be worse. */
770     for (item = cauldron->inv; item; item = item->below)
771     {
772     strcpy (name, item->name);
773     if (item->title)
774     sprintf (name, "%s %s", &item->name, &item->title);
775     danger += (strtoint (name) / 1000) + 3;
776     nrofi++;
777     }
778     if (rp == NULL)
779     danger += 110;
780     else
781     danger += rp->diff * 3;
782    
783     /* Using a bad device is *majorly* stupid */
784     if (QUERY_FLAG (cauldron, FLAG_CURSED))
785     danger += 80;
786     if (QUERY_FLAG (cauldron, FLAG_DAMNED))
787     danger += 200;
788 elmex 1.1
789     #ifdef ALCHEMY_DEBUG
790 root 1.7 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
791 elmex 1.1 #endif
792    
793 root 1.7 return danger;
794 elmex 1.1 }
795    
796     /**
797     * Determines if ingredients in a container match the
798     * proper ingredients for a recipe.
799     *
800     * rp is the recipe to check
801     * cauldron is the container that holds the ingredients
802     * returns 1 if the ingredients match the recipe, 0 if not
803     *
804     * This functions tries to find each defined ingredient in the container. It is
805     * the defined recipe iff
806     * - the number of ingredients of the recipe and in the container is equal
807     * - all ingredients of the recipe are found in the container
808     * - the number of batches is the same for all ingredients
809     */
810 root 1.7 static int
811     is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
812 elmex 1.1 {
813 root 1.7 uint32 batches_in_cauldron;
814     const linked_char *ingredient;
815     int number;
816     const object *ob;
817    
818     /* check for matching number of ingredients */
819     number = 0;
820     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
821     number++;
822     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
823     number--;
824     if (number != 0)
825     return 0;
826    
827     /* check for matching ingredients */
828     batches_in_cauldron = 0;
829     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
830     {
831     uint32 nrof;
832     const char *name;
833     int ok;
834    
835     /* determine and remove nrof from name */
836     name = ingredient->name;
837     nrof = 0;
838     while (isdigit (*name))
839     {
840     nrof = 10 * nrof + (*name - '0');
841     name++;
842     }
843     if (nrof == 0)
844     nrof = 1;
845     while (*name == ' ')
846     name++;
847    
848     /* find the current ingredient in the cauldron */
849     ok = 0;
850     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
851     {
852     char name_ob[MAX_BUF];
853     const char *name2;
854    
855     if (ob->title == NULL)
856     name2 = ob->name;
857     else
858     {
859     snprintf (name_ob, sizeof (name_ob), "%s %s", &ob->name, &ob->title);
860     name2 = name_ob;
861     }
862    
863     if (strcmp (name2, name) == 0)
864     {
865     if (ob->nrof % nrof == 0)
866     {
867     uint32 batches;
868    
869     batches = ob->nrof / nrof;
870     if (batches_in_cauldron == 0)
871     {
872     batches_in_cauldron = batches;
873     ok = 1;
874     }
875     else if (batches_in_cauldron == batches)
876     ok = 1;
877 root 1.4 }
878 root 1.7 break;
879 root 1.4 }
880     }
881 root 1.7 if (!ok)
882     return (0);
883 elmex 1.1 }
884    
885 root 1.7 return (1);
886 elmex 1.1 }
887    
888     /**
889     * Find a recipe from a recipe list that matches the given formula. If there
890     * is more than one matching recipe, it selects a random one. If at least one
891     * transmuting recipe matches, it only considers matching transmuting recipes.
892     *
893     * @return one matching recipe, or NULL if no recipe matches
894     */
895 root 1.7 static recipe *
896     find_recipe (recipelist * fl, int formula, object *ingredients)
897 elmex 1.1 {
898 root 1.7 recipe *rp;
899     recipe *result; /* winning recipe, or NULL if no recipe found */
900     int recipes_matching; /* total number of matching recipes so far */
901     int transmute_found; /* records whether a transmuting recipe was found so far */
902     size_t rp_arch_index;
903 elmex 1.1
904     #ifdef EXTREME_ALCHEMY_DEBUG
905 root 1.7 LOG (llevDebug, "looking for formula %d:\n", formula);
906 elmex 1.1 #endif
907 root 1.7 result = NULL;
908     recipes_matching = 0;
909     transmute_found = 0;
910     for (rp = fl->items; rp != NULL; rp = rp->next)
911     {
912     /* check if recipe matches at all */
913     if (formula % rp->index != 0)
914     {
915 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
916 root 1.7 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
917 elmex 1.1 #endif
918 root 1.7 continue;
919 elmex 1.1 }
920    
921 root 1.7 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
922     {
923 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
924 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);
925 elmex 1.1 #endif
926 root 1.7 /* transmution recipe with matching base ingredient */
927     if (!transmute_found)
928     {
929     transmute_found = 1;
930     recipes_matching = 0;
931 elmex 1.1 }
932 root 1.7 }
933     else if (transmute_found)
934     {
935 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
936 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,
937     rp->index);
938 elmex 1.1 #endif
939 root 1.7 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
940     continue;
941 elmex 1.1 }
942     #ifdef EXTREME_ALCHEMY_DEBUG
943 root 1.7 else
944     {
945     LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
946 elmex 1.1 }
947     #endif
948    
949 root 1.7 if (rndm (0, recipes_matching) == 0)
950     result = rp;
951 elmex 1.1
952 root 1.7 recipes_matching++;
953 elmex 1.1 }
954    
955 root 1.7 if (result == NULL)
956     {
957 elmex 1.1 #ifdef ALCHEMY_DEBUG
958 root 1.7 LOG (llevDebug, "couldn't find formula for ingredients.\n");
959 elmex 1.1 #endif
960 root 1.7 return NULL;
961 elmex 1.1 }
962    
963     #ifdef ALCHEMY_DEBUG
964 root 1.7 if (strcmp (result->title, "NONE") != 0)
965     LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula / result->index);
966     else
967     LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
968 elmex 1.1 #endif
969 root 1.7 return result;
970 elmex 1.1 }