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

Myriad Lite

// Myriad_Lite-v0.1.14-20131014.lsl
// Copyright (c) 2013 by Allen Kerensky (OSG/SL) All Rights Reserved.
// This work is dual-licensed under
// Creative Commons Attribution (CC BY) 3.0 Unported
// http://creativecommons.org/licenses/by/3.0/
// - or -
// Modified BSD License (3-clause)
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, 
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Myriad Lite nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
// NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The Myriad RPG System was designed, written, and illustrated by Ashok Desai
// Myriad RPG System licensed under:
// Creative Commons Attribution (CC BY) 2.0 UK: England and Wales
// http://creativecommons.org/licenses/by/2.0/uk/
 
// CONSTANTS - DO NOT CHANGE DURING RUN
string BASENAME = "Myriad Lite"; // base name of this script without version or date
string VERSION = "0.1.14"; // Allen Kerensky's script version
string VERSIONDATE = "20131014"; // Allen Kerensky's script yyyymmdd
 
// Module to Module Messaging Constants
integer MODULE_HUD = -1;
integer LM_SENDTOATTACHMENT = 0x80000000;
integer RENDEZVOUS1 = -999; // OLD "region broadcast" channel - chat sent to ALL Myriad players in region
integer RENDEZVOUS2; // NEW "region broadcast" channel
 
// Configuration Items loaded in SETUP()
integer CHANCOMMAND; // chat sent by player to their meter? see OOMA CONFIG,CHANCOMMAND,<number> default 5
integer CHANNEL_OOCCHAT; // what channel for OOC chat? see PPMA CONFIG,CHANOOCCHAT,<number> default 22
integer CHANNEL_OOCEMOTE; // what channel for OOC emote? see PPMA CONFIG,CHANOOCEMOTE,<number> default 23
integer CHANNEL_ICCHAT; // what channel for IC chat? see PPMA CONFIG,CHANICCHAT,<number> default 44
integer CHANNEL_ICTHINK; // what channel for IC thinking out loud? see PPMA CONFIG,CHANICTHINK,<number> default 45
integer CHANNEL_ICEMOTE; // what channel for IC emotes? see PPMA CONFIG,CHANICEMOTE,<number> default 66
integer CHANNEL_NARRATE; // what channel for narrations? see PPMA CONFIG,CHANNARRATE,<number> default 88
string OOCPREFIX; // what to put before OOC messages? see PPMA CONFIG,OOCPREFIX,<string> default ((
string OOCSUFFIX; // what to put after OOC messages? see PPMA CONFIG,OOCSUFFIX,<string> default ))
// FIXME MOVE TO RANGED COMBAT
integer ALLOW_PHYS_BULLETS; // collision or Myriad Bullets? FALSE = Myriad Skill-based bullets. see PPMA CONFIG,PHYSBULLET,<true|false>
 
// RUNTIME GLOBALS - CAN CHANGE DURING RUN
integer CHANPLAYER; // dynamic channel to one player's UUID
integer CHANOBJECT; // dynamic channel to one object's UUID
integer CHANHUD; // dyname channel to HUD object UUID
integer CHANATTACH; // dynamic channel for attachments
integer CHANBAM; // dynamic channel for BAM quests
 
integer HANDRENDV1; // Myriad Rendezvous1 channel handle
integer HANDRENDV2; // Myriad Rendezvous2 channel handle
integer HANDCOMMAND; // command channel handle
integer HANDPLAYER; // player channel handle
integer HANDHUD; // HUD channel handle
integer HANDATTACH; // attachment channel handle
integer HANDBAM; // BAM channel update
 
integer HANDLE_OOCCHAT; // callback handle for llListen
integer HANDLE_OOCEMOTE; // callback handle for llListen
integer HANDLE_ICCHAT; // callback handle for llListen
integer HANDLE_ICTHINK; // callback handle for llListen
integer HANDLE_ICEMOTE; // callback handle for llListen
integer HANDLE_NARRATE; // callback handle for llListen
string TRUENAME; // name to reset item to after talker/emoter use
 
// Texture menu handling
// float bottom left X, bottom left y, top right X, top right Y, command
// coords are float 0.0 - 1.0 for X and Y - command is a string passed to PARSE() function
list TEXMENU;
 
//
// PPMA GET_VAL
//
string KEY_NOT_FOUND = "[KEY_NOT_FOUND]";
string GET_VAL(string dbkey,string field) {
    //OWNERSAY("GET_VAL KEY=["+dbkey+"] FIELD=["+field+"]");
    string out = KEY_NOT_FOUND;
    integer i;
    string name;
    string desc;
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
        desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
        if ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) {
            out = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
            //DEBUG("GET_VAL RETURN=["+out+"]");
            return out; // bail on first match?
        }
    }
    //OWNERSAY("GET_VAL RETURN=["+out+"]");
    return out;
}
 
