ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.c
Revision: 1.4
Committed: Sun Aug 13 17:16:03 2006 UTC (17 years, 9 months ago) by elmex
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +0 -0 lines
State: FILE REMOVED
Log Message:
Made server compile with C++.
Removed cfanim plugin and crossedit.
C++ here we come.

File Contents

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