ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.8
Committed: Thu Sep 14 22:34:03 2006 UTC (17 years, 8 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.7: +1 -7 lines
Log Message:
indent

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