ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.16
Committed: Sat Jan 6 14:42:30 2007 UTC (17 years, 5 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.15: +1 -0 lines
Log Message:
added some copyrights

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