ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/server/alchemy.C
Revision: 1.1
Committed: Sun Aug 13 17:16:03 2006 UTC (17 years, 9 months ago) by elmex
Content type: text/plain
Branch: MAIN
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+= (int) (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 = (int) (
338 (1.0 - 1.0/(lvl/10.0 + 1.0)) *
339 (rndm(0, yield-1) + rndm(0, yield-1) + rndm(0, yield-1)) + 1
340 );
341 if (nrof > yield)
342 nrof = yield;
343 item->nrof=nrof;
344 }
345 }
346
347
348 /**
349 * Using a list of items and a recipe to make an artifact.
350 *
351 * @param cauldron the cauldron (including the ingredients) used to make the item
352 *
353 * @param rp the recipe to make the artifact from
354 *
355 * @return the newly created object, NULL if something failed
356 */
357
358 object * make_item_from_recipe(object *cauldron, recipe *rp) {
359 artifact *art=NULL;
360 object *item=NULL;
361 size_t rp_arch_index;
362
363 if(rp==NULL) return (object *) NULL;
364
365 /* Find the appropriate object to transform...*/
366 if((item=find_transmution_ob(cauldron->inv, rp, &rp_arch_index, 1))==NULL) {
367 LOG(llevDebug,"make_alchemy_item(): failed to create alchemical object.\n");
368 return (object *) NULL;
369 }
370
371 /* Find the appropriate artifact template...*/
372 if(strcmp(rp->title,"NONE")) {
373 if((art=locate_recipe_artifact(rp, rp_arch_index))==NULL) {
374 LOG(llevError,"make_alchemy_item(): failed to locate recipe artifact.\n");
375 LOG(llevDebug," --requested recipe: %s of %s.\n",rp->arch_name[0],rp->title);
376 return (object *) NULL;
377 }
378 transmute_materialname(item, art->item);
379 give_artifact_abilities(item, art->item);
380 }
381
382 if(QUERY_FLAG(cauldron,FLAG_CURSED)) SET_FLAG(item,FLAG_CURSED);
383 if(QUERY_FLAG(cauldron,FLAG_DAMNED)) SET_FLAG(item,FLAG_DAMNED);
384
385 return item;
386 }
387
388
389 /**
390 * Looks through the ingredient list. If we find a
391 * suitable object in it - we will use that to make the requested artifact.
392 * Otherwise the code returns a 'generic' item if create_item is set. -b.t.
393 *
394 * @param rp_arch_index pointer to return value; set to arch index for recipe;
395 * set to zero if not using a transmution formula
396 */
397
398 object * find_transmution_ob ( object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item) {
399 object *item=NULL;
400
401 *rp_arch_index = 0;
402
403 if(rp->transmute) /* look for matching ingredient/prod archs */
404 for(item=first_ingred;item;item=item->below) {
405 size_t i;
406
407 for (i = 0; i < rp->arch_names; i++) {
408 if(strcmp(item->arch->name, rp->arch_name[i]) == 0) {
409 *rp_arch_index = i;
410 break;
411 }
412 }
413 if (i < rp->arch_names)
414 break;
415 }
416
417 /* failed, create a fresh object. Note no nrof>1 because that would
418 * allow players to create massive amounts of artifacts easily */
419 if(create_item && (!item || item->nrof > 1)) {
420 *rp_arch_index = RANDOM()%rp->arch_names;
421 item = get_archetype(rp->arch_name[*rp_arch_index]);
422 }
423
424 #ifdef ALCHEMY_DEBUG
425 LOG(llevDebug,"recipe calls for%stransmution.\n",rp->transmute?" ":" no ");
426 if (item != NULL) {
427 LOG(llevDebug," find_transmutable_ob(): returns arch %s(sp:%d)\n",
428 item->arch->name,item->stats.sp);
429 }
430 #endif
431
432 return item;
433 }
434
435
436 /**
437 * Ouch. We didnt get the formula we wanted.
438 * This fctn simulates the backfire effects--worse effects as the level
439 * increases. If SPELL_FAILURE_EFFECTS is defined some really evil things
440 * can happen to the would be alchemist. This table probably needs some
441 * adjustment for playbalance. -b.t.
442 */
443
444 void alchemy_failure_effect(object *op,object *cauldron,recipe *rp,int danger) {
445 int level=0;
446
447 if(!op || !cauldron) return;
448
449 if(danger>1) level=random_roll(1, danger, op, PREFER_LOW);
450
451 #ifdef ALCHEMY_DEBUG
452 LOG(llevDebug,"Alchemy_failure_effect(): using level=%d\n",level);
453 #endif
454
455 /* possible outcomes based on level */
456 if(level<25) { /* INGREDIENTS USED/SLAGGED */
457 object *item=NULL;
458
459 if(rndm(0, 2)) { /* slag created */
460 object *tmp=cauldron->inv;
461 int weight=0;
462 uint16 material=M_STONE;
463
464 while(tmp) { /* slag has coadded ingredient properties */
465 weight+=tmp->weight;
466 if(!(material&tmp->material))
467 material |= tmp->material;
468 tmp=tmp->below;
469 }
470 tmp = get_archetype("rock");
471 tmp->weight=weight;
472 tmp->value=0;
473 tmp->material=material;
474 tmp->materialname = add_string("stone");
475 free_string(tmp->name);
476 tmp->name=add_string("slag");
477 if (tmp->name_pl) free_string(tmp->name_pl);
478 tmp->name_pl=add_string("slags");
479 item=insert_ob_in_ob(tmp,cauldron);
480 CLEAR_FLAG(tmp,FLAG_CAN_ROLL);
481 CLEAR_FLAG(tmp,FLAG_NO_PICK);
482 tmp->move_block = 0;
483 }
484 remove_contents(cauldron->inv,item);
485 new_draw_info_format(NDI_UNIQUE,0,op,
486 "The %s %s.",cauldron->name,cauldron_sound());
487 return;
488 } else if (level< 40) { /* MAKE TAINTED ITEM */
489 object *tmp=NULL;
490
491 if (!rp)
492 if((rp=get_random_recipe((recipelist *) NULL))==NULL)
493 return;
494
495 if((tmp=attempt_recipe(op,cauldron,1,rp,-1))) {
496 if(!QUERY_FLAG(tmp,FLAG_CURSED)) /* curse it */
497 SET_FLAG(tmp,FLAG_CURSED);
498
499 /* the apply code for potions already deals with cursed
500 * potions, so any code here is basically ignored.
501 */
502 if(tmp->type==FOOD) {
503 tmp->stats.hp=random_roll(0, 149, op, PREFER_LOW);
504 }
505 tmp->value = 0; /* unsaleable item */
506
507 /* change stats downward */
508 do {
509 change_attr_value(&tmp->stats,rndm(0, 6),-1*(rndm(1, 3)));
510 } while (rndm(0, 2));
511 }
512 return;
513 } if(level==40) { /* MAKE RANDOM RECIPE */
514 recipelist *fl;
515 int numb=numb_ob_inside(cauldron);
516
517 fl=get_formulalist(numb-1); /* take a lower recipe list */
518 if(fl &&(rp=get_random_recipe(fl)))
519 /* even though random, don't grant user any EXP for it */
520 (void) attempt_recipe(op,cauldron,1,rp,-1);
521 else
522 alchemy_failure_effect(op,cauldron,rp,level-1);
523 return;
524
525 } else if (level<45) { /* INFURIATE NPC's */
526 /* this is kind of kludgy I know...*/
527 cauldron->enemy=op;
528 npc_call_help(cauldron);
529 cauldron->enemy=NULL;
530
531 alchemy_failure_effect(op,cauldron,rp,level-5);
532 return;
533 } else if (level<50) { /* MINOR EXPLOSION/FIREBALL */
534 object *tmp;
535 remove_contents(cauldron->inv,NULL);
536 switch(rndm(0, 2)) {
537 case 0:
538 tmp=get_archetype("bomb");
539 tmp->stats.dam=random_roll(1, level, op, PREFER_LOW);
540 tmp->stats.hp=random_roll(1, level, op, PREFER_LOW);
541 new_draw_info_format(NDI_UNIQUE,0,op,"The %s creates a bomb!",
542 cauldron->name);
543 break;
544
545 default:
546 tmp=get_archetype("fireball");
547 tmp->stats.dam=random_roll(1, level, op, PREFER_LOW)/5+1;
548 tmp->stats.hp=random_roll(1, level, op, PREFER_LOW)/10+2;
549 new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
550 cauldron->name);
551 break;
552 }
553 tmp->x=cauldron->x,tmp->y=cauldron->y;
554 insert_ob_in_map(tmp,op->map,NULL,0);
555 return;
556
557 } else if (level<60) { /* CREATE MONSTER */
558 new_draw_info_format(NDI_UNIQUE,0,op,
559 "The %s %s.",cauldron->name,cauldron_sound());
560 remove_contents(cauldron->inv,NULL);
561 return;
562 } else if (level<80) { /* MAJOR FIRE */
563 object *fb = get_archetype(SP_MED_FIREBALL);
564 remove_contents(cauldron->inv,NULL);
565 fire_arch_from_position(cauldron, cauldron,cauldron->x, cauldron->y,
566 0, fb);
567 free_object(fb);
568 new_draw_info_format(NDI_UNIQUE,0,op,"The %s erupts in flame!",
569 cauldron->name);
570 return;
571
572 } else if (level<100) { /* WHAMMY the CAULDRON */
573 if(!QUERY_FLAG(cauldron,FLAG_CURSED))
574 SET_FLAG(cauldron,FLAG_CURSED);
575 else cauldron->magic--;
576 cauldron->magic -= random_roll(0, 4, op, PREFER_LOW);
577 if(rndm(0, 1)) {
578 remove_contents(cauldron->inv,NULL);
579 new_draw_info_format(NDI_UNIQUE,0,op,
580 "Your %s turns darker then makes a gulping sound!",
581 cauldron->name);
582 } else
583 new_draw_info_format(NDI_UNIQUE,0,op,
584 "Your %s becomes darker.",cauldron->name);
585 return;
586
587 } else if (level<110) { /* SUMMON EVIL MONSTERS */
588 object *tmp=get_random_mon(level/5);
589
590 remove_contents(cauldron->inv,NULL);
591 if(!tmp)
592 alchemy_failure_effect(op,cauldron,rp,level);
593 else if(summon_hostile_monsters(cauldron, random_roll(1, 10, op, PREFER_LOW), tmp->arch->name))
594 new_draw_info_format(NDI_UNIQUE, 0,op,
595 "The %s %s and then pours forth monsters!",
596 cauldron->name,cauldron_sound());
597 return;
598
599 } else if (level<150) { /* COMBO EFFECT */
600 int roll = rndm(1, 3);
601 while(roll) {
602 alchemy_failure_effect(op,cauldron,rp,level-39);
603 roll--;
604 }
605 return;
606 } else if (level==151) { /* CREATE RANDOM ARTIFACT */
607 object *tmp;
608 /* this is meant to be better than prior possiblity,
609 * in this one, we allow *any* valid alchemy artifact
610 * to be made (rather than only those on the given
611 * formulalist) */
612 if(!rp) rp=get_random_recipe((recipelist *) NULL);
613 if(rp && (tmp=get_archetype(rp->arch_name[RANDOM()%rp->arch_names]))) {
614 generate_artifact(tmp,random_roll(1, op->level/2+1, op, PREFER_HIGH)+1);
615 if((tmp=insert_ob_in_ob(tmp,cauldron))) {
616 remove_contents(cauldron->inv,tmp);
617 new_draw_info_format(NDI_UNIQUE, 0,op,
618 "The %s %s.",cauldron->name,cauldron_sound());
619 }
620 }
621 return;
622 } else { /* MANA STORM - watch out!! */
623 object *tmp = get_archetype(LOOSE_MANA);
624 new_draw_info(NDI_UNIQUE,0,op,"You unwisely release potent forces!");
625 remove_contents (cauldron->inv,NULL);
626 cast_magic_storm(op,tmp, level);
627 return;
628 }
629 }
630
631
632 /**
633 * All but object "save_item" are elimentated from
634 * the container list. Note we have to becareful to remove the inventories
635 * of objects in the cauldron inventory (ex icecube has stuff in it).
636 */
637
638 void remove_contents (object *first_ob, object *save_item) {
639 object *next,*tmp=first_ob;
640
641 while(tmp) {
642 next = tmp->below;
643 if(tmp==save_item) {
644 if(!(tmp=next)) break;
645 else next=next->below;
646 }
647 if(tmp->inv) remove_contents(tmp->inv,NULL);
648 remove_ob(tmp);
649 free_object(tmp);
650 tmp=next;
651 }
652 }
653
654 /**
655 *"Danger" level, will determine how bad the backfire
656 * could be if the user fails to concoct a recipe properly. Factors include
657 * the number of ingredients, the length of the name of each ingredient,
658 * the user's effective level, the user's Int and the enchantment on the
659 * mixing device (aka "cauldron"). Higher values of 'danger' indicate more
660 * danger. Note that we assume that we have had the caster ready the alchemy
661 * skill *before* this routine is called. (no longer auto-readies that skill)
662 * -b.t.
663 */
664
665 int calc_alch_danger(object *caster,object *cauldron, recipe *rp) {
666 object *item;
667 char name[MAX_BUF];
668 int danger=0,nrofi=0;
669
670 /* Knowing alchemy skill reduces yer risk */
671 danger -= caster->chosen_skill?caster->chosen_skill->level:caster->level;
672
673 /* better cauldrons reduce risk */
674 danger -= cauldron->magic;
675
676 /* Higher Int, lower the risk */
677 danger -= 3 * (caster->stats.Int - 15);
678
679 /* Ingredients. Longer names usually mean rarer stuff.
680 * Thus the backfire is worse. Also, more ingredients
681 * means we are attempting a more powerfull potion,
682 * and thus the backfire will be worse. */
683 for(item=cauldron->inv;item;item=item->below) {
684 strcpy(name,item->name);
685 if(item->title) sprintf(name,"%s %s",item->name,item->title);
686 danger += (strtoint(name)/1000) + 3;
687 nrofi++;
688 }
689 if (rp == NULL)
690 danger += 110;
691 else
692 danger += rp->diff*3;
693
694 /* Using a bad device is *majorly* stupid */
695 if(QUERY_FLAG(cauldron,FLAG_CURSED)) danger +=80;
696 if(QUERY_FLAG(cauldron,FLAG_DAMNED)) danger +=200;
697
698 #ifdef ALCHEMY_DEBUG
699 LOG(llevDebug,"calc_alch_danger() returned danger=%d\n",danger);
700 #endif
701
702 return danger;
703 }
704
705 /**
706 * Determines if ingredients in a container match the
707 * proper ingredients for a recipe.
708 *
709 * rp is the recipe to check
710 * cauldron is the container that holds the ingredients
711 * returns 1 if the ingredients match the recipe, 0 if not
712 *
713 * This functions tries to find each defined ingredient in the container. It is
714 * the defined recipe iff
715 * - the number of ingredients of the recipe and in the container is equal
716 * - all ingredients of the recipe are found in the container
717 * - the number of batches is the same for all ingredients
718 */
719 static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster)
720 {
721 uint32 batches_in_cauldron;
722 const linked_char *ingredient;
723 int number;
724 const object *ob;
725
726 /* check for matching number of ingredients */
727 number = 0;
728 for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next)
729 number++;
730 for(ob = cauldron->inv; ob != NULL; ob = ob->below)
731 number--;
732 if(number != 0)
733 return 0;
734
735 /* check for matching ingredients */
736 batches_in_cauldron = 0;
737 for(ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) {
738 uint32 nrof;
739 const char *name;
740 int ok;
741
742 /* determine and remove nrof from name */
743 name = ingredient->name;
744 nrof = 0;
745 while(isdigit(*name)) {
746 nrof = 10*nrof+(*name-'0');
747 name++;
748 }
749 if(nrof == 0)
750 nrof = 1;
751 while(*name == ' ')
752 name++;
753
754 /* find the current ingredient in the cauldron */
755 ok = 0;
756 for(ob = cauldron->inv; ob != NULL; ob = ob->below) {
757 char name_ob[MAX_BUF];
758 const char *name2;
759
760 if(ob->title == NULL)
761 name2 = ob->name;
762 else {
763 snprintf(name_ob, sizeof(name_ob), "%s %s", ob->name, ob->title);
764 name2 = name_ob;
765 }
766
767 if(strcmp(name2, name) == 0) {
768 if(ob->nrof%nrof == 0) {
769 uint32 batches;
770
771 batches = ob->nrof/nrof;
772 if(batches_in_cauldron == 0) {
773 batches_in_cauldron = batches;
774 ok = 1;
775 } else if(batches_in_cauldron == batches)
776 ok = 1;
777 }
778 break;
779 }
780 }
781 if(!ok)
782 return(0);
783 }
784
785 return(1);
786 }
787
788 /**
789 * Find a recipe from a recipe list that matches the given formula. If there
790 * is more than one matching recipe, it selects a random one. If at least one
791 * transmuting recipe matches, it only considers matching transmuting recipes.
792 *
793 * @return one matching recipe, or NULL if no recipe matches
794 */
795 static recipe *find_recipe(recipelist *fl, int formula, object *ingredients)
796 {
797 recipe *rp;
798 recipe *result; /* winning recipe, or NULL if no recipe found */
799 int recipes_matching; /* total number of matching recipes so far */
800 int transmute_found; /* records whether a transmuting recipe was found so far */
801 size_t rp_arch_index;
802
803 #ifdef EXTREME_ALCHEMY_DEBUG
804 LOG(llevDebug, "looking for formula %d:\n", formula);
805 #endif
806 result = NULL;
807 recipes_matching = 0;
808 transmute_found = 0;
809 for (rp = fl->items; rp != NULL; rp = rp->next) {
810 /* check if recipe matches at all */
811 if (formula%rp->index != 0) {
812 #ifdef EXTREME_ALCHEMY_DEBUG
813 LOG(llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index);
814 #endif
815 continue;
816 }
817
818 if (rp->transmute && find_transmution_ob(ingredients, rp, &rp_arch_index, 0) != NULL) {
819 #ifdef EXTREME_ALCHEMY_DEBUG
820 LOG(llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], rp->title, rp->index);
821 #endif
822 /* transmution recipe with matching base ingredient */
823 if (!transmute_found) {
824 transmute_found = 1;
825 recipes_matching = 0;
826 }
827 } else if (transmute_found) {
828 #ifdef EXTREME_ALCHEMY_DEBUG
829 LOG(llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], rp->title, rp->index);
830 #endif
831 /* "normal" recipe found after previous transmution recipe => ignore this recipe */
832 continue;
833 }
834 #ifdef EXTREME_ALCHEMY_DEBUG
835 else {
836 LOG(llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index);
837 }
838 #endif
839
840 if (rndm(0, recipes_matching) == 0)
841 result = rp;
842
843 recipes_matching++;
844 }
845
846 if (result == NULL) {
847 #ifdef ALCHEMY_DEBUG
848 LOG(llevDebug, "couldn't find formula for ingredients.\n");
849 #endif
850 return NULL;
851 }
852
853 #ifdef ALCHEMY_DEBUG
854 if(strcmp(result->title, "NONE") != 0)
855 LOG(llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula/result->index);
856 else
857 LOG(llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula/result->index);
858 #endif
859 return result;
860 }