ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.22
Committed: Mon Apr 16 06:23:42 2007 UTC (17 years, 1 month ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.21: +2 -1 lines
Log Message:
VERY EXPERIMENTAL

- change the way archetypes and treasurelists are being loaded:
  - referring to a nonexisting treasurelist will create an empty one
  - referring to a nonexisting archetype will create an empty one
  - archetypes/treasurelists will overwrite any existing object
    of the same name.

- net effect should be to allow reloading of archetypes and treasurelists
  at runtime at a later stage.

File Contents

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