// ----------------------------------------------------------------------------
/*
Skill_d controls weapon skill learning.
It is based off skill_pts which are used to calculate the actual skill.
Note: Adventurer is a 'special' class. When changing classes, points are
not recalculated on change out of the class (to adventurer). This
would cause something like Longsword from Fighter -> Adv -> Paladin
to drop a lot, whereas ideally it should remain fairly high.
This means adventurers when they skill up will be ignored if they have
too high a skill (ie fighter -> adv) but will be clipped if they were
too low. Otherwise they would gain 'free' skills.
*/
#include <wizards.h>
// This needs to be changed before installing to just the default skills.h
#ifdef REALMUD
#include <skills.h>
#else
#include "skills.h"
#endif
#include <combat.h>
#include <terminal.h>
#define WIS_CHECK 20
#define INT_CHECK 20
#define MAX_LEARN 20
// ----------------------------------------------------------------------------
private static mapping learning_rate;
private static mapping partial_learning;
private static mapping max_learning;
private static mapping taught_skills;
private static string *skill_types;
int calc_skill(object person, string type, int amount);
int calc_points(object person, string type, int amount);
// ----------------------------------------------------------------------------
void create() {
if ( is_clone() ) {
destruct( this_object() );
raise_error( "*** Do not clone this object.\n" );
}
learning_rate = SKILL_RATES;
max_learning = MAX_LEARNING;
taught_skills = ([]);
skill_types = SKILL_TYPES;
}
// find_k() calculates the actual number of points that are needed for
// a particular skill rate per skill point.
int find_k( int rate ) {
int k;
k = 6 + (11 - rate) * (11 - rate) / 4;
return k;
}
// Log any errors found in players skills.
void log_error(object person, string type, string msg) {
mapping spts;
string who;
int sk, calc_sk;
int skp, calc_skp;
if (!person) {
// This shouldnt happen, but just incase give it something.
who = "Unknown";
sk = -1;
skp = -1;
calc_sk = -1;
calc_skp = -1;
} else {
who = person->query_real_name();
// use the mapping as a direct query uses tmp value.
sk = person->query_weapon_skills()[type];
spts = person->query_skill_pts(); // can be a 0 if player has no skills
if (spts) skp = spts[type];
calc_sk = calc_skill(person, type, skp);
calc_skp = calc_points(person, type, sk);
}
log_file("SKILL_ERROR","["+ctime()+"] "+who+" "+msg+" in "+type+" "+
"Skill "+sk+" -> "+calc_sk+", Pts "+skp+" -> "+calc_skp+"\n");
}
// Attempt to learn a skill.
// Note: This function uses max variables.
mapping do_learn( mapping pts, mixed weapon, object attacker, int weap_sk,
int Int, int Wis, string Name, string class, int pl_level ) {
object person;
string type, colour,reset;
int cur_skill, rate, level_diff, max_skill;
int calculated_skill;
int learn_bonus;
if ( !pts )
pts = ([]);
if (!attacker) return pts;
if (attacker->query_is_player()) return pts;
person = previous_object();
if (!living(person)) person = this_player();
if ( weapon )
if (objectp(weapon))
type = (string)weapon->query_weapon_type();
else
type = weapon;
else
type = "Unarmed";
// Capping p_level to 20
if (pl_level > MAX_LEARN) pl_level = MAX_LEARN;
// To learn from your actual skill
weap_sk -= (int)person->query_tmp_skill( type );
if ( member(learning_rate, class) ) {
rate = learning_rate[class][type];
max_skill = max_learning[class][type];
} else {
rate = 0; /* why doesn't this class exist? */
if ( member(max_learning, class) )
max_skill = max_learning[class][type];
else
max_skill = MAX_UNDEFINED_SKILL;
}
learn_bonus = (int)person->hook("skill_rate_bonus",
({type, max_skill, rate, weapon }));
if(max_skill>100)
max_skill=100;
level_diff = ( (pl_level<MAX_LEARN?
pl_level:MAX_LEARN) + 1 + (weap_sk*MAX_LEARN)/(weap_sk<max_skill?
(max_skill+1):(weap_sk+1)))/2 - (int)attacker->query_level();
if ( taught_skills[Name] == type ) {
level_diff -= 10;
// tell_object( person, "You feel the training helps you a little.\n" );
}
if ( level_diff > 5 )
return pts;
calculated_skill = calc_skill(person, type, pts[type]);
// Cap skill at the max hidden skill.
if (calculated_skill >= MAX_HIDDEN_SKILL)
return pts;
// Want to go by say int and wis also a random
// Don't go above max skill and get 1/20th per level of max skill
// so if max is 100 then get 5 skill per level +5
if ( (random( max_skill + 1 ) >= weap_sk &&
weap_sk < max_skill * (pl_level + 1) / MAX_LEARN ) ||
(weap_sk >= max_skill && pl_level > MAX_LEARN - 2 &&
random( 101 ) >= weap_sk && max_skill) ) {
// To allow anyone up, just making it harder
if ( weap_sk >= max_skill )
rate -= 5;
if ( random( Int + rate + learn_bonus ) >
random( INT_CHECK ) + level_diff &&
random( Wis + rate + learn_bonus ) >
random( WIS_CHECK ) + level_diff ) {
weap_sk=(person->query_weapon_skills())[type];
// Only recalc if skill < max display as points can go higher
// to allow death of high levels to be less painful
// If too low a skill, bring points down to visible level.
if (weap_sk < MAX_DISPLAY_SKILL &&
weap_sk < calculated_skill) {
log_error(person, type, "too low a skill");
pts[type] = calc_points(person,type,weap_sk);
}
// Too high a skill, bring points up to visible level.
if (weap_sk < MAX_DISPLAY_SKILL &&
weap_sk > calculated_skill) {
if (class) {
log_error(person, type, "too high a skill");
pts[type] = calc_points(person, type, weap_sk);
} else {
log_error(person, type, "adventurer skill high, not changed");
}
}
// Now increase points and raise skill if necessary.
pts[type]++;
person->set_skill_pts(type,pts[type]);
if ((person->query_weapon_skills())[type]>weap_sk) {
if (person->query_ansi()) {
colour=ANSI_YELLOW;
reset=ANSI_RESET;
}
else {
colour="";
reset="";
}
tell_object(person,colour+"Your skill in "+type+" has gone up! "+
"You now have "+(person->query_weapon_skills())[type]+".\n"+
reset);
}
}
}
return pts;
}
// Remove all visible skills and set all skill_pts to 0.
int clear_skills( object ob ) {
mapping skill_map;
int i, j;
skill_map = (mapping)ob->query_weapon_skills();
if ( skill_map ) {
string *ind;
ind = m_indices( skill_map );
for ( i = sizeof( ind ) ; i-- ; ) {
j += ob->remove_skill( ind[i] );
ob->set_skill_pts( ind[i], 0);
}
}
return j;
}
// Reduce skills on death (lose 1/50 (2%) from all skill_pts
int reduce_skill( object ob ) {
mapping pl_skills;
string *ind;
int max_skill, i, points, rate, p_level, new_points, bonus;
string class;
class = (string)ob->query_class();
p_level = (int)ob->query_level();
pl_skills = (mapping)ob->query_skill_pts();
if (!pl_skills) return 0;
ind = m_indices(pl_skills);
for ( i = sizeof( ind ) ; i-- ; ) {
if ( member(learning_rate, class) )
rate = learning_rate[class][ind[i]];
else
rate = 0;
if ( max_learning[class] )
max_skill = max_learning[class][ind[i]];
else
max_skill = MAX_UNDEFINED_SKILL;
points = pl_skills[ind[i]];
bonus = (int)ob->hook("skill_loss_bonus", ({ind[i], rate, points, max_skill}));
points -= points / 50;
points += bonus;
if (points < 0) points = 0; // Shouldnt happen, but the hook could anything
ob->set_skill_pts( ind[i], points );
}
return 1;
}
// Original function used to set 'initial' skills when skills were
// implemented. This should no longer be necessary for anything.
int initialise_skills( object player, string choice) {
string *categories;
string class, prim_abi;
float temp, k;
int xp, i, orig_k, rate, val, Int, Wis, num_pts, max_skill;
int pl_xp, pl_level, pl_int, pl_wis;
if ( !player ) return 0;
prim_abi = choice;
if ( !prim_abi ) return 0; /* Hasn't been chosen */
clear_skills( player );
pl_xp = (int)player->query_exp();
pl_level = (int)player->query_level();
class = (string)player->query_class();
pl_int = (int)player->query_int();
pl_wis = (int)player->query_wis();
categories = m_indices( learning_rate[class] );
xp = pl_xp / sizeof( categories );
xp = xp / 20; /* ie. number of hits to make that xp. I hope */
xp = xp / 2; /* Half for all skills, and Half for a chosen skill */
for ( i = 0 ; i < sizeof( categories ) ; ++i ) {
if ( class )
rate = learning_rate[class][categories[i]];
else
rate = 6; /* basic adv. ones */
Int = pl_int + rate; /* Assume half time less stats and half more */
if ( Int < 1 ) Int = 1;
Wis = pl_wis + rate; /* for the passing of the int/wis checks */
if ( Wis < 1 ) Wis = 1;
orig_k = find_k( rate );
k = (float)(orig_k * INT_CHECK * WIS_CHECK / (Int * Wis));
if ( !k || (k == 0.0) ) k = 1.0;
temp = sqrt( (float)(xp / k) );
val = (int)temp;
max_skill = max_learning[class][categories[i]] * (pl_level+1)/MAX_LEARN;
if ( val > max_skill*95/100 ) val = max_skill - max_skill * 5 / 100;
if ( val )
player->set_skill( categories[i], val );
player->set_skill_pts( categories[i], orig_k * val );
}
xp = pl_xp / 80; /* 1/4 xp used for skill */
if ( class )
rate = learning_rate[class][prim_abi];
else
rate = 6; /* basic adv. ones */
Int = pl_int + rate; /* Assume half time less stats and half more */
if ( Int < 1 ) Int = 1;
Wis = pl_wis + rate; /* for the passing of the int/wis checks */
if ( Wis < 1 ) Wis = 1;
orig_k = find_k( rate );
k = (float)(orig_k * INT_CHECK * WIS_CHECK / (Int * Wis));
temp = sqrt( (float)(xp / k) );
val = (int)temp;
// partial skill from level.
// Fighter requires 30 points for max skill so choose half.
num_pts = pl_level * 15;
num_pts = val * orig_k + num_pts;
val = num_pts / orig_k;
max_skill = max_learning[class][prim_abi] * (pl_level + 1) / MAX_LEARN;
if ( val > max_skill*95/100 ) val = max_skill - max_skill * 5 / 100;
if ( val )
player->set_skill( prim_abi, val );
player->set_skill_pts( prim_abi, orig_k * val );
return 1;
}
// Changing class code.
// This should be called after the player has changed classes.
//
void change_class( object player ) {
mapping pts;
string *ind;
string class;
int calculated_skill;
int calculated_points;
int i;
int skill;
class = (string)player->query_class();
/* If going to Adv dont lose skills till rejoin */
if (!class) {
return;
}
if (!learning_rate[class]) return;
ind = m_indices( learning_rate[class] );
pts = player->query_skill_pts();
if (!pts) pts = ([]); // Can be a 0 if no skills at all
for ( i = sizeof( ind ) ; i-- ; ) {
skill = player->query_skill( ind[i] );
calculated_skill = calc_skill(player, ind[i], pts[ind[i]]);
// If calculated points == skill then do nothing, as same skill rate.
// Just requires the 'death' change at the end.
// Changed class to a harder skill. Drop skill drop based on points.
if (skill > calculated_skill) {
player->set_skill( ind[i], calculated_skill);
}
// Changed class to an easier skill. Drop points down and leave skill.
if (skill < calculated_skill) {
calculated_points = calc_points(player, ind[i], skill);
player->set_skill_pts(ind[i], calculated_points);
}
}
reduce_skill( player );
}
// Calculate a skill value based on the number of points passed in.
int calc_skill(object person, string type, int amount) {
int max_skill,rate,k1,k2,weap_sk;
string class;
class = person->query_class();
if ( member(learning_rate, class) ) {
rate = learning_rate[class][type];
max_skill = max_learning[class][type];
} else {
rate = 0;
if (member(max_learning,class))
max_skill = max_learning[class][type];
else
max_skill = MAX_UNDEFINED_SKILL;
}
k1 = find_k(rate);
if (amount > (max_skill-1)*k1) {
k2 = find_k(rate-5);
weap_sk = (amount - (max_skill-1)*k1)/k2 + max_skill;
} else {
weap_sk = amount/k1 + 1;
}
return weap_sk;
}
// For a given skill, calculate the points to achieve the skill.
int calc_points(object person, string type, int amount) {
int max_skill,rate,k1,k2,points;
string class;
if (amount == 1) return 1;
class = person->query_class();
if ( member(learning_rate, class) ) {
rate = learning_rate[class][type];
max_skill = max_learning[class][type];
} else {
rate = 0;
if ( member(max_learning, class) )
max_skill = max_learning[class][type];
else
max_skill = MAX_UNDEFINED_SKILL;
}
k1 = find_k(rate);
if (amount >= max_skill) {
k2 = find_k(rate-5);
points = (amount - max_skill)*k2 + (max_skill-1)*k1;
} else {
points = (amount - 1)*k1;
}
if (points < 0) points = 0;
return points;
}
// Zhou teaching.
void teach_skill( string who, string what ) {
if (member(taught_skills, who))
taught_skills[who] = what;
else
taught_skills += ([ who : what ]);
call_out( "forget_skill", 600, who );
}
void forget_skill( string who ) {
if ( taught_skills[who] )
m_delete( taught_skills, who );
}
mixed query_taught_skills( string who ) {
return who ? taught_skills[who] : copy_mapping(taught_skills);
}
mapping query_learning_rate() {
return copy_mapping(learning_rate);
}
string *query_skill_types() {
return skill_types + ({});
}
// ------------------------------------------------------------------------ EOF