ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.9
Committed: Fri Sep 15 23:11:57 2006 UTC (17 years, 8 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.8: +3 -7 lines
Log Message:
dumber bug than ever imagined

File Contents

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