dw_fluffos_v1/
dw_fluffos_v1/fluffos-1.22c11/
dw_fluffos_v1/fluffos-1.22c11/ChangeLog.old/
dw_fluffos_v1/fluffos-1.22c11/Win32/
dw_fluffos_v1/fluffos-1.22c11/compat/
dw_fluffos_v1/fluffos-1.22c11/compat/simuls/
dw_fluffos_v1/fluffos-1.22c11/include/
dw_fluffos_v1/fluffos-1.22c11/mudlib/
dw_fluffos_v1/fluffos-1.22c11/testsuite/
dw_fluffos_v1/fluffos-1.22c11/testsuite/clone/
dw_fluffos_v1/fluffos-1.22c11/testsuite/command/
dw_fluffos_v1/fluffos-1.22c11/testsuite/data/
dw_fluffos_v1/fluffos-1.22c11/testsuite/etc/
dw_fluffos_v1/fluffos-1.22c11/testsuite/include/
dw_fluffos_v1/fluffos-1.22c11/testsuite/inherit/
dw_fluffos_v1/fluffos-1.22c11/testsuite/inherit/master/
dw_fluffos_v1/fluffos-1.22c11/testsuite/log/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/tests/compiler/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/tests/efuns/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/tests/operators/
dw_fluffos_v1/fluffos-1.22c11/testsuite/u/
dw_fluffos_v1/fluffos-1.22c11/tmp/
dw_fluffos_v1/lib/
dw_fluffos_v1/lib/binaries/cmds/
dw_fluffos_v1/lib/binaries/cmds/creator/
dw_fluffos_v1/lib/binaries/cmds/living/
dw_fluffos_v1/lib/binaries/cmds/player/
dw_fluffos_v1/lib/binaries/d/admin/obj/
dw_fluffos_v1/lib/binaries/d/liaison/
dw_fluffos_v1/lib/binaries/global/virtual/
dw_fluffos_v1/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v1/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v1/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v1/lib/binaries/obj/misc/
dw_fluffos_v1/lib/binaries/obj/misc/buckets/
dw_fluffos_v1/lib/binaries/obj/monster/
dw_fluffos_v1/lib/binaries/obj/reactions/
dw_fluffos_v1/lib/binaries/obj/reagents/
dw_fluffos_v1/lib/binaries/secure/cmds/creator/
dw_fluffos_v1/lib/binaries/secure/master/
dw_fluffos_v1/lib/binaries/std/
dw_fluffos_v1/lib/binaries/std/dom/
dw_fluffos_v1/lib/binaries/std/effects/object/
dw_fluffos_v1/lib/binaries/std/guilds/
dw_fluffos_v1/lib/binaries/std/languages/
dw_fluffos_v1/lib/binaries/std/races/
dw_fluffos_v1/lib/binaries/std/room/
dw_fluffos_v1/lib/binaries/std/room/basic/
dw_fluffos_v1/lib/binaries/std/shops/
dw_fluffos_v1/lib/binaries/std/shops/inherit/
dw_fluffos_v1/lib/binaries/www/
dw_fluffos_v1/lib/cmds/guild-race/
dw_fluffos_v1/lib/cmds/guild-race/crafts/
dw_fluffos_v1/lib/cmds/guild-race/other/
dw_fluffos_v1/lib/cmds/playtester/
dw_fluffos_v1/lib/cmds/playtester/senior/
dw_fluffos_v1/lib/d/admin/
dw_fluffos_v1/lib/d/admin/log/
dw_fluffos_v1/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v1/lib/d/admin/meetings/
dw_fluffos_v1/lib/d/admin/obj/
dw_fluffos_v1/lib/d/admin/room/we_care/
dw_fluffos_v1/lib/d/admin/save/
dw_fluffos_v1/lib/d/dist/
dw_fluffos_v1/lib/d/dist/mtf/
dw_fluffos_v1/lib/d/dist/pumpkin/
dw_fluffos_v1/lib/d/dist/pumpkin/chars/
dw_fluffos_v1/lib/d/dist/pumpkin/desert/
dw_fluffos_v1/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v1/lib/d/dist/pumpkin/hospital/
dw_fluffos_v1/lib/d/dist/pumpkin/inherit/
dw_fluffos_v1/lib/d/dist/pumpkin/map/
dw_fluffos_v1/lib/d/dist/pumpkin/plain/
dw_fluffos_v1/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v1/lib/d/dist/pumpkin/save/
dw_fluffos_v1/lib/d/dist/pumpkin/squash/
dw_fluffos_v1/lib/d/dist/pumpkin/terrain/
dw_fluffos_v1/lib/d/dist/pumpkin/woods/
dw_fluffos_v1/lib/d/dist/start/
dw_fluffos_v1/lib/d/learning/TinyTown/buildings/
dw_fluffos_v1/lib/d/learning/TinyTown/map/
dw_fluffos_v1/lib/d/learning/TinyTown/roads/
dw_fluffos_v1/lib/d/learning/add_command/
dw_fluffos_v1/lib/d/learning/arms_and_weps/
dw_fluffos_v1/lib/d/learning/chars/
dw_fluffos_v1/lib/d/learning/cutnpaste/
dw_fluffos_v1/lib/d/learning/examples/npcs/
dw_fluffos_v1/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v1/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v1/lib/d/learning/functions/
dw_fluffos_v1/lib/d/learning/handlers/
dw_fluffos_v1/lib/d/learning/help_topics/npcs/
dw_fluffos_v1/lib/d/learning/help_topics/objects/
dw_fluffos_v1/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v1/lib/d/learning/help_topics/rooms/
dw_fluffos_v1/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v1/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v1/lib/d/learning/items/
dw_fluffos_v1/lib/d/learning/save/
dw_fluffos_v1/lib/d/liaison/
dw_fluffos_v1/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v1/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v1/lib/db/
dw_fluffos_v1/lib/doc/
dw_fluffos_v1/lib/doc/creator/
dw_fluffos_v1/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v1/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v1/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v1/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v1/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v1/lib/doc/creator/autodoc/std/key/
dw_fluffos_v1/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v1/lib/doc/creator/autodoc/std/map/
dw_fluffos_v1/lib/doc/creator/autodoc/std/race/
dw_fluffos_v1/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v1/lib/doc/creator/files/
dw_fluffos_v1/lib/doc/creator/policy/
dw_fluffos_v1/lib/doc/creator/room/
dw_fluffos_v1/lib/doc/effects/
dw_fluffos_v1/lib/doc/ideas/
dw_fluffos_v1/lib/doc/known_command/
dw_fluffos_v1/lib/doc/lpc/basic_manual/
dw_fluffos_v1/lib/doc/lpc/intermediate/
dw_fluffos_v1/lib/doc/new/add_command/
dw_fluffos_v1/lib/doc/new/handlers/
dw_fluffos_v1/lib/doc/new/living/
dw_fluffos_v1/lib/doc/new/living/race/
dw_fluffos_v1/lib/doc/new/living/spells/
dw_fluffos_v1/lib/doc/new/player/
dw_fluffos_v1/lib/doc/new/room/guild/
dw_fluffos_v1/lib/doc/new/room/outside/
dw_fluffos_v1/lib/doc/new/room/storeroom/
dw_fluffos_v1/lib/doc/object/
dw_fluffos_v1/lib/doc/playtesters/
dw_fluffos_v1/lib/doc/policy/
dw_fluffos_v1/lib/doc/weapons/
dw_fluffos_v1/lib/global/handlers/
dw_fluffos_v1/lib/global/virtual/setup_compiler/
dw_fluffos_v1/lib/include/
dw_fluffos_v1/lib/include/cmds/
dw_fluffos_v1/lib/include/effects/
dw_fluffos_v1/lib/include/npc/
dw_fluffos_v1/lib/include/shops/
dw_fluffos_v1/lib/net/daemon/chars/
dw_fluffos_v1/lib/net/inherit/
dw_fluffos_v1/lib/net/intermud3/
dw_fluffos_v1/lib/net/intermud3/services/
dw_fluffos_v1/lib/net/obj/
dw_fluffos_v1/lib/net/save/
dw_fluffos_v1/lib/net/smnmp/
dw_fluffos_v1/lib/net/snmp/
dw_fluffos_v1/lib/obj/amulets/
dw_fluffos_v1/lib/obj/b_day/
dw_fluffos_v1/lib/obj/examples/
dw_fluffos_v1/lib/obj/food/alcohol/
dw_fluffos_v1/lib/obj/food/chocolates/
dw_fluffos_v1/lib/obj/food/fruits/
dw_fluffos_v1/lib/obj/food/meat/
dw_fluffos_v1/lib/obj/food/nuts/
dw_fluffos_v1/lib/obj/food/seafood/
dw_fluffos_v1/lib/obj/food/vegetables/
dw_fluffos_v1/lib/obj/fungi/
dw_fluffos_v1/lib/obj/furnitures/artwork/
dw_fluffos_v1/lib/obj/furnitures/bathroom/
dw_fluffos_v1/lib/obj/furnitures/beds/
dw_fluffos_v1/lib/obj/furnitures/cabinets/
dw_fluffos_v1/lib/obj/furnitures/chairs/
dw_fluffos_v1/lib/obj/furnitures/chests/
dw_fluffos_v1/lib/obj/furnitures/clocks/
dw_fluffos_v1/lib/obj/furnitures/crockery/
dw_fluffos_v1/lib/obj/furnitures/cupboards/
dw_fluffos_v1/lib/obj/furnitures/cushions/
dw_fluffos_v1/lib/obj/furnitures/fake_plants/
dw_fluffos_v1/lib/obj/furnitures/lamps/
dw_fluffos_v1/lib/obj/furnitures/mirrors/
dw_fluffos_v1/lib/obj/furnitures/outdoor/
dw_fluffos_v1/lib/obj/furnitures/safes/
dw_fluffos_v1/lib/obj/furnitures/shelves/
dw_fluffos_v1/lib/obj/furnitures/sideboards/
dw_fluffos_v1/lib/obj/furnitures/sofas/
dw_fluffos_v1/lib/obj/furnitures/stoves/
dw_fluffos_v1/lib/obj/furnitures/tables/
dw_fluffos_v1/lib/obj/furnitures/wardrobes/
dw_fluffos_v1/lib/obj/handlers/
dw_fluffos_v1/lib/obj/handlers/autodoc/
dw_fluffos_v1/lib/obj/jewellery/anklets/
dw_fluffos_v1/lib/obj/jewellery/bracelets/
dw_fluffos_v1/lib/obj/jewellery/earrings/
dw_fluffos_v1/lib/obj/jewellery/misc/
dw_fluffos_v1/lib/obj/jewellery/necklaces/
dw_fluffos_v1/lib/obj/jewellery/rings/
dw_fluffos_v1/lib/obj/media/
dw_fluffos_v1/lib/obj/misc/buckets/
dw_fluffos_v1/lib/obj/misc/jars/
dw_fluffos_v1/lib/obj/misc/papers/
dw_fluffos_v1/lib/obj/misc/player_shop/
dw_fluffos_v1/lib/obj/misc/shops/
dw_fluffos_v1/lib/obj/misc/traps/
dw_fluffos_v1/lib/obj/monster/
dw_fluffos_v1/lib/obj/monster/godmother/
dw_fluffos_v1/lib/obj/monster/transport/
dw_fluffos_v1/lib/obj/plants/inherit/
dw_fluffos_v1/lib/obj/potions/
dw_fluffos_v1/lib/open/boards/
dw_fluffos_v1/lib/save/autodoc/
dw_fluffos_v1/lib/save/bank_accounts/
dw_fluffos_v1/lib/save/boards/frog/
dw_fluffos_v1/lib/save/books/bed_catalog/
dw_fluffos_v1/lib/save/creators/
dw_fluffos_v1/lib/save/mail/
dw_fluffos_v1/lib/save/mail/p/
dw_fluffos_v1/lib/save/newsrc/b/
dw_fluffos_v1/lib/save/newsrc/c/
dw_fluffos_v1/lib/save/newsrc/d/
dw_fluffos_v1/lib/save/newsrc/f/
dw_fluffos_v1/lib/save/newsrc/p/
dw_fluffos_v1/lib/save/newsrc/s/
dw_fluffos_v1/lib/save/newsrc/w/
dw_fluffos_v1/lib/save/players/c/
dw_fluffos_v1/lib/save/players/d/
dw_fluffos_v1/lib/save/players/g/
dw_fluffos_v1/lib/save/players/p/
dw_fluffos_v1/lib/save/players/s/
dw_fluffos_v1/lib/save/soul/data/
dw_fluffos_v1/lib/save/tasks/
dw_fluffos_v1/lib/save/vaults/
dw_fluffos_v1/lib/secure/cmds/lord/
dw_fluffos_v1/lib/secure/config/
dw_fluffos_v1/lib/secure/items/
dw_fluffos_v1/lib/secure/player/
dw_fluffos_v1/lib/soul/
dw_fluffos_v1/lib/soul/i/
dw_fluffos_v1/lib/soul/j/
dw_fluffos_v1/lib/soul/k/
dw_fluffos_v1/lib/soul/o/
dw_fluffos_v1/lib/soul/q/
dw_fluffos_v1/lib/soul/to_approve/
dw_fluffos_v1/lib/soul/u/
dw_fluffos_v1/lib/soul/v/
dw_fluffos_v1/lib/soul/wish_list/
dw_fluffos_v1/lib/soul/y/
dw_fluffos_v1/lib/soul/z/
dw_fluffos_v1/lib/std/creator/
dw_fluffos_v1/lib/std/effects/
dw_fluffos_v1/lib/std/effects/attached/
dw_fluffos_v1/lib/std/effects/external/
dw_fluffos_v1/lib/std/effects/fighting/
dw_fluffos_v1/lib/std/effects/other/
dw_fluffos_v1/lib/std/environ/
dw_fluffos_v1/lib/std/guilds/
dw_fluffos_v1/lib/std/hospital/
dw_fluffos_v1/lib/std/house/
dw_fluffos_v1/lib/std/house/onebedhouse/
dw_fluffos_v1/lib/std/house/onebedhut/
dw_fluffos_v1/lib/std/house/tworoomflat/
dw_fluffos_v1/lib/std/languages/
dw_fluffos_v1/lib/std/liquids/
dw_fluffos_v1/lib/std/nationality/
dw_fluffos_v1/lib/std/nationality/accents/
dw_fluffos_v1/lib/std/nationality/accents/national/
dw_fluffos_v1/lib/std/nationality/accents/regional/
dw_fluffos_v1/lib/std/npc/goals/
dw_fluffos_v1/lib/std/npc/goals/basic/
dw_fluffos_v1/lib/std/npc/goals/misc/
dw_fluffos_v1/lib/std/npc/inherit/
dw_fluffos_v1/lib/std/npc/plans/
dw_fluffos_v1/lib/std/npc/plans/basic/
dw_fluffos_v1/lib/std/outsides/
dw_fluffos_v1/lib/std/races/shadows/
dw_fluffos_v1/lib/std/room/basic/topography/
dw_fluffos_v1/lib/std/room/controller/
dw_fluffos_v1/lib/std/room/controller/topography/
dw_fluffos_v1/lib/std/room/furniture/games/
dw_fluffos_v1/lib/std/room/furniture/inherit/
dw_fluffos_v1/lib/std/room/inherit/carriage/
dw_fluffos_v1/lib/std/room/inherit/topography/
dw_fluffos_v1/lib/std/room/punishments/
dw_fluffos_v1/lib/std/room/topography/area/
dw_fluffos_v1/lib/std/room/topography/iroom/
dw_fluffos_v1/lib/std/room/topography/milestone/
dw_fluffos_v1/lib/std/shadows/
dw_fluffos_v1/lib/std/shadows/attached/
dw_fluffos_v1/lib/std/shadows/curses/
dw_fluffos_v1/lib/std/shadows/disease/
dw_fluffos_v1/lib/std/shadows/fighting/
dw_fluffos_v1/lib/std/shadows/room/
dw_fluffos_v1/lib/std/shops/controllers/
dw_fluffos_v1/lib/std/shops/objs/
dw_fluffos_v1/lib/std/shops/player_shop/
dw_fluffos_v1/lib/std/shops/player_shop/office_code/
dw_fluffos_v1/lib/std/socket/
dw_fluffos_v1/lib/www/
dw_fluffos_v1/lib/www/external/autodoc/
dw_fluffos_v1/lib/www/external/java/telnet/Documentation/
dw_fluffos_v1/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v1/lib/www/external/java/telnet/examples/
dw_fluffos_v1/lib/www/external/java/telnet/tools/
dw_fluffos_v1/lib/www/pics/
dw_fluffos_v1/lib/www/secure/creator/
dw_fluffos_v1/lib/www/secure/editors/
dw_fluffos_v1/lib/www/secure/survey_results/
/*  -*- LPC -*-  */
/*
 * $Locker:  $
 * $Id: taskmaster.c,v 2.13 2003/02/26 02:29:47 ceres Exp $
 */

