ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.13
Committed: Tue Dec 12 21:39:57 2006 UTC (17 years, 6 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.12: +6 -4 lines
Log Message:
- more ooficiation
- removed now superfluous remove calls

File Contents

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