ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.20
Committed: Mon Feb 5 02:07:40 2007 UTC (17 years, 4 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.19: +4 -3 lines
Log Message:
remove many strcmps on shstr, added fast strcmp wrapper that only etsts for inequality

File Contents

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