/**
 * This handler gives access to the functions needed for the taskmaster
 * system.
 *
 * Rewritten by Shrike to include degrees of success.
 *
 * @author Deutha
 * @see help::tasks
 */
#include <tasks.h>

#define TIME_PER_USER 1800
#define SAVE_FILES "/save/tasks/"

//#define DEBUG "ceres"
//#define WATCH_PLAYER "serec"
//#define MIN_LEVEL 200
#define LOG_STATS 2
#undef LOGGING

mapping stats;
nosave int last_save;

nosave int last;
nosave string skill;
nosave mixed *control;
nosave int * critical_chances;

varargs mixed perform_task( object person, string skill, int difficulty,
                         int tm_type, int degree );
varargs mixed attempt_task( int difficulty, int bonus, int upper,
                            mixed extra, int degree );
varargs mixed attempt_task_e( int difficulty, int bonus, int upper,
                              int half, int degree );
int is_critical( int margin );

/** @ignore yes */
void create() {
   float a, b;

   seteuid( "Root" );

   // It's fairly processor intensive to calculate exponential values,
   // So we'll do them once and keep them in a lookup table. Equation
   // is y = a*e^(b*i), with constants chosen such that
   // y(1) = 1 and y(100) = 1000. (these numbers are chance out of 10000
   // that a critical will occur)
   critical_chances = allocate(100);
   a = 0.93260;
   b = 0.06978;
   for( int i = 0; i < 100; i++ ) {
      critical_chances[i] = a * exp( b * ( i + 1 ) );
   }
} /* create() */

