Myriad Lite Module Character Sheet

// Myriad_Lite_Module_Character_Sheet-v0.0.10-20131019.lsl
// Copyright (c) 2012-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/
 
// VERSION CONTROL
string BASENAME = "Myriad Lite Module Character Sheet";
string VERSION = "0.0.10"; // Allen Kerensky's script version
string VERSIONDATE = "20131019"; // Allen Kerensky's script yyyymmdd
 
// MODULE TO MODULE MESSAGING CONSTANTS
integer MODULE_CHARSHEET = -2;
integer LM_SENDTOATTACHMENT = 0x80000000; // FIXME move to SENDTOATTACH|<message>
 
//
// 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");
}
 
DUMP_RECORDS() {
    //OWNERSAY("DUMP_RECORDS STARTS");
    // 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);
        desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
        if ( name != "" && desc != "" ) {
            text = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
            OWNERSAY("RECORD=["+(string)i+"] NAME=["+name+"] DESC=["+desc+"] TEXT=["+text+"]");
        }
    }
    //OWNERSAY("DUMP_RECORDS ENDS");
}
 
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 == "CHARACTER" ) llSay(chan,"CHARACTER|"+desc+"="+text);
            if ( type == "STATISTIC" ) llSay(chan,"STATISTIC|NAME="+desc+"|"+text);
            if ( type == "SKILL" ) llSay(chan,"SKILL|NAME="+desc+"|"+text);
            if ( type == "EFFECT" ) llSay(chan,"EFFECT|NAME="+desc+"|"+text);            
            if ( type == "RESILIENCE" ) llSay(chan,"RESILIENCE|NAME="+desc+"|"+text);
            if ( type == "BOON" ) llSay(chan,"BOON|NAME="+desc+"|"+text);
            if ( type == "FLAW" ) llSay(chan,"FLAW|NAME="+desc+"|"+text);
            if ( type == "CAMPAIGN" ) llSay(chan,"CAMPAIGN|NAME="+desc+"|"+text);
            if ( type == "SPECIE" ) llSay(chan,"SPECIE|NAME="+desc+"|"+text);
            if ( type == "BACKGROUND" ) llSay(chan,"BACKGROUND|NAME="+desc+"|"+text);
            if ( type == "CAREER" ) llSay(chan,"CAREER|NAME="+desc+"|"+text);            
            if ( type == "ITEM" ) llSay(chan,"ITEM|NAME="+desc+"|"+text);
            if ( type == "STUNT" ) llSay(chan,"STUNT|NAME="+desc+"|"+text);
            if ( type == "QUOTE" ) llSay(chan,"QUOTE|NAME="+desc+"|"+text);
        }
    }
    //OWNERSAY("DUMP_TYPE ENDS");
}
 
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");    
}
 
FORMAT_DB() {
    //OWNERSAY("FORMATTING DATABASE");
    integer i;
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        if ( llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0) != "" ) {
            llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,"",PRIM_DESC,"",PRIM_TEXT,"",ZERO_VECTOR,0.0]);
        }
    }
    //OWNERSAY("DATABASE FORMAT COMPLETE");
}
 
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;
}
 
LIST_TYPE(key id,string type) {
    integer chan = (integer)("0x"+llGetSubString(id,0,6));
    type = llToUpper(type);
    // scan index or prim names for empty, get number for update
    integer i;
    string name = "";
    string desc = "";
    list out = [];
    for ( i = 2; i <= llGetNumberOfPrims(); i++) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
        if ( name == type ) {
            desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
            out = out + [desc];
        }
    }
    out = llListSort(out,1,TRUE);
    if ( type == "STATISTIC" ) llSay(chan,"STATISTICS|"+llList2CSV(out));
    if ( type == "SKILL" ) llSay(chan,"SKILLS|"+llList2CSV(out));
    if ( type == "EFFECT" ) llSay(chan,"EFFECTS|"+llList2CSV(out));
    if ( type == "RESILIENCE" ) llSay(chan,"RESILIENCES|"+llList2CSV(out));
    if ( type == "BOON" ) llSay(chan,"BOONS|"+llList2CSV(out));
    if ( type == "FLAW" ) llSay(chan,"FLAWS|"+llList2CSV(out));
    if ( type == "CAMPAIGN" ) llSay(chan,"CAMPAIGNS|"+llList2CSV(out));
    if ( type == "SPECIE" ) llSay(chan,"SPECIES|"+llList2CSV(out));
    if ( type == "BACKGROUND" ) llSay(chan,"BACKGROUNDS|"+llList2CSV(out));
    if ( type == "CAREER" ) llSay(chan,"CAREERS|"+llList2CSV(out));
    if ( type == "ITEM" ) llSay(chan,"ITEMS|"+llList2CSV(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
// 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 = 1;
        if ( setting == "MAXDAMAGE" ) retval = 5;
        ERROR("Unable to locate Myriad setting "+setting+" returning "+(string)retval);
    } else {
        retval = (integer)value;
    }
    return retval;
}
 
