skylib_fluffos_v3/
skylib_fluffos_v3/bin/
skylib_fluffos_v3/bin/db/
skylib_fluffos_v3/fluffos-2.9-ds2.04/
skylib_fluffos_v3/fluffos-2.9-ds2.04/ChangeLog.old/
skylib_fluffos_v3/fluffos-2.9-ds2.04/Win32/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/
skylib_fluffos_v3/fluffos-2.9-ds2.04/compat/simuls/
skylib_fluffos_v3/fluffos-2.9-ds2.04/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/clone/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/command/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/data/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/etc/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/include/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/inherit/master/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/log/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/compiler/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/efuns/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/single/tests/operators/
skylib_fluffos_v3/fluffos-2.9-ds2.04/testsuite/u/
skylib_fluffos_v3/fluffos-2.9-ds2.04/tmp/
skylib_fluffos_v3/fluffos-2.9-ds2.04/windows/
skylib_fluffos_v3/mudlib/
skylib_fluffos_v3/mudlib/cmds/
skylib_fluffos_v3/mudlib/cmds/admin/
skylib_fluffos_v3/mudlib/cmds/guild-race/
skylib_fluffos_v3/mudlib/cmds/living/broken/
skylib_fluffos_v3/mudlib/cmds/player/group_cmds/
skylib_fluffos_v3/mudlib/cmds/playtester/
skylib_fluffos_v3/mudlib/d/admin/
skylib_fluffos_v3/mudlib/d/admin/room/
skylib_fluffos_v3/mudlib/d/admin/room/we_care/
skylib_fluffos_v3/mudlib/d/admin/save/
skylib_fluffos_v3/mudlib/d/admin/text/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/buildings/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/map/
skylib_fluffos_v3/mudlib/d/learning/TinyTown/roads/
skylib_fluffos_v3/mudlib/d/learning/chars/
skylib_fluffos_v3/mudlib/d/learning/functions/
skylib_fluffos_v3/mudlib/d/learning/handlers/
skylib_fluffos_v3/mudlib/d/learning/help_topics/
skylib_fluffos_v3/mudlib/d/learning/help_topics/npcs/
skylib_fluffos_v3/mudlib/d/learning/help_topics/objects/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rcs_demo/RCS/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/crowd/
skylib_fluffos_v3/mudlib/d/learning/help_topics/rooms/situations/
skylib_fluffos_v3/mudlib/d/learning/save/
skylib_fluffos_v3/mudlib/d/learning/school/
skylib_fluffos_v3/mudlib/d/learning/school/add_sc/
skylib_fluffos_v3/mudlib/d/learning/school/characters/
skylib_fluffos_v3/mudlib/d/learning/school/general/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/basic_commands/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/edtutor/
skylib_fluffos_v3/mudlib/d/learning/school/getting-started/unix_tutor/
skylib_fluffos_v3/mudlib/d/learning/school/items/
skylib_fluffos_v3/mudlib/d/learning/school/npc_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/room_basic/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/situations/
skylib_fluffos_v3/mudlib/d/learning/school/room_school/terrain_tutor/
skylib_fluffos_v3/mudlib/d/learning/text/
skylib_fluffos_v3/mudlib/d/liaison/
skylib_fluffos_v3/mudlib/d/mudlib/
skylib_fluffos_v3/mudlib/d/mudlib/changes/
skylib_fluffos_v3/mudlib/d/playtesters/
skylib_fluffos_v3/mudlib/d/playtesters/effects/
skylib_fluffos_v3/mudlib/d/playtesters/handlers/
skylib_fluffos_v3/mudlib/d/playtesters/items/
skylib_fluffos_v3/mudlib/d/sage/
skylib_fluffos_v3/mudlib/doc/
skylib_fluffos_v3/mudlib/doc/creator/
skylib_fluffos_v3/mudlib/doc/driver/
skylib_fluffos_v3/mudlib/doc/driver/efuns/arrays/
skylib_fluffos_v3/mudlib/doc/driver/efuns/buffers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/calls/
skylib_fluffos_v3/mudlib/doc/driver/efuns/compile/
skylib_fluffos_v3/mudlib/doc/driver/efuns/filesystem/
skylib_fluffos_v3/mudlib/doc/driver/efuns/floats/
skylib_fluffos_v3/mudlib/doc/driver/efuns/functions/
skylib_fluffos_v3/mudlib/doc/driver/efuns/general/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mappings/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mixed/
skylib_fluffos_v3/mudlib/doc/driver/efuns/mudlib/
skylib_fluffos_v3/mudlib/doc/driver/efuns/numbers/
skylib_fluffos_v3/mudlib/doc/driver/efuns/parsing/
skylib_fluffos_v3/mudlib/doc/login/
skylib_fluffos_v3/mudlib/doc/lpc/basic_manual/
skylib_fluffos_v3/mudlib/doc/lpc/intermediate/
skylib_fluffos_v3/mudlib/doc/new/add_command/
skylib_fluffos_v3/mudlib/doc/new/events/
skylib_fluffos_v3/mudlib/doc/new/handlers/
skylib_fluffos_v3/mudlib/doc/new/living/race/
skylib_fluffos_v3/mudlib/doc/new/living/spells/
skylib_fluffos_v3/mudlib/doc/new/object/
skylib_fluffos_v3/mudlib/doc/new/player/
skylib_fluffos_v3/mudlib/doc/new/room/guild/
skylib_fluffos_v3/mudlib/doc/new/room/outside/
skylib_fluffos_v3/mudlib/doc/new/room/storeroom/
skylib_fluffos_v3/mudlib/doc/object/
skylib_fluffos_v3/mudlib/doc/playtesters/
skylib_fluffos_v3/mudlib/doc/policy/
skylib_fluffos_v3/mudlib/doc/weapons/
skylib_fluffos_v3/mudlib/global/
skylib_fluffos_v3/mudlib/global/creator/
skylib_fluffos_v3/mudlib/handlers/
skylib_fluffos_v3/mudlib/include/casino/
skylib_fluffos_v3/mudlib/include/cmds/
skylib_fluffos_v3/mudlib/include/effects/
skylib_fluffos_v3/mudlib/include/npc/
skylib_fluffos_v3/mudlib/include/room/
skylib_fluffos_v3/mudlib/include/shops/
skylib_fluffos_v3/mudlib/net/daemon/
skylib_fluffos_v3/mudlib/net/daemon/chars/
skylib_fluffos_v3/mudlib/net/inherit/
skylib_fluffos_v3/mudlib/net/obj/
skylib_fluffos_v3/mudlib/net/obj/BACKUPS/
skylib_fluffos_v3/mudlib/obj/amulets/
skylib_fluffos_v3/mudlib/obj/armours/plate/
skylib_fluffos_v3/mudlib/obj/b_day/
skylib_fluffos_v3/mudlib/obj/clothes/transport/horse/
skylib_fluffos_v3/mudlib/obj/faith/symbols/
skylib_fluffos_v3/mudlib/obj/fungi/
skylib_fluffos_v3/mudlib/obj/gatherables/
skylib_fluffos_v3/mudlib/obj/instruments/
skylib_fluffos_v3/mudlib/obj/media/
skylib_fluffos_v3/mudlib/obj/misc/player_shop/
skylib_fluffos_v3/mudlib/obj/monster/godmother/
skylib_fluffos_v3/mudlib/obj/monster/transport/
skylib_fluffos_v3/mudlib/obj/rings/
skylib_fluffos_v3/mudlib/obj/scabbards/
skylib_fluffos_v3/mudlib/obj/spells/
skylib_fluffos_v3/mudlib/obj/stationery/
skylib_fluffos_v3/mudlib/obj/stationery/envelopes/
skylib_fluffos_v3/mudlib/obj/toys/
skylib_fluffos_v3/mudlib/obj/vessels/
skylib_fluffos_v3/mudlib/obj/weapons/axes/
skylib_fluffos_v3/mudlib/obj/weapons/chains/
skylib_fluffos_v3/mudlib/obj/weapons/maces/BACKUPS/
skylib_fluffos_v3/mudlib/save/autodoc/
skylib_fluffos_v3/mudlib/save/book_handler/
skylib_fluffos_v3/mudlib/save/books/history/calarien/
skylib_fluffos_v3/mudlib/save/mail/
skylib_fluffos_v3/mudlib/save/new_soul/data/
skylib_fluffos_v3/mudlib/save/parcels/
skylib_fluffos_v3/mudlib/save/playerinfo/
skylib_fluffos_v3/mudlib/save/players/d/
skylib_fluffos_v3/mudlib/save/players/s/
skylib_fluffos_v3/mudlib/save/random_names/
skylib_fluffos_v3/mudlib/save/random_names/data/
skylib_fluffos_v3/mudlib/save/terrains/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_desert/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_grassy_field/
skylib_fluffos_v3/mudlib/save/terrains/tutorial_mountain/
skylib_fluffos_v3/mudlib/save/todo_lists/
skylib_fluffos_v3/mudlib/secure/
skylib_fluffos_v3/mudlib/secure/cmds/admin/
skylib_fluffos_v3/mudlib/secure/cmds/lord/
skylib_fluffos_v3/mudlib/secure/config/
skylib_fluffos_v3/mudlib/secure/handlers/autodoc/
skylib_fluffos_v3/mudlib/secure/handlers/intermud/
skylib_fluffos_v3/mudlib/secure/include/global/
skylib_fluffos_v3/mudlib/secure/save/
skylib_fluffos_v3/mudlib/secure/save/handlers/
skylib_fluffos_v3/mudlib/secure/std/
skylib_fluffos_v3/mudlib/secure/std/classes/
skylib_fluffos_v3/mudlib/secure/std/modules/
skylib_fluffos_v3/mudlib/std/creator/
skylib_fluffos_v3/mudlib/std/dom/
skylib_fluffos_v3/mudlib/std/effects/
skylib_fluffos_v3/mudlib/std/effects/external/
skylib_fluffos_v3/mudlib/std/effects/fighting/
skylib_fluffos_v3/mudlib/std/effects/magic/
skylib_fluffos_v3/mudlib/std/effects/magic/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/other/BACKUPS/
skylib_fluffos_v3/mudlib/std/effects/priest/
skylib_fluffos_v3/mudlib/std/effects/room/
skylib_fluffos_v3/mudlib/std/environ/
skylib_fluffos_v3/mudlib/std/guilds/
skylib_fluffos_v3/mudlib/std/guilds/old/
skylib_fluffos_v3/mudlib/std/languages/
skylib_fluffos_v3/mudlib/std/liquids/
skylib_fluffos_v3/mudlib/std/npc/
skylib_fluffos_v3/mudlib/std/npc/goals/
skylib_fluffos_v3/mudlib/std/npc/goals/basic/
skylib_fluffos_v3/mudlib/std/npc/goals/misc/
skylib_fluffos_v3/mudlib/std/npc/plans/
skylib_fluffos_v3/mudlib/std/npc/plans/basic/
skylib_fluffos_v3/mudlib/std/npc/types/
skylib_fluffos_v3/mudlib/std/npc/types/helper/
skylib_fluffos_v3/mudlib/std/npcs/
skylib_fluffos_v3/mudlib/std/outsides/
skylib_fluffos_v3/mudlib/std/races/shadows/
skylib_fluffos_v3/mudlib/std/room/basic/BACKUPS/
skylib_fluffos_v3/mudlib/std/room/basic/topography/
skylib_fluffos_v3/mudlib/std/room/controller/
skylib_fluffos_v3/mudlib/std/room/inherit/topography/
skylib_fluffos_v3/mudlib/std/room/topography/area/
skylib_fluffos_v3/mudlib/std/room/topography/iroom/
skylib_fluffos_v3/mudlib/std/room/topography/milestone/
skylib_fluffos_v3/mudlib/std/shadows/curses/
skylib_fluffos_v3/mudlib/std/shadows/disease/
skylib_fluffos_v3/mudlib/std/shadows/fighting/
skylib_fluffos_v3/mudlib/std/shadows/healing/
skylib_fluffos_v3/mudlib/std/shadows/magic/
skylib_fluffos_v3/mudlib/std/shadows/poison/
skylib_fluffos_v3/mudlib/std/shadows/room/
skylib_fluffos_v3/mudlib/std/shops/controllers/
skylib_fluffos_v3/mudlib/std/shops/objs/
skylib_fluffos_v3/mudlib/std/shops/player_shop/
skylib_fluffos_v3/mudlib/std/socket/
skylib_fluffos_v3/mudlib/std/soul/d/
skylib_fluffos_v3/mudlib/std/soul/e/
skylib_fluffos_v3/mudlib/std/soul/i/
skylib_fluffos_v3/mudlib/std/soul/j/
skylib_fluffos_v3/mudlib/std/soul/k/
skylib_fluffos_v3/mudlib/std/soul/l/
skylib_fluffos_v3/mudlib/std/soul/n/
skylib_fluffos_v3/mudlib/std/soul/o/
skylib_fluffos_v3/mudlib/std/soul/q/
skylib_fluffos_v3/mudlib/std/soul/r/
skylib_fluffos_v3/mudlib/std/soul/u/
skylib_fluffos_v3/mudlib/std/soul/v/
skylib_fluffos_v3/mudlib/std/soul/y/
skylib_fluffos_v3/mudlib/std/soul/z/
skylib_fluffos_v3/mudlib/std/stationery/
skylib_fluffos_v3/mudlib/w/
skylib_fluffos_v3/mudlib/w/default/
skylib_fluffos_v3/mudlib/w/default/armour/
skylib_fluffos_v3/mudlib/w/default/clothes/
skylib_fluffos_v3/mudlib/w/default/item/
skylib_fluffos_v3/mudlib/w/default/npc/
skylib_fluffos_v3/mudlib/w/default/room/
skylib_fluffos_v3/mudlib/w/default/weapon/
skylib_fluffos_v3/mudlib/www/
skylib_fluffos_v3/mudlib/www/java/
skylib_fluffos_v3/mudlib/www/secure/
skylib_fluffos_v3/mudlib/www/secure/lpc/advanced/
skylib_fluffos_v3/mudlib/www/secure/lpc/intermediate/
skylib_fluffos_v3/win32/
/*
 *  reaction handler - Handles reactions between substances that have
 *      been mixed together.
 *
 *  A few notes of what I have planned:
 *  
 *  the substance mapping will have as its keys the substance names
 *  (as returned by query_medium_alias()).
 *  The value will be another mapping (one for each substance).  This
 *  second-level mapping will have as its keys the substances with
 *  which it reacts.  To save space, the names of the two substances
 *  will be compared, and the alphanumerically lower one will be the
 *  index into the top-level mapping.
 *  
 *  The value of the second-level mapping is the tricky part.
 *  It is currently a class that contains all the relevant information.
 *  See the definition of the class for more info.
 *
 *  The following pseudo-objects may be used in the message strings and
 *  function call strings; process_string is explicitly called in both
 *  cases:
 *    #env# - replaced by the filename of the environment
 *    #env2# - replaced by the filename of the environment of the enviroment
 *    #obj_a# - The object being added to the container
 *    #obj_b# - The object with which 'a' is reacting
 *    #obj_x# - The transient object, or the result object if no transient
 *
 *  Various debugging messages will be printed if this_player() has a
 *  property call "debug_rh" with a non-zero value.
 *
 *  To Do:
 *    Handle concentrations.
 *
 */