/** @ignore yes */
mapping query_stats( string s_name ) {
  if ( skill != s_name ) {
    skill = s_name;
    if ( file_size( SAVE_FILES + skill +".o" ) > 0 )
      unguarded( (: restore_object, SAVE_FILES + skill :) );
    else
      stats = 0;
  }
  if ( !stats )
    stats = ([ ]);
  return copy( stats );
} /* query_stats() */

/** @ignore yes */
int query_last() { return last; }

/** @ignore yes */
mixed *query_control() { return control; }

/**
 * This function should only be used in the very rare situations
 * where the last skill checked with query_skill_bonus() wasn't the
 * one relevant for the task attempt.
 *
 * @param args an array consisting of ({ object player, string skill })
 */
void set_control( mixed *args ) { control = args; }

/** @ignore yes */
void reset_control() { control = 0; }

/** @ignore yes */
void award_made( string p_name, string o_name, string s_name, int level ) {
  user_event( "inform", p_name +" gains a level in "+ s_name +" from "+
              o_name + " at level " + level, "skill" );
#ifdef LOG_STATS
#if LOG_STATS == 1
  query_stats( s_name );
  if ( !stats[ level ] )
    stats[ level ] = ([ ]);
  stats[ level ][ explode( o_name, "#" )[ 0 ] ]++;
#else
  query_stats("summary");

  // these two just skew the stats so we don't record them.
  if(s_name[<7..] == ".points" || s_name[0..13] == "other.language")
    return;

  s_name = explode(s_name, ".")[0];
  if(!stats[s_name])
    stats[s_name] = ({ ({ 0 , 0 }),  ({ 0, 0 }),  ({ 0, 0 }) , ({0,0})});

  switch(level) {
  case 0..149:
    stats[s_name][0] = ({ stats[s_name][0][0], stats[s_name][0][1]+1 });
    break;
  case 150..299:
    stats[s_name][1] = ({ stats[s_name][1][0], stats[s_name][1][1]+1 });
    break;
  case 300..599:
    stats[s_name][2] = ({ stats[s_name][2][0], stats[s_name][2][1]+1 });
    break;
  default:
    stats[s_name][3] = ({ stats[s_name][3][0], stats[s_name][3][1]+1 });
    break;
  }

  if(last_save < time() - 300) {
    unguarded( (: save_object, SAVE_FILES + "summary" :) );
    last_save = time();
  }
#endif
#endif
} /* award_made() */

