ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.7
Committed: Sun Sep 10 15:59:57 2006 UTC (17 years, 9 months ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.6: +725 -613 lines
Log Message:
indent

File Contents

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