ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.35
Committed: Sun Jan 4 16:30:39 2009 UTC (15 years, 5 months ago) by elmex
Content type: text/plain
Branch: MAIN
Changes since 1.34: +8 -27 lines
Log Message:
sanatized alchemy skill.

File Contents

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