User:Allen Kerensky/Myriad Lite/Myriad Lite Module Resilience-v0.0.4-20120827.lsl
From OpenSimulator
< User:Allen Kerensky | Myriad Lite
Revision as of 17:35, 27 August 2012 by Allen Kerensky (Talk | contribs)
Myriad_Lite_Module_Resilience-v0.0.4-20120827.lsl
// Myriad_Lite_Module_Resilience-v0.0.4-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/ // CONSTANTS - DO NOT CHANGE DURING RUN string VERSION = "0.0.4"; // Allen Kerensky's script version string VERDATE = "20120827"; // Allen Kerensky's script yyyymmdd integer MINSTAT = 1; // min value for statistics integer MAXSTAT = 5; // max human value for a statistic/attribute integer MINRESILIENCE = 0; // min value for resilience integer MAXRESILIENCE = 20; // max value for resilience integer MINSKILL = 1; // min value for skill rank integer MAXSKILL = 5; // max value for skill rank integer CHANMYRIAD = -999; // chat sent to ALL Myriad players in region float RESPAWN_TIME = 30.0; // time dead before automatic respawn string DIV = "|"; // message field divider string ANIM_INCAPACITATED = "sleep"; // anim when incapacitated string ANIM_DEAD = "dead"; // anim when dead integer MAXARMOR = 5; // max legal armor rating // 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; // RUNTIME GLOBALS - CAN CHANGE DURING RUN integer FLAG_DEBUG; // see debug messages? key PLAYERID = NULL_KEY; // cached player UUID string PLAYERNAME = ""; // cached player name string NAME = ""; // character name string SPECIES = ""; // character species string BACKGROUND = ""; // character childhood history string CAREER = ""; // character career or faction list STATISTICS = []; list RESILIENCES = []; list CURRENT_RESILIENCES = []; list SKILLS = []; // skills [ string SkillName, integer SkillRank ] integer FLAG_ANIMATE; // integer FLAG_INCAPACITATED; // incapacitated by wounds? integer FLAG_DEAD; // killed by critical wounds? vector MOVELOCK = <0,0,0>; // movelock position when incapacitated or dead float TAU = 0.05; // movelock tau integer CURARMOR = 0; // highest armor value worn out of all armor worn, not a total // FIXME MOVE THIS TO ARMOR AND SEND LINK MESSAGES integer CHANATTACH = 0; // dynamic channel for attachments vector DEATHPOINT; // OPENSIM ONLY coordinates to move to on death vector RESPAWNPOINT; // OPENSIM ONLY coordinates to move to on respawn // DEAD - player is dead, kill them and wait to respawn DEAD() { FLAG_DEAD = TRUE; // remember that we're now dead llStartAnimation(ANIM_DEAD); // start dead animation RPEVENT("has been killed!"); llOwnerSay("You've been killed!"); llMessageLinked(LINK_THIS,MODULE_RESILIENCE,"DEAD",PLAYERID); // FIXME if ( DEATHPOINT != ZERO_VECTOR ) osTeleportAgent(llGetKey(),DEATHPOINT); METER(); // update hover text llSetTimerEvent(RESPAWN_TIME); // respawn in a bit } // DEBUG - show debug chat with wearer name for sorting DEBUG(string dmessage) { if ( FLAG_DEBUG == TRUE ) { // are we debugging? llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") Module Resilience: "+dmessage); } } // DEBUGOFF - turn off the DEBUG flag DEBUGOFF() { DEBUG("Debug Mode Deactivated"); FLAG_DEBUG = FALSE; // set debug flag to FALSE } // DEBUGON - turn on the DEBUG flag DEBUGON() { FLAG_DEBUG = TRUE; // set debug flag TRUE DEBUG("Debug Mode Activated"); } // ERROR - show errors on debug channel with wearer name for sorting ERROR(string emessage) { llSay(DEBUG_CHANNEL,"ERROR ("+llKey2Name(PLAYERID)+"): "+emessage); } // GET_MAX_RESILIENCE integer GET_MAX_RESILIENCE(string name) { integer pos = llListFindList(RESILIENCES,[name]); if ( pos >= 0 ) { return llList2Integer(RESILIENCES,pos + 1); } return 0; } // GET_RESILIENCE integer GET_RESILIENCE(string name) { integer pos = llListFindList(CURRENT_RESILIENCES,[name]); if ( pos >= 0 ) { return llList2Integer(CURRENT_RESILIENCES,pos + 1); } return 0; } // GETVERSION GETVERSION() { SENDTOHUD("VERSION="+VERSION+DIV+"VERSIONDATE="+VERDATE+DIV+llGetObjectName()); } // HEAL - restore lost WOUND and CRITICAL resilience // Thanks to Artemis Tesla for contributing summary report logic HEAL(integer healamount) { integer critsHealed = 0; // track how many crit boxes restored for summary report integer woundsHealed = 0; // track how many non-crit boxes restored for summary report integer reborn = FALSE; // track if reborn/respawn or not for summary report integer revived = FALSE; // track of revived or not for summary report integer curwounds = GET_RESILIENCE("Wounds"); integer maxwounds = GET_MAX_RESILIENCE("Wounds"); integer curcritical = GET_RESILIENCE("Critical"); integer maxcritical = GET_MAX_RESILIENCE("Critical"); // TODO report once for multiple healing amounts while ( healamount-- ) { // step through each point of healing if ( curcritical < maxcritical ) { // is current critical less than max critical DEBUG("Heal one critical wound"); curcritical++; // heal one current critical SET_RESILIENCE("Critical",curcritical); critsHealed++; // add a point back if ( FLAG_DEAD == TRUE ) { // healed a critical, critical now > 0 so not dead anymore FLAG_DEAD = FALSE; // no longer dead llMessageLinked(LINK_THIS,MODULE_RESILIENCE,"ALIVE",PLAYERID); METER(); reborn = TRUE; // show rebirth in summary report DEBUG("Heal: reborn"); } } else { if ( curwounds < maxwounds ) { // player not critical, heal non-critical? DEBUG("Heal one wound"); curwounds++; // add the healing point to current wounds SET_RESILIENCE("Wounds",curwounds); woundsHealed++; // add a point of non-critical if ( FLAG_INCAPACITATED == TRUE ) { // were they incapacitated? FLAG_INCAPACITATED = FALSE; // no longer gravely wounded llMessageLinked(LINK_THIS,MODULE_RESILIENCE,"REVIVED",PLAYERID); METER(); revived = TRUE; // show revival in summary report DEBUG("Heal: Revived!"); llStopMoveToTarget(); MOVELOCK = <0,0,0>; } } // end if curwounds < wounds } } // end while // Summary report of healing effects if ( critsHealed > 0 ) { // was at least one critical healed? DEBUG("Critical Heal: "+(string)curcritical+" of "+(string)maxcritical+" critical wound boxes."); if (critsHealed > 1) { // was more then one critical wound healed? llOwnerSay("Critical " + (string)critsHealed + " wounds healed."); } else { llOwnerSay("Critical " + (string)critsHealed + " wound healed."); } } if (reborn == TRUE ) { // if player reborn from this heal RPEVENT("has been resurrected!"); if ( FLAG_ANIMATE == TRUE ) { // if we're allowed to change animations llStopAnimation(ANIM_DEAD); // stop "we're dead" animation } llOwnerSay("You've been resurrected! Welcome back to the land of the living."); } if ( woundsHealed > 0 ) { // was at least 1 non-critical healed? DEBUG("Heal Non-Critical Wounds: "+(string)curwounds+" of "+(string)maxwounds+" non-critical wound boxes."); if (woundsHealed > 1) { // was more than one non-critical healed? llOwnerSay((string)woundsHealed + " non-critical wounds healed."); } else { llOwnerSay((string)woundsHealed + " non-critical wound healed."); } } if ( revived == TRUE ) { // if player revived from this heal RPEVENT("has revived and is no longer incapacitated!"); if ( FLAG_ANIMATE == TRUE ) { // if we're allowed to change anims llStopAnimation(ANIM_INCAPACITATED); // stop the "we're down" animation } llOwnerSay("You are no longer incapacitated! Welcome back to the fight!"); } METER(); // update hovertext } // HIT - player is hit - check to see if attack dice breach armor // Making A Damage Roll (Myriad p25, Myriad Special Edition p31) HIT(integer attackdice) { integer damagetaken = 0; // start with zero damage while(attackdice--) { // roll for each attack dice integer dieroll = 1+(integer)llFrand(5.0); // reasonably uniform d6 if ( dieroll > CURARMOR ) { // attack roll stronger than armor worn? damagetaken++; // add a wound point } } // finished roll how did we do? if ( damagetaken > 0 ) { // we took damage if ( CURARMOR > 0 ) { // wearing armor? tell them it was breached llOwnerSay("That attack penetrated your armor and you've been wounded!"); llWhisper(CHANATTACH,"ARMOREFFECTHIT"); } else { // fighting in no armor? llOwnerSay("You've been wounded! Wear some armor next time?"); } WOUNDED(damagetaken); // apply damage taken to resilences } else { // hit, but no damage taken // must be wearing *some* armor to be hit but avoid a wound, don't recheck for armor here llOwnerSay("Your armor blocked the damage from that attack!"); llWhisper(CHANATTACH,"ARMOREFFECTBLOCKED"); } } // INCAPACITATED - player lost all WOUNDS - unable to act INCAPACITATED() { FLAG_INCAPACITATED = TRUE; // yes, we're now incapacitated if ( MOVELOCK == <0,0,0> ) MOVELOCK = llGetPos(); llMoveToTarget(MOVELOCK,TAU); llStartAnimation(ANIM_INCAPACITATED); // "we're hurt and down" animation RPEVENT("has been incapacitated!"); llOwnerSay("You've been incapacitated!"); llMessageLinked(LINK_THIS,MODULE_RESILIENCE,"INCAPACITATED",PLAYERID); METER(); // update meter llSetTimerEvent(RESPAWN_TIME); // heal in a bit } // METER - update a hovertext health meter or HUD bar graph METER() { llMessageLinked(LINK_THIS,MODULE_RESILIENCE,"METER",PLAYERID); } // RESET - shut down running animations then reset the script to reload character sheet RESET() { llResetScript(); // now reset } // RPEVENT // FIXME - change all RPEVENT to link message for main HUD to send? RPEVENT(string rpevent) { llRegionSay(CHANMYRIAD,"RPEVENT|"+NAME+" ("+PLAYERNAME+") "+rpevent); } // SET_RESILIENCE SET_RESILIENCE(string name,integer value) { if ( value < MINRESILIENCE || value > MAXRESILIENCE ) { ERROR("Resilience rank "+(string)name+" out of allowed range "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE); return; } // out of range integer curpos = llListFindList(CURRENT_RESILIENCES,[name]); integer curval; integer maxval; if ( curpos >= 0 ) { curval = llList2Integer(CURRENT_RESILIENCES,curpos + 1); } else { // resilience not found CURRENT_RESILIENCES = [name,value] + CURRENT_RESILIENCES; RESILIENCES = [name,value] + RESILIENCES; return; } integer maxpos = llListFindList(RESILIENCES,[name]); if ( maxpos >=0 ) { maxval = llList2Integer(RESILIENCES,maxpos + 1); } if ( value <= maxval) { CURRENT_RESILIENCES = llListReplaceList(CURRENT_RESILIENCES,[value],curpos + 1, curpos + 1); } llMessageLinked(LINK_THIS,MODULE_RESILIENCE,"SET_RESILIENCE|"+name+"="+(string)value,llGetOwner()); METER(); } // SENDTOHUD - send reponses to HUD as Link Messages SENDTOHUD(string str) { DEBUG("SENDTOHUD("+str+")"); llMessageLinked(LINK_THIS,LM_SENDTOATTACHMENT,str,PLAYERID); } // SETUP - begin bringing the HUD online SETUP() { FLAG_DEBUG = FALSE; PLAYERID = llGetOwner(); // remember the owner's UUID PLAYERNAME = llKey2Name(PLAYERID); // remember the owner's legacy name CHANATTACH = (integer)("0x"+llGetSubString((string)PLAYERID,1,7)); // attachment-specific channel llRegionSay(CHANMYRIAD,"GET_REGION_SETTING|DEATHPOINT"); llRegionSay(CHANMYRIAD,"GET_REGION_SETTING|RESPAWNPOINT"); llRequestPermissions(PLAYERID,PERMISSION_TRIGGER_ANIMATION); } // WOUNDED - Player takes Resilience damage WOUNDED(integer amount) { while (amount--) { // for each wound taken integer curwounds = GET_RESILIENCE("Wounds"); integer curcritical = GET_RESILIENCE("Critical"); integer maxcritical = GET_MAX_RESILIENCE("Critical"); if ( curwounds > 0 && curcritical != maxcritical ) { llSay(DEBUG_CHANNEL,"ERROR! WOUND STATE IS NONSENSE! CANNOT APPLY DAMAGE!"); llOwnerSay("ERROR! WOUND STATE IS NONSENSE! CANNOT APPLY DAMAGE! TELL ALLEN KERENSKY!"); return; } if ( curwounds > 1 && curcritical == maxcritical ) { // wound boxes left? curwounds--; // scratch off one SET_RESILIENCE("Wounds",curwounds); llOwnerSay("You've been wounded!"); } else if ( curwounds > 0 && curcritical == maxcritical ) { // last wound box lost, now incapacitated curwounds = 0; // force to zero SET_RESILIENCE("Wounds",curwounds); INCAPACITATED(); // show incapacitation } else if ( curwounds == 0 && curcritical > 1 ) { // out of wounds, but still have critical curwounds = 0; // force zero SET_RESILIENCE("Wounds",curwounds); curcritical--; // force zero SET_RESILIENCE("Critical",curcritical); } else { // out of critical wounds too, dead! curwounds = 0; // force zero SET_RESILIENCE("Wounds",curwounds); curcritical = 0; // force zero SET_RESILIENCE("Critical",curcritical); DEAD(); // show death } } // end while } // DEFAULT STATE - load character sheet default { // CHANGED - triggered for many changes to the avatar // TODO reload sim-specific settings on region change changed(integer changes) { if ( changes & CHANGED_REGION || changes & CHANGED_TELEPORT ) { llRequestPermissions(PLAYERID,PERMISSION_TRIGGER_ANIMATION); } } link_message(integer sender_num,integer sender,string str,key id) { if ( sender == MODULE_RESILIENCE || sender == LM_SENDTOATTACHMENT ) return; // ignore our own link messages DEBUG("EVENT: link_message("+(string)sender_num+","+(string)sender+","+str+","+(string)id+")"); list fields = llParseString2List(str,[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 string data = llStringTrim(llList2String(fields,1),STRING_TRIM); // field one is the data list subfields = llParseString2List(data,["="],[]); // break data field into comma-delimited subfields if needed // Module Resilience specific commands if ( command == "set_name" ) { NAME = llList2String(subfields,1); // set the name return; } if ( command == "set_species" ) { SPECIES = llList2String(subfields,1); // set the species; return; } if ( command == "set_background" ) { BACKGROUND = llList2String(subfields,1); // set the species; return; } if ( command == "set_career" ) { CAREER = llList2String(subfields,1); // set the species; return; } if ( command == "set_statistic" ) { string statname = llList2String(subfields,0); // find the boon name integer statrank = llList2Integer(subfields,1); // find the boon rank value // TODO how to verify stat names are valid? if ( statrank >= MINSTAT && statrank <= MAXSTAT ) { // rank valid? STATISTICS = [statname,statrank] + STATISTICS; // add statistic to list } else { // invalid, report it ERROR("STATISTIC "+statname+" rank "+(string)statrank+" value out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT); } return; } if ( command == "set_resilience" ) { string resname = llList2String(subfields,0); // find the boon name integer resrank = llList2Integer(subfields,1); // find the boon rank value // TODO how to verify resilience names are valid? if ( resrank >= MINRESILIENCE && resrank <= MAXRESILIENCE ) { // rank valid? RESILIENCES = [resname,resrank] + RESILIENCES; // add resilience to list CURRENT_RESILIENCES = [resname,resrank] + CURRENT_RESILIENCES; // add to current list too } else { // invalid, report it ERROR("RESILIENCE "+resname+" rank "+(string)resrank+" value out of allowed range: "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE); } return; } if ( command == "set_skill" ) { string skillname = llList2String(subfields,0); // find the skill name integer skillrank = llList2Integer(subfields,1); // find the skill rank // TODO how to verify skill names are valid? if ( skillrank >= MINSKILL && skillrank <= MAXSKILL ) { // skill rank valid? SKILLS = [skillname,skillrank] + SKILLS; // add skill to list } else { // invalid, report it ERROR("SKILL "+skillname+" rank "+(string)skillrank+" value out of allowed range: "+(string)MINSKILL+"-"+(string)MAXSKILL); } return; } if ( command == "armorcurrent" ) { // ARMORCURRENT|integer newcurrentarmor integer rating = llList2Integer(fields,1); if ( rating >= 0 && rating <= MAXARMOR ) { CURARMOR = rating; } return; } if ( command == "debugoff" ) { DEBUGOFF(); return; } if ( command == "debugon" ) { DEBUGON(); return; } if ( command == "hit") { integer attdice = llList2Integer(fields,1); if ( attdice >= 1 && attdice <= 5 ) { HIT(attdice); } return; } if ( command == "healpartial" ) { integer healpart = llList2Integer(fields,1); HEAL(healpart); return; } if ( command == "healfull" ) { HEAL(100); return; } // FIXME HEAL ALL means 100 points of healing if ( command == "reset" ) { RESET(); return;} if ( command == "version" ) { GETVERSION(); return; } // show the version if ( command == "region_setting" ) { // look for deathpoint and respawnpoint string attrib = llToLower(llStringTrim(llList2String(subfields,0),STRING_TRIM)); // find the boon name vector pos = llList2Vector(subfields,1); // find the boon rank value if ( attrib == "deathpoint" ) { DEATHPOINT = pos; } if ( attrib == "respawnpoint" ) { RESPAWNPOINT = pos; } return; } //COMMAND(str); // send to shared command processor for chat and link messages return; } // end of link_message event // STATE ENTRY - called on Reset state_entry() { SETUP(); // show credits and start character sheet load } // TIMER - scheduled events timer() { // Respawn timer ended if ( FLAG_DEAD == TRUE ) { // if dead RPEVENT("respawns!"); HEAL(100); // heal 100 points of damage to respawn //FIXME if ( RESPAWNPOINT != ZERO_VECTOR ) osTeleportAgent(llGetKey(),RESPAWNPOINT); if ( RESPAWNPOINT != ZERO_VECTOR ) llTeleportAgentHome(PLAYERID); } if ( FLAG_INCAPACITATED == TRUE ) { // if hurt HEAL(1); // heal 1 wound } integer curwounds = GET_RESILIENCE("Wounds"); integer maxwounds = GET_MAX_RESILIENCE("Wounds"); if ( curwounds == maxwounds ) { // fully healed? llSetTimerEvent(0.0); // stop timer } } } // end state running // END