#if LOG_STATS == 2
/** @ignore yes */
void skill_checked(string s_name, int level) {
  query_stats("summary");
  if(s_name[<7..] == ".points" || s_name[0..13] == "other.language")
    return;
  s_name = explode(s_name, ".")[0];
  if(!stats[s_name])
    stats[s_name] = ({ ({0,0}), ({0,0}), ({0,0}), ({0,0}), ({0,0}) });

  switch(level) {
  case 0..49:
    stats[s_name][0] = ({ stats[s_name][0][0]+1, stats[s_name][0][1] });
    break;
  case 50..149:
    stats[s_name][1] = ({ stats[s_name][1][0]+1, stats[s_name][1][1] });
    break;
  case 150..299:
    stats[s_name][2] = ({ stats[s_name][2][0]+1, stats[s_name][2][1] });
    break;
  case 300..599:
    stats[s_name][3] = ({ stats[s_name][3][0]+1, stats[s_name][3][1] });
    break;
  default:
    stats[s_name][4] = ({ stats[s_name][4][0]+1, stats[s_name][4][1] });
    break;
  }
  if(last_save < time() - 300) {
    unguarded( (: save_object, SAVE_FILES + "summary" :) );
    last_save = time();
  }
}
#endif

/** @ignore yes */
string *query_skill_files() {
  return unguarded( (: get_dir, SAVE_FILES +"*.o" :) );
} /* query_skill_files() */

/** @ignore yes */
void clear_skill_files() {
  string word;
  foreach ( word in unguarded( (: get_dir, SAVE_FILES +"*.o" :) ) )
    unguarded( (: rm, SAVE_FILES + word :) );
  skill = 0;
} /* clear_skill_files() */