///////////////////
// ASPECTS
///////////////////
 
// STATISTICS
// LMIN: GET_STATISTIC|<name>
// PPMA: STATISTIC,<name>,<rank>
// LMOUT: STATISTIC|<name>=<rank>
// LMERR: FIXME
integer GET_STATISTIC(string stat) {
    // FIXME how to verify stat?
    integer rank = (integer)GET_VAL("STATISTIC",stat);
    if ( rank < GET_MYRIAD("MINSTAT") ) { rank = GET_MYRIAD("MINSTAT"); SET_STATISTIC(stat,rank); }
    if ( rank > GET_MYRIAD("MAXSTAT") ) { rank = GET_MYRIAD("MAXSTAT"); SET_STATISTIC(stat,rank); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"STATISTIC|"+stat+"="+(string)rank,llGetOwner()); // REMOVEME
    return rank;
}
 
// LMIN: SET_STATISTIC|<name>=<rank>
// PPMA: STATISTIC,<name>,<rank>
// LMOUT: SET_STATISTIC|<name>,<rank>
// LMERR: FIXME
SET_STATISTIC(string stat,integer rank) {
    // FIXME how to verify stat names are valid?
    if ( rank < GET_MYRIAD("MINSTAT") ) rank = GET_MYRIAD("MINSTAT");
    if ( rank > GET_MYRIAD("MAXSTAT") ) rank = GET_MYRIAD("MAXSTAT");
    SET_VAL("STATISTIC",stat,(string)rank);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_STATISTIC|"+stat+"="+(string)rank,llGetOwner()); // REMOVEME
}
 
