User:Allen Kerensky/Myriad Lite/Myriad Lite Module Character Sheet-v0.0.4-20120826.lsl

From OpenSimulator

Jump to: navigation, search

Myriad_Lite_Module_Character_Sheet-v0.0.4-20120826.lsl

// Myriad_Lite_Module_Character_Sheet-Preview6.lsl
// Copyright (c) 2012 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 VERSION = "0.0.4"; // Allen Kerensky's script version
string VERSIONDATE = "20120826"; // Allen Kerensky's script yyyymmdd
 
// CHARACTER SHEET STORAGE
string CARDVERSION = "0.0.5"; // what card format version do we expect
string NAME = ""; // character name
string SPECIES = ""; // species template used for character
string BACKGROUND = ""; // background template
string CAREER = ""; // career template
 
list STATISTICS = [];
list RESILIENCES = [];
list CURRENT_RESILIENCES = [];
list RES_STATS = [];
list RES_TYPES = [];
list BOONS = []; // boons [ string BoonName, integer BoonRank ]
list FLAWS = []; // flaws [ string FlawName, integer FlawRank ]
list SKILLS = []; // skills [ string SkillName, integer SkillRank ]
list MORTAL_EFFECTS = []; // special effects (SFX) [ string EffectName, integer EffectRank ]
list SOCIAL_EFFECTS = []; // special effects (SFX) [ string EffectName, integer EffectRank ]
list MAGIC_EFFECTS = []; // special effects (SFX) [ string EffectName, integer EffectRank ]
list VEHICLE_EFFECTS = []; // special effects (SFX) [ string EffectName, integer EffectRank ]
list STUNTS = []; // pre-set martial combat stunts TODO how will this work?
list QUOTES = []; // pre-set social combat quotes TODO how will this work?
list EQUIPMENT = []; // Equipment [ string ItemName, integer NumberCarried ]
 
// Level-Based Progress
integer XP = 0; // 0-2320
integer XPLEVEL = 1; // 1-30
integer GP = 0; // general points for point-based character builder
integer STATPOOL = 0; // statistics point pool
integer HEALTHPOOL = 0; // resilience point pool
integer SKILLPOOL = 0; // skill point pool
integer SFXPOOL = 0; // special effect ability point pool
integer RP = 0; // resource point pool
 
// Gradual Progress
integer XPLEFT = 0; // unspent XP
 
// Random Progress
integer SKILL_INCREASES; // how many times have we increased skills, when 5 skills, get new skill
list SKILLS_INCREASED = []; // [ SkillName, TimesFailed ] if TimesFailed = 5, increase skill and reset
integer NEW_SKILLS; // number of new skills earned through RANDOM skill increases since last reset
list STATS_INCREASED = []; // [ StatName, Times Advanced ] if TimesAdvanced = 5, increase stat and reset
integer SIXES_BURNED; // number of sixes burned > ( 10 * #SFX you have ) then gain new SFX
 
// MYRIAD CONSTANTS
integer MINXP = 0; // min experience points
integer MAXXP = 2320; // max experience points
integer MIN_XPLEVEL = 1; // min XP level
integer MAX_XPLEVEL = 30; // max XP level
list 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 ];
integer MINSTAT = 1; // min value for statistics
integer MAXSTAT = 5; // max human value for a statistic/attribute
integer MINRESILIENCE = 1; // min value for resilience
integer MAXRESILIENCE = 20; // max value for resilience
integer MINBOON = 1; // min value for boon rank
integer MAXBOON = 5; // max value for boon rank
integer MINFLAW = 1; // min value for flaw rank
integer MAXFLAW = 5; // max value for flaw rank
integer MINSKILL = 1; // min value for skill rank
integer MAXSKILL = 5; // max value for skill rank
integer MINEFFECT = 1; // min value for special effect
integer MAXEFFECT = 5; // max value for special effect
integer MINEQUIPPED = 1; // min number of items player can carry
integer MAXEQUIPPED = 100; // max number of items player can carry TODO: what about bullets?
//integer MINDAMAGE = 1; // min attack dice for weapon
//integer MAXDAMAGE = 5; // max attack dice for weapon
 
// MODULE TO MODULE MESSAGING CONSTANTS
//integer MODULE_HUD = -1;
integer MODULE_CHARSHEET = -2;
//integer MODULE_ARMOR = -3;
//integer MODULE_BAM = -4;
//integer MODULE_RUMORS = -5;
// integer MODULE_CLOSE = -6;
// integer MODULE_RANGED = -7;
// integer MODULE_RESILIENCE = -8;
// integer MODULE_PROGRESS = -9;
// integer MODULE_WELL = -10;
// integer MODULE_METER = -11;
integer LM_SENDTOATTACHMENT = 0x80000000;
 
// Runtimes
integer FLAG_DEBUG;
key PLAYERID;
string DIV="|";
string ESTATE; // what estate does this region belong to - loaded from region server
string PROGRESSION; // Progression Method: LEVEL, GRADUAL, or RANDOM
integer CHANMYRIAD = -999;
integer CHANOBJECT;
integer HANDOBJECT;
 
// Menu Handling
integer MENU_TIMER;
integer MENU_TIMEOUT = 30;
integer MENU_CHANNEL;
integer MENU_HANDLE; 
list MENU;
 
