ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.c
Revision: 1.1.1.2 (vendor branch)
Committed: Wed Feb 22 18:03:17 2006 UTC (18 years, 3 months ago) by elmex
Content type: text/plain
Branch: UPSTREAM
CVS Tags: UPSTREAM_2006_03_15, UPSTREAM_2006_02_22
Changes since 1.1.1.1: +3 -3 lines
Log Message:
cvs -z7 -d:ext:elmex@cvs.schmorp.de:/schmorpforge import cf.schmorp.de UPSTREAM UPSTREAM_2006_02_22

File Contents

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