User:Allen Kerensky/Myriad Lite Dev/Myriad Lite-v0.1.6-20120501.lsl

From OpenSimulator

Jump to: navigation, search

Myriad_Lite-v0.1.6-20120501.lsl

// Myriad_Lite-v0.1.6-20120501.lsl
// Copyright (c) 2012 By Allen Kerensky (OSG/SL)
// The Myriad RPG System was designed, written, and illustrated by Ashok Desai
// Myriad RPG licensed under the Creative Commons Attribution 2.0 UK: England and Wales
// http://creativecommons.org/licenses/by/2.0/uk/
// Myriad Lite software Copyright (c) 2011-2012 by Allen Kerensky (OSG/SL)
// Baroun's Adventure Machine Copyright (c) 2008-2011 by Baroun Tardis (SL)
// Myriad Lite and Baroun's Adventure Machine licensed under the
// Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported
// http://creativecommons.org/licenses/by-nc-sa/3.0/
// You must agree to the terms of this license before making any use of this software.
// If you do not agree to this license, simply delete these materials.
// There is no warranty, express or implied, for your use of these materials.
 
// CONSTANTS - DO NOT CHANGE DURING RUN
string VERSION = "0.1.6"; // Allen Kerensky's script version
string VERSIONDATE = "20120501"; // Allen Kerensky's script yyyymmdd
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 MINSKILL = 1; // min value for skill rank
integer MAXSKILL = 5; // max value for skill rank
integer CHANMYRIAD = -999; // chat sent to ALL Myriad players in region
integer CHANCOMMAND = 5; // chat sent by player to their meter
float RESPAWN_TIME = 30.0; // time dead before automatic respawn
string DIV = "|"; // message field divider
string ANIM_INCAPACITATED = "sleep"; // anim when incapacitated
string ANIM_DEAD = "dead"; // anim when dead
string CHAN_PREFIX = "0x"; // channel prefix for calculating dynamic channels
integer MAXARMOR = 5; // max legal armor rating
 
// 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 LM_SENDTOATTACHMENT = 0x80000000;
 
// RUNTIME GLOBALS - CAN CHANGE DURING RUN
integer FLAG_DEBUG = FALSE; // see debug messages?
key PLAYERID = NULL_KEY; // cached player UUID
string PLAYERNAME = ""; // cached player name
string NAME = ""; // character name
string SPECIES = ""; // character species
string BACKGROUND = ""; // character childhood history
string CAREER = ""; // character career or faction
list STATISTICS = [];
list RESILIENCES = [];
list CURRENT_RESILIENCES = [];
list SKILLS = []; // skills [ string SkillName, integer SkillRank ]
integer HANDMYRIAD = 0; // Myriad channel handle
integer CHANPLAYER = 0; // dynamic channel to one player's UUID
integer HANDPLAYER = 0; // player channel handle
integer CHANOBJECT = 0; // dynamic channel to one object's UUID
integer HANDCOMMAND = 0; // command channel handle
integer HANDATTACH = 0; // attachment channel handle
integer CHANATTACH = 0; // dynamic channel for attachments
integer CHANBAM = 0; // dynamic channel for BAM quests
integer HANDBAM = 0; // BAM channel update
integer FLAG_ANIMATE; //
integer FLAG_INCAPACITATED; // incapacitated by wounds?
integer FLAG_DEAD; // killed by critical wounds?
vector  MOVELOCK; // movelock position when incapacitated or dead
float   TAU = 0.5; // movelock tau
integer CURARMOR = 0; // highest armor value worn out of all armor worn, not a total
integer METERWORN; // using meter?
 
// DEBUG - show debug chat with wearer name for sorting
DEBUG(string dmessage) {
    if ( FLAG_DEBUG == TRUE ) { // are we debugging?
        llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") HUD: "+dmessage);
    }
}
 
// ERROR - show errors on debug channel with wearer name for sorting
ERROR(string emessage) {
    llSay(DEBUG_CHANNEL,"ERROR ("+llKey2Name(PLAYERID)+"): "+emessage);
}
 
RPEVENT(string rpevent) {
    llRegionSay(CHANMYRIAD,"RPEVENT|"+NAME+" ("+PLAYERNAME+") "+rpevent);
}
 
integer GET_RESILIENCE(string name) {
    integer pos = llListFindList(CURRENT_RESILIENCES,[name]);
    if ( pos >= 0 ) {
        return llList2Integer(CURRENT_RESILIENCES,pos + 1);
    }
    return 0;
}
 
integer GET_MAX_RESILIENCE(string name) {
    integer pos = llListFindList(RESILIENCES,[name]);
    if ( pos >= 0 ) {
        return llList2Integer(RESILIENCES,pos + 1);
    }
    return 0;
}
 