// SKILLS
// LMIN: GET_SKILL|<name>
// PPMA: SKILL,<name>,<rank>
// LMOUT: SKILL|<name>=<rank>
// LMERR: FIXME
integer GET_SKILL(string skill) {
    integer rank = (integer)GET_VAL("SKILL",skill);
    if ( rank < GET_MYRIAD("MINSKILL") ) { rank = GET_MYRIAD("MINSKILL"); SET_SKILL(skill,rank); }
    if ( rank > GET_MYRIAD("MAXSKILL") ) { rank = GET_MYRIAD("MAXSKILL"); SET_SKILL(skill,rank); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SKILL|"+skill+"="+(string)rank,llGetOwner()); // REMOVEME
    return rank;
}
 
// LMIN: SET_SKILL|<name>=<rank>
// PPMA: SKILL,<name>,<rank>
// LMOUT: SET_SKILL|<name>=<rank>
// LMERR: FIXME
SET_SKILL(string skill,integer rank) {
    // TODO how to verify skill names are valid?
    if ( rank < GET_MYRIAD("MINSKILL") ) rank = GET_MYRIAD("MINSKILL");
    if ( rank > GET_MYRIAD("MAXSKILL") ) rank = GET_MYRIAD("MAXSKILL");
    SET_VAL("SKILL",skill,(string)rank);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SKILL|"+skill+"="+(string)rank,llGetOwner()); // REMOVEME
}
 
// SFX
// LMIN: GET_EFFECT|<name>
// PPMA: EFFECT,<name>,<rank>
// LMOUT: EFFECT|<name>=<rank>
// LMERR: FIXME
integer GET_EFFECT(string name) {
    integer rank = (integer)GET_VAL("EFFECT",name);
    if ( rank < GET_MYRIAD("MINEFFECT") ) { rank = GET_MYRIAD("MINEFFECT"); SET_EFFECT(name,rank); }
    if ( rank > GET_MYRIAD("MAXEFFECT") ) { rank = GET_MYRIAD("MAXEFFECT"); SET_EFFECT(name,rank); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"EFFECT|"+name+"="+(string)rank,llGetOwner()); // REMOVEME    
    return rank;
}
 
// LMIN: SET_EFFECT|<name>=<rank>
// PPMA: EFFECT,<name>,<rank>
// LMOUT: SET_EFFECT|<name>=<rank>
// LMERR: FIXME
SET_EFFECT(string name,integer rank) {
    // TODO how to verify effect name?
    if ( rank < GET_MYRIAD("MINEFFECT") ) rank = GET_MYRIAD("MINEFFECT");
    if ( rank > GET_MYRIAD("MAXEFFECT") ) rank = GET_MYRIAD("MAXEFFECT");
    SET_VAL("EFFECT",name,(string)rank);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_EFFECT|"+name+"="+(string)rank,llGetOwner()); // REMOVEME
}
 
// RESILIENCES
// LMIN: GET_RESILIENCE|<name>
// PPMA: RESILIENCE,<name>,<amount>
// LMOUT: RESILIENCE|<name>=<amount>
// LMERR: FIXME
integer GET_RESILIENCE(string name) {
    integer amount = (integer)GET_VAL("RESILIENCE",name);
    if ( amount < GET_MYRIAD("MINRESILIENCE") ) { amount = GET_MYRIAD("MINRESILIENCE"); SET_RESILIENCE(name,amount); }
    if ( amount > GET_MYRIAD("MAXRESILIENCE") ) { amount = GET_MYRIAD("MAXRESILIENCE"); SET_RESILIENCE(name,amount); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"RESILIENCE|"+name+"="+(string)amount,llGetOwner()); // REMOVEME
    return amount;
}
 
// LMIN: SET_RESILIENCE|<name>=<amount>
// PPMA: RESILIENCE,<name>,<amount>
// LMOUT: SET_RESILIENCE|<name>=<amount>
// LMERR: FIXME
SET_RESILIENCE(string name,integer amount) {
    // TODO how to verify resilience names are valid?
    if ( amount < GET_MYRIAD("MINRESILIENCE") ) amount = GET_MYRIAD("MINRESILIENCE");
    if ( amount > GET_MYRIAD("MAXRESILIENCE") ) amount = GET_MYRIAD("MAXRESILIENCE");
    SET_VAL("RESILIENCE",name,(string)amount);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_RESILIENCE|"+name+"="+(string)amount,llGetOwner()); // REMOVEME
}
 
// BOONS
// LMIN: GET_BOON|<name>
// PPMA: BOON,<name>,<rank>
// LMOUT: BOON|<name>=<rank>
// LMERR: FIXME
integer GET_BOON(string boon) {
    integer rank = (integer)GET_VAL("BOON",boon);
    if ( rank < GET_MYRIAD("MINBOON") ) { rank = GET_MYRIAD("MINBOON"); SET_BOON(boon,rank); }
    if ( rank > GET_MYRIAD("MAXBOON") ) { rank = GET_MYRIAD("MAXBOON"); SET_BOON(boon,rank); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"BOON|"+boon+"="+(string)rank,llGetOwner()); // REMOVEME
    return rank;
}
 
// LMIN: SET_BOON|<name>=<rank>
// PPMA: BOON,<name>,<rank>
// LMOUT: SET_BOON|<name>=<rank>
// LMERR: FIXME
SET_BOON(string boon,integer rank) {
    // TODO how to verify boon names are valid?
    if ( rank < GET_MYRIAD("MINBOON") ) rank = GET_MYRIAD("MINBOON");
    if ( rank > GET_MYRIAD("MAXBOON") ) rank = GET_MYRIAD("MAXBOON");
    SET_VAL("BOON",boon,(string)rank);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_BOON|"+boon+"="+(string)rank,llGetOwner()); // REMOVEME
}
 
// FLAWS
// LMIN: GET_FLAW|<name>
// PPMA: FLAW,<name>,<rank>
// LMOUT: FLAW|<name>=<rank>
// LMERR: FIXME
integer GET_FLAW(string flaw) {
    integer rank = (integer)GET_VAL("FLAW",flaw);
    if ( rank < GET_MYRIAD("MINFLAW") ) { rank = GET_MYRIAD("MINFLAW"); SET_FLAW(flaw,rank); }
    if ( rank > GET_MYRIAD("MAXFLAW") ) { rank = GET_MYRIAD("MAXFLAW"); SET_FLAW(flaw,rank); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"FLAW|"+flaw+"="+(string)rank,llGetOwner()); // REMOVEME
    return rank;
}
 
// LMIN: SET_FLAW|<name>=<rank>
// PPMA: FLAW,<name>,<rank>
// LMOUT: SET_FLAW|<name>=<rank>
// LMERR: FIXME
SET_FLAW(string flaw,integer rank) {
    // TODO how to verify flaw names are valid?
    if ( rank < GET_MYRIAD("MINFLAW") ) rank = GET_MYRIAD("MINFLAW");
    if ( rank > GET_MYRIAD("MAXFLAW") ) rank = GET_MYRIAD("MAXFLAW");        
    SET_VAL("FLAW",flaw,(string)rank);        
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_FLAW|"+flaw+"="+(string)rank,llGetOwner());
}
 
// SPECIES
// LMIN: GET_SPECIE|<name>
// PPMA: SPECIE,<name>,<data>
// LMOUT: SPECIE|<name>=<data>
// LMERR: FIXME
string GET_SPECIE(string name) {
    // FIXME how to verify requested species name?
    string speciedata = GET_VAL("SPECIE",name);
    if ( speciedata == KEY_NOT_FOUND ) speciedata = "None";
    // FIXME how to verify species data
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SPECIE|"+name+"="+speciedata,llGetOwner()); // REMOVEME
    return speciedata;
}
 
// FIXME LIST_SPECIES
//string LIST_SPECIES() {
//    return "";
//}
 
// LMIN: SET_SPECIE|<name>=<data>
// PPMA: SPECIE,<name>,<data>
// LMOUT: SET_SPECIE|<name>=<data>
// LMERR: FIXME
SET_SPECIE(string aspecies,string data) {
    // FIXME how to verify a good species name?
    // FIXME how to verify data
    SET_VAL("SPECIE",aspecies,data);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SPECIE|"+aspecies+"="+data,llGetOwner()); // REMOVEME
}
 
// BACKGROUND
// LMIN: GET_BACKGROUND|<name>=<data>
// PPMA: BACKGROUND,<name>,<data>
// LMOUT: BACKGROUND|<name>=<data>
// LMERR: FIXME
string GET_BACKGROUND(string name) {
    string bkgnddata = GET_VAL("BACKGROUND",name);
    if ( bkgnddata == KEY_NOT_FOUND ) bkgnddata = "None";
    // FIXME how to verify background data?
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"BACKGROUND|"+name+"="+bkgnddata,llGetOwner());
    return bkgnddata;
}
 