/**
 * This function will compare the skills of two objects and return which one
 * won and if the winner got a TM.
 * With equal skills the chances are 50/50 as to who will win. As the balance
 * shifts so do the chances. Additionally a modifier can be applied to
 * account for conditions favouring one or the other. This should be
 * considered a percentage eg. -50 will add 50% to the defenders chances of
 * winning.
 * If
 *
 * @param offob The attacking object
 * @param offskill The name of the skill the attacker is using
 * @param deffob The defending object
 * @param deffskill The name of the skill the defender is using
 * @param modifier The percentage modifier
 * @param off_tm_type This should be one of the standard definitions in
 * /include/tasks.h and is for the attacker
 * @param def_tm_type This should be one of the standard definitions in
 * /include/tasks.h and is for the defender
 * @param degree Enable the degree-of-success code.  If this is used, the
 *  return value becomes a class instead of an int.
 *
 * @example
 * This is an example of the simpler, binary success/failure behaviour.
 *
 * switch(TASKER->compare_skills(attacker, "fighting.combat.melee.sharp",
 *                               defender, "fighting.combat.parry.unarmed",
 *                               25, TM_COMMAND, TM_FREE) {
 * case OFFAWARD:
 *   tell_object(attacker, "%^YELLOW%^You manage to grasp one of the "
 *                         "principles of slicing people up more firmly."
 *                         "%^RESET%^\n");
 * case OFFWIN:
 *   say(defender->one_short() + " loses an arm!\n");
 *   break;
 * case DEFAWARD:
 *   tell_object(defender, "%^YELLOW%^You feel better able to keep your arms "
 *                         "attached when parrying unarmed.%^RESET%^\n");
 * case DEFWIN:
 *   say(defender->one_short() + " keeps " + defender->query_possesive() +
 *       " arm attached.\n");
 *   break;
 * }
 *
 * @example
 * This is an example of the finer grained degree-of-success behaviour.
 *
 * class tasker_result tr;
 *
 * tr = (class tasker_result)TASKER->compare_skills(
 *   attacker, "fighting.combat.melee.sharp",
 *   defender, "fighting.combat.parry.unarmed",
 *   25, TM_COMMAND, TM_FREE, 1 );
 *
 * switch( tr->result ) {
 * case OFFAWARD:
 *   tell_object(attacker, "%^YELLOW%^You manage to grasp one of the "
 *                         "principles of slicing people up more firmly."
 *                         "%^RESET%^\n");
 * case OFFWIN:
 *   switch( tr->degree ) {
 *   case TASKER_MARGINAL:
 *     say( "You just barely scrape your opponent's hand.\n" );
 *     break;
 *   case TASKER_NORMAL:
 *     say( "You slice into your opponent's arm.\n" );
 *     break;
 *   case TASKER_EXCEPTIONAL:
 *     say( "You slice deeply into your opponent's muscle and sinew, and "
 *       "he screams in pain!\n" );
 *     break;
 *   case TASKER_CRITICAL:
 *     say( "You slice your opponent's arm clean off.  Blood spurts "
 *       "everywhere like a cherry slurpee machine gone insane!\n" );
 *     break;
 *   }
 *   break;
 * case DEFAWARD:
 *   tell_object(defender, "%^YELLOW%^You feel better able to keep your arms "
 *                         "attached when parrying unarmed.%^RESET%^\n");
 * case DEFWIN:
 *   switch( tr->degree ) {
 *      ...
 *      similar code in here...
 *      ...
 *   }
 *   break;
 * }
 *
 * @see perform_task()
 * @see tasker_result
 *
 */
varargs mixed compare_skills( object offob, string offskill,
                              object defob, string defskill,
                              int modifier, int off_tm_type,
                              int def_tm_type, int degree )
{
  int offbonus, defbonus;
  int perc, chance;
  int success_margin, res, deg;

  // Make sure that we actually have two objects and the associated skills
  // to check against..
  if( !offob || !defob || !offskill || !defskill )
    return BARF;

  offbonus = offob->query_skill_bonus(offskill);
  defbonus = defob->query_skill_bonus(defskill);

  if( !defbonus ) {
    defbonus = 1;
  }
  if( !offbonus ) {
    offbonus = 1;
  }

  // This gives us a sliding scale where off == def is 50 and it climbs
  // from there hitting 0 at def = 2*off and 100 at off = 2*def.
  (offbonus > defbonus) ?
    perc = (50 * offbonus * offbonus) / (offbonus * defbonus) :
    perc = 100 - (50 * defbonus * defbonus) / (offbonus * defbonus);

  perc += modifier; // the difficulty may be weighted.

  // Always got a small chance :)
  if(perc > 99)
    perc = 99;
  if(perc < 1)
    perc = 1;

  chance = random(100);
  success_margin = perc - chance;
  if( success_margin > 0 ) {
    // winner is 1 now do a TM check.
    switch( perform_task( offob, offskill, defbonus-modifier,
                          off_tm_type, 0 ) )
    {
    case AWARD:
      res = OFFAWARD;
      break;
    default:
      res = OFFWIN;
    }

    if( degree ) {
      if( is_critical(success_margin) ) {
        deg = TASKER_CRITICAL;
      } else {
         if( success_margin < TASKER_MARGINAL_UPPER ) {
           deg = TASKER_MARGINAL;
         } else if( success_margin < TASKER_NORMAL_UPPER ) {
           deg = TASKER_NORMAL;
         } else {
           deg = TASKER_EXCEPTIONAL;
         }
      }

      return new( class tasker_result,
                  result : res,
                  degree : deg,
                  raw    : success_margin );
    } else {
      return res;
    }
  } else {
    // winner is 2
    switch( perform_task( defob, defskill, offbonus-modifier,
                          def_tm_type, 0 ) ) {
    case AWARD:
      res = DEFAWARD;
      break;
    default:
      res = DEFWIN;
    }

    if( degree ) {
      if( is_critical(success_margin) ) {
        deg = TASKER_CRITICAL;
      } else {
         if( -success_margin < TASKER_MARGINAL_UPPER ) {
           deg = TASKER_MARGINAL;
         } else if( -success_margin < TASKER_NORMAL_UPPER ) {
           deg = TASKER_NORMAL;
         } else {
           deg = TASKER_EXCEPTIONAL;
         }
      }

      return new( class tasker_result,
                  result : res,
                  degree : deg,
                  raw    : success_margin );
    } else {
      return res;
    }
  }
}

