ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.9
Committed: Fri Sep 15 23:11:57 2006 UTC (17 years, 9 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.8: +3 -7 lines
Log Message:
dumber bug than ever imagined

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