// FIXME LIST_BACKGROUNDS
//string LIST_BACKGROUNDS() {
//    return "";
//}
 
// LMIN: SET_BACKGROUND|<name>=<data>
// PPMA: BACKGROUND,<name>,<data>
// LMOUT: SET_BACKGROUND|<name>,<data>
// LMERR: FIXME
SET_BACKGROUND(string abkgnd,string data) {
    // FIXME how to verify background?
    SET_VAL("BACKGROUND",abkgnd,data);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_BACKGROUND|"+abkgnd+"="+data,llGetOwner()); // REMOVEME
}
 
// CAREER
// LMIN: GET_CAREER|<name>
// PPMA: CAREER,<name>,<data>
// LMOUT: CAREER|<name>=<data>
// LMERR: FIXME
string GET_CAREER(string name) {
    string careerdata = GET_VAL("CAREER",name);
    if ( careerdata == KEY_NOT_FOUND ) careerdata = "None";
    // FIXME how to verify career?
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CAREER|"+name+"="+careerdata,llGetOwner());
    return careerdata;
}
 
// FIXME LIST_CAREERS
//string LIST_CAREERS() {
//    return "";
//}
 
// LMIN: SET_CAREER|<name>=<data>
// PPMA: CAREER,<name>,<data>
// LMOUT: SET_CAREER|<name>=<data>
// LMERR: FIXME
SET_CAREER(string acareer,string data) {
    // FIXME how to verify name
    // FIXME how to verify data
    SET_VAL("CAREER",acareer,data);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CAREER|"+acareer+"="+data,llGetOwner());
}
 
// EQUIPMENT
// LMIN: GET_ITEM|<name>
// PPMA: ITEM,<name>,<amount>
// LMOUT: ITEM|<name>=<amount>
// LMERR: FIXME
integer GET_ITEM(string name) {
    // FIXME verify equipment name is valid?
    integer amt = (integer)GET_VAL("ITEM",name);
    if ( amt < GET_MYRIAD("MINITEM") ) { amt = GET_MYRIAD("MINITEM"); SET_ITEM(name,amt); }
    if ( amt > GET_MYRIAD("MAXITEM") ) { amt = GET_MYRIAD("MAXITEM"); SET_ITEM(name,amt); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"ITEM|"+name+"="+(string)amt,llGetOwner());        
    return amt;
}
 
