ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.10
Committed: Mon Dec 11 02:54:57 2006 UTC (17 years, 6 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.9: +5 -0 lines
Log Message:
- rename $uptime to $UPTIME
- hopefully force alchemy to use one second delay

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