Myriad Lite Meter

// Myriad_Lite_Meter-v0.0.7-20120827.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/
 
//============================================================================
// MESSAGE FORMAT REFERENCE
//============================================================================
// CHANPLAYER IN - METER|str PLAYER NAME|str GAMENAME|int CURWOUNDS|int MAXWOUNDS|int CURCRITICAL|int MAXCRITICAL|int ISDEAD|int ISINCAPACITATED
 
//============================================================================
// GLOBAL VARIABLES
//============================================================================
string  VERSION = "0.0.7"; // version number
string  VERDATE = "20120827"; // version date
integer FLAG_DEBUG; // show debugging messages?
integer CHANATTACH; // dynamic channel for player attachments 
integer HANDATTACH; // attach channel handle for llRemove
string CHAN_PREFIX = "0x"; // prefix to convert to hexadecimal
string DIV = "|"; // Myriad message field divider
string FORMATSTRING = " @GN \n @FA @TI \n @HP / @RP \n @ST "; // GN = GameName, HP = HealthPercent, ST = Status (incap, dead, etc)
string  STATUS; // settext message
vector  COLOR = <0,1,0>; // set text color
float   ALPHA = 1.0; // set text alpha 0.0 = clear, 1.0 = solid
vector GREEN = <0,1,0>; // color constant for convenience
vector YELLOW = <1,1,0>; // color constant for convenience
vector RED = <1,0,0>; // color constant for convencience
vector BLACK = <0,0,0>; // color constant for convenience
 
//============================================================================
// DEBUG
//============================================================================
DEBUG(string debugmsg) {
    if ( FLAG_DEBUG == TRUE ) llSay(DEBUG_CHANNEL,"("+llKey2Name(llGetOwner())+") METER DEBUG: "+debugmsg);
}
 
//============================================================================
// HEALTHPERCENT - generate a percentage health status
//============================================================================
string HEALTHPERCENT(integer curwounds,integer maxwounds,integer curcritical,integer maxcritical) {
    string out;
    float currentpoints = (float)curwounds + (float)curcritical; // add up noncritical and critical wounds boxes remaining
    float maxpoints = (float)maxwounds + (float)maxcritical; // add up total wounds boxes player should have
    if ( currentpoints > 0.0 ) { // if player has some wounds left
        float health = ( ( currentpoints / maxpoints ) * 100.0 ); // get a percentage
        out = "Health: "+(string)llRound(health)+"%"; // add the percentage health to the status
    } else { // oops all resilience gone
        out = "Health: 0%"; // so add a zero% to status
    }
    return out;
}
 
//============================================================================
// RESOLVEPERCENT - generate a percentage resolve status
//============================================================================
string RESOLVEPERCENT(integer curresolve,integer resolve) {
    string out;
    float currentpoints = (float)curresolve; // noncritical resolve boxes remaining
    float maxpoints = (float)resolve; // total resolve boxes player should have
    if ( currentpoints > 0.0 ) { // if player has some wounds left
        float health = ( ( currentpoints / maxpoints ) * 100.0 ); // get a percentage
        out = "Resolve: "+(string)llRound(health)+"%"; // add the percentage health to the status
    } else { // oops all resilience gone
        out = "Resolve: 0%"; // so add a zero% to status
    }
    return out;
}
 
//============================================================================
// SEARCHANDREPLACE - search and replace tokens
//============================================================================
string SEARCHANDREPLACE(string input, string old, string new)  {
   return llDumpList2String(llParseString2List(input, [old], []), new);
} 
 
//============================================================================
// SETUP()
//============================================================================
SETUP() {
    FLAG_DEBUG = FALSE;
    llSetText("--- Waiting for Myriad Update ---",<1,0,0>,1); // set a default banner to show we haven't been updated yet
    CHANATTACH = (integer)(CHAN_PREFIX+llGetSubString((string)llGetOwner(),1,7)); // calculate wearer's dynamic attachment channel
    if ( HANDATTACH != 0 ) llListenRemove(HANDATTACH); // remove previously open channel
    HANDATTACH = llListen(CHANATTACH,"",NULL_KEY,""); // start a listener on the dynamic channel
    llWhisper(CHANATTACH,"ATTACHMETER"); // tell HUD we're attached
}
 