SET_VAL(string dbkey, string field, string val) {
    //OWNERSAY("SET_VAL KEY=["+dbkey+"] FIELD=["+field+"] VAL=["+val+"]");
    integer i;
    string name;
    string desc;
    integer written = FALSE;
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM);
        desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM);
        if ( ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) && written == FALSE ) {
            //OWNERSAY("SET_VAL UPDATE RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]");
            llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]);
            written = TRUE; // we did an update, remember it
        }    
    }
    if ( written == TRUE ) {
        //OWNERSAY("SET_VAL UPDATE COMPLETE.");
        return;
    }
    // data hasn't been written, scan for free prim
    for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
        name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM);
        desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM);
        if ( ( name == "" && desc == "" ) && written == FALSE ) {
            //OWNERSAY("SET_VAL INSERT RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]");
            llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]);
            written = TRUE; // we did an update, remember it
        }
    }
    if ( written == TRUE ) {
        //OWNERSAY("SET_VAL INSERT COMPLETED.");
        return;
    }
    //OWNERSAY("SET_VAL NO FREE RECORD FOUND TO INSERT INTO! DATA LOST");
}
 
//////////////////////////////////////////////////////////////////////////////
// FLAG FUNCTIONS
// INCAPACITATED - lost wounds
// DEAD - lost critical wounds
// DEBUG - show debug messages or not
// IDEA: INDECISION - lost resolve?
 
// LMIM: SET_FLAG|<name>=<TRUE|FALSE>
// PPMA: FLAG,<flagname>,<TRUE|FALSE>
// LMOUT: FLAG|<flagname>=<TRUE|FALSE>
// LMERR: FIXME
integer GET_FLAG(string flag) {
    string val = GET_VAL("FLAG",llToUpper(flag));
    if ( val == KEY_NOT_FOUND ) {
        ERROR("Flag ["+flag+"] does not exist.");
    }
    if ( val != "0" && val != "1") {
        ERROR("Flag: "+flag+" invalid value ["+val+"]");
    }
    integer bool = (integer)val;
    return bool;
}
 
// SET_FLAG
// LMIM: SET_FLAG|<flagname>=<TRUE|FALSE>
// PPMA: FLAG,<flagname>,<TRUE|FALSE>
// LMOUT: SET_FLAG|<flagname>=<TRUE|FALSE>
// LMERR: FIXME
SET_FLAG(string flag,integer value) {
    if ( flag == "" ) return; // FIXME add error
    if ( value != TRUE && value != FALSE ) return; // FIXME add err
    SET_VAL("FLAG",llToUpper(flag),(string)value);
}
 
//
// PPMA GET_CONFIG
//
string GET_CONFIG(string config) {
    config = llToUpper(config);
    string out = GET_VAL("CONFIG",config);
    if ( out == KEY_NOT_FOUND ) {
        if ( config == "CHANCOMMAND" ) { out = "5"; SET_VAL("CONFIG",config,out);}
        if ( config == "CHANOOCCHAT" ) { out = "22"; SET_VAL("CONFIG",config,out);}
        if ( config == "CHANOOCEMOTE" ) { out = "23"; SET_VAL("CONFIG",config,out);}
        if ( config == "CHANICCHAT" ) { out = "44"; SET_VAL("CONFIG",config,out);}
        if ( config == "CHANICTHINK" ) { out = "45"; SET_VAL("CONFIG",config,out);}
        if ( config == "CHANICEMOTE" ) { out = "66"; SET_VAL("CONFIG",config,out);}
        if ( config == "CHANNARRATE" ) { out = "88"; SET_VAL("CONFIG",config,out);}
        if ( config == "OOCPREFIX" ) { out = "(("; SET_VAL("CONFIG",config,out);}
        if ( config == "OOCSUFFIX" ) { out = "))"; SET_VAL("CONFIG",config,out);}
        if ( config == "PHYSBULLETS" ) { out = "0"; SET_VAL("CONFIG",config,out);}
    }
    return out;
}
 
//
// PPMA GET_NAME - returns player firstname or nickname for Talker/Emoter
//
string GET_NAME() {
    string defaultname = "Myriad Player";
    string name = GET_VAL("CHARACTER","NAME");
    list tokens = llParseString2List(name,[" ",".","_"],[]);
    string firstname = llList2String(tokens,0);
    string lastname = llList2String(tokens,1);
    string nickname = GET_VAL("CHARACTER","NICKNAME");
    if ( name == KEY_NOT_FOUND && nickname == KEY_NOT_FOUND ) return defaultname;
    if ( name != KEY_NOT_FOUND && nickname == KEY_NOT_FOUND ) return name;
    if ( name == KEY_NOT_FOUND && nickname != KEY_NOT_FOUND ) return nickname;
    if ( name != KEY_NOT_FOUND && nickname != KEY_NOT_FOUND ) return firstname+" \""+nickname+"\" "+lastname;
    return defaultname;
}
 