SET_RESILIENCE(string name,integer value) {
    if ( value < 0 ) { return;} // out of range
    if ( value > 20 ) { return; } // out of range
    integer curpos = llListFindList(CURRENT_RESILIENCES,[name]);
    integer curval;
    integer maxval;
    if ( curpos >= 0 ) {
        curval = llList2Integer(CURRENT_RESILIENCES,curpos + 1);
    } else { // resilience not found
            return;
    }
    integer maxpos = llListFindList(RESILIENCES,[name]);
    if ( maxpos >=0 ) {
        maxval = llList2Integer(RESILIENCES,maxpos + 1);
    } else { // resilience not found
            return;
    }
    if ( value <= maxval) {
        CURRENT_RESILIENCES = llListReplaceList(CURRENT_RESILIENCES,[value],curpos + 1, curpos + 1);
    }
}
 
// HIT - player is hit - check to see if attack dice breach armor
// Making A Damage Roll (Myriad p25, Myriad Special Edition p31)
HIT(integer attackdice) {
    integer damagetaken = 0; // start with zero damage
    while(attackdice--) { // roll for each attack dice
        integer dieroll = 1+(integer)llFrand(5.0); // reasonably uniform d6
        if ( dieroll > CURARMOR ) { // attack roll stronger than armor worn?
            damagetaken++; // add a wound point
        }
    }
    // finished roll how did we do?
    if ( damagetaken > 0 ) { // we took damage
        if ( CURARMOR > 0 ) { // wearing armor? tell them it was breached
            llOwnerSay("That attack penetrated your armor and you've been wounded!");
            llWhisper(CHANATTACH,"ARMOREFFECTHIT");
        } else { // fighting in no armor?
                llOwnerSay("You've been wounded! Wear some armor next time?");
        }
        WOUNDED(damagetaken); // apply damage taken to resilences
    } else { // hit, but no damage taken
            // must be wearing *some* armor to be hit but avoid a wound, don't recheck for armor here
            llOwnerSay("Your armor blocked the damage from that attack!");
        llWhisper(CHANATTACH,"ARMOREFFECTBLOCKED");
    }
}
 
// WOUNDED - Player takes Resilience damage
WOUNDED(integer amount) {
    while (amount--) { // for each wound taken
        integer curwounds = GET_RESILIENCE("Wounds");
        integer curcritical = GET_RESILIENCE("Critical");
        integer maxcritical = GET_MAX_RESILIENCE("Critical");
        if ( curwounds > 0 && curcritical != maxcritical ) {
            llSay(DEBUG_CHANNEL,"ERROR! WOUND STATE IS NONSENSE! CANNOT APPLY DAMAGE!");
            llOwnerSay("ERROR! WOUND STATE IS NONSENSE! CANNOT APPLY DAMAGE! TELL ALLEN KERENSKY!");
            return;
        }
        if ( curwounds > 0 && curcritical == maxcritical ) { // wound boxes left?
            curwounds--; // scratch off one
            SET_RESILIENCE("Wounds",curwounds);
            METER(); // update
            llOwnerSay("You've been wounded!");
        } else if ( curwounds < 1 && curcritical > 0 ) { // incapacitated
                curwounds = 0; // force to zero
            SET_RESILIENCE("Wounds",curwounds);
            curcritical--; // scratch off a critical wound box
            SET_RESILIENCE("Critical",curcritical);
            INCAPACITATED(); // show incapacitation
        } else if ( curwounds < 1 && curcritical < 1 ) { // out of critical wounds?
                curwounds = 0; // force zero
            SET_RESILIENCE("Wounds",curwounds);
            curcritical = 0; // force zero
            SET_RESILIENCE("Critical",curcritical);
            DEAD(); // show death
        }
    } // end while
}
 
// INCAPACITATED - player lost all WOUNDS - unable to act
INCAPACITATED() {
    FLAG_INCAPACITATED = TRUE; // yes, we're now incapacitated
    MOVELOCK = llGetPos();
    llMoveToTarget(MOVELOCK,TAU);
    METER(); // update meter
    llStartAnimation(ANIM_INCAPACITATED); // "we're hurt and down" animation
    RPEVENT("has been incapacitated!");
    llOwnerSay("You've been incapacitated!");
    llMessageLinked(LINK_THIS,MODULE_HUD,"INCAPACITATED",PLAYERID);
    llSetTimerEvent(RESPAWN_TIME); // heal in a bit
}
 
// DEAD - player is dead, kill them and wait to respawn
DEAD() {
    FLAG_DEAD = TRUE; // remember that we're now dead
    METER(); // update hover text
    llStartAnimation(ANIM_DEAD); // start dead animation
    RPEVENT("has been killed!");
    llOwnerSay("You've been killed!");
    llMessageLinked(LINK_THIS,MODULE_HUD,"DEAD",PLAYERID);
    llSetTimerEvent(RESPAWN_TIME); // respawn in a bit
}
 
