ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.19
Committed: Mon Feb 5 01:24:45 2007 UTC (17 years, 3 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.18: +2 -2 lines
Log Message:
replace amny strcpy by checked assign's

File Contents

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