ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.67
Committed: Tue May 10 11:02:56 2022 UTC (2 years ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.66: +1 -4 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 /*
2 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 *
4 * Copyright (©) 2017,2018 Marc Alexander Lehmann / the Deliantra team
5 * Copyright (©) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
6 * Copyright (©) 2002 Mark Wedel & Crossfire Development Team
7 * Copyright (©) 1992 Frank Tore Johansen
8 *
9 * Deliantra is free software: you can redistribute it and/or modify it under
10 * the terms of the Affero GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the Affero GNU General Public License
20 * and the GNU General Public License along with this program. If not, see
21 * <http://www.gnu.org/licenses/>.
22 *
23 * The authors can be reached via e-mail to <support@deliantra.net>
24 */
25
26 /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
27
28 #include <global.h>
29 #include <object.h>
30 #include <sproto.h>
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 static int is_defined_recipe (const recipe *rp, const object *cauldron, object *caster);
63 static recipe *find_recipe (recipelist * fl, int formula, object *ingredients);
64
65 /** Returns a random selection from cauldron_effect[] */
66 static const char *
67 cauldron_sound ()
68 {
69 return cauldron_effect [rndm (ecb_array_length (cauldron_effect))];
70 }
71
72 /**
73 * Recipe value of the entire contents of a container.
74 * This appears to just generate a hash value, which I guess for now works
75 * ok, but the possibility of duplicate hashes is certainly possible - msw
76 */
77 static int
78 content_recipe_value (object *op)
79 {
80 object *tmp = op->inv;
81 int formula = 0;
82
83 while (tmp)
84 {
85 const char *name = tmp->title
86 ? (const char *)format ("%s %s", &tmp->name, &tmp->title)
87 : tmp->name;
88
89 int tval = strtoint (name) * tmp->number_of ();
90 #ifdef ALCHEMY_DEBUG
91 LOG (llevDebug, "Got ingredient %d %s(%d)\n", tmp->nrof ? tmp->nrof : 1, name, tval);
92 #endif
93 formula += tval;
94 tmp = tmp->below;
95 }
96 #ifdef ALCHEMY_DEBUG
97 LOG (llevDebug, " Formula value=%d\n", formula);
98 #endif
99
100 return formula;
101 }
102
103 /**
104 * Returns total number of items in op
105 */
106 static int
107 numb_ob_inside (object *op)
108 {
109 object *tmp = op->inv;
110 int number = 0, o_number = 0;
111
112 while (tmp)
113 {
114 if (tmp->nrof)
115 number += tmp->nrof;
116 else
117 number++;
118 o_number++;
119 tmp = tmp->below;
120 }
121 #ifdef ALCHEMY_DEBUG
122 LOG (llevDebug, "numb_ob_inside(%s): found %d ingredients\n", &op->name, o_number);
123 #endif
124 return o_number;
125 }
126
127 /**
128 * Looks through the ingredient list. If we find a
129 * suitable object in it - we will use that to make the requested artifact.
130 * Otherwise the code returns a 'generic' item if create_item is set. -b.t.
131 *
132 * @param rp_arch_index pointer to return value; set to arch index for recipe;
133 * set to zero if not using a transmution formula
134 */
135 static object *
136 find_transmution_ob (object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item)
137 {
138 object *prod_item = 0;
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 = archetype::get (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 = archetype::get (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 = archetype::get (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 = archetype::get (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 = archetype::get (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 = archetype::get (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 = archetype::get (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 object *item;
831
832 if (caster->type != PLAYER)
833 return; /* only players for now */
834
835 if (get_map_flags (caster->map, NULL, caster->x, caster->y, NULL, NULL) & P_SAFE)
836 {
837 new_draw_info (NDI_UNIQUE, 0, caster, "This is sacred ground, the gods prevent you from using this device.");
838 return;
839 }
840
841 /* if no ingredients, no formula! lets forget it */
842 if (!(formula = content_recipe_value (cauldron)))
843 return;
844
845 numb = numb_ob_inside (cauldron);
846 if ((fl = get_formulalist (numb)))
847 {
848 if (caster->flag [FLAG_WIZ])
849 {
850 rp = find_recipe (fl, formula, cauldron->inv);
851 if (rp != NULL)
852 {
853 #ifdef ALCHEMY_DEBUG
854 if (strcmp (rp->title, "NONE"))
855 LOG (llevDebug, "WIZ got formula: %s of %s\n", rp->arch_name[0], &rp->title);
856 else
857 LOG (llevDebug, "WIZ got formula: %s (nbatches:%d)\n", rp->arch_name[0], formula / rp->index);
858 #endif
859 attempt_recipe (caster, cauldron, ability, rp, formula / rp->index);
860 }
861 else
862 LOG (llevDebug, "WIZ couldn't find formula for ingredients.\n");
863 return;
864 } /* End of WIZ alchemy */
865
866 /* find the recipe */
867 rp = find_recipe (fl, formula, cauldron->inv);
868 if (rp
869 && rp->skill == skill->skill
870 && (!rp->cauldron
871 || rp->cauldron == cauldron->arch->archname))
872 {
873 uint64 value_ingredients;
874 uint64 value_item;
875 object *tmp;
876 int attempt_shadow_alchemy;
877
878 //ave_chance = fl->total_chance / (float)fl->number;
879
880 ability += skill->level * ((4.0 + cauldron->magic) / 4.0);
881
882 /* determine value of ingredients */
883 value_ingredients = 0;
884 for (tmp = cauldron->inv; tmp; tmp = tmp->below)
885 value_ingredients += query_cost (tmp, NULL, F_TRUE);
886
887 attempt_shadow_alchemy = !is_defined_recipe (rp, cauldron, caster);
888
889 /* create the object **FIRST**, then decide whether to keep it. */
890 if ((item = attempt_recipe (caster, cauldron, ability, rp, formula / rp->index)) != NULL)
891 {
892 /* compute base chance of recipe success */
893 //TODO: (marc) diff is documenmted as percentage difficulty, not as divisor.
894 success_chance = ((float)ability / (float)(rp->diff * (item->level + 2)));
895
896 #ifdef ALCHEMY_DEBUG
897 LOG (llevDebug, "percent success chance = %f ab%d / diff%d*lev%d\n", success_chance, ability, rp->diff, item->level);
898 #endif
899
900 value_item = query_cost (item, NULL, F_TRUE | F_IDENTIFIED | F_NOT_CURSED);
901 if (attempt_shadow_alchemy && value_item > value_ingredients)
902 {
903 #ifdef ALCHEMY_DEBUG
904 LOG (llevDebug,
905 "Forcing failure for shadow alchemy recipe because price of ingredients (%llu) is less than price of result (%llu).\n",
906 value_ingredients, value_item);
907 #endif
908 }
909 /* roll the dice */
910 else if (random_roll (0, 101, caster, PREFER_LOW) <= 100.0 * success_chance)
911 {
912 change_exp (caster, rp->exp, rp->skill, SK_EXP_NONE);
913
914 // let alchemy consume some time, so that exploits are less easy
915 caster->speed_left -= 1.0;
916
917 return;
918 }
919 }
920 }
921 }
922
923 /* if we get here, we failed!! */
924 alchemy_failure_effect (caster, cauldron, rp, calc_alch_danger (caster, cauldron, skill, rp));
925 }
926