#include <move_failures.h>
#include <reaction.h>

#define REACTIONS "/obj/reactions/"
#define SUBSTANCES 10

mapping substance = allocate_mapping(SUBSTANCES);
int debug_rh = 0;

void restore_from_files();

void write_dbg(string str) { if (debug_rh) write(str); }

void create() {
    seteuid("/secure/master"->creator_file(file_name(this_object())));
    restore_from_files();
}

// Merge the effects mappings, weighted by relative amounts, and return
// a mapping that can be used to add the new effects.  Note that, since
// add_xxx_effect() is cumulative, the result reflects the difference.
mapping merge_effects(mapping effects_a, mapping effects_m, int q_a, int q_m) {
    int q_t, strength;
    string key;
    mapping ret = ([ ]);

    if(!mappingp(effects_a))
      return 0;
    
    q_t = q_a + q_m;
    foreach (key in keys(effects_a)) {
        if (!intp(effects_a[key]) || !intp(effects_m[key])) {
            continue;
        }
        if (undefinedp(effects_m[key])) {
            strength = (effects_a[key]*q_a)/q_t;
        } else {
            strength = (effects_a[key]*q_a + effects_m[key]*q_m)/q_t
                - effects_m[key];
            map_delete(effects_m, key);
        }
        ret += ([key: strength]);
    }
    foreach (key in keys(effects_m)) {
        if (!intp(effects_m[key])) {
            continue;
        }
        strength = (effects_a[key]*q_a + effects_m[key]*q_m)/q_t
            - effects_m[key];
        ret += ([key: strength]);
    }
    return ret;
}