// LMIN: SET_ITEM|<name>=<amount>
// PPMA: ITEM,<name>,<amount>
// LMOUT: SET_ITEM|<name>=<amount>
// LMERR: FIXME
SET_ITEM(string name,integer amt) {
    // TODO how to verify the equipment name is valid?
    if ( amt < GET_MYRIAD("MINITEM") ) amt = GET_MYRIAD("MINITEM");
    if ( amt > GET_MYRIAD("MAXITEM") ) amt = GET_MYRIAD("MAXITEM");
    SET_VAL("ITEM",name,(string)amt);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_ITEM|"+name+"="+(string)amt,llGetOwner());        
}
 
///////////
/// Player Only aspects
///////////
 
// NAME
// LMIN: GET_NAME
// PPMA: CHARACTER,NAME,<firstname> <lastname>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|NAME=<firstname> <lastname>
// RSERR: Undefined
// RETURN: NONE
GET_NAME(key id) {
    string name = GET_VAL("CHARACTER","NAME");
    if ( name == KEY_NOT_FOUND ) {
        OWNERSAY("Name not found. Resetting to default name Myriad Player");
        name = "Myriad Player"; // set a default name if none is in the database
        SET_NAME(name);
    }
    string out = "CHARACTER|NAME="+name;
    if ( id != NULL_KEY ) {
        integer chan = (integer)("0x"+llGetSubString(id,0,6));
        llRegionSay(chan,out);
    }
    // FIXME how to verify good name?
    //llMessageLinked(LINK_THIS,MODULE_CHARSHEET,out,llGetOwner()); // REMOVEME
    //return name;
}
 
// LMIN: SET_NAME|NAME=<firstname> <lastname>
// PPMA: CHARACTER,NAME,<firstname> <lastname>
// LMOUT: SET_CHARACTER|NAME=<firstname> <lastname>
// LMERR: Undefined
SET_NAME(string aname) {
    if ( aname == "" ) { // set NAME empty = delete
        SET_VAL("CHARACTER","NAME","Myriad Player");
        OWNERSAY("Name deleted. Resetting name to Myriad Player.");
        return;
    }
    // FIXME how to verify a good name?
    SET_VAL("CHARACTER","NAME",aname);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|NAME="+aname,llGetOwner()); // REMOVEME
}
 
// NICKNAME
// LMIN: GET_NICKNAME
// PPMA: CHARACTER,NICKNAME,<nickname>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|NICKNAME=<nickname>
// RSERR: Undefined
// RETURN: NONE
GET_NICKNAME(key id) {
    string nickname = GET_VAL("CHARACTER","NICKNAME");
    string out = "CHARACTER|NICKNAME="+nickname;
    if ( id != NULL_KEY ) {
        integer chan = (integer)("0x"+llGetSubString(id,0,6));
        llRegionSay(chan,out);
    }
    // FIXME how to verify good name?
    //llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|NICKNAME="+nickname,llGetOwner()); // REMOVEME
    //return nickname;
}
 
// LMIN: SET_CHARACTER|NICKNAME=<nickname>
// PPMA: CHARACTER,NICKNAME,<nickname>
// LMOUT: SET_CHARACTER|NICKNAME=<nickname>
// LMERR: Undefined
SET_NICKNAME(string anick) {
    if ( anick == "" ) { // set NICK empty = delete
        DELETE_RECORD("CHARACTER","NICKNAME");
        OWNERSAY("Nickname deleted");
        return;
    }
    // FIXME how to verify a good name?
    SET_VAL("CHARACTER","NICKNAME",anick);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|NICKNAME="+anick,llGetOwner()); // REMOVEME
}
 
// TITLE
// LMIN: GET_TITLE
// PPMA: CHARACTER,TITLE,<title>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|TITLE=<title>
// RSERR: Undefined
// RETURN: NONE
GET_TITLE(key id) {
    string title = GET_VAL("CHARACTER","TITLE");
    string out = "CHARACTER|TITLE="+title;
    if ( id != NULL_KEY ) {
        integer chan = (integer)("0x"+llGetSubString(id,0,6));
        llRegionSay(chan,out);
    }
    // FIXME how to verify good name?
    //llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|TITLE="+title,llGetOwner()); // REMOVEME
    //return title;
}
 
// LMIN: SET_TITLE|TITLE=<title>
// PPMA: CHARACTER,TITLE,<title>
// LMOUT: SET_CHARACTER|TITLE=<title>
// LMERR: Undefined
SET_TITLE(string atitle) {
    if ( atitle == "" ) { // set TITLE empty = delete
        DELETE_RECORD("CHARACTER","TITLE");
        OWNERSAY("Title deleted");
        return;
    }
    // FIXME how to verify a good name?
    SET_VAL("CHARACTER","TITLE",atitle);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|TITLE="+atitle,llGetOwner()); // REMOVEME
}
 
