ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.6
Committed: Sun Sep 3 14:33:45 2006 UTC (17 years, 9 months ago) by elmex
Content type: text/plain
Branch: MAIN
Changes since 1.5: +10 -8 lines
Log Message:
removed ox/oy, and then added shstr stuff to the recipes in alchemy

File Contents

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