// This is used to merge the new substance with any amounts of the same
// type already in the container, so that the total is checked.
object merge_cont_medium(object a, string medium_alias) {
    object medium;
    int no_join, q_a, q_m;
    mapping effects_a, effects_m, eff;
    string key;

    a->remove_alias(medium_alias);
    no_join = a->query_no_join();
    if ((medium = present(medium_alias, environment(a))) && !no_join) {
        a->set_no_join();
        a->move("/room/void");
        q_a = a->query_amount();
        q_m = medium->query_amount();
        medium->adjust_amount(q_a);
        // Adjust strengths of effects
        effects_a = copy(a->query_eat_effects());
        effects_m = copy(medium->query_eat_effects());
        eff = merge_effects(effects_a, effects_m, q_a, q_m);
        foreach (key in keys(eff)) {
            medium->add_eat_effect(key, eff[key]);
        }
        effects_a = copy(a->query_external_effects());
        effects_m = copy(medium->query_external_effects());
        eff = merge_effects(effects_a, effects_m, q_a, q_m);
        foreach (key in keys(eff)) {
            medium->add_external_effect(key, eff[key]);
        }
        call_out("dest_substance", 0, a);
        return medium;
    } else {
        a->add_alias(medium_alias);
        return a;
    }
}

void check_reaction(object a) {
    int amt_a, need_amt_a, amt_b, need_amt_b;
    string name_a, name_b, i_a, i_b, msg, fcn;
    mixed *fcns;
    int i, j, amt_result;
    object x, *bs, ob_a, ob_b;
    float ratio;
    class reaction rcn;

    if (objectp(this_player()))
      debug_rh = this_player()->query_property("debug_rh");
    else
      debug_rh = 0;
    write_dbg("Entering reaction_handler.\n");
    name_a = a->query_medium_alias();
    if (!name_a || !a->query_continuous()) {
        write_dbg("Leaving reaction_handler.\n");
        return; // Only bother if added item is a cont_medium
    }
    a = merge_cont_medium(a, name_a);
    bs = all_inventory(environment(a));
    if ((sizeof(bs) < 2) || (!a->query_amount())) {
        write_dbg("Leaving reaction_handler.\n");
        return;
    }
    write_dbg("Checking the following: ");
    for (i = 0; i < sizeof(bs); i++) {
        write_dbg(bs[i]->query_medium_alias() + ", ");
    }
    write_dbg("\n");
    for (i = 0; i < sizeof(bs); i++) {
        // Check if a has been all used up yet.
        if (!a->query_amount()) {
            write_dbg("Leaving reaction_handler.\n");
            return;
        }
        // Find if a reacts with b
        name_b = bs[i]->query_medium_alias();
        if (!name_b || !bs[i]->query_continuous() || !bs[i]->query_amount()) {
            continue;
        }
        // The substance table is indexed in alphabetical order;
        // ob_a and ob_b are used to keep the two objects straight
        // whether or not they had to be swapped (mainly when dealing
        // with amounts).
        if (name_a < name_b) {
            i_a = name_a;
            i_b = name_b;
            ob_a = a;
            ob_b = bs[i];
        } else {
            i_a = name_b;
            i_b = name_a;
            ob_a = bs[i];
            ob_b = a;
        }
        write_dbg("Checking " + i_a + " against " + i_b + ".\n");
        if (!substance[i_a] || !substance[i_a][i_b]) {
            continue;
        }
        write_dbg("...they react.\n");
        rcn = substance[i_a][i_b];
        amt_a = ob_a->query_amount();
        amt_b = ob_b->query_amount();
        ratio = to_float(amt_a) / amt_b;
        if (intp(rcn->ratio)) {
            // These need to be floats
            rcn->ratio = to_float(rcn->ratio);
        }
        if (floatp(rcn->ratio)) {
            write_dbg("Single ratio...\n");
            if (ratio > rcn->ratio) {
                need_amt_b = amt_b;
                // Round up fractions (maybe this could be random?)
                need_amt_a = to_int(ceil(amt_b*rcn->ratio));
            } else {
                need_amt_a = amt_a;
                need_amt_b = to_int(ceil(amt_a/rcn->ratio));
            }
        } else if (arrayp(rcn->ratio) && (sizeof(rcn->ratio) == 2)) {
            // Ratio range
            // I've gotten confused by this, so here's my rationale:
            //   a/b should be in the range (r[0], r[1]).
            //   If above range, too much of a: take all of b; take as much of
            //     a as necessary to match upper ratio (ie, b*r[1]).
            //   If below range, too much of b: take all of a; take as much of
            //     b as necessary to match lower ratio (ie, a/r[0]).
            //   If in range, take all of both.
            write_dbg("Range of ratios...");
            if (intp(rcn->ratio[0])) {
                rcn->ratio[0] = to_float(rcn->ratio[0]);
            }
            if (intp(rcn->ratio[1])) {
                rcn->ratio[1] = to_float(rcn->ratio[1]);
            }
            if (ratio > rcn->ratio[1]) {
                write_dbg("above range...(" + ratio + ")\n");
                need_amt_b = amt_b;
                need_amt_a = to_int(ceil(amt_b*rcn->ratio[1]));
            } else if (ratio < rcn->ratio[0]) {
                write_dbg("below range...(" + ratio + ")\n");
                need_amt_a = amt_a;
                need_amt_b = to_int(ceil(amt_a/rcn->ratio[0]));
            } else {
                // Ratio within range, take all of both
                write_dbg("within range...(" + ratio + ")\n");
                need_amt_a = amt_a;
                need_amt_b = amt_b;
            }
        } else {
            error("Illegal reaction ratio; expecting float, int, or "
                  "array of two floats or ints.");
        }
        amt_result = to_int((need_amt_a + need_amt_b)*rcn->result_amt);
        write_dbg("...got ("+amt_a+","+amt_b+"), taking ("+need_amt_a+","+
                  need_amt_b+"), creating " + amt_result + ".\n");
        write_dbg("...creating "+rcn->result+".\n");
        // Create result object
        x = clone_object(rcn->result);
        if (rcn->result_amt) {
            if (function_exists("set_amount", x)) {
                x->set_amount(amt_result);
            } else {
                x->set_weight(amt_result);
            }
        }
        // Print out message when they mix.  This is pretty much
        // obsolete, since 'func' can do it also; but I kept it in
        // anyway.
        msg = rcn->message;
        if (msg) {
            msg = replace(rcn->message, ({
                "#env#", file_name(environment(a)),
                "#env2#", file_name(environment(environment(a))),
                "#obj_a#", file_name(ob_a),
                "#obj_b#", file_name(ob_b),
                "#obj_x#", file_name(x)
              }));
            tell_room(environment(environment(a)), msg);
        }
        // This is a back-door for any special functions that need
        // to be called.
        fcns = rcn->func;
        if (fcns) {
            for (j = 0; j < sizeof(fcns); j++) {
                if (stringp(fcns[j])) {
                    fcn = replace(fcns[j], ({
                        "#env#", file_name(environment(a)),
                        "#env2#", file_name(environment(environment(a))),
                        "#obj_a#", file_name(ob_a),
                        "#obj_b#", file_name(ob_b),
                        "#obj_x#", file_name(x)
                      }));
                } else if (functionp(fcns[j])) {
                    evaluate(fcns[j], ob_a, ob_b, x, environment(a),
                             environment(environment(a)), need_amt_a,
                             need_amt_b);
                }
            }
        }
        // Decrease amounts and dest if gone
        amt_a -= need_amt_a;
        ob_a->set_amount(amt_a);
        amt_b -= need_amt_b;
        ob_b->set_amount(amt_b);
        // Question: should I move the new object to the container
        // right now, or save it and add them all at once?  It might
        // make the order of reaction different; then again, it may
        // be arbitrary anyway.
        //x->move(environment(a));
        // I decided to postpone the move until after I leave the
        // handler.  Probably not necessary, but it makes debugging
        // easier.
        call_out("move_substance", 0, ({x, environment(a)}));
        if (!amt_a) {
            // Since this object is in the middle of being added, it
            // has to be handled specially.
            //call_out("dest_substance", 0, ob_a);
            ob_a->move("/room/rubbish");
        }
        if (!amt_b) {
            // It turns out that this one has to be dested later also.
            // This is because it may be in the bs array of another call
            // of this function.
            //call_out("dest_substance", 0, ob_b);
            ob_b->move("/room/rubbish");
        }
    }
    write_dbg("Leaving reaction_handler.\n");
}