// 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 modifiers) {
    integer highroll = 0; // clear out the highest roll
    while( attribute-- ) { // roll a dice for each point of the attribute
        integer roll = 1+(integer)llFrand(6.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 + modifiers; // now, return the total of highest dice roll + skill value + modifiers
}
 
// CHECK AMMO
CHECKAMMO() {
    llWhisper(CHANATTACH,"CHECKAMMO");
}
 
// 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,key id) {
    // break down the commands and messages into units we can work with
    list fields = llParseString2List(msg,["|"," "],[]); // break into list of fields based on "|"ider
    string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command
 
    if ( command == "debug" || command == "error" || command == "help" || command == "ownersay" || command == "rpevent" ) return; // ignore WELL commands FIXME duped from link messages - cleanup entire chat/lm message routing
 
    if ( command == "checkammo" ) { CHECKAMMO(); return;} // ATTACH_FIREARM check ammo in weapons
    if ( command == "combatoff") { COMBATOFF(); return; } // MODULE_SKILL_CLOSE turn off fist fighter
    if ( command == "combaton" ) { COMBATON(); return; } // MODULE_SKILL_CLOSE turn on the fist fighter
    if ( command == "credits" ) { CREDITS(); return;} // ALL MODULES show the credits including version number
    if ( command == "debugoff" ) { DEBUGOFF(); return; } // ALL MODULES player turn off debugging
    if ( command == "debugon" ) { DEBUGON(); return;} // ALL MODULES player turn on debugging
    if ( command == "deceive" ) { DECEIVE(msg); return;} // MODULE_SOCIAL player starts DECEIT-based social attack
    if ( command == "drawboth" ) { DRAW("both"); return; } // ATTACH_FIREARM ATTACH_MELEE draw both weapons
    if ( command == "drawleft" ) { DRAW("left"); return; } // ATTACH_FIREARM ATTACH_MELEE draw weapon in left hand
    if ( command == "drawright" ) { DRAW("right"); return; } // ATTACH_FIREARM ATTACH_MELEE draw weapon using right hand
    if ( command == "dump_records" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET
    if ( command == "format_db" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET
    if ( command == "holsterboth" ) { HOLSTER("both"); return; } // ATTACH_HOLSTER holster both weapons
    if ( command == "holsterleft" ) { HOLSTER("left"); return; } // ATTACH_HOLSTER holster weapon in left hand
    if ( command == "holsterright" ) { HOLSTER("right"); return; } // ATTACH_HOLSTER holster weapon in right hand
    if ( command == "memory" ) { GET_MEMORY(); return;} // return script memory information
    if ( command == "persuade" ) { PERSUADE(msg); return;} // MODULE_SOCIAL social attack persuasion
    if ( command == "quest" ) { QUEST(); return; } // MODULE_BAM check our current quest status
    if ( command == "reload" ) { RELOAD(); return;}  // ATTACH_FIREARM reload weapons
    if ( command == "reset" ) { RESET(); return;} // ALL MODULES reset HUD and all modules
    if ( command == "safetyoff" ) { SAFETYOFF(); return;} // ATTACH_FIREARM unsafe the weapons
    if ( command == "safetyon" ) { SAFETYON(); return;} // ATTACH_FIREARM safe the weapons
    if ( command == "setup_hud" ) { SETUP_HUD(llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)),id); return; }
    if ( command == "sheatheboth" ) { SHEATHE("both"); return; } // ATTACH_HOLSTER sheathe both weapons
    if ( command == "sheatheleft" ) { SHEATHE("left"); return; } // ATTACH_HOLSTER sheathe weapon in left hand
    if ( command == "sheatheright" ) { SHEATHE("right"); return; } // ATTACH_HOLSTER sheathe weapon in right hand
    if ( command == "socialoff" ) { SOCIALOFF(); return;} // MODULE_SOCIAL
    if ( command == "socialon" ) { SOCIALON(); return;} // MODULE_SOCIAL
    if ( command == "version" ) { GET_VERSION(); return;} // ALL MODULES show the version internals
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "armor" ) { SENDTOMODULE(msg,id); return; } // MODULE_ARMOR
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "get_" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET GET_*
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "list_" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET LIST_*
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "roll" ) { ROLL(msg); return; } // roll dice
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "rlv_" ) { SENDTOMODULE(msg,id); return; } // MODULE_RLV
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "rumor" ) { RUMOR(msg); return; } // MODULE_RUMOR
    if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "set_" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET SET_*
}
 
// CREDITS comply with Myriad RPG Creative Common-Attribution legal requirement
CREDITS() {
    OWNERSAY("The Myriad RPG System was designed, written, and illustrated by Ashok Desai.");
    OWNERSAY("RPG System licensed under the Creative Commons Attribution 2.0 UK: England and Wales.");
    OWNERSAY("Myriad Lite v"+VERSION+" "+VERSIONDATE+" Copyright (c) 2011-2014 by Allen Kerensky (OSG/SL)");
    OWNERSAY("Licensed under Creative Commons Attribution (CC-BY) 3.0 Unported and BSD 3-clause.");
    // llMessageLinked(LINK_THIS,MODULE_HUD,"VERSION",llGetOwner());
}
 
// DEBUG - show debug chat with wearer name for sorting
DEBUG(string dmessage) {
    if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_HUD,"DEBUG|"+dmessage,llGetOwner());
}
 
// DEBUGOFF - turn off the DEBUG flag
DEBUGOFF() {
    SET_FLAG("DEBUG",FALSE); // set debug flag to FALSE
    OWNERSAY("Debug Mode Deactivated");
}
 
// DEBUGON - turn on the DEBUG flag
DEBUGON() {
    SET_FLAG("DEBUG",TRUE); // set debug flag TRUE
    OWNERSAY("Debug Mode Activated");
}
 
// DECEIT SOCIAL COMBAT SKILL
DECEIVE(string msg) {
    string message = llDumpList2String( llList2List( llParseString2List(msg,[" "],[]) , 1, -1 ) , " " );
    if ( llStringLength( message ) > 3 && message != "deceive" ) {
        llSetObjectName(GET_NAME());
        llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\"");
        llSetObjectName(TRUENAME);
    }
 
    // FIXME - if in mouselook and cursor pointing at 1 avatar, make TARGETED SOCIAL ATTACK else make AREA SOCIAL ATTACK
    // FIXME if in mouselook, focus the attack on that one person, with a bonus Gotta look them in the eye when you're lying LOL
    llMessageLinked(LINK_THIS,MODULE_HUD,"DECEIVE",llGetOwner());
}
 