// HEAL - restore lost WOUND and CRITICAL resilience
// Thanks to Artemis Tesla for contributing summary report logic
HEAL(integer healamount) {
    integer critsHealed = 0; // track how many crit boxes restored for summary report
    integer woundsHealed = 0; // track how many non-crit boxes restored for summary report
    integer reborn = FALSE; // track if reborn/respawn or not for summary report
    integer revived = FALSE;  // track of revived or not for summary report
    integer curwounds = GET_RESILIENCE("Wounds");
    integer maxwounds = GET_MAX_RESILIENCE("Wounds");
    integer curcritical = GET_RESILIENCE("Critical");
    integer maxcritical = GET_MAX_RESILIENCE("Critical");
 
    // TODO report once for multiple healing amounts
    while ( healamount-- ) {
        // step through each point of healing
        if ( curcritical < maxcritical ) { // is current critical less than max critical
            DEBUG("Heal one critical wound");
            curcritical++; // heal one current critical
            SET_RESILIENCE("Critical",curcritical);
            critsHealed++; // add a point back
            if ( FLAG_DEAD == TRUE ) {   // healed a critical, critical now > 0 so not dead anymore
                FLAG_DEAD = FALSE; // no longer dead
                llMessageLinked(LINK_THIS,MODULE_HUD,"ALIVE",PLAYERID);
                reborn = TRUE; // show rebirth in summary report
                DEBUG("Heal: reborn");
            }
        } else {
                if ( curwounds < maxwounds ) {   // player not critical, heal non-critical?
                    DEBUG("Heal one wound");
                    curwounds++; // add the healing point to current wounds
                    SET_RESILIENCE("Wounds",curwounds);
                    woundsHealed++; // add a point of non-critical
                    if ( FLAG_INCAPACITATED == TRUE ) { // were they incapacitated?
                        FLAG_INCAPACITATED = FALSE; // no longer gravely wounded
                        llMessageLinked(LINK_THIS,MODULE_HUD,"REVIVED",PLAYERID);
                        revived = TRUE; // show revival in summary report
                        DEBUG("Heal: Revived!");
                        llStopMoveToTarget();
                    }
                } // end if curwounds < wounds
        }
    } // end while
 
    // Summary report of healing effects
    if ( critsHealed > 0 ) {   // was at least one critical healed?
        DEBUG("Critical Heal: "+(string)curcritical+" of "+(string)maxcritical+" critical wound boxes.");
        if (critsHealed > 1) { // was more then one critical wound healed?
            llOwnerSay("Critical " + (string)critsHealed + " wounds healed.");
        } else {
                llOwnerSay("Critical " + (string)critsHealed + " wound healed.");
        }
    }
    if (reborn == TRUE ) { // if player reborn from this heal
        RPEVENT("has been resurrected!");
        if ( FLAG_ANIMATE == TRUE ) { // if we're allowed to change animations
            llStopAnimation(ANIM_DEAD); // stop "we're dead" animation
        }
        llOwnerSay("You've been resurrected! Welcome back to the land of the living.");
    }
 
    if ( woundsHealed > 0 ) { // was at least 1 non-critical healed?
        DEBUG("Heal Non-Critical Wounds: "+(string)curwounds+" of "+(string)maxwounds+" non-critical wound boxes.");
        if (woundsHealed > 1) { // was more than one non-critical healed?
            llOwnerSay((string)woundsHealed + " non-critical wounds healed.");
        } else {
                llOwnerSay((string)woundsHealed + " non-critical wound healed.");
        }
    }
 
    if ( revived == TRUE ) { // if player revived from this heal
        RPEVENT("has revived and is no longer incapacitated!");
        if ( FLAG_ANIMATE == TRUE ) { // if we're allowed to change anims
            llStopAnimation(ANIM_INCAPACITATED); // stop the "we're down" animation
        }
        llOwnerSay("You are no longer incapacitated! Welcome back to the fight!");
    }
    METER(); // update hovertext
}
 
// ABILITY TEST
// Requires ATTRIBUTE NAME, SKILL NAME
// Returns the ability test score for use by success fail, opposed rolls, etc
// See Myriad PDF page 18, Myriad Special Edition page 24
integer ABILITY_TEST(integer attribute,integer skill) {
    integer highroll = 0; // clear out the highest roll
    while( attribute-- ) { // roll a dice for each point of the attribute
        integer roll = 1+(integer)llFrand(5.0); // roll this d6
        if ( roll > highroll) highroll = roll; // if this is highest roll so far, remember it
    } // finished rolling a dice for each point of the base attribute
    return highroll + skill; // now, return the total of highest dice roll + skill value
}
 
