Crossfire Server, Branch 1.12  R12190
alchemy.c
Go to the documentation of this file.
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 }