ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.33
Committed: Sat Dec 27 02:31:19 2008 UTC (15 years, 5 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_74
Changes since 1.32: +3 -2 lines
Log Message:
implement smell member for mapspaces, remove all traces of the old RANDOM macro

File Contents

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