User:Allen Kerensky/Myriad Lite Dev/Myriad Lite-v0.1.2-20120317.lsl
From OpenSimulator
< User:Allen Kerensky | Myriad Lite Dev(Difference between revisions)
(created) |
m (set FLAG_DEBUG false by default) |
||
Line 67: | Line 67: | ||
// RUNTIME GLOBALS - CAN CHANGE DURING RUN | // RUNTIME GLOBALS - CAN CHANGE DURING RUN | ||
− | integer FLAG_DEBUG = | + | integer FLAG_DEBUG = FALSE; // see debug messages? |
key PLAYERID = NULL_KEY; // cached player UUID | key PLAYERID = NULL_KEY; // cached player UUID | ||
string PLAYERNAME = ""; // cached player name | string PLAYERNAME = ""; // cached player name |
Revision as of 19:12, 17 March 2012
Myriad_Lite-v0.1.2-20120317.lsl
// Myriad_Lite-v0.1.2-20120317.lsl // 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.2"; // Allen Kerensky's script version string VERSIONDATE = "20120317"; // 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 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 LM_SENDTOATTACHMENT = 0x80000000; // 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,"("+llKey2Name(PLAYERID)+") HUD: "+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,"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"); } } // 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,MODULE_HUD,"BAMRESET",NULL_KEY); // reset the BAM module too llMessageLinked(LINK_THIS,MODULE_HUD,"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 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 if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "armor" ) { SENDTOMODULE(msg,PLAYERID); return; } if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "rumor" ) { RUMOR(msg); return; } } SENDTOMODULE(string msg,key speaker) { DEBUG("SENDTOMODULE("+msg+")"); llMessageLinked(LINK_THIS,MODULE_HUD,msg,speaker); } SENDTOATTACHMENT(string msg) { DEBUG("SENDTOATTACHMENT("+msg+")"); llWhisper(CHANATTACH,msg); } // RUMOR CONTROL RUMOR(string cmdrumor) { DEBUG("Sending to rumor module: "+cmdrumor); llMessageLinked(LINK_THIS,MODULE_HUD,cmdrumor,PLAYERID); // relay rumor commands to module } // QUEST STATUS QUEST() { llMessageLinked(LINK_THIS,MODULE_HUD,"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 RUMOR("RUMOR_RESET"); // reset the rumor module to load new rumor server UUID if needed 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 sending_module,string str, key id) { if ( sending_module == MODULE_HUD ) return; // ignore our own link messages DEBUG("HUD Link Message: "+str); sender = 0; // LSLINT id = NULL_KEY; // LSLINT 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 if ( command == "armorcurrent" ) { // ARMORCURRENT|integer newcurrentarmor integer rating = llList2Integer(fields,1); if ( rating >= 0 && rating <= MAXARMOR ) { CURARMOR = rating; } return; } if ( llGetSubString(command,0,4) == "armor" ) { SENDTOATTACHMENT(str); return; } // process armor messages if ( sending_module == LM_SENDTOATTACHMENT ) { SENDTOATTACHMENT(str); return; } // send module messages to attachments COMMAND(str); // 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) { DEBUG("HUD Listen: channel=["+(string)channel+"] name=["+speakername+"] id=["+(string)speakerid+"] message=["+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 ) { SENDTOMODULE(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 ( llToLower(llGetSubString(llStringTrim(command,STRING_TRIM),0,4)) == "armor" ) { SENDTOMODULE(message,PLAYERID); return; } // process armor messages 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 // incoming message from rumor server? if ( llGetSubString(llToLower(llStringTrim(command,STRING_TRIM)),0,4) == "rumor" ) { llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid); // send message and key of speaker to rumors return; } 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