ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.37
Committed: Fri Apr 3 10:01:31 2009 UTC (15 years, 1 month ago) by elmex
Content type: text/plain
Branch: MAIN
CVS Tags: rel-2_80, rel-2_79, rel-2_78
Changes since 1.36: +14 -14 lines
Log Message:
fixed alchemy bug.

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 *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->name, prod_item->stats.sp);
457 }
458 #endif
459
460 return prod_item;
461 }
462
463
464 /**
465 * Ouch. We didnt get the formula we wanted.
466 * This fctn simulates the backfire effects--worse effects as the level
467 * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
468 * can happen to the would be alchemist. This table probably needs some
469 * adjustment for playbalance. -b.t.
470 */
471
472 void
473 alchemy_failure_effect (object *op, object *cauldron, recipe *rp, int danger)
474 {
475 int level = 0;
476
477 if (!op || !cauldron)
478 return;
479
480 if (danger > 1)
481 level = random_roll (1, danger, op, PREFER_LOW);
482
483 #ifdef ALCHEMY_DEBUG
484 LOG (llevDebug, "Alchemy_failure_effect(): using level=%d\n", level);
485 #endif
486
487 /* possible outcomes based on level */
488 if (level < 25)
489 { /* INGREDIENTS USED/SLAGGED */
490 object *item = NULL;
491
492 if (rndm (0, 2))
493 { /* slag created */
494 object *tmp = cauldron->inv;
495 int weight = 0;
496
497 tmp = get_archetype ("rock");
498 tmp->weight = weight;
499 tmp->value = 0;
500 tmp->materialname = "stone";
501 tmp->name = "slag";
502 tmp->name_pl = "slags";
503 item = insert_ob_in_ob (tmp, cauldron);
504 CLEAR_FLAG (tmp, FLAG_CAN_ROLL);
505 CLEAR_FLAG (tmp, FLAG_NO_PICK);
506 tmp->move_block = 0;
507 }
508
509 remove_contents (cauldron->inv, item);
510 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
511 return;
512 }
513 else if (level < 40)
514 { /* MAKE TAINTED ITEM */
515 object *tmp = NULL;
516
517 if (!rp)
518 if ((rp = get_random_recipe ((recipelist *) NULL)) == NULL)
519 return;
520
521 if ((tmp = attempt_recipe (op, cauldron, 1, rp, -1)))
522 {
523 if (!QUERY_FLAG (tmp, FLAG_CURSED)) /* curse it */
524 SET_FLAG (tmp, FLAG_CURSED);
525
526 /* the apply code for potions already deals with cursed
527 * potions, so any code here is basically ignored.
528 */
529 if (tmp->type == FOOD)
530 {
531 tmp->stats.hp = random_roll (0, 149, op, PREFER_LOW);
532 }
533 tmp->value = 0; /* unsaleable item */
534
535 /* change stats downward */
536 do
537 {
538 change_attr_value (&tmp->stats, rndm (0, 6), -1 * (rndm (1, 3)));
539 }
540 while (rndm (0, 2));
541 }
542 return;
543 }
544
545 if (level == 40)
546 { /* MAKE RANDOM RECIPE */
547 recipelist *fl;
548 int numb = numb_ob_inside (cauldron);
549
550 fl = get_formulalist (numb - 1); /* take a lower recipe list */
551 if (fl && (rp = get_random_recipe (fl)))
552 /* even though random, don't grant user any EXP for it */
553 (void) attempt_recipe (op, cauldron, 1, rp, -1);
554 else
555 alchemy_failure_effect (op, cauldron, rp, level - 1);
556 }
557 else if (level < 45)
558 { /* INFURIATE NPC's */
559 /* this is kind of kludgy I know... */
560 cauldron->enemy = op;
561 npc_call_help (cauldron);
562 cauldron->enemy = NULL;
563
564 alchemy_failure_effect (op, cauldron, rp, level - 5);
565 }
566 else if (level < 50)
567 { /* MINOR EXPLOSION/FIREBALL */
568 object *tmp;
569
570 remove_contents (cauldron->inv, NULL);
571 switch (rndm (0, 2))
572 {
573 case 0:
574 tmp = get_archetype ("bomb");
575 tmp->stats.dam = random_roll (1, level, op, PREFER_LOW);
576 tmp->stats.hp = random_roll (1, level, op, PREFER_LOW);
577 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s creates a bomb!", &cauldron->name);
578 break;
579
580 default:
581 tmp = get_archetype ("fireball");
582 tmp->stats.dam = random_roll (1, level, op, PREFER_LOW) / 5 + 1;
583 tmp->stats.hp = random_roll (1, level, op, PREFER_LOW) / 10 + 2;
584 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
585 break;
586 }
587
588 tmp->insert_at (cauldron);
589 }
590 else if (level < 60)
591 { /* CREATE MONSTER */
592 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
593 remove_contents (cauldron->inv, NULL);
594 }
595 else if (level < 80)
596 { /* MAJOR FIRE */
597 object *fb = get_archetype (SP_MED_FIREBALL);
598
599 remove_contents (cauldron->inv, NULL);
600 fire_arch_from_position (cauldron, cauldron, cauldron->x, cauldron->y, 0, fb);
601 fb->destroy ();
602 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s erupts in flame!", &cauldron->name);
603 }
604 else if (level < 100)
605 { /* WHAMMY the CAULDRON */
606 if (!QUERY_FLAG (cauldron, FLAG_CURSED))
607 SET_FLAG (cauldron, FLAG_CURSED);
608 else
609 cauldron->magic--;
610
611 cauldron->magic -= random_roll (0, 4, op, PREFER_LOW);
612
613 if (rndm (0, 1))
614 {
615 remove_contents (cauldron->inv, NULL);
616 new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s turns darker then makes a gulping sound!", &cauldron->name);
617 }
618 else
619 new_draw_info_format (NDI_UNIQUE, 0, op, "Your %s becomes darker.", &cauldron->name);
620 }
621 else if (level < 110)
622 { /* SUMMON EVIL MONSTERS */
623 object *tmp = get_random_mon (level / 5);
624
625 remove_contents (cauldron->inv, NULL);
626
627 if (!tmp)
628 alchemy_failure_effect (op, cauldron, rp, level);
629 else if (summon_hostile_monsters (cauldron, random_roll (1, 10, op, PREFER_LOW), tmp->arch->archname))
630 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s and then pours forth monsters!", &cauldron->name, cauldron_sound ());
631 }
632 else if (level < 150)
633 { /* COMBO EFFECT */
634 int roll = rndm (1, 3);
635
636 while (roll)
637 {
638 alchemy_failure_effect (op, cauldron, rp, level - 39);
639 roll--;
640 }
641 }
642 else if (level == 151)
643 { /* CREATE RANDOM ARTIFACT */
644 object *tmp;
645
646 /* this is meant to be better than prior possiblity,
647 * in this one, we allow *any* valid alchemy artifact
648 * to be made (rather than only those on the given
649 * formulalist) */
650 if (!rp)
651 rp = get_random_recipe ((recipelist *) NULL);
652
653 if (rp && (tmp = get_archetype (rp->arch_name [rndm (rp->arch_names)])))
654 {
655 generate_artifact (tmp, random_roll (1, op->level / 2 + 1, op, PREFER_HIGH) + 1);
656 if ((tmp = insert_ob_in_ob (tmp, cauldron)))
657 {
658 remove_contents (cauldron->inv, tmp);
659 new_draw_info_format (NDI_UNIQUE, 0, op, "The %s %s.", &cauldron->name, cauldron_sound ());
660 }
661 }
662 }
663 else
664 { /* MANA STORM - watch out!! */
665 object *tmp = get_archetype (LOOSE_MANA);
666
667 new_draw_info (NDI_UNIQUE, 0, op, "You unwisely release potent forces!");
668 remove_contents (cauldron->inv, NULL);
669 cast_magic_storm (op, tmp, level);
670 }
671 }
672
673 /*
674 * All but object "save_item" are elimentated from
675 * the container list. Note we have to becareful to remove the inventories
676 * of objects in the cauldron inventory (ex icecube has stuff in it).
677 */
678 void
679 remove_contents (object *first_ob, object *save_item)
680 {
681 // this cries for a cleaner rewrite, removing save_item first possibly
682 object *next, *tmp = first_ob;
683
684 while (tmp)
685 {
686 next = tmp->below;
687
688 if (tmp == save_item)
689 {
690 if (!(tmp = next))
691 break;
692 else
693 next = next->below;
694 }
695
696 if (tmp->inv)
697 remove_contents (tmp->inv, NULL);
698
699 tmp->destroy ();
700 tmp = next;
701 }
702 }
703
704 /**
705 *"Danger" level, will determine how bad the backfire
706 * could be if the user fails to concoct a recipe properly. Factors include
707 * the number of ingredients, the length of the name of each ingredient,
708 * the user's effective level, the user's Int and the enchantment on the
709 * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
710 * danger. Note that we assume that we have had the caster ready the alchemy
711 * skill *before* this routine is called. (no longer auto-readies that skill)
712 * -b.t.
713 */
714 int
715 calc_alch_danger (object *caster, object *cauldron, recipe *rp)
716 {
717 object *item;
718 char name[MAX_BUF];
719 int danger = 0, nrofi = 0;
720
721 /* Knowing alchemy skill reduces yer risk */
722 danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level;
723
724 if (!caster->chosen_skill)
725 LOG (llevError | logBacktrace, "calc_alch_danger called without a chosen skill, caster %s, cauldron %s\n",
726 caster->debug_desc (), cauldron->debug_desc ());
727
728 /* better cauldrons reduce risk */
729 danger -= cauldron->magic;
730
731 /* Higher Int, lower the risk */
732 danger -= 3 * (caster->stats.Int - 15);
733
734 /* Ingredients. Longer names usually mean rarer stuff.
735 * Thus the backfire is worse. Also, more ingredients
736 * means we are attempting a more powerfull potion,
737 * and thus the backfire will be worse. */
738 for (item = cauldron->inv; item; item = item->below)
739 {
740 assign (name, item->name);
741 if (item->title)
742 sprintf (name, "%s %s", &item->name, &item->title);
743 danger += (strtoint (name) / 1000) + 3;
744 nrofi++;
745 }
746
747 if (rp == NULL)
748 danger += 110;
749 else
750 danger += rp->diff * 3;
751
752 /* Using a bad device is *majorly* stupid */
753 if (QUERY_FLAG (cauldron, FLAG_CURSED))
754 danger += 80;
755 if (QUERY_FLAG (cauldron, FLAG_DAMNED))
756 danger += 200;
757
758 #ifdef ALCHEMY_DEBUG
759 LOG (llevDebug, "calc_alch_danger() returned danger=%d\n", danger);
760 #endif
761
762 return danger;
763 }
764
765 /**
766 * Determines if ingredients in a container match the
767 * proper ingredients for a recipe.
768 *
769 * rp is the recipe to check
770 * cauldron is the container that holds the ingredients
771 * returns 1 if the ingredients match the recipe, 0 if not
772 *
773 * This functions tries to find each defined ingredient in the container. It is
774 * the defined recipe iff
775 * - the number of ingredients of the recipe and in the container is equal
776 * - all ingredients of the recipe are found in the container
777 * - the number of batches is the same for all ingredients
778 */
779 static int
780 is_defined_recipe (const recipe *rp, const object *cauldron, object *caster)
781 {
782 uint32 batches_in_cauldron;
783 const linked_char *ingredient;
784 int number;
785 const object *ob;
786
787 /* check for matching number of ingredients */
788 number = 0;
789 for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
790 number++;
791 for (ob = cauldron->inv; ob != NULL; ob = ob->below)
792 number--;
793 if (number != 0)
794 return 0;
795
796 /* check for matching ingredients */
797 batches_in_cauldron = 0;
798 for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
799 {
800 uint32 nrof;
801 const char *name;
802 int ok;
803
804 /* determine and remove nrof from name */
805 name = ingredient->name;
806 nrof = 0;
807 while (isdigit (*name))
808 {
809 nrof = 10 * nrof + (*name - '0');
810 name++;
811 }
812 if (nrof == 0)
813 nrof = 1;
814 while (*name == ' ')
815 name++;
816
817 /* find the current ingredient in the cauldron */
818 ok = 0;
819 for (ob = cauldron->inv; ob != NULL; ob = ob->below)
820 {
821 char name_ob[MAX_BUF];
822 const char *name2;
823
824 if (!ob->title)
825 name2 = ob->name;
826 else
827 {
828 snprintf (name_ob, sizeof (name_ob), "%s %s", &ob->name, &ob->title);
829 name2 = name_ob;
830 }
831
832 if (strcmp (name2, name) == 0)
833 {
834 if (ob->nrof % nrof == 0)
835 {
836 uint32 batches;
837
838 batches = ob->nrof / nrof;
839 if (batches_in_cauldron == 0)
840 {
841 batches_in_cauldron = batches;
842 ok = 1;
843 }
844 else if (batches_in_cauldron == batches)
845 ok = 1;
846 }
847 break;
848 }
849 }
850 if (!ok)
851 return (0);
852 }
853
854 return (1);
855 }
856
857 /**
858 * Find a recipe from a recipe list that matches the given formula. If there
859 * is more than one matching recipe, it selects a random one. If at least one
860 * transmuting recipe matches, it only considers matching transmuting recipes.
861 *
862 * @return one matching recipe, or NULL if no recipe matches
863 */
864 static recipe *
865 find_recipe (recipelist * fl, int formula, object *ingredients)
866 {
867 recipe *rp;
868 recipe *result; /* winning recipe, or NULL if no recipe found */
869 int recipes_matching; /* total number of matching recipes so far */
870 int transmute_found; /* records whether a transmuting recipe was found so far */
871 size_t rp_arch_index;
872
873 #ifdef EXTREME_ALCHEMY_DEBUG
874 LOG (llevDebug, "looking for formula %d:\n", formula);
875 #endif
876 result = NULL;
877 recipes_matching = 0;
878 transmute_found = 0;
879
880 for (rp = fl->items; rp; rp = rp->next)
881 {
882 /* check if recipe matches at all */
883 if (formula % rp->index != 0)
884 {
885 #ifdef EXTREME_ALCHEMY_DEBUG
886 LOG (llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
887 #endif
888 continue;
889 }
890
891 if (rp->transmute && find_transmution_ob (ingredients, rp, &rp_arch_index, 0) != NULL)
892 {
893 #ifdef EXTREME_ALCHEMY_DEBUG
894 LOG (llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], rp->title, rp->index);
895 #endif
896 /* transmution recipe with matching base ingredient */
897 if (!transmute_found)
898 {
899 transmute_found = 1;
900 recipes_matching = 0;
901 }
902 }
903 else if (transmute_found)
904 {
905 #ifdef EXTREME_ALCHEMY_DEBUG
906 LOG (llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], rp->title,
907 rp->index);
908 #endif
909 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
910 continue;
911 }
912 #ifdef EXTREME_ALCHEMY_DEBUG
913 else
914 {
915 LOG (llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
916 }
917 #endif
918
919 if (rndm (0, recipes_matching) == 0)
920 result = rp;
921
922 recipes_matching++;
923 }
924
925 if (result == NULL)
926 {
927 #ifdef ALCHEMY_DEBUG
928 LOG (llevDebug, "couldn't find formula for ingredients.\n");
929 #endif
930 return NULL;
931 }
932
933 #ifdef ALCHEMY_DEBUG
934 if (strcmp (result->title, "NONE") != 0)
935 LOG (llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula / result->index);
936 else
937 LOG (llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula / result->index);
938 #endif
939 return result;
940 }