// NOTECARD HANDLING
list CHARACTERS;
string CARD;
string DEFAULT = "Myriad_Lite_Character_Sheet-Preview6.txt"; // character sheet notecard
integer LINE = 0; // reading line number
key QUERY = NULL_KEY; // track notecard queries
 
// ADD_XP - Add a point of XP
ADD_XP(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)XP+" XPLEVEL="+(string)XPLEVEL);
    if ( PROGRESSION == "RANDOM" ) {
        ERROR("Unable to add XP in region using Random Progression");
        return;
    }
    if ( XP < MAXXP ) {
        XP++; // add one to total XP
        if ( PROGRESSION == "LEVEL-BASED" ) {
            integer currentlevel = XPLEVEL;
            integer templevel = GET_LEVEL_BY_XP(XP);            
            if ( templevel > currentlevel ) {
                XPLEVEL = templevel;
                llOwnerSay("LEVEL UP! Congratulations, you are now XP Level "+(string)XPLEVEL);
                RPEVENT("LEVEL UP! Congratulations, "+llKey2Name(llGetOwner())+" is now XP Level "+(string)XPLEVEL);
                LEVELUP(XPLEVEL);
            }
            return;
        }
        if ( PROGRESSION == "GRADUAL" ) {
            XPLEFT++; // add one to XP you can spend in gradual progress mode
            return;
        }
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"XP_CHANGED|XP="+(string)XP,llGetOwner());
        DEBUG("Progression="+PROGRESSION+" XP="+(string)XP+" XPLEVEL="+(string)XPLEVEL);
    } else {
        ERROR("XP already maxed.");
    }
}
 
CALCULATE_LEVEL_BY_XP() {
    integer i;
    for ( i=1; i < llGetListLength(XP_BY_LEVEL); i++ ) {
        integer basexp = llList2Integer(XP_BY_LEVEL,i);
        if ( XP >= basexp ) {
            SET_XPLEVEL(i);
        }
    }
}
 
CHECK_CARDVERSION(string ncversion) {
    if ( ncversion != CARDVERSION ) {
        ERROR("Character sheet format "+ncversion+" found. Format version "+CARDVERSION+" expected. Please update character sheets to newer versions.");
    }
}
 
