Allen Kerensky"It seems you've been living two lives ..."

Myriad Lite Module Progress

// Myriad_Lite_Module_Progress-v0.0.2-20131019.lsl
// Copyright (c) 2013 by Allen Kerensky (OSG/SL) All Rights Reserved.
// This work is dual-licensed under
// Creative Commons Attribution (CC BY) 3.0 Unported
// http://creativecommons.org/licenses/by/3.0/
// - or -
// Modified BSD License (3-clause)
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, 
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Myriad Lite nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
// NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The Myriad RPG System was designed, written, and illustrated by Ashok Desai
// Myriad RPG System licensed under:
// Creative Commons Attribution (CC BY) 2.0 UK: England and Wales
// http://creativecommons.org/licenses/by/2.0/uk/
 
// FIXME - LEVEL TITLES - should this be part of the CAREER TEMPLATE?
 
// FIXME CONVERT INCAP and DEAD to FLAG
// FIXME more RPEVENT
// FIXME more HELP
 
// CONSTANTS - DO NOT CHANGE DURING RUN
string BASENAME = "Myriad Lite Module Progress";
string VERSION = "0.0.2"; // Allen Kerensky's script version
string VERSIONDATE = "20131019"; // Allen Kerensky's script yyyymmdd
 
// Module to Module Messaging Constants
integer MODULE_PROGRESS = -9;
integer LM_SENDTOATTACHMENT = 0x80000000;
 
// RUNTIME GLOBALS - MAY CHANGE
//integer XP;
//integer XPLEVEL;
list XP_BY_LEVEL; // list of XP per level from 0 to 30
integer ADD_XP_SESSION; // total of XP player has earned this session
string PROGRESSION; // Progression Method: LEVEL, GRADUAL, or RANDOM
integer FLAG_SESSION; // TRUE if player in session, FALSE if player out of session
integer XPSPENT;
integer DEL_XP_SESSION; // total of XP player lost this session
 
// Prim Persistent Memory Array Support
DELETE_RECORD(string dbkey, string field) {
    //OWNERSAY("DELETE_RECORD=["+dbkey+"] FIELD=["+field+"] START");
    // scan index or prim names for empty, get number for update
    integer i;
    string name;
    string desc;
    for ( i = 2; i <= llGetNumberOfPrims(); i++) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);        
        desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
        if ( name == dbkey && desc == field ) { // record found, delete
            llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,"",PRIM_DESC,"",PRIM_TEXT,"",ZERO_VECTOR,0.0]); // save to database
        }
    }
    //OWNERSAY("DELETE_RECORD=["+dbkey+"] FIELD=["+field+"] ENDS");
}
 
integer COUNT_TYPE(string type) {
    //OWNERSAY("COUNT_TYPE TYPE=["+type+"] STARTS");
    type = llToUpper(type);
    // scan index or prim names for empty, get number for update
    integer count = 0;
    integer i;
    string name = "";
    //string desc = "";
    //string text = "";
    for ( i = 2; i <= llGetNumberOfPrims(); i++) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
        if ( name == type ) count++;
    }
    //OWNERSAY("COUNT_TYPE TYPE=["+type+"] COUNT=["+(string)count+"]");
    return count;
}
 
DUMP_TYPE(integer chan,string type) {
    //OWNERSAY("DUMP_TYPES CHAN=["+(string)chan+"] TYPE=["+type+"] STARTS");
    type = llToUpper(type);
    // scan index or prim names for empty, get number for update
    integer i;
    string name = "";
    string desc = "";
    string text = "";
    for ( i = 2; i <= llGetNumberOfPrims(); i++) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
        if ( name == type ) {
            desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
            text = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
            if ( type == "PROGRESS" ) llSay(chan,"PROGRESS|"+desc+"="+text);
            if ( type == "STAT_INCREASE" ) llSay(chan,"STAT_INCREASE|"+desc+"="+text);
            if ( type == "SKILL_INCREASE" ) llSay(chan,"SKILL_INCREASE|"+desc+"="+text);
        }
    }
    //OWNERSAY("DUMP_TYPE ENDS");
}
 
// FIXME NEEDS TO BECOME LINK MESSAGE TO MOD_CHARSHEET!
ERASE_TYPE(string type) {
    //OWNERSAY("ERASE TYPE ["+type+"] STARTS");
    // scan index or prim names for empty, get number for update
    integer i;
    string name = "";
    string desc = "";
    for ( i = 2; i <= llGetNumberOfPrims(); i++) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
        if ( name == type ) {
            desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
            DELETE_RECORD(type,desc);
        }
    }
    //OWNERSAY("ERASE_TYPE ENDS");    
}
string KEY_NOT_FOUND = "[KEY_NOT_FOUND]";
string GET_VAL(string dbkey,string field) {
    //OWNERSAY("GET_VAL KEY=["+dbkey+"] FIELD=["+field+"]");
    string out = KEY_NOT_FOUND;
    integer i;
    string name;
    string desc;
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
        desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
        if ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) {
            out = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
            //DEBUG("GET_VAL RETURN=["+out+"]");
            return out; // bail on first match?
        }
    }
    //OWNERSAY("GET_VAL RETURN=["+out+"]");
    return out;
}
 
SET_VAL(string dbkey, string field, string val) {
    //OWNERSAY("SET_VAL KEY=["+dbkey+"] FIELD=["+field+"] VAL=["+val+"]");
    integer i;
    string name;
    string desc;
    integer written = FALSE;
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM);
        desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM);
        if ( ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) && written == FALSE ) {
            //OWNERSAY("SET_VAL UPDATE RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]");
            llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]);
            written = TRUE; // we did an update, remember it
        }    
    }
    if ( written == TRUE ) {
        //OWNERSAY("SET_VAL UPDATE COMPLETE.");
        return;
    }
    // data hasn't been written, scan for free prim
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM);
        desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM);
        if ( ( name == "" && desc == "" ) && written == FALSE ) {
            //OWNERSAY("SET_VAL INSERT RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]");
            llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]);
            written = TRUE; // we did an update, remember it
        }
    }
    if ( written == TRUE ) {
        //OWNERSAY("SET_VAL INSERT COMPLETED.");
        return;
    }
    //OWNERSAY("SET_VAL NO FREE RECORD FOUND TO INSERT INTO! DATA LOST");
}
 
//////////////////////////////////////////////////////////////////////////////
// FLAG FUNCTIONS
// INCAPACITATED - lost wounds
// DEAD - lost critical wounds
// DEBUG - show debug messages or not
// IDEA: INDECISION - lost resolve?
 
