ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.15
Committed: Tue Dec 26 20:04:09 2006 UTC (17 years, 5 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.14: +14 -14 lines
Log Message:
- added maptile->insert and object->insert_at methods that might
  make code using it clearer.
- replaced some insert_ob_in_map calls.

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