DEBUG(string debugmsg) {
    if ( FLAG_DEBUG == TRUE ) { // are we debugging?
        llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") MODULE CHARSHEET DEBUG: "+debugmsg);
    }
}
 
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;
    }
    if ( PROGRESSION == "LEVEL-BASED" ) {
        STATPOOL = 0;
        HEALTHPOOL = 0;
        SKILLPOOL = 0;
        SFXPOOL = 0;
        return;
    }
    if ( PROGRESSION == "GRADUAL" ) {
        XPLEFT = 0;
        return;
    }
    if ( PROGRESSION == "RANDOM" ) {
        SKILL_INCREASES = 0;
        NEW_SKILLS = 0;
        SKILLS_INCREASED = [];
        STATS_INCREASED = [];
        SIXES_BURNED = 0;
        return;
    }
}
 
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));
    string tempname = llGetObjectName();
    llSetObjectName("");
    llSay(chan,"VERSION=0.0.3");
    llSay(chan,"NAME|NAME="+NAME);
    // Species
    llSay(chan,"SPECIES|SPECIES="+SPECIES);
    // Backgrounds
    llSay(chan,"BACKGROUNDS|BACKGROUND="+BACKGROUND);    
    // Careers
    llSay(chan,"CAREERS|CAREER="+CAREER);
    //llSay(chan,"XP="+(string)XP);
    //llSay(chan,"XPLEVEL="+(string)XPLEVEL);
    // Statistics
    integer numstats = llGetListLength(STATISTICS);
    integer count;
    for ( count = 0; count < numstats; count += 2 ) {
        string name = llList2String(STATISTICS,count);
        integer amount = llList2Integer(STATISTICS,count + 1);
        if ( amount > 0 ) llSay(chan,"STATISTIC|"+name+"="+(string)amount);
    }
    // Resiliences
    numstats = llGetListLength(RESILIENCES);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(RESILIENCES,count++);
        integer amount = llList2Integer(RESILIENCES,count);
        string base = llList2String(RES_STATS,llListFindList(RES_STATS,[name]) + 1);
        integer baseamt;
            if ( llList2String(RES_TYPES,llListFindList(RES_TYPES,[name]) + 1 ) == "Critical" ) {
                baseamt = llCeil( llList2Float(STATISTICS,llListFindList(STATISTICS,[base]) + 1) / 2 );
            } else {
                baseamt = llList2Integer(STATISTICS,llListFindList(STATISTICS,[base]) + 1);
            }
        if ( ( amount ) > 0 ) llSay(chan,"RESILIENCE|"+name+"="+(string)(amount));
    }
    // Boons
    numstats = llGetListLength(BOONS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(BOONS,count++);
        integer amount = llList2Integer(BOONS,count);
        if ( amount > 0 ) llSay(chan,"BOON|"+name+"="+(string)amount);
    }
    // Flaws
    numstats = llGetListLength(FLAWS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(FLAWS,count++);
        integer amount = llList2Integer(FLAWS,count);
        if ( amount > 0 ) llSay(chan,"FLAW|"+name+"="+(string)amount);
    }
    // Skills
    numstats = llGetListLength(SKILLS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(SKILLS,count++);
        integer amount = llList2Integer(SKILLS,count);
        if ( amount > 0 ) llSay(chan,"SKILL|"+name+"="+(string)amount);
    }
    // Mortal Combat SFX    
    numstats = llGetListLength(MORTAL_EFFECTS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(MORTAL_EFFECTS,count++);
        integer amount = llList2Integer(MORTAL_EFFECTS,count);
        if ( amount > 0 ) llSay(chan,"MORTAL_EFFECT|"+name+"="+(string)amount);
    }
    // Social Combat SFX    
    numstats = llGetListLength(SOCIAL_EFFECTS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(SOCIAL_EFFECTS,count++);
        integer amount = llList2Integer(SOCIAL_EFFECTS,count);
        if ( amount > 0 ) llSay(chan,"SOCIAL_EFFECT|"+name+"="+(string)amount);
    }
    // Mortal Combat SFX    
    numstats = llGetListLength(MAGIC_EFFECTS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(MAGIC_EFFECTS,count++);
        integer amount = llList2Integer(MAGIC_EFFECTS,count);
        if ( amount > 0 ) llSay(chan,"MAGIC_EFFECT|"+name+"="+(string)amount);
    }
    // Mortal Combat SFX    
    numstats = llGetListLength(VEHICLE_EFFECTS);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(VEHICLE_EFFECTS,count++);
        integer amount = llList2Integer(VEHICLE_EFFECTS,count);
        if ( amount > 0 ) llSay(chan,"VEHICLE_EFFECT|"+name+"="+(string)amount);
    }
    // Stunts    
    llSay(chan,"STUNT|STUNT=FIXME");
    // Quotes
    llSay(chan,"QUOTE|QUOTE=FIXME");
    // Equipment
    numstats = llGetListLength(EQUIPMENT);
    for ( count = 0; count < numstats; count++) {
        string name = llList2String(EQUIPMENT,count++);
        integer amount = llList2Integer(EQUIPMENT,count);
        if ( amount > 0 ) llSay(chan,"EQUIPMENT|"+name+"="+(string)amount);
    }
    llSay(chan,"CHARACTER_LOADED");   
    llSetObjectName(tempname);     
}
 
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));
    if ( PROGRESSION == "LEVEL-BASED" ) {
        llSay(chan,"PROGRESS|XP="+(string)XP);
        llSay(chan,"PROGRESS|XPLEVEL="+(string)XPLEVEL);
        llSay(chan,"PROGRESS|STATPOOL="+(string)STATPOOL);
        llSay(chan,"PROGRESS|HEALTHPOOL="+(string)HEALTHPOOL);
        llSay(chan,"PROGRESS|SKILLPOOL="+(string)SKILLPOOL);
        llSay(chan,"PROGRESS|SFXPOOL="+(string)SFXPOOL);
        return;
    }
    if ( PROGRESSION == "GRADUAL" ) {
        llSay(chan,"PROGRESS|XP="+(string)XP);
        llSay(chan,"PROGRESS|XPLEFT="+(string)XPLEFT);
        return;
    }
    if ( PROGRESSION == "RANDOM" ) {
        llSay(chan,"PROGRESS|SKILL_INCREASES="+(string)SKILL_INCREASES);
        llSay(chan,"PROGRESS|NEW_SKILLS="+(string)NEW_SKILLS);
        llSay(chan,"PROGRESS|SIXES_BURNED="+(string)SIXES_BURNED);
        integer count = llGetListLength(SKILLS_INCREASED);
        integer i;
        for ( i=0; i< count; i++) {
            llSay(chan,"PROGRESS|SKILL="+llList2String(SKILLS_INCREASED,i)+"|TIMESFAILED="+llList2String(SKILLS_INCREASED,i + 1));
        }   
        count = llGetListLength(STATS_INCREASED);
        for ( i=0; i< count; i++) {
            llSay(chan,"PROGRESS|STAT="+llList2String(STATS_INCREASED,i)+"|TIMESADVANCERD="+llList2String(STATS_INCREASED,i + 1));
        }
        return;
    }
}
 
ERROR(string errmsg) {
    llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") MODULE CHARSHEET ERROR: "+errmsg);
}
 
FIND_NOTECARD() {
    CHARACTERS = [];
    MENU = ["Default"];
    //string regionname = llGetRegionName();
    integer count = llGetInventoryNumber(INVENTORY_NOTECARD);
    while (count--) {
        string currentcard = llGetInventoryName(INVENTORY_NOTECARD,count);
        list tokens = llParseString2List(currentcard,["@"],[]);
        string cardname = llList2String(tokens,0);
        string cardestate = llList2String(tokens,1);
        if ( cardname != DEFAULT && cardestate == ESTATE ) {
            CHARACTERS = [cardname,currentcard] + CHARACTERS;
            MENU = [cardname] + MENU;
            DEBUG("Found estate-specific character sheet "+cardname+"@"+cardestate);
        }
    }
    MENU_CHANNEL = (integer)llFrand(9999.0) * -1;
    MENU_HANDLE = llListen(MENU_CHANNEL,"",llGetOwner(),"");
    llDialog(llGetOwner(),"Choose your character",MENU,MENU_CHANNEL);
    MENU_TIMER = MENU_TIMEOUT;
    llSetTimerEvent(1.0);
}
 
integer GET_LEVEL_BY_XP(integer amount) {
    integer count = 0;
    integer outlevel = 0;
    for (count = 0; count < MAX_XPLEVEL; count++ ) {
        if ( amount > llList2Integer(XP_BY_LEVEL,count) ) outlevel = count;
    }
    DEBUG("GET_LEVEL_BY_XP("+(string)amount+") returning "+(string)outlevel);
    return outlevel;
}
 