// 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
}
 
// ERROR - show errors on debug channel with wearer name for sorting
ERROR(string emessage) {
    llMessageLinked(LINK_THIS,MODULE_HUD,"ERROR|"+emessage,llGetOwner());
}
 
// MEMORY
GET_MEMORY() {
    OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory()); // show this module's free memory info
    llMessageLinked(LINK_THIS,MODULE_HUD,"MEMORY",llGetOwner()); // trigger the rest of the modules too
}
 
// GETVERSION
GET_VERSION() {
    OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE); // show this module's version info
    llMessageLinked(LINK_THIS,MODULE_HUD,"VERSION",llGetOwner()); // trigger the rest of the modules too
}
 
// HELP
HELP(string help) {
    llMessageLinked(LINK_THIS,MODULE_HUD,"HELP|"+help,llGetOwner());
}
 
// 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
}
 
// METER - update a hovertext health meter or HUD bar graph
METER() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"METER",llGetOwner());
}
 
// OWNERSAY - sent messages to player only
OWNERSAY(string message) {
    llMessageLinked(LINK_THIS,MODULE_HUD,"OWNERSAY|"+message,llGetOwner());
}
 
// PERSUASION SOCIAL COMBAT SKILL
PERSUADE(string msg) {
    string message = llDumpList2String( llList2List( llParseString2List(msg,[" "],[]) , 1, -1 ) , " " );
    if ( llStringLength( message ) > 3 && message != "persuade" ) {
        llSetObjectName(GET_NAME());
        llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\"");
        llSetObjectName(TRUENAME);
    }
 
    // FIXME - if in mouselook and cursor pointing at 1 avatar, make TARGETED SOCIAL ATTACK else make AREA SOCIAL ATTACK
    // FIXME if in mouselook, focus the attack on that one person, with a bonus Gotta look them in the eye when you're lying LOL
    llMessageLinked(LINK_THIS,MODULE_HUD,"PERSUADE",llGetOwner());
}
 
// QUEST STATUS
QUEST() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"BAMSTATUS",llGetOwner()); // send a status request to BAM Modules
}
 
// RELOAD
RELOAD() {
    llWhisper(CHANATTACH,"RELOAD");
}
 
// RESET - shut down running animations then reset the script to reload character sheet
RESET() {
    if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) { // don't allow reset if already on respawn timer
        OWNERSAY("Cannot reset while incapacitated or dead. You will respawn in a few moments.");
        return;
    }
    OWNERSAY("Resetting Myriad Lite. Please wait...");
    llMessageLinked(LINK_THIS,MODULE_HUD,"RESET",llGetOwner()); // send reset to modules
    llResetScript(); // now reset this script
}
 
// ROLL DICE
ROLL(string message) {
    list input = llParseString2List(message, [" ","d"],[] );
    string command = llList2String(input,0);
    integer numberOfDice = llList2Integer(input,1);
    integer numberOfSides = llList2Integer(input,2);
    string output = "";
    integer total = 0;
    if ( command == "roll" ) {
        if ( numberOfDice >= 1 && numberOfDice <= 20 ) {
            if ( numberOfSides > 1 && numberOfSides <= 100 ) {
                integer index;
                for( index = 1; index <= numberOfDice; index++) {
                    integer roll = (integer)((llFrand(1.0) * numberOfSides)+1);
                    output += (string)roll;
                    output += ", ";
                    total += roll;                          
                }
                string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names
                llSetObjectName("⚅ "+ownername);
                llWhisper(PUBLIC_CHANNEL,"/me rolls "+(string)numberOfDice+"d"+(string)numberOfSides+" resulting in "+output+" totalling "+(string)total+".");
                llSetObjectName(TRUENAME);
                return;
            }
        }
        HELP("To roll, say /5 roll #d# or /5 roll #d#. For example, saying /5 roll 1d20 in chat rolls one 20-sided dice.\"");
    }
}
 
// RPEVENT
//RPEVENT(string rpevent) {
//    llMessageLinked(LINK_THIS,MODULE_HUD,"RPEVENT|"+rpevent,llGetOwner());
//}
 
//
// RPEVENTIN - handle incoming RPevents here to prevent link message loops
//
string LAST_RP_MSG;
RPEVENTIN(string rpevent) {
    if ( rpevent == LAST_RP_MSG ) return; // ignore duplicates
    LAST_RP_MSG = rpevent; // remember this one
    string objname = llGetObjectName();
    llSetObjectName("★");    
    llOwnerSay("/me "+rpevent); // now tell the owner the rest of the RPEVENT| message
    llSetObjectName(objname);    
}
 
// RUMOR CONTROL
RUMOR(string cmdrumor) {
    DEBUG("Sending to rumor module: "+cmdrumor);
    llMessageLinked(LINK_THIS,MODULE_HUD,cmdrumor,llGetOwner()); // relay rumor commands to module
}
 
// SAFETY OFF
SAFETYOFF() {
    llWhisper(CHANATTACH, "SAFETYOFF");
}
 
