ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.17
Committed: Sat Jan 6 21:31:44 2007 UTC (17 years, 5 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.16: +11 -8 lines
Log Message:
remove debugging code

File Contents

# User Rev Content
1 elmex 1.1 /*
2     CrossFire, A Multiplayer game for X-windows
3    
4 pippijn 1.16 Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team
5 elmex 1.1 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 root 1.8 The authors can be reached via e-mail at <crossfire@schmorp.de>
23 elmex 1.1 */
24    
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     strcpy (name, tmp->name);
238     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     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 root 1.17 for (tmp = caster->inv; tmp; tmp = tmp->below)
314 root 1.7 {
315     if (tmp->type == FORCE && tmp->slaying && !strcmp (rp->keycode, tmp->slaying))
316     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     if (strcmp (item->arch->name, rp->arch_name[i]) == 0)
454     {
455     *rp_arch_index = i;
456 elmex 1.1 break;
457 root 1.7 }
458     }
459     if (i < rp->arch_names)
460     break;
461     }
462    
463     /* failed, create a fresh object. Note no nrof>1 because that would
464     * allow players to create massive amounts of artifacts easily */
465     if (create_item && (!item || item->nrof > 1))
466     {
467     *rp_arch_index = RANDOM () % rp->arch_names;
468     item = get_archetype (rp->arch_name[*rp_arch_index]);
469 elmex 1.1 }
470    
471     #ifdef ALCHEMY_DEBUG
472 root 1.7 LOG (llevDebug, "recipe calls for%stransmution.\n", rp->transmute ? " " : " no ");
473     if (item != NULL)
474     {
475     LOG (llevDebug, " find_transmutable_ob(): returns arch %s(sp:%d)\n", item->arch->name, item->stats.sp);
476 elmex 1.1 }
477     #endif
478 root 1.7
479     return item;
480 elmex 1.1 }
481    
482    
483     /**
484     * Ouch. We didnt get the formula we wanted.
485     * This fctn simulates the backfire effects--worse effects as the level
486     * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
487     * can happen to the would be alchemist. This table probably needs some
488     * adjustment for playbalance. -b.t.
489     */
490 root 1.7
491     void
492     alchemy_failure_effect (object *op, object *cauldron, recipe *rp, int danger)
493     {
494     int level = 0;
495    
496     if (!op || !cauldron)
497     return;
498    
499     if (danger > 1)
500     level = random_roll (1, danger, op, PREFER_LOW);
501    
502     #ifdef ALCHEMY_DEBUG
503     LOG (llevDebug, "Alchemy_failure_effect(): using level=%d\n", level);
504     #endif
505    
506     /* possible outcomes based on level */
507     if (level < 25)
508     { /* INGREDIENTS USED/SLAGGED */
509     object *item = NULL;
510    
511     if (rndm (0, 2))
512     { /* slag created */
513     object *tmp = cauldron->inv;
514     int weight = 0;
515     uint16 material = M_STONE;
516    
517     while (tmp)
518     { /* slag has coadded ingredient properties */
519     weight += tmp->weight;
520     if (!(material & tmp->material))
521     material |= tmp->material;
522     tmp = tmp->below;
523     }
524     tmp = get_archetype ("rock");
525     tmp->weight = weight;
526     tmp->value = 0;
527     tmp->material = material;
528     tmp->materialname = "stone";
529     tmp->name = "slag";
530     tmp->name_pl = "slags";
531     item = insert_ob_in_ob (tmp, cauldron);
532     CLEAR_FLAG (tmp, FLAG_CAN_ROLL);
533     CLEAR_FLAG (tmp, FLAG_NO_PICK);
534     tmp->move_block = 0;
535     }
536     remove_contents (cauldron->inv, item);
537     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
538     return;
539     }
540     else if (level < 40)
541     { /* MAKE TAINTED ITEM */
542     object *tmp = NULL;
543    
544     if (!rp)
545     if ((rp = get_random_recipe ((recipelist *) NULL)) == NULL)
546     return;
547    
548     if ((tmp = attempt_recipe (op, cauldron, 1, rp, -1)))
549     {
550     if (!QUERY_FLAG (tmp, FLAG_CURSED)) /* curse it */
551     SET_FLAG (tmp, FLAG_CURSED);
552    
553     /* the apply code for potions already deals with cursed
554     * potions, so any code here is basically ignored.
555     */
556     if (tmp->type == FOOD)
557     {
558     tmp->stats.hp = random_roll (0, 149, op, PREFER_LOW);
559     }
560     tmp->value = 0; /* unsaleable item */
561    
562     /* change stats downward */
563     do
564     {
565     change_attr_value (&tmp->stats, rndm (0, 6), -1 * (rndm (1, 3)));
566     }
567     while (rndm (0, 2));
568     }
569     return;
570     }
571     if (level == 40)
572     { /* MAKE RANDOM RECIPE */
573     recipelist *fl;
574     int numb = numb_ob_inside (cauldron);
575    
576     fl = get_formulalist (numb - 1); /* take a lower recipe list */
577     if (fl && (rp = get_random_recipe (fl)))
578     /* even though random, don't grant user any EXP for it */
579     (void) attempt_recipe (op, cauldron, 1, rp, -1);
580     else
581     alchemy_failure_effect (op, cauldron, rp, level - 1);
582     return;
583    
584     }
585     else if (level < 45)
586     { /* INFURIATE NPC's */
587     /* this is kind of kludgy I know... */
588     cauldron->enemy = op;
589     npc_call_help (cauldron);
590     cauldron->enemy = NULL;
591    
592     alchemy_failure_effect (op, cauldron, rp, level - 5);
593     return;
594     }
595     else if (level < 50)
596     { /* MINOR EXPLOSION/FIREBALL */
597     object *tmp;
598    
599     remove_contents (cauldron->inv, NULL);
600     switch (rndm (0, 2))
601     {
602 root 1.15 case 0:
603     tmp = get_archetype ("bomb");
604     tmp->stats.dam = random_roll (1, level, op, PREFER_LOW);
605     tmp->stats.hp = random_roll (1, level, op, PREFER_LOW);
606     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s creates a bomb!", &cauldron->name);
607     break;
608 root 1.4
609 root 1.15 default:
610     tmp = get_archetype ("fireball");
611     tmp->stats.dam = random_roll (1, level, op, PREFER_LOW) / 5 + 1;
612     tmp->stats.hp = random_roll (1, level, op, PREFER_LOW) / 10 + 2;
613     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
614     break;
615 root 1.7 }
616 root 1.15
617     op->insert_at (cauldron);
618 root 1.7 return;
619    
620     }
621     else if (level < 60)
622     { /* CREATE MONSTER */
623     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
624     remove_contents (cauldron->inv, NULL);
625     return;
626     }
627     else if (level < 80)
628     { /* MAJOR FIRE */
629     object *fb = get_archetype (SP_MED_FIREBALL);
630    
631     remove_contents (cauldron->inv, NULL);
632     fire_arch_from_position (cauldron, cauldron, cauldron->x, cauldron->y, 0, fb);
633 root 1.13 fb->destroy ();
634 root 1.7 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
635     return;
636    
637     }
638     else if (level < 100)
639     { /* WHAMMY the CAULDRON */
640     if (!QUERY_FLAG (cauldron, FLAG_CURSED))
641     SET_FLAG (cauldron, FLAG_CURSED);
642     else
643     cauldron->magic--;
644     cauldron->magic -= random_roll (0, 4, op, PREFER_LOW);
645     if (rndm (0, 1))
646     {
647     remove_contents (cauldron->inv, NULL);
648     new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s turns darker then makes a gulping sound!", &cauldron->name);
649     }
650     else
651     new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s becomes darker.", &cauldron->name);
652     return;
653    
654     }
655     else if (level < 110)
656     { /* SUMMON EVIL MONSTERS */
657     object *tmp = get_random_mon (level / 5);
658    
659     remove_contents (cauldron->inv, NULL);
660     if (!tmp)
661     alchemy_failure_effect (op, cauldron, rp, level);
662     else if (summon_hostile_monsters (cauldron, random_roll (1, 10, op, PREFER_LOW), tmp->arch->name))
663     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s and then pours forth monsters!", &cauldron->name, cauldron_sound ());
664     return;
665    
666     }
667     else if (level < 150)
668     { /* COMBO EFFECT */
669     int roll = rndm (1, 3);
670    
671     while (roll)
672     {
673     alchemy_failure_effect (op, cauldron, rp, level - 39);
674     roll--;
675     }
676     return;
677     }
678     else if (level == 151)
679     { /* CREATE RANDOM ARTIFACT */
680     object *tmp;
681    
682     /* this is meant to be better than prior possiblity,
683     * in this one, we allow *any* valid alchemy artifact
684     * to be made (rather than only those on the given
685     * formulalist) */
686     if (!rp)
687     rp = get_random_recipe ((recipelist *) NULL);
688     if (rp && (tmp = get_archetype (rp->arch_name[RANDOM () % rp->arch_names])))
689     {
690     generate_artifact (tmp, random_roll (1, op->level / 2 + 1, op, PREFER_HIGH) + 1);
691     if ((tmp = insert_ob_in_ob (tmp, cauldron)))
692     {
693     remove_contents (cauldron->inv, tmp);
694     new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
695     }
696 root 1.4 }
697 root 1.7 return;
698     }
699     else
700     { /* MANA STORM - watch out!! */
701     object *tmp = get_archetype (LOOSE_MANA);
702    
703     new_draw_info (NDI_UNIQUE, 0, op, "You unwisely release potent forces!");
704     remove_contents (cauldron->inv, NULL);
705     cast_magic_storm (op, tmp, level);
706     return;
707     }
708 elmex 1.1 }
709    
710    
711 root 1.13 /*
712 elmex 1.1 * All but object "save_item" are elimentated from
713     * the container list. Note we have to becareful to remove the inventories
714     * of objects in the cauldron inventory (ex icecube has stuff in it).
715     */
716 root 1.7
717     void
718     remove_contents (object *first_ob, object *save_item)
719     {
720     object *next, *tmp = first_ob;
721    
722     while (tmp)
723     {
724     next = tmp->below;
725 root 1.13
726 root 1.7 if (tmp == save_item)
727     {
728     if (!(tmp = next))
729     break;
730     else
731     next = next->below;
732     }
733 root 1.13
734 root 1.7 if (tmp->inv)
735     remove_contents (tmp->inv, NULL);
736 root 1.13
737     tmp->destroy ();
738 root 1.7 tmp = next;
739 elmex 1.1 }
740     }
741    
742     /**
743     *"Danger" level, will determine how bad the backfire
744     * could be if the user fails to concoct a recipe properly. Factors include
745     * the number of ingredients, the length of the name of each ingredient,
746     * the user's effective level, the user's Int and the enchantment on the
747     * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
748     * danger. Note that we assume that we have had the caster ready the alchemy
749     * skill *before* this routine is called. (no longer auto-readies that skill)
750     * -b.t.
751     */
752 root 1.7
753     int
754     calc_alch_danger (object *caster, object *cauldron, recipe *rp)
755     {
756     object *item;
757     char name[MAX_BUF];
758     int danger = 0, nrofi = 0;
759    
760     /* Knowing alchemy skill reduces yer risk */
761     danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
762    
763     /* better cauldrons reduce risk */
764     danger -= cauldron->magic;
765    
766     /* Higher Int, lower the risk */
767     danger -= 3 * (caster->stats.Int - 15);
768    
769     /* Ingredients. Longer names usually mean rarer stuff.
770     * Thus the backfire is worse. Also, more ingredients
771     * means we are attempting a more powerfull potion,
772     * and thus the backfire will be worse. */
773     for (item = cauldron->inv; item; item = item->below)
774     {
775     strcpy (name, item->name);
776     if (item->title)
777     sprintf (name, "%s %s", &item->name, &item->title);
778     danger += (strtoint (name) / 1000) + 3;
779     nrofi++;
780     }
781     if (rp == NULL)
782     danger += 110;
783     else
784     danger += rp->diff * 3;
785    
786     /* Using a bad device is *majorly* stupid */
787     if (QUERY_FLAG (cauldron, FLAG_CURSED))
788     danger += 80;
789     if (QUERY_FLAG (cauldron, FLAG_DAMNED))
790     danger += 200;
791 elmex 1.1
792     #ifdef ALCHEMY_DEBUG
793 root 1.7 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
794 elmex 1.1 #endif
795    
796 root 1.7 return danger;
797 elmex 1.1 }
798    
799     /**
800     * Determines if ingredients in a container match the
801     * proper ingredients for a recipe.
802     *
803     * rp is the recipe to check
804     * cauldron is the container that holds the ingredients
805     * returns 1 if the ingredients match the recipe, 0 if not
806     *
807     * This functions tries to find each defined ingredient in the container. It is
808     * the defined recipe iff
809     * - the number of ingredients of the recipe and in the container is equal
810     * - all ingredients of the recipe are found in the container
811     * - the number of batches is the same for all ingredients
812     */
813 root 1.7 static int
814     is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
815 elmex 1.1 {
816 root 1.7 uint32 batches_in_cauldron;
817     const linked_char *ingredient;
818     int number;
819     const object *ob;
820    
821     /* check for matching number of ingredients */
822     number = 0;
823     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
824     number++;
825     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
826     number--;
827     if (number != 0)
828     return 0;
829    
830     /* check for matching ingredients */
831     batches_in_cauldron = 0;
832     for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
833     {
834     uint32 nrof;
835     const char *name;
836     int ok;
837    
838     /* determine and remove nrof from name */
839     name = ingredient->name;
840     nrof = 0;
841     while (isdigit (*name))
842     {
843     nrof = 10 * nrof + (*name - '0');
844     name++;
845     }
846     if (nrof == 0)
847     nrof = 1;
848     while (*name == ' ')
849     name++;
850    
851     /* find the current ingredient in the cauldron */
852     ok = 0;
853     for (ob = cauldron->inv; ob != NULL; ob = ob->below)
854     {
855     char name_ob[MAX_BUF];
856     const char *name2;
857    
858     if (ob->title == NULL)
859     name2 = ob->name;
860     else
861     {
862     snprintf (name_ob, sizeof (name_ob), "%s %s", &ob->name, &ob->title);
863     name2 = name_ob;
864     }
865    
866     if (strcmp (name2, name) == 0)
867     {
868     if (ob->nrof % nrof == 0)
869     {
870     uint32 batches;
871    
872     batches = ob->nrof / nrof;
873     if (batches_in_cauldron == 0)
874     {
875     batches_in_cauldron = batches;
876     ok = 1;
877     }
878     else if (batches_in_cauldron == batches)
879     ok = 1;
880 root 1.4 }
881 root 1.7 break;
882 root 1.4 }
883     }
884 root 1.7 if (!ok)
885     return (0);
886 elmex 1.1 }
887    
888 root 1.7 return (1);
889 elmex 1.1 }
890    
891     /**
892     * Find a recipe from a recipe list that matches the given formula. If there
893     * is more than one matching recipe, it selects a random one. If at least one
894     * transmuting recipe matches, it only considers matching transmuting recipes.
895     *
896     * @return one matching recipe, or NULL if no recipe matches
897     */
898 root 1.7 static recipe *
899     find_recipe (recipelist * fl, int formula, object *ingredients)
900 elmex 1.1 {
901 root 1.7 recipe *rp;
902     recipe *result; /* winning recipe, or NULL if no recipe found */
903     int recipes_matching; /* total number of matching recipes so far */
904     int transmute_found; /* records whether a transmuting recipe was found so far */
905     size_t rp_arch_index;
906 elmex 1.1
907     #ifdef EXTREME_ALCHEMY_DEBUG
908 root 1.7 LOG (llevDebug, "looking for formula %d:\n", formula);
909 elmex 1.1 #endif
910 root 1.7 result = NULL;
911     recipes_matching = 0;
912     transmute_found = 0;
913     for (rp = fl->items; rp != NULL; rp = rp->next)
914     {
915     /* check if recipe matches at all */
916     if (formula % rp->index != 0)
917     {
918 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
919 root 1.7 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
920 elmex 1.1 #endif
921 root 1.7 continue;
922 elmex 1.1 }
923    
924 root 1.7 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
925     {
926 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
927 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);
928 elmex 1.1 #endif
929 root 1.7 /* transmution recipe with matching base ingredient */
930     if (!transmute_found)
931     {
932     transmute_found = 1;
933     recipes_matching = 0;
934 elmex 1.1 }
935 root 1.7 }
936     else if (transmute_found)
937     {
938 elmex 1.1 #ifdef EXTREME_ALCHEMY_DEBUG
939 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,
940     rp->index);
941 elmex 1.1 #endif
942 root 1.7 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
943     continue;
944 elmex 1.1 }
945     #ifdef EXTREME_ALCHEMY_DEBUG
946 root 1.7 else
947     {
948     LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
949 elmex 1.1 }
950     #endif
951    
952 root 1.7 if (rndm (0, recipes_matching) == 0)
953     result = rp;
954 elmex 1.1
955 root 1.7 recipes_matching++;
956 elmex 1.1 }
957    
958 root 1.7 if (result == NULL)
959     {
960 elmex 1.1 #ifdef ALCHEMY_DEBUG
961 root 1.7 LOG (llevDebug, "couldn't find formula for ingredients.\n");
962 elmex 1.1 #endif
963 root 1.7 return NULL;
964 elmex 1.1 }
965    
966     #ifdef ALCHEMY_DEBUG
967 root 1.7 if (strcmp (result->title, "NONE") != 0)
968     LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula / result->index);
969     else
970     LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
971 elmex 1.1 #endif
972 root 1.7 return result;
973 elmex 1.1 }