// LMIM: SET_FLAG|<name>=<TRUE|FALSE>
// PPMA: FLAG,<flagname>,<TRUE|FALSE>
// LMOUT: FLAG|<flagname>=<TRUE|FALSE>
// LMERR: FIXME
integer GET_FLAG(string flag) {
    string val = GET_VAL("FLAG",llToUpper(flag));
    if ( val == KEY_NOT_FOUND ) {
        ERROR("Flag ["+flag+"] does not exist.");
    }
    if ( val != "0" && val != "1") {
        ERROR("Flag: "+flag+" invalid value ["+val+"]");
    }
    integer bool = (integer)val;
    return bool;
}
 
// SET_FLAG
// LMIM: SET_FLAG|<flagname>=<TRUE|FALSE>
// PPMA: FLAG,<flagname>,<TRUE|FALSE>
// LMOUT: SET_FLAG|<flagname>=<TRUE|FALSE>
// LMERR: FIXME
SET_FLAG(string flag,integer value) {
    if ( flag == "" ) return; // FIXME add error
    if ( value != TRUE && value != FALSE ) return; // FIXME add err
    SET_VAL("FLAG",llToUpper(flag),(string)value);
}
 
//
// GET_MYRIAD
// Requires a MYRIAD CONSTANT NAME
// Returns the amount of that Myriad rules constant or defaultif the player does not currently have that constant
//
integer GET_MYRIAD(string setting) {
    integer retval;
    string value = GET_VAL("MYRIAD",setting);
    if ( value == KEY_NOT_FOUND ) {
        if ( setting == "MINSTAT" ) retval = 1;
        if ( setting == "MAXSTAT" ) retval = 10;
        if ( setting == "MINSKILL" ) retval = 0;
        if ( setting == "MAXSKILL" ) retval = 7;
        if ( setting == "MINEFFECT" ) retval = 0;
        if ( setting == "MAXEFFECT" ) retval = 5;
        if ( setting == "MINRESILIENCE" ) retval = 0;
        if ( setting == "MAXRESILIENCE" ) retval = 20;
        if ( setting == "MINBOON" ) retval = 0;
        if ( setting == "MAXBOON" ) retval = 5;
        if ( setting == "MINFLAW" ) retval = 0;
        if ( setting == "MAXFLAW" ) retval = 5;
        if ( setting == "MINRP" ) retval = 0;
        if ( setting == "MAXRP" ) retval = 10;
        if ( setting == "MINITEM" ) retval = 0;
        if ( setting == "MAXITEM" ) retval = 100;
        if ( setting == "MINARMOR" ) retval = 0;
        if ( setting == "MAXARMOR" ) retval = 5;
        if ( setting == "MINDAMAGE" ) retval = 0;
        if ( setting == "MAXDAMAGE" ) retval = 5;
        // CAMPAIGN constants FIXME Get from region server
        if ( setting == "MINGP" ) retval = 0;
        if ( setting == "MAXGP" ) retval = 112; // GP for godlike campaign
        if ( setting == "MINSTATPOOL" ) retval = 0;
        if ( setting == "MAXSTATPOOL" ) retval = 48; // 12 example stats in book times 4 pts per stat in godlike campaign
        if ( setting == "MINHEALTHPOOL") retval = 0;
        if ( setting == "MAXHEALTHPOOL") retval = 48; // 6 example resilience times 8 pts per resilience in godlike campaign
        if ( setting == "MINSKILLPOOL") retval = 0;
        if ( setting == "MAXSKILLPOOL") retval = 36; // 4 per 4 skills in godlike campaign, 36 example skills in book
        if ( setting == "MINEFFECTPOOL") retval = 0;
        if ( setting == "MAXEFFECTPOOL") retval = 7; // for godlike campaigns
        // Progress XP
        if ( setting == "MINXPLEVEL" ) retval = 1;
        if ( setting == "MAXXPLEVEL" ) retval = 30;
        if ( setting == "MINXP" ) retval = 0;
        if ( setting == "MAXXP") retval = 2320;
        if ( setting == "MINADDXP" ) retval = 1;
        if ( setting == "MAXADDXP" ) retval =  10;
        if ( setting == "MINDELXP" ) retval = 1;
        if ( setting == "MAXDELXP" ) retval = 10;
        if ( setting == "XPSCALE" ) retval = 1; // XPSCALE can be 1 or 10, FIXME region setting
        //    XP_BY_LEVEL = [ 0,0,10,25,45,70,100,135,175,220,270,325,385,450,520,595,675,760,850,945,1045,1150,1260,1375,1495,1620,1750,1885,2025,2170,2320 ];
        // Progress Gradual
        if ( setting == "STATCOSTFACTOR" ) retval = 5;
        if ( setting == "SKILLCOSTFACTOR" ) retval = 3;
        if ( setting == "EFFECTCOSTBASE" ) retval = 5;
        if ( setting == "EFFECTCOSTFACTOR" ) retval = 2;    
        // Progress Random
        if ( setting == "MINSTATINC" ) retval = 0;
        if ( setting == "MAXSTATINC" ) retval = 5; // after 5 stat increases, it should reset
        if ( setting == "MINSKILLINC" ) retval = 0;
        if ( setting == "MAXSKILLINC" ) retval = 5;
        if ( setting == "MINNEWSKILLS" ) retval = 0;
        if ( setting == "MAXNEWSKILLS" ) retval = 7; // FIXME arbitrarily chosen amount
        SET_VAL("MYRIAD",setting,(string)retval);
        ERROR("Unable to locate Myriad setting "+setting+" returning "+(string)retval);
    } else {
        retval = (integer)value;
    }
    return retval;
}
 
// STATISTICS
integer GET_STATISTIC(string stat) {
    // FIXME how to verify stat?
    integer points = (integer)GET_VAL("STATISTIC",stat);
    integer minstat = GET_MYRIAD("MINSTAT");
    integer maxstat = GET_MYRIAD("MAXSTAT");
    if ( points < minstat ) { points = minstat; SET_STATISTIC(stat,points); }
    if ( points > maxstat ) { points = maxstat; SET_STATISTIC(stat,points); }
    return points;
}
 
SET_STATISTIC(string statname,integer statrank) {
    // FIXME how to verify stat names are valid?
    integer minstat = GET_MYRIAD("MINSTAT");
    integer maxstat = GET_MYRIAD("MAXSTAT");
    if ( statrank < minstat ) statrank = minstat;
    if ( statrank > maxstat ) statrank = maxstat;
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SET_STATISTIC|"+statname+"="+(string)statrank,llGetOwner()); // Notify CharSheet to update statistic
}
 