// SAFETY ON
SAFETYON() {
    llWhisper(CHANATTACH, "SAFETYON");
}
 
// SENDTOATTACHMENT
SENDTOATTACHMENT(string msg) {
    DEBUG("SENDTOATTACHMENT("+msg+")");
    llWhisper(CHANATTACH,msg);
}
 
// SENDTOMODULE
SENDTOMODULE(string msg,key speaker) {
    if ( llGetSubString(llStringTrim(llToLower(msg),STRING_TRIM),0,3) == "set_" ) { // is this a privileged SET_* command?
        if ( llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0) != speaker ) { // if so, check speaker is region owner or object owned by region owner
            ERROR("Ignoring request ["+msg+"] from unprivileged speaker ["+llKey2Name(llGetOwnerKey(speaker))+"]");
            return;
        }
    }
    // allow safer GET_* and LIST_* commands to pass through
    llMessageLinked(LINK_THIS,MODULE_HUD,msg,speaker);
}
 
// SETUP - begin bringing the HUD online
SETUP() {
    SET_FLAG("DEBUG",FALSE);
    CREDITS(); // show Myriad credits as required by the Creative Commons - Attribution license
    llSetText("",<0,0,0>,0); // clear any previous hovertext
 
    // Configuration Items
    CHANCOMMAND = (integer)GET_CONFIG("CHANCOMMAND");
    CHANNEL_OOCCHAT = (integer)GET_CONFIG("CHANOOCCHAT");
    CHANNEL_OOCEMOTE = (integer)GET_CONFIG("CHANOOCEMOTE");
    CHANNEL_ICCHAT = (integer)GET_CONFIG("CHANICCHAT");
    CHANNEL_ICTHINK = (integer)GET_CONFIG("CHANICTHINK");
    CHANNEL_ICEMOTE = (integer)GET_CONFIG("CHANICEMOTE");
    CHANNEL_NARRATE = (integer)GET_CONFIG("CHANNARRATE");    
    OOCPREFIX = GET_CONFIG("OOCPREFIX");
    OOCSUFFIX = GET_CONFIG("OOCSUFFIX");
    ALLOW_PHYS_BULLETS = (integer)GET_CONFIG("PHYSBULLETS");    
 
    // Talker/Emoter setup
    TRUENAME = BASENAME + " " + VERSION + " " + VERSIONDATE; // put together full item name for talker/emoter
    llSetObjectName(TRUENAME); // force object title back if the talker/emoter messed it up
    //OWNERSAY("Character Sheet loaded. You are now ready to roleplay.");
    if ( HANDRENDV1 != 0 ) llListenRemove(HANDRENDV1);
    HANDRENDV1 = llListen(RENDEZVOUS1,"",NULL_KEY,""); // setup listener for Myriad RP events
    if ( HANDRENDV2 != 0 ) llListenRemove(HANDRENDV2);
    list details = llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_ID]);
    string parcelid = llList2String(details,0);
    RENDEZVOUS2 = (integer)("0x"+llGetSubString(parcelid,0,7));    
    HANDRENDV2 = llListen(RENDEZVOUS2,"",NULL_KEY,"");
    if ( HANDCOMMAND != 0 ) llListenRemove(HANDCOMMAND);
    HANDCOMMAND = llListen(CHANCOMMAND,"",llGetOwner(),""); // listen to chat commands from owner
    CHANHUD = (integer)("0x"+llGetSubString((string)llGetKey(),0,6)); // calculate a player-specfic dynamic chat channel
    if ( HANDHUD != 0 ) llListenRemove(HANDHUD);
    HANDHUD = llListen(CHANHUD,"",NULL_KEY,""); // listen on the HUD object dynamic chat channel
    CHANPLAYER = (integer)("0x"+llGetSubString((string)llGetOwner(),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)llGetOwner(),1,7)); // attachment-specific channel
    if ( HANDATTACH != 0 ) llListenRemove(HANDATTACH);
    HANDATTACH = llListen(CHANATTACH,"",NULL_KEY,""); // listen for messages from attachments
    CHANBAM = (integer)("0x" + llGetSubString((string)llGetOwner(),-7,-1));
    if ( HANDBAM != 0 ) llListenRemove(HANDBAM);
    HANDBAM = llListen(CHANBAM,"",NULL_KEY,""); // start listener with listenremove handle
 
    // Talker/Emoter
    if ( HANDLE_OOCCHAT != 0 ) llListenRemove(HANDLE_OOCCHAT);
    HANDLE_OOCCHAT = llListen(CHANNEL_OOCCHAT,"",llGetOwner(),"");
    if ( HANDLE_OOCEMOTE != 0 ) llListenRemove(HANDLE_OOCEMOTE);
    HANDLE_OOCEMOTE = llListen(CHANNEL_OOCEMOTE,"",llGetOwner(),"");
    if ( HANDLE_ICCHAT != 0 ) llListenRemove(HANDLE_ICCHAT);
    HANDLE_ICCHAT  = llListen(CHANNEL_ICCHAT,"",llGetOwner(),"");
    if ( HANDLE_ICTHINK != 0 ) llListenRemove(HANDLE_ICTHINK);
    HANDLE_ICTHINK  = llListen(CHANNEL_ICTHINK,"",llGetOwner(),"");
    if ( HANDLE_ICEMOTE != 0 ) llListenRemove(HANDLE_ICEMOTE);
    HANDLE_ICEMOTE = llListen(CHANNEL_ICEMOTE,"",llGetOwner(),"");
    if ( HANDLE_NARRATE != 0 ) llListenRemove(HANDLE_NARRATE);
    HANDLE_NARRATE = llListen(CHANNEL_NARRATE,"",llGetOwner(),"");
 
    OWNERSAY("Registering any Myriad Lite-compatible attachments...");
    llWhisper(CHANATTACH,"REGISTERATTACHMENTS"); // ask for attachments on their dynamic channel
    METER(); // update hovertext
    QUEST(); // update the BAM Module
    OWNERSAY("Core module active.");
}
 
