ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.20
Committed: Mon Feb 5 02:07:40 2007 UTC (17 years, 4 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.19: +4 -3 lines
Log Message:
remove many strcmps on shstr, added fast strcmp wrapper that only etsts for inequality

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