User:Allen Kerensky/Myriad Lite Preview 5/Myriad Lite

= Myriad Lite =

Quick Start

 * (required) Wear the HUD.
 * (optional) Wear the hovertext meter
 * (optional) Wear armor, which you should see reported in your chat window.
 * (optional) Wear a holster
 * (optional) Wear a melee or ranged combat weapon

Heads Up Display (HUD)
The HUD is the core of the game system and is the only "mandatory" piece you need to play the game.

You should see Myriad Lite begin loading your character from the character sheet and tell you when its ready to play.
 * 1) Create a simple cube
 * 2) Size the cube to 0.250 meters for X,Y, and Z
 * 3) Apply the Myriad logo to it
 * 4) Set the cube to 50% transparent.
 * 5) Drag and Drop the following pieces from inventory into the cube:
 * 6) The default character sheet notecard
 * 7) The Myriad Lite Module Armor script
 * 8) The Myriad Lite Module BAM script
 * 9) The Myriad Lite script (below) itself (Must be compiled as Mono due to size)
 * 10) Edit the character sheet notecard to set a default character name for yourself at the top.
 * 11) Take the cube into inventory
 * 12) Right-click the cube in inventory and choose the "Attach to HUD -> Bottom Left" attachment point.
 * 1) Edit the HUD attachment to position it.
 * 2) Detach the HUD back to inventory to "save" the position.

Myriad_Lite-v0.1.0-20120125.lsl
// Myriad Lite v0.1.0 20120101 // Copyright (c) 2012 By Allen Kerensky (OSG/SL) // The Myriad RPG System was designed, written, and illustrated by Ashok Desai // Myriad RPG licensed under the Creative Commons Attribution 2.0 UK: England and Wales // http://creativecommons.org/licenses/by/2.0/uk/ // Myriad Lite software Copyright (c) 2011-2012 by Allen Kerensky (OSG/SL) // Baroun's Adventure Machine Copyright (c) 2008-2011 by Baroun Tardis (SL) // Myriad Lite and Baroun's Adventure Machine licensed under the // Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported // http://creativecommons.org/licenses/by-nc-sa/3.0/ // You must agree to the terms of this license before making any use of this software. // If you do not agree to this license, simply delete these materials. // There is no warranty, express or implied, for your use of these materials.

// CONSTANTS - DO NOT CHANGE DURING RUN string VERSION = "0.1.0"; // Allen Kerensky's script version string VERSIONDATE = "20120101"; // Allen Kerensky's script yyyymmdd integer MINXP = 0; // min experience points integer MAXXP = 2320; // max experience points integer MINLEVEL = 1; // min XP level integer MAXLEVEL = 30; // max XP level integer MINSTAT = 1; // min value for statistics integer MAXSTAT = 5; // max human value for a statistic/attribute integer MINRESILIENCE = 1; // min value for resilience integer MAXRESILIENCE = 20; // max value for resilience integer MINBOON = 1; // min value for boon rank integer MAXBOON = 5; // max value for boon rank integer MINFLAW = 1; // min value for flaw rank integer MAXFLAW = 5; // max value for flaw rank integer MINSKILL = 1; // min value for skill rank integer MAXSKILL = 5; // max value for skill rank integer MINEFFECT = 1; // min value for special effect integer MAXEFFECT = 5; // max value for special effect integer MINEQUIPPED = 1; // min number of items player can carry integer MAXEQUIPPED = 100; // max number of items player can carry TODO: what about bullets? integer CHANMYRIAD = -999; // chat sent to ALL Myriad players in region integer CHANCOMMAND = 5; // chat sent by player to their meter integer MINDAMAGE = 1; // min attack dice for weapon integer MAXDAMAGE = 5; // max attack dice for weapon float RESPAWN_TIME = 30.0; // time dead before automatic respawn string DIV = "|"; // message field divider float WEAPON_LENGTH = 0.0; // weapon length in last attack float ARM_LENGTH = 1.0; // arm is 1m long float LEG_LENGTH = 1.5; // leg is 1.5m long integer MELEEATTACKDICE = 1; // 1 attack dice for fists and feet string ANIM_INCAPACITATED = "sleep"; // anim when incapacitated string ANIM_DEAD = "dead"; // anim when dead string ANIM_PUNCH_LEFT = "punch_l"; // anim for left punch string ANIM_PUNCH_RIGHT = "punch_r"; // anim for right punch string ANIM_PUNCH_ONETWO = "punch_onetwo"; // anim for 1-2 punch string ANIM_KICK = "kick_roundhouse_r"; // anim for kick integer SINGLE_PUNCH_DELAY = 1; // recovery time between single punches TODO fix to Myriad rules times integer DOUBLE_PUNCH_DELAY = 2; // recovery time between one-two punches TODO fix to Myriad rules times integer KICK_DELAY = 3; // recovery time between kicks TODO fix to Myriad rules times string CHAN_PREFIX = "0x"; // channel prefix for calculating dynamic channels

integer LM_ATTACHARMOR  = 0x80000000; integer LM_DETACHARMOR  = 0x80000001; integer LM_ARMORCURRENT = 0x80000002; integer LM_ARMORRESET   = 0x80000003; integer LM_ARMORON      = 0x80000004; integer LM_ARMOROFF     = 0x80000005; integer LM_ARMORBATTERY = 0x80000006; integer LM_ARMORRECHARGE = 0x80000007; integer LM_ARMORCHECK   = 0x80000008; integer MAXARMOR = 5; // max legal armor rating

