ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.15
Committed: Tue Dec 26 20:04:09 2006 UTC (17 years, 4 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.14: +14 -14 lines
Log Message:
- added maptile->insert and object->insert_at methods that might
  make code using it clearer.
- replaced some insert_ob_in_map calls.

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