void move_substance(object *ob) {
    // Used to move substance from a call_out
    int vol, vol_left, cont, closed, i, mv_stat;
    object *contents;

    if (!ob[0] || !ob[1]) return;
    vol_left = (int)ob[1]->query_max_volume() - (int)ob[1]->query_volume();
    if (cont = ob[0]->query_continuous()) {
        vol = ob[0]->query_amount();
    } else {
        vol = (int)ob[0]->query_weight()*200;
    }
    closed = ob[1]->query_closed();
    ob[1]->set_open();
    if ((mv_stat = ob[0]->move(ob[1])) != MOVE_OK) {
        write_dbg(sprintf("Move error (%d)...", vol));
        if ((vol > vol_left) && cont && !closed) {
            write_dbg("too much...\n");
            ob[0]->set_amount(vol_left);
            tell_room(environment(ob[1]), ob[0]->short(0) +
                      " leaks out of the " + ob[1]->short(0) + ".\n");
            mv_stat = ob[0]->move(ob[1]);
        }
        if (mv_stat == MOVE_OK) {
            if (closed) ob[1]->set_closed();
        } else {
            // Too big to fit! (or other problem)
            // Destroy container (unless living or a room) and contents.
            contents = all_inventory(ob[1]);
            write_dbg(sprintf("contents = %O\n", contents));
            for (i = 0; i < sizeof(contents); i++) {
                contents[i]->dest_me();
            }
            if (!living(ob[1]) && !function_exists("query_co_ord", ob[1])) {
                tell_room(environment(ob[1]), ob[1]->the_short(0) +
                          " explodes, splattering the contents all over.\n");
                //ob[1]->dest_me();
                ob[1]->move("/room/rubbish");
            }
            //ob[0]->dest_me();
            ob[0]->move("/room/rubbish");
        }
    } else {
        if (closed) ob[1]->set_closed();
    }
}

