/**
* This skills modules for living creates. This deals will all the
* skill interactions needed for the living objects.
*
* @author Pinkfish
*/
/*
* sigh, this will be interesting wont it?
* quick summary of routines
* skill_bonus(string,int) - gives the bonus...
* skill_lvl(string) - gives the raw level, with out stat bonus etc
* modify_skill(string,int,int)- modifies the skills level by int.
* calc_bonus(int,string,int) - given the skill lvl, stat bonus str cals bonus
* calc_lvl(string *) - calculate number of lvls in the path
* add_skill_lvl(...) - horror recursive skill adder. arghh
* teach_skill(objct *,string) - Used to teach skills to other people.
*/
/*
* Modifications:
* Changed to use new creator checks - Shaydz - 07/03/01
* Fixed a bug with add_skill_level - Shiannar.
* Fixed a bug with disappearing first skill levels in add_skill_level
* - Sandoz 31/01/02
* Fixed another bug with disappearing first skill levels in add_skill_level
* - Sandoz 06/02/02
*/
#include <skills.h>
#include <tasks.h>
#include <top_ten_tables.h>
#include <tune.h>
// #define BAD_TM "BAD_TM"
#undef LOGGING
varargs int calc_bonus( int lvl, string skill, int use_base_stats );
varargs int stat_modify( int bonus, string skill, int use_base_stats );
mixed recursive_skill_add( mixed skil, string *path, int avr, int lvl, int exp,
mixed standard );
private void convert_skills( mixed skills, string path );
int query_skill( string skill );
mapping new_skills;
nosave mapping _bonus_cache, _stat_cache, _teach_offer;
string last_award, last_ob;
int last_award_time;
mapping query_skills() { return copy( new_skills ); }
void set_skills( mapping map ) { new_skills = map; }
int calc_level(string *path);
void create() {
_bonus_cache = ([ ]);
_stat_cache = ([ ]);
new_skills = ([ ]);
} /* create() */
/**
* This method checks to see if the skill exists in the skill array or
* not.
* @param skill the skill to check for non-existance
* @return 0 if it does not exist, 1 if it does
*/
int not_there( string skill ) {
return undefinedp( new_skills[skill] );
} /* not_there() */
/**
* This method returns the current bonus cache for the living thing.
* The bonus cache is where the calculated bonuses for the skills are
* kept.
* @return the bonus cache mapping
*/
mapping query_bonus_cache() { return copy(_bonus_cache); }
/**
* This method returns the cached values for the stats.
* @return the caches stat values
*/
mapping query_stat_cache() { return copy(_stat_cache); }
/**
* This method zaps the stat cache when a certain stat changes.
* It calls the function stats_to_zap() on the living object to
* figure out which stats have changed.
* @see /std/living/stats->stats_to_zap()
*/
void zap_stat_cache() {
int i;
string stat, word, *list;
stat = TO->stats_to_zap();
if( !stat )
return;
if( find_call_out("reset_all2") == -1 )
call_out("reset_all2", 1 );
foreach( i in stat ) {
if( !list = _stat_cache[ i ] )
continue;
foreach( word in list )
map_delete( _stat_cache, word );
}
if( word = (string)TO->query_race_ob() )
word->set_unarmed_attacks( TO );
} /* zap_stat_cache() */
/**
* This method zaps the bonus cache.
*/
protected void totaly_zap_bonus_cache() {
_bonus_cache = ([ ]);
} /* zap_bonus_cache() */
/**
* This method zaps the stat cache.
*/
protected void totaly_zap_stat_cache() {
_stat_cache = ([ ]);
} /* zap_stat_cache() */
/**
* This is used to convert a previously not only_leaf tree into an only_leaf
* tree.
*/
private void flatten_it( string skill ) {
int value;
string *same, tmp;
reset_eval_cost();
value = new_skills[skill];
same = SKILL_H->query_immediate_children(skill);
foreach( tmp in same ) {
if( not_there( tmp ) )
new_skills[tmp] = value;
flatten_it( tmp );
}
if( sizeof(same) )
map_delete( new_skills, skill );
} /* flatten_it() */
/**
* This method returns the skill bonus for the specified skill.
* It returns the skill + all its bonsues for stats/whatever.
* It first checks to see if the skill is in it's cache. THe
* real stat values are ones not modified by bonuses or temporary
* values.
* @param skill the skill to get the bonus for
* @param use_base_stats tells the system not to use the real stat values
* @return the skill bonus
*/
varargs int query_skill_bonus( string skill, int use_base_stats ) {
if( !stringp(skill) || !strlen(skill) )
return 0;
if( !new_skills )
new_skills = ([ ]);
if( skill[0] == '.' )
skill = skill[1..];
if( _bonus_cache[ skill ] ) {
TASKER->set_control( ({ TO, skill }) );
return stat_modify( _bonus_cache[ skill ], skill, use_base_stats );
}
return calc_bonus( query_skill(skill), skill, use_base_stats );
} /* query_skill_bonus() */
/**
* This returns just the skill level. Used a lot to determine if you
* can use/teach/whatever a skill.
* This also uses a cache.
* @param skill the skill to return the level of
* @return the skill level
*/
int query_skill( string skill ) {
string *path, tmp;
if( !stringp(skill) )
return 0;
if( !new_skills )
new_skills = ([ ]);
if( skill[0] == '.' )
skill = skill[1..];
TASKER->set_control( ({ TO, skill }) );
if( not_there( skill ) ) {
if( path = SKILL_H->query_skill_tree(skill) )
foreach( tmp in path )
if( !not_there( tmp ) )
return new_skills[tmp];
}
return new_skills[skill];
} /* query_skill() */
/**
* This method adds a skill level to the specified skill to the
* system.
* @param skill the skill to add a level to
* @param lvl the number of levels to add
* @param exp the amount of exp spent on the skill
* @return 1 if the skill level was changed
* @see query_skill()
* @see query_skill_bonus()
*/
varargs int add_skill_level( string skill, int lvl, mixed exp ) {
string tmp, sk;
string *recursive_skills;
string *same_level;
string *bits;
string *tree;
int i;
reset_eval_cost();
if( !stringp(skill) || !intp(lvl) || lvl > 1000 )
return 0;
if( lvl > 100 && interactive(TO) && !creatorp(TO) ) {
log_file("CHEAT", "%s %s gave %d levels of %s to %s\n", ctime(time()),
( PO->query_name() || base_name(PO) ), lvl, skill,
( TO->query_name() || TO ) );
}
if( !new_skills || !mapp(new_skills) )
new_skills = ([ ]);
if( skill[0] == '.' )
skill = skill[1..];
recursive_skills = SKILL_H->query_related_skills(skill);
if( !recursive_skills )
return 0;
bits = explode( skill, ".");
/*
* Make sure the path leading up to this skill exists so that we can
* get the right value for the skill when we add it in.
* This should only be done if they are not only leaf skills.
*/
if( not_there( skill ) && !SKILL_H->query_only_leaf(skill) ) {
int tmp_lvl;
if( sizeof(bits) > 1 ) {
tmp_lvl = 0;
for( i = sizeof(bits)-1; !tmp_lvl && i >= 0; i-- ) {
if( !not_there( implode( bits[ 0 .. i ], "." ) ) ) {
tmp_lvl = new_skills[ implode(bits[0..i], ".") ];
break;
}
}
if( i >= 0 ) {
for( ; i < sizeof(bits); i++ ) {
same_level = SKILL_H->query_immediate_children(
implode( bits[0..i], ".") );
foreach( tmp in same_level ) {
if( not_there( tmp ) ) {
new_skills[ tmp ] = tmp_lvl;
map_delete( _bonus_cache, tmp );
}
}
}
} else {
tmp_lvl = 0;
}
}
}
/* Includes the current skill */
foreach( tmp in recursive_skills ) {
if( !not_there( tmp ) ) {
new_skills[ tmp ] += lvl;
if( new_skills[ tmp ] < 0 )
new_skills[ tmp ] = 0;
}
map_delete( _bonus_cache, tmp );
}
if( not_there( skill ) )
new_skills[skill] = lvl;
/*
* If it is not a only_leaf heirarchy, then fix up all the lower level
* average skill levels.
* The first element is the current skill.
*/
tree = SKILL_H->query_skill_tree(skill)[1..];
foreach( sk in tree ) {
int total;
same_level = SKILL_H->query_immediate_children( sk );
if( sizeof(same_level) ) {
total = 0;
foreach( tmp in same_level ) {
/* If it does not exist. Set it from the top value down. */
if( not_there( tmp ) ) {
if( !not_there( sk ) )
new_skills[ tmp ] = new_skills[ sk ];
map_delete( _bonus_cache, tmp );
}
total += new_skills[ tmp ];
}
new_skills[ sk ] = total / sizeof(same_level);
map_delete( _bonus_cache, sk );
}
}
// Update the high level players table.
if( interactive(TO) )
TOP_TEN_HANDLER->player_skill_advance( TO );
if( !exp )
exp = PO;
if( lvl == 1 && userp(TO) && objectp(exp) )
TASKMASTER_H->award_made( (string)TO->query_name(), base_name( exp ),
skill, new_skills[ skill ] );
// Make sure that there is at most one call_out running.
if( find_call_out( "reset_all" ) == -1 )
call_out( "reset_all", 1 );
return 1;
} /* add_skill_level() */
/**
* This method returns the skill as it should be modified by the
* stats associated with it.
* @param lvl the level to modify
* @param skill the skill the modify the bonus of
* @param use_base_stats use the real unmodified stat values
* @see query_skill_bonus()
* @return the stat modification
*/
varargs int stat_modify( int lvl, string skill, int use_base_stats ) {
int i, stat, bonus;
string stat_bonus;
if( !_stat_cache[ skill ] || use_base_stats ) {
stat_bonus = SKILL_H->query_skill_stat(skill);
foreach( i in stat_bonus ) {
switch( i ) {
case 'C' :
stat = ( use_base_stats ? (int)TO->query_real_con() :
(int)TO->query_con() );
break;
case 'D' :
stat = ( use_base_stats ? (int)TO->query_real_dex() :
(int)TO->query_dex() );
break;
case 'I' :
stat = ( use_base_stats ? (int)TO->query_real_int() :
(int)TO->query_int() );
break;
case 'S' :
stat = ( use_base_stats ? (int)TO->query_real_str() :
(int)TO->query_str() );
break;
case 'W' :
default :
stat = ( use_base_stats ? (int)TO->query_real_wis() :
(int)TO->query_wis() );
}
bonus += ( stat - 13 ) * 3;
if( !_stat_cache[ i ] ) {
_stat_cache[ i ] = ({ skill });
} else {
if( member_array( skill, _stat_cache[ i ] ) == -1 )
_stat_cache[ i ] += ({ skill });
}
}
if( !use_base_stats )
_stat_cache[ skill ] = ({ bonus, stat_bonus });
} else {
bonus = _stat_cache[ skill ][ 0 ];
stat_bonus = _stat_cache[ skill ][ 1 ];
}
if( i = strlen( stat_bonus ) )
return lvl + ( lvl * bonus ) / ( i * 60 );
return lvl;
} /* stat_modify() */
/*
* Handy fact: stat_modify( 100, skill ) - 35 is the stat total
* for that skill.
*/
/**
* This method calculates the bonus for the skill. It takes the raw
* level and turns that into a bonus and then adds on the stats
* modifications.
* @param lvl the level to turn into bonus
* @param skill the skill to modify the bonus of
* @param use_base_stats use the real unmodified stats
* @return the bonus associated with the skill
*/
varargs int calc_bonus( int lvl, string skill, int use_base_stats ) {
if( lvl > 60 )
lvl = 170 + ( ( lvl - 60 ) >> 1 );
else if( lvl > 40 )
lvl = 150 + ( lvl - 40 );
else if( lvl > 20 )
lvl = 100 + ( ( ( lvl - 20 ) * 5 ) >> 1 );
else
lvl = lvl * 5;
if( !use_base_stats )
_bonus_cache[ skill ] = lvl;
return stat_modify( lvl, skill, use_base_stats );
} /* calc_bonus() */
/**
* This method does a skill successful check. Does this check:<br>
* (bonus + mos) >= random(200)
* @param str the skill to check
* @param mod the modification value
* @return 1 if the skill check is successful
*/
int query_skill_successful(string str, int mod) {
return ( query_skill_bonus( str, 0 ) + mod >= random(200) );
} /* query_skill_successful */
/**
* This method adds a teaching offer to the living object.
* @param ob the object teaching us
* @param skill the skill they are teaching
* @param num the number of levels they are teaching
* @param lvl the level they are teaching us from
* @param xp the cost of the level increase in xp
*/
void add_teach_offer( object ob, string skill, int num, int lvl, int xp ) {
if( !mapp(_teach_offer) )
_teach_offer = ([ ]);
_teach_offer[ob] = ({ skill, num, lvl, xp });
} /* add_teach_offer() */
/**
* This method returns the current list of teach offerings on the
* living object.
* @return the mapping containing the teach offerings
*/
mapping query_teach_offer() { return copy(_teach_offer) || ([ ]); }
/**
* The method to call when we stop teaching skills. THis will stop the
* stuff being taught if the stop is successful, and only teach partial
* amounts if we are not finished yet.
* @param left the amount of time left
* @param bing the data associated with the command
*/
void stop_teaching_skills(int left, mixed bing) {
object ob;
if (left > 0) {
/* Someone did a stop! Naughty frogs! */
if (bing[O_OTHER_PER] == TO) {
say(TO->short() + " stops teaching themselves some "
"skills.\n");
} else if (previous_object() == TO) {
ob = bing[O_OTHER_PER];
tell_object(ob, TO->short() + " interupts your "
"training.\n");
} else {
ob = TO;
tell_object(ob, bing[O_OTHER_PER]->short() + " interupts your "
"training.\n");
}
say(bing[O_OTHER_PER]->short() + " stops teaching some skills to " +
TO->short() + ".\n",
({ TO, bing[O_OTHER_PER] }));
TO->adjust_time_left(-((int)TO->query_time_left()));
TO->set_interupt_command(0);
return ;
}
if (previous_object() != TO) {
/* First make sure we dont get the level twice... */
return ;
}
// additional test added by ceres coz people are getting put into negative
// xp by getting taught twice somehow.
if(TO->query_xp() < bing[O_XP]) {
write("Something has gone wrong. :(\n");
return;
}
/* Ok... We did it! Finished! */
if (TO != bing[O_OTHER_PER]) {
bing[O_OTHER_PER]->adjust_xp(bing[O_XP]/10);
}
TO->adjust_xp(-bing[O_XP]);
add_skill_level(bing[O_SKILL], bing[O_NUM], bing[O_XP]);
if (TO != bing[O_OTHER_PER]) {
tell_object(TO, "You finish learning " + bing[O_NUM] +
" levels of "
+ bing[O_SKILL] + " from " + bing[O_OTHER_PER]->short() +
".\n");
tell_object(bing[O_OTHER_PER], TO->short() + " finishes " +
"learning " + bing[O_NUM] + " levels of "
+bing[O_SKILL] + " from you.\n");
say(TO->short() + " finishes learning some skills "+
"from "+bing[O_OTHER_PER]->short()+".\n",
({ TO, bing[O_OTHER_PER] }));
} else {
tell_object(TO, "You finish teaching yourself " + bing[O_NUM] +
" levels of " + bing[O_SKILL] + ".\n");
say(TO->short() + " finishes learning some skills "
"from " + TO->query_objective() + "self.\n",
({ TO, bing[O_OTHER_PER] }));
}
} /* stop_teaching_skills() */