SETUP_HUD(string texmenu,key id) {
    if ( llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0) != id ) { // if so, check speaker is region owner or object owned by region owner
        ERROR("Ignoring SETUP_HUD command ["+texmenu+"] from unprivileged speaker ["+llKey2Name(llGetOwnerKey(id))+"]");
        return;
    }
    list tokens = llCSV2List(texmenu); // convert CSV in fromtexturename, and coord/command lists to list
    key texid = llGetInventoryKey(llList2String(tokens,0)); // first item is texture to show
    if ( texid != NULL_KEY ) {
        llSetLinkPrimitiveParamsFast(1,[PRIM_TEXTURE,ALL_SIDES,texid,<1,1,0>,<0,0,0>,0.0]);
        // FIXME make sure the BLX,BLY,TRX,TRY,COMMAND format is held to
        TEXMENU = llList2List(tokens,1,-1); // put the rest in to menu list
    }
}
 
// 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
}
 
// SOCIALOFF - turn off social combat module
SOCIALOFF() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"SOCIALOFF",llGetOwner());
}
 
// SOCIALON - turn on social combat module
SOCIALON() {
    llMessageLinked(LINK_THIS,MODULE_HUD,"SOCIALON",llGetOwner());
}
 
// FIXME MOVE TO GENERIC SKILL MODULE?
// An Unopposed Ability Test - Myriad PDF p. 19, Myriad Special Edition p. 25
// Requires TargetNumber, Stat Name, Skill Name, one or more things to do on success, one or more things to do on fail
// UNOPPOSED_TEST|<target number>|<stat name>|<skill name>|<CSV success actions>|<CSV fail actions>
// CSV success and fail actions are CSV lists action1[,action2[,...]]
// Each action is an embedded link message which uses ^ in place of the usual | used to separate fields
// Example Actions: SAY^<channel#>^<message_with_no_commas> , SHOUT^<channel#>,<message_with_no_commas>
// See the WELL module for a preset library of actions you can stack from, but any valid link message is possible
// FIXME This is a huge cheat and security hole as is without testing for valid actions by players vs. region owners
// Returns TRUE for Success and False for Fail
string valid = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.?!,^";
integer UNOPPOSED_TEST1(integer targetnum,string statname, string skillname,string onsuccess,string onfail) {
    DEBUG("CORE UNOPPOSED_TEST targetnum=["+(string)targetnum+"] statname=["+statname+"] skillname=["+skillname+"] onsuccess=["+onsuccess+"] onfail=["+onfail+"]");
    // FIXME check targetnum - replace constants with MIN_TNUM/MAX_TNUM
    if ( targetnum < 4 || targetnum > 17 ) { ERROR("Invalid target number "+(string)targetnum+" in unopposed test. Ending unopposed test."); return FALSE; }
    // FIXME check stat range - replace constants with MIN_STAT/MAX_STAT
    integer tstat = (integer)GET_VAL("STATISTIC",statname);
    if ( tstat < 0 || tstat > 10 ) { ERROR("Invalid stat amount "+(string)tstat+" in unopposed test. Ending unopposed test."); return FALSE;}
    // FIXME check skill range - replace constants with MIN_SKILL/MAX_SKILL
    integer tskill = (integer)GET_VAL("SKILL",skillname);
    if ( tskill < 0 || tskill > 7 ) { ERROR("Invalid skill amount "+(string)tskill+" in unopposed test. Ending unopposed test."); return FALSE;}
    // FIXME modifiers
    integer tmods = 0;
    integer check = ABILITY_TEST(tstat,tskill,tmods); // calculate the player's ability test value (stat, skill, modifiers
    integer result;
    string acts;
    if ( check >= targetnum ) {
        acts = onsuccess;
        result = TRUE;
    } else {  // player lost the ability test
        acts = onfail;
        result = FALSE;
    }
    // Do onsuccess or onfail actions
    // ugly string filter and conversion of ^ to | for passing to link message
    list actions = llCSV2List(acts); // split onsuccess or onfail into list of actions separated by commas
    integer count = llGetListLength(actions); // how many actions in list
    integer i; // temp variable for action by action loop
    for ( i = 0; i < count; i++ ) { // step through each action in comma list of actions
        string well = llList2String(actions,i); // get this specific action
        string out = ""; // start a new empty output string
        integer len = llStringLength(well); // get the length of this action string
        integer j; // temp variable for character by character loop
        for ( j = 0; j < len; j++) { // step through each character of current action
            string char = llGetSubString(well,j,j); // get the current character
            if ( char != "^" && llSubStringIndex(valid,char) >= 0 ) { // if the character is not a ^ and is in the list of VALID string characters
                out = out + char; // add it to the current output string
            } else { // otherwise its invalid
                out = out + " "; // and add a placeholder space instead
            } // done with valid character scan, excepting the ^
            if ( char == "^" ) out = out + "|"; // replace the ^ with a | divider used in link messages to WELL
        } // done with all actions in this list
        llMessageLinked(LINK_THIS,MODULE_HUD,out,llGetOwner()); // fire the resulting cleaned/converted action over to WELL or other hud module to act upon
        // and go on  to next action in list if any
    } // all done with actions
    return result;
}
 
