ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.c
Revision: 1.1
Committed: Fri Feb 3 07:14:24 2006 UTC (18 years, 4 months ago) by root
Content type: text/plain
Branch: MAIN
Branch point for: UPSTREAM
Log Message:
Initial revision

File Contents

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