// 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