/**
 * This function will perform a task.  It handles all the stuff about
 * looking up the skill, and giving the tm advance, but doesn't give
 * any messages to the player, you'll have to do that.
 * If you set the 'degree' parameter to 1, it will return a 3-element
 * class instead of the standard binary success/failure result.
 *
 * @param person the one attempting the task (could be any living thing)
 * @param skill the skill tested against
 * @param difficulty the lowest bonus where the attempt can succeed
 * @param tm_type This should use one of the standard definitions in 
 * /include/tasks.h. They are:
 *        TM_FIXED - for use where the difficulty is a fixed value
 *        TM_FREE  - for use when the tm attempt doesn't cost anything.
 *        TM_CONTINUOUS - for use in continuous actions eg. combat or sneak
 *        TM_COMMAND - for use with guild commands
 *        TM_RITUAL - when the action is a ritual
 *        TM_SPELL - when the action is a spell
 *        TM_NONE - when you want there to be no chance of a tm
 * @param degree Whether or not to use the degree of success code
 *
 * @return BARF if something screwed up, AWARD if the task succeeded, and
 * should give an advance, SUCCEED if it succeeded, FAIL if it failed. If
 * the degree of success code is used, it will pass out a 3-element class.
 *
 * @example
 * This is an example of the simpler, binary success/failure behaviour.
 *
 * switch(TASKER->perform_task( person, "covert.manipulation.stealing",
 *                              300, TM_COMMAND ) ) {
 *  case AWARD :
 *    tell_object( person, "%^YELLOW%^You manage to grasp the principles "
 *                "of stealing more firmly.%^RESET%^\n" );
 *    // Note, no break;
 *  case SUCCEED :
 *    // Whatever happens when it succeeds
 *    break;
 *  default :
 *    // Whatever happens when it fails
 * }
 *
 * @example
 * This is an example of the finer grained degree-of-success behaviour.
 *
 * class tasker_result tr;
 *
 * tr = (class tasker_result)TASKER->perform_task( person,
 *   "other.direction", 300, TM_FREE, 1 );
 * switch( tr->result ) {
 * case AWARD:
 *   tell_object(attacker, "%^YELLOW%^You feel very aligned!%^RESET%^\n" );
 * case SUCCEED:
 *    switch( tr->degree ) {
 *    case TASKER_MARGINAL:
 *       say( "You think he went thataway. Maybe.\n" );
 *       break;
 *    case TASKER_NORMAL:
 *       say( "You're pretty sure he went that way.\n" );
 *       break;
 *    case TASKER_EXCEPTIONAL:
 *       say( "He definitely went thataway!\n" );
 *       break;
 *    case TASKER_CRITICAL:
 *       say( "Your surity that he went thataway is so powerful that even "
 *         "if he didn't go thataway, he'll be in that direction.\n" );
 *       break;
 *   }
 *   break;
 * case FAIL:
 *   switch( tr->degree ) {
 *      ...
 *      Boy, it's a good thing I've already explained this, or I'd be
 *      pretty hard-pressed to think of an example of a critical failure
 *      for other.direction.  You get the idea.
 *      ...
 *   }
 *   break;
 * }
 *
 * @see compare_skills()
 * @see tasker_result
 *
 */
varargs mixed perform_task( object person, string skill, int difficulty,
                            int tm_type, int degree ) {
  mixed result;
  int bonus, upper;
  
  // Make sure that we actually have an object and a skill to check against..
  if( !person || !skill )
    return BARF;
  
  bonus = person->query_skill_bonus( skill );
  
  switch(tm_type) {
  case TM_FIXED:
    result = attempt_task( difficulty, bonus, 100, 0, degree );
    break;
  case TM_FREE:
    result = attempt_task( difficulty, bonus, 25, 0, degree );
    break;
  case TM_CONTINUOUS:
    result = attempt_task( difficulty, bonus, 50, 0, degree );
    break;
  case TM_COMMAND:
    if( explode( skill, "." )[0] == "covert" )
      result = attempt_task_e 
  ( difficulty, bonus, 60, 40, degree );
    else
      result = attempt_task( difficulty, bonus, 100, 0, degree );
    break;
  case TM_RITUAL:
    result = attempt_task_e( difficulty, bonus, 50, 25, degree );
    break;
  case TM_SPELL:
    result = attempt_task_e( difficulty, bonus, 60, 40, degree );
    break;
  case TM_NONE:
    result = attempt_task_e(difficulty, bonus, 1, 0, degree);
    if(classp(result) && ( (class tasker_result)result )->result == AWARD) {
      result->result = SUCCEED;
    } else if( result == AWARD ) {
      result = SUCCEED;
    }
    break;
  default:                     // this is for backward compatibility
#ifdef LOGGING
    if(file_name(previous_object())[0..2] != "/w/")
      log_file("ATTEMPT_TASK",
               "%s Object %s using old perform_task [%d, %d]\n",
               ctime(time()), base_name(previous_object()), tm_type, degree );
#endif
    upper = tm_type;
    if ( !upper )
      upper = 100;
    result = attempt_task( difficulty, bonus, upper, 0, degree );   
  }
  
  
  if(classp(result) && ( (class tasker_result)result )->result == AWARD ||
     result == AWARD) {
    
    if(person->advancement_restriction() ||
       !person->add_skill_level(skill, 1, previous_object())) {
      if(classp(result))
        result->result = SUCCEED;
      else
        result = SUCCEED;
    }
  }
  return result;
} /* perform_task() */

/*
 * This function will attempt a task and return whether it succeedd.<br>
 * <br>
 * Chance of      /|\<br>
 * success  100% __|            ______..<br>
 *                 |           /<br>
 *                 |          /<br>
 *                 |         /<br>
 *            0% __| .._____/<br>
 *                 |_____________________\   Player's bonus<br>
 *                          |   |        /<br>
 *                 difficulty   difficulty + margin<br>
 * <br>
 * Chance of       /|\<br>
 * gaining  upper __|        .<br>
 *                  |        |\<br>
 *                  |        | \<br>
 *                  |        |  \<br>
 *             0% __| .._____|   \_____..<br>
 *                  |_____________________\  Player's bonus<br>
 *                           |   |        /   <br>
 *                  difficulty   difficulty + margin<br>
 * <br>
 * @param difficulty the lowest bonus where the attempt can succeed
 * @param bonus the bonus the player has in the relevant skill
 * @param upper the maximum chance of getting an advance
 * @param extra the margin control. If it is an int, the margin is set
 * to extra. It it's 0 the margin will be calculated automatically from
 * the difficulty as 3*sqrt(difficulty), if it's an array, it'll be
 * calculated as extra[0]+extra[1]*sqrt(difficulty)
 * @param degree set to one if we want to get a degree of success value
 * returned.
 * @return BARF if something screwed up, AWARD if the task succeeded, and
 * should give an advance, SUCCEED if it succeeded, FAIL if it failed. If
 * degree is one, return an array, where element 0 is one of the above
 * codes, and element 1 is the percentage by which the task was succeeded
 * or failed.
 */
