// Myriad_Lite_Module_Armor-v0.0.3-20120826.lsl
// Copyright (c) 2012 by Allen Kerensky (OSG/SL) All Rights Reserved.
// This work is dual-licensed under
// Creative Commons Attribution (CC BY) 3.0 Unported
// http://creativecommons.org/licenses/by/3.0/
// - or -
// Modified BSD License (3-clause)
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Myriad Lite nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
// NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The Myriad RPG System was designed, written, and illustrated by Ashok Desai
// Myriad RPG System licensed under:
// Creative Commons Attribution (CC BY) 2.0 UK: England and Wales
// http://creativecommons.org/licenses/by/2.0/uk/
// ===========================================================================
// INTERNAL CONSTANTS - should not change during runtime
// ===========================================================================
string VERSION = "0.0.3";
string VERDATE = "20120826";
key PLAYERID; // cached player UUID
// Module to Module Messaging Constants
//integer MODULE_HUD = -1;
//integer MODULE_CHARSHEET = -2;
integer MODULE_ARMOR = -3;
//integer MODULE_BAM = -4;
//integer MODULE_RUMORS = -5;
// integer MODULE_CLOSE = -6;
// integer MODULE_RANGED = -7;
// integer MODULE_RESILIENCE = -8;
// integer MODULE_PROGRESS = -9;
// integer MODULE_WELL = -10;
// integer MODULE_METER = -11;
integer LM_SENDTOATTACHMENT = 0x80000000;
string DIV = "|"; // message divider
// string names for each attach point - waste of memory?
list ATTACHPOINTS = ["INVALID","chest","head","left shoulder","right shoulder","left hand","right hand","left foot","right foot","back","pelvis","mouth","chin","left ear","right ear","left eye","right eye","nose","right upper arm","right lower arm","left upper arm","left lower arm","right hip","right upper leg","right lower leg","left hip","left upper leg","left lower leg","stomach","left pectoral","right pectoral","HUD Center 2","HUD Top Right","HUD Top","HUD Top Left","HUD Center","HUD Bottom Left","HUD Bottom","HUD Bottom Right", "neck", "root" ];
// ===========================================================================
// MYRIAD LITE CONSTANTS - should not be changed
// ===========================================================================
integer MINATTACH = 1; // min valid attach point
integer MINHUDATTACH = 31; // lower HUD attach point number
integer MAXHUDATTACH = 38; // upper HUD attach point number
integer MAXWEAR = 40; // max valid in-world wearable attach point
integer MINARMOR = 1; // min armor defense value
integer MAXARMOR = 5; // max armor defense value
// ===========================================================================
// CONFIGURATION VALUES - CONFIGURE VALUES IN SETUP() FUNCTION BELOW
// ===========================================================================
// FIXME - MAXEFFECTTIME this needs to be easily configurable and persistent
integer MAXEFFECTTIME; // maximum time to show armor hit/blocked effects
integer MAXBATTERY; // maximum power armor battery life - all armor powered equally
// ===========================================================================
// GLOBAL RUNTIMES - expected to change at runtime
// ===========================================================================
integer FLAG_DEBUG; // configure in setup
list ARMOR; // list of armor amount (1-5) on each attachpoint from 0 (unused) to 30
list ARMORPOWER; // list of true/false flags for each attachpoint from 0 (unused) to 30 if power armor or not
integer CURARMOR; // highest armor value worn out of all armor worn, not a total
integer POWERARMOR;
integer BATTERY;
integer EFFECTTIME; // how much time is left to show armor effects
integer ARMOR_ON; // is armor "on" and protecting?
//============================================================================
// ARMORATTACH - Wearing a piece of armor
// ARMORATTACH|ARMORRATING|ARMORPOWERED?|ARMORATTACHPOINT|ARMORNAME
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMORATTACH(integer waamount,integer wapower,integer waattachpoint,string waname) {
DEBUG("ARMORATTACH Amount=["+(string)waamount+"] Power=["+(string)wapower+"] AttachPoint=["+(string)waattachpoint+"] Name=["+waname+"]");
if ( waattachpoint < MINATTACH || ( waattachpoint >= MINHUDATTACH && waattachpoint <= MAXHUDATTACH ) || waattachpoint > MAXWEAR ) { // valid attach point?
ERROR("Invalid armor attachment point: "+(string)waattachpoint);
return;
}
if ( waamount < MINARMOR || waamount > MAXARMOR ) { // is armor rating valid or legal?
ERROR("Invalid armor amount "+(string)waamount+" out of range "+(string)MINARMOR+"-"+(string)MAXARMOR);
return;
}
if ( wapower != TRUE && wapower != FALSE ) {
ERROR("Cannot determine if worn armor is power armor.");
return;
}
// FIXME move ARMOR and POWERARMOR into single list?
ARMORPOWER = llListReplaceList(ARMORPOWER,[wapower],waattachpoint,waattachpoint); // insert armor value into armor list
// FIXME move ARMOR to 3-element strided list? [attachpoint,value,name?]
ARMOR = llListReplaceList(ARMOR,[waamount],waattachpoint,waattachpoint); // insert armor value into armor list
llOwnerSay("Armor "+waname+" ("+(string)waamount+") attached to "+llList2String(ATTACHPOINTS,waattachpoint));
ARMORCHECK(); // find new highest armor value
}
//============================================================================
// ARMORBATTERY
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMORBATTERY() {
//llWhisper(CHANATTACH,"ARMORBATTERY");
if ( POWERARMOR != TRUE ) {
llOwnerSay("Cannot check battery level for non-powered armor.");
return;
}
llOwnerSay("Armor battery level: "+(string)BATTERY+" of "+(string)MAXBATTERY+" total.");
SENDTOHUD("ARMORBATTERY");
}
//============================================================================
// ARMORCHECK - sets CURARMOR to highest armor value worn after attach or detach
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMORCHECK() {
CURARMOR = 0; // start with zero armor
POWERARMOR = FALSE;
integer racount = llGetListLength(ARMOR); // how long is armor list?
while (racount--) { // look at each list item from last to first
integer rapoints = llList2Integer(ARMOR,racount); // what is armor value at this point in list?
integer pa = llList2Integer(ARMORPOWER,racount);
if ( pa == FALSE ) { // not power armor in this slot, so do check regardless of power state
if ( rapoints > CURARMOR ) { // is this armor value higher than current max?
CURARMOR = rapoints; // yes, save new highest amount
}
} else { // this is power armor in this slot.
POWERARMOR = TRUE;
if ( ARMOR_ON == TRUE && BATTERY > 0 && rapoints > CURARMOR ) {
CURARMOR = rapoints; // yes armor is on, has power, and current value is higher, save new highest amount
}
}
}
if ( POWERARMOR == TRUE ) {
llOwnerSay("Power Armor Rating is "+(string)CURARMOR);
} else {
llOwnerSay("Non-Power Armor Rating is "+(string)CURARMOR);
}
SENDTOHUD("ARMORCURRENT|"+(string)CURARMOR);
// FIXME send new ARMORCURRENT with RATING, POWERED?, ATTACHPOINT, ARMORNAME, MAXBATTERY, CURRENTBATTERY?
// FIXME tell world too?
}
//============================================================================
// ARMORDETACH - Removing a piece of armor
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMORDETACH(integer raamount,integer rapower,integer raattachpoint,string raname) {
DEBUG("DETACH Amount=["+(string)raamount+"] Power=["+(string)rapower+"] Attachpoint=["+(string)raattachpoint+"] Name=["+raname+"]");
if ( raattachpoint < MINATTACH || ( raattachpoint >= MINHUDATTACH && raattachpoint <= MAXHUDATTACH ) || raattachpoint > MAXWEAR ) { // valid attach point?
ERROR("Invalid armor detachment point: "+(string)raattachpoint);
return;
}
if ( raamount < MINARMOR || raamount > MAXARMOR ) { // is armor rating valid or legal?
ERROR("Invalid armor amount "+(string)raamount+" out of range "+(string)MINARMOR+"-"+(string)MAXARMOR);
return;
}
if ( rapower != TRUE && rapower != FALSE ) {
ERROR("Cannot determine if detached armor is power armor.");
return;
}
// FIXME move ARMOR and POWERARMOR into single list?
ARMORPOWER = llListReplaceList(ARMORPOWER,[FALSE],raattachpoint,raattachpoint); // insert armor value into armor list
// FIXME move ARMOR to 3-element strided list? [attachpoint,value,name?]
ARMOR = llListReplaceList(ARMOR,[0],raattachpoint,raattachpoint); // zero out the armor value in armor list
llOwnerSay("Armor "+raname+" ("+(string)raamount+") detached from "+llList2String(ATTACHPOINTS,raattachpoint));
ARMORCHECK(); // find new highest armor value
}
//============================================================================
// ARMOREFFECTBLOCKED - CHANGE ARMOR EFFECT WHEN ARMOR HIT AND BLOCKS DAMAGE
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMOREFFECTBLOCKED() {
// your commands go here for armor special effect when armor BLOCKS a hit
// llWhisper(CHANATTACH,"ARMOREFFECTBLOCKED");
SENDTOHUD("ARMOREFFECTBLOCKED");
EFFECTTIME = MAXEFFECTTIME; // load the countdown
llSetTimerEvent(1.0); // start the effect timer
}
//============================================================================
// ARMOREFFECTHIT() - SHOW SPECIAL ARMOR EFFECTS WHEN ARMOR HIT BUT FAILS TO BLOCK
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMOREFFECTHIT() {
//llWhisper(CHANATTACH,"ARMOREFFECTHIT");
SENDTOHUD("ARMOREFFECTHIT");
EFFECTTIME = MAXEFFECTTIME; // load the countdown
llSetTimerEvent(1.0); // start the effect timer
}
//============================================================================
// ARMOREFFECTOFF - RESET ARMOR TO NORMAL VIEW
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMOREFFECTOFF() {
//llWhisper(CHANATTACH,"ARMOREFFECTOFF");
SENDTOHUD("ARMOREFFECTOFF");
}
//============================================================================
// ARMOROFF
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMOROFF() {
//llWhisper(CHANATTACH,"ARMOROFF"); // tell attachments to do some inworld special effects if needed
if ( POWERARMOR == TRUE ) {
llOwnerSay("Power armor deactivated.");
ARMOR_ON = FALSE;
SENDTOHUD("ARMOROFF");
llSetTimerEvent(0.0); // stop battery drain timer - FIXME does this kill effect timers?
return;
}
ARMORCHECK();
}
//============================================================================
// ARMOR ON
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMORON() {
//llWhisper(CHANATTACH,"ARMORON"); // tell attachment to do some inworld special effect if needed
if ( BATTERY <= 0 && POWERARMOR == TRUE ) {
llOwnerSay("Power armor out of power. Recharge.");
return;
}
if ( POWERARMOR == TRUE ) {
llOwnerSay("Power armor activating.");
ARMOR_ON = TRUE;
SENDTOHUD("ARMORON");
llSetTimerEvent(1.0); // run a battery drain timer, battery already checked > 0
}
ARMORCHECK();
}
//============================================================================
// ARMORRECHARGE
// FIXME - Medieval, Modern, Futuristic
// FIXME - Armor, Power Armor, Shield, PowerShield
//============================================================================
ARMORRECHARGE() {
//llWhisper(CHANATTACH,"ARMORRECHARGE");
if ( POWERARMOR != TRUE ) {
llOwnerSay("Cannot recharge non-powered armor.");
return;
}
// TODO Partial Recharges?
BATTERY = MAXBATTERY;
llOwnerSay("Armor recharged.");
SENDTOHUD("ARMORRECHARGE");
}
//============================================================================
// COMMAND processor
//============================================================================
COMMAND(string message) {
list fields = llParseString2List(message,[DIV],[]); // break into list of fields based on DIVider
string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command
// General Myriad Module Commnads
if ( command == "debugoff" ) { FLAG_DEBUG = FALSE; return;} // disable debugging
if ( command == "debugon" ) { FLAG_DEBUG = TRUE; return;} // enable debugging
if ( command == "reset" ) { RESET(); return;} // reset when told
if ( command == "version" ) { GETVERSION(); return;} // get version when needed
// Armor-Specific Module Commands in alphabetical order by command
if ( command == "armorattach" ) { // player attached armor somewhere
integer armorrating = llList2Integer(fields,1); // get armor value
integer armorpower = llList2Integer(fields,2); // get power armor or not
integer attachpoint = llList2Integer(fields,3); // get armor location
string armorname = llList2String(fields,4); // get armor's name
ARMORATTACH(armorrating,armorpower,attachpoint,armorname); // add armor to set of armor worn
return;
}
if ( command == "armorbattery") { ARMORBATTERY(); return;} // check power armor battery
if ( command == "armorcheck" ) { ARMORCHECK(); return; } // check our current armor value
// armorcurrent skipped intentionally
if ( command == "armordetach" ) { // player attached armor somewhere
integer armorrating = llList2Integer(fields,1); // get armor value
integer armorpower = llList2Integer(fields,2); // get power armor or not
integer attachpoint = llList2Integer(fields,3); // get armor location
string armorname = llList2String(fields,4); // get armor's name
ARMORDETACH(armorrating,armorpower,attachpoint,armorname); // detach armor from set of armor worn
return;
}
if ( command == "armoreffecthit" ) { ARMOREFFECTHIT(); return;} // show SFX on hit
if ( command == "armoreffectblocked" ) { ARMOREFFECTBLOCKED(); return;} // show SFX on hit
if ( command == "armoreffectoff" ) { ARMOREFFECTOFF(); return;} // show SFX on hit
if ( command == "armoron" ) { ARMORON(); return;} // turn on power armor
if ( command == "armoroff" ) { ARMOROFF(); return;} // turn off power armor
if ( command == "armorrecharge" ) { ARMORRECHARGE(); return;} // recharge power armor battery
}
//============================================================================
// DEBUG - show errors on debug channel with wearer name for sorting
//============================================================================
DEBUG(string dmessage) {
if ( FLAG_DEBUG == TRUE ) llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") MOD ARMOR: "+dmessage);
}
//============================================================================
// ERROR - show errors on debug channel with wearer name for sorting
//============================================================================
ERROR(string emessage) {
llSay(DEBUG_CHANNEL,"ERROR ("+llKey2Name(PLAYERID)+"): "+emessage);
}
//============================================================================
// GETVERSION
//============================================================================
GETVERSION() {
SENDTOHUD("VERSION="+VERSION+DIV+"VERSIONDATE="+VERDATE+DIV+llGetObjectName());
}
//============================================================================
// RESET - shut down running animations then reset the script to reload character sheet
//============================================================================
RESET() {
llResetScript(); // now reset
}
//============================================================================
// SENDTOHUD - send reponses to HUD as Link Messages
//============================================================================
SENDTOHUD(string str) {
DEBUG("SENDTOHUD("+str+")");
llMessageLinked(LINK_THIS,LM_SENDTOATTACHMENT,str,PLAYERID);
}
//============================================================================
// SETUP - begin
//============================================================================
SETUP() {
FLAG_DEBUG = FALSE; // do we want debug messages
ARMOR_ON = FALSE; // is armor "on" and protecting? off by default to save battery
MAXBATTERY = 3600; // total battery capacity when fully charged - in seconds
BATTERY = MAXBATTERY; // start with charged battery FIXME how to save state from wear to wear
MAXEFFECTTIME = 3; // show armor effects for how long in seconds?
PLAYERID = llGetOwner(); // remember the owner's UUID
integer attachpoints = MAXWEAR; // counting from 0 to 30
while ( attachpoints-- ) {
ARMOR = ARMOR + [0]; // create 30 empty armor slots - avoids SL stack depth error and LSLINT warning
ARMORPOWER = ARMORPOWER + [FALSE]; // create 30 empty power armor flags - avoid SL stack depth and LSLINT warning
}
llSetTimerEvent(0.0); // stop any running timer
}
//============================================================================
// TIMER_HEARTBEAT - code called each time a timer event fires
// CALLED TO TURN OFF THE ARMOR SPECIAL EFFECTS AND DRAIN POWER ARMOR BATTERIES
//============================================================================
TIMER_HEARTBEAT() {
if ( EFFECTTIME > 0 ) { // if there is still time to show armor special effects inworld, decrement counter
EFFECTTIME--;
}
if ( EFFECTTIME <= 0 ) { // once armor effect timer counts down, cleanup.
EFFECTTIME = 0; // force to zero if somehow it went less than zero
ARMOREFFECTOFF(); // send message to shut off special effects
}
if ( POWERARMOR == TRUE && ARMOR_ON == TRUE && BATTERY > 0 ) { // if power armor on, decrement battery counter
BATTERY--; // remove some battery
}
if ( POWERARMOR == TRUE && ARMOR_ON == TRUE && BATTERY <= 0 ) { // power armor battery drained, cleanup
llOwnerSay("Power armor battery drained. Shutting down.");
BATTERY = 0;
ARMOROFF();
}
if ( EFFECTTIME <= 0 && ARMOR_ON == FALSE ) llSetTimerEvent(0.0); // all timers done, stop timer events
}
// ===========================================================================
// DEFAULT state
// ===========================================================================
default {
// -----------------------------------------------------------------------
// LINK_MESSAGE - note: NUM field is the number of the sending module
// -----------------------------------------------------------------------
link_message(integer sender_num,integer num,string str,key id) {
if ( num == MODULE_ARMOR || num == LM_SENDTOATTACHMENT ) return; // ignore our own messages
DEBUG("EVENT: link_message("+(string)sender_num+","+(string)num+","+str+","+(string)id+")");
COMMAND(str); // jump to command processor
}
// -----------------------------------------------------------------------
// STATE_ENTRY
// -----------------------------------------------------------------------
state_entry() {
DEBUG("EVENT: state_entry()");
SETUP(); // setup defaults and go into event wait
}
//------------------------------------------------------------------------
// TIMER
//------------------------------------------------------------------------
timer() {
DEBUG("EVENT: timer()");
TIMER_HEARTBEAT();
}
}