integer GET_MAXRESILIENCE(string name) {
    integer pos = llListFindList(RESILIENCES,[name]);
    if ( pos >= 0 ) {
        return llList2Integer(RESILIENCES,pos + 1);
    }
    return 0;
}
 
integer GET_RESILIENCE(string name) {
    integer pos = llListFindList(CURRENT_RESILIENCES,[name]);
    if ( pos >= 0 ) {
        return llList2Integer(CURRENT_RESILIENCES,pos + 1);
    }
    return 0;
}
 
integer GET_STAT(string name) {
    integer pos = llListFindList(STATISTICS,[name]);
    if ( pos >= 0 ) {
        return llList2Integer(STATISTICS,pos + 1);
    }
    return 0;
}
 
GETVERSION() {
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"VERSION_INFO"+DIV+"NAME="+llGetScriptName()+DIV+"VERSION="+VERSION+DIV+"VERSIONDATE="+VERSIONDATE,llGetOwner());
}
 
integer GET_XP() {
    if ( XP >= MINXP && XP <= MAXXP) {
        // inform all other modules that might need to track XPLEVEL
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"GET_XP|XP="+(string)XP,llGetOwner());
        return XP;
    } else {
        ERROR("GET_XP("+(string)XP+") AMOUNT OUT OF RANGE "+(string)MINXP+"-"+(string)MAXXP+"! RESETTING");
    }
    RESET();
    return 0;
}
 
integer GET_XPLEVEL() {
    if ( XPLEVEL >= MIN_XPLEVEL && XPLEVEL <= MAX_XPLEVEL) {
        // inform all other modules that might need to track XPLEVEL
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"GET_XPLEVEL|XPLEVEL="+(string)XPLEVEL,llGetOwner());
        return XPLEVEL;
    } else {
        ERROR("GET_XPLEVEL ["+(string)XPLEVEL+"] AMOUNT OUT OF RANGE "+(string)MIN_XPLEVEL+"-"+(string)MAX_XPLEVEL+"! RESETTING");
    }
    RESET();
    return 0;
}
 
 
INCREASE_SKILL(string sname) {
    integer pos = llListFindList(SKILLS,[sname]);
    if ( pos >= 0 ) { // found skill in list
        integer curval = llList2Integer(SKILLS,pos + 1);
        if ( curval < MAXSKILL ) {
            SKILLS = llListReplaceList(SKILLS,[curval++],pos + 1,pos + 1);
            RPEVENT("increased their "+sname+" skill by one.");
        }
        llRegionSay(CHANMYRIAD,"GET_SKILL|"+sname);
    } else {
        ERROR("Requested Skill increase for nonexistent skill "+sname);
    }
}
 
INCREASE_STAT(string sname) {
    integer pos = llListFindList(STATISTICS,[sname]);
    if ( pos >= 0 ) { // found skill in list
        integer curval = llList2Integer(STATISTICS,pos + 1);
        if ( curval < MAXSTAT ) {
            STATISTICS = llListReplaceList(STATISTICS,[curval++],pos + 1,pos + 1);
            RPEVENT("increased their "+sname+" statistic by one.");
        }
    } else {
        ERROR("Requested stat increase for nonexistent stat "+sname);
    }
}
 
// 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.
    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
        SFXPOOL++; // 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.
    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
        STATPOOL++; // add a new stat point
    }
}
 
RESET() {
    // do any final work, then reset
    llResetScript();
}
 
RPEVENT(string text) {
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,text,llGetOwner());
}
 
SET_BACKGROUND(string abackground) {
    BACKGROUND = abackground;
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_BACKGROUND|BACKGROUND="+BACKGROUND,llGetOwner());
}
 
SET_BOON(string boonname,integer boonrank) {
    // TODO how to verify boon names are valid?
    if ( boonrank >= MINBOON && boonrank <= MAXBOON ) { // rank valid?
        BOONS = [boonname,boonrank] + BOONS; // add boon to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_BOON|"+boonname+"="+(string)boonrank,llGetOwner());
    } else { // invalid, report it
        ERROR("BOON "+boonname+" rank "+(string)boonrank+" value out of allowed range: "+(string)MINBOON+"-"+(string)MAXBOON);
    }
}
 
SET_CAREER(string acareer) {
    CAREER = acareer;
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CAREER|CAREER="+CAREER,llGetOwner());
}
 
SET_MORTAL_EFFECT(string effectname,integer effectrank) {
    // TODO how to verify effect name?
    if ( effectrank >= MINEFFECT && effectrank <= MAXEFFECT ) { // effect rank valid?
        MORTAL_EFFECTS = [effectname,effectrank] + MORTAL_EFFECTS; // add effect to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_MORTAL_EFFECT|"+effectname+"="+(string)effectrank,llGetOwner());
    } else { // invalid, report it
        ERROR("MORTAL EFFECT "+effectname+" rank "+(string)effectrank+" value out of allowed range: "+(string)MINEFFECT+"-"+(string)MAXEFFECT);
    }
}
 