// An Unopposed Ability Test - Myriad PDF p. 19, Myriad Special Edition p. 25
// Requires TargetNumber, Attribute Name, Skill Name
// Returns TRUE for Success and False for Fail
integer UNOPPOSED_TEST(integer targetnum,integer tattribute,integer tskill ) {
    integer check = ABILITY_TEST(tattribute,tskill); // calculate the player's ability test value
    if ( check >= targetnum ) return TRUE; // player won the ability test
    return FALSE; // player lost the ability test
}
 
// SETUP - begin bringing the HUD online
SETUP() {
    CREDITS(); // show Myriad credits as required by the Creative Commons - Attribution license
    PLAYERID = llGetOwner(); // remember the owner's UUID
    PLAYERNAME = llKey2Name(PLAYERID); // remember the owner's legacy name
    llSetText("",<0,0,0>,0); // clear any previous hovertext
    llMessageLinked(LINK_THIS,MODULE_HUD,"BAMRESET",NULL_KEY); // reset the BAM module too
    llMessageLinked(LINK_THIS,MODULE_HUD,"ARMORRESET",PLAYERID); // send reset to armor module
}
 
// CREDITS comply with Myriad RPG Creative Common-Attribution legal requirement
CREDITS() {
    llOwnerSay("The Myriad RPG System was designed, written, and illustrated by Ashok Desai.");
    llOwnerSay("RPG System licensed under the Creative Commons Attribution 2.0 UK: England and Wales.");
    llOwnerSay("Myriad Lite v"+VERSION+" "+VERSIONDATE+" Copyright (c) 2011 by Allen Kerensky (OSG/SL)");
    llOwnerSay("Licensed under Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported.");
    llMessageLinked(LINK_THIS,MODULE_HUD,"VERSION",llGetOwner());
}
 
// RESET - shut down running animations then reset the script to reload character sheet
RESET() {
    if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) { // don't allow reset if already on respawn timer
        llOwnerSay("Cannot reset while incapacitated or dead. You will respawn in a few moments.");
        return;
    }
    llOwnerSay("Resetting Myriad Lite. Please wait...");
    // stop all running animations
    if ( FLAG_ANIMATE == TRUE ) { // do we have permission to animate?
        list anims = llGetAnimationList(PLAYERID); // get list of current animations for owner
        integer animcount = llGetListLength(anims); // count the number of animations in the list
        while (animcount--) { // step from end of animation list to beginning
            llStopAnimation(llList2String(anims,animcount)); // stopping each animation
        }
    }
    llMessageLinked(LINK_THIS,MODULE_HUD,"ARMORRESET",PLAYERID); // send reset to armor module
    llMessageLinked(LINK_THIS,MODULE_HUD,"BAMRESET",PLAYERID); // reset the BAM module too
    llMessageLinked(LINK_THIS,MODULE_HUD,"RESET",PLAYERID); // send reset to armor module    
    llResetScript(); // now reset
}
 
// METER - update a hovertext health meter or HUD bar graph
METER() {
    if ( METERWORN == FALSE ) return;
    integer curwounds = GET_RESILIENCE("Wounds");
    integer maxwounds = GET_MAX_RESILIENCE("Wounds");
    integer curcritical = GET_RESILIENCE("Critical");
    integer maxcritical = GET_MAX_RESILIENCE("Critical");
    // create a meter message packet
    string message = "METER"+DIV+PLAYERNAME+DIV+NAME+DIV+(string)curwounds+DIV+(string)maxwounds+DIV+(string)curcritical+DIV+(string)maxcritical+DIV+(string)FLAG_DEAD+DIV+(string)FLAG_INCAPACITATED;
    llRegionSay(CHANMYRIAD,message); // send the update to region for scorekeepers, etc
    llWhisper(CHANATTACH,message); // whisper to the wearer's actual meter
    llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner()); // send meter updates to bus
    DEBUG("Wounds: "+(string)curwounds+" of "+(string)maxwounds+" wound boxes. Critical: "+(string)curcritical+" of "+(string)maxcritical+" critical wound boxes.");
}
 
// DEBUGON - turn on the DEBUG flag
DEBUGON() {
    FLAG_DEBUG = TRUE; // set debug flag TRUE
    llOwnerSay("Debug Mode Activated");
    llMessageLinked(LINK_THIS,MODULE_HUD,"DEBUGON",llGetOwner());    
}
 
// DEBUGOFF - turn off the DEBUG flag
DEBUGOFF() {
    FLAG_DEBUG = FALSE; // set debug flag to FALSE
    llOwnerSay("Debug Mode Deactivated");
    llMessageLinked(LINK_THIS,MODULE_HUD,"DEBUGOFF",llGetOwner());    
}
 
// COMBATOFF - turn off fist fighter
COMBATOFF() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"COMBATOFF",llGetOwner());
}
 
// COMBATON - turn on fist fighter
COMBATON() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"COMBATON",llGetOwner());
}
 