// RUNTIME GLOBALS - CAN CHANGE DURING RUN integer FLAG_DEBUG = FALSE; // see debug messages? key PLAYERID = NULL_KEY; // cached player UUID string PLAYERNAME = ""; // cached player name string NAME = ""; // character name string SPECIES = ""; // species template used for character string BACKGROUND = ""; // background template string CAREER = ""; // career template integer XP = 0; // 0-2320 integer XPLEVEL = 1; // 1-30 list STATISTICS = []; list RESILIENCES = []; list CURRENT_RESILIENCES = []; list BOONS = []; // boons [ string BoonName, integer BoonRank ] list FLAWS = []; // flaws [ string FlawName, integer FlawRank ] list SKILLS = []; // skills [ string SkillName, integer SkillRank ] list EFFECTS = []; // special effects (SFX) [ string EffectName, integer EffectRank ] list STUNTS = []; // pre-set martial combat stunts TODO how will this work? list QUOTES = []; // pre-set social combat quotes TODO how will this work? list EQUIPMENT = []; // Equipment [ string ItemName, integer NumberCarried ] string CARD = "Myriad_Lite_Character_Sheet-v0.0.3-20120101.txt"; // character sheet notecard integer LINE = 0; // reading line number key QUERY = NULL_KEY; // track notecard queries integer HANDMYRIAD = 0; // Myriad channel handle integer CHANPLAYER = 0; // dynamic channel to one player's UUID integer HANDPLAYER = 0; // player channel handle integer CHANOBJECT = 0; // dynamic channel to one object's UUID integer HANDCOMMAND = 0; // command channel handle integer HANDATTACH = 0; // attachment channel handle integer CHANATTACH = 0; // dynamic channel for attachments integer CHANBAM = 0; // dynamic channel for BAM quests integer HANDBAM = 0; // BAM channel update integer FLAG_INCAPACITATED = FALSE; // incapacitated by wounds? integer FLAG_DEAD = FALSE; // killed by critical wounds? vector MOVELOCK; // movelock position when incapacitated or dead float  TAU = 0.5; // movelock tau integer CURARMOR = 0; // highest armor value worn out of all armor worn, not a total integer FLAG_FISTS = FALSE; // using fist-fighter? integer FLAG_CONTROLS = FALSE; // permission to take controls? integer FLAG_ANIMATE = FALSE; // permission to animate avatar? integer TIME_NEXT_ATTACK = 0; // time of last attack integer CONTROLS = 0; // bitfield of controls to monitor float FIELD_OF_ATTACK = PI; // controls field of attack. set later to PI/6 later for 60 degree field of attack in front of avatar integer METERWORN = FALSE; // using meter?

// DEBUG - show debug chat with wearer name for sorting DEBUG(string dmessage) { if ( FLAG_DEBUG == TRUE ) { // are we debugging? llSay(DEBUG_CHANNEL,"DEBUG ("+llKey2Name(PLAYERID)+"): "+dmessage); } }

// ERROR - show errors on debug channel with wearer name for sorting ERROR(string emessage) { llSay(DEBUG_CHANNEL,"ERROR ("+llKey2Name(PLAYERID)+"): "+emessage); }

RPEVENT(string rpevent) { llRegionSay(CHANMYRIAD,"RPEVENT|"+NAME+" ("+PLAYERNAME+") "+rpevent); }

integer GETSTAT(string name) { integer pos = llListFindList(STATISTICS,[name]); if ( pos >= 0 ) { return llList2Integer(STATISTICS,pos + 1); }   return 0; }

integer GET_RESILIENCE(string name) { integer pos = llListFindList(CURRENT_RESILIENCES,[name]); if ( pos >= 0 ) { return llList2Integer(CURRENT_RESILIENCES,pos + 1); }   return 0; }

integer GET_MAX_RESILIENCE(string name) { integer pos = llListFindList(RESILIENCES,[name]); if ( pos >= 0 ) { return llList2Integer(RESILIENCES,pos + 1); }   return 0; }