SET_SOCIAL_EFFECT(string effectname,integer effectrank) {
    // TODO how to verify effect name?
    if ( effectrank >= MINEFFECT && effectrank <= MAXEFFECT ) { // effect rank valid?
        SOCIAL_EFFECTS = [effectname,effectrank] + SOCIAL_EFFECTS; // add effect to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SOCIAL_EFFECT|"+effectname+"="+(string)effectrank,llGetOwner());
    } else { // invalid, report it
        ERROR("SOCIAL EFFECT "+effectname+" rank "+(string)effectrank+" value out of allowed range: "+(string)MINEFFECT+"-"+(string)MAXEFFECT);
    }
}
 
SET_MAGIC_EFFECT(string effectname,integer effectrank) {
    // TODO how to verify effect name?
    if ( effectrank >= MINEFFECT && effectrank <= MAXEFFECT ) { // effect rank valid?
        MAGIC_EFFECTS = [effectname,effectrank] + MAGIC_EFFECTS; // add effect to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_MAGIC_EFFECT|"+effectname+"="+(string)effectrank,llGetOwner());
    } else { // invalid, report it
        ERROR("MAGIC EFFECT "+effectname+" rank "+(string)effectrank+" value out of allowed range: "+(string)MINEFFECT+"-"+(string)MAXEFFECT);
    }
}
 
SET_VEHICLE_EFFECT(string effectname,integer effectrank) {
    // TODO how to verify effect name?
    if ( effectrank >= MINEFFECT && effectrank <= MAXEFFECT ) { // effect rank valid?
        VEHICLE_EFFECTS = [effectname,effectrank] + VEHICLE_EFFECTS; // add effect to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_VEHICLE_EFFECT|"+effectname+"="+(string)effectrank,llGetOwner());
    } else { // invalid, report it
        ERROR("VEHICLE EFFECT "+effectname+" rank "+(string)effectrank+" value out of allowed range: "+(string)MINEFFECT+"-"+(string)MAXEFFECT);
    }
}
 
SET_EQUIPMENT(string equipmentname,integer equipmentamount) {
    // TODO how to verify the equipment name is valid?
    if ( equipmentamount >= MINEQUIPPED && equipmentamount <= MAXEQUIPPED ) { // amount valid?
        EQUIPMENT = [equipmentname,equipmentamount] + EQUIPMENT; // add equipment to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_EQUIPMENT|"+equipmentname+"="+(string)equipmentamount,llGetOwner());        
    } else { // invalid, report it
        ERROR("EQUIPMENT "+equipmentname+" amount "+(string)equipmentamount+" value out of allowed range: "+(string)MINEQUIPPED+"-"+(string)MAXEQUIPPED);
    }
}
 
SET_ESTATE(string anestate) {
    ESTATE = anestate;
    DEBUG("Estate: ["+ESTATE+"]");
}
 
SET_FLAW(string flawname,integer flawrank) {
    // TODO how to verify flaw names are valid?
    if ( flawrank >= MINFLAW && flawrank <= MAXFLAW ) { // rank valid?
        FLAWS = [flawname,flawrank] + FLAWS; // add flaw to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_FLAW|"+flawname+"="+(string)flawrank,llGetOwner());
    } else { // invalid, report it
        ERROR("FLAW "+flawname+" rank "+(string)flawrank+" value out of allowed range: "+(string)MINFLAW+"-"+(string)MAXFLAW);
    }
}
 
SET_GP(integer gpamt) {
    if ( gpamt >= 0 ) {
        GP = gpamt;
    } else {
        ERROR("SET_GP("+(string)gpamt+") REQUESTED AMOUNT OUT OF RANGE: LESS THAN ZERO");
    }
}
 
SET_HEALTHPOOL(integer healthamt) {
    if ( healthamt >= 0 ) {
        HEALTHPOOL = healthamt;
    } else {
        ERROR("SET_HEALTHPOOL("+(string)healthamt+") REQUESTED AMOUNT OUT OF RANGE: LESS THAN ZERO");
    }
}
 
SET_NAME(string aname) {
    NAME = aname;
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_NAME|NAME="+NAME,llGetOwner());
}
 
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.");
    }
}
 
SET_QUOTE(string quotename) {
    // TODO how to verify quote?
    QUOTES = [quotename] + QUOTES; // add quote to list
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_QUOTE|QUOTE="+quotename,llGetOwner());
}
 
SET_RESILIENCE(string resname,integer resrank) {
    // TODO how to verify resilience names are valid?
 
    if ( resrank < 0 || resrank > 20 ) {
        ERROR("Resilience rank "+(string)resrank+" out of allowed range "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE);
        return;
    }
 
    integer curpos = llListFindList(CURRENT_RESILIENCES,[resname]);
    if ( curpos >= 0 ) {
        CURRENT_RESILIENCES = llListReplaceList(CURRENT_RESILIENCES,[resrank],curpos + 1,curpos + 1);
    } else {
        CURRENT_RESILIENCES = [resname,resrank] + CURRENT_RESILIENCES; // add to current list too
    }
 
    integer maxpos = llListFindList(RESILIENCES,[resname]);
    if ( maxpos >=0 ) {
        RESILIENCES = llListReplaceList(RESILIENCES,[resrank],maxpos + 1,maxpos + 1);
    } else {
        RESILIENCES = [resname,resrank] + RESILIENCES; // add resilience to list
    }
 
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_RESILIENCE|"+resname+"="+(string)resrank,llGetOwner());
}
 
SET_RP(integer rpamt) {
    if ( rpamt >= 0 ) {
        RP = rpamt;
    } else {
        ERROR("SET_RP("+(string)rpamt+") REQUESTED AMOUNT OUT OF RANGE: LESS THAN ZERO");
    }
}
 