/**
 * Do not use this function. Use perform_task instead.
 * @see perform_task()
 */
varargs mixed attempt_task( int difficulty, int bonus, int upper,
                            mixed extra, int degree )
{
   int margin, success_margin, deg, res;
   float tmp;

#ifdef LOGGING
   if( call_stack(2)[1] != "perform_task" &&
       base_name( previous_object() ) != "/std/effects/fighting/combat" &&
       file_name( previous_object() )[0..2] != "/w/" )
   {
     log_file("ATTEMPT_TASK", "%s Object %s is using old attempt_task "
              "[%O]\n",
              ctime( time() ), base_name( previous_object() ), call_stack(2) );
   }
#endif

#if LOG_STATS == 2
   if(pointerp(control) && control[1]) {
      skill_checked(control[1], control[0]->query_skill(control[1]));
   }
#endif

   /* If the bonus is below the difficulty, they fail. */
   if ( bonus < difficulty ) {
#ifdef DEBUG
      if(pointerp(control) && control[0]->query_name() == WATCH_PLAYER &&
         find_player(DEBUG))
      {
         tell_creator( DEBUG, "%^RED%^"
           + sprintf( "TM: %s Skill: %s [%d] [%d] Failed\n",
                      control[0]->query_name(), control[1], bonus, difficulty )
           + "%^RESET%^" );
      }
#endif
      if( degree ) {
         return new( class tasker_result,
                     result : FAIL,
                     degree : ( is_critical( -100 ) ?
                                TASKER_CRITICAL :
                                TASKER_EXCEPTIONAL ),
                     raw    : -100 );
      } else {
         return FAIL;
      }
   }

   /* Work out the margin between total failure and total success. */
   if ( !extra )
      margin = 3 * sqrt( difficulty );
   else {
      if ( intp( extra ) )
         margin = extra;
      if ( pointerp( extra ) )
         margin = extra[ 0 ] + extra[ 1 ] * sqrt( difficulty );
   }
   if ( !margin )
      return BARF;

   /* If the bonus is above the margin, they succeed. */
   if ( bonus > difficulty + margin ) {
#ifdef DEBUG
     if(pointerp(control) &&
        WATCH_PLAYER || control[0]->query_name() == WATCH_PLAYER &&
        find_player(DEBUG))
       tell_object(find_player(DEBUG), "%^RED%^" + sprintf("TM: %s Skill: %s [%d] [%d] [%d] Succeeded\n", control[0]->query_name(), control[1], bonus, difficulty, margin) + "%^RESET%^");
#endif
      if( degree ) {
         return new( class tasker_result,
                     result : SUCCEED,
                     degree : ( is_critical(100) ?
                                TASKER_CRITICAL :
                                TASKER_EXCEPTIONAL ),
                     raw    : 100 );
      } else {
         return SUCCEED;
      }
   }

   /* In the margin, they might fail. */
   if(!margin)
     margin = 1;

   success_margin = ( ( 100 * ( bonus - difficulty ) ) / margin ) - random(100);
   if ( success_margin <= 0 ) {
#ifdef DEBUG
     if(pointerp(control) &&
        control[0]->query_name() == WATCH_PLAYER &&
        find_player(DEBUG))
       tell_object(find_player(DEBUG), "%^RED%^" + sprintf("TM: %s Skill: %s [%d] [%d] Failed (2)\n", control[0]->query_name(), control[1], bonus, difficulty) + "%^RESET%^");
#endif

      if( degree ) {
         if( is_critical(success_margin) ) {
            deg = TASKER_CRITICAL;
         } else {
            if( -success_margin < TASKER_MARGINAL_UPPER ) {
               deg = TASKER_MARGINAL;
            } else if( -success_margin < TASKER_NORMAL_UPPER ) {
               deg = TASKER_NORMAL;
            } else {
               deg = TASKER_EXCEPTIONAL;
            }
         }

         return new( class tasker_result,
                     result : FAIL,
                     degree : deg,
                     raw    : success_margin );
      } else {
         return FAIL;
      }
   }

   /* If information is available, adjust the chance to award based on stats.*/
   if ( pointerp( control ) ) {
#ifdef DEBUG
     if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG), "%^RED%^" +
                   sprintf("TM: %s Skill: %s [%d,%d] OU: %d ",
                           control[0]->query_name(),
                           control[1], bonus,
                           control[0]->query_skill(control[1]),
                           upper));
#endif

     // Reduce the upper dependaont on their stats.
     upper = (int)control[ 0 ]->stat_modify(upper, control[ 1 ] );

#ifdef DEBUG
     if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG),
                   sprintf("SU: %d ", upper));
#endif

     // Reduce the upper dependant on their level.
     tmp = exp((control[0]->query_skill(control[1])-BASE) / DECAY);
     upper = to_int(upper / tmp) - MODIFIER;

#ifdef DEBUG
     if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG), sprintf("LU: %d ", upper));
#endif

     // prevent upper going negative
     if(upper < 0)
       upper = 0;

#ifdef DEBUG
      if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG), sprintf("FU: %d\n",
                                                 upper) + "%^RESET%^");
#endif
   }
#ifdef DEBUG
   else
     if(find_player(DEBUG))
       tell_object(find_player(DEBUG), sprintf("No control: %O\n",
                                                 previous_object(-1)));
