/* * Modifications : * * Changed to use general.language instead of other.language - Shaydz * * Tried to improve the level of information returned by the * perform task function. - Shiannar. * * Added a new function to handle TMing points skills - Sandoz * */ #include <tasks.h> #define SAVE_FILES "/save/tasks/" #define TIME_PER_USER 1800 // #define DEBUG "shiannar" #define WATCH_PLAYER "raistlin" #define MIN_LEVEL 20 #define LOG_STATS 2 #undef LOGGING mapping stats; nosave int last_save; nosave int last; nosave string skill; nosave mixed *control; /** @ignore yes */ void create() { seteuid( "Root" ); } varargs mixed perform_task( object person, string skill, int difficulty, int tm_type, int use_class); varargs mixed attempt_task( int difficulty, int bonus, int upper, mixed extra, int use_class ); varargs mixed attempt_task_e( int difficulty, int bonus, int upper, int half, int use_class ); int point_tasker( object person, string type, int amount ); /** @ignore yes */ int is_valid_tm( object person, string skill ) { skill = explode( skill, "." )[0]; if( interactive(person) && !person->query_property("tm_"+skill) ) { person->remove_property("player_tm"); // remove the old prop. person->add_property("tm_"+skill, 1, 180 ); return 1; } return 0; } /* is_valid_tm() */ /** @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] == "general.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] == "general.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]+1, stats[s_name][0][1] }); break; case 150..299: stats[s_name][1] = ({ stats[s_name][1][0]+1, stats[s_name][1][1] }); break; case 300..599: stats[s_name][2] = ({ stats[s_name][2][0]+1, stats[s_name][2][1] }); break; default: stats[s_name][3] = ({ stats[s_name][3][0]+1, stats[s_name][3][1] }); break; } if( last_save < time() - 300 ) { unguarded( (: save_object, SAVE_FILES + "summary" :) ); last_save = time(); } } /* skill_checked() */ #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. * * @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 fo 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 * * @example * * 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; * } * * @see perform_task * */ int compare_skills( object offob, string offskill, object defob, string defskill, int modifier, int off_tm_type, int def_tm_type ) { int offbonus, defbonus, perc, chance; offbonus = offob->query_skill_bonus(offskill); defbonus = defob->query_skill_bonus(defskill); perc = (offbonus * 100) / (offbonus + defbonus); perc += modifier; // the difficulty may be weighted. chance = random(100); if( chance < perc ) { // winner is 1, now do a TM check. switch( perform_task( offob, offskill, defbonus-modifier, off_tm_type ) ) { case AWARD: return OFFAWARD; default: return OFFWIN; } } else { // winner is 2 switch( perform_task( defob, defskill, offbonus-modifier, def_tm_type ) ) { case AWARD: return DEFAWARD; default: return DEFWIN; } } } /* compare_skills() */ /** * This function will attempt 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. * * @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 * @param use_class This is 0 when you don't want a class returned, * and 1 when you do. * * @return BARF if something screwed up, AWARD if the task succeeded, and * should give an advance, SUCCEED if it succeeded, FAIL if it failed. * * @example * * switch(TASKER->perform_task(person,"covert.manipulation.stealing", 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 * } */ varargs mixed perform_task( object person, string skill, int difficulty, int tm_type, int use_class ) { int bonus, upper, extra; mixed result; bonus = person->query_skill_bonus( skill ); if( difficulty ) extra = 6 * sqrt(difficulty); switch( tm_type ) { case TM_FIXED: result = attempt_task( difficulty, bonus, 100, extra, use_class ); break; case TM_FREE: result = attempt_task( difficulty, bonus, 25, extra, use_class ); break; case TM_CONTINUOUS: result = attempt_task( difficulty, bonus, 50, extra, use_class ); break; case TM_COMMAND: if( explode(skill, ".")[0] == "covert" ) result = attempt_task_e( difficulty, bonus, 50, 30, use_class ); else result = attempt_task( difficulty, bonus, 60, extra, use_class ); break; case TM_RITUAL: // result = attempt_task_e( difficulty, bonus, 30, 40, use_class ); // approximate old behaviour - Sandoz. result = attempt_task( difficulty, bonus, 18, 100 + to_int( 5 * sqrt( to_float( difficulty < 0 ? 0 : difficulty ) ) ), use_class ); break; case TM_SPELL: result = attempt_task_e( difficulty, bonus, 30, 40, use_class ); break; case TM_NONE: result = attempt_task_e( difficulty, bonus, 1, extra, use_class ); if( result == AWARD ) result = SUCCEED; break; default: // this is for backward compatibility #ifdef LOGGING if( file_name(PO)[0..2] != "/w/" ) log_file("ATTEMPT_TASK", "%s Object %s using old perform_task [%d, %d]\n", ctime(time()), base_name(PO), tm_type, use_class ); #endif upper = tm_type; extra = use_class; if( !upper ) upper = 100; result = attempt_task( difficulty, bonus, upper, extra ); } if( classp(result) ) { if( result->result == AWARD ) { if( !( random(100) < TM_RATES[explode(skill, ".")[0]] ) || !is_valid_tm( person, skill ) || !person->add_skill_level( skill, 1, PO ) ) { result->result = SUCCEED; // No advance. } } } else { if( result == AWARD ) { if( !( random(100) < TM_RATES[explode(skill, ".")[0]] ) || !is_valid_tm( person, skill ) || !person->add_skill_level( skill, 1, PO ) ) { result = SUCCEED; // No advance. } } } return result; } /* perform_task() */ /** * This function will attempt to figure out if a tm should be given * in the points skill used. It handles all the stuff about looking * up the skill, giving the tm advance, and reducing the guild points. * It will also give a generic tm message to the player. * * @param person the one attempting the points task (could be any living thing) * @param type the type (covert, magic etc.) of points skill to check against * @param amount the amount of guild points to check against * @return 1 if player has enough available guild points of the type, 0 if not * * @example * * if( !TASKER->point_tasker( person, "covert", 80 ) ) { * add_failed_mess("You can't concentrate enough to hide."); * return 0; * } */ int point_tasker( object person, string type, int amount ) { string skill; int m, level, bonus; float b; if( ( amount < 1 ) || ( person->query_specific_gp( type ) < amount ) ) return 0; person->adjust_gp( -amount ); skill = type+".points"; level = (int)person->query_skill( skill ); bonus = (int)person->query_skill_bonus( skill ); b = to_float( ( bonus < 50 ? 50 : bonus ) + ( level < 50 ? 50 : level ) ); m = (int)person->stat_modify( 10, skill, 1 ); if( random(1000) < to_int( 25 * sqrt( to_float( 20 + amount ) ) * ( m / b ) ) && is_valid_tm( person, type ) && person->add_skill_level( skill, 1, PO ) ) tell_object( person, "%^YELLOW%^You feel more able to concentrate " "on this task than you thought.%^RESET%^\n"); return 1; } /* point_tasker() */ /* * 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) * @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 */ mixed attempt_task( int difficulty, int bonus, int upper, mixed extra, int use_class ) { int margin, result; float tmp; class task_class_result return_class; #ifdef LOGGING if( call_stack(2)[1] != "perform_task" && base_name(PO) != "/std/effects/fighting/combat" && file_name(PO)[0..2] != "/w/") { log_file("ATTEMPT_TASK", "%s Object %s is using old attempt_task " "[%O]\n", ctime(time()), base_name(PO), 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( use_class ) { return_class = new( class task_class_result ); return_class->result = FAIL; // Compatible with degree of failure,and not always critical. return_class->degree = ( 0 - random(100) ); return return_class; } else { return FAIL; } } /* Work out the margin between total failure and total success. */ if( !extra ) if( !difficulty ) margin = 6; else margin = 6 * sqrt( difficulty ); else { if( intp( extra ) ) margin = extra; if( pointerp( extra ) ) margin = extra[ 0 ] + extra[ 1 ] * sqrt( difficulty ); } if( !margin ) return BARF; else /* If the bonus is above the margin, they succeed. */ if( bonus > difficulty + margin ) { #ifdef DEBUG if( pointerp(control ) && find_player(DEBUG) && WATCH_PLAYER || control[0]->query_name() == WATCH_PLAYER ) TCRE( DEBUG, sprintf("%^RED%^TM: %s Skill: %s [%d] [%d] [%d] " "Succeeded%^RESET%^", control[0]->query_name(), control[1], bonus, difficulty, margin ) ); #endif if( use_class ) { return_class = new( class task_class_result ); return_class->result = SUCCEED; //Compatible with degree of success, and not always critical. return_class->degree = (random(100)); return return_class; } else { return SUCCEED; } } /* In the margin, they might fail. */ if( !margin ) margin = 1; result = ( -99 + random(100) + ( ( bonus - difficulty ) + 100 / margin ) ); if( result < 1 ) result -= 1; if( use_class ) { return_class = new( class task_class_result ); return_class->degree = result; } if( result < 0 ) { #ifdef DEBUG if( pointerp(control) && find_player(DEBUG) && control[0]->query_name() == WATCH_PLAYER ) TCRE( DEBUG, sprintf("%^RED%^TM: %s Skill: %s [%d] [%d] " "Failed (2)%^RESET%^", control[0]->query_name(), control[1], bonus, difficulty ) ); #endif if( use_class ) { return_class->result = FAIL; return return_class; } 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_creator( DEBUG, "%^RED%^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 dependant on their stats. upper = (int)control[ 0 ]->stat_modify( upper, control[ 1 ] ); #ifdef DEBUG if( find_player(DEBUG) && bonus >= MIN_LEVEL) tell_creator( DEBUG, "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_creator( DEBUG, "LU: %d ", upper ); #endif // prevent upper going negative if( upper < 0 ) upper = 0; #ifdef DEBUG if( find_player(DEBUG) && bonus >= MIN_LEVEL ) tell_creator( DEBUG, "FU: %d\n%^RESET%^", upper ); #endif } #ifdef DEBUG else if( find_player(DEBUG) ) TCRE( DEBUG, sprintf("No control: %O\n", previous_object(-1) ) ); #endif /* If they succeed, they might be awarded a level. */ if( !margin ) margin = 1; #ifdef DEBUG TCRE( DEBUG, sprintf("Tm variable %d.", ( upper * ( difficulty + margin - bonus + 5) ) / margin ) ); #endif if( random( 100 ) < ( upper * ( difficulty + margin - bonus + 5 ) ) / margin ) { if( use_class) { return_class->result = AWARD; return return_class; } else { return AWARD; } } if( use_class ) { return_class->result = SUCCEED; return return_class; } else { return SUCCEED; } } /* 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. * This function should never be called directly, use perform_task instead. * * @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 * chance is halved * * @return BARF is something screwed up, AWARD if the task succeeded, and * should give an advance, SUCCEED if it succeeded, FAIL if it failed. * * @see perform_task */ /** * Do not use this function. Use perform_task instead. */ varargs mixed attempt_task_e( int difficulty, int bonus, int upper, int half, int use_class ) { float fail_chance, tmp; class task_class_result return_class; #ifdef DEBUG TCRE( DEBUG, sprintf("D[%d], B[%d], U[%d], H[%d]", difficulty, bonus, upper, half ) ); #endif #ifdef LOGGING if( call_stack(2)[1] != "perform_task" && base_name(PO) != "/std/effects/fighting/combat" && file_name(PO)[0..2] != "/w/" ) { log_file("ATTEMPT_TASK", "%s Object %s is using old attempt_task " "[%O]\n", ctime(time()), base_name(PO), 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( use_class ) { return_class = new( class task_class_result ); return_class->result = FAIL; // Compatible with degree of failure,and not always critical. return_class->degree = ( 0 - random(100) ); return return_class; } else { return FAIL; } } if( !half ) /* Approximate old behaviour. */ half = 6 * sqrt( difficulty ); fail_chance = exp( ( -0.693 * ( bonus - difficulty ) ) / half ); // if ( random( 1000 ) < ( 1000 * fail_chance ) ) { // return FAIL; // } tmp = - 99 + random(100) + ( 100 * ( 1 - fail_chance ) ); // tmp = ( ( ( 1000 * fail_chance) - random(1000) - 500 ) / 5 ); if( tmp == 100 ) tmp = 99; // So incredibly unlikely, but possible. if( use_class ) { return_class = new( class task_class_result ); return_class->degree = tmp; } if( tmp < 0 ) { if( use_class ) { return_class->result = FAIL; return return_class; } 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_creator( DEBUG, "%^RED%^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_creator( DEBUG, "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_creator( DEBUG, "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_creator( DEBUG, "FU: %d\n%^RESET%^", upper ); #endif } /* If they succeed, they might be awarded a level. */ #ifdef DEBUG TCRE( DEBUG, sprintf("TM chance part 1, %O\nTm chance part 2 %O < part 3 %O", ( upper * fail_chance * 10 ), bonus, difficulty + ( half * 5 ) ) ); #endif if( random( 1100 ) < ( upper * fail_chance * 10 ) && bonus < difficulty + ( half * 5 ) ) { #ifdef DEBUG TCRE( DEBUG, sprintf("TM given out, difficulty was %O, upper was %O, " "bonus was %O.", difficulty, upper, bonus ) ); #endif if( use_class ) { return_class->result = AWARD; return return_class; } else { return AWARD; } } if( use_class ) { return_class->result = SUCCEED; return return_class; } else { return SUCCEED; } } /* attempt_task_e() */