Myriad Lite Module WELL

// Myriad_Lite_Module_WELL-v0.0.4-20131025.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 Module WELL";
string VERSION = "0.0.4"; // Allen Kerensky's script version
string VERSIONDATE = "20131025"; // Allen Kerensky's script yyyymmdd
 
// Module to Module Messaging Constants
integer MODULE_WELL = -10;
integer LM_SENDTOATTACHMENT = 0x80000000;
integer RENDEZVOUS1 = -999;
 
string ANIM_INCAPACITATED = "sleep"; // FIXME WELL anim when incapacitated
string ANIM_DEAD = "dead"; // FIXME WELL anim when dead
vector MOVELOCK = <0,0,0>; // FIXME WELL movelock position when incapacitated or dead
float  TAU = 0.05; // FIXME WELL movelock tau
 
// Particle system presets
// name, length of list for name, list of rules data ready to pass to llParticleSystem
list PRESETS = [
    "Ruth2", 39,
        PSYS_PART_FLAGS, 0, // color interp true, glow true, size interp true, followsrc true, followtarget true
        PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_ANGLE_CONE,
        PSYS_SRC_INNERANGLE, PI,
        PSYS_SRC_OUTERANGLE, 0.0,
        PSYS_PART_START_SCALE, <.8,1,1>,
        PSYS_PART_END_SCALE, <0.02, 0.02, 0.02>,
        PSYS_PART_START_ALPHA, 1.0,
        PSYS_PART_END_ALPHA, 0.0,
        PSYS_PART_START_COLOR, <1,1,1>,
        PSYS_PART_END_COLOR, <1,1,1>,
        PSYS_PART_MAX_AGE, 3.0,
        PSYS_SRC_MAX_AGE, 0.0,
        PSYS_SRC_BURST_RATE, 0.020,
        PSYS_SRC_BURST_PART_COUNT, 1,
        PSYS_SRC_BURST_RADIUS, 1.0,
        PSYS_SRC_BURST_SPEED_MAX, 1,
        PSYS_SRC_BURST_SPEED_MIN, .1,
        PSYS_SRC_TEXTURE, "e50ed3cf-6fab-4afe-ac37-187a7d7ab0b8", // ruth cloud particle
        PSYS_SRC_OMEGA, <0,0,0>,
        PSYS_SRC_ACCEL, <0,0,0>
];
 
//////////////////////////////////////////////////////////////////////////////
// PPMA FUNCTIONS
string KEY_NOT_FOUND = "[KEY_NOT_FOUND]";
string GET_VAL(string dbkey,string field) {
    //llOwnerSay("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?
        }
    }
    //llOwnerSay("GET_VAL RETURN=["+out+"]");
    return out;
}
 
// GET_FLAG - DEBUG
// 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(MODULE_WELL,"Flag ["+flag+"] does not exist.");
    }
    if ( val != "0" && val != "1") {
        ERROR(MODULE_WELL,"Flag: "+flag+" invalid value ["+val+"]");
    }
    integer bool = (integer)val;
    //llMessageLinked(LINK_THIS,MODULE_WELL,"FLAG|"+flag+"="+val,llGetOwner()); // FIXME remove?
    return bool;
}
 
//
// 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;
}
 
//
// DEBUG
//
list modules = [ "none","Core","Character Sheet","Armor","BAM","Rumors","Close Combat","Ranged Combat","Resilience","Progress","WELL","Meter","NPC","Notecards","Social Combat","RLV" ];
string LAST_DEBUG_MSG;
integer LAST_DEBUG_MOD;
DEBUG(integer modnum,string debugmsg) {
    if ( GET_FLAG("DEBUG") == TRUE ) {
        if ( modnum == LAST_DEBUG_MOD && debugmsg == LAST_DEBUG_MSG ) return; // suppress duplicate debug messages to cut down on spam
        LAST_DEBUG_MOD = modnum; // remember this one
        LAST_DEBUG_MSG = debugmsg; // remember this one
        modnum *= -1; // convert negative module constants to positive number
        string modname = llList2String(modules,modnum);
        string objname = llGetObjectName();
        llSetObjectName("⚙");
        string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names
        llSay(PUBLIC_CHANNEL,"/me ("+ownername+") Mod "+modname+": "+debugmsg);
        llSetObjectName(objname);
    }
}
 