// SKILLS
integer GET_SKILL(string skill) {
    integer rank = (integer)GET_VAL("SKILL",skill);
    integer minskill = GET_MYRIAD("MINSKILL");
    integer maxskill = GET_MYRIAD("MAXSKILL");
    if ( rank < minskill ) { rank = minskill; SET_SKILL(skill,rank); }
    if ( rank > maxskill ) { rank = maxskill; SET_SKILL(skill,rank); }
    return rank;
}
 
SET_SKILL(string skill,integer rank) {
    // FIXME how to verify skill names are valid?
    integer minskill = GET_MYRIAD("MINSKILL");
    integer maxskill = GET_MYRIAD("MAXSKILL");
    if ( rank < minskill ) rank = minskill;
    if ( rank > maxskill ) rank = maxskill;
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SET_SKILL|"+skill+"="+(string)rank,llGetOwner()); // notify character sheet to update skill
}
 
// SFX
integer GET_EFFECT(string name) {
    integer rank = (integer)GET_VAL("EFFECT",name);
    integer mineffect = GET_MYRIAD("MINEFFECT");
    integer maxeffect = GET_MYRIAD("MAXEFFECT");
    if ( rank < mineffect ) { rank = mineffect; SET_EFFECT(name,rank); }
    if ( rank > maxeffect ) { rank = maxeffect; SET_EFFECT(name,rank); }
    return rank;
}
 
SET_EFFECT(string name,integer rank) {
    // FIXME how to verify effect name?
    integer mineffect = GET_MYRIAD("MINEFFECT");
    integer maxeffect = GET_MYRIAD("MAXEFFECT");
    if ( rank < mineffect ) rank = mineffect;
    if ( rank > maxeffect ) rank = maxeffect;
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SET_EFFECT|"+name+"="+(string)rank,llGetOwner()); // notify character sheet to update effect
}
 
// RESILIENCES
integer GET_RESILIENCE(string name) {
    integer cr = (integer)GET_VAL("RESILIENCE",name);
    integer minres = GET_MYRIAD("MINRESILIENCE");
    integer maxres = GET_MYRIAD("MAXRESILIENCE");
    if ( cr < minres ) { cr = minres; SET_RESILIENCE(name,cr); }
    if ( cr > maxres ) { cr = maxres; SET_RESILIENCE(name,cr); }
    return cr;
}
 
SET_RESILIENCE(string resname,integer resrank) {
    // FIXME how to verify resilience names are valid?
    integer minres = GET_MYRIAD("MINRESILIENCE");
    integer maxres = GET_MYRIAD("MAXRESILIENCE");
    if ( resrank < minres ) resrank = minres;
    if ( resrank > maxres ) resrank = maxres;
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SET_RESILIENCE|"+resname+"="+(string)resrank,llGetOwner()); //notify character sheet to update resilience
}
 
//////////////////////
// PROGRESS UTILITY //
//////////////////////
 
