ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.5
Committed: Sun Sep 3 00:18:42 2006 UTC (17 years, 9 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.4: +18 -20 lines
Log Message:
THIS CODE WILL NOT COMPILE
use the STABLE tag instead.

- major changes in object lifetime and memory management
- replaced manual refcounting by shstr class
- removed quest system
- many optimisations
- major changes

File Contents

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