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

File Contents

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