ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.8
Committed: Thu Sep 14 22:34:03 2006 UTC (17 years, 9 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.7: +1 -7 lines
Log Message:
indent

File Contents

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