SET_RESILIENCE(string name,integer value) { if ( value < 0 ) { return;} // out of range if ( value > 20 ) { 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 return; }   integer maxpos = llListFindList(RESILIENCES,[name]); if ( maxpos >=0 ) { maxval = llList2Integer(RESILIENCES,maxpos + 1); } else { // resilience not found return; }   if ( value <= maxval) { CURRENT_RESILIENCES = llListReplaceList(CURRENT_RESILIENCES,[value],curpos + 1, curpos + 1); } }

// 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,"ARMORHIT"); } 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,"ARMORBLOCKED"); } }

// 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 > 0 && curcritical == maxcritical ) { // wound boxes left? curwounds--; // scratch off one SET_RESILIENCE("Wounds",curwounds); METER; // update llOwnerSay("You've been wounded!"); } else if ( curwounds < 1 && curcritical > 0 ) { // incapacitated curwounds = 0; // force to zero SET_RESILIENCE("Wounds",curwounds); curcritical--; // scratch off a critical wound box SET_RESILIENCE("Critical",curcritical); INCAPACITATED; // show incapacitation } else if ( curwounds < 1 && curcritical < 1 ) { // out of critical wounds? curwounds = 0; // force zero SET_RESILIENCE("Wounds",curwounds); curcritical = 0; // force zero SET_RESILIENCE("Critical",curcritical); DEAD; // show death }   } // end while }

// INCAPACITATED - player lost all WOUNDS - unable to act INCAPACITATED { FLAG_INCAPACITATED = TRUE; // yes, we're now incapacitated MOVELOCK = llGetPos; llMoveToTarget(MOVELOCK,TAU); METER; // update meter llStartAnimation(ANIM_INCAPACITATED); // "we're hurt and down" animation RPEVENT("has been incapacitated!"); llOwnerSay("You've been incapacitated!"); llSetTimerEvent(RESPAWN_TIME); // heal in a bit }

// DEAD - player is dead, kill them and wait to respawn DEAD { FLAG_DEAD = TRUE; // remember that we're now dead METER; // update hover text llStartAnimation(ANIM_DEAD); // start dead animation RPEVENT("has been killed!"); llOwnerSay("You've been killed!"); llSetTimerEvent(RESPAWN_TIME); // respawn in a bit }

// 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 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 revived = TRUE; // show revival in summary report DEBUG("Heal: Revived!"); llStopMoveToTarget; }           } // 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 }

// GET SKILL RANK // Requires a SKILL NAME // Returns the rank value for that skill, or zero if player doesn't have skill integer GET_SKILL_RANK(string askill) { integer atskill = 0; // start with skill zero in case character does not have that skill integer skillpos = llListFindList(SKILLS,[askill]); // position of skill name in list 0-n, or -1 if not found if ( skillpos >= 0 ) { // skill name was somewhere in list atskill = llList2Integer(SKILLS,++skillpos); // move to next pos in list after skillname and get skill rank there if ( atskill >= MINSKILL && atskill <= MAXSKILL ) return atskill; // found skill with value in range, return it; } // fall through... return 0; // player has zero levels in skill }

// 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 highroll = 0; // clear out the highest roll while( attribute-- ) { // roll a dice for each point of the attribute integer roll = 1+(integer)llFrand(5.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; // now, return the total of highest dice roll + skill value }

// An Unopposed Ability Test - Myriad PDF p. 19, Myriad Special Edition p. 25 // Requires TargetNumber, Attribute Name, Skill Name // Returns TRUE for Success and False for Fail integer UNOPPOSED_TEST(integer targetnum,integer tattribute,integer tskill ) { integer check = ABILITY_TEST(tattribute,tskill); // calculate the player's ability test value if ( check >= targetnum ) return TRUE; // player won the ability test return FALSE; // player lost the ability test }

// An Opposed Ability Test - Myriad PDF p. 19 Myriad Special Edition p. 25 // Requires Attacker Attribute Name, Attacker Skill Name, Defender Attribute Name, Defender Skill Name // Returns TRUE for Success, FALSE for failure integer OPPOSED_TEST(integer aattrib,integer askill,integer dattrib,integer dskill) { integer acheck = ABILITY_TEST(aattrib,askill); // calculate attacker's ability test integer dcheck = ABILITY_TEST(dattrib,dskill); // calculate defender's ability test if ( acheck > dcheck ) return TRUE; // attacker more than defender = attacker wins return FALSE; // defender wins }

// SETUP - begin bringing the HUD online SETUP { CREDITS; // show Myriad credits as required by the Creative Commons - Attribution license PLAYERID = llGetOwner; // remember the owner's UUID PLAYERNAME = llKey2Name(PLAYERID); // remember the owner's legacy name llSetText("",<0,0,0>,0); // clear any previous hovertext llOwnerSay("Loading character sheet. Please wait..."); // tell player we're waiting for data server if ( llGetInventoryName(INVENTORY_NOTECARD,0) == CARD ) { // check inventory for notecard QUERY = llGetNotecardLine(CARD,LINE++); // ask for line from notecard and advance to next line } else { ERROR("Cannot locate character sheet from notecard: "+CARD); // TODO: what next? choose defaults? fall over? } }

// CREDITS comply with Myriad RPG Creative Common-Attribution legal requirement CREDITS { llOwnerSay("The Myriad RPG System was designed, written, and illustrated by Ashok Desai."); llOwnerSay("RPG System licensed under the Creative Commons Attribution 2.0 UK: England and Wales."); llOwnerSay("Myriad Lite v"+VERSION+" "+VERSIONDATE+" Copyright (c) 2011 by Allen Kerensky (OSG/SL)"); llOwnerSay("Licensed under Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported."); }

// RESET - shut down running animations then reset the script to reload character sheet RESET { if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) { // don't allow reset if already on respawn timer llOwnerSay("Cannot reset while incapacitated or dead. You will respawn in a few moments."); return; }   llOwnerSay("Resetting Myriad Lite. Please wait..."); // stop all running animations if ( FLAG_ANIMATE == TRUE ) { // do we have permission to animate? list anims = llGetAnimationList(PLAYERID); // 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 }   }    llMessageLinked(LINK_THIS,0,"BAMRESET",NULL_KEY); // reset the BAM module too llMessageLinked(LINK_THIS,LM_ARMORRESET,"ARMORRESET",PLAYERID); // send reset to armor module llResetScript; // now reset }

// METER - update a hovertext health meter or HUD bar graph METER { if ( METERWORN == FALSE ) return; integer curwounds = GET_RESILIENCE("Wounds"); integer maxwounds = GET_MAX_RESILIENCE("Wounds"); integer curcritical = GET_RESILIENCE("Critical"); integer maxcritical = GET_MAX_RESILIENCE("Critical"); // create a meter message packet string message = "METER"+DIV+PLAYERNAME+DIV+NAME+DIV+(string)curwounds+DIV+(string)maxwounds+DIV+(string)curcritical+DIV+(string)maxcritical+DIV+(string)FLAG_DEAD+DIV+(string)FLAG_INCAPACITATED; llRegionSay(CHANMYRIAD,message); // send the update to region for scorekeepers, etc llWhisper(CHANATTACH,message); // whisper to the wearer's actual meter DEBUG("Wounds: "+(string)curwounds+" of "+(string)maxwounds+" wound boxes. Critical: "+(string)curcritical+" of "+(string)maxcritical+" critical wound boxes."); }

// HAND_TO_HAND attack for fist fighter // TODO fix timing to Myriad rules HAND_TO_HAND(integer delay,string anim,float reach) { // TODO need "someone moves to attack" RP event messages here? TIME_NEXT_ATTACK = llGetUnixTime + delay; // attack again after delay for attack and followup recovery llStartAnimation(anim); // run the punch left animation WEAPON_LENGTH = reach; // save the weapon reach from the last attack llSensor("",NULL_KEY,(AGENT|ACTIVE|PASSIVE),reach,FIELD_OF_ATTACK); // sensor sweep to see if we hit someone }

// DEBUGON - turn on the DEBUG flag DEBUGON { FLAG_DEBUG = TRUE; // set debug flag TRUE llOwnerSay("Debug Mode Activated"); }

// DEBUGOFF - turn off the DEBUG flag DEBUGOFF { FLAG_DEBUG = FALSE; // set debug flag to FALSE llOwnerSay("Debug Mode Deactivated"); }

// COMBATOFF - turn off fist fighter COMBATOFF { FLAG_FISTS = FALSE; // disable flag to exit/ignore any more control events if ( FLAG_CONTROLS == TRUE ) { // do we have control permission? llReleaseControls; // release the controls FLAG_CONTROLS = FALSE; // remember that we released controls }   llOwnerSay("Close Combat Deactivated"); }

// COMBATON - turn on fist fighter COMBATON { FLAG_FISTS = TRUE; // yep, using fist fighter, for control events if ( FLAG_CONTROLS == FALSE ) { // do we have permission to read controls? No? we need it. llReleaseControls; // release any previous controls on avatar llRequestPermissions(PLAYERID,PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION); // request permissions needed for fist fighter }                llOwnerSay("Close Combat Activated"); }

// COMMAND - process chat and link message commands together COMMAND(string msg) { // break down the commands and messages into units we can work with list fields = llParseString2List(msg,[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 // ARMOR if ( command == "attacharmor" ) { llMessageLinked(LINK_THIS,LM_ATTACHARMOR,msg,PLAYERID); return; } if ( command == "detacharmor" ) { llMessageLinked(LINK_THIS,LM_DETACHARMOR,msg,PLAYERID); return; } if ( command == "armoroff" ) { llMessageLinked(LINK_THIS,LM_ARMOROFF,msg,PLAYERID); return; } if ( command == "armoron" ) { llMessageLinked(LINK_THIS,LM_ARMORON,msg,PLAYERID); return; } if ( command == "checkarmor" ) { llMessageLinked(LINK_THIS,LM_ARMORCHECK,msg,PLAYERID); return; } if ( command == "checkbattery" ) { llMessageLinked(LINK_THIS,LM_ARMORBATTERY,msg,PLAYERID); return; } if ( command == "recharge" ) { llMessageLinked(LINK_THIS,LM_ARMORRECHARGE,msg,PLAYERID); return; }

if ( command == "checkammo" ) { CHECKAMMO; return;} // check ammo in weapons if ( command == "combatoff") { COMBATOFF; return; } // turn off fist fighter if ( command == "combaton" ) { COMBATON; return; } // turn on the fist fighter if ( command == "credits" ) { CREDITS; return;} // show the credits including version number if ( command == "debugoff" ) { DEBUGOFF; return; } // player turn off debugging if ( command == "debugon" ) { DEBUGON; return;} // player turn on debugging if ( command == "drawboth" ) { DRAW("both"); return; } // draw both weapons if ( command == "drawleft" ) { DRAW("left"); return; } // draw weapon in left hand if ( command == "drawright" ) { DRAW("right"); return; } // draw weapon using right hand if ( command == "holsterboth" ) { HOLSTER("both"); return; } // holster both weapons if ( command == "holsterleft" ) { HOLSTER("left"); return; } // holster weapon in left hand if ( command == "holsterright" ) { HOLSTER("right"); return; } // holster weapon in right hand if ( command == "quest" ) { QUEST; return; } // check our current quest status if ( command == "reload" ) { RELOAD; return;} // reload weapons if ( command == "reset" ) { RESET; return;} // reset HUD if ( command == "rumor" ) { RUMOR(msg); return;} // rumors if ( command == "safetyoff" ) { SAFETYOFF; return;} // unsafe the weapons if ( command == "safetyon" ) { SAFETYON; return;} // safe the weapons if ( command == "sheatheboth" ) { SHEATHE("both"); return; } // sheathe both weapons if ( command == "sheatheleft" ) { SHEATHE("left"); return; } // sheathe weapon in left hand if ( command == "sheatheright" ) { SHEATHE("right"); return; } // sheathe weapon in right hand if ( command == "version" ) { CREDITS; return;} // show the credits including version number }

// RUMOR CONTROL RUMOR(string rumor) { llMessageLinked(LINK_THIS,0,"RUMOR_GET|"+rumor,PLAYERID); // get a rumor }

// QUEST STATUS QUEST { llMessageLinked(LINK_THIS,0,"BAMSTATUS",PLAYERID); // send a status request to BAM Modules }

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

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

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

// CHECK AMMO CHECKAMMO { llWhisper(CHANATTACH,"CHECKAMMO"); }

// RELOAD RELOAD { llWhisper(CHANATTACH,"RELOAD"); }

// SAFETY ON SAFETYON { llWhisper(CHANATTACH, "SAFETYON"); }

// SAFETY OFF SAFETYOFF { llWhisper(CHANATTACH, "SAFETYOFF"); }

// DEFAULT STATE - load character sheet default {

// STATE ENTRY - called on Reset state_entry { SETUP; // show credits and start character sheet load }

// on_rez - when rezzed to ground or from inventory as attachment during login on_rez(integer params) { params = 0; // LSLINT RESET; // force to go through state entry }

// attach - when attached or detached from inventory or during login attach(key id) { id = NULL_KEY; // LSLINT RESET; // force to go through state entry }

// dataserver called for each line of notecard requested - process character sheet dataserver(key queryid,string data) { if ( queryid == QUERY ) { // ataserver gave us line we asked for? if ( data != EOF ) { // we're not at end of notecard file? if ( llGetSubString(data,0,0) == "#" ) { // does this line start with comment mark? QUERY = llGetNotecardLine(CARD,LINE++); // ignore comment and ask for the next line return; }               // Parse non-comment lines in keyword = value[,value,...] format list FIELDS = llParseString2List(data,["="],[]); // break line of text into = delimited fields string CMD = llStringTrim(llList2String(FIELDS,0),STRING_TRIM); // field zero is the "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 if ( CMD == "NAME" )       { NAME = DATA; } // TODO verify names are appropriate if ( CMD == "SPECIES" )    { SPECIES = DATA; } // TODO verify valid species template name if ( CMD == "BACKGROUND" ) { BACKGROUND = DATA; } // TODO verify valid background template name if ( CMD == "CAREER" )    { CAREER = DATA; } // TODO verify valid career template name if ( CMD == "XP" ) { integer amount = (integer)DATA; // convert to number if ( amount >= MINXP && amount <= MAXXP ) { // XP valid? XP = amount; // store it                   } else { // invalid, report it                        ERROR("XP amount out of allowed range: "+(string)MINXP+"-"+(string)MAXXP); }               }                if ( CMD == "XPLEVEL" ) { integer amount = (integer)DATA; // convert to number if ( amount >= MINLEVEL && amount <= MAXLEVEL ) { // XPLEVEL valid? XPLEVEL = amount; // store it                   } else { // invalid, report it                        ERROR("XPLEVEL amount out of allowed range: "+(string)MINLEVEL+"-"+(string)MAXLEVEL); }               }                if ( CMD == "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); }               }                if ( CMD == "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); }               }                if ( CMD == "BOON" ) { string boonname = llList2String(SUBFIELDS,0); // find the boon name integer boonrank = llList2Integer(SUBFIELDS,1); // find the boon rank value // TODO how to verify boon names are valid? if ( boonrank >= MINBOON && boonrank <= MAXBOON ) { // rank valid? BOONS = [boonname,boonrank] + BOONS; // add boon to list } else { // invalid, report it                       ERROR("BOON "+boonname+" rank "+(string)boonrank+" value out of allowed range: "+(string)MINBOON+"-"+(string)MAXBOON); }               }                if ( CMD == "FLAW" ) { string flawname = llList2String(SUBFIELDS,0); // find the flaw name integer flawrank = llList2Integer(SUBFIELDS,1); // find the flaw rank value // TODO how to verify flaw names are valid? if ( flawrank >= MINFLAW && flawrank <= MAXFLAW ) { // rank valid? FLAWS = [flawname,flawrank] + FLAWS; // add flaw to list } else { // invalid, report it                       ERROR("FLAW "+flawname+" rank "+(string)flawrank+" value out of allowed range: "+(string)MINFLAW+"-"+(string)MAXFLAW); }               }                if ( CMD == "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); }               }                if ( CMD == "EFFECT" ) { string effectname = llList2String(SUBFIELDS,0); // find effect name integer effectrank = llList2Integer(SUBFIELDS,1); // find effect rank // TODO how to verify effect name? if ( effectrank >= MINEFFECT && effectrank <= MAXEFFECT ) { // effect rank valid? EFFECTS = [effectname,effectrank] + EFFECTS; // add effect to list } else { // invalid, report it                       ERROR("EFFECT "+effectname+" rank "+(string)effectrank+" value out of allowed range: "+(string)MINEFFECT+"-"+(string)MAXEFFECT); }               }                if ( CMD == "STUNT" ) { // TODO how to handle stunts with commas? string stuntname = llList2String(SUBFIELDS,0); // find stunt // TODO how to verify stunt? STUNTS = [stuntname] + STUNTS; // add stunt to list }               if ( CMD == "QUOTE" ) { // TODO how to handle quotes with commas? string quotename = llList2String(SUBFIELDS,0); // find quote QUOTES = [quotename] + QUOTES; // add quote to list }               if ( CMD == "EQUIPMENT" ) { string equipmentname = llList2String(SUBFIELDS,0); // find equipment note integer equipmentamount = llList2Integer(SUBFIELDS,1); // find equipment count // TODO how to verify the equipment name is valid? if ( equipmentamount >= MINEQUIPPED && equipmentamount <= MAXEQUIPPED ) { // amount valid? EQUIPMENT = [equipmentname,equipmentamount] + EQUIPMENT; // add equipment to list } else { // invalid, report it                       ERROR("EQUIPMENT "+equipmentname+" amount "+(string)equipmentamount+" value out of allowed range: "+(string)MINEQUIPPED+"-"+(string)MAXEQUIPPED); }               }                QUERY = llGetNotecardLine(CARD,LINE++); // finished with known keywords, get next line } else { // end of notecard // TODO how to verify entire character sheet was completed and loaded? state running; // we're out of notecard, so character sheet is loaded - start playing } // end if data not equal eof } // end if query id equal } // end if data server event } // end default state

// STATE RUNNING - character sheet loaded - player is active in the game state running {

// STATE ENTRY state_entry { llOwnerSay("Character Sheet loaded. You are now ready to roleplay."); if ( HANDMYRIAD != 0 ) llListenRemove(HANDMYRIAD); HANDMYRIAD = llListen(CHANMYRIAD,"",NULL_KEY,""); // setup listener for Myriad RP events if ( HANDCOMMAND != 0 ) llListenRemove(HANDCOMMAND); HANDCOMMAND = llListen(CHANCOMMAND,"",PLAYERID,""); // listen to chat commands from owner CHANPLAYER = (integer)("0x"+llGetSubString((string)PLAYERID,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)PLAYERID,1,7)); // attachment-specific channel if ( HANDATTACH != 0 ) llListenRemove(HANDATTACH); HANDATTACH = llListen(CHANATTACH,"",NULL_KEY,""); // listen for messages from attachments CHANBAM = (integer)(CHAN_PREFIX + llGetSubString((string)PLAYERID,-7,-1)); if ( HANDBAM != 0 ) llListenRemove(HANDBAM); HANDBAM = llListen(CHANBAM,"",NULL_KEY,""); // start listener with listenremove handle // setup bitfield of controls we're going to monitor in fist fighter mode CONTROLS = CONTROL_ML_LBUTTON | CONTROL_LBUTTON | CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN; FIELD_OF_ATTACK = PI/6; // set fist fighter field of attack to +/- 30 degree cone from direction avatar faces - PERSONAL CHOICE NOT IN MYRIAD RULES llOwnerSay("Registering any Myriad Lite-compatible attachments..."); llWhisper(CHANATTACH,"REGISTERATTACHMENTS"); // ask for attachments on their dynamic channel llRequestPermissions(PLAYERID,PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS); // calculate player's dynamic BAM channel METER; // update hovertext QUEST; // update the BAM Module llOwnerSay("HUD startup complete. "+(string)llGetFreeMemory+" bytes free."); }

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

// ATTACH - logged in with meter or worn from inventory/ground while running attach(key id) { id = NULL_KEY; // LSLINT RESET; // a reset to reload character }

// 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? llOwnerSay("Inventory changed. Reloading."); RESET; // saved a new character sheet? - reset and re-read it. }       if ( changes & CHANGED_REGION || changes & CHANGED_TELEPORT ) { llRequestPermissions(PLAYERID,PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS); METER; // update the meter after a shift }   }

// RUN_TIME_PERMISSIONS run_time_permissions(integer perm) { if ( perm & PERMISSION_TAKE_CONTROLS ) { // was script granted permission to take avatar controls? llTakeControls(CONTROLS,TRUE,TRUE); // then take them, but still pass them to other scripts like vehicles FLAG_CONTROLS = TRUE; // remember that we got permission for this }       if ( perm & PERMISSION_TRIGGER_ANIMATION ) { // we script granted permission to trigger animations on the avatar? FLAG_ANIMATE = TRUE; // remember that we got permission for this }   }

// TOUCH_START - touch HUD for adventure update touch_start(integer total_number) { total_number = 0; // LSLINT string action = llGetLinkName(llDetectedLinkNumber(0)); // get name of prim clicked in link set if ( action != "" && action != llGetObjectName ) { // someone clicked a named button prim on this linkset COMMAND(action); // try that prim name as a command return; }       METER; }

// CONTROL - read arrow keys and mouse button in first or third person mode control(key id,integer level,integer edge) { id = NULL_KEY; // LSLINT if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // dead or incapacitated can't fight if ( FLAG_FISTS == FALSE ) return; // not using fist fighter if ( FLAG_ANIMATE == FALSE ) return; // can't show animations if ( llGetUnixTime <= TIME_NEXT_ATTACK ) return; // too soon since last attack

// Is the mouse button held down? if ( ( level & CONTROL_LBUTTON ) || ( level & CONTROL_ML_LBUTTON ) ) { // Mouse + Left Arrow = left-handed punch if ( ( edge & CONTROL_LEFT ) || ( edge & CONTROL_ROT_LEFT ) ) { // TODO fix timing to Myriad rules HAND_TO_HAND(SINGLE_PUNCH_DELAY,ANIM_PUNCH_LEFT,ARM_LENGTH); // left punch with 1m reach, 1 second recover return; }           // Mouse + Rigth Arrow = right-handed punch if ( ( edge & CONTROL_RIGHT ) || ( edge & CONTROL_ROT_RIGHT ) ) { // TODO fix timing to Myriad rules HAND_TO_HAND(SINGLE_PUNCH_DELAY,ANIM_PUNCH_RIGHT,ARM_LENGTH); // right punch, 1m reach, 1 second recover return; }           if ( ( edge & CONTROL_UP ) || ( edge & CONTROL_FWD ) ) { // TODO fix timing to Myriad rules HAND_TO_HAND(DOUBLE_PUNCH_DELAY,ANIM_PUNCH_ONETWO,ARM_LENGTH); // left-right combo, 1m reach, 2 second recover return; }           if ( ( edge & CONTROL_DOWN ) || ( edge & CONTROL_BACK ) ) { // TODO fix timing to Myriad rules HAND_TO_HAND(KICK_DELAY,ANIM_KICK,LEG_LENGTH); // kick, 1.5m reach, 3 second recover return; }       } // end if mouse button held down } // end of control event

// SENSOR for who was in attack range and field of attack sensor(integer num_detected) { while(num_detected--) { // count down all results in range and field of attack key hitwho = llDetectedKey(num_detected); // key of who or what we hit string name = llDetectedName(num_detected); // name of who we hit integer attskill = GET_SKILL_RANK("Close Combat"); // get our close combat skill rank integer victimchan = (integer)("0x"+llGetSubString(hitwho,0,6)); // calculate dynamic channel of who we hit RPEVENT("strikes at "+name+" in Close Combat!"); // tell victim HUD to perform a CLOSE COMBAT opposed ability test // attacker Power stat/Close Combat skill rank vs. Defender Grace stat/Close Combat skill rank // See Myriad PDF pp. 21-22 and Myriad Special Edition pp.27-28 llRegionSay(victimchan,"CLOSEHIT"+DIV+(string)GETSTAT("Power")+DIV+(string)attskill+DIV+(string)MELEEATTACKDICE+DIV+(string)PLAYERID+DIV+"fists and feet"); llOwnerSay("You struck at "+name+" in Close Combat"); } // end while } // end sensor

// NO_SENSOR - this is called when the attack sensor detects nothing in range and field of attack no_sensor { // here to fix rare bugs where sensor fails unles no_sensor is in state too }

// TIMER - scheduled events timer { // Respawn timer ended if ( FLAG_DEAD == TRUE ) { // if dead RPEVENT("respawns!"); RESET; // reset and reload character }       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 }   }    // LINK MESSAGE - commands to and from other prims in HUD link_message(integer sender,integer num,string msg, key id) { sender = 0; // LSLINT id = NULL_KEY; // LSLINT if ( num == LM_ARMORCURRENT ) { // ARMORCURRENT|integer newcurrentarmor list fields = llParseString2List(msg,[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 if ( command == "armorcurrent" ) { integer rating = llList2Integer(fields,1); if ( rating > 0 && rating <= MAXARMOR ) { CURARMOR = rating; }               return; }           return; }       COMMAND(msg); // 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) { speakername = ""; // LSLINT // calculate the dynamic channel of who is speaking in case we need to return commands CHANOBJECT = (integer)(CHAN_PREFIX+llGetSubString((string)speakerid,0,6));

// break down the commands and messages into units we can work with list fields = llParseString2List(message,[DIV],[]); // break into list of fields based on DIVider 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); // send to shared command processor for chat and link messages return; } // end of if channel == player commands

// --- BAM CHANNEL if ( channel == CHANBAM ) { llMessageLinked(LINK_THIS,channel,message,speakerid); // send BAM to Module return; } // end if channel BAMCHAN

// --- Myriad Lite regionwide messages if ( channel == CHANMYRIAD ) { // handle Myriad system messages if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting string oldname = llGetObjectName; // save the current object name llSetObjectName("Myriad RP Event"); // change the object name to               llOwnerSay(llList2String(fields,1)); // now tell the owner the rest of the RPEVENT| message llSetObjectName(oldname); // restore the HUD back to its original name return; } // end if RPEVENT return; } // end if channel == CHANMYRIAD

// --- ATTACHMENT CHANNEL if ( channel == CHANATTACH ) { // handle the attachment commands if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // can't mess with attachments while down if ( command == "ATTACHARMOR" ) { // player attached armor somewhere llMessageLinked(LINK_THIS,LM_ATTACHARMOR,message,PLAYERID); return; }           if ( command == "DETACHARMOR" ) { // player attached armor somewhere llMessageLinked(LINK_THIS,LM_DETACHARMOR,message,PLAYERID); return; }           if ( command == "ATTACHMELEE" || command == "ATTACHRANGED" ) { // holding a weapon rather than using fists? FLAG_FISTS = FALSE; // turn off fist fighter if ( FLAG_CONTROLS == TRUE ) { // if we own controls... llReleaseControls; // release them FLAG_CONTROLS = FALSE; // and remember }               return; }           if ( command == "DETACHMELEE" || command == "DETACHRANGED" ) { // are we going back to fists? FLAG_FISTS = TRUE; // turn fist fighter back on               if ( FLAG_CONTROLS == FALSE ) { // if we don't have controls llReleaseControls; // release them just in case llRequestPermissions(PLAYERID,PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION); // then ask to take controls }               return; }           if ( command == "ATTACHMETER" ) { METERWORN = TRUE; // we need to send meter events METER; // send update return; }           if ( command == "DETACHMETER" ) { METERWORN = FALSE; return; }       }        // --- CHANPLAYER if ( channel == CHANPLAYER ) { // handle player dynamic commands if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting string oldname = llGetObjectName; // save the current object name llSetObjectName("Myriad RP Event (Private)"); // change the object name to               llOwnerSay(llList2String(fields,1)); // now tell the owner the rest of the RPEVENT| message llSetObjectName(oldname); // restore the HUD back to its original name return; } // end if RPEVENT if ( command == "UNOPPOSED_CHECK" ) { // object in sim wants a simple skill check integer targetnum = llList2Integer(fields,1); // what is unopposed check target num? integer tattrib = llList2Integer(fields,2); // target attribute integer tskill = llList2Integer(fields,3); // target skill UNOPPOSED_TEST(targetnum,tattrib,tskill); return; }           // we've been hit and have to make an opposed ability test to avoid it            if ( command == "HITCHECK" || command == "RANGEDHIT" || command == "CLOSEHIT" ) { // mortal combat attack message? integer attackstat = llList2Integer(fields,1); // get attackers stat integer attackskill = llList2Integer(fields,2); // get attackers skill integer attackdice = llList2Integer(fields,3); // get attacker object's attack dice key owner = llList2Key(fields,4); // get attacker object's key string item = llList2String(fields,5); // get attacker object name if ( attackstat < MINSTAT || attackstat > MAXSTAT ) { // is the attack stat value out of allowed range? ERROR("Attack stat value "+(string)attackstat+" out of range: "+(string)MINSTAT+"-"+(string)MAXSTAT); // TODO make a tattletale RP event? return; }               if ( attackskill < MINSKILL || attackstat > MAXSKILL ) { // is the attack skill value out of allowed range? ERROR("Attack skill value "+(string)attackskill+" out of range: "+(string)MINSKILL+"-"+(string)MAXSKILL); // TODO make a tattletale RP event? return; }                            if ( attackdice < MINDAMAGE || attackdice > MAXDAMAGE ) { // is the attacking weapon's attack dice value out of allowed range? ERROR("Attack dice value out of range: "+(string)MINDAMAGE+"-"+(string)MAXDAMAGE); // TODO make a tattletale RP event? return; }               integer skillamount = 0; // create a place to hold the defenders mortal combat skill rank if ( command == "HITCHECK" || command == "RANGEDHIT" ) { // if this is ranged combat skillamount = GET_SKILL_RANK("Ranged Combat"); // get ranged combat skill rank }               if ( command == "CLOSEHIT" ) { // if this is close combat skillamount = GET_SKILL_RANK("Close Combat"); // get close combat skill rank }               // see if we're hit integer amihit = OPPOSED_TEST(attackstat,attackskill,GETSTAT("Grace"),skillamount); // attacker power+skill vs. defender grace+skill if ( amihit == TRUE ) { // we're hit! if ( command == "HITCHECK" || command == "RANGEDHIT" ) { // we're hit by ranged attack llOwnerSay("You've been hit in ranged combat by "+llKey2Name(owner)+"'s "+item+"!"); }                   if ( command == "CLOSEHIT" ) { // we're hit by melee or hand to hand attack llOwnerSay("You been hit in close combat by "+llKey2Name(owner)+"!"); }                   HIT(attackdice); // apply the hit }               return; }           // Heal Some Damage if ( command == "HEALPARTIAL" ) { // only a partial heal integer boxeshealed = llList2Integer(fields,1); // how many boxes are we healing? HEAL(boxeshealed); // heal X number of boxes METER; // update return; }           if ( command == "HEALFULL" ) { // full heal, reset state HEAL(100); // heal up to 100 damage METER; // update return; }           // Actions NOT Allowed When Dead/Incapacitated go below here if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // If Your Bullet has hit, fire a hitcheck regionwide at targetplayer's channel if ( command == "CLOSECOMBAT" || command == "RANGEDCOMBAT" || command == "TOHIT" ) { integer attdice = llList2Integer(fields,1); // get attack dice of weapon used string hitwho = llList2String(fields,2); // get UUID of who we hit string bywho = llList2String(fields,3); // should be our own UUID string bywhat = llList2String(fields,4); // name of item we hit with (good for bullets/missiles)

integer victimchan = (integer)("0x"+llGetSubString(hitwho,0,6)); // calculate victim's dynamic channel integer attskill = 0; // zero our attack skill if ( command == "RANGEDCOMBAT" || command == "TOHIT" ) { // if this is a ranged attack attskill = GET_SKILL_RANK("Ranged Combat"); // get ranged combat skill level llRegionSay(victimchan,"RANGEDHIT"+DIV+(string)GETSTAT("Power")+DIV+(string)attskill+DIV+(string)attdice+DIV+bywho+DIV+bywhat); // attack! return; }               if ( command == "CLOSECOMBAT" ) { // if this is melee or hand to hand attskill = GET_SKILL_RANK("Close Combat"); // get close combat skill level llRegionSay(victimchan,"CLOSEHIT"+DIV+(string)GETSTAT("Power")+DIV+(string)attskill+DIV+(string)attdice+DIV+bywho+DIV+bywhat); // attack! return; }               return; } // end if CLOSECOMBAT/RANGEDCOMBAT/TOHID } // end if channel CHANPLAYER } // end listen } // end state running // END