// FACTION
// LMIN: GET_FACTION
// PPMA: CHARACTER,FACTION,<factionname>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|FACTION=<factionname>
// RSERR: Undefined
// RETURN: NONE
GET_FACTION(key id) {
    string faction = GET_VAL("CHARACTER","FACTION");
    // FIXME how to verify good faction name?
    string out = "CHARACTER|FACTION="+faction;
    if ( id != NULL_KEY ) {
        integer chan = (integer)("0x"+llGetSubString(id,0,6));
        llRegionSay(chan,out);
    }
    //llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|FACTION="+faction,llGetOwner()); // REMOVEME
    //return faction;
}
 
// LMIN: SET_FACTION|FACTION=<factionname>
// PPMA: CHARACTER,FACTION,<factionname>
// LMOUT: SET_CHARACTER|FACTION=<factionname>
// LMERR: Undefined
SET_FACTION(string afaction) {
    if ( afaction == "" ) { // set FACTION empty = delete
        DELETE_RECORD("CHARACTER","FACTION");
        OWNERSAY("Faction deleted");
        return;
    }
    // FIXME how to verify a good name?
    SET_VAL("CHARACTER","FACTION",afaction);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|FACTION="+afaction,llGetOwner()); // REMOVEME
}
 
 
// RP - GENERATOR POINTBUY
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
integer GET_RP() {
    integer rp = (integer)GET_VAL("CHARACTER","RP");
    if ( rp < GET_MYRIAD("MINRP") ) { rp = GET_MYRIAD("MINRP"); SET_RP(rp); }
    if ( rp > GET_MYRIAD("MAXRP") ) { rp = GET_MYRIAD("MAXRP"); SET_RP(rp); }
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"PROGRESS|RP="+(string)rp,llGetOwner()); // REMOVEME
    return rp;
}
 
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
SET_RP(integer rpamt) {
    if ( rpamt < GET_MYRIAD("MINRP") ) rpamt = GET_MYRIAD("MINRP");
    if ( rpamt > GET_MYRIAD("MAXRP") ) rpamt = GET_MYRIAD("MAXRP");
    SET_VAL("CHARACTER","RP",(string)rpamt);    
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"PROGRESS|RP="+(string)rpamt,llGetOwner()); // REMOVEME    
}
 
// STUNTS
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
string GET_STUNT(string name) {
    string stunt = GET_VAL("STUNT",name);
    // TODO how to verify stunt?
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"STUNT|"+name+"="+stunt,llGetOwner()); // REMOVEME
    return stunt;
}
 
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
SET_STUNT(string name,string stunt) {
    // TODO how to verify stunt?
    SET_VAL("STUNT",name,stunt);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_STUNT|"+name+"="+stunt,llGetOwner());
}
 
// QUOTES
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
string GET_QUOTE(string name) {
    string quote = GET_VAL("QUOTE",name);
    // TODO how to verify quote?
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"QUOTE|"+name+"="+quote,llGetOwner()); // REMOVEME
    return quote;
}
 
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
SET_QUOTE(string name,string quote) {
    // TODO how to verify quote?
    SET_VAL("QUOTE",name,quote);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_QUOTE|"+name+"="+quote,llGetOwner());
}
 
 
DEBUG(string debugmsg) {
    if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"DEBUG|"+debugmsg,llGetOwner());
}
 
 
DUMP_SHEET(key id) { // dump current character sheet
    key objectowner = llList2Key(llGetObjectDetails(id,[OBJECT_OWNER]),0);
    key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
    if ( objectowner != regionowner ) {
        ERROR("DUMP_SHEET called by object or player who is not region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
        return;
    }
    DEBUG("Dumping character sheet to "+llKey2Name(id)+" ("+(string)id+")");
    integer chan = (integer)("0x"+llGetSubString((string)id,0,6));
    DUMP_TYPE(chan,"CHARACTER"); // FIXME
    DUMP_TYPE(chan,"STATISTIC"); // dump list of stats and values
    DUMP_TYPE(chan,"RESILIENCE"); // dump list of resiliences and values
    DUMP_TYPE(chan,"BOON"); // dump list of boons and values
    DUMP_TYPE(chan,"FLAW"); // dump list of flaws and values 
    DUMP_TYPE(chan,"EFFECT"); // dump list of mortal sfx and values
    DUMP_TYPE(chan,"SKILL"); // dump list of skills and values
    DUMP_TYPE(chan,"ITEM"); // dump list of equipment
    DUMP_TYPE(chan,"STUNT"); // dump list of stunts
    DUMP_TYPE(chan,"QUOTE"); // dump list of quotes
    llSay(chan,"CHARACTER_LOADED");   
}
 
ERASE_SHEET(key id) { // erase current character sheet
    key objectowner = llList2Key(llGetObjectDetails(id,[OBJECT_OWNER]),0);
    key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
    if ( objectowner != regionowner || objectowner != llGetOwner() || id != llGetOwner()  ) {
        ERROR("ERASE_SHEET called by object or player who is not player or region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
        return;
    }
    DEBUG("Eraseing character sheet");
    ERASE_TYPE("CHARACTER"); // FIXME
    ERASE_TYPE("STATISTIC"); // dump list of stats and values
    ERASE_TYPE("RESILIENCE"); // dump list of resiliences and values
    ERASE_TYPE("BOON"); // dump list of boons and values
    ERASE_TYPE("FLAW"); // dump list of flaws and values 
    ERASE_TYPE("EFFECT"); // dump list of mortal sfx and values
    ERASE_TYPE("SKILL"); // dump list of skills and values
    ERASE_TYPE("ITEM"); // dump list of equipment
    ERASE_TYPE("STUNT"); // dump list of stunts
    ERASE_TYPE("QUOTE"); // dump list of quotes
    OWNERSAY("Character Sheet erased.");   
}
 
ERROR(string errmsg) {
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"ERROR|"+errmsg,llGetOwner());
}
 
// MEMORY
GET_MEMORY() {
    OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory());
}
 