DEL_PROGRESS(key id) {
    key objectowner = llList2Key(llGetObjectDetails(id,[OBJECT_OWNER]),0);
    key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
    if ( objectowner != regionowner ) {
        ERROR("DEL_PROGRESS called by object or player who is not region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
        return;
    }
    // Delete all progress for LEVEL, GRADUAL, and RANDOM
    SET_STATPOOL(0);
    SET_HEALTHPOOL(0);
    SET_SKILLPOOL(0);
    SET_EFFECTPOOL(0);
    SET_XPLEFT(0);
    SET_SKILL_INC(0);
    SET_NEW_SKILLS(0);
    ERASE_TYPE("SKILL_INCREASE");
    ERASE_TYPE("STAT_INCREASE");
    SET_SIXES_BURNED(0);
}
 
DUMP_PROGRESS(key id) { // id to dump progress back to
    key objectowner = llList2Key(llGetObjectDetails(id,[OBJECT_OWNER]),0);
    key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
    if ( objectowner != regionowner ) {
        ERROR("DUMP_PROGRESS called by object or player who is not region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
        return;
    }
    integer chan = (integer)("0x"+llGetSubString((key)id,0,6));
    DUMP_TYPE(chan,"PROGRESS");
    DUMP_TYPE(chan,"STAT_INCREASE");
    DUMP_TYPE(chan,"SKILL_INCREASE");
    llSay(chan,"PROGRESS_LOADED");
}
 
END_SESSION() {
    if ( FLAG_SESSION == TRUE ) {
        DEBUG("Session ending. "+(string)ADD_XP_SESSION+"XP earned.");
        FLAG_SESSION = FALSE;
    }
}
 
REPORT_POOLS() {
    string report = "PROGRESS";
    if ( PROGRESSION == "LEVEL" ) {
        report += "|"+"XP="+(string)GET_XP();
        report += "|"+"XPLEVEL="+(string)GET_XPLEVEL();
        report += "|"+"STATPOOL="+(string)GET_STATPOOL();
        report += "|"+"SKILLPOOL="+(string)GET_SKILLPOOL();
        report += "|"+"HEALTHPOOL="+(string)GET_HEALTHPOOL();
        report += "|"+"SFXPOOL="+(string)GET_EFFECTPOOL();
    }
    if ( PROGRESSION == "GRADUAL" ) {
        report += "|"+"XP="+(string)GET_XP();
        report += "|"+"XPSPENT="+(string)XPSPENT;     
    }
    if ( PROGRESSION == "RANDOM" ) {
        DEBUG("RANDOM");
        // FIXME PPMA Storage of CSV list PROGRESS,STATISTICSUSED,<csv_statname_times>
        // FIXME PPMA Storage of CSV list PROGRESS,SKILLSUSED,<csv_skillname_times>
        // FIXME PPMA Storage of CSV list PROGRESS,EFFECTSUSED,<csv_effectname_times>
    }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,report,llGetOwner());
}
 
SET_PROGRESSION(string method) {
    if ( method == "LEVEL" || method == "GRADUAL" || method == "RANDOM" ) {
        PROGRESSION = method;
        DEBUG("Character progression method set to: "+method);
    } else {
        ERROR("Unknown progression method: "+method+"! Roleplay progress will not be counted.");
    }
}
 
START_SESSION() {
    if ( PROGRESSION != "LEVEL" || PROGRESSION != "GRADUAL" || PROGRESSION != "RANDOM" ) {
        ERROR("Progression method not set. Cannot start roleplay session. Roleplay progress will not be counted.");
        return;
    }
    if ( FLAG_SESSION == FALSE ) {
        DEBUG("Session starting.");
        FLAG_SESSION = TRUE;
        ADD_XP_SESSION = 0; // zero out the XP earned this session counter
    }
}
 
////////////////
// GENERATORS //
////////////////
 
//
// GET_GP
//
integer GET_GP() {
    integer gp = (integer)GET_VAL("PROGRESS","GP");
    integer mingp = GET_MYRIAD("MINGP");
    integer maxgp = GET_MYRIAD("MAXGP");
    if ( gp < mingp ) { gp = mingp; SET_GP(gp); }
    if ( gp > maxgp ) { gp = maxgp; SET_GP(gp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|GP="+(string)gp,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return gp;
}
 
SET_GP(integer gpamt) {
    integer mingp = GET_MYRIAD("MINGP");
    integer maxgp = GET_MYRIAD("MAXGP");
    if ( gpamt < mingp ) gpamt = mingp;
    if ( gpamt > maxgp ) gpamt = maxgp;
    SET_VAL("PROGRESS","GP",(string)gpamt);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|GP="+(string)gpamt,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
}
 
// STATPOOL - GENERATOR POINTBUY, UPDATER LEVEL
integer GET_STATPOOL() {
    integer sp = (integer)GET_VAL("PROGRESS","STATPOOL");
    integer minstatpool = GET_MYRIAD("MINSTATPOOL");
    integer maxstatpool = GET_MYRIAD("MAXSTATPOOL");
    if ( sp < minstatpool ) { sp = minstatpool; SET_STATPOOL(sp); }
    if ( sp > maxstatpool ) { sp = maxstatpool; SET_STATPOOL(sp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|STATPOOL="+(string)sp,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return sp;
}
 
SET_STATPOOL(integer statamt) {
    integer minstatpool = GET_MYRIAD("MINSTATPOOL");
    integer maxstatpool = GET_MYRIAD("MAXSTATPOOL");
    if ( statamt < minstatpool ) statamt = minstatpool;
    if ( statamt > maxstatpool ) statamt = maxstatpool;
    SET_VAL("PROGRESS","STATPOOL",(string)statamt);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|STATPOOL="+(string)statamt,llGetOwner()); //FIXME remove unneeded link message when PPMA complete
}
 
// HEALTHPOOL - GENERATOR POINTBUY, UPDATER LEVEL
integer GET_HEALTHPOOL() {
    integer hp = (integer)GET_VAL("PROGRESS","HEALTHPOOL");
    integer minhealthpool = GET_MYRIAD("MINHEALTHPOOL");
    integer maxhealthpool = GET_MYRIAD("MAXHEALTHPOOL");
    if ( hp < minhealthpool ) { hp = minhealthpool; SET_HEALTHPOOL(hp); }
    if ( hp > maxhealthpool ) { hp = maxhealthpool; SET_HEALTHPOOL(hp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|HEALTHPOOL="+(string)hp,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return hp;
}
 
SET_HEALTHPOOL(integer healthamt) {
    integer minhealthpool = GET_MYRIAD("MINHEALTHPOOL");
    integer maxhealthpool = GET_MYRIAD("MAXHEALTHPOOL");
    if ( healthamt < minhealthpool ) healthamt = minhealthpool;
    if ( healthamt > maxhealthpool ) healthamt = maxhealthpool;
    SET_VAL("PROGRESS","HEALTHPOOL",(string)healthamt);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|HEALTHPOOL="+(string)healthamt,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
}
 
// SKILLPOOL - GENERATOR POINTBUY, UPDATER LEVEL
integer GET_SKILLPOOL() {
    integer skp = (integer)GET_VAL("PROGRESS","SKILLPOOL");
    integer minskillpool = GET_MYRIAD("MINSKILLPOOL");
    integer maxskillpool = GET_MYRIAD("MAXSKILLPOOL");
    if ( skp < minskillpool ) { skp = minskillpool; SET_SKILLPOOL(skp); }
    if ( skp > maxskillpool ) { skp = maxskillpool; SET_SKILLPOOL(skp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SKILLPOOL="+(string)skp,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return skp;
}
 
SET_SKILLPOOL(integer skillamt) {
    integer minskillpool = GET_MYRIAD("MINSKILLPOOL");
    integer maxskillpool = GET_MYRIAD("MAXSKILLPOOL");
    if ( skillamt < minskillpool ) skillamt = minskillpool;
    if ( skillamt > maxskillpool ) skillamt = maxskillpool;
    SET_VAL("PROGRESS","SKILLPOOL",(string)skillamt);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SKILLPOOL="+(string)skillamt,llGetOwner()); //FIXME remove unneeded link message when PPMA complete
}
 
// SFXPOOL - GENERATOR POINTBUY, UPDATER LEVEL
integer GET_EFFECTPOOL() {
    integer sfp = (integer)GET_VAL("PROGRESS","EFFECTPOOL");
    integer mineffectpool = GET_MYRIAD("MINEFFECTPOOL");
    integer maxeffectpool = GET_MYRIAD("MAXEFFECTPOOL");
    if ( sfp < mineffectpool ) { sfp = mineffectpool; SET_EFFECTPOOL(sfp); }
    if ( sfp > maxeffectpool ) { sfp = maxeffectpool; SET_EFFECTPOOL(sfp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SFXPOOL="+(string)sfp,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return sfp;
}
 
SET_EFFECTPOOL(integer sfxamt) {
    integer mineffectpool = GET_MYRIAD("MINEFFECTPOOL");
    integer maxeffectpool = GET_MYRIAD("MAXEFFECTPOOL");
    if ( sfxamt < mineffectpool ) sfxamt = mineffectpool;
    if ( sfxamt > maxeffectpool ) sfxamt = maxeffectpool;
    SET_VAL("PROGRESS","EFFECTPOOL",(string)sfxamt);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SFXPOOL="+(string)sfxamt,llGetOwner()); // FIXME remove unneeded link message when PPMA complete    
}
 
///////////////////////////
// PROGRESS: LEVEL-BASED //
///////////////////////////
//
// XP
integer GET_XP() {
    integer xp = (integer)GET_VAL("PROGRESS","XP");
    integer minxp = GET_MYRIAD("MINXP");
    integer maxxp = GET_MYRIAD("MAXXP");
    if ( xp < minxp ) { xp = minxp; SET_XP(xp); }
    if ( xp > maxxp ) { xp = maxxp; SET_XP(xp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|XP="+(string)xp,llGetOwner());
    return xp;
}
 
SET_XP(integer amount) {
    integer xp;
    integer minxp = GET_MYRIAD("MINXP");
    integer maxxp = GET_MYRIAD("MAXXP");
    integer xplevel = GET_XPLEVEL();
    if ( amount >= minxp && amount <= maxxp ) {
        xp = amount;
        integer templevel = GET_LEVEL_BY_XP(xp);
        if ( templevel != 0 ) {
            if ( templevel > xplevel ) { // player is levelling up!
                LEVELUP(templevel); // level up player
            }
            xplevel = templevel; // set new XP level to current 
        }
        DEBUG("XP set to: "+(string)xp+" XPLEVEL set to: "+(string)xplevel);
    } else {
        ERROR("XP "+(string)amount+" out of range: "+(string)minxp+"-"+(string)maxxp);
    }
    if ( xp < minxp ) xp = minxp;
    if ( xp > maxxp ) xp = maxxp;
    SET_VAL("PROGRESS","XP",(string)xp);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|XP="+(string)xp,llGetOwner());
}
 
ADD_XP(integer amount) {
    integer maxxp = GET_MYRIAD("MAXXP");
    integer minaddxp = GET_MYRIAD("MINADDXP");
    integer maxaddxp = GET_MYRIAD("MAXADDXP");
    if ( amount >= minaddxp && amount <= maxaddxp ) {
        if ( amount + ADD_XP_SESSION > maxaddxp ) {
            DEBUG("Max XP gain per session reached. Unable to add more XP.");
        } else {
            SET_XP(GET_XP() + amount); // add XP to player pool
            ADD_XP_SESSION += amount; // keep track of how many earned this session
        }
    } else {
        ERROR("ADD_XP amount "+(string)amount+" out of range: "+(string)minaddxp+"-"+(string)maxaddxp);
    }
    if ( GET_XP() > maxxp ) SET_XP(maxxp); // FIXME need to set XP in PPMA lol
}
 
// ADD_XP - Add a point of XP
ADD_XP2(key granterid) {
    key objectowner = llList2Key(llGetObjectDetails(granterid,[OBJECT_OWNER]),0);
    key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
    if ( objectowner != regionowner ) {
        ERROR("ADD_XP called by object or player who is not region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
        return;
    }
    DEBUG("Progression="+PROGRESSION+" XP="+(string)GET_XP()+" XPLEVEL="+(string)GET_XPLEVEL());
    if ( PROGRESSION == "RANDOM" ) {
        ERROR("Unable to add XP in region using Random Progression");
        return;
    }
    integer maxxp = GET_MYRIAD("MAXXP");
    if ( GET_XP() < maxxp ) {
        SET_XP( GET_XP() + 1); // add one to total XP
        if ( PROGRESSION == "LEVEL-BASED" ) {
            integer currentlevel = GET_XPLEVEL();
            integer templevel = GET_LEVEL_BY_XP(GET_XP());            
            if ( templevel > currentlevel ) {
                SET_XPLEVEL(templevel);
                string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names
                OWNERSAY("LEVEL UP! Congratulations, you are now XP Level "+(string)GET_XPLEVEL());
                RPEVENT("LEVEL UP! Congratulations, "+ownername+" is now XP Level "+(string)GET_XPLEVEL());
                LEVELUP(GET_XPLEVEL());
            }
            return;
        }
        if ( PROGRESSION == "GRADUAL" ) {
            SET_XPLEFT ( GET_XPLEFT() + 1); // add one to XP you can spend in gradual progress mode
            return;
        }
        llMessageLinked(LINK_THIS,MODULE_PROGRESS,"XP_CHANGED|XP="+(string)GET_XP(),llGetOwner());
        DEBUG("Progression="+PROGRESSION+" XP="+(string)GET_XP()+" XPLEVEL="+(string)GET_XPLEVEL());
    } else {
        ERROR("ADD_XP(): XP already maxed.");
    }
}
 
DEL_XP(integer amount) {
    integer minxp = GET_MYRIAD("MINXP");
    integer mindelxp = GET_MYRIAD("MINDELXP");
    integer maxdelxp = GET_MYRIAD("MAXDELXP");
    if ( amount >= mindelxp && amount <= maxdelxp ) {
        if ( amount + DEL_XP_SESSION > maxdelxp ) {
            DEBUG("Max XP loss per session reached. Unable to add more XP.");
        } else {
            SET_XP(GET_XP() - amount); // add XP to player pool
            DEL_XP_SESSION += amount; // 
        }
    } else {
        ERROR("DEL_XP amount "+(string)amount+" out of range: "+(string)mindelxp+"-"+(string)maxdelxp);
    }
    if ( GET_XP() < 0 ) SET_XP(minxp); // FIXME save XP to PPMA lol
}
 
integer GET_LEVEL_BY_XP(integer amount) {
    integer count = 0;
    integer outlevel = 0;
    integer maxxplevel = GET_MYRIAD("MAXXPLEVEL");
    for (count = 0; count < maxxplevel; count++ ) {
        if ( amount > llList2Integer(XP_BY_LEVEL,count) ) outlevel = count;
    }
    DEBUG("GET_LEVEL_BY_XP("+(string)amount+") returning "+(string)outlevel);
    return outlevel;
}
 
ADD_XPLEVEL() {
    integer maxxplevel = GET_MYRIAD("MAXXPLEVEL");
    integer xplevel = GET_XPLEVEL();
    if ( xplevel < maxxplevel ) {
        SET_XPLEVEL(xplevel++); // add one
        DEBUG("Level added. New level: "+(string)xplevel);
    } else {
        ERROR("Unable to add a level. Current level already: "+(string)xplevel);
    }
    integer tempxp = GET_XP_BY_LEVEL(xplevel);
    if ( tempxp != 0 ) { 
        if ( tempxp > GET_XP() ) SET_XP(tempxp); // if new level XP greater than current saved XP
    }
}
 
DEL_XPLEVEL() {
    integer minxplevel = GET_MYRIAD("MINXPLEVEL"); 
    integer xplevel = GET_XPLEVEL();
    if ( xplevel > minxplevel ) {
        SET_XPLEVEL(xplevel--);
        DEBUG("Level deleted. New level: "+(string)xplevel);
    } else {
        ERROR("Unable to delete a level. Current level already: "+(string)xplevel);
    }
    integer tempxp = GET_XP_BY_LEVEL(xplevel);
    if ( tempxp != 0 ) SET_XP(tempxp); // FIXME PPMA XP
}
 
SET_XPLEVEL(integer amount) {
    integer xplevel;
    integer minxplevel = GET_MYRIAD("MINXPLEVEL");
    integer maxxplevel = GET_MYRIAD("MAXXPLEVEL");
    if ( amount < minxplevel || amount > maxxplevel ) {
        ERROR("Set level "+(string)amount+" out of range: "+(string)minxplevel+"-"+(string)maxxplevel);
        return;
    }
    xplevel = amount; 
    integer tempxp = GET_XP_BY_LEVEL(xplevel);
    if ( tempxp != 0 ) SET_XP(tempxp); // set 
 
    if ( xplevel < minxplevel ) xplevel = minxplevel;
    if ( xplevel > maxxplevel ) xplevel = maxxplevel;
    SET_VAL("PROGRESS","XPLEVEL",(string)xplevel);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|XPLEVEL="+(string)xplevel,llGetOwner());
}
 
integer GET_XP_BY_LEVEL(integer amount ) {
    integer minxplevel = GET_MYRIAD("MINXPLEVEL");
    integer maxxplevel = GET_MYRIAD("MAXXPLEVEL");
    if ( amount >= minxplevel && amount <= maxxplevel ) {
        return llList2Integer(XP_BY_LEVEL,amount);
    }
    ERROR("Requested level "+(string)amount+" out of range "+(string)minxplevel+"-"+(string)maxxplevel);
    return 0;
}
 
// XPLEVEL
integer GET_XPLEVEL() {
    integer xplevel = (integer)GET_VAL("PROGRESS","XPLEVEL");
    integer minxplevel = GET_MYRIAD("MINXPLEVEL");
    integer maxxplevel = GET_MYRIAD("MAXXPLEVEL");
    if ( xplevel < minxplevel ) { xplevel = minxplevel; SET_XPLEVEL(xplevel); }
    if ( xplevel > maxxplevel ) { xplevel = maxxplevel; SET_XPLEVEL(xplevel); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|XPLEVEL="+(string)xplevel,llGetOwner());
    return xplevel;
}
 
// XP LEVEL
CALCULATE_LEVEL_BY_XP() {
    integer i;
    for ( i=1; i < llGetListLength(XP_BY_LEVEL); i++ ) {
        integer basexp = llList2Integer(XP_BY_LEVEL,i);
        if ( GET_XP() >= basexp ) {
            SET_XPLEVEL(i);
        }
    }
}
 
// LEVEL UP - Calculate bonuses related to new level
LEVELUP(integer newlevel) {
    // In the Myriad system, each time a character gains a new level he is given two skill points, each of which can be used to purchase a new skill at level 1 or improve an existing skill by one level.
    SET_SKILLPOOL ( GET_SKILLPOOL() + 2 ); // add two skill points per level
    // The character also gains an SFX point on every even-numbered level if the module being played supports SFX.
    if ( ( newlevel % 2 ) == 0 ) { // every even level
        SET_EFFECTPOOL ( GET_EFFECTPOOL () + 1 ); // add new SFX point
    }
    // One new health point may also be used to upgrade any one of the character's resilience lines, or saved in order to buy a box that would normally cost two points.
    SET_HEALTHPOOL ( GET_HEALTHPOOL() + 1 ); // add a point of health
    // Finally, the character earns one quarter of a statistic point to improve any one statistic with.
    if ( ( newlevel % 4 ) == 0 ) { // every 4th level
        SET_STATPOOL( GET_STATPOOL() + 1 ); // add a new stat point
    }
}
 
// ADD_OPPONENT_XP - add a scale of XP based on opponent's level
// +2 pts if enemy 2 or more levels above, 1 point if enemy is 1 level above or below, and zero points if enemy 2 or more levels lowere
ADD_OPPONENT_XP(integer opponentlevel) {
    integer diff = opponentlevel - GET_XPLEVEL();
    integer add = 0;
    if ( diff >= 2 ) add = 2;
    if ( diff >= -1 && diff <= 1 ) add = 1;
    if ( add == 0 ) {
        OWNERSAY("No XP given for lower level opponent.");
        return;
    }
    OWNERSAY("You earned "+(string)add+" XP for that opponent.");
    ADD_XP(add);
}
 
// ADD_OPPONENT_XP_X10
// if XPSCALE = 10, multiply all the values in the XP level table by ten and use the following rule:
// a monster is worth 3XP, minus 1 for every level that it is below the character's own to a minimum of zero, or plus one for every level that it is above the character's own to a maximum of ten.
//
ADD_OPPONENT_XP_X10(integer opponentlevel) {
    integer base = 3;
    integer add;
    integer diff;
    integer xplevel = GET_XPLEVEL();
    if ( opponentlevel > xplevel ) {
        diff = opponentlevel - xplevel;
        if ( diff > 10 ) diff = 10;
        add = base + diff;
    }
    if ( opponentlevel < xplevel ) {
        diff = xplevel - opponentlevel;
        add = base - diff;
        if ( add < 0 ) add = 0;
    }
    OWNERSAY("You earned "+(string)add+" XP for that opponent.");
    ADD_XP(add);
}
 
///////////////////////
// PROGRESS: GRADUAL //
///////////////////////
//
// XPLEFT
//
integer GET_XPLEFT() {
    integer xp = (integer)GET_VAL("PROGRESS","XPLEFT");
    integer minxp = GET_MYRIAD("MINXP");
    integer maxxp = GET_MYRIAD("MAXXP");
    if ( xp < minxp ) { xp = minxp; SET_XPLEFT(xp); }
    if ( xp > maxxp ) { xp = maxxp; SET_XPLEFT(xp); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|XPLEFT="+(string)xp,llGetOwner());
    return xp;
}
 
SET_XPLEFT(integer xp) {
    integer minxp = GET_MYRIAD("MINXP");
    integer maxxp = GET_MYRIAD("MAXXP");
    if ( xp < minxp ) xp = minxp;
    if ( xp > maxxp ) xp = maxxp;
    SET_VAL("PROGRESS","XPLEFT",(string)xp);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|XPLEFT="+(string)xp,llGetOwner());
}
 
 
//
// FIXME SPENDXP
//
SPENDXP() {
    if ( FLAG_SESSION == TRUE ) {
        ERROR("Cannot spend XP during play session.");
        return;
    }
}
 
//
// FIXME IMPROVE_STATISTIC
// FIXME - list of stats and whether or not they've been improved this session?
//
IMPROVE_STAT(string statname) {
}
 
//
// FIXME IMPROVE_SKILL
//
IMPROVE_SKILL(string skillname) {
}
 
//
// FIXME IMPROVE_EFFECT
//
IMPROVE_EFFECT(string effect) {
}
 
//
// FIXME CALC_STAT_COST
//
integer CALC_STAT_COST(string statname) {
    integer statcostfactor = GET_MYRIAD("STATCOSTFACTOR");
    integer cost = ( GET_STATISTIC(statname) + 1) * statcostfactor;
    DEBUG("Cost to increase stat "+statname+": "+(string)cost);
    return cost;
}
 
//
// FIXME CALC_SKILL_COST
//
integer CALC_SKILL_COST(string skillname) { // calculate to raise existing OR buy new
    integer skillcostfactor = GET_MYRIAD("SKILLCOSTFACTOR");
    integer cost = ( GET_SKILL(skillname) + 1) * skillcostfactor;
    DEBUG("Cost to increase skill "+skillname+": "+(string)cost);
    return cost;
}
 
//
// FIXME CALC_EFFECT_COST
//
integer CALC_SFX_COST(string sfxname) { // calculate cost to buy new
    integer count = COUNT_TYPE("EFFECT");
    integer effectcostbase = GET_MYRIAD("EFFECTCOSTBASE");
    integer effectcostfactor = GET_MYRIAD("EFFECTCOSTFACTOR");
    integer cost = effectcostbase + ( effectcostfactor * count );
    DEBUG("Cost to buy new special effect ability "+sfxname+": "+(string)cost);
    return cost;
}
 
//////////////////////
// PROGRESS: RANDOM //
//////////////////////
 
INCREASE_STAT(string sname) {
    integer rank = GET_STATISTIC(sname);
    SET_STATISTIC(sname,rank + 1);
}
 
integer GET_STAT_INC(string stat) {
    // FIXME how to verify stat?
    integer points = (integer)GET_VAL("STAT_INCREASE",stat);
    integer minstatinc = GET_MYRIAD("MINSTATINC");
    integer maxstatinc = GET_MYRIAD("MAXSTATINC");
    if ( points < minstatinc ) { points = minstatinc; SET_STAT_INC(stat,points); }
    if ( points > maxstatinc ) { points = maxstatinc; SET_STAT_INC(stat,points); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"STAT_INCREASE|"+stat+"="+(string)points,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return points;
}
 
SET_STAT_INC(string statname,integer amt) {
    // FIXME how to verify stat names are valid?
    integer minstatinc = GET_MYRIAD("MINSTATINC");
    integer maxstatinc = GET_MYRIAD("MAXSTATINC");
    if ( amt < minstatinc ) { amt = maxstatinc; }
    if ( amt > maxstatinc ) { 
        amt = minstatinc; // reset to zero and increase stat itself
        SET_STATISTIC(statname, GET_STATISTIC(statname) + 1 );
    }
    SET_VAL("STAT_INCREASE",statname,(string)amt); // save new amount
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SET_STAT_INCREASE|"+statname+"="+(string)amt,llGetOwner()); // FIXME remove unneeded link message when PPMA complete    
}
 
INCREASE_STAT_INC(string sname) {
    integer rank = GET_STAT_INC(sname);
    SET_STAT_INC(sname,rank + 1);
}
 
INCREASE_SKILL(string skill) {
    integer rank = GET_SKILL(skill);
    integer maxskill = GET_MYRIAD("MAXSKILL");
    if ( rank < maxskill ) {
        SET_SKILL(skill,rank + 1);
        RPEVENT("increased their "+skill+" skill by one.");    
    } else {
        ERROR("INCREASE_SKILL("+skill+") CANNOT INCREASE PAST MAX SKILL LEVEL "+(string)maxskill);
    }
}
 
// SKILL INCREASES
integer GET_SKILL_INC() {
    integer inc = (integer)GET_VAL("PROGRESS","SKILL_INCREASES");
    integer minskillinc = GET_MYRIAD("MINSKILLINC");
    integer maxskillinc = GET_MYRIAD("MAXSKILLINC");
    if ( inc < minskillinc ) { inc = minskillinc; SET_SKILL_INC(inc); }
    if ( inc > maxskillinc ) { inc = maxskillinc; SET_SKILL_INC(inc); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SKILL_INCREASES="+(string)inc,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return inc;
}
 
SET_SKILL_INC(integer si) {
    integer minskillinc = GET_MYRIAD("MINSKILLINC");
    integer maxskillinc = GET_MYRIAD("MAXSKILLINC");
    if ( si < minskillinc ) si = minskillinc;
    if ( si > maxskillinc ) si = maxskillinc;
    SET_VAL("PROGRESS","SKILL_INCREASES",(string)si);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SKILL_INCREASES="+(string)si,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
}
 
// NEW SKILLS - number of new skills earned through RANDOM skill increases since last reset
integer GET_NEW_SKILLS() {
    integer new = (integer)GET_VAL("PROGRESS","NEW_SKILLS"); // FIXME NEWSKILLS
    integer minnewskills = GET_MYRIAD("MINNEWSKILLS");
    integer maxnewskills = GET_MYRIAD("MAXNEWSKILLS");
    if ( new < minnewskills ) { new = minnewskills; SET_NEW_SKILLS(new); }
    if ( new > minnewskills ) { new = maxnewskills; SET_NEW_SKILLS(new); }
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|NEW_SKILLS="+(string)new,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return new;
}
 
SET_NEW_SKILLS(integer new) {
    integer minnewskills = GET_MYRIAD("MINNEWSKILLS");
    integer maxnewskills = GET_MYRIAD("MAXNEWSKILLS");
    if ( new < minnewskills ) new = minnewskills;
    if ( new > maxnewskills ) new = maxnewskills;
    SET_VAL("PROGRESS","NEW_SKILLS",(string)new); // FIXME NEWSKILLS
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|NEW_SKILLS="+(string)new,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
}
 
integer GET_SKILLS_INCREASED(string name) {
    integer num = (integer)GET_VAL("SKILL_INCREASE",name);
    // FIXME need range checks
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SKILL_INCREASE|"+name+"="+(string)num,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return num;
}
 
SET_SKILLS_INCREASED(string name,integer rank) {
    // FIXME need range checks
    SET_VAL("SKILL_INCREASE",name,(string)rank);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"SKILL_INCREASE|"+name+"="+(string)rank,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
}
 
SKILL_FAILED(string sname) {
    if ( PROGRESSION != "RANDOM" ) return; // tracking skill failure only applies to random progression - ignore message
    integer current = GET_SKILLS_INCREASED(sname);
    if ( current < 4 ) { // not yet, just note it
    } else { // has hit 5, increase skill and stat
        INCREASE_SKILL(sname); // increase skill
        SET_SKILLS_INCREASED(sname,current + 1);
    }
    integer si = GET_SKILL_INC() + 1;
    SET_SKILL_INC(si);
    // now - 5 skill increases = new skill earned
    if ( si == 5 ) {
        SET_SKILL_INC(0);
        SET_NEW_SKILLS ( GET_NEW_SKILLS() + 1);
        RPEVENT("earned a new skill!");
    }
}
 
integer GET_SIXES_BURNED() {
    integer sb = (integer)GET_VAL("PROGRESS","SIXES_BURNED");
    // FIXME range checking
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SIXES_BURNED="+(string)sb,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
    return sb;
}
 
SET_SIXES_BURNED(integer sb) {
    // FIXME range checking
    SET_VAL("PROGRESS","SIXES_BURNED",(string)sb);
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"PROGRESS|SIXES_BURNED="+(string)sb,llGetOwner()); // FIXME remove unneeded link message when PPMA complete
}
 
/////////////
// UTILITY //
/////////////
 
//
// DEBUG - show debug chat with wearer name for sorting
//
DEBUG(string dmessage) {
    if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_PROGRESS,"DEBUG|"+dmessage,llGetOwner());
}
 
//
// ERROR - show errors on debug channel with wearer name for sorting
//
ERROR(string emessage) {
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"ERROR|"+emessage,llGetOwner());
}
 
//
// GET_MEMORY
//
GET_MEMORY() {
    OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory()); // show this module's free memory info
}
 
//
// GET_VERSION
//
GET_VERSION() {
    OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE); // show this module's version info
}
 
//
// OWNERSAY
//
OWNERSAY(string msg) {
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"OWNERSAY|"+msg,llGetOwner());
}
 
//
// PARSE
//
PARSE(string message,key id) {
    // No need for debug - only link messages to this module and debug is done there
    // First - handle type 1 messages that do not require breaking down
    list tokens = llParseString2List(message,["|"],[]);
    string cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));    
    string data = llList2String(tokens,1);
    list subtokens = llParseString2List(data,["="],[]);
    string attrib = llList2String(subtokens,0);
    integer idata = llList2Integer(subtokens,1);
    string sdata = llList2String(subtokens,1);
 
    if ( cmd == "memory" ) { GET_MEMORY(); return; }        
    if ( cmd == "reset" ) { RESET(); return; }
    if ( cmd == "version" ) { GET_VERSION(); return;} // show version info
 
    // Progression
    if ( cmd == "set_progression" ) { SET_PROGRESSION(sdata); return; } // set progress method to LEVEL, GRADUAL, or RANDOM
    if ( cmd == "start_session" ) { START_SESSION(); return; }
    if ( cmd == "end_session" ) { END_SESSION(); return; }
    if ( cmd == "report_pools" ) { REPORT_POOLS(); return;} // used for character builder to ask player HUD for current pools
    // Progression API?
    if ( cmd == "add_xp" ) { ADD_XP(idata); return; }// ADD_XP|XP=###
    if ( cmd == "del_xp" ) { DEL_XP(idata); return; } // DEL_XP|XP=###
    if ( cmd == "add_xplevel" ) { ADD_XPLEVEL(); return; }// ADD_XPLEVEL|XPLEVEL=###
    if ( cmd == "del_xplevel" ) { DEL_XPLEVEL(); return; }// DEL_XPLEVEL|XPLEVEL=###
 
    // FIXME remove unneeded link message when PPMA complete all GET_* command blocks once PPMA is integrated in all HUD scripts    
    if ( cmd == "dump_progress" ) { DUMP_PROGRESS(id); return; } // id is UUID of character builder, sent over from HUD script
    if ( cmd == "del_progress" ) { DEL_PROGRESS(id); return; } // id is UUID of item that requested del progress
    if ( cmd == "add_xp2" ) { ADD_XP2(id); return; } // add a point of total and spendable XP
    if ( cmd == "region_setting" && llToLower(attrib) == "progression" ) { SET_PROGRESSION(sdata); return;}
    if ( cmd == "set_xp" ) { SET_XP(idata); return;}
    if ( cmd == "set_xplevel" ) { SET_XPLEVEL(idata); return;}
    if ( cmd == "set_gp" ) { SET_GP(idata); return;}
    if ( cmd == "set_statpool" ) { SET_STATPOOL(idata); return;}
    if ( cmd == "set_healthpool" ) { SET_HEALTHPOOL(idata); return;}
    if ( cmd == "set_skillpool" ) { SET_SKILLPOOL(idata); return;}
    if ( cmd == "set_sfxpool" ) { SET_EFFECTPOOL(idata); return;}
    if ( cmd == "skill_failed" ) { SKILL_FAILED(sdata); return; } // SKILL_FAILED|SKILL=name
    if ( cmd == "progress" ) { // remove me later
        if ( llToLower(attrib) == "xp" ) { SET_XP(idata); return;}
        if ( llToLower(attrib) == "xplevel" ) { SET_XPLEVEL(idata); return;}
        if ( llToLower(attrib) == "statpool" ) { SET_STATPOOL(idata); return; }
        if ( llToLower(attrib) == "healthpool" ) { SET_HEALTHPOOL(idata); return; }
        if ( llToLower(attrib) == "skillpool" ) { SET_SKILLPOOL(idata); return; }
        if ( llToLower(attrib) == "sfxpool" ) { SET_EFFECTPOOL(idata); return; }
        return;
    }
}
 
//
// RESET - shut down running animations then reset the script to reload character sheet
//
RESET() {
    llResetScript(); // now reset
}
 
//
// RPEVENT
//
RPEVENT(string rpevent) {
    llMessageLinked(LINK_THIS,MODULE_PROGRESS,"RPEVENT|"+rpevent,llGetOwner());
}
 
//
// SETUP - begin bringing the HUD online
//
SETUP() {
    XP_BY_LEVEL = [ 0,0,10,25,45,70,100,135,175,220,270,325,385,450,520,595,675,760,850,945,1045,1150,1260,1375,1495,1620,1750,1885,2025,2170,2320 ];
    OWNERSAY("Progress module active.");
}
 
//
// DEFAULT STATE
//
default {
 
    link_message(integer sender_num,integer sender,string message,key id) {
        if ( sender == MODULE_PROGRESS || sender == LM_SENDTOATTACHMENT ) return; // ignore link messages not sent to us specifically
 
        // First - handle type 1 messages that do not require breaking down
        list tokens = llParseString2List(message,["|"],[]);
        string cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
 
        if ( cmd == "debug" || cmd == "error" || cmd == "help" || cmd == "ownersay" || cmd == "rpevent" ) return; // ignore WELL commands
        // only debug when its module specific 
        DEBUG("EVENT: link_message("+(string)sender_num+","+(string)sender+","+message+","+(string)id+")");        
        PARSE(message,id); // parse incoming message
    } // end of link_message event
 
    // STATE ENTRY - called on Reset
    state_entry() {
        SETUP(); // show credits and start character sheet load
    }
} // end state
// END
This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies
DokuWiki