SET_SPECIES(string aspecies) {
    SPECIES = aspecies;
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SPECIES|SPECIES="+SPECIES,llGetOwner());
}
 
SET_SFXPOOL(integer sfxamt) {
    if ( sfxamt >= 0 ) {
        SFXPOOL = sfxamt;
    } else {
        ERROR("SET_SFXPOOL("+(string)sfxamt+") REQUESTED AMOUNT OUT OF RANGE: LESS THAN ZERO");
    }
}
 
SET_SKILL(string skillname,integer skillrank) {
    // TODO how to verify skill names are valid?
    if ( skillrank >= MINSKILL && skillrank <= MAXSKILL ) { // skill rank valid?
        SKILLS = [skillname,skillrank] + SKILLS; // add skill to list
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SKILL|"+skillname+"="+(string)skillrank,llGetOwner());
    } else { // invalid, report it
        ERROR("SKILL "+skillname+" rank "+(string)skillrank+" value out of allowed range: "+(string)MINSKILL+"-"+(string)MAXSKILL);
    }
}
 
SET_SKILLPOOL(integer skillamt) {
    if ( skillamt >= 0 ) {
        SKILLPOOL = skillamt;
    } else {
        ERROR("SET_SKILLPOOL("+(string)skillamt+") REQUESTED AMOUNT OUT OF RANGE: LESS THAN ZERO");
    }
}
 
