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