ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.38
Committed: Fri Oct 9 22:46:39 2009 UTC (14 years, 8 months ago) by sf-marcmagus
Content type: text/plain
Branch: MAIN
Changes since 1.37: +17 -9 lines
Log Message:
Fix alchemy debug logging to support shstrs.

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