//
// ERROR
//
integer LAST_ERROR_MOD;
string LAST_ERROR_MSG;
ERROR(integer modnum,string errmsg) {
    if ( modnum == LAST_ERROR_MOD && errmsg == LAST_ERROR_MSG ) return; // suppress duplicate debug messages to cut down on spam
    LAST_ERROR_MOD = modnum; // remember this one
    LAST_ERROR_MSG = errmsg; // remember this one
    modnum *= -1; // convert negative module constants to positive number
    string modname = llList2String(modules,modnum);
    string objname = llGetObjectName();
    llSetObjectName("⚠");
    string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names
    llSay(PUBLIC_CHANNEL,"/me ("+ownername+") Mod "+modname+": "+errmsg);
    llSetObjectName(objname);
}
 
// MEMORY
GET_MEMORY() {
    OWNERSAY(MODULE_WELL,BASENAME+" free memory: "+(string)llGetFreeMemory()); // show this module's free memory info
}
 
// GETVERSION
GET_VERSION() {
    OWNERSAY(MODULE_WELL,BASENAME+" v"+VERSION+"-"+VERSIONDATE); // show this module's version info
}
 
//
// HELP
//
integer LAST_HELP_MOD;
string LAST_HELP_MSG;
HELP(integer modnum,string help) {
    if ( modnum == LAST_HELP_MOD && help == LAST_HELP_MSG) return; // suppress duplicate debug messages to cut down spam
    string objname = llGetObjectName();
    llSetObjectName("⎗");    
    llOwnerSay("/me "+help); // now tell the owner the help message
    llSetObjectName(objname);
}
 
//
// OWNERSAY - sent messages to player only
//
string modcodes = " ▶☐♞☩◊⚔➤❤♦⛃▚♟▤☑☍";
integer LAST_OWNSAY_MOD;
string LAST_OWNSAY_MSG;
OWNERSAY(integer modnum,string message) {
    if ( modnum == LAST_OWNSAY_MOD && message == LAST_OWNSAY_MSG ) return; // suppress duplicate debug messages to cut down on spam
    LAST_OWNSAY_MOD = modnum; // remember this one
    LAST_OWNSAY_MSG = message; // remember this one
    modnum *= -1; // convert negative module constants to positive number
    string unicode = llGetSubString(modcodes,modnum,modnum);
    string objname = llGetObjectName();
    llSetObjectName(unicode);
    llOwnerSay("/me "+message);
    llSetObjectName(objname);
}
 
//============================================================================
// RESET - do any final cleanup work here then reset
//============================================================================
RESET() {
    STOP_ALL(); // stop all running animations
    llResetScript();
}
 
//
// RPEVENT
//
string LAST_RP_MSG;
integer LAST_RP_MOD;
RPEVENT(integer rpmod,string rpevent) {
    if ( rpmod == LAST_RP_MOD && rpevent == LAST_RP_MSG ) return; // ignore duplicates
    LAST_RP_MOD = rpmod; // remember this one
    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);
    string sendername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names
    llRegionSay(RENDEZVOUS1,"RPEVENT|"+GET_NAME()+" ("+sendername+") "+rpevent);
}
 
//============================================================================
// SETUP
//============================================================================
SETUP() {
    llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION);
    OWNERSAY(MODULE_WELL,"WELL module active.");
}
 
//
// STOP_ALL - stop all animations
STOP_ALL() {
    if ( ( llGetPermissions() & PERMISSION_TRIGGER_ANIMATION ) != TRUE ) {
        ERROR(MODULE_WELL,"Unable to stop all animations due to lack of granted permission.");
        return;
    }
    // stop all running animations
    list anims = llGetAnimationList(llGetOwner()); // 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
    }
}
 