//============================================================================
// DEFAULT STATE
//============================================================================
default {
 
    //------------------------------------------------------------------------
    // ATTACH EVENT
    //------------------------------------------------------------------------    
    attach(key id) { // 
        if ( id != NULL_KEY ) {
            SETUP(); // wearing, let's setup
            return;
        }        
        if ( id == NULL_KEY ) { // detach, drop, derezzed to inventory - NOT ON LOGOUT
            if ( HANDATTACH != 0 ) llListenRemove(HANDATTACH);
            llWhisper(CHANATTACH,"DETACHMETER");
            return;
        }
    }
    //------------------------------------------------------------------------
    // CHANGED EVENT
    //------------------------------------------------------------------------    
    changed(integer changes) {
        if ( changes & CHANGED_OWNER ) { // if owner has changed, we need to recalculate the dynamic channel
            SETUP(); // setup the hovertext meter
            return;
        }
        if ( changes & CHANGED_REGION || changes & CHANGED_TELEPORT ) { // owner jumped to new location? restart
            SETUP(); // setup hovertext and channel
            return;
        }
    }
 
    //------------------------------------------------------------------------
    // LISTEN EVENT
    //------------------------------------------------------------------------    
    listen(integer channel,string name,key id,string message) {
        DEBUG("listen("+(string)channel+","+name+","+(string)id+","+message+")");
        if ( channel == CHANATTACH ) { // did this message come in on attachment channel?
            list fields = llParseString2List(message,[DIV],[]); // break message down into list separated by |
            string command = llList2String(fields,0); // read first item in list to get the Myriad command
            if ( command == "METER") { // if this is the METER command, let's update the meter status
                string playername   = llList2String(fields,1); // get the full player name from the list of METER values
                list   name2        = llParseString2List(playername,[" "],[]); // separate first and last name by space
                string firstname    = llList2String(name2,0); // get the firstname from the name2
                string lastname     = llList2String(name2,1); // get the lastname, if any, from the name2
                string gamename     = llList2String(fields,2); // get player's alias/game name (originally set in their character sheet)
                integer curwounds   = llList2Integer(fields,3); // what is player's current wound value?
                integer maxwounds   = llList2Integer(fields,4); // what is player's maximum healed wounds allowed by level/stats?
                integer curcritical = llList2Integer(fields,5); // what is player's current critical wounds value?
                integer maxcritical = llList2Integer(fields,6); // what is player's maximum healed critical wounds value?
                integer isdead      = llList2Integer(fields,7); // is player dead?
                integer isincap     = llList2Integer(fields,8); // is player incapacitated?
                string  species     = llList2String(fields,9); // player species
                string  background  = llList2String(fields,10); // player background
                string  career      = llList2String(fields,11); // player career
                integer curresolve  = llList2Integer(fields,12); // player current resolve
                integer resolve     = llList2Integer(fields,13); // player total resolve
                string  title       = llList2String(fields,14); // player's social title
                string  faction     = llList2String(fields,15); // players faction name
 
                // okay, we've broken down status, lets create a banner from that using colors
                STATUS = FORMATSTRING; // start with a default FORMATSTRING
                STATUS = SEARCHANDREPLACE(STATUS,"@PN",playername); // @PN = Player Name
                STATUS = SEARCHANDREPLACE(STATUS,"@FN",firstname); // @FN = First Name
                STATUS = SEARCHANDREPLACE(STATUS,"@LN",lastname); // @PN = Last Name                
                STATUS = SEARCHANDREPLACE(STATUS,"@GN",gamename); // @GN = Game Name
                STATUS = SEARCHANDREPLACE(STATUS,"@TI",title); // @TI = Title
                STATUS = SEARCHANDREPLACE(STATUS,"@FA",faction); // @FA = Faction
                STATUS = SEARCHANDREPLACE(STATUS,"@SP",species); // @SP = Species
                STATUS = SEARCHANDREPLACE(STATUS,"@BG",background); // @BG = Background Name
                STATUS = SEARCHANDREPLACE(STATUS,"@CA",career); // @CA = Career
 
                STATUS = SEARCHANDREPLACE(STATUS,"@CW",(string)curwounds); // @CW = Current Wounds
                STATUS = SEARCHANDREPLACE(STATUS,"@MW",(string)maxwounds); // @MW = Max Wounds
                STATUS = SEARCHANDREPLACE(STATUS,"@CC",(string)curcritical); // @CC = Current Critical                
                STATUS = SEARCHANDREPLACE(STATUS,"@MC",(string)maxcritical); // @MC = Max Critical
                if ( isincap == 1 && isdead == 0 ) {
                    STATUS = SEARCHANDREPLACE(STATUS,"@ST"," ! INCAPACITATED ! "); // @ST = Status
                } else if ( isdead == 1 ) {
                    STATUS = SEARCHANDREPLACE(STATUS,"@ST"," !!! DEAD !!! "); // @ST = Status
                } else {
                    STATUS = SEARCHANDREPLACE(STATUS,"@ST",""); // @ST = Status
                }
                string hp = HEALTHPERCENT(curwounds,maxwounds,curcritical,maxcritical);
                STATUS = SEARCHANDREPLACE(STATUS,"@HP",hp); // @HP = Health Percent
                string rp = RESOLVEPERCENT(curresolve,resolve); // calculate resolve percent
                STATUS = SEARCHANDREPLACE(STATUS,"@RP",rp); // @RP = Resolve Percent                
                // PICK Color based on status
                COLOR = GREEN; // start with fully healthy color
                if ( curwounds == maxwounds && curcritical == maxcritical && curresolve == resolve ) { COLOR = GREEN; } // are all health boxes full?
                if ( curwounds < maxwounds && curwounds >= 1 || curresolve < resolve ) { COLOR = YELLOW; } // we've lost some wounds, but not incapacitated
                if ( curwounds < 1 || curresolve < 1 ) { COLOR = RED; } // if we're out of non-critical wounds, we're bleeding out
                if ( isincap == 1 && isdead == 0 ) { COLOR = RED;} // set color to warning that we're down but not dead yet
                if ( isdead == 1 ) { COLOR = BLACK; } // if we're dead
                llSetText(STATUS,COLOR,ALPHA); // show the new status text over the meter
                return; // we're done with this command, exit entire listen event
            } // end if command meter
            if ( command == "REGISTERATTACHMENTS" ) { // HUD asking for attachments attached?
                SETUP(); // just setup
                return;
            }
        } // end if chanattach
    }
 
    //------------------------------------------------------------------------
    // ON_REZ EVENT
    //------------------------------------------------------------------------    
    on_rez(integer param) {
        param = 0; // LSLINT
        SETUP(); // setup the hovertext meter
    }
 
    //------------------------------------------------------------------------
    // STATE_ENTRY EVENT
    //------------------------------------------------------------------------    
    state_entry() {
        SETUP(); // setup the hovertext meter
    }
 
} // end default
//============================================================================
// END
//============================================================================