// COMMAND - process chat and link message commands together
COMMAND(string msg) {
    // break down the commands and messages into units we can work with
    list fields = llParseString2List(msg,[DIV],[]); // break into list of fields based on DIVider
    string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command
    if ( command == "checkammo" ) { CHECKAMMO(); return;} // check ammo in weapons
    if ( command == "combatoff") { COMBATOFF(); return; } // turn off fist fighter
    if ( command == "combaton" ) { COMBATON(); return; } // turn on the fist fighter
    if ( command == "credits" ) { CREDITS(); return;} // show the credits including version number
    if ( command == "debugoff" ) { DEBUGOFF(); return; } // player turn off debugging
    if ( command == "debugon" ) { DEBUGON(); return;} // player turn on debugging
    if ( command == "drawboth" ) { DRAW("both"); return; } // draw both weapons
    if ( command == "drawleft" ) { DRAW("left"); return; } // draw weapon in left hand
    if ( command == "drawright" ) { DRAW("right"); return; } // draw weapon using right hand
    if ( command == "holsterboth" ) { HOLSTER("both"); return; } // holster both weapons
    if ( command == "holsterleft" ) { HOLSTER("left"); return; } // holster weapon in left hand
    if ( command == "holsterright" ) { HOLSTER("right"); return; } // holster weapon in right hand
    if ( command == "quest" ) { QUEST(); return; } // check our current quest status
    if ( command == "reload" ) { RELOAD(); return;}  // reload weapons
    if ( command == "reset" ) { RESET(); return;} // reset HUD
    //if ( command == "rumor" ) { RUMOR(msg); return;} // rumors
    if ( command == "safetyoff" ) { SAFETYOFF(); return;} // unsafe the weapons
    if ( command == "safetyon" ) { SAFETYON(); return;} // safe the weapons
    if ( command == "sheatheboth" ) { SHEATHE("both"); return; } // sheathe both weapons
    if ( command == "sheatheleft" ) { SHEATHE("left"); return; } // sheathe weapon in left hand
    if ( command == "sheatheright" ) { SHEATHE("right"); return; } // sheathe weapon in right hand
    if ( command == "version" ) { CREDITS(); return;} // show the credits including version number
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "armor" ) { SENDTOMODULE(msg,PLAYERID); return; }
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "rumor" ) { RUMOR(msg); return; }
}
 
SENDTOMODULE(string msg,key speaker) {
    DEBUG("SENDTOMODULE("+msg+")");
    llMessageLinked(LINK_THIS,MODULE_HUD,msg,speaker);
}
 
SENDTOATTACHMENT(string msg) {
    DEBUG("SENDTOATTACHMENT("+msg+")");
    llWhisper(CHANATTACH,msg);
}
 
// RUMOR CONTROL
RUMOR(string cmdrumor) {
    DEBUG("Sending to rumor module: "+cmdrumor);
    llMessageLinked(LINK_THIS,MODULE_HUD,cmdrumor,PLAYERID); // relay rumor commands to module
}
 
// QUEST STATUS
QUEST() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"BAMSTATUS",PLAYERID); // send a status request to BAM Modules
}
 
// DRAW weapons
DRAW(string hand) {
    if ( hand == "left" ) { llWhisper(CHANATTACH,"DRAWLEFT"); return; } // draw left-hand weapon
    if ( hand == "right" ) { llWhisper(CHANATTACH,"DRAWRIGHT"); return; } // draw right-hand weapon
    if ( hand == "both" ) { llWhisper(CHANATTACH,"DRAWBOTH"); return; } // draw both weapons
}
 
// SHEATHE weapons
SHEATHE(string hand) {
    if ( hand == "left" ) { llWhisper(CHANATTACH,"SHEATHELEFT"); return; } // sheathe left-hand weapon
    if ( hand == "right" ) { llWhisper(CHANATTACH,"SHEATHERIGHT"); return; } // sheathe right-hand weapon
    if ( hand == "both" ) { llWhisper(CHANATTACH,"SHEATHEBOTH"); return; } // sheathe both weapons
}
 
// HOLSTER weapons
HOLSTER(string hand) {
    if ( hand == "left" ) { llWhisper(CHANATTACH,"HOLSTERLEFT"); return; } // holster left-hand weapon
    if ( hand == "right" ) { llWhisper(CHANATTACH,"HOLSTERRIGHT"); return; } // holster right-hand weapon
    if ( hand == "both" ) { llWhisper(CHANATTACH,"HOLSTERBOTH"); return; } // holster both weapons
}
 
// CHECK AMMO
CHECKAMMO() {
    llWhisper(CHANATTACH,"CHECKAMMO");
}
 
// RELOAD
RELOAD() {
    llWhisper(CHANATTACH,"RELOAD");
}
 
// SAFETY ON
SAFETYON() {
    llWhisper(CHANATTACH, "SAFETYON");
}
 