// GETVERSION
GET_VERSION() {
    OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE);
}
 
OWNERSAY(string msg) {
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"OWNERSAY|"+msg,llGetOwner());
}
 
PARSE(string message,key id) {
    // debugged in chat and link message events
    // 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);
    // REMOVEME all GET_* command blocks once PPMA is integrated in all HUD scripts    
    if ( cmd == "memory" ) { GET_MEMORY(); return;} // dump script memory
    if ( cmd == "reset" ) { RESET(); } // reset on request
    if ( cmd == "version") { GET_VERSION(); return; } // respond with version info when requested
    if ( cmd == "get_status" ) { GET_STATUS(id); return; } // respond to character sheet status requests
    if ( cmd == "set_status" ) { SET_STATUS(sdata); return; } // save character status
    if ( cmd == "dump_sheet" ) { DUMP_SHEET(id); return; } // id is UUID of character builder, sent over from HUD script
    if ( cmd == "dump_records") { DUMP_RECORDS(); return; } // dump prim persistent memory array
    if ( cmd == "erase_sheet" ) { ERASE_SHEET(id); return;} // erase character sheet
    if ( cmd == "format_db" ) { FORMAT_DB(); return;}
    if ( cmd == "region_setting" && llToLower(attrib) == "progression" ) { SET_PROGRESSION(sdata); return;}
    // ASPECTS
    if ( cmd == "get_statistic" ) { GET_STATISTIC(attrib); return;}
    if ( cmd == "set_statistic" ) { SET_STATISTIC(attrib,idata); return;}
    if ( cmd == "get_skill" ) { GET_SKILL(attrib); return;}
    if ( cmd == "set_skill" ) { SET_SKILL(attrib,idata); return;}
    if ( cmd == "get_effect" ) { GET_EFFECT(attrib); return;}
    if ( cmd == "set_effect" ) { SET_EFFECT(attrib,idata); return;}
    if ( cmd == "get_resilience" ) { GET_RESILIENCE(attrib); return;}
    if ( cmd == "set_resilience" ) { SET_RESILIENCE(attrib,idata); return;}
    if ( cmd == "get_boon" ) { GET_BOON(attrib); return;}
    if ( cmd == "set_boon" ) { SET_BOON(attrib,idata); return;}
    if ( cmd == "get_flaw" ) { GET_FLAW(attrib); return;}
    if ( cmd == "set_flaw" ) { SET_FLAW(attrib,idata); return;}
    if ( cmd == "get_specie" ) { GET_SPECIE(attrib); return;}
    if ( cmd == "set_specie" ) { SET_SPECIE(attrib,sdata); return;}
    if ( cmd == "get_background" ) { GET_BACKGROUND(attrib); return;}
    if ( cmd == "set_background" ) { SET_BACKGROUND(attrib,sdata); return;}        
    if ( cmd == "get_career" ) { GET_CAREER(attrib); return;}
    if ( cmd == "set_career" ) { SET_CAREER(attrib,sdata); return;}
    if ( cmd == "get_item" ) { GET_ITEM(attrib); return;}
    if ( cmd == "set_item" ) { SET_ITEM(attrib,idata); return;}
 
    if ( cmd == "get_name" ) { GET_NAME(id); return;}
    if ( cmd == "set_name" ) { SET_NAME(sdata); return;}
    if ( cmd == "get_nickname" ) { GET_NICKNAME(id); return;}
    if ( cmd == "set_nickname" ) { SET_NICKNAME(sdata); return;}
    if ( cmd == "get_title" ) { GET_TITLE(id); return;}
    if ( cmd == "set_title" ) { SET_TITLE(sdata); return;}
    if ( cmd == "get_faction") { GET_FACTION(id); return;}
    if ( cmd == "set_faction" ) { SET_FACTION(sdata); return;}
    if ( cmd == "get_stunt" ) { GET_STUNT(sdata); return;}
    if ( cmd == "set_stunt" ) { SET_STUNT(attrib,sdata); return;}
    if ( cmd == "get_quote" ) { GET_QUOTE(sdata); return;}
    if ( cmd == "set_quote" ) { SET_QUOTE(attrib,sdata); return;}
    if ( cmd == "get_rp" ) { GET_RP(); return;}        
    if ( cmd == "set_rp" ) { SET_RP(idata); return;}        
 
    // LIST FUNCTIONS
    if ( cmd == "list_statistics" ) { LIST_TYPE(id,"STATISTIC"); return;}
    if ( cmd == "list_skills" ) { LIST_TYPE(id,"SKILL"); return;}
    if ( cmd == "list_effects" ) { LIST_TYPE(id,"EFFECT"); return;}
    if ( cmd == "list_resiliences" ) { LIST_TYPE(id,"RESILIENCE"); return;}
    if ( cmd == "list_boons" ) { LIST_TYPE(id,"BOON"); return;}
    if ( cmd == "list_flaws" ) { LIST_TYPE(id,"FLAW"); return;}
    if ( cmd == "list_species" ) { LIST_TYPE(id,"SPECIE"); return;}
    if ( cmd == "list_backgrounds" ) { LIST_TYPE(id,"BACKGROUND"); return;}
    if ( cmd == "list_careers" ) { LIST_TYPE(id,"CAREER"); return;}
    if ( cmd == "list_items" ) { LIST_TYPE(id,"ITEM"); return;}
}
 