#endif
   /* If they succeed, they might be awarded a level. */
     if(!margin)
        margin = 1;

   if( random(100) < ( upper * ( difficulty + margin - bonus ) ) / margin )
      res = AWARD;
   else
      res = SUCCEED;

   if( degree ) {
      if( is_critical(success_margin) ) {
         deg = TASKER_CRITICAL;
      } else {
         if( success_margin < TASKER_MARGINAL_UPPER ) {
            deg = TASKER_MARGINAL;
         } else if( success_margin < TASKER_NORMAL_UPPER ) {
            deg = TASKER_NORMAL;
         } else {
            deg = TASKER_EXCEPTIONAL;
         }
      }

      return new( class tasker_result,
                  result : res,
                  degree : deg,
                  raw    : success_margin );
   } else {
      return res;
   }
} /* attempt_task() */

/*
 * This one acts like the other one except that the failure chance
 * starts at 100% at the difficulty and is halved every (half) bonus levels.
 *
 * @param difficulty the lowest bonus where the attempt can succeed
 * @param bonus the bonus the player has in the relevant skill
 * @param upper the maximum chance of getting an advance
 * @param half every time the bonus rise by half, the failure
 * change is halved
 *
 * @return BARF if something screwed up, AWARD if the task succeeded and
 * should give an advance, SUCCEED if it succeeded, FAIL if it failed.
 */
/**
 * Do not use this function. Use perform_task instead.
 * @see perform_task()
 */
varargs mixed attempt_task_e( int difficulty, int bonus, int upper,
                              int half, int degree )
{
   float fail_chance;
   float tmp;
   int success_margin, deg, res;

#ifdef LOGGING
   if(call_stack(2)[1] != "perform_task" &&
      base_name(previous_object()) != "/std/effects/fighting/combat" &&
      file_name(previous_object())[0..2] != "/w/") {
     log_file("ATTEMPT_TASK", "%s Object %s is using old attempt_task "
              "[%O]\n",
              ctime(time()), base_name(previous_object()), call_stack(2));
   }
#endif

#if LOG_STATS == 2
   if(pointerp(control) && control[1]) {
     skill_checked(control[1], control[0]->query_skill(control[1]));
   }
#endif

   /* If the bonus is below the difficulty, they fail. */
   if ( bonus < difficulty ) {
      if( degree ) {
         return new( class tasker_result,
                     result : FAIL,
                     degree : ( is_critical( -100 ) ?
                                TASKER_CRITICAL :
                                TASKER_EXCEPTIONAL ),
                     raw    : -100 );
      } else {
         return FAIL;
      }
   }

   if( !half ) /* Approximate old behaviour. */
      half = 6 * sqrt( difficulty );
   // We don't want to divide by 0...
   if( !half )
      half = 1;

   fail_chance = exp( ( -0.693 * ( bonus - difficulty ) ) / half );
   success_margin = ( random( 1000 ) - ( 1000 * fail_chance ) ) / 10;

   if ( success_margin < 0 ) {
      if( degree ) {
         if( is_critical(success_margin) ) {
            deg = TASKER_CRITICAL;
         } else {
            if( -success_margin < TASKER_MARGINAL_UPPER ) {
               deg = TASKER_MARGINAL;
            } else if( -success_margin < TASKER_NORMAL_UPPER ) {
               deg = TASKER_NORMAL;
            } else {
               deg = TASKER_EXCEPTIONAL;
            }
         }

         return new( class tasker_result,
                     result : FAIL,
                     degree : deg,
                     raw    : success_margin );
      } else {
         return FAIL;
      }
   }

   /* If information is available, adjust the chance to award based on stats.*/
   if ( pointerp( control ) ) {

#ifdef DEBUG
     if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG), "%^RED%^" +
                   sprintf("TM: %s Skill: %s [%d,%d] OU: %d ",
                           control[0]->query_name(),
                           control[1], bonus,
                           control[0]->query_skill(control[1]),
                           upper));
#endif

     // Reduce the upper dependaont on their stats.
     upper = (int)control[ 0 ]->stat_modify( upper, control[ 1 ] );

#ifdef DEBUG
     if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG),
                   sprintf("SU: %d ", upper));
#endif

     // Reduce the upper dependant on their level.
     tmp = exp((control[0]->query_skill(control[1])-BASE) / DECAY);
     upper = to_int(upper / tmp) - E_MODIFIER;

#ifdef DEBUG
     if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG), sprintf("LU: %d ", upper));
#endif

     // prevent upper going negative and round down to zero from 10
     if(upper < 0)
       upper = 0;
#ifdef DEBUG
      if(find_player(DEBUG) && bonus >= MIN_LEVEL)
       tell_object(find_player(DEBUG), sprintf("FU: %d\n",
                                                 upper) + "%^RESET%^");
#endif

   }

   /* If they succeed, they might be awarded a level. */
   if ( random(1000) < ( upper * fail_chance * 10 )
    &&  bonus < difficulty + ( half * 5 ) ) {
      res = AWARD;
   } else {
      res = SUCCEED;
   }

   if( degree ) {
      if( is_critical(success_margin) ) {
         deg = TASKER_CRITICAL;
      } else {
         if( success_margin < TASKER_MARGINAL_UPPER ) {
            deg = TASKER_MARGINAL;
         } else if( success_margin < TASKER_NORMAL_UPPER ) {
            deg = TASKER_NORMAL;
         } else {
            deg = TASKER_EXCEPTIONAL;
         }
      }

      return new( class tasker_result,
                  result : res,
                  degree : deg,
                  raw    : success_margin );
   } else {
      return res;
   }
} /* attempt_task_e() */

/**
 * Figure out whether a given margin of success/failure will result
 * in a critical or not.  Takes an integer between -100 and 100,
 * representing the margin by which a skillcheck succeeded, and does
 * some logarithmic-type equations to figure out whether it should
 * result in a critical or not.
 * @param margin the margin of success.
 * @return 1 if the result is a critical, 0 otherwise.
*/
int is_critical( int margin ) {
   if ( margin < 0 ) {
      margin *= -1;
   }

   if ( margin > 100 ) {
      margin = 100;
   }

   if ( margin == 0 ) {
      return 0;
   }

   // critical chances are calculated above in create() so that the chance
   // at a margin of 1 is 1/10000 and the chance at a margin of 100 is 1/10.
   return random(10000) < critical_chances[ margin - 1 ];
} /* is_critical() */