// SAFETY OFF
SAFETYOFF() {
    llWhisper(CHANATTACH, "SAFETYOFF");
}
 
// DEFAULT STATE - load character sheet
default {
 
    // STATE ENTRY - called on Reset
    state_entry() {
        SETUP(); // show credits and start character sheet load
    }
 
    // on_rez - when rezzed to ground or from inventory as attachment during login
    on_rez(integer params) {
        params = 0; // LSLINT
        RESET(); // force to go through state entry
    }
 
    // attach - when attached or detached from inventory or during login
    attach(key id) {
        id = NULL_KEY; // LSLINT
        RESET(); // force to go through state entry
    }
 
    link_message(integer sender_num,integer sender,string data,key id) {
        if ( sender == MODULE_HUD ) return; // ignore our own messages
        DEBUG("HUD Link Message: "+data);        
        sender_num = 0; // LSLINT
        id = NULL_KEY; // LSLINT
        list FIELDS = llParseString2List(data,["|"],[]); // break line of text into = delimited fields
        string CMD = llStringTrim(llList2String(FIELDS,0),STRING_TRIM); // field zero is the "command"
        string DATA = llStringTrim(llList2String(FIELDS,1),STRING_TRIM); // field one is the data
        list SUBFIELDS = llParseString2List(DATA,["="],[]); // break data field into comma-delimited subfields if needed
        if ( CMD == "SET_NAME" ) {
            NAME = llList2String(SUBFIELDS,1); // set the name
            return;
        }
        if ( CMD == "SET_SPECIES" ) {
            SPECIES = llList2String(SUBFIELDS,1); // set the species;
            return;
        }
        if ( CMD == "SET_BACKGROUND" ) {
            BACKGROUND = llList2String(SUBFIELDS,1); // set the species;
            return;
        }
        if ( CMD == "SET_CAREER" ) {
            CAREER = llList2String(SUBFIELDS,1); // set the species;
            return;
        }
        if ( CMD == "SET_STATISTIC" ) {
            string statname = llList2String(SUBFIELDS,0); // find the boon name
            integer statrank = llList2Integer(SUBFIELDS,1); // find the boon rank value
            // TODO how to verify stat names are valid?
            if ( statrank >= MINSTAT && statrank <= MAXSTAT ) { // rank valid?
                STATISTICS = [statname,statrank] + STATISTICS; // add statistic to list
            } else { // invalid, report it
                ERROR("STATISTIC "+statname+" rank "+(string)statrank+" value out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
            }
            return;
        }
        if ( CMD == "SET_RESILIENCE" ) {
            string resname = llList2String(SUBFIELDS,0); // find the boon name
            integer resrank = llList2Integer(SUBFIELDS,1); // find the boon rank value
            // TODO how to verify resilience names are valid?
            if ( resrank >= MINRESILIENCE && resrank <= MAXRESILIENCE ) { // rank valid?
                RESILIENCES = [resname,resrank] + RESILIENCES; // add resilience to list
                CURRENT_RESILIENCES = [resname,resrank] + CURRENT_RESILIENCES; // add to current list too
            } else { // invalid, report it
                ERROR("RESILIENCE "+resname+" rank "+(string)resrank+" value out of allowed range: "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE);
            }
            return;
        }
        if ( CMD == "SET_SKILL" ) {
            string skillname = llList2String(SUBFIELDS,0); // find the skill name
            integer skillrank = llList2Integer(SUBFIELDS,1); // find the skill rank
            // TODO how to verify skill names are valid?
            if ( skillrank >= MINSKILL && skillrank <= MAXSKILL ) { // skill rank valid?
                SKILLS = [skillname,skillrank] + SKILLS; // add skill to list
            } else { // invalid, report it
                ERROR("SKILL "+skillname+" rank "+(string)skillrank+" value out of allowed range: "+(string)MINSKILL+"-"+(string)MAXSKILL);
            }
            return;
        }
        if ( CMD == "CHARACTER_LOADED" ) {
            state running; // we're out of notecard, so character sheet is loaded - start playing
        }
    } // end of link_message event
} // end default state
 
// STATE RUNNING - character sheet loaded - player is active in the game
state running {
 
    // STATE ENTRY
    state_entry() {
        llOwnerSay("Character Sheet loaded. You are now ready to roleplay.");
        if ( HANDMYRIAD != 0 ) llListenRemove(HANDMYRIAD);
        HANDMYRIAD = llListen(CHANMYRIAD,"",NULL_KEY,""); // setup listener for Myriad RP events
        if ( HANDCOMMAND != 0 ) llListenRemove(HANDCOMMAND);
        HANDCOMMAND = llListen(CHANCOMMAND,"",PLAYERID,""); // listen to chat commands from owner
        CHANPLAYER = (integer)("0x"+llGetSubString((string)PLAYERID,0,6)); // calculate a player-specfic dynamic chat channel
        if ( HANDPLAYER != 0 ) llListenRemove(HANDPLAYER);
        HANDPLAYER = llListen(CHANPLAYER,"",NULL_KEY,""); // listen on the player dynamic chat channel
        CHANATTACH = (integer)("0x"+llGetSubString((string)PLAYERID,1,7)); // attachment-specific channel
        if ( HANDATTACH != 0 ) llListenRemove(HANDATTACH);
        HANDATTACH = llListen(CHANATTACH,"",NULL_KEY,""); // listen for messages from attachments
        CHANBAM = (integer)(CHAN_PREFIX + llGetSubString((string)PLAYERID,-7,-1));
        if ( HANDBAM != 0 ) llListenRemove(HANDBAM);
        HANDBAM = llListen(CHANBAM,"",NULL_KEY,""); // start listener with listenremove handle
        llOwnerSay("Registering any Myriad Lite-compatible attachments...");
        llWhisper(CHANATTACH,"REGISTERATTACHMENTS"); // ask for attachments on their dynamic channel
        llRequestPermissions(PLAYERID,PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS);
        // calculate player's dynamic BAM channel
        METER(); // update hovertext
        RUMOR("RUMOR_RESET"); // reset the rumor module to load new rumor server UUID if needed
        QUEST(); // update the BAM Module
        llOwnerSay("HUD startup complete. "+(string)llGetFreeMemory()+" bytes free.");
    }
 
    // ON_REZ - logged in with meter, or worn from inventory while running
    on_rez(integer param) {
        param = 0; // LSLINT
        RESET(); // a reset to reload character
    }
 
    // ATTACH - logged in with meter or worn from inventory/ground while running
    attach(key id) {
        id = NULL_KEY; // LSLINT
        RESET(); // a reset to reload character
    }
 
    // CHANGED - triggered for many changes to the avatar
    // TODO reload sim-specific settings on region change
    changed(integer changes) {
        if ( changes & CHANGED_INVENTORY ) { // inventory changed somehow?
            llOwnerSay("Inventory changed. Reloading.");
            RESET(); // saved a new character sheet? - reset and re-read it.
        }
        if ( changes & CHANGED_REGION || changes & CHANGED_TELEPORT ) {
            llRequestPermissions(PLAYERID,PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS);
            METER(); // update the meter after a shift
        }
    }
 
    // TOUCH_START - touch HUD for adventure update
    touch_start(integer total_number) {
        total_number = 0; // LSLINT
        string action = llGetLinkName(llDetectedLinkNumber(0)); // get name of prim clicked in link set
        if ( action != "" && action != llGetObjectName() ) { // someone clicked a named button prim on this linkset
            COMMAND(action); // try that prim name as a command
            return;
        }
        METER();
    }
 
    // TIMER - scheduled events
    timer() {
        // Respawn timer ended
        if ( FLAG_DEAD == TRUE ) { // if dead
            RPEVENT("respawns!");
            llMessageLinked(LINK_THIS,MODULE_HUD,"ALIVE",PLAYERID);
            RESET(); // reset and reload character
        }
        if ( FLAG_INCAPACITATED == TRUE ) { // if hurt
            HEAL(1); // heal 1 wound
        }
        integer curwounds = GET_RESILIENCE("Wounds");
        integer maxwounds = GET_MAX_RESILIENCE("Wounds");
        if ( curwounds == maxwounds ) { // fully healed?
            llSetTimerEvent(0.0); // stop timer
        }
    }
 
    // LINK MESSAGE - commands to and from other prims in HUD
    link_message(integer sender,integer sending_module,string str, key id) {
        if ( sending_module == MODULE_HUD ) return; // ignore our own link messages
        DEBUG("HUD Link Message: "+str);
        sender = 0; // LSLINT
        id = NULL_KEY; // LSLINT
        list fields = llParseString2List(str,[DIV],[]); // break into list of fields based on DIVider
        string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command
        if ( command == "armorcurrent" ) { // ARMORCURRENT|integer newcurrentarmor
            integer rating = llList2Integer(fields,1);
            if ( rating >= 0 && rating <= MAXARMOR ) {
                CURARMOR = rating;
            }
            return;
        }
        if ( command == "hit") {
            integer attdice = llList2Integer(fields,1);
            if ( attdice >= 1 && attdice <= 5 ) {
                HIT(attdice);
            }
            return;
        }
        if ( llGetSubString(command,0,4) == "armor" ) { SENDTOATTACHMENT(str); return; } // process armor messages
        if ( sending_module == LM_SENDTOATTACHMENT ) { SENDTOATTACHMENT(str); return; } // send module messages to attachments
        COMMAND(str); // send to shared command processor for chat and link messages
        return;
    }
 
    // LISTEN - the main Myriad Lite message processor for RP events and player commands
    listen(integer channel, string speakername, key speakerid, string message) {
        DEBUG("HUD Listen: channel=["+(string)channel+"] name=["+speakername+"] id=["+(string)speakerid+"] message=["+message+"]");
        speakername = ""; // LSLINT
        // calculate the dynamic channel of who is speaking in case we need to return commands
        CHANOBJECT = (integer)(CHAN_PREFIX+llGetSubString((string)speakerid,0,6));
 
        // break down the commands and messages into units we can work with
        list fields = llParseString2List(message,[DIV],[]); // break into list of fields based on DIVider
        string command = llList2String(fields,0); // assume the first field is a Myriad Lite command
 
        // --- PLAYER COMMAND CHANNEL
        if ( channel == CHANCOMMAND ) { // handle player chat commands
            COMMAND(message); // send to shared command processor for chat and link messages
            return;
        } // end of if channel == player commands
 
        // --- BAM CHANNEL
        if ( channel == CHANBAM ) {
            SENDTOMODULE(message,speakerid); // send BAM to Module
            return;
        } // end if channel BAMCHAN
 
        // --- Myriad Lite regionwide messages
        if ( channel == CHANMYRIAD ) { // handle Myriad system messages
            if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting
                string oldname = llGetObjectName(); // save the current object name
                llSetObjectName("Myriad RP Event"); // change the object name to
                llOwnerSay(llList2String(fields,1)); // now tell the owner the rest of the RPEVENT| message
                llSetObjectName(oldname); // restore the HUD back to its original name
                return;
            } // end if RPEVENT
            return;
        } // end if channel == CHANMYRIAD
 
        // --- ATTACHMENT CHANNEL
        if ( channel == CHANATTACH ) { // handle the attachment commands
            if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // can't mess with attachments while down
            if ( llToLower(llGetSubString(llStringTrim(command,STRING_TRIM),0,4)) == "armor" ) { SENDTOMODULE(message,PLAYERID); return; } // process armor messages
            if ( command == "ATTACHMELEE" || command == "ATTACHRANGED" ) { // holding a weapon rather than using fists?
                llMessageLinked(LINK_THIS,MODULE_HUD,message,PLAYERID);
                return;
            }
            if ( command == "DETACHMELEE" || command == "DETACHRANGED" ) { // are we going back to fists?
                llMessageLinked(LINK_THIS,MODULE_HUD,message,PLAYERID);            
                return;
            }
            if ( command == "ATTACHMETER" ) {
                METERWORN = TRUE; // we need to send meter events
                METER(); // send update
                return;
            }
            if ( command == "DETACHMETER" ) {
                METERWORN = FALSE;
                return;
            }
        }
        // --- CHANPLAYER
        if ( channel == CHANPLAYER ) { // handle player dynamic commands
            if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting
                string oldname = llGetObjectName(); // save the current object name
                llSetObjectName("Myriad RP Event (Private)"); // change the object name to
                llOwnerSay(llList2String(fields,1)); // now tell the owner the rest of the RPEVENT| message
                llSetObjectName(oldname); // restore the HUD back to its original name
                return;
            } // end if RPEVENT
            // incoming message from rumor server?
            if ( llGetSubString(llToLower(llStringTrim(command,STRING_TRIM)),0,4) == "rumor" ) {
                llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid); // send message and key of speaker to rumors
                return;
            }
            if ( command == "UNOPPOSED_CHECK" ) { // object in sim wants a simple skill check
                integer targetnum = llList2Integer(fields,1); // what is unopposed check target num?
                integer tattrib = llList2Integer(fields,2); // target attribute
                integer tskill = llList2Integer(fields,3); // target skill
                UNOPPOSED_TEST(targetnum,tattrib,tskill);
                return;
            }
            // we've been hit and have to make an opposed ability test to avoid it
            if ( command == "CLOSEHIT" ) {
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());
                return;
            }
            if ( command == "RANGEDHIT" ) { // mortal combat attack message?
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());
                return;
            }
            // Heal Some Damage
            if ( command == "HEALPARTIAL" ) { // only a partial heal
                integer boxeshealed = llList2Integer(fields,1); // how many boxes are we healing?
                HEAL(boxeshealed); // heal X number of boxes
                METER(); // update
                return;
            }
            if ( command == "HEALFULL" ) { // full heal, reset state
                HEAL(100); // heal up to 100 damage
                METER(); // update
                return;
            }
            // Actions NOT Allowed When Dead/Incapacitated go below here
            if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return;
            // If Your Bullet has hit, fire a hitcheck regionwide at targetplayer's channel
            if ( command == "CLOSECOMBAT" ) {
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());
                return;
            }
            if ( command == "RANGEDCOMBAT" || command == "TOHIT" ) {
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());
                return;
            }
        } // end if channel CHANPLAYER
    } // end listen
} // end state running
// END
Personal tools
General
About This Wiki