ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.42
Committed: Fri Oct 16 16:29:47 2009 UTC (14 years, 7 months ago) by sf-marcmagus
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_82
Changes since 1.41: +1 -1 lines
Log Message:
Make slag no_drop so it can't be dumped out of cauldrons.
Fix bug where no_drop items can be transferred between containers.

File Contents

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