ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
(Generate patch)

Comparing deliantra/server/server/alchemy.C (file contents):
Revision 1.6 by elmex, Sun Sep 3 14:33:45 2006 UTC vs.
Revision 1.63 by root, Sun Jan 29 02:47:05 2017 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines