Author Topic: Code for character skill based leveling  (Read 831 times)

Offline Lash

  • Acquaintance
  • *
  • Posts: 42
    • View Profile
Code for character skill based leveling
« on: January 30, 2016, 11:58:21 AM »
I've tested this out on a new Dead Souls installation and a running mud. Only tested low level test characters so not sure how this will work at character levels above 20.

Here's my modded code changes for player based skill selection and advancement. I'm not going to post the complete files for all this so as not to spam the post too much. Line numbers are for Dead Souls 3.8.6 default base mudlib.

An alternative to having players select their character skills during their first log in is to have an NPC assign skills to players after entering the game using the SetSkill() function. But, for skill based advancement vs. XP based advancing the below files can be added.

Configuration Files:
note: CLASS_SELECTION and SKILL_SELECTION should not both be set to 1!

config.h in /lib/secure/include

Code: [Select]
//line 44
#define CLASS_SELECTION          0
#define SKILL_SELECTION          1
#define XP_ADVANCE               0
#define SKILL_ADVANCE            1

cfg.h in lib/secure/include
Code: [Select]
//line 19
#define CFG_SKILLS DIR_SECURE_CFG "/skills.cfg"

skills.cfg (file) in /lib/secure/cfg

Code: [Select]
#/lib/secure/cfg/skills.c
#list of available skills
bargaining
blade attack
blade defense
blunt attack
blunt defense
concealment
conjuring
detection
faith
fishing
healing
knife attack
knife defense
magic attack
magic defense
melee attack
melee defense
multi-hand
multi-weapon
projectile attack
projectile defense
psionic attack
psionic defense
psionic detection
stealing
stealth
murder

Added mudconfig commands to change whether classes with experience point advancement are used or not, or skills chosen on player creation and
advancement based on skill point leveling. 

mudconfig.c in lib/secure/cmds/admin

Code: [Select]
//line 23
string array modals = antimodals + ({ "channelpipes", "fastcombat",
        "catchtell","matchcommand", "matchobject", "autowiz", "locked",
        "localtime", "justenglish", "justhumans", "encumbrance", "pk",
        "compat", "exitsbare", "nmexits", "grid", "minimap", "wizmap",
        "cgi", "dirlist", "creweb", "selectclass", "selectskills",
        "xpadvancement", "skilladvancement", "severable", "retain",
        "defaultparse", "disablereboot", "loglocal", "logremote",
        "questrequired", "autoadvance","guestallowed", "playerintertell" });

//line 574
        case "selectclass" : which = "CLASS_SELECTION";break;
        case "selectskills" : which = "SKILL_SELECTION";break;
        case "xpadvancement" : which = "XP_ADVANCE";break;
        case "skilladvancement" : which = "SKILL_ADVANCE";break;
       
//line 629
       if(which == "CLASS_SELECTION" || which == "SKILL_SELECTION"
       || which =="XP_ADVANCE" || which == "SKILL_ADVANCE")
        write("To make this configuration take effect, reboot the mud.\n"+
                "It might be a good idea to disable the other SELECTION and/or\n"+
                "ADVANCE options before reboot.");

//line 1007 in string(GetHelp())
            "\nmudconfig selectclass [ yes | no ] (whether new players "
            "choose a class on login)"
            "\nmudconfig selectskills [yes | no ] (whether new players "
            "select their own skill set on login (classless mud)"
            "\nmudconfig xpadvancement [yes | no ] (whether players gain levels "
            "using the experience point system or not"
            "\nmudconfig skilladvancement [yes | no ] (whether players gain levels "
            "by increasing their skill points or not"

Connection File:
Note: for a virgin installation rename this to connect.real; if the mud is up and running replace the connect.c file and reboot. To change the amount of skills a character chooses, change the values in the if(sizeof(?tmp) < x) expression in the CoomandPick(whatever)Skills() functions.

Help files for skills not written yet so code is commented out for  cmdHelpSkills(args) function.
Not good at daisy-chaining input_to() so this spam is the result:

connect.c in /lib/secure/lib

Code: [Select]
//FUNCTION PROTOTYPES

static private string *Skills;
static private string *tskills = ({});
static private string *ptmp = ({});
static private string *stmp = ({});
static private string *mtmp = ({});
static void eventSkillSelectionInstructions();
static void eventSelectPrimarySkills();
static void InputPrimarySkills(string str);
static void cmdPickPrimarySkills(string args);
static void eventSelectSecondarySkills();
static void InputSecondarySkills(string str);
static void cmdPickSecondarySkills(string args);
static void eventSelectMinorSkills();
static void InputMinorSkills(string str);
static void cmdPickMinorSkills(string args);
static void cmdHelpSkills(string args);
static private string *GetSkills();

//CODE BLOCKS

//line 698
if(!AUTO_WIZ){
        if(CLASS_SELECTION){
            eventSelectClass();
            return;
        }           
        if(SKILL_SELECTION){
            eventSkillSelectionInstructions();
            //eventSelectPrimarySkills();
            return;
        }
        eventCompleteChar();
    }
//line 818
static void eventSkillSelectionInstructions(){
    receive("\n\nYou must now choose the skill sets that will define your character.\n"
            "Available are:\n"
            "    5 PRIMARY skill slots\n"
            "    3 SECONDARY skill slots\n"
            "    5 MINOR skill slots\n\n"
            "The more you use a skill the faster your level will increase.\n"
            "Think about your character concept and choose wisely.\n");
    receive("\nDuring skill selection you will be presented with a list of available\n"
            "skills, as well as a list of skills you have already chosen. The\n"
            "following commands will be available to you:\n\n");
    receive("\thelp - shows the help file for SKILLS\n"
            "\tpick SKILL - pick a particular SKILL for yourself\n\n"
            "Press return to begin skill selection.");
    input_to((: eventSelectPrimarySkills :), I_NOESC);
}

static void eventSelectPrimarySkills(){
    int x = sizeof(ptmp);
    string rskills = format_page(sort_array(Skills, 1), 4);
    //string cpskills = format_page(sort_array(ptmp, 1), 1);
    string cpskills = format_page(sort_array(ptmp, 1), 3);
               
    receive("\n\nYou must now pick "+(5-x)+" PRIMARY Skills.\n");
    receive("\nYou have chosen the following PRIMARY Skills:\n");
    receive(cpskills);
    receive("\nValid skills to choose from:\n\n");
    receive(rskills);
    receive("\nSkill: \n");
    input_to((: InputPrimarySkills :), I_NOESC);
}

static void InputPrimarySkills(string str){
    string cmd, args, argse;
    string *tmp = Skills;
    string rskills = format_page(sort_array(Skills, 1), 4);

    if( str == "" || !str ) {
        receive("\nYou must pick a Skill: ");
        input_to((: InputPrimarySkills :), I_NOESC);
        return;
    }
    if( sscanf(str, "%s %s", cmd, args) != 2){
        cmd = str;
        args = 0;
    }
    if( sscanf(str, "%s %s", cmd, args) == 2){
        if(cmd != "pick"){
            cmd = str;
            args = 0;
        }
    }
    if(member_array(cmd, tmp) != -1){
        args = cmd;
        cmd = "pick";
    }
    switch(cmd) {
        /*case "help":
            cmdHelpSkills(args);
        return;*/

        case "pick":
            cmdPickPrimarySkills(args);
        return;

        default:
        receive("Invalid command or no such skill. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputPrimarySkills :), I_NOESC);
        return;
    }
}

static void cmdPickPrimarySkills(string args) {
    string *tmp = Skills;
    string str;
    string rskills = format_page(sort_array(Skills, 1), 4);
       
    if( !args || args == "" ) {
        receive("You must specify a skill to pick. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputPrimarySkills :), I_NOESC);
        return;
    }
    if( member_array(args, tmp)  == -1 ) {
        receive("No such Skill. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputPrimarySkills :), I_NOESC);
        return;
    }
    if(sizeof(ptmp) < 5){
        ptmp+=({args});
        Skills -=({args});
        if(sizeof(ptmp) == 5){
            eventSelectSecondarySkills();
        }else{ eventSelectPrimarySkills();
        }
    }
}

static void eventSelectSecondarySkills(){
    int x = sizeof(stmp);
    string rskills = format_page(sort_array(Skills, 1), 4);
    string cpskills = format_page(sort_array(ptmp, 1), 3);
    string csskills = format_page(sort_array(stmp, 1), 3);
         
    receive("\nYou must now pick "+(3-x)+" SECONDARY Skills.\n");
    receive("\nYou have chosen the following PRIMARY Skills:\n");
    receive(cpskills);
    receive("\nYou have chosen the following SECONDARY Skills:\n");
    receive(csskills);
    receive("\nValid skills to choose from:\n\n");
    receive(rskills);
    receive("\nSkill: \n");
    input_to((: InputSecondarySkills :), I_NOESC);
}

static void InputSecondarySkills(string str){
    string cmd, args, argse;
    string *tmp = Skills;
    string rskills = format_page(sort_array(Skills, 1), 4);

    if( str == "" || !str ) {
        receive("\nYou must pick a Skill: ");
        input_to((: InputSecondarySkills :), I_NOESC);
        return;
    }
    if( sscanf(str, "%s %s", cmd, args) != 2){
        cmd = str;
        args = 0;
    }
    if( sscanf(str, "%s %s", cmd, args) == 2){
        if(cmd != "pick"){
            cmd = str;
            args = 0;
        }
    }
    if(member_array(cmd, tmp) != -1){
        args = cmd;
        cmd = "pick";
    }
    switch(cmd) {
        /*case "help":
            cmdHelpSkills(args);
        return;*/

        case "pick":
            cmdPickSecondarySkills(args);
        return;

        default:
        receive("Invalid command or no such skill. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputSecondarySkills :), I_NOESC);
        return;
    }
}

static void cmdPickSecondarySkills(string args) {
    string *tmp = Skills;
    string str;
    string rskills = format_page(sort_array(Skills, 1), 4);

    if( !args || args == "" ) {
        receive("You must specify a skill to pick. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputSecondarySkills :), I_NOESC);
        return;
    }
    if( member_array(args, tmp)  == -1 ) {
        receive("No such Skill. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputSecondarySkills :), I_NOESC);
        return;
    }
    if(sizeof(stmp) < 3){
        stmp+=({args});
        Skills -=({args});
        if(sizeof(stmp) == 3){
            eventSelectMinorSkills();
        }else{ eventSelectSecondarySkills();
        }
    }
}

static void eventSelectMinorSkills(){
    int x = sizeof(mtmp);
    string rskills = format_page(sort_array(Skills, 1), 4);
    string cpskills = format_page(sort_array(ptmp, 1), 3);
    string csskills = format_page(sort_array(stmp, 1), 3);
    string cmskills = format_page(sort_array(mtmp, 1), 3);   

    receive("\n\nYou must now pick "+(5-x)+" MINOR Skills.\n");
    receive("\nYou have chosen the following PRIMARY Skills:\n");
    receive(cpskills);
    receive("\nYou have chosen the following SECONDARY Skills:\n");
    receive(csskills);
    receive("\nYou have chosen the following MINOR Skills:\n");
    receive(cmskills);
    receive("\nValid skills to choose from:\n\n");
    receive(rskills);
    receive("\nSkill: \n");
    input_to((: InputMinorSkills :), I_NOESC);
}

static void InputMinorSkills(string str){
    string cmd, args, argse;
    string *tmp = Skills;
    string rskills = format_page(sort_array(Skills, 1), 4);
   
    if( str == "" || !str ) {
        receive("\nYou must pick a Skill: ");
        input_to((: InputSecondarySkills :), I_NOESC);
        return;
    }
    if( sscanf(str, "%s %s", cmd, args) != 2){
        cmd = str;
        args = 0;
    }
    if( sscanf(str, "%s %s", cmd, args) == 2){
        if(cmd != "pick"){
            cmd = str;
            args = 0;
        }
    }
    if(member_array(cmd, tmp) != -1){
        args = cmd;
        cmd = "pick";
    }
    switch(cmd) {
        /*case "help":
            cmdHelpSkills(args);
        return;*/

        case "pick":
            cmdPickMinorSkills(args);
        return;

        default:
        receive("Invalid command or no such skill. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputMinorSkills :), I_NOESC);
        return;
    }
}

static void cmdPickMinorSkills(string args) {
    string *tmp = Skills;
    string str;
    string rskills = format_page(sort_array(Skills, 1), 4);
       
    if( !args || args == "" ) {
        receive("You must specify a skill to pick. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputMinorSkills :), I_NOESC);
        return;
    }
    if( member_array(args, tmp)  == -1 ) {
        receive("No such Skill. Valid skills to choose from:\n");
        receive(rskills);
        receive("\nSkill: \n");
        input_to((: InputMinorSkills :), I_NOESC);
        return;
    }
    if(sizeof(mtmp) < 5){
        mtmp+=({args});
        Skills -=({args});
        if(sizeof(mtmp) == 5){
            eventCompleteChar();
        }else{ eventSelectMinorSkills();
        }
    }


/*static void cmdHelpSkills(){
}*/

static private string *GetSkills(){
    string *ret;
    ret = Skills;
    return ret;
}

//line 900 in eventCompleteChar()
    if(ptmp){
        string str;
        receive("\nCharacter Summary\n");
        receive("-------------------------\n");
        receive("PRIMARY Skills picked:\n");
        foreach(str in ptmp){
            receive("'"+str+"' ");
            Player->AddSkill(str,1,1);
        }
        receive("\n");
    }
    if(stmp){
        string str;
        receive("\nSECONDARY Skills picked:\n");
        foreach(str in stmp){
            receive("'"+str+"' ");
            Player->AddSkill(str,2,1);
        }
        receive("\n");
    }
    if(mtmp){
        string str;
        receive("\nMINOR Skills picked:\n");
        foreach(str in mtmp){
            receive("'"+str+"' ");
            Player->AddSkill(str,3,1);
        }
        receive("\n");
    }
    this_player()->SetTerminal("ansi");
    PLAYERS_D->AddPlayerInfo(Name);
    call_out( (: eventCre, Name :), 60);
    receive("\nIf you wish to create a different character,\n"
            "use the 'suicide' command after logging in and\n"
            "start over. Have FUN!\n"
            "Press <return> within 60 seconds to continue.");     
    input_to((: eventEnterGame :), I_NOESC);
}

Leveling files for skill based leveling:

Used for collecting player data used in the skill leveling process for players.c

abilities.c in /lib/lib/lvs

Code: [Select]
//FUNCTION PROTOTYPES
private string array GetPrimarySkills();
private string array GetSecondarySkills();
private string array GetMinorSkills();

//CODE BLOCKS
//line 120
string array GetPrimarySkills(){
    return filter(keys(Skills), (: Skills[$1]["class"] == 1 :));
}

string array GetSecondarySkills(){
    return filter(keys(Skills), (: Skills[$1]["class"] == 2 :));
}

string array GetMinorSkills(){
    return filter(keys(Skills), (: Skills[$1]["class"] == 3 :));
}

Check to see if 3 primary skills, 2 secondary skills, and 1 minor skill are at max levels for the character at their current player level. If so, advance the player level. Has checks for SKILL_ADVANCE and XP_ADVANCE variables from config.h

players.c in /lib/secure/daemon
Code: [Select]
//line 306
int CheckAdvance(object ob){
    string str;
    string *ptmp = ({});
    string *stmp = ({});
    string *mtmp = ({});
    int dlev, x ,y ,z, xp, qp;
    int *plevels = ({});
    int *slevels = ({});
    int *mlevels = ({});
    int *psorted, *ssorted, *msorted;
   
    dlev = (ob->GetLevel())+1;
    /* get and sort primary skills and levels */
    if(SKILL_ADVANCE){
        ptmp = this_player()->GetPrimarySkills();
        foreach(str in ptmp){
            plevels += ({this_player()->GetSkillLevel(str)});
        }
        plevels = sort_array(plevels,-1);
        x = this_player()->GetMaxSkillLevel(ptmp[0]);
       
        /* get and sort secondary skills and levels */
        stmp = this_player()->GetSecondarySkills();
        foreach(str in stmp){
            slevels += ({this_player()->GetSkillLevel(str)});
        }   
        slevels = sort_array(slevels,-1);
        y = this_player()->GetMaxSkillLevel(stmp[0]);
       
        /* get and sort minor skills and levels */
        mtmp = this_player()->GetMinorSkills();
        foreach(str in mtmp){
            mlevels += ({this_player()->GetSkillLevel(str)});
        }
        mlevels = sort_array(mlevels,-1);
        z = this_player()->GetMaxSkillLevel(mtmp[0]);
       
        if(plevels[0] >= x && plevels[1] >= x && plevels[2] >=x
           && slevels[0] >=y && slevels[1] >= y
           && mlevels[0] >= z){
            if(AUTO_ADVANCE) AutoAdvance(ob, dlev);
            return 1;
        }
    return 0;
    }
    if(XP_ADVANCE){
        if(!ob || !playerp(ob)) return 0;
        if(!sizeof(Levels)) CompileLevelList();
        if(!Levels[dlev]) return 0;
        xp = ob->GetExperiencePoints();
        qp = ob->GetQuestPoints();
        if(xp >= Levels[dlev]["xp"] && qp >= Levels[dlev]["qp"]){
            if(AUTO_ADVANCE) AutoAdvance(ob, dlev);
            return 1;
        }
    }
    return 0;
}

Errors thrown for AddChannel() function when player logs into the mud:

/lib/player.c

Code: [Select]
//line 318 in int SetUP()
if( !GetClass() && CLASS_SELECTION ) SetClass("explorer");

//line 357
if( GetClass() ) AddChannel(GetClass());

Adding custom points when player attains a new level (optional).

genetics.c in /lib/lib

Code: [Select]
//line 42
void AddCustomizationPoints(){
     int x, y, z;
     string *stats;
     string str;

     x = this_player()->GetCustomStats(); //don't blow away players custom stat points if not used
       
     stats = this_player()->GetStats();
        foreach(str in stats){
            x += this_player()->GetBaseStatLevel(str);
            y++;
        }
        write("x is "+x+" y is "+y+"\n");
        if(x <= ((y*100)-15)){ //stat points not greater than 100
            z = random(15)+1;
            write("z is "+z+"\n");
        }else{ z = random(x)+1;
        }
     Custom = ([ "stats" : z, "deviations" : 0, "deviating" : 0, ]);
}

Where customization points get added upon leveling:

level.c in /lib/lib/lvs

Code: [Select]
//line 45
if(XP_ADVANCE){
        foreach(mixed key, mixed val in skills){
            if(skills[key]["class"] > 3) skills[key]["class"] = 4;
            subject->SetSkill(key,
                    skills[key]["level"] + moduli[skills[key]["class"]],
                    skills[key]["class"]);
        }

        foreach(mixed key, mixed val in stats){
            if(stats[key]["class"] > 3) stats[key]["class"] = 4;
            subject->SetStat(key,
                    stats[key]["level"] + moduli[stats[key]["class"]],
                    stats[key]["class"]);
        }

        subject->SetLevel(desired_level);

        if(interactive() && find_object(INSTANCES_D)){
            INSTANCES_D->SendWhoUpdate(this_object()->GetKeyName());
        }
    }

//line 65
if(SKILL_ADVANCE){
        subject->AddCustomizationPoints();
        subject->SetLevel(desired_level);

        if(interactive() && find_object(INSTANCES_D)){
            INSTANCES_D->SendWhoUpdate(this_object()->GetKeyName());
        }
    }

Offline Lash

  • Acquaintance
  • *
  • Posts: 42
    • View Profile
Re: Code for character skill based leveling
« Reply #1 on: March 15, 2016, 06:12:15 AM »
Some code changes and cleanup.

/lib/lib/secure/connect.c

changed code in eventCompleteChar() [cleanup]
Basically this changes the output depending on whether AUTO_WIZ is enabled or not upon character creation.

Code: [Select]
if(trabajo && !ptmp) Player->ChangeClass(trabajo);
    if(ptmp && !trabajo){
        string str;
        receive("\nCharacter Summary\n");
        receive("-------------------------\n");
        receive("PRIMARY Skills picked:\n");
        foreach(str in ptmp){
            receive("'"+str+"' ");
            Player->AddSkill(str,1,1);
        }
        receive("\n");
    }
    if(stmp && !trabajo){
        string str;
        receive("\nSECONDARY Skills picked:\n");
        foreach(str in stmp){
            receive("'"+str+"' ");
            Player->AddSkill(str,2,1);
        }
        receive("\n");
    }
    if(mtmp && !trabajo){
        string str;
        receive("\nMINOR Skills picked:\n");
        foreach(str in mtmp){
            receive("'"+str+"' ");
            Player->AddSkill(str,3,1);
        }
        receive("\n");
    }
    this_player()->SetTerminal("ansi");
    PLAYERS_D->AddPlayerInfo(Name);
    if(yescre){
        receive("\nAs a creator you will be booted out and have to\n"
                "log back in in order to complete the creator process\n");     
        call_out( (: eventCre, Name :), 3);
        input_to((: eventEnterGame :), I_NOESC);
    }
    receive("\nIf you wish to create a different character,\n"
              "use the 'suicide' command after logging in and\n"
              "start over. Have FUN!\n"
              "Press <return> to continue.");     
    input_to((: eventEnterGame :), I_NOESC);
}

/lib/lib/lvs/abilities.c - the GetMaxSkillPoints() function

This relaxes the amount of skill points required to advance a skill level. For skill based leveling this results (for example) in slower overall character leveling than with the default experience point leveling system if skilladvancement, instead of experience point advancement, is enabled. The other change is that with the default skill point systems, leveling based on skill advancement is painfully slow at higher levels due to the exponentiation of skill point level accrual.

With this skill based leveling system my test character is level 9 with 127069 experience points (normally level 14). Skills are as follows:
Code: [Select]
Primary skills:
blade attack        : 22/24  (71%)     magic attack        : 16/24  (56%)     
conjuring           : 16/24  (37%)     melee defense       : 21/24  (28%)     
healing             : 14/24  (20%)     

Secondary skills:
bargaining          : 14/22  (24%)                                             
blade defense       : 8/22   (89%)                                             
magic defense       : 14/22  (54%)                                             

Minor skills:
blunt attack        : 7/20   (42%)     melee attack        : 11/20  (93%)     
concealment         : 1/20   (0%)      stealing            : 1/20   (36%)     
knife attack        : 1/20   (0%)

Here are the changes:

Code: [Select]
int GetMaxSkillPoints(string skill, int level){
   if( !Skills[skill] ){
        return 0;
    }
    else if( level == 0 ){
        if(SKILL_ADVANCE){
            return 50;
        }
        else{ return 200;
        }
    }
    else {
        int cl, x;
       
       if( !(cl = Skills[skill]["class"]) ){
            if(SKILL_ADVANCE) return level * 200;
            else return level * 600;
        }
        if( cl > 4 ){
            cl = 4;
        }
        if( cl < 1 ){
            cl = 4;
        }
        x = level;
        if(SKILL_ADVANCE){
            return level * cl * 500;
        }
        else{
            while( cl-- ){
            x *= level;
            }
       return x * 400;
       }
    }
}

if interested, this project and changes are updated at https://github.com/LashMUD/TheBrassRing