ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.1
Committed: Sun Aug 13 17:16:03 2006 UTC (17 years, 9 months ago) by elmex
Content type: text/plain
Branch: MAIN
Log Message:
Made server compile with C++.
Removed cfanim plugin and crossedit.
C++ here we come.

File Contents

# User Rev Content
1 elmex 1.1 /*
2     * static char *rcsid_alchemy_c =
3     * "$Id$";
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 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     /* 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+= (int) (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     /* Recalc carrying of the cauldron, in case recipe did not conserve mass */
306     sum_weight(cauldron);
307     /* adj lvl, nrof on caster level */
308     adjust_product(item, ability, rp->yield?(rp->yield*batches):batches);
309     if(!item->env && (item=insert_ob_in_ob(item,cauldron)) == NULL) {
310     new_draw_info(NDI_UNIQUE, 0,caster,"Nothing happened.");
311     /* new_draw_info_format(NDI_UNIQUE, 0,caster,
312     "Your spell causes the %s to explode!",cauldron->name); */
313     /* kaboom_cauldron(); */
314     } else {
315     new_draw_info_format(NDI_UNIQUE, 0,caster,
316     "The %s %s.",cauldron->name,cauldron_sound());
317     }
318     }
319     return item;
320     }
321    
322    
323    
324     /**
325     * We adjust the nrof, exp and level of the final product, based
326     * on the item's default parameters, and the relevant caster skill level.
327     */
328    
329     void adjust_product(object *item, int lvl, int yield) {
330     int nrof=1;
331    
332     if (!yield)
333     yield = 1;
334     if (lvl<=0)
335     lvl = 1; /* lets avoid div by zero! */
336     if (item->nrof) {
337     nrof = (int) (
338     (1.0 - 1.0/(lvl/10.0 + 1.0)) *
339     (rndm(0, yield-1) + rndm(0, yield-1) + rndm(0, yield-1)) + 1
340     );
341     if (nrof > yield)
342     nrof = yield;
343     item->nrof=nrof;
344     }
345     }
346    
347    
348     /**
349     * Using a list of items and a recipe to make an artifact.
350     *
351     * @param cauldron the cauldron (including the ingredients) used to make the item
352     *
353     * @param rp the recipe to make the artifact from
354     *
355     * @return the newly created object, NULL if something failed
356     */
357    
358     object * make_item_from_recipe(object *cauldron, recipe *rp) {
359     artifact *art=NULL;
360     object *item=NULL;
361     size_t rp_arch_index;
362    
363     if(rp==NULL) return (object *) NULL;
364    
365     /* Find the appropriate object to transform...*/
366     if((item=find_transmution_ob(cauldron->inv, rp, &rp_arch_index, 1))==NULL) {
367     LOG(llevDebug,"make_alchemy_item(): failed to create alchemical object.\n");
368     return (object *) NULL;
369     }
370    
371     /* Find the appropriate artifact template...*/
372     if(strcmp(rp->title,"NONE")) {
373     if((art=locate_recipe_artifact(rp, rp_arch_index))==NULL) {
374     LOG(llevError,"make_alchemy_item(): failed to locate recipe artifact.\n");
375     LOG(llevDebug," --requested recipe: %s of %s.\n",rp->arch_name[0],rp->title);
376     return (object *) NULL;
377     }
378     transmute_materialname(item, art->item);
379     give_artifact_abilities(item, art->item);
380     }
381    
382     if(QUERY_FLAG(cauldron,FLAG_CURSED)) SET_FLAG(item,FLAG_CURSED);
383     if(QUERY_FLAG(cauldron,FLAG_DAMNED)) SET_FLAG(item,FLAG_DAMNED);
384    
385     return item;
386     }
387    
388    
389     /**
390     * Looks through the ingredient list. If we find a
391     * suitable object in it - we will use that to make the requested artifact.
392     * Otherwise the code returns a 'generic' item if create_item is set. -b.t.
393     *
394     * @param rp_arch_index pointer to return value; set to arch index for recipe;
395     * set to zero if not using a transmution formula
396     */
397    
398     object * find_transmution_ob ( object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item) {
399     object *item=NULL;
400    
401     *rp_arch_index = 0;
402    
403     if(rp->transmute) /* look for matching ingredient/prod archs */
404     for(item=first_ingred;item;item=item->below) {
405     size_t i;
406    
407     for (i = 0; i < rp->arch_names; i++) {
408     if(strcmp(item->arch->name, rp->arch_name[i]) == 0) {
409     *rp_arch_index = i;
410     break;
411     }
412     }
413     if (i < rp->arch_names)
414     break;
415     }
416    
417     /* failed, create a fresh object. Note no nrof>1 because that would
418     * allow players to create massive amounts of artifacts easily */
419     if(create_item && (!item || item->nrof > 1)) {
420     *rp_arch_index = RANDOM()%rp->arch_names;
421     item = get_archetype(rp->arch_name[*rp_arch_index]);
422     }
423    
424     #ifdef ALCHEMY_DEBUG
425     LOG(llevDebug,"recipe calls for%stransmution.\n",rp->transmute?" ":" no ");
426     if (item != NULL) {
427     LOG(llevDebug," find_transmutable_ob(): returns arch %s(sp:%d)\n",
428     item->arch->name,item->stats.sp);
429     }
430     #endif
431    
432     return item;
433     }
434    
435    
436     /**
437     * Ouch. We didnt get the formula we wanted.
438     * This fctn simulates the backfire effects--worse effects as the level
439     * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
440     * can happen to the would be alchemist. This table probably needs some
441     * adjustment for playbalance. -b.t.
442     */
443    
444     void alchemy_failure_effect(object *op,object *cauldron,recipe *rp,int danger) {
445     int level=0;
446    
447     if(!op || !cauldron) return;
448    
449     if(danger>1) level=random_roll(1, danger, op, PREFER_LOW);
450    
451     #ifdef ALCHEMY_DEBUG
452     LOG(llevDebug,"Alchemy_failure_effect(): using level=%d\n",level);
453     #endif
454    
455     /* possible outcomes based on level */
456     if(level<25) { /* INGREDIENTS USED/SLAGGED */
457     object *item=NULL;
458    
459     if(rndm(0, 2)) { /* slag created */
460     object *tmp=cauldron->inv;
461     int weight=0;
462     uint16 material=M_STONE;
463    
464     while(tmp) { /* slag has coadded ingredient properties */
465     weight+=tmp->weight;
466     if(!(material&tmp->material))
467     material |= tmp->material;
468     tmp=tmp->below;
469     }
470     tmp = get_archetype("rock");
471     tmp->weight=weight;
472     tmp->value=0;
473     tmp->material=material;
474     tmp->materialname = add_string("stone");
475     free_string(tmp->name);
476     tmp->name=add_string("slag");
477     if (tmp->name_pl) free_string(tmp->name_pl);
478     tmp->name_pl=add_string("slags");
479     item=insert_ob_in_ob(tmp,cauldron);
480     CLEAR_FLAG(tmp,FLAG_CAN_ROLL);
481     CLEAR_FLAG(tmp,FLAG_NO_PICK);
482     tmp->move_block = 0;
483     }
484     remove_contents(cauldron->inv,item);
485     new_draw_info_format(NDI_UNIQUE,0,op,
486     "The %s %s.",cauldron->name,cauldron_sound());
487     return;
488     } else if (level< 40) { /* MAKE TAINTED ITEM */
489     object *tmp=NULL;
490    
491     if (!rp)
492     if((rp=get_random_recipe((recipelist *) NULL))==NULL)
493     return;
494    
495     if((tmp=attempt_recipe(op,cauldron,1,rp,-1))) {
496     if(!QUERY_FLAG(tmp,FLAG_CURSED)) /* curse it */
497     SET_FLAG(tmp,FLAG_CURSED);
498    
499     /* the apply code for potions already deals with cursed
500     * potions, so any code here is basically ignored.
501     */
502     if(tmp->type==FOOD) {
503     tmp->stats.hp=random_roll(0, 149, op, PREFER_LOW);
504     }
505     tmp->value = 0; /* unsaleable item */
506    
507     /* change stats downward */
508     do {
509     change_attr_value(&tmp->stats,rndm(0, 6),-1*(rndm(1, 3)));
510     } while (rndm(0, 2));
511     }
512     return;
513     } if(level==40) { /* MAKE RANDOM RECIPE */
514     recipelist *fl;
515     int numb=numb_ob_inside(cauldron);
516    
517     fl=get_formulalist(numb-1); /* take a lower recipe list */
518     if(fl &&(rp=get_random_recipe(fl)))
519     /* even though random, don't grant user any EXP for it */
520     (void) attempt_recipe(op,cauldron,1,rp,-1);
521     else
522     alchemy_failure_effect(op,cauldron,rp,level-1);
523     return;
524    
525     } else if (level<45) { /* INFURIATE NPC's */
526     /* this is kind of kludgy I know...*/
527     cauldron->enemy=op;
528     npc_call_help(cauldron);
529     cauldron->enemy=NULL;
530    
531     alchemy_failure_effect(op,cauldron,rp,level-5);
532     return;
533     } else if (level<50) { /* MINOR EXPLOSION/FIREBALL */
534     object *tmp;
535     remove_contents(cauldron->inv,NULL);
536     switch(rndm(0, 2)) {
537     case 0:
538     tmp=get_archetype("bomb");
539     tmp->stats.dam=random_roll(1, level, op, PREFER_LOW);
540     tmp->stats.hp=random_roll(1, level, op, PREFER_LOW);
541     new_draw_info_format(NDI_UNIQUE,0,op,"The %s creates a bomb!",
542     cauldron->name);
543     break;
544    
545     default:
546     tmp=get_archetype("fireball");
547     tmp->stats.dam=random_roll(1, level, op, PREFER_LOW)/5+1;
548     tmp->stats.hp=random_roll(1, level, op, PREFER_LOW)/10+2;
549     new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
550     cauldron->name);
551     break;
552     }
553     tmp->x=cauldron->x,tmp->y=cauldron->y;
554     insert_ob_in_map(tmp,op->map,NULL,0);
555     return;
556    
557     } else if (level<60) { /* CREATE MONSTER */
558     new_draw_info_format(NDI_UNIQUE,0,op,
559     "The %s %s.",cauldron->name,cauldron_sound());
560     remove_contents(cauldron->inv,NULL);
561     return;
562     } else if (level<80) { /* MAJOR FIRE */
563     object *fb = get_archetype(SP_MED_FIREBALL);
564     remove_contents(cauldron->inv,NULL);
565     fire_arch_from_position(cauldron, cauldron,cauldron->x, cauldron->y,
566     0, fb);
567     free_object(fb);
568     new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
569     cauldron->name);
570     return;
571    
572     } else if (level<100) { /* WHAMMY the CAULDRON */
573     if(!QUERY_FLAG(cauldron,FLAG_CURSED))
574     SET_FLAG(cauldron,FLAG_CURSED);
575     else cauldron->magic--;
576     cauldron->magic -= random_roll(0, 4, op, PREFER_LOW);
577     if(rndm(0, 1)) {
578     remove_contents(cauldron->inv,NULL);
579     new_draw_info_format(NDI_UNIQUE,0,op,
580     "Your %s turns darker then makes a gulping sound!",
581     cauldron->name);
582     } else
583     new_draw_info_format(NDI_UNIQUE,0,op,
584     "Your %s becomes darker.",cauldron->name);
585     return;
586    
587     } else if (level<110) { /* SUMMON EVIL MONSTERS */
588     object *tmp=get_random_mon(level/5);
589    
590     remove_contents(cauldron->inv,NULL);
591     if(!tmp)
592     alchemy_failure_effect(op,cauldron,rp,level);
593     else if(summon_hostile_monsters(cauldron, random_roll(1, 10, op, PREFER_LOW), tmp->arch->name))
594     new_draw_info_format(NDI_UNIQUE, 0,op,
595     "The %s %s and then pours forth monsters!",
596     cauldron->name,cauldron_sound());
597     return;
598    
599     } else if (level<150) { /* COMBO EFFECT */
600     int roll = rndm(1, 3);
601     while(roll) {
602     alchemy_failure_effect(op,cauldron,rp,level-39);
603     roll--;
604     }
605     return;
606     } else if (level==151) { /* CREATE RANDOM ARTIFACT */
607     object *tmp;
608     /* this is meant to be better than prior possiblity,
609     * in this one, we allow *any* valid alchemy artifact
610     * to be made (rather than only those on the given
611     * formulalist) */
612     if(!rp) rp=get_random_recipe((recipelist *) NULL);
613     if(rp && (tmp=get_archetype(rp->arch_name[RANDOM()%rp->arch_names]))) {
614     generate_artifact(tmp,random_roll(1, op->level/2+1, op, PREFER_HIGH)+1);
615     if((tmp=insert_ob_in_ob(tmp,cauldron))) {
616     remove_contents(cauldron->inv,tmp);
617     new_draw_info_format(NDI_UNIQUE, 0,op,
618     "The %s %s.",cauldron->name,cauldron_sound());
619     }
620     }
621     return;
622     } else { /* MANA STORM - watch out!! */
623     object *tmp = get_archetype(LOOSE_MANA);
624     new_draw_info(NDI_UNIQUE,0,op,"You unwisely release potent forces!");
625     remove_contents (cauldron->inv,NULL);
626     cast_magic_storm(op,tmp, level);
627     return;
628     }
629     }
630    
631    
632     /**
633     * All but object "save_item" are elimentated from
634     * the container list. Note we have to becareful to remove the inventories
635     * of objects in the cauldron inventory (ex icecube has stuff in it).
636     */
637    
638     void remove_contents (object *first_ob, object *save_item) {
639     object *next,*tmp=first_ob;
640    
641     while(tmp) {
642     next = tmp->below;
643     if(tmp==save_item) {
644     if(!(tmp=next)) break;
645     else next=next->below;
646     }
647     if(tmp->inv) remove_contents(tmp->inv,NULL);
648     remove_ob(tmp);
649     free_object(tmp);
650     tmp=next;
651     }
652     }
653    
654     /**
655     *"Danger" level, will determine how bad the backfire
656     * could be if the user fails to concoct a recipe properly. Factors include
657     * the number of ingredients, the length of the name of each ingredient,
658     * the user's effective level, the user's Int and the enchantment on the
659     * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
660     * danger. Note that we assume that we have had the caster ready the alchemy
661     * skill *before* this routine is called. (no longer auto-readies that skill)
662     * -b.t.
663     */
664    
665     int calc_alch_danger(object *caster,object *cauldron, recipe *rp) {
666     object *item;
667     char name[MAX_BUF];
668     int danger=0,nrofi=0;
669    
670     /* Knowing alchemy skill reduces yer risk */
671     danger -= caster->chosen_skill?caster->chosen_skill->level:caster->level;
672    
673     /* better cauldrons reduce risk */
674     danger -= cauldron->magic;
675    
676     /* Higher Int, lower the risk */
677     danger -= 3 * (caster->stats.Int - 15);
678    
679     /* Ingredients. Longer names usually mean rarer stuff.
680     * Thus the backfire is worse. Also, more ingredients
681     * means we are attempting a more powerfull potion,
682     * and thus the backfire will be worse. */
683     for(item=cauldron->inv;item;item=item->below) {
684     strcpy(name,item->name);
685     if(item->title) sprintf(name,"%s %s",item->name,item->title);
686     danger += (strtoint(name)/1000) + 3;
687     nrofi++;
688     }
689     if (rp == NULL)
690     danger += 110;
691     else
692     danger += rp->diff*3;
693    
694     /* Using a bad device is *majorly* stupid */
695     if(QUERY_FLAG(cauldron,FLAG_CURSED)) danger +=80;
696     if(QUERY_FLAG(cauldron,FLAG_DAMNED)) danger +=200;
697    
698     #ifdef ALCHEMY_DEBUG
699     LOG(llevDebug,"calc_alch_danger() returned danger=%d\n",danger);
700     #endif
701    
702     return danger;
703     }
704    
705     /**
706     * Determines if ingredients in a container match the
707     * proper ingredients for a recipe.
708     *
709     * rp is the recipe to check
710     * cauldron is the container that holds the ingredients
711     * returns 1 if the ingredients match the recipe, 0 if not
712     *
713     * This functions tries to find each defined ingredient in the container. It is
714     * the defined recipe iff
715     * - the number of ingredients of the recipe and in the container is equal
716     * - all ingredients of the recipe are found in the container
717     * - the number of batches is the same for all ingredients
718     */
719     static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster)
720     {
721     uint32 batches_in_cauldron;
722     const linked_char *ingredient;
723     int number;
724     const object *ob;
725    
726     /* check for matching number of ingredients */
727     number = 0;
728     for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
729     number++;
730     for(ob = cauldron->inv; ob != NULL; ob = ob->below)
731     number--;
732     if(number != 0)
733     return 0;
734    
735     /* check for matching ingredients */
736     batches_in_cauldron = 0;
737     for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) {
738     uint32 nrof;
739     const char *name;
740     int ok;
741    
742     /* determine and remove nrof from name */
743     name = ingredient->name;
744     nrof = 0;
745     while(isdigit(*name)) {
746     nrof = 10*nrof+(*name-'0');
747     name++;
748     }
749     if(nrof == 0)
750     nrof = 1;
751     while(*name == ' ')
752     name++;
753    
754     /* find the current ingredient in the cauldron */
755     ok = 0;
756     for(ob = cauldron->inv; ob != NULL; ob = ob->below) {
757     char name_ob[MAX_BUF];
758     const char *name2;
759    
760     if(ob->title == NULL)
761     name2 = ob->name;
762     else {
763     snprintf(name_ob, sizeof(name_ob), "%s %s", ob->name, ob->title);
764     name2 = name_ob;
765     }
766    
767     if(strcmp(name2, name) == 0) {
768     if(ob->nrof%nrof == 0) {
769     uint32 batches;
770    
771     batches = ob->nrof/nrof;
772     if(batches_in_cauldron == 0) {
773     batches_in_cauldron = batches;
774     ok = 1;
775     } else if(batches_in_cauldron == batches)
776     ok = 1;
777     }
778     break;
779     }
780     }
781     if(!ok)
782     return(0);
783     }
784    
785     return(1);
786     }
787    
788     /**
789     * Find a recipe from a recipe list that matches the given formula. If there
790     * is more than one matching recipe, it selects a random one. If at least one
791     * transmuting recipe matches, it only considers matching transmuting recipes.
792     *
793     * @return one matching recipe, or NULL if no recipe matches
794     */
795     static recipe *find_recipe(recipelist *fl, int formula, object *ingredients)
796     {
797     recipe *rp;
798     recipe *result; /* winning recipe, or NULL if no recipe found */
799     int recipes_matching; /* total number of matching recipes so far */
800     int transmute_found; /* records whether a transmuting recipe was found so far */
801     size_t rp_arch_index;
802    
803     #ifdef EXTREME_ALCHEMY_DEBUG
804     LOG(llevDebug, "looking for formula %d:\n", formula);
805     #endif
806     result = NULL;
807     recipes_matching = 0;
808     transmute_found = 0;
809     for (rp = fl->items; rp != NULL; rp = rp->next) {
810     /* check if recipe matches at all */
811     if (formula%rp->index != 0) {
812     #ifdef EXTREME_ALCHEMY_DEBUG
813     LOG(llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
814     #endif
815     continue;
816     }
817    
818     if (rp->transmute && find_transmution_ob(ingredients, rp, &rp_arch_index, 0) != NULL) {
819     #ifdef EXTREME_ALCHEMY_DEBUG
820     LOG(llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], rp->title, rp->index);
821     #endif
822     /* transmution recipe with matching base ingredient */
823     if (!transmute_found) {
824     transmute_found = 1;
825     recipes_matching = 0;
826     }
827     } else if (transmute_found) {
828     #ifdef EXTREME_ALCHEMY_DEBUG
829     LOG(llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], rp->title, rp->index);
830     #endif
831     /* "normal" recipe found after previous transmution recipe => ignore this recipe */
832     continue;
833     }
834     #ifdef EXTREME_ALCHEMY_DEBUG
835     else {
836     LOG(llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
837     }
838     #endif
839    
840     if (rndm(0, recipes_matching) == 0)
841     result = rp;
842    
843     recipes_matching++;
844     }
845    
846     if (result == NULL) {
847     #ifdef ALCHEMY_DEBUG
848     LOG(llevDebug, "couldn't find formula for ingredients.\n");
849     #endif
850     return NULL;
851     }
852    
853     #ifdef ALCHEMY_DEBUG
854     if(strcmp(result->title, "NONE") != 0)
855     LOG(llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula/result->index);
856     else
857     LOG(llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula/result->index);
858     #endif
859     return result;
860     }