UNOPPOSED_TEST2(key id,string task, string stat,string skill) {
    integer chan = (integer)("0x"+llGetSubString(id,0,6)); // calculate dynamic channel of object that triggered this test
    OWNERSAY("Unopposed Ability Test: "+task); // tell the player the task we're starting
    integer statamount = (integer)GET_VAL("STATISTIC",stat);
    if ( statamount == 0 ) return; // no such stat, can't really test or succeed can we
    if ( statamount < 0 || statamount > 10 ) return; // bogus info returned - FIXME MINSTAT/MAXSTAT
    integer skillamount = (integer)GET_VAL("SKILL",skill);
    // note skillamount = 0 is still a valid return - unskilled tests just have harder time to succeed, based on raw stat only
    if ( skillamount < 0 || skillamount > 7 ) return; // bogus info returned - FIXME MINSKILL/MAXSKILL
    integer modifiers = 0; // FIXME calculate applicable modifiers
    llRegionSay(chan,"UNOPPOSED_TEST2|"+(string)statamount+"|"+(string)skillamount+"|"+(string)modifiers);
}
 
// DEFAULT STATE - load character sheet
default {
 
    // ATTACH - logged in with meter or worn from inventory/ground while running
    attach(key id) {
        if ( id != NULL_KEY ) {
            RESET();
        }
    }
 
    // COLLISION_START -- FIXME move to Ranged COmbat module?
    collision_start(integer num_detected) {
        if ( ALLOW_PHYS_BULLETS == FALSE ) { return;} // skip phys bullet code
        if ( GET_FLAG("DEAD") == TRUE ) { return; } // can't take more damage when dead, but you can when incapacitated
        while ( num_detected-- ) { // loop through all hits this server frame
            float impactspeed = llVecMag(llDetectedVel(num_detected)); 
            if ( impactspeed > 15.0 && impactspeed <= 255.0 ) { // hit by something moving between 15.0 and 255.0 m/s
                integer impact = llRound( (impactspeed - 15.0) / 5.0 ) + 1; // calculate 1-5 range bands of damage from 0.0 to 240.0 speed
                // RANGEDCOMBAT|attackdice (1-5)|hitwho (my UUID here)|hitbywho (shooter UUID)|hitbywhat (objectname)
                string impactmsg = "RANGEDCOMBAT"+"|"+(string)impact+"|"+(string)llGetOwner()+"|"+(string)llGetOwnerKey(llDetectedKey(num_detected))+"|"+llDetectedName(num_detected);
                llMessageLinked(LINK_THIS,MODULE_HUD,impactmsg,llDetectedKey(num_detected));
            }
        }
    }
 
    // 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?
            //OWNERSAY("Inventory changed. Reloading character sheet."); // FIXME do we still need this?
            RESET(); // saved a new character sheet? - reset and re-read it.
        }
    }
 
    // 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
 
                list fields = llParseString2List(str,["|"],[]); // break into list of fields based on "|"ider
        string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command
        if ( command == "debug" || command == "error" || command == "help" || command == "ownersay" || command == "rpevent" ) return; // ignore WELL commands - region RPevents come in through LISTEN on RENDEZVOUS1
 
        // show debug only when not ignoring other commands
        DEBUG("EVENT: link_message("+(string)sender+","+(string)sending_module+","+str+","+(string)id+")");
 
        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,id); // 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+"]");
        // calculate the dynamic channel of who is speaking in case we need to return commands
        CHANOBJECT = (integer)("0x"+llGetSubString((string)speakerid,0,6));
 
        // break down the commands and messages into units we can work with
        list fields = llParseString2List(message,["|"],[]); // break into list of fields based on "|"ider
        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,speakerid); // 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 == RENDEZVOUS1 || channel == RENDEZVOUS2 ) { // handle Myriad system messages    
            if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting
                RPEVENTIN(llList2String(fields,1));
                return;
            } // end if RPEVENT
            return;
        } // end if channel == RENDEZVOUSx
 
        // --- ATTACHMENT CHANNEL
        if ( channel == CHANATTACH ) { // handle the attachment commands
            if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) return; // can't mess with attachments while down
            if ( llToLower(llGetSubString(llStringTrim(command,STRING_TRIM),0,4)) == "armor" ) { SENDTOMODULE(message,llGetOwner()); return; } // process armor messages
            if ( command == "ATTACHMELEE" || command == "ATTACHRANGED" ) { // holding a weapon rather than using fists?
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());
                return;
            }
            if ( command == "DETACHMELEE" || command == "DETACHRANGED" ) { // are we going back to fists?
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());            
                return;
            }
            if ( command == "ATTACHMETER" || command == "DETACHMETER" ) {
                llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner());
                //METERWORN = TRUE; // we need to send meter events
                //METER(); // send update
                return;
            }
        }
        // --- CHANHUD - get region settings messages
        if ( channel == CHANHUD ) {
            llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid);
            return; // done with the HUD dynamic channel message processing, return            
        }
        // --- CHANPLAYER
        if ( channel == CHANPLAYER ) { // handle player dynamic commands
            if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting
                RPEVENTIN(llList2String(fields,1));
                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_TEST1" ) { // object in sim wants a simple skill check
                integer targetnum = llList2Integer(fields,1); // what is unopposed check target num?
                string tattrib = llList2String(fields,2); // target attribute
                string tskill = llList2String(fields,3); // target skill
                string onsuccess = llList2String(fields,4); // what to do on success
                string onfail = llList2String(fields,5); // what to do on fail
                UNOPPOSED_TEST1(targetnum,tattrib,tskill,onsuccess,onfail);
                return;
            }
            if ( command == "UNOPPOSED_TEST2" ) {
                string testtask = llList2String(fields,1); // target attribute
                string teststat = llList2String(fields,2); // target skill
                string testskill = llList2String(fields,3); // what to do on success
                UNOPPOSED_TEST2(speakerid,testtask,teststat,testskill);                
                return;
            }
            // Check social combat before physical - can still talk your way out of trouble when physically incapacitated
            if ( (command == "DECEIVE" || command == "PERSUADE") &&  GET_FLAG("DEAD") == TRUE ) return; // can't argue when dead, but you can when incapacitated
            if ( (command == "CLOSECOMBAT" || command == "RANGEDCOMBAT") && ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) ) return; // can't fight when dead or down
            // The last action of the PLAYER CHAN processing is to put the message on the link-message bus
            // the speakerid is sent as the uuid field in case the modules need to send a message back out to another dynamic channel object
            // SOCIAL COMBAT: ATTACKER: DECEIVE, PERSUADE - DEFENDER: DECEITATTACK, PERSUASIONATTACK
            llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid); // passthru incoming messages including Myriad Bullets
            return; // done with the player channel message processing, return
        } // end of if channel CHANPLAYER
        // Handle Out-Of-Character speaking
        if ( channel == CHANNEL_OOCCHAT ) {
            string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names
            llSetObjectName(OOCPREFIX+ownername);
            llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\""+OOCSUFFIX);
            llSetObjectName(TRUENAME);
            return;
        }
        // Handle Out-Of-Character emotes
        if ( channel == CHANNEL_OOCEMOTE ) {
            string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names            
            llSetObjectName(OOCPREFIX+ownername);
            llSay(PUBLIC_CHANNEL,"/me "+message+OOCSUFFIX);
            llSetObjectName(TRUENAME);
            return;
        }        
        // Handle In-Character speaking
        if ( channel == CHANNEL_ICCHAT ) {
            llSetObjectName(GET_NAME());
            llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\"");
            llSetObjectName(TRUENAME);
            return;
        }
        // Handle In-Character thinking
        if ( channel == CHANNEL_ICTHINK ) {
            llSetObjectName(GET_NAME());
            llSay(PUBLIC_CHANNEL,"/me thinks, \'"+message+"\'");
            llSetObjectName(TRUENAME);
            return;
        }
        // Handle In-Character emotes
        if ( channel == CHANNEL_ICEMOTE ) {
            llSetObjectName(GET_NAME());
            llSay(PUBLIC_CHANNEL,"/me "+message);
            llSetObjectName(TRUENAME);
            return;
        }
        // Handle Narration
        if ( channel == CHANNEL_NARRATE ) {
            llSetObjectName("❖");
            llSay(PUBLIC_CHANNEL,"/me "+message);
            llSetObjectName(TRUENAME);
            return;
        }
    } // end listen
 
    // 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
    }
 
    // STATE ENTRY
    state_entry() {
        SETUP();
        SETUP_HUD("MyriadLogoWhiteTransparent512,0.0,0.0,1.0,1.0,dump_records",llGetOwner());        
    }
 
    // TOUCH_START - touch HUD for adventure update
    touch_start(integer total_number) {
        integer primnum = llDetectedLinkNumber(0); 
        string action = llGetLinkName(primnum); // get name of prim clicked in link set
        if ( llGetListLength(TEXMENU) > 0 && action == llGetObjectName() ) { // click rootprim, use texture button coords
            vector coord = llDetectedTouchST(0);
            integer i;
            for (i = 0; i < llGetListLength(TEXMENU); i+=5 ) {
                // strided list - X1,Y1,X2,Y2,Command
                float x1 = llList2Float(TEXMENU,i);
                float y1 = llList2Float(TEXMENU,i + 1 );
                float x2 = llList2Float(TEXMENU,i + 2 );
                float y2 = llList2Float(TEXMENU,i + 3 );
                string button = llList2String(TEXMENU,i + 4);
                if ( coord.x >= x1 && coord.x <= x2 && coord.y >= y1 && coord.y <= y2 ) {
                    COMMAND(button,llDetectedKey(0));
                }
            }
        }
        if ( action != "" && action != llGetObjectName() ) { // someone clicked a named button prim on this linkset
            COMMAND(action,llDetectedKey(0)); // try that prim name as a command
            return;
        }
        METER();
        QUEST();
    }
} // end state running
// END
This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies
DokuWiki