ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.11
Committed: Mon Dec 11 19:46:46 2006 UTC (17 years, 6 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.10: +0 -6 lines
Log Message:
removed #ifn?def WIN32 from all files

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