ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.11
Committed: Mon Dec 11 19:46:46 2006 UTC (17 years, 5 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.10: +0 -6 lines
Log Message:
removed #ifn?def WIN32 from all files

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     free_object (fb);
632     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     /**
710     * 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     if (tmp == save_item)
724     {
725     if (!(tmp = next))
726     break;
727     else
728     next = next->below;
729     }
730     if (tmp->inv)
731     remove_contents (tmp->inv, NULL);
732     remove_ob (tmp);
733     free_object (tmp);
734     tmp = next;
735 elmex 1.1 }
736     }
737    
738     /**
739     *"Danger" level, will determine how bad the backfire
740     * could be if the user fails to concoct a recipe properly. Factors include
741     * the number of ingredients, the length of the name of each ingredient,
742     * the user's effective level, the user's Int and the enchantment on the
743     * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
744     * danger. Note that we assume that we have had the caster ready the alchemy
745     * skill *before* this routine is called. (no longer auto-readies that skill)
746     * -b.t.
747     */
748 root 1.7
749     int
750     calc_alch_danger (object *caster, object *cauldron, recipe *rp)
751     {
752     object *item;
753     char name[MAX_BUF];
754     int danger = 0, nrofi = 0;
755    
756     /* Knowing alchemy skill reduces yer risk */
757     danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
758    
759     /* better cauldrons reduce risk */
760     danger -= cauldron->magic;
761    
762     /* Higher Int, lower the risk */
763     danger -= 3 * (caster->stats.Int - 15);
764    
765     /* Ingredients. Longer names usually mean rarer stuff.
766     * Thus the backfire is worse. Also, more ingredients
767     * means we are attempting a more powerfull potion,
768     * and thus the backfire will be worse. */
769     for (item = cauldron->inv; item; item = item->below)
770     {
771     strcpy (name, item->name);
772     if (item->title)
773     sprintf (name, "%s %s", &item->name, &item->title);
774     danger += (strtoint (name) / 1000) + 3;
775     nrofi++;
776     }
777     if (rp == NULL)
778     danger += 110;
779     else
780     danger += rp->diff * 3;
781    
782     /* Using a bad device is *majorly* stupid */
783     if (QUERY_FLAG (cauldron, FLAG_CURSED))
784     danger += 80;
785     if (QUERY_FLAG (cauldron, FLAG_DAMNED))
786     danger += 200;
787 elmex 1.1
788     #ifdef ALCHEMY_DEBUG
789 root 1.7 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
790 elmex 1.1 #endif
791    
792 root 1.7 return danger;
793 elmex 1.1 }
794    
795     /**
796     * Determines if ingredients in a container match the
797     * proper ingredients for a recipe.
798     *
799     * rp is the recipe to check
800     * cauldron is the container that holds the ingredients
801     * returns 1 if the ingredients match the recipe, 0 if not
802     *
803     * This functions tries to find each defined ingredient in the container. It is
804     * the defined recipe iff
805     * - the number of ingredients of the recipe and in the container is equal
806     * - all ingredients of the recipe are found in the container
807     * - the number of batches is the same for all ingredients
808     */
809 root 1.7 static int
810     is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
811 elmex 1.1 {
812 root 1.7 uint32 batches_in_cauldron;
813     const linked_char *ingredient;
814     int number;
815     const object *ob;
816    
817     /* check for matching number of ingredients */
818     number = 0;
819     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
820     number++;
821     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
822     number--;
823     if (number != 0)
824     return 0;
825    
826     /* check for matching ingredients */
827     batches_in_cauldron = 0;
828     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
829     {
830     uint32 nrof;
831     const char *name;
832     int ok;
833    
834     /* determine and remove nrof from name */
835     name = ingredient->name;
836     nrof = 0;
837     while (isdigit (*name))
838     {
839     nrof = 10 * nrof + (*name - '0');
840     name++;
841     }
842     if (nrof == 0)
843     nrof = 1;
844     while (*name == ' ')
845     name++;
846    
847     /* find the current ingredient in the cauldron */
848     ok = 0;
849     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
850     {
851     char name_ob[MAX_BUF];
852     const char *name2;
853    
854     if (ob->title == NULL)
855     name2 = ob->name;
856     else
857     {
858     snprintf (name_ob, sizeof (name_ob), "%s %s", &ob->name, &ob->title);
859     name2 = name_ob;
860     }
861    
862     if (strcmp (name2, name) == 0)
863     {
864     if (ob->nrof % nrof == 0)
865     {
866     uint32 batches;
867    
868     batches = ob->nrof / nrof;
869     if (batches_in_cauldron == 0)
870     {
871     batches_in_cauldron = batches;
872     ok = 1;
873     }
874     else if (batches_in_cauldron == batches)
875     ok = 1;
876 root 1.4 }
877 root 1.7 break;
878 root 1.4 }
879     }
880 root 1.7 if (!ok)
881     return (0);
882 elmex 1.1 }
883    
884 root 1.7 return (1);
885 elmex 1.1 }
886    
887     /**
888     * Find a recipe from a recipe list that matches the given formula. If there
889     * is more than one matching recipe, it selects a random one. If at least one
890     * transmuting recipe matches, it only considers matching transmuting recipes.
891     *
892     * @return one matching recipe, or NULL if no recipe matches
893     */
894 root 1.7 static recipe *
895     find_recipe (recipelist * fl, int formula, object *ingredients)
896 elmex 1.1 {
897 root 1.7 recipe *rp;
898     recipe *result; /* winning recipe, or NULL if no recipe found */
899     int recipes_matching; /* total number of matching recipes so far */
900     int transmute_found; /* records whether a transmuting recipe was found so far */
901     size_t rp_arch_index;
902 elmex 1.1
903     #ifdef EXTREME_ALCHEMY_DEBUG
904 root 1.7 LOG (llevDebug, "looking for formula %d:\n", formula);
905 elmex 1.1 #endif
906 root 1.7 result = NULL;
907     recipes_matching = 0;
908     transmute_found = 0;
909     for (rp = fl->items; rp != NULL; rp = rp->next)
910     {
911     /* check if recipe matches at all */
912     if (formula % rp->index != 0)
913     {
914 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
915 root 1.7 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
916 elmex 1.1 #endif
917 root 1.7 continue;
918 elmex 1.1 }
919    
920 root 1.7 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
921     {
922 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
923 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);
924 elmex 1.1 #endif
925 root 1.7 /* transmution recipe with matching base ingredient */
926     if (!transmute_found)
927     {
928     transmute_found = 1;
929     recipes_matching = 0;
930 elmex 1.1 }
931 root 1.7 }
932     else if (transmute_found)
933     {
934 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
935 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,
936     rp->index);
937 elmex 1.1 #endif
938 root 1.7 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
939     continue;
940 elmex 1.1 }
941     #ifdef EXTREME_ALCHEMY_DEBUG
942 root 1.7 else
943     {
944     LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
945 elmex 1.1 }
946     #endif
947    
948 root 1.7 if (rndm (0, recipes_matching) == 0)
949     result = rp;
950 elmex 1.1
951 root 1.7 recipes_matching++;
952 elmex 1.1 }
953    
954 root 1.7 if (result == NULL)
955     {
956 elmex 1.1 #ifdef ALCHEMY_DEBUG
957 root 1.7 LOG (llevDebug, "couldn't find formula for ingredients.\n");
958 elmex 1.1 #endif
959 root 1.7 return NULL;
960 elmex 1.1 }
961    
962     #ifdef ALCHEMY_DEBUG
963 root 1.7 if (strcmp (result->title, "NONE") != 0)
964     LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula / result->index);
965     else
966     LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
967 elmex 1.1 #endif
968 root 1.7 return result;
969 elmex 1.1 }