ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.56
Committed: Sat Apr 16 02:35:53 2011 UTC (13 years, 1 month ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.55: +3 -2 lines
Log Message:
*** empty log message ***

File Contents

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