RESET() { // do any final work, then reset
    llResetScript();
}
 
string PROGRESSION; // Progression Method: LEVEL, GRADUAL, or RANDOM
 
GET_PROGRESSION() {
    integer CHANMYRIAD = -999; // REMOVEME
    llRegionSay(CHANMYRIAD,"GET_REGION_SETTING|PROGRESSION"); // ask for this first, asking for estate triggers find_notecard function    
}
 
SET_PROGRESSION(string method) {
    method = llToUpper(method);
    if ( method == "LEVEL-BASED" || 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.");
    }
}
 
GET_STATUS(key id) {
    DEBUG("Sending character sheet status to "+llKey2Name(id)+" ("+(string)id+")");
    integer chan = (integer)("0x"+llGetSubString((string)id,0,6));
    string status = GET_VAL("CHARACTER","STATUS");
    if ( status != "NONE" && status != "DRAFT" && status != "COMPLETE" ) {
        status = "NONE";
    }
    llSay(chan,"CHARACTER|STATUS="+status);
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|STATUS="+status,llGetOwner());
}
 
SET_STATUS(string status) {
    if ( status == "NONE" || status == "DRAFT" || status == "COMPLETE" ) {
        SET_VAL("CHARACTER","STATUS",status);
    } else {
        ERROR("SET_STATUS: Invalid character status: "+status);
    }
}
 
SETUP() {
 
    GET_PROGRESSION();
    OWNERSAY("Character Sheet module active.");
}
 
default {
 
    link_message(integer sender_num,integer num,string str,key id) {
        if ( num == MODULE_CHARSHEET || num == LM_SENDTOATTACHMENT ) return; // ignore link messages not sent to us specifically
        list tokens = llParseString2List(str,["|"],[]);
        string cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
        if ( cmd == "debug" || cmd == "error" || cmd == "help" || cmd == "ownersay" || cmd == "rpevent" ) return; // ignore WELL commands
        // debug after message we're ignoring
        DEBUG("EVENT link_message sender_num=["+(string)sender_num+"] num=["+(string)num+"] str=["+str+"] id=["+(string)id+"]");        
        PARSE(str,id); // parse incoming message
    }
 
    state_entry() {
        SETUP();
    }
}
// END