//============================================================================
// DEFAULT STATE
//============================================================================
default {
 
    //------------------------------------------------------------------------
    // CHANGED EVENT
    //------------------------------------------------------------------------    
    changed(integer change) {
        if ( change & CHANGED_REGION || change & CHANGED_TELEPORT ) {
            DEBUG(MODULE_WELL,"Region Change or Teleport detected. Re-requesting permissions again.");
            llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION);
        }
    }
 
    //------------------------------------------------------------------------
    // LINK_MESSAGE EVENT
    //------------------------------------------------------------------------    
    link_message(integer sender_num,integer num,string str,key id) {
        if ( num == MODULE_WELL || num == LM_SENDTOATTACHMENT ) return; // ignore our own link messages
        //DEBUG(MODULE_WELL,"EVENT: link_message("+(string)sender_num+","+(string)num+","+str+","+(string)id+")");     
 
        // Break down incoming command
        list tokens = llParseString2List(str,["|"],[]);
        integer fields = llGetListLength(tokens);
        string cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
        //
        // Utility Output
        //
        // DEBUG|<debug_message>
        if ( cmd == "debug" ) { DEBUG(num,llList2String(tokens,1)); return; } // DEBUG|<debug_message>
        if ( cmd == "error" ) { ERROR(num,llList2String(tokens,1)); return; } // ERROR|<error_message>
        if ( cmd == "help" ) { HELP(num,llList2String(tokens,1)); return; } // HELP|<help_message>
        if ( cmd == "memory" ) { GET_MEMORY(); return;} // show memory info
        if ( cmd == "ownersay" ) { OWNERSAY(num,llList2String(tokens,1)); return; } // OWNERSAY|<ownersay_message>
        if ( cmd == "rpevent" ) { RPEVENT(num,llList2String(tokens,1)); return; } // RPEVENT|<rp_event_message>
        if ( cmd == "reset" ) { RESET(); return; } // RESET
        if ( cmd == "version" ) { GET_VERSION(); return; } // VERSION
        // WHISPER|CHANNEL=###|MESSAGE=...
        // SAY|CHANNEL=###|MESSAGE=...
        // SHOUT|CHANNEL=###|MESSAGE=...
        // REGIONSAY|CHANNEL=###|MESSAGE=...
        if ( cmd == "whisper" || cmd == "say" || cmd == "shout" || cmd == "regionsay" ) {
            integer channel = DEBUG_CHANNEL;
            string message = "";
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "channel" ) channel = llList2Integer(llParseString2List(llList2String(tokens,fields),["="],[]),1);
                if ( attrib == "message" ) message = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( channel != DEBUG_CHANNEL && message != "" ) {
                if ( cmd == "whisper" ) llWhisper(channel,message);
                if ( cmd == "say" ) llSay(channel,message);
                if ( cmd == "shout" ) llShout(channel,message);
                if ( cmd == "regionsay") llRegionSay(channel,message);
            } else {
                ERROR(MODULE_WELL,"Invalid "+cmd+" Command Received");
            }
            return;
        }
        // INSTANTMESSAGE|USER=uuid|MESSAGE=...
        if ( cmd == "instantmessage" ) {
            key user = NULL_KEY;
            string message = "";
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "user" ) user = llList2Key(llParseString2List(llList2String(tokens,fields),["="],[]),1);
                if ( attrib == "message" ) message = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( user != NULL_KEY && message != "" ) {
                llInstantMessage(user,message);
            } else {
                ERROR(MODULE_WELL,"Invalid INSTANTMESSAGE Command Received");
            }
            return;
        }
        // STARTANIMATION|ANIMATION=uuid or name...
        if ( cmd == "startanimation" && fields == 2 ) {
            string animation = "";
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "animation" ) animation = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( animation != "" ) {
                llStartAnimation(animation); // FIXME Permissions? Track list of running?
            } else {
                ERROR(MODULE_WELL,"Invalid STARTANIMATION Command Received");
            }
            return;
        }
        // STOPANIMATION|ANIMATION=uuid or name in inventory or all
        if ( cmd == "stopanimation" && fields == 2 ) {
            string animation = "";
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "animation" ) animation = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( llToLower(animation) == "all" ) {
                STOP_ALL(); 
                return;
            } else if ( animation != "" ) {
                llStopAnimation(animation); // FIXME Permissions? Track list of running?
            } else {
                ERROR(MODULE_WELL,"Invalid STOPANIMATION Command Received");
            }
            return;
        }
        // PLAYSOUND|SOUND=...|VOLUME=#.# - attached - does not play inworld from HUD
        if ( cmd == "playsound" && fields == 3 ) {
            string sound = "";
            float volume = 0.0;
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "sound" ) sound = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
                if ( attrib == "volume" ) volume = llList2Float(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( sound != "" && volume != 0.0 ) {
                llPlaySound(sound,volume);
            } else {
                ERROR(MODULE_WELL,"Invalid PLAYSOUND Command Received");
            }
            return;
        }
        // TRIGGERSOUND|SOUND=...|VOLUME=#.# - unattached, can play inworld from HUD
        if ( cmd == "triggersound" && fields == 3 ) {
            string sound = "";
            float volume = 0.0;
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "sound" ) sound = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
                if ( attrib == "volume" ) volume = llList2Float(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( sound != "" && volume != 0.0 ) {
                llTriggerSound(sound,volume);
            } else {
                ERROR(MODULE_WELL,"Invalid TRIGGERSOUND Command Received");
            }
            return;
        }
        // LOOPSOUND|SOUND=...|VOLUME=#.# - does not play inworld from HUD
        if ( cmd == "loopsound" && fields == 3 ) {
            string sound = "";
            float volume = 0.0;
            while ( fields-- ) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "sound" ) sound = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
                if ( attrib == "volume" ) volume = llList2Float(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( sound != "" && volume != 0.0 ) {
                llLoopSound(sound,volume);
            } else {
                ERROR(MODULE_WELL,"Invalid LOOPSOUND Command Received");
            }
            return;
        }
        // STOPSOUND
        if ( cmd == "stopsound" ) {
            llStopSound();
            return;
        }
        // PARTICLEPRESET|NAME=presetname...
        if ( cmd == "particlepreset" && fields == 2 ) {
            string name = "";
            while (fields--) {
                string attrib = llToLower(llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),0));
                if ( attrib == "name" ) name = llList2String(llParseString2List(llList2String(tokens,fields),["="],[]),1);
            }
            if ( name != "" ) { // found preset
                integer start = llListFindList(PRESETS,[name]) + 2;
                integer end = start + llList2Integer(PRESETS,start - 1);
                llParticleSystem(llList2List(PRESETS,start,end));
            } else {
                ERROR(MODULE_WELL,"Invalid PARTICLEPRESET Command Received.");
            }
            return;
        }
        // PARTICLES|whoanelly
        // PARTICLESOFF
        if ( cmd == "particlesoff" ) {
            llParticleSystem([]);
            return;
        }
        // MOVELOCK
        if ( cmd == "stopmovelock" ) {
            llStopMoveToTarget();
            MOVELOCK = <0,0,0>;
            return;
        }
        if ( cmd == "startmovelock" ) {
            if ( MOVELOCK == <0,0,0> ) MOVELOCK = llGetPos();
            llMoveToTarget(MOVELOCK,TAU);
            return;
        }
    }
 
    //------------------------------------------------------------------------
    // RUN_TIME_PERMISSIONS EVENT
    //------------------------------------------------------------------------
    run_time_permissions(integer perm) {
        if ( perm & PERMISSION_TRIGGER_ANIMATION) {
            DEBUG(MODULE_WELL,"PERMISSION_TRIGGER_ANIMATION granted.");
        }
    }    
 
    //------------------------------------------------------------------------
    // STATE_ENTRY EVENT
    //------------------------------------------------------------------------
    state_entry() {
        SETUP();
    }
}
// END