void dest_substance(object ob) {
    // Used to dest substance from a call_out.
    if (ob) {
        ob->dest_me();
    }
}

mixed *query_reaction(string name_a, string name_b) {
    // Return reaction parameters
    if (!substance[name_a] || !substance[name_a][name_b]) {
        return 0;
    }
    return substance[name_a][name_b];
}

mapping query_reactions() { return substance; }

// This should eliminate the need for the (now deleted) database manipulation
// functions.  Basically, all reactions in all files in the directory
// REACTIONS will be loaded.  This uses /handlers/data.  See the
// documentation on it and /include/reaction.h for the file format.

void restore_from_files() {
    string *files;
    int i;
    
    // This is the file with the #include and stuff that should only be
    // there once.
    files = ({ "base.hdr" });
    // These are the real data files
    files += get_dir(REACTIONS + "*.rcn");
    for (i = 0; i < sizeof(files); i++) {
        files[i] = REACTIONS + files[i];
    }
    //printf("files = %O\n", files);
    substance = "/handlers/data"->compile_data(files);
}

void update_from_files(string fn) {
    mapping new_substance;
    string a, b;

    new_substance = "/handlers/data"->compile_data( ({ fn }) );
    foreach (a in keys(new_substance)) {
      if (undefinedp(substance[a])) {
        substance[a] = ([ ]);
      }
      foreach (b in keys(new_substance[a])) {
        substance[a][b] = new_substance[a][b];
      }
    }
}