ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.10
Committed: Mon Dec 11 02:54:57 2006 UTC (17 years, 6 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.9: +5 -0 lines
Log Message:
- rename $uptime to $UPTIME
- hopefully force alchemy to use one second delay

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