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

# Content
1 /*
2 * static char *rcsid_alchemy_c =
3 * "$Id: alchemy.C,v 1.4 2006-08-29 08:01:37 root 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 (get_map_flags(caster->map, NULL, caster->x, caster->y, NULL, NULL) & P_SAFE)
116 {
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 /* 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 rp = find_recipe(fl, formula, cauldron->inv);
130 if (rp != NULL) {
131 #ifdef ALCHEMY_DEBUG
132 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
186 #ifdef ALCHEMY_DEBUG
187 LOG(llevDebug, "percent success chance = %f ab%d / diff%d*lev%d\n",
188 success_chance, ability, rp->diff, item->level);
189 #endif
190
191 value_item = query_cost(item, NULL, F_TRUE|F_IDENTIFIED|F_NOT_CURSED);
192 if(attempt_shadow_alchemy && value_item > value_ingredients) {
193 #ifdef ALCHEMY_DEBUG
194 #ifndef WIN32
195 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 #else
197 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 #endif
199 #endif
200 }
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 }
209 /* if we get here, we failed!! */
210 alchemy_failure_effect(caster, cauldron, rp,
211 calc_alch_danger(caster, cauldron, rp));
212 }
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 tval=0;
227 strcpy(name, tmp->name);
228 if (tmp->title)
229 sprintf (name, "%s %s", &tmp->name, &tmp->title);
230 tval = (strtoint(name) * (tmp->nrof?tmp->nrof:1));
231 #ifdef ALCHEMY_DEBUG
232 LOG(llevDebug,"Got ingredient %d %s(%d)\n", tmp->nrof?tmp->nrof:1,
233 name, tval);
234 #endif
235 formula += tval;
236 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 o_number++;
256 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 new_draw_info(NDI_UNIQUE, 0, caster, "You are not using the proper"
281 " facilities for this formula.");
282 return 0;
283 }
284
285 skop = find_skill_by_name(caster, rp->skill);
286 /* does the caster have the skill? */
287 if (!skop)
288 return 0;
289
290 /* code required for this recipe, search the caster */
291 if(rp->keycode) {
292 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 }
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 rp->title?rp->title:"unknown");
309 #endif
310
311 if((item=make_item_from_recipe(cauldron, rp))!=NULL) {
312 remove_contents(cauldron->inv, item);
313 /* Recalc carrying of the cauldron, in case recipe did not conserve mass */
314 sum_weight(cauldron);
315 /* adj lvl, nrof on caster level */
316 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 "Your spell causes the %s to explode!",&cauldron->name); */
321 /* kaboom_cauldron(); */
322 } else {
323 new_draw_info_format(NDI_UNIQUE, 0,caster,
324 "The %s %s.", &cauldron->name,cauldron_sound());
325 }
326 }
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 yield = 1;
342 if (lvl<=0)
343 lvl = 1; /* lets avoid div by zero! */
344 if (item->nrof) {
345 nrof = (int) (
346 (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 if (nrof > yield)
350 nrof = yield;
351 item->nrof=nrof;
352 }
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 }
386 transmute_materialname(item, art->item);
387 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 object *item=NULL;
466
467 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 tmp->materialname = "stone";
483 tmp->name="slag";
484 tmp->name_pl="slags";
485 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 "The %s %s.", &cauldron->name,cauldron_sound());
493 return;
494 } else if (level< 40) { /* MAKE TAINTED ITEM */
495 object *tmp=NULL;
496
497 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
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 }
518 return;
519 } if(level==40) { /* MAKE RANDOM RECIPE */
520 recipelist *fl;
521 int numb=numb_ob_inside(cauldron);
522
523 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
531 } else if (level<45) { /* INFURIATE NPC's */
532 /* this is kind of kludgy I know...*/
533 cauldron->enemy=op;
534 npc_call_help(cauldron);
535 cauldron->enemy=NULL;
536
537 alchemy_failure_effect(op,cauldron,rp,level-5);
538 return;
539 } else if (level<50) { /* MINOR EXPLOSION/FIREBALL */
540 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 &cauldron->name);
549 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 &cauldron->name);
557 break;
558 }
559 tmp->x=cauldron->x,tmp->y=cauldron->y;
560 insert_ob_in_map(tmp,op->map,NULL,0);
561 return;
562
563 } else if (level<60) { /* CREATE MONSTER */
564 new_draw_info_format(NDI_UNIQUE,0,op,
565 "The %s %s.",&cauldron->name,cauldron_sound());
566 remove_contents(cauldron->inv,NULL);
567 return;
568 } else if (level<80) { /* MAJOR FIRE */
569 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 &cauldron->name);
576 return;
577
578 } else if (level<100) { /* WHAMMY the CAULDRON */
579 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 &cauldron->name);
588 } else
589 new_draw_info_format(NDI_UNIQUE,0,op,
590 "Your %s becomes darker.",&cauldron->name);
591 return;
592
593 } else if (level<110) { /* SUMMON EVIL MONSTERS */
594 object *tmp=get_random_mon(level/5);
595
596 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 &cauldron->name,cauldron_sound());
603 return;
604
605 } else if (level<150) { /* COMBO EFFECT */
606 int roll = rndm(1, 3);
607 while(roll) {
608 alchemy_failure_effect(op,cauldron,rp,level-39);
609 roll--;
610 }
611 return;
612 } else if (level==151) { /* CREATE RANDOM ARTIFACT */
613 object *tmp;
614 /* this is meant to be better than prior possiblity,
615 * in this one, we allow *any* valid alchemy artifact
616 * to be made (rather than only those on the given
617 * 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 "The %s %s.",&cauldron->name,cauldron_sound());
625 }
626 }
627 return;
628 } else { /* MANA STORM - watch out!! */
629 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 }
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 }
653 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 if(item->title) sprintf(name,"%s %s", &item->name, &item->title);
692 danger += (strtoint(name)/1000) + 3;
693 nrofi++;
694 }
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 number++;
736 for(ob = cauldron->inv; ob != NULL; ob = ob->below)
737 number--;
738 if(number != 0)
739 return 0;
740
741 /* check for matching ingredients */
742 batches_in_cauldron = 0;
743 for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) {
744 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 snprintf(name_ob, sizeof(name_ob), "%s %s", &ob->name, &ob->title);
770 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 }
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 }