/* -*- 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() */