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