ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.c
Revision: 1.4
Committed: Sun Aug 13 17:16:03 2006 UTC (17 years, 9 months ago) by elmex
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.3: +0 -0 lines
State: FILE REMOVED
Log Message:
Made server compile with C++.
Removed cfanim plugin and crossedit.
C++ here we come.

File Contents

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