Crossfire Server, Branch 1.12
R12190
|
00001 /* 00002 * static char *rcsid_alchemy_c = 00003 * "$Id: alchemy.c 13894 2010-09-26 13:28:50Z ryo_saeba $"; 00004 */ 00005 00006 /* 00007 CrossFire, A Multiplayer game for X-windows 00008 00009 Copyright (C) 2002-2006 Mark Wedel & Crossfire Development Team 00010 Copyright (C) 1992 Frank Tore Johansen 00011 00012 This program is free software; you can redistribute it and/or modify 00013 it under the terms of the GNU General Public License as published by 00014 the Free Software Foundation; either version 2 of the License, or 00015 (at your option) any later version. 00016 00017 This program is distributed in the hope that it will be useful, 00018 but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 GNU General Public License for more details. 00021 00022 You should have received a copy of the GNU General Public License 00023 along with this program; if not, write to the Free Software 00024 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00025 00026 The authors can be reached via e-mail at crossfire-devel@real-time.com 00027 */ 00028 00029 /* March 96 - Laid down original code. -b.t. thomas@astro.psu.edu */ 00030 00036 #include <global.h> 00037 #include <object.h> 00038 #ifndef __CEXTRACT__ 00039 #include <sproto.h> 00040 #endif 00041 #include <skills.h> 00042 #include <spells.h> 00043 00045 #if 0 00046 #define ALCHEMY_DEBUG 00047 #endif 00048 00050 #if 0 00051 #define EXTREME_ALCHEMY_DEBUG 00052 #endif 00053 00055 static const char *const cauldron_effect [] = { 00056 "vibrates briefly", 00057 "produces a cloud of steam", 00058 "emits bright flames", 00059 "pours forth heavy black smoke", 00060 "emits sparks", 00061 "shoots out small flames", 00062 "whines painfully", 00063 "hiccups loudly", 00064 "wheezes", 00065 "burps", 00066 "shakes", 00067 "rattles", 00068 "makes chugging sounds", 00069 "smokes heavily for a while" 00070 }; 00071 00072 00073 static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster); 00074 static recipe *find_recipe(recipelist *fl, int formula, object *ingredients); 00075 static int content_recipe_value(object *op); 00076 static int numb_ob_inside(object *op); 00077 static void alchemy_failure_effect(object *op, object *cauldron, recipe *rp, int danger); 00078 static object *attempt_recipe(object *caster, object *cauldron, int ability, recipe *rp, int nbatches, int ignore_cauldron); 00079 static int calc_alch_danger(object *caster, object *cauldron, recipe *rp); 00080 static object *make_item_from_recipe(object *cauldron, recipe *rp); 00081 static void remove_contents(object *first_ob, object *save_item); 00082 static void adjust_product(object *item, int lvl, int yield); 00083 static object *find_transmution_ob(object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item); 00084 static void attempt_do_alchemy(object *caster, object *cauldron); 00085 00087 static const char *cauldron_sound(void) { 00088 int size = sizeof(cauldron_effect)/sizeof(char *); 00089 00090 return cauldron_effect[rndm(0, size-1)]; 00091 } 00092 00121 static void attempt_do_alchemy(object *caster, object *cauldron) { 00122 recipelist *fl; 00123 recipe *rp = NULL; 00124 float success_chance; 00125 int numb, ability = 1; 00126 int formula = 0; 00127 float ave_chance; 00128 object *item, *skop; 00129 00130 if (caster->type != PLAYER) 00131 return; /* only players for now */ 00132 00133 /* if no ingredients, no formula! lets forget it */ 00134 if (!(formula = content_recipe_value(cauldron))) { 00135 draw_ext_info_format(NDI_UNIQUE, 0, caster, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00136 "The %s is empty.", 00137 "The %s is empty.", 00138 cauldron->name); 00139 return; 00140 } 00141 00142 numb = numb_ob_inside(cauldron); 00143 if ((fl = get_formulalist(numb))) { 00144 if (QUERY_FLAG(caster, FLAG_WIZ)) { 00145 rp = find_recipe(fl, formula, cauldron->inv); 00146 if (rp != NULL) { 00147 #ifdef ALCHEMY_DEBUG 00148 if (strcmp(rp->title, "NONE")) 00149 LOG(llevDebug, "WIZ got formula: %s of %s\n", rp->arch_name[0], rp->title); 00150 else 00151 LOG(llevDebug, "WIZ got formula: %s (nbatches:%d)\n", rp->arch_name[0], formula/rp->index); 00152 #endif 00153 attempt_recipe(caster, cauldron, ability, rp, formula/rp->index, !is_defined_recipe(rp, cauldron, caster)); 00154 } else 00155 LOG(llevDebug, "WIZ couldn't find formula for ingredients.\n"); 00156 return; 00157 } /* End of WIZ alchemy */ 00158 00159 /* find the recipe */ 00160 rp = find_recipe(fl, formula, cauldron->inv); 00161 if (rp != NULL) { 00162 uint64 value_ingredients; 00163 uint64 value_item; 00164 object *tmp; 00165 int attempt_shadow_alchemy; 00166 00167 ave_chance = fl->total_chance/(float)fl->number; 00168 /* the caster gets an increase in ability based on thier skill lvl */ 00169 if (rp->skill != NULL) { 00170 skop = find_skill_by_name(caster, rp->skill); 00171 if (!skop) { 00172 draw_ext_info(NDI_UNIQUE, 0, caster, MSG_TYPE_SKILL, MSG_TYPE_SKILL_MISSING, 00173 "You do not have the proper skill for this recipe", NULL); 00174 } else { 00175 ability += skop->level*((4.0+cauldron->magic)/4.0); 00176 } 00177 } else { 00178 LOG(llevDebug, "Recipe %s has NULL skill!\n", rp->title); 00179 return; 00180 } 00181 00182 if (rp->cauldron == NULL) { 00183 LOG(llevDebug, "Recipe %s has NULL cauldron!\n", rp->title); 00184 return; 00185 } 00186 00187 /* determine value of ingredients */ 00188 value_ingredients = 0; 00189 for (tmp = cauldron->inv; tmp != NULL; tmp = tmp->below) 00190 value_ingredients += query_cost(tmp, NULL, F_TRUE); 00191 00192 attempt_shadow_alchemy = !is_defined_recipe(rp, cauldron, caster); 00193 00194 /* create the object **FIRST**, then decide whether to keep it. */ 00195 if ((item = attempt_recipe(caster, cauldron, ability, rp, formula/rp->index, attempt_shadow_alchemy)) != NULL) { 00196 /* compute base chance of recipe success */ 00197 success_chance = ((float)ability/(float)(rp->diff*(item->level+2))); 00198 if (ave_chance == 0) 00199 ave_chance = 1; 00200 00201 #ifdef ALCHEMY_DEBUG 00202 LOG(llevDebug, "percent success chance = %f ab%d / diff%d*lev%d\n", success_chance, ability, rp->diff, item->level); 00203 #endif 00204 00205 value_item = query_cost(item, NULL, F_TRUE|F_IDENTIFIED|F_NOT_CURSED); 00206 if (attempt_shadow_alchemy && value_item > value_ingredients) { 00207 #ifdef ALCHEMY_DEBUG 00208 #ifndef WIN32 00209 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); 00210 #else 00211 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); 00212 #endif 00213 #endif 00214 /* roll the dice */ 00215 } else if ((float)(random_roll(0, 101, caster, PREFER_LOW)) <= 100.0*success_chance) { 00216 change_exp(caster, rp->exp, rp->skill, SK_EXP_NONE); 00217 return; 00218 } 00219 } 00220 } 00221 } 00222 /* if we get here, we failed!! */ 00223 alchemy_failure_effect(caster, cauldron, rp, calc_alch_danger(caster, cauldron, rp)); 00224 } 00225 00236 static int content_recipe_value(object *op) { 00237 char name[MAX_BUF]; 00238 object *tmp = op->inv; 00239 int tval = 0, formula = 0; 00240 00241 while (tmp) { 00242 tval = 0; 00243 strcpy(name, tmp->name); 00244 if (tmp->title) 00245 snprintf(name, sizeof(name), "%s %s", tmp->name, tmp->title); 00246 tval = (strtoint(name)*(tmp->nrof ? tmp->nrof : 1)); 00247 #ifdef ALCHEMY_DEBUG 00248 LOG(llevDebug, "Got ingredient %d %s(%d)\n", tmp->nrof ? tmp->nrof : 1, name, tval); 00249 #endif 00250 formula += tval; 00251 tmp = tmp->below; 00252 } 00253 #ifdef ALCHEMY_DEBUG 00254 LOG(llevDebug, " Formula value=%d\n", formula); 00255 #endif 00256 return formula; 00257 } 00258 00266 static int numb_ob_inside(object *op) { 00267 object *tmp = op->inv; 00268 int o_number = 0; 00269 00270 while (tmp) { 00271 o_number++; 00272 tmp = tmp->below; 00273 } 00274 #ifdef ALCHEMY_DEBUG 00275 LOG(llevDebug, "numb_ob_inside(%s): found %d ingredients\n", op->name, o_number); 00276 #endif 00277 return o_number; 00278 } 00279 00305 static object *attempt_recipe(object *caster, object *cauldron, int ability, recipe *rp, int nbatches, int ignore_cauldron) { 00306 object *item = NULL, *skop; 00307 /* this should be passed to this fctn, not effiecent cpu use this way */ 00308 int batches = abs(nbatches); 00309 00310 /* is the cauldron the right type? */ 00311 if (!ignore_cauldron && (strcmp(rp->cauldron, cauldron->arch->name) != 0)) { 00312 draw_ext_info(NDI_UNIQUE, 0, caster, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00313 "You are not using the proper facilities for this formula.", NULL); 00314 return NULL; 00315 } 00316 00317 skop = find_skill_by_name(caster, rp->skill); 00318 /* does the caster have the skill? */ 00319 if (!skop) 00320 return NULL; 00321 00322 /* code required for this recipe, search the caster */ 00323 if (rp->keycode) { 00324 object *tmp; 00325 00326 for (tmp = caster->inv; tmp != NULL; tmp = tmp->below) { 00327 if (tmp->type == FORCE 00328 && tmp->slaying 00329 && !strcmp(rp->keycode, tmp->slaying)) 00330 break; 00331 } 00332 if (tmp == NULL) { /* failure--no code found */ 00333 draw_ext_info(NDI_UNIQUE, 0, caster, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 00334 "You know the ingredients, but not the technique. Go learn how to do this recipe.", 00335 NULL); 00336 return NULL; 00337 } 00338 } 00339 00340 #ifdef EXTREME_ALCHEMY_DEBUG 00341 LOG(llevDebug, "attempt_recipe(): got %d nbatches\n", nbatches); 00342 LOG(llevDebug, "attempt_recipe(): using recipe %s\n", rp->title ? rp->title : "unknown"); 00343 #endif 00344 00345 if ((item = make_item_from_recipe(cauldron, rp)) != NULL) { 00346 remove_contents(cauldron->inv, item); 00347 /* adj lvl, nrof on caster level */ 00348 adjust_product(item, ability, rp->yield ? (rp->yield*batches) : batches); 00349 if (!item->env && (item = insert_ob_in_ob(item, cauldron)) == NULL) { 00350 draw_ext_info(NDI_UNIQUE, 0, caster, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00351 "Nothing happened.", NULL); 00352 } else { 00353 draw_ext_info_format(NDI_UNIQUE, 0, caster, 00354 MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00355 "The %s %s.", 00356 "The %s %s.", 00357 cauldron->name, cauldron_sound()); 00358 } 00359 } 00360 return item; 00361 } 00362 00373 static void adjust_product(object *item, int lvl, int yield) { 00374 int nrof = 1; 00375 00376 if (!yield) 00377 yield = 1; 00378 if (lvl <= 0) 00379 lvl = 1; /* lets avoid div by zero! */ 00380 if (item->nrof) { 00381 nrof = (1.0-1.0/(lvl/10.0+1.0))*(rndm(0, yield-1)+rndm(0, yield-1)+rndm(0, yield-1))+1; 00382 if (nrof > yield) 00383 nrof = yield; 00384 item->nrof = nrof; 00385 } 00386 } 00387 00398 static object *make_item_from_recipe(object *cauldron, recipe *rp) { 00399 artifact *art = NULL; 00400 object *item = NULL; 00401 size_t rp_arch_index; 00402 00403 if (rp == NULL) 00404 return (object *)NULL; 00405 00406 /* Find the appropriate object to transform...*/ 00407 if ((item = find_transmution_ob(cauldron->inv, rp, &rp_arch_index, 1)) == NULL) { 00408 LOG(llevDebug, "make_alchemy_item(): failed to create alchemical object.\n"); 00409 return (object *)NULL; 00410 } 00411 00412 /* If item is already in container, we need to remove its weight, since it can change later on. */ 00413 if (item->env != NULL) 00414 sub_weight(cauldron, item->weight*(item->nrof != 0 ? item->nrof : 1)); 00415 00416 /* Find the appropriate artifact template...*/ 00417 if (strcmp(rp->title, "NONE")) { 00418 if ((art = locate_recipe_artifact(rp, rp_arch_index)) == NULL) { 00419 LOG(llevError, "make_alchemy_item(): failed to locate recipe artifact.\n"); 00420 LOG(llevDebug, " --requested recipe: %s of %s.\n", rp->arch_name[0], rp->title); 00421 return (object *)NULL; 00422 } 00423 transmute_materialname(item, art->item); 00424 give_artifact_abilities(item, art->item); 00425 } 00426 if (item->env != NULL) 00427 add_weight(cauldron, item->weight*(item->nrof != 0 ? item->nrof : 1)); 00428 00429 if (QUERY_FLAG(cauldron, FLAG_CURSED)) 00430 SET_FLAG(item, FLAG_CURSED); 00431 if (QUERY_FLAG(cauldron, FLAG_DAMNED)) 00432 SET_FLAG(item, FLAG_DAMNED); 00433 00434 return item; 00435 } 00436 00450 static object *find_transmution_ob(object *first_ingred, recipe *rp, size_t *rp_arch_index, int create_item) { 00451 object *item = NULL; 00452 00453 *rp_arch_index = 0; 00454 00455 if (rp->transmute) /* look for matching ingredient/prod archs */ 00456 for (item = first_ingred; item; item = item->below) { 00457 size_t i; 00458 00459 for (i = 0; i < rp->arch_names; i++) { 00460 if (strcmp(item->arch->name, rp->arch_name[i]) == 0) { 00461 *rp_arch_index = i; 00462 break; 00463 } 00464 } 00465 if (i < rp->arch_names) 00466 break; 00467 } 00468 00469 /* failed, create a fresh object. Note no nrof>1 because that would 00470 * allow players to create massive amounts of artifacts easily */ 00471 if (create_item && (!item || item->nrof > 1)) { 00472 *rp_arch_index = RANDOM()%rp->arch_names; 00473 item = create_archetype(rp->arch_name[*rp_arch_index]); 00474 } 00475 00476 #ifdef ALCHEMY_DEBUG 00477 LOG(llevDebug, "recipe calls for%stransmution.\n", rp->transmute ? " " : " no "); 00478 if (item != NULL) { 00479 LOG(llevDebug, " find_transmutable_ob(): returns arch %s(sp:%d)\n", item->arch->name, item->stats.sp); 00480 } 00481 #endif 00482 00483 return item; 00484 } 00485 00502 static void alchemy_failure_effect(object *op, object *cauldron, recipe *rp, int danger) { 00503 int level = 0; 00504 00505 if (!op || !cauldron) 00506 return; 00507 00508 if (danger > 1) 00509 level = random_roll(1, danger, op, PREFER_LOW); 00510 00511 #ifdef ALCHEMY_DEBUG 00512 LOG(llevDebug, "Alchemy_failure_effect(): using level=%d\n", level); 00513 #endif 00514 00515 /* possible outcomes based on level */ 00516 if (level < 25) { /* INGREDIENTS USED/SLAGGED */ 00517 object *item = NULL; 00518 00519 if (rndm(0, 2)) { /* slag created */ 00520 object *tmp = cauldron->inv; 00521 int weight = 0; 00522 uint16 material = M_STONE; 00523 00524 while (tmp) { /* slag has coadded ingredient properties */ 00525 weight += tmp->weight; 00526 if (!(material&tmp->material)) 00527 material |= tmp->material; 00528 tmp = tmp->below; 00529 } 00530 tmp = create_archetype("rock"); 00531 tmp->weight = weight; 00532 tmp->value = 0; 00533 tmp->material = material; 00534 tmp->materialname = add_string("stone"); 00535 free_string(tmp->name); 00536 tmp->name = add_string("slag"); 00537 if (tmp->name_pl) 00538 free_string(tmp->name_pl); 00539 tmp->name_pl = add_string("slags"); 00540 item = insert_ob_in_ob(tmp, cauldron); 00541 CLEAR_FLAG(tmp, FLAG_CAN_ROLL); 00542 CLEAR_FLAG(tmp, FLAG_NO_PICK); 00543 tmp->move_block = 0; 00544 } 00545 remove_contents(cauldron->inv, item); 00546 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00547 "The %s %s.", 00548 "The %s %s.", 00549 cauldron->name, cauldron_sound()); 00550 return; 00551 } else if (level < 40) { /* MAKE TAINTED ITEM */ 00552 object *tmp = NULL; 00553 00554 /* 00555 * Note by Nicolas Weeger 2010-09-26 00556 * This is an incorrect part. 00557 * Calling again attempt_recipe in case of failure will apply again the artifact 00558 * combination to the item. 00559 * This leads to items with eg 100% resist, or more. 00560 * So use the actual item in the cauldron, don't retry the recipe. 00561 * This should fix bug #2020224: buggy(?) crafting yields. 00562 * 00563 if (!rp) 00564 if ((rp = get_random_recipe((recipelist *)NULL)) == NULL) 00565 return; 00566 */ 00567 00568 if ((tmp = cauldron->inv)) /*attempt_recipe(op, cauldron, 1, rp, -1, 0)))*/ { 00569 if (!QUERY_FLAG(tmp, FLAG_CURSED)) { /* curse it */ 00570 SET_FLAG(tmp, FLAG_CURSED); 00571 CLEAR_FLAG(tmp, FLAG_KNOWN_CURSED); 00572 CLEAR_FLAG(tmp, FLAG_IDENTIFIED); 00573 } 00574 00575 /* the apply code for potions already deals with cursed 00576 * potions, so any code here is basically ignored. 00577 */ 00578 if (tmp->type == FOOD) { 00579 tmp->stats.hp = random_roll(0, 149, op, PREFER_LOW); 00580 } 00581 tmp->value = 0; /* unsaleable item */ 00582 00583 /* change stats downward */ 00584 do { 00585 change_attr_value(&tmp->stats, rndm(0, 6), -1*(rndm(1, 3))); 00586 } while (rndm(0, 2)); 00587 } 00588 return; 00589 } 00590 #if 0 00591 /* 00592 Note: this does not work as expected... 00593 At this point there is only one item in the cauldron, and get_formulalist(0) will return 00594 the first formula list for recipes with 1 ingredient. 00595 So disable this, and just use the next case. 00596 */ 00597 00598 if (level == 40) { /* MAKE RANDOM RECIPE */ 00599 recipelist *fl; 00600 int numb = numb_ob_inside(cauldron); 00601 00602 fl = get_formulalist(numb-1); /* take a lower recipe list */ 00603 if (fl &&(rp = get_random_recipe(fl))) 00604 /* even though random, don't grant user any EXP for it */ 00605 (void)attempt_recipe(op, cauldron, 1, rp, -1, 0); 00606 else 00607 alchemy_failure_effect(op, cauldron, rp, level-1); 00608 return; 00609 00610 } else 00611 #endif 00612 if (level < 45) { /* INFURIATE NPC's */ 00613 /* this is kind of kludgy I know...*/ 00614 cauldron->enemy = op; 00615 npc_call_help(cauldron); 00616 cauldron->enemy = NULL; 00617 00618 alchemy_failure_effect(op, cauldron, rp, level-5); 00619 return; 00620 } else if (level < 50) { /* MINOR EXPLOSION/FIREBALL */ 00621 object *tmp; 00622 00623 remove_contents(cauldron->inv, NULL); 00624 switch (rndm(0, 2)) { 00625 case 0: 00626 tmp = create_archetype("bomb"); 00627 tmp->stats.dam = random_roll(1, level, op, PREFER_LOW); 00628 tmp->stats.hp = random_roll(1, level, op, PREFER_LOW); 00629 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00630 "The %s creates a bomb!", 00631 "The %s creates a bomb!", 00632 cauldron->name); 00633 break; 00634 00635 default: 00636 tmp = create_archetype("fireball"); 00637 tmp->stats.dam = random_roll(1, level, op, PREFER_LOW)/5+1; 00638 tmp->stats.hp = random_roll(1, level, op, PREFER_LOW)/10+2; 00639 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00640 "The %s erupts in flame!", 00641 "The %s erupts in flame!", 00642 cauldron->name); 00643 break; 00644 } 00645 tmp->x = cauldron->x, 00646 tmp->y = cauldron->y; 00647 insert_ob_in_map(tmp, op->map, NULL, 0); 00648 return; 00649 } else if (level < 60) { /* CREATE MONSTER */ 00650 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00651 "The %s %s.", NULL, cauldron->name, cauldron_sound()); 00652 remove_contents(cauldron->inv, NULL); 00653 return; 00654 } else if (level < 80) { /* MAJOR FIRE */ 00655 object *fb = create_archetype(SP_MED_FIREBALL); 00656 00657 remove_contents(cauldron->inv, NULL); 00658 fire_arch_from_position(cauldron, cauldron, cauldron->x, cauldron->y, 0, fb); 00659 free_object(fb); 00660 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00661 "The %s erupts in flame!", 00662 "The %s erupts in flame!", 00663 cauldron->name); 00664 return; 00665 } else if (level < 100) { /* WHAMMY the CAULDRON */ 00666 if (!QUERY_FLAG(cauldron, FLAG_CURSED)) { 00667 SET_FLAG(cauldron, FLAG_CURSED); 00668 CLEAR_FLAG(cauldron, FLAG_KNOWN_CURSED); 00669 CLEAR_FLAG(cauldron, FLAG_IDENTIFIED); 00670 } else 00671 cauldron->magic--; 00672 cauldron->magic -= random_roll(0, 4, op, PREFER_LOW); 00673 if (rndm(0, 1)) { 00674 remove_contents(cauldron->inv, NULL); 00675 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00676 "Your %s turns darker then makes a gulping sound!", 00677 "Your %s turns darker then makes a gulping sound!", 00678 cauldron->name); 00679 } else 00680 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00681 "Your %s becomes darker.", 00682 "Your %s becomes darker.", 00683 cauldron->name); 00684 return; 00685 00686 } else if (level < 110) { /* SUMMON EVIL MONSTERS */ 00687 object *tmp = get_random_mon(level/5); 00688 00689 remove_contents(cauldron->inv, NULL); 00690 if (!tmp) 00691 alchemy_failure_effect(op, cauldron, rp, level); 00692 else if (summon_hostile_monsters(cauldron, random_roll(1, 10, op, PREFER_LOW), tmp->arch->name)) 00693 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00694 "The %s %s and then pours forth monsters!", 00695 "The %s %s and then pours forth monsters!", 00696 cauldron->name, cauldron_sound()); 00697 return; 00698 } else if (level < 150) { /* COMBO EFFECT */ 00699 int roll = rndm(1, 3); 00700 while (roll) { 00701 alchemy_failure_effect(op, cauldron, rp, level-39); 00702 roll--; 00703 } 00704 return; 00705 } else if (level == 151) { /* CREATE RANDOM ARTIFACT */ 00706 object *tmp; 00707 00708 /* this is meant to be better than prior possiblity, 00709 * in this one, we allow *any *valid alchemy artifact 00710 * to be made (rather than only those on the given 00711 * formulalist) */ 00712 if (!rp) 00713 rp = get_random_recipe((recipelist *)NULL); 00714 if (rp && (tmp = create_archetype(rp->arch_name[RANDOM()%rp->arch_names]))) { 00715 generate_artifact(tmp, random_roll(1, op->level/2+1, op, PREFER_HIGH)+1); 00716 if ((tmp = insert_ob_in_ob(tmp, cauldron))) { 00717 remove_contents(cauldron->inv, tmp); 00718 draw_ext_info_format(NDI_UNIQUE, 0, op, 00719 MSG_TYPE_SKILL, MSG_TYPE_SKILL_SUCCESS, 00720 "The %s %s.", 00721 "The %s %s.", 00722 cauldron->name, cauldron_sound()); 00723 } 00724 } 00725 return; 00726 } else { /* MANA STORM - watch out!! */ 00727 object *tmp = create_archetype(LOOSE_MANA); 00728 draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_FAILURE, 00729 "You unwisely release potent forces!", NULL); 00730 remove_contents(cauldron->inv, NULL); 00731 cast_magic_storm(op, tmp, level); 00732 return; 00733 } 00734 } 00735 00745 static void remove_contents(object *first_ob, object *save_item) { 00746 object *next, *tmp = first_ob; 00747 00748 while (tmp) { 00749 next = tmp->below; 00750 if (tmp == save_item) { 00751 if (!(tmp = next)) 00752 break; 00753 else 00754 next = next->below; 00755 } 00756 if (tmp->inv) 00757 remove_contents(tmp->inv, NULL); 00758 remove_ob(tmp); 00759 free_object(tmp); 00760 tmp = next; 00761 } 00762 } 00763 00783 static int calc_alch_danger(object *caster, object *cauldron, recipe *rp) { 00784 object *item; 00785 char name[MAX_BUF]; 00786 int danger = 0, nrofi = 0; 00787 00788 /* Knowing alchemy skill reduces yer risk */ 00789 danger -= caster->chosen_skill ? caster->chosen_skill->level : caster->level; 00790 00791 /* better cauldrons reduce risk */ 00792 danger -= cauldron->magic; 00793 00794 /* Higher Int, lower the risk */ 00795 danger -= 3*(caster->stats.Int-15); 00796 00797 /* Ingredients. Longer names usually mean rarer stuff. 00798 * Thus the backfire is worse. Also, more ingredients 00799 * means we are attempting a more powerfull potion, 00800 * and thus the backfire will be worse. */ 00801 for (item = cauldron->inv; item; item = item->below) { 00802 strcpy(name, item->name); 00803 if (item->title) 00804 snprintf(name, sizeof(name), "%s %s", item->name, item->title); 00805 danger += (strtoint(name)/1000)+3; 00806 nrofi++; 00807 } 00808 if (rp == NULL) 00809 danger += 110; 00810 else 00811 danger += rp->diff*3; 00812 00813 /* Using a bad device is *majorly *stupid */ 00814 if (QUERY_FLAG(cauldron, FLAG_CURSED)) 00815 danger += 80; 00816 if (QUERY_FLAG(cauldron, FLAG_DAMNED)) 00817 danger += 200; 00818 00819 #ifdef ALCHEMY_DEBUG 00820 LOG(llevDebug, "calc_alch_danger() returned danger=%d\n", danger); 00821 #endif 00822 00823 return danger; 00824 } 00825 00845 static int is_defined_recipe(const recipe *rp, const object *cauldron, object *caster) { 00846 uint32 batches_in_cauldron; 00847 const linked_char *ingredient; 00848 int number; 00849 const object *ob; 00850 00851 /* check for matching number of ingredients */ 00852 number = 0; 00853 for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) 00854 number++; 00855 for (ob = cauldron->inv; ob != NULL; ob = ob->below) 00856 number--; 00857 if (number != 0) 00858 return 0; 00859 00860 /* check for matching ingredients */ 00861 batches_in_cauldron = 0; 00862 for (ingredient = rp->ingred; ingredient != NULL; ingredient = ingredient->next) { 00863 uint32 nrof; 00864 const char *name; 00865 int ok; 00866 00867 /* determine and remove nrof from name */ 00868 name = ingredient->name; 00869 nrof = 0; 00870 while (isdigit(*name)) { 00871 nrof = 10*nrof+(*name-'0'); 00872 name++; 00873 } 00874 if (nrof == 0) 00875 nrof = 1; 00876 while (*name == ' ') 00877 name++; 00878 00879 /* find the current ingredient in the cauldron */ 00880 ok = 0; 00881 for (ob = cauldron->inv; ob != NULL; ob = ob->below) { 00882 char name_ob[MAX_BUF]; 00883 const char *name2; 00884 00885 if (ob->title == NULL) 00886 name2 = ob->name; 00887 else { 00888 snprintf(name_ob, sizeof(name_ob), "%s %s", ob->name, ob->title); 00889 name2 = name_ob; 00890 } 00891 00892 if (strcmp(name2, name) == 0) { 00893 if (ob->nrof%nrof == 0) { 00894 uint32 batches; 00895 00896 batches = ob->nrof/nrof; 00897 if (batches_in_cauldron == 0) { 00898 batches_in_cauldron = batches; 00899 ok = 1; 00900 } else if (batches_in_cauldron == batches) 00901 ok = 1; 00902 } 00903 break; 00904 } 00905 } 00906 if (!ok) 00907 return(0); 00908 } 00909 00910 return(1); 00911 } 00912 00929 static recipe *find_recipe(recipelist *fl, int formula, object *ingredients) { 00930 recipe *rp; 00931 recipe *result; /* winning recipe, or NULL if no recipe found */ 00932 int recipes_matching; /* total number of matching recipes so far */ 00933 int transmute_found; /* records whether a transmuting recipe was found so far */ 00934 size_t rp_arch_index; 00935 00936 #ifdef EXTREME_ALCHEMY_DEBUG 00937 LOG(llevDebug, "looking for formula %d:\n", formula); 00938 #endif 00939 result = NULL; 00940 recipes_matching = 0; 00941 transmute_found = 0; 00942 for (rp = fl->items; rp != NULL; rp = rp->next) { 00943 /* check if recipe matches at all */ 00944 if (formula%rp->index != 0) { 00945 #ifdef EXTREME_ALCHEMY_DEBUG 00946 LOG(llevDebug, " formula %s of %s (%d) does not match\n", rp->arch_name[0], rp->title, rp->index); 00947 #endif 00948 continue; 00949 } 00950 00951 if (rp->transmute && find_transmution_ob(ingredients, rp, &rp_arch_index, 0) != NULL) { 00952 #ifdef EXTREME_ALCHEMY_DEBUG 00953 LOG(llevDebug, " formula %s of %s (%d) is a matching transmuting formula\n", rp->arch_name[rp_arch_index], rp->title, rp->index); 00954 #endif 00955 /* transmution recipe with matching base ingredient */ 00956 if (!transmute_found) { 00957 transmute_found = 1; 00958 recipes_matching = 0; 00959 } 00960 } else if (transmute_found) { 00961 #ifdef EXTREME_ALCHEMY_DEBUG 00962 LOG(llevDebug, " formula %s of %s (%d) matches but is not a matching transmuting formula\n", rp->arch_name[0], rp->title, rp->index); 00963 #endif 00964 /* "normal" recipe found after previous transmution recipe => ignore this recipe */ 00965 continue; 00966 } 00967 #ifdef EXTREME_ALCHEMY_DEBUG 00968 else { 00969 LOG(llevDebug, " formula %s of %s (%d) matches\n", rp->arch_name[0], rp->title, rp->index); 00970 } 00971 #endif 00972 00973 if (rndm(0, recipes_matching) == 0) 00974 result = rp; 00975 00976 recipes_matching++; 00977 } 00978 00979 if (result == NULL) { 00980 #ifdef ALCHEMY_DEBUG 00981 LOG(llevDebug, "couldn't find formula for ingredients.\n"); 00982 #endif 00983 return NULL; 00984 } 00985 00986 #ifdef ALCHEMY_DEBUG 00987 if (strcmp(result->title, "NONE") != 0) 00988 LOG(llevDebug, "got formula: %s of %s (nbatches:%d)\n", result->arch_name[0], result->title, formula/result->index); 00989 else 00990 LOG(llevDebug, "got formula: %s (nbatches:%d)\n", result->arch_name[0], formula/result->index); 00991 #endif 00992 return result; 00993 } 00994 01006 int use_alchemy(object *op) { 01007 object *tmp, *item, *next; 01008 object *unpaid_cauldron = NULL; 01009 object *unpaid_item = NULL; 01010 int did_alchemy = 0; 01011 char name[MAX_BUF]; 01012 01013 for (tmp = GET_MAP_OB(op->map, op->x, op->y); tmp != NULL; tmp = next) { 01014 next = tmp->above; 01015 if (QUERY_FLAG(tmp, FLAG_IS_CAULDRON)) { 01016 if (QUERY_FLAG(tmp, FLAG_UNPAID)) { 01017 unpaid_cauldron = tmp; 01018 continue; 01019 } 01020 unpaid_item = NULL; 01021 for (item = tmp->inv; item; item = item->below) { 01022 if (QUERY_FLAG(item, FLAG_UNPAID)) { 01023 unpaid_item = item; 01024 break; 01025 } 01026 } 01027 if (unpaid_item != NULL) 01028 continue; 01029 01030 attempt_do_alchemy(op, tmp); 01031 if (QUERY_FLAG(tmp, FLAG_APPLIED)) 01032 esrv_send_inventory(op, tmp); 01033 did_alchemy = 1; 01034 } 01035 } 01036 if (unpaid_cauldron) { 01037 query_base_name(unpaid_cauldron, 0, name, MAX_BUF); 01038 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01039 "You must pay for your %s first!", 01040 "You must pay for your %s first!", 01041 name); 01042 } else if (unpaid_item) { 01043 query_base_name(unpaid_item, 0, name, MAX_BUF); 01044 draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SKILL, MSG_TYPE_SKILL_ERROR, 01045 "You must pay for your %s first!", 01046 "You must pay for your %s first!", 01047 name); 01048 } 01049 01050 return did_alchemy; 01051 }