SET_STAT(string statname,integer statrank) {
    // TODO how to verify stat names are valid?
    if ( statrank >= MINSTAT && statrank <= MAXSTAT ) { // rank valid?
        integer statpos = llListFindList(STATISTICS,[statname]);
        if ( statpos < 0 ) {
            STATISTICS = [statname,statrank] + STATISTICS; // add statistic to list
        } else {
            STATISTICS = llListReplaceList(STATISTICS,[statrank],statpos + 1,statpos + 1);
        }
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_STATISTIC|"+statname+"="+(string)statrank,llGetOwner());
 
    } else { // invalid, report it
        ERROR("STATISTIC "+statname+" rank "+(string)statrank+" value out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
    }
}
 
SET_STATPOOL(integer statamt) {
    if ( statamt >= 0 ) {
        STATPOOL = statamt;
    } else {
        ERROR("SET_STATPOOL("+(string)statamt+") REQUESTED AMOUNT OUT OF RANGE: LESS THAN ZERO");
    }
}
 
SET_STUNT(string stuntname) {
    // TODO how to verify stunt?
    STUNTS = [stuntname] + STUNTS; // add stunt to list
    llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_STUNT|STUNT="+stuntname,llGetOwner());
}
 
SET_XP(integer xpamt) {
    if ( xpamt >= MINXP && xpamt <= MAXXP ) {
        XP = xpamt;
        CALCULATE_LEVEL_BY_XP();
        // inform all other modules that might need to track XP
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_XP|XP="+(string)XP,llGetOwner());
    } else {
        ERROR("SET_XP("+(string)xpamt+") REQUESTED AMOUNT OF RANGE: "+(string)MINXP+"-"+(string)MAXXP);
    }
}
 
SET_XPLEVEL(integer xplevelamt) {
    if ( xplevelamt >= MIN_XPLEVEL && xplevelamt <= MAX_XPLEVEL ) {
        XPLEVEL = xplevelamt; // save local state
        llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_XPLEVEL|XPLEVEL="+(string)XPLEVEL,llGetOwner());
    } else {
        ERROR("SET_XPLEVEL("+(string)xplevelamt+") REQUESTED AMOUNT OUT OF RANGE: "+(string)MIN_XPLEVEL+"-"+(string)MAX_XPLEVEL);
    }    
}
 
SETUP() {
    FLAG_DEBUG = FALSE;
    PLAYERID = llGetOwner();
    CHANOBJECT = (integer)("0x"+llGetSubString(llGetKey(),0,6));
    if ( HANDOBJECT != 0 ) llListenRemove(HANDOBJECT);
    HANDOBJECT = llListen(CHANOBJECT,"",NULL_KEY,"");
    llRegionSay(CHANMYRIAD,"GET_REGION_SETTING|PROGRESSION"); // ask for this first, asking for estate triggers find_notecard function
    llRegionSay(CHANMYRIAD,"GET_REGION_SETTING|ESTATE");
    MENU_TIMER = MENU_TIMEOUT;
    llSetTimerEvent(1.0);
}
 
SKILL_FAILED(string sname) {
    if ( PROGRESSION != "RANDOM" ) return; // tracking skill failure only applies to random progression - ignore message
    integer pos = llListFindList(SKILLS_INCREASED,[sname]);
    if ( pos >= 0 ) { // skill already in increase list
        integer current = llList2Integer(SKILLS_INCREASED,pos + 1);
        if ( current < 4 ) { // not yet, just note it
        } else { // has hit 5, increase skill and stat
            INCREASE_SKILL(sname); // increase skill
            SKILLS_INCREASED = llListReplaceList(SKILLS_INCREASED,[0],pos + 1,pos + 1);
        }
    } else { // skill not in increase list
        SKILLS_INCREASED = [sname,1] + SKILLS_INCREASED; // add the skill to the increased list
    }
    SKILL_INCREASES++;
    // now - 5 skill increases = new skill earned
    if ( SKILL_INCREASES == 5 ) {
        SKILL_INCREASES = 0;
        NEW_SKILLS++;
        RPEVENT("earned a new skill!");
    }
}
 
PARSE(string message,key id) {
    //DEBUG("Parse: message=["+message+"] id=["+(string)id+"]");
    // First - handle type 1 messages that do not require breaking down
    string msg = llToLower(message);
    if ( msg == "debugoff" ) { FLAG_DEBUG=FALSE; } // turn off debugging on request
    if ( msg == "debugon" ) { FLAG_DEBUG=TRUE; } // turn on debugging on request
    if ( msg == "reset" ) { RESET(); } // reset on request
    if ( msg == "version") { GETVERSION(); } // respond with version info when requested
    if ( msg == "get_xp" ) { GET_XP(); return; }
    if ( msg == "get_xplevel" ) { GET_XPLEVEL(); return; }
    if ( msg == "dump_sheet" ) { DUMP_SHEET(id); return; } // id is UUID of character builder, sent over from HUD script
    if ( msg == "dump_progress" ) { DUMP_PROGRESS(id); return; } // id is UUID of character builder, sent over from HUD script
    if ( msg == "del_progress" ) { DEL_PROGRESS(id); return; } // id is UUID of item that requested del progress
    if ( msg == "add_xp" ) { ADD_XP(id); return; } // add a point of total and spendable XP
    // Set up variables to process type 2 and type 3 messages
    list tokens;
    string cmd;
    string data;
    list subtokens;
    string attrib;
    integer idata;
    string sdata;
    // process type 2 bus messages COMMAND|DATA=...|DATA=...
    if ( llSubStringIndex(message,DIV) >=0 ) {
        tokens = llParseString2List(message,[DIV],[]);
        cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
        data = llList2String(tokens,1);
        subtokens = llParseString2List(data,["="],[]);
        attrib = llList2String(subtokens,0);
        idata = llList2Integer(subtokens,1);
        sdata = llList2String(subtokens,1);
        //if ( command == "SET_ESTATE" ) { SET_ESTATE(svalue); return;}
        if ( cmd == "set_xp" ) { SET_XP(idata); return; }
        if ( cmd == "set_xplevel" ) { SET_XPLEVEL(idata); return; }
        if ( cmd == "get_stat" ) { GET_STAT(attrib); return; }
        if ( cmd == "get_resilience" ) { GET_RESILIENCE(attrib); return; }        
        if ( cmd == "get_maxresilience" ) { GET_MAXRESILIENCE(attrib);  return; }
        if ( cmd == "set_resilience" ) { SET_RESILIENCE(attrib,idata); return; }
        // Progress
        if ( cmd == "skill_failed" ) { SKILL_FAILED(sdata); return; } // SKILL_FAILED|SKILL=name
        if ( cmd == "progress" ) {
            if ( llToLower(attrib) == "xp" ) {
                XP = idata;
                if ( XP > MAXXP ) XP = MAXXP;
                llSay(PUBLIC_CHANNEL,"Progress XP: "+(string)XP);
                return;
            }
            if ( llToLower(attrib) == "xplevel" ) {
                XPLEVEL = idata;
                if ( XPLEVEL > MAX_XPLEVEL ) XPLEVEL = MAX_XPLEVEL;
                llSay(PUBLIC_CHANNEL,"Progress XP Level: "+(string)XPLEVEL);
                return;
            }
            if ( llToLower(attrib) == "statpool" ) {
                STATPOOL=idata;
                llSay(PUBLIC_CHANNEL,"Progress Statistics Pool: "+(string)STATPOOL);
                return;
            }
            if ( llToLower(attrib) == "healthpool" ) {
                HEALTHPOOL=idata;
                llSay(PUBLIC_CHANNEL,"Progress Resilience Pool: "+(string)HEALTHPOOL);
                return;                    
            }
            if ( llToLower(attrib) == "skillpool" ) {
                SKILLPOOL=idata;
                llSay(PUBLIC_CHANNEL,"Progress Skill Pool: "+(string)SKILLPOOL);
                return;                    
            }
            if ( llToLower(attrib) == "sfxpool" ) {
                SFXPOOL=idata;
                llSay(PUBLIC_CHANNEL,"Progress SFX Pool: "+(string)SFXPOOL);
                return;                  
            }
            return;
        }
        // done processing commands with DIV
        return;
    }
    // Process type 3 messages CATEGORY=ATTRIBUTE,VALUE
    tokens = llParseString2List(message,["="],[]);
    cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
    data = llList2String(tokens,1);
    subtokens = llCSV2List(data);
    attrib = llList2String(subtokens,0);
    idata = llList2Integer(subtokens,1);
    sdata = llList2String(subtokens,1);
    if ( cmd == "version" ) { CHECK_CARDVERSION(sdata); return;}
    if ( cmd == "name" ) { SET_NAME(sdata); return; }
    if ( cmd == "species" ) { SET_SPECIES(sdata); return;}
    if ( cmd == "background" ) { SET_BACKGROUND(sdata); return; }
    if ( cmd == "career" ) { SET_CAREER(sdata); return; }
    if ( cmd == "statistic" ) { SET_STAT(attrib,idata); return; }
    if ( cmd == "resilience" ) { SET_RESILIENCE(attrib,idata); return;}
    if ( cmd == "boon" ) { SET_BOON(attrib,idata); return;}
    if ( cmd == "flaw" ) { SET_FLAW(attrib,idata); return;}
    if ( cmd == "skill" ) { SET_SKILL(attrib,idata); return;}
    if ( cmd == "mortal_effect" ) { SET_MORTAL_EFFECT(attrib,idata); return;}
    if ( cmd == "social_effect" ) { SET_SOCIAL_EFFECT(attrib,idata); return;}
    if ( cmd == "magic_effect" ) { SET_MAGIC_EFFECT(attrib,idata); return;}            
    if ( cmd == "vehicle_effect" ) { SET_VEHICLE_EFFECT(attrib,idata); return;}
    if ( cmd == "stunt" ) { SET_STUNT(sdata); return;}
    if ( cmd == "quote" ) { SET_QUOTE(sdata); return;}
    if ( cmd == "equipment" ) { SET_EQUIPMENT(attrib,idata); return;}
    if ( cmd == "xp" )          { SET_XP(idata); return; }
    if ( cmd == "xplevel" )     { SET_XPLEVEL(idata); return; }
    if ( cmd == "gp" )          { SET_GP(idata); return; }
    if ( cmd == "statpool" )    { SET_STATPOOL(idata); return; }
    if ( cmd == "healthpool" )  { SET_HEALTHPOOL(idata); return; }
    if ( cmd == "skillpool" )   { SET_SKILLPOOL(idata); return; }
    if ( cmd == "sfxpool" )     { SET_SFXPOOL(idata); return; }
    if ( cmd == "rp" )          { SET_RP(idata); return; }
    if ( cmd == "character_loaded" ) {
        llSay(PUBLIC_CHANNEL,llKey2Name(PLAYERID)+"'s character loaded.");
    }
}
 
default {
 
    // dataserver called for each line of notecard requested - process character sheet
    dataserver(key queryid,string data) {
        if ( queryid == QUERY ) { // ataserver gave us line we asked for?
            if ( data != EOF ) { // we're not at end of notecard file?
                if ( llGetSubString(data,0,0) == "#" ) { // does this line start with comment mark?
                    QUERY = llGetNotecardLine(CARD,LINE++); // ignore comment and ask for the next line
                    return;
                }
                PARSE(data,llGetOwner()); // parse incoming notecard line
                QUERY = llGetNotecardLine(CARD,LINE++); // finished with known keywords, get next line
            } else { // end of notecard
                // TODO how to verify entire character sheet was completed and loaded?
                llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER_LOADED",llGetOwner()); // done loading
                DEBUG("Character Sheet Loaded.");
            } // end if data not equal eof
        } // end if query id equal
    } // end if data server event
 
    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
        DEBUG("EVENT: link_message("+(string)sender_num+","+(string)num+","+str+","+(string)id+")");        
        PARSE(str,id); // parse incoming message
    }
 
    listen(integer channel,string name,key id, string msg) {
        name = ""; // LSLint
        id = NULL_KEY; // LSLint
        if ( channel == CHANOBJECT ) {
            list tokens = llParseString2List(msg,[DIV],[]);
            string command = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
            if ( command == "region_setting" ) {
                list sublist = llParseString2List(llList2String(tokens,1),["="],[]);
                if ( llToLower(llStringTrim(llList2String(sublist,0),STRING_TRIM)) == "progression" ) {
                    SET_PROGRESSION(llToUpper(llList2String(sublist,1)));
                    return;
                }
                if ( llToLower(llStringTrim(llList2String(sublist,0),STRING_TRIM)) == "estate" ) {
                    SET_ESTATE(llList2String(sublist,1));
                    FIND_NOTECARD();
                    return;
                }
                return;
            }
            if ( command == "skill" ) {
                integer t = llGetListLength(tokens);
                integer c;
                for ( c = 0; c < t; c++) {
                    list sublist = llParseString2List(llList2String(tokens,c),["="],[]);
                    string attrib = llToLower(llList2String(sublist,0));
                    if ( attrib == "basestat" ) {
                        string value = llList2String(sublist,1);
                        INCREASE_STAT(value);
                    }
                }
            }
            PARSE(msg,id); // parse incoming chat message not REGION_SETTING or SKILL data
            return;
        }
        if ( channel == MENU_CHANNEL ) {
            if ( msg == "Default" ) {
                CARD = DEFAULT;
            } else {
                integer listpos = llListFindList(CHARACTERS,[msg]);
                if ( listpos >= 0 ) {
                    CARD = llList2String(CHARACTERS,listpos + 1);
                }
            }
            DEBUG("Loading character sheet: "+CARD);
            llSetTimerEvent(0.0);
            MENU_TIMER = 0;
            llListenRemove(MENU_HANDLE);
            QUERY = llGetNotecardLine(CARD,LINE++); // ask for line from notecard and advance to next line
            return;
        }
    }
 
    state_entry() {
        SETUP();
    }
 
    timer() {
        MENU_TIMER--; // timer still running, decrement
        if ( MENU_TIMER <= 0 ) { // timed out
            DEBUG("Character Sheet Menu timed out. Using default character sheet."); // tell the owner
            llListenRemove(MENU_HANDLE); // remove the listener
            MENU_TIMER = 0;
            llSetTimerEvent(0.0); // stop the timer
            CARD = DEFAULT;
            QUERY = llGetNotecardLine(CARD,LINE++);
        }
    }
}
// END
Personal tools
General
About This Wiki