ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.57
Committed: Sat Apr 23 04:56:54 2011 UTC (13 years, 1 month ago) by root
Content type: text/plain
Branch: MAIN
Changes since 1.56: +1 -1 lines
Log Message:
update copyright to 2011

File Contents

# User Rev Content
1 elmex 1.1 /*
2 root 1.27 * This file is part of Deliantra, the Roguelike Realtime MMORPG.
3 pippijn 1.18 *
4 root 1.57 * Copyright (©) 2005,2006,2007,2008,2009,2010,2011 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
5 root 1.49 * Copyright (©) 2002 Mark Wedel & Crossfire Development Team
6     * Copyright (©) 1992 Frank Tore Johansen
7 pippijn 1.18 *
8 root 1.40 * Deliantra is free software: you can redistribute it and/or modify it under
9     * the terms of the Affero GNU General Public License as published by the
10     * Free Software Foundation, either version 3 of the License, or (at your
11     * option) any later version.
12 pippijn 1.18 *
13 root 1.26 * This program is distributed in the hope that it will be useful,
14     * but WITHOUT ANY WARRANTY; without even the implied warranty of
15     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     * GNU General Public License for more details.
17 pippijn 1.18 *
18 root 1.40 * You should have received a copy of the Affero GNU General Public License
19     * and the GNU General Public License along with this program. If not, see
20     * <http://www.gnu.org/licenses/>.
21 root 1.24 *
22 root 1.27 * The authors can be reached via e-mail to <support@deliantra.net>
23 pippijn 1.18 */
24 elmex 1.1
25     /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */
26    
27     #include <global.h>
28     #include <object.h>
29 root 1.14 #include <sproto.h>
30 elmex 1.1 #include <skills.h>
31     #include <spells.h>
32    
33     /** define this for some helpful debuging information */
34     #if 0
35 root 1.7 # define ALCHEMY_DEBUG
36 elmex 1.1 #endif
37    
38     /** define this for loads of (marginal) debuging information */
39     #if 0
40 root 1.7 # define EXTREME_ALCHEMY_DEBUG
41 elmex 1.1 #endif
42    
43     /** Random cauldrons effects */
44 root 1.7 static const char *const cauldron_effect[] = {
45     "vibrates briefly",
46     "produces a cloud of steam",
47     "emits bright flames",
48     "pours forth heavy black smoke",
49     "emits sparks",
50     "shoots out small flames",
51     "whines painfully",
52     "hiccups loudly",
53     "wheezes",
54     "burps",
55     "shakes",
56     "rattles",
57     "makes chugging sounds",
58     "smokes heavily for a while"
59     };
60 elmex 1.1
61 root 1.7 static int is_defined_recipe (const recipe *rp, const object *cauldron, object *caster);
62     static recipe *find_recipe (recipelist * fl, int formula, object *ingredients);
63 elmex 1.1
64     /** Returns a random selection from cauldron_effect[] */
65 root 1.7 static const char *
66 root 1.48 cauldron_sound ()
67 root 1.7 {
68 root 1.54 return cauldron_effect [rndm (array_length (cauldron_effect))];
69 elmex 1.1 }
70    
71 root 1.41 /**
72 elmex 1.1 * Recipe value of the entire contents of a container.
73     * This appears to just generate a hash value, which I guess for now works
74     * ok, but the possibility of duplicate hashes is certainly possible - msw
75     */
76 root 1.44 static int
77 root 1.7 content_recipe_value (object *op)
78     {
79     object *tmp = op->inv;
80 root 1.53 int formula = 0;
81 elmex 1.1
82 root 1.7 while (tmp)
83     {
84 root 1.53 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 root 1.7 #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 elmex 1.1 #ifdef ALCHEMY_DEBUG
96 root 1.7 LOG (llevDebug, " Formula value=%d\n", formula);
97 elmex 1.1 #endif
98 root 1.53
99 root 1.7 return formula;
100 elmex 1.1 }
101    
102 root 1.41 /**
103     * Returns total number of items in op
104 elmex 1.1 */
105 root 1.45 static int
106 root 1.7 numb_ob_inside (object *op)
107     {
108     object *tmp = op->inv;
109     int number = 0, o_number = 0;
110    
111     while (tmp)
112     {
113     if (tmp->nrof)
114     number += tmp->nrof;
115     else
116     number++;
117     o_number++;
118     tmp = tmp->below;
119     }
120     #ifdef ALCHEMY_DEBUG
121 sf-marcmagus 1.38 LOG (llevDebug, "numb_ob_inside(%s): found %d ingredients\n", &op->name, o_number);
122 root 1.7 #endif
123     return o_number;
124 elmex 1.1 }
125 root 1.7
126 elmex 1.6 /**
127 root 1.43 * 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 root 1.7 */
134 root 1.45 static object *
135 root 1.43 find_transmution_ob (object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item)
136 root 1.7 {
137 root 1.43 object *prod_item = 0;
138     bool found = false;
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     prod_item = get_archetype (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     * of objects in the cauldron inventory (ex icecube has stuff in it).
238     */
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     * Essentially a wrapper for make_item_from_recipe() and
293     * insert_ob_in_ob. If the caster has some alchemy skill, then they might
294     * gain some exp from (successfull) fabrication of the product.
295     * If nbatches==-1, don't give exp for this creation (random generation/
296     * failed recipe)
297 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.46 tmp = get_archetype (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.46 tmp = get_archetype (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.46 tmp = get_archetype (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     object *fb = get_archetype (SP_MED_FIREBALL);
494    
495     remove_contents (cauldron->inv, NULL);
496     fire_arch_from_position (cauldron, cauldron, cauldron->x, cauldron->y, 0, fb);
497 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     /* this is meant to be better than prior possiblity,
543     * in this one, we allow *any* valid alchemy artifact
544     * to be made (rather than only those on the given
545     * formulalist) */
546     if (!rp)
547     rp = get_random_recipe ((recipelist *) NULL);
548 root 1.33
549     if (rp && (tmp = get_archetype (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     object *tmp = get_archetype (LOOSE_MANA);
562    
563     new_draw_info (NDI_UNIQUE, 0, op, "You unwisely release potent forces!");
564     remove_contents (cauldron->inv, NULL);
565     cast_magic_storm (op, tmp, level);
566     }
567 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     * skill *before* this routine is called. (no longer auto-readies that skill)
577     * -b.t.
578     */
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     * that take a look at the contents of the 'cauldron' and, using these ingredients,
802     * we construct an integer formula value which is referenced (randomly) against a
803     * formula list (the formula list chosen is based on the # contents of the cauldron).
804     *
805     * If we get a match between the recipe indicated in cauldron contents and a
806     * randomly chosen one, an item is created and experience awarded. Otherwise
807     * various failure effects are possible (getting worse and worse w/ # cauldron
808     * ingredients). Note that the 'item' to be made can be *anything* listed on
809     * the artifacts list in lib/artifacts which has a recipe listed in lib/formulae.
810     *
811     * To those wondering why I am using the funky formula index method:
812     * 1) I want to match recipe to ingredients regardless of ordering.
813     * 2) I want a fast search for the 'right' recipe.
814     *
815     * Note: it is just possible that a totally different combination of
816     * ingredients will result in a match with a given recipe. This is not a bug!
817     * There is no good reason (in my mind) why alchemical processes have to be
818     * unique -- such a 'feature' is one reason why players might want to experiment
819     * around. :)
820     * -b.t.
821     */
822     void
823     attempt_do_alchemy (object *caster, object *cauldron, object *skill)
824     {
825     recipelist *fl;
826     recipe *rp = NULL;
827     float success_chance;
828     int numb, ability = 1;
829     int formula = 0;
830     float ave_chance;
831     object *item, *skop;
832    
833     if (caster->type != PLAYER)
834     return; /* only players for now */
835    
836     if (get_map_flags (caster->map, NULL, caster->x, caster->y, NULL, NULL) & P_SAFE)
837     {
838     new_draw_info (NDI_UNIQUE, 0, caster, "This is sacred ground, the gods prevent you from using this device.");
839     return;
840     }
841    
842     /* if no ingredients, no formula! lets forget it */
843     if (!(formula = content_recipe_value (cauldron)))
844     return;
845    
846     numb = numb_ob_inside (cauldron);
847     if ((fl = get_formulalist (numb)))
848     {
849 root 1.51 if (caster->flag [FLAG_WIZ])
850 root 1.43 {
851     rp = find_recipe (fl, formula, cauldron->inv);
852     if (rp != NULL)
853     {
854     #ifdef ALCHEMY_DEBUG
855     if (strcmp (rp->title, "NONE"))
856     LOG (llevDebug, "WIZ got formula: %s of %s\n", rp->arch_name[0], &rp->title);
857     else
858     LOG (llevDebug, "WIZ got formula: %s (nbatches:%d)\n", rp->arch_name[0], formula / rp->index);
859     #endif
860     attempt_recipe (caster, cauldron, ability, rp, formula / rp->index);
861     }
862     else
863     LOG (llevDebug, "WIZ couldn't find formula for ingredients.\n");
864     return;
865     } /* End of WIZ alchemy */
866    
867     /* find the recipe */
868     rp = find_recipe (fl, formula, cauldron->inv);
869     if (rp
870     && rp->skill == skill->skill
871     && (!rp->cauldron
872     || rp->cauldron == cauldron->arch->archname))
873     {
874     uint64 value_ingredients;
875     uint64 value_item;
876     object *tmp;
877     int attempt_shadow_alchemy;
878    
879 root 1.46 ave_chance = fl->total_chance / (float)fl->number;
880 root 1.43
881 root 1.52 ability += skill->level * ((4.0 + cauldron->magic) / 4.0);
882 root 1.43
883     /* determine value of ingredients */
884     value_ingredients = 0;
885 root 1.46 for (tmp = cauldron->inv; tmp; tmp = tmp->below)
886 root 1.43 value_ingredients += query_cost (tmp, NULL, F_TRUE);
887    
888     attempt_shadow_alchemy = !is_defined_recipe (rp, cauldron, caster);
889    
890     /* create the object **FIRST**, then decide whether to keep it. */
891     if ((item = attempt_recipe (caster, cauldron, ability, rp, formula / rp->index)) != NULL)
892     {
893     /* compute base chance of recipe success */
894 root 1.56 success_chance = ((float)ability / (float)(rp->diff * (item->level + 2)));
895 root 1.43 if (ave_chance == 0)
896     ave_chance = 1;
897    
898     #ifdef ALCHEMY_DEBUG
899     LOG (llevDebug, "percent success chance = %f ab%d / diff%d*lev%d\n", success_chance, ability, rp->diff, item->level);
900     #endif
901    
902     value_item = query_cost (item, NULL, F_TRUE | F_IDENTIFIED | F_NOT_CURSED);
903     if (attempt_shadow_alchemy && value_item > value_ingredients)
904     {
905     #ifdef ALCHEMY_DEBUG
906     LOG (llevDebug,
907     "Forcing failure for shadow alchemy recipe because price of ingredients (%llu) is less than price of result (%llu).\n",
908     value_ingredients, value_item);
909     #endif
910     }
911     /* roll the dice */
912 root 1.52 else if (random_roll (0, 101, caster, PREFER_LOW) <= 100.0 * success_chance)
913 root 1.43 {
914     change_exp (caster, rp->exp, rp->skill, SK_EXP_NONE);
915    
916     // let alchemy consume some time, so that exploits are less easy
917     caster->speed_left -= 1.0;
918    
919     return;
920     }
921     }
922     }
923     }
924    
925     /* if we get here, we failed!! */
926 root 1.55 alchemy_failure_effect (caster, cauldron, rp, calc_alch_danger (caster, cauldron, skill, rp));
927 root 1.43 }
928