ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.3
Committed: Tue Aug 15 17:35:51 2006 UTC (17 years, 9 months ago) by elmex
Content type: text/plain
Branch: MAIN
Changes since 1.2: +2 -2 lines
Log Message:
removed P_SAFE_MAP and added P_SAFE as map flag set by an item with type SAFE_FLOOR (165)

File Contents

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