User:Allen Kerensky/Myriad Lite Preview2/Myriad Lite v0.0.9 20110606

From Second Life Wiki
< User:Allen Kerensky
Revision as of 14:22, 3 August 2011 by Allen Kerensky (talk | contribs) (applied a 20110803 patch to GET_SKILL_RANK function to fix melee combat and OSgrid/opensim compatibility)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Myriad Lite v0.0.9 20110606

<lsl> //============================================================================ // Myriad Lite v0.0.9 20110606 // Myriad Lite software Copyright (c) 2011 by Allen Kerensky (OSG/SL) // Licensed under Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported // The Myriad RPG System was designed, written, and illustrated by Ashok Desai // Licensed under the Creative Commons Attribution 2.0 UK: England and Wales. //============================================================================

key PLAYERID = NULL_KEY; string PLAYERNAME = "";

// Metadata string NAME = ""; string SPECIES = ""; // name of species template string BACKGROUND = ""; // name of background template string CAREER = ""; // name of career template

// Experience integer MINXP = 0; integer MAXXP = 2320; integer XP = 0; // 0-2320 integer MINLEVEL = 1; integer MAXLEVEL = 30; integer XPLEVEL = 1; // 1-30

// Attributes integer MINSTAT = 1; integer MAXSTAT = 5; integer POWER = 0; integer GRACE = 0; integer INTELLECT = 0; integer SPIRIT = 0;

// Resiliences integer MINRESILIENCE = 1; integer MAXRESILIENCE = 20; integer WOUNDS = 0; integer CURWOUNDS = 0; integer CRITICAL = 0; integer CURCRITICAL = 0; integer RESOLVE = 0; integer CURRESOLVE = 0;

// Boons integer MINBOON = 1; integer MAXBOON = 5; list BOONS = [];

// Flaws integer MINFLAW = 1; integer MAXFLAW = 5; list FLAWS = [];

// Skills [ string SkillName, integer SkillRank ] integer MINSKILL = 1; integer MAXSKILL = 5; list SKILLS = [];

// Special Effects (SFX) [ string EffectName, integer EffectRank ] integer MINEFFECT = 1; integer MAXEFFECT = 5; list EFFECTS = [];

// Stunts and Quotes list STUNTS = []; list QUOTES = [];

// Equipment [ string ItemName, integer NumberCarried ] integer MINEQUIPPED = 1; integer MAXEQUIPPED = 100; list EQUIPMENT = [];

// Notecard management runtimes string CARD = "Myriad Lite Character Sheet v0.0.2 20110521"; // name of character sheet notecard integer LINE = 0; // line number of notecard key QUERY = NULL_KEY; // to track notecard queries

// Channels - using HTCS defaults for now integer CHANMYRIAD = -999; // Messages sent to ALL Myriad players in region - CONSTANT integer HANDMYRIAD = 0; // Handle to close Myriad channel integer CHANPLAYER = 0; // Messages sent to one player - dynamic channel from player UUID integer HANDPLAYER = 0; // Handle to close player channel integer CHANOBJECT = 0; // Messages sent to one object - dynamic channel from object UUID integer HANDOBJECT = 0; // Handle to close object channel integer CHANCOMMAND = 5; // Messages sent by player to their meter - CONSTANT integer HANDCOMMAND = 0; // Handle to close command channel

// Melee and Ranged combat values integer MINDAMAGE = 1; integer MAXDAMAGE = 5; integer FLAG_INCAPACITATED = FALSE; string ANIM_INCAPACITATED = "sleep"; integer FLAG_DEAD = FALSE; string ANIM_DEAD = "dead"; float RESPAWN_TIME = 15.0; // time before automatic reset when dead // Armor integer MINATTACH = 1; integer MAXWEAR = 30; integer MAXATTACH = 38; list ATTACHPOINTS = ["INVALID","chest","head","left shoulder","right shoulder","left hand","right hand","left foot","right foot","back","pelvis","mouth","chin","left ear","right ear","left eye","right eye","nose","right upper arm","right lower arm","left upper arm","left lower arm","right hip","right upper leg","right lower leg","left hip","left upper leg","left lower leg","stomach","left pectoral","right pectoral","HUD Center 2","HUD Top Right","HUD Top","HUD Top Left","HUD Center","HUD Bottom Left","HUD Bottom","HUD Bottom Right" ]; integer MINARMOR = 1; integer MAXARMOR = 5; // list of armor amounts worn on all attach points list ARMOR = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; integer CURARMOR = 0;

string DIV="|"; // message field divider

// Fist Fighter controls integer FLAG_FISTS = FALSE; // if true, enable built in fist-fighter. integer FLAG_CONTROLS; // a flag to track control permissions integer FLAG_ANIMATE; // a flag to track animation permissions integer TIME_LAST_ATTACK; // last time there was an attack - to pace/control rate of attack float ARM_LENGTH = 1.0; // an arm is roughly 1m and a kick 1.5m or so. float LEG_LENGTH = 1.5; // for kicks float WEAPON_LENGTH; // length of last weapon used to attack. integer MELEEATTACKDICE = 1; // fists and feet do 1 attack dice of damage if they hit integer CONTROLS; string ANIM_PUNCH_LEFT = "punch_l"; string ANIM_PUNCH_RIGHT = "punch_r"; string ANIM_PUNCH_ONETWO = "punch_onetwo"; string ANIM_KICK = "kick_roundhouse_r";

ERROR(string message) {

   llSay(DEBUG_CHANNEL,"("+llKey2Name(llGetOwner())+") "+message);

}

// Wear a piece of armor WEARARMOR(integer waattachpoint,integer waamount) {

   if ( waattachpoint < MINATTACH || waattachpoint > MAXWEAR ) {
       ERROR("Invalid armor attachment point "+llList2String(ATTACHPOINTS,waattachpoint));
       return;
   }
   ARMOR = llListReplaceList(ARMOR,[waamount],waattachpoint,waattachpoint);
   RECALCULATE_ARMOR();

}

// Remove a piece of armor REMOVEARMOR(integer raattachpoint,integer raamount) {

   if ( raattachpoint < MINATTACH || raattachpoint > MAXWEAR ) {
       ERROR("Invalid armor detachment point "+llList2String(ATTACHPOINTS,raattachpoint));
       return;
   }
   ARMOR = llListReplaceList(ARMOR,[0],raattachpoint,raattachpoint);
   RECALCULATE_ARMOR();

}

// Find the highest armor value of all the armor worn RECALCULATE_ARMOR() {

   CURARMOR = 0;
   integer racount = llGetListLength(ARMOR);
   while (racount--) {
       integer rapoints = llList2Integer(ARMOR,racount);
       if ( rapoints > CURARMOR ) {
           CURARMOR = rapoints;
       }
   }
   llOwnerSay("New Armor Rating is "+(string)CURARMOR);

}

// Making A Damage Roll (Myriad p25, Myriad Special Edition p31) HIT(integer attackdice) {

   integer damagetaken = 0;
   while(attackdice--) { // roll a damage dice for each dice of attack
       integer dieroll = 1+(integer)llFrand(5.0); // hell of a way to have to roll a reasonable uniform d6
       // armor check
       if ( dieroll > CURARMOR ) {
           damagetaken++;
       }
   }
   // finished rolling hit dice, how did we do?
   if ( damagetaken > 0 ) {
       if ( CURARMOR > 0 ) {
           llOwnerSay("That attack penetrated your armor and you've been wounded!");
       } else {
           llOwnerSay("You've been wounded! Perhaps some armor would help, Conan?");
       }
       WOUNDED(damagetaken);
   } else {
       llOwnerSay("Your armor took the damage from that attack!");
   }

}

WOUNDED(integer amount) {

   while (amount--) {
       CURWOUNDS--;
       METER();
       llOwnerSay("Wounded! Wounds: "+(string)CURWOUNDS+" of "+(string)WOUNDS+" wound boxes. Critical: "+(string)CURCRITICAL+" of "+(string)CRITICAL+" critical wound boxes.");
       if ( CURWOUNDS < 1 ) { // incapacitated
           CURWOUNDS = 0;
           INCAPACITATED();
           CURCRITICAL--;
           METER();
           llOwnerSay("Critically Wounded! Wounds: "+(string)CURWOUNDS+" of "+(string)WOUNDS+" wound boxes. Critical: "+(string)CURCRITICAL+" of "+(string)CRITICAL+" critical wound boxes.");
           if ( CURCRITICAL < 1 ) { // dead
               CURCRITICAL = 0;
               DEAD();
           }
       }
   }

}

INCAPACITATED() {

   FLAG_INCAPACITATED = TRUE;
   llStartAnimation(ANIM_INCAPACITATED);
   llRegionSay(CHANMYRIAD,PLAYERNAME+" has been incapacitated!");
   METER();
   llOwnerSay("You've been incapacitated!");

}

DEAD() {

   FLAG_DEAD = TRUE;
   llStartAnimation(ANIM_DEAD);
   llRegionSay(CHANMYRIAD,PLAYERNAME+" has been killed!");
   METER();
   llOwnerSay("You've been killed!");
   llSetTimerEvent(RESPAWN_TIME); // okay, we're dead - reset in a bit.

}

HEAL(integer healamount) {

   while ( healamount-- ) {
       if ( CURCRITICAL < CRITICAL ) {
           CURCRITICAL++;
           METER();
           if ( FLAG_DEAD == TRUE ) {
               FLAG_DEAD=FALSE;
               METER();
               if ( FLAG_ANIMATE == TRUE ) {
                   llStopAnimation(ANIM_DEAD);
               }
               llOwnerSay("1 critical wound healed. Welcome back to the land of the living.");
           } else {
               llOwnerSay("1 critical wound healed.");
           }
       } else if ( CURWOUNDS < WOUNDS ) {
           CURWOUNDS++;
           METER();
           if ( FLAG_INCAPACITATED == TRUE ) {
               FLAG_INCAPACITATED=FALSE;
               METER();
               if ( FLAG_ANIMATE == TRUE ) {
                   llStopAnimation(ANIM_INCAPACITATED);
               }
               llOwnerSay("1 wound healed. You are no longer incapacitated!");
           } else {  
               llOwnerSay("1 wound healed.");
           }
       }
   }
   METER();

}

// Get Attribute Value by Name integer GET_ATTRIB_BY_NAME(string attrib) {

   integer stat = 0;
   attrib = llToLower(attrib);
   if ( attrib == "POWER" ) stat = POWER;
   if ( attrib == "GRACE" ) stat = GRACE;
   if ( attrib == "INTELLECT" ) stat = INTELLECT;
   if ( attrib == "SPIRIT" ) stat = SPIRIT;
   if ( stat < 1 ) {
       ERROR("Invalid attribute for ability test: "+attrib);
       return 0;
   }
   return stat;

}

//============================================================= // GET_SKILL_RANK - find the current ranks (1-5) of a named skill // Requires: A case-sensitive string of the skill name to find // Returns: A zero for any skill the player does not possess, or the player's rank value 1-5 for the skill //============================================================= integer GET_SKILL_RANK(string askill) {

   integer atskill = 0; // set default to return zero as skill value if requested skill is not found in skill list
   integer whereskill = llListFindList(SKILLS,[askill]); // search the skill list for requested skill by name
   if ( whereskill >= 0 ) {
       integer atskill = llList2Integer(SKILLS,++whereskill); // get the skill rank in the next position after skill name in list
       if ( atskill >= MINSKILL && atskill <= MAXSKILL) return atskill; // if rank found is valid, return that as skill value
   }
   return 0; // no valid skill found, return nothing instead

}

// ABILITY TEST // Requires ATTRIBUTE NAME, SKILL NAME // Returns the ability test score for use by success fail, opposed rolls, etc integer ABILITY_TEST(integer attribute,integer skill) {

   integer highroll = 0;
   while( attribute-- ) {
       integer roll = 1+(integer)llFrand(5.0);
       if ( roll > highroll) highroll = roll;
   }
   return highroll + skill;

}

// An Unopposed Ability Test // 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);
   if ( check >= targetnum ) return TRUE;
   return FALSE;    

}

// An Opposed Ability Test // 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);
   integer dcheck = ABILITY_TEST(dattrib,dskill);
   if ( acheck > dcheck ) return TRUE;
   return FALSE;

}

SETUP() {

   CREDITS();
   llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION);
   llOwnerSay("Loading character sheet... please wait.");
   if ( llGetInventoryName(INVENTORY_NOTECARD,0) == CARD ) { // check inventory for notecard
       QUERY = llGetNotecardLine(CARD,LINE++);
   } else {
       ERROR("Cannot locate character sheet notecard by name: "+CARD);
   }
   CONTROLS = CONTROL_ML_LBUTTON | CONTROL_LBUTTON | CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN;
   PLAYERID = llGetOwner();
   PLAYERNAME = llKey2Name(llGetOwner());

}

CREDITS() {

   llOwnerSay("Myriad Lite software Copyright (c) 2011 by Allen Kerensky (OSG/SL)");
   llOwnerSay("Licensed under Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported.");
   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.");

}

RESET() {

   llOwnerSay("Resetting on your command.");
   // stop all running animations
   if ( FLAG_ANIMATE == TRUE ) {
       list anims = llGetAnimationList(llGetOwner());
       integer animcount = llGetListLength(anims); 
       while (animcount--) {
           llStopAnimation(llList2String(anims,animcount));
       }
   }
   llResetScript();

}

METER() {

   string message = "METER"+DIV+PLAYERNAME+DIV+NAME+DIV+(string)CURWOUNDS+DIV+(string)WOUNDS+DIV+(string)CURCRITICAL+DIV+(string)CRITICAL+DIV+(string)FLAG_DEAD+DIV+(string)FLAG_INCAPACITATED;
   llRegionSay(CHANMYRIAD,message);
   llWhisper(CHANPLAYER,message);

}

// Default State - load character sheet default {

   state_entry() {
       SETUP();
   }
   attach(key id) {
       SETUP();
   }
   
   run_time_permissions(integer perm) {
       if ( perm & PERMISSION_TAKE_CONTROLS ) {
           //llTakeControls(0xFFFFFFFF,TRUE,TRUE);
       }
       if ( perm & PERMISSION_TRIGGER_ANIMATION ) {
       }
   }
   
   dataserver(key queryid,string data) {
       if ( queryid == QUERY ) { // is this the line we asked for?
           if ( llGetSubString(data,0,0) == "#" ) { // is line a comment
               QUERY = llGetNotecardLine(CARD,LINE++); // trigger request for next line
               return; // exit early
           }
           if ( data != EOF ) {
               // Parse line
               list FIELDS = llParseString2List(data,["="],[]);
               string CMD = llStringTrim(llList2String(FIELDS,0),STRING_TRIM);
               string DATA = llStringTrim(llList2String(FIELDS,1),STRING_TRIM);
               list SUBFIELDS = llParseString2List(DATA,[","],[]);
               if ( CMD == "NAME" ) {
                   NAME = DATA;
               }
               if ( CMD == "SPECIES" ) {
                   SPECIES = DATA;
               }
               if ( CMD == "BACKGROUND" ) {
                   BACKGROUND = DATA;
               }
               if ( CMD == "CAREER" ) {
                   CAREER = DATA;
               }
               if ( CMD == "XP" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINXP && amount <= MAXXP ) {
                       XP = amount;
                   } else {
                       ERROR("XP amount out of allowed range: "+(string)MINXP+"-"+(string)MAXXP);
                   }
               }
               if ( CMD == "XPLEVEL" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINLEVEL && amount <= MAXLEVEL ) {
                       XPLEVEL = amount;
                   } else {
                       ERROR("XPLEVEL amount out of allowed range: "+(string)MINLEVEL+"-"+(string)MAXLEVEL);
                   }
               }
               if ( CMD == "POWER" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINSTAT && amount <= MAXSTAT ) {
                       POWER = amount;
                   } else {
                       ERROR("POWER amount out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
                   }
               }
               if ( CMD == "GRACE" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINSTAT && amount <= MAXSTAT ) {
                       GRACE = amount;
                   } else {
                       ERROR("GRACE amount out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
                   }
               }
               if ( CMD == "INTELLECT" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINSTAT && amount <= MAXSTAT ) {
                       INTELLECT = amount;
                   } else {
                       ERROR("INTELLECT amount out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
                   }
               }
               if ( CMD == "SPIRIT" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINSTAT && amount <= MAXSTAT ) {
                       SPIRIT = amount;
                   } else {
                       ERROR("SPIRIT amount out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
                   }
               }
               if ( CMD == "WOUNDS" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINRESILIENCE && amount <= MAXRESILIENCE ) {
                       WOUNDS = amount;
                       CURWOUNDS = amount;
                   } else {
                       ERROR("WOUNDS amount out of allowed range: "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE);
                   }
               }
               if ( CMD == "CRITICAL" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINRESILIENCE && amount <= MAXRESILIENCE ) {
                       CRITICAL = amount;
                       CURCRITICAL = amount;
                   } else {
                       ERROR("CRITICAL amount out of allowed range: "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE);
                   }
               }
               if ( CMD == "RESOLVE" ) {
                   integer amount = (integer)DATA;
                   if ( amount >= MINRESILIENCE && amount <= MAXRESILIENCE ) {
                       RESOLVE = (integer)DATA;
                   } else {
                       ERROR("RESOLVE amount out of allowed range: "+(string)MINRESILIENCE+"-"+(string)MAXRESILIENCE);
                   }
               }
               if ( CMD == "BOON" ) {
                   string boonname = llList2String(SUBFIELDS,0);
                   integer boonrank = llList2Integer(SUBFIELDS,1);
                   if ( boonrank >= MINBOON && boonrank <= MAXBOON ) {
                       BOONS = [boonname,boonrank] + BOONS;
                   } else {
                       ERROR("BOON rank out of allowed range: "+(string)MINBOON+"-"+(string)MAXBOON);
                   }
               }                
               if ( CMD == "FLAW" ) {
                   string flawname = llList2String(SUBFIELDS,0);
                   integer flawrank = llList2Integer(SUBFIELDS,1);
                   if ( flawrank >= MINFLAW && flawrank <= MAXFLAW ) {
                       FLAWS = [flawname,flawrank] + FLAWS;
                   } else {
                       ERROR("FLAW rank out of allowed range: "+(string)MINFLAW+"-"+(string)MAXFLAW);
                   }
               }
               if ( CMD == "SKILL" ) {
                   string skillname = llList2String(SUBFIELDS,0);
                   integer skillrank = llList2Integer(SUBFIELDS,1);
                   if ( skillrank >= MINSKILL && skillrank <= MAXSKILL ) {
                       SKILLS = [skillname,skillrank] + SKILLS;
                   } else {
                       ERROR("SKILL rank out of allowed range: "+(string)MINSKILL+"-"+(string)MAXSKILL);
                   }
               }
               if ( CMD == "EFFECT" ) {
                   string effectname = llList2String(SUBFIELDS,0);
                   integer effectrank = llList2Integer(SUBFIELDS,1);
                   if ( effectrank >= MINEFFECT && effectrank <= MAXEFFECT ) {
                       EFFECTS = [effectname,effectrank] + EFFECTS;
                   } else {
                       ERROR("EFFECT rank out of allowed range: "+(string)MINEFFECT+"-"+(string)MAXEFFECT);
                   }
               }
               if ( CMD == "STUNT" ) {
                   string stuntname = llList2String(SUBFIELDS,0);
                   STUNTS = [stuntname] + STUNTS;
               }
               if ( CMD == "QUOTE" ) {
                   string quotename = llList2String(SUBFIELDS,0);
                   QUOTES = [quotename] + QUOTES;
               }                
               if ( CMD == "EQUIPMENT" ) {
                   string equipmentname = llList2String(SUBFIELDS,0);
                   integer equipmentamount = llList2Integer(SUBFIELDS,1);
                   if ( equipmentamount >= MINEQUIPPED && equipmentamount <= MAXEQUIPPED ) {
                       EQUIPMENT = [equipmentname,equipmentamount] + EQUIPMENT;
                   } else {
                       ERROR("EQUIPMENT amount out of allowed range: "+(string)MINEQUIPPED+"-"+(string)MAXEQUIPPED);
                   }
               }
           } else { // end of notecard
               state running;
           }
           QUERY = llGetNotecardLine(CARD,LINE++); // trigger request for next line
       }
   }

}

state running {

   state_entry() {
       llOwnerSay("Character Sheet loaded. You are now ready to roleplay.");
       HANDMYRIAD = llListen(CHANMYRIAD,"",NULL_KEY,"");
       HANDCOMMAND = llListen(CHANCOMMAND,"",llGetOwner(),""); // listen to owner only on COMMAND
       CHANPLAYER = (integer)("0x"+llGetSubString((string)llGetOwner(),0,6));
       HANDPLAYER  = llListen(CHANPLAYER,"",NULL_KEY,"");
       llOwnerSay("Scanning for Myriad-compatible attachments...");
       llWhisper(CHANPLAYER,"REGISTERATTACHMENTS");
       llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION);
       METER();
   }
   
   on_rez(integer rezparam) {
       RESET();
   }
   
   attach(key id) {
       RESET();
   }
   
   changed(integer changes) {
       if ( changes & CHANGED_INVENTORY ) {
           llOwnerSay("Inventory changed. Reloading.");
           RESET();
       }
   }
   
   run_time_permissions(integer perm) {
       if ( perm & PERMISSION_TAKE_CONTROLS ) {
           llTakeControls(CONTROLS,TRUE,TRUE);
           FLAG_CONTROLS = TRUE;
       }
       if ( perm & PERMISSION_TRIGGER_ANIMATION ) {
           FLAG_ANIMATE = TRUE;
       }
   }
   
   control(key id,integer level,integer edge) {
       if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // can't attack when dead or incapacitated
       if ( FLAG_FISTS == FALSE ) return; // fist fighter disabled, ignore control event
       if ( FLAG_ANIMATE == FALSE ) return; // fist fighter disabled since perms are off
       //if ( llGetUnixTime() <= TIME_LAST_ATTACK ) return; // attacked again too soon
       //integer start = level & edge;
       //integer end = ~level & edge;
       //integer held = level & ~edge;
       //integer untouched = ~(level | edge);
       
       if ( ( level & CONTROL_LBUTTON ) || ( level & CONTROL_ML_LBUTTON ) ) {
           
           if ( ( edge & CONTROL_LEFT ) || ( edge & CONTROL_ROT_LEFT ) ) {
               TIME_LAST_ATTACK = llGetUnixTime();
               llStartAnimation(ANIM_PUNCH_LEFT);
               WEAPON_LENGTH = ARM_LENGTH;
               llSensor("",NULL_KEY,AGENT,ARM_LENGTH,PI/6); 
               return;           
           }
           if ( ( edge & CONTROL_RIGHT ) || ( edge & CONTROL_ROT_RIGHT ) ) {
               TIME_LAST_ATTACK = llGetUnixTime();
               llStartAnimation(ANIM_PUNCH_RIGHT);
               WEAPON_LENGTH = ARM_LENGTH;
               llSensor("",NULL_KEY,AGENT,ARM_LENGTH,PI/6);            
               return;
           }
           if ( ( edge & CONTROL_UP ) || ( edge & CONTROL_FWD ) ) {
               TIME_LAST_ATTACK = llGetUnixTime();
               llStartAnimation(ANIM_PUNCH_ONETWO);
               WEAPON_LENGTH = ARM_LENGTH;
               llSensor("",NULL_KEY,AGENT,ARM_LENGTH,PI/6);
               return;
           }
           if ( ( edge & CONTROL_DOWN ) || ( edge & CONTROL_BACK ) ) {
               TIME_LAST_ATTACK = llGetUnixTime();
               llStartAnimation(ANIM_KICK);
               WEAPON_LENGTH = LEG_LENGTH;
               llSensor("",NULL_KEY,AGENT,LEG_LENGTH,PI/6);
               return;
           }
       }
   }
   
   sensor(integer num_detected) {
       while(num_detected--) {
           // This is a trick to calculate a point in front of me weapon_length away
           // Thanks Mephistopheles Thalheimer (SL) for this trick
           //vector A = ( llGetPos() + < WEAPON_LENGTH,0,0> * llGetRot());
           // Where is Defender
           //vector D = llDetectedPos(num_detected);
           // Is defender center within 1m of my calculated point in front of me?
           // If so, my sword had a chance to hit when I swung it.
           //if ( llVecDist(A,D) < 1.0 ) { 
               key hitwho = llDetectedKey(num_detected);
               string name = llDetectedName(num_detected);
               key owner = llList2Key(llGetObjectDetails(hitwho,[OBJECT_OWNER]),0);
               if ( hitwho == owner ) { // we hit an avatar
               
                   integer victimchan = (integer)("0x"+llGetSubString(hitwho,0,6));
                   integer attskill = GET_SKILL_RANK("Close Combat");
                   llRegionSay(CHANMYRIAD,"RPEVENT"+DIV+llKey2Name(llGetOwner())+" strikes "+name+" in Close Combat!");                    
                   llRegionSay(victimchan,"CLOSEHIT"+DIV+(string)POWER+DIV+(string)attskill+DIV+(string)MELEEATTACKDICE+DIV+(string)llGetOwner()+DIV+"fists and feet");                            
                   llOwnerSay("You struck "+name+" in Close Combat");
               }
           //}
       }        
   }
   
   no_sensor() {
       //llOwnerSay("Your attack missed!");
   }
   timer() {
       // Respawn timer ended
       if ( FLAG_DEAD == TRUE ) {
           llRegionSay(CHANMYRIAD,llKey2Name(llGetOwner())+" respawns!");
           RESET();
       }
   }
   
   listen(integer channel, string speakername, key speakerid, string message) {
       // calculate the dynamic channel of who is speaking in case we need to return commands
       CHANOBJECT = (integer)("0x"+llGetSubString((string)speakerid,0,6));
       list fields = llParseString2List(message,["|"],[]);
       string command = llList2String(fields,0);
       if ( channel == CHANMYRIAD ) { // handle Myriad system messages
           if ( command == "RPEVENT" ) {
               string oldname = llGetObjectName();
               llSetObjectName("Myriad RP Event");
               llOwnerSay(llList2String(fields,1));
               llSetObjectName(oldname);
               return;
           }
       }
       if ( channel == CHANCOMMAND ) { // handle player chat commands
           if ( command == "RESET" ) {
               if ( FLAG_DEAD == FALSE && FLAG_INCAPACITATED == FALSE ) { // don't allow reset if on respawn timer
                   RESET();
               } else {
                   llOwnerSay("You will respawn in a few moments.");
               }
               return;
           }
           if ( command == "combaton" ) {
               FLAG_FISTS = TRUE;
               if ( FLAG_CONTROLS == FALSE ) {
                   llReleaseControls();
                   llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION);
               }                
               llOwnerSay("Close Combat now active.");
               return;
           }
           if ( command == "combatoff") {
               FLAG_FISTS = FALSE;
               if ( FLAG_CONTROLS == TRUE ) {
                   llReleaseControls();
                   FLAG_CONTROLS = FALSE;
               }
               llOwnerSay("Close Combat now inactive.");
               return;
           }
       }
       if ( channel == CHANPLAYER ) { // handle player dynamic commands
           // we've been hit and have to make a hit check
           if ( command == "HITCHECK" || command == "RANGEDHIT" || command == "CLOSEHIT" ) {
               integer attackstat = llList2Integer(fields,1);
               integer attackskill = llList2Integer(fields,2);
               integer attackdice = llList2Integer(fields,3); 
               key owner = llList2Key(fields,4);
               string item = llList2String(fields,5);               
               if ( attackstat < MINSTAT || attackstat > MAXSTAT ) {
                   ERROR("Attack stat value out of range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
                   return;
               }
               if ( attackskill < MINSKILL || attackstat > MAXSKILL ) {
                   ERROR("Attack skill value out of range: "+(string)MINSKILL+"-"+(string)MAXSKILL);
                   return;
               }                
               if ( attackdice < MINDAMAGE || attackdice > MAXDAMAGE ) {
                   ERROR("Attack dice value out of range: "+(string)MINDAMAGE+"-"+(string)MAXDAMAGE);
                   return;
               }
               integer skillamount;
               if ( command == "HITCHECK" || command == "RANGEDHIT" ) {
                   skillamount = GET_SKILL_RANK("Ranged Combat");
               }
               if ( command == "CLOSEHIT" ) {
                   skillamount = GET_SKILL_RANK("Close Combat");
               }
               // see if we're hit
               integer amihit = OPPOSED_TEST(attackstat,attackskill,GRACE,skillamount);
               if ( amihit == TRUE ) {
                   if ( command == "HITCHECK" || command == "RANGEDHIT" ) {
                       llOwnerSay("You've been hit by "+llKey2Name(owner)+"'s "+item+"!");
                   }
                   if ( command == "CLOSEHIT" ) {
                       llOwnerSay("You been hit in close combat by "+llKey2Name(owner)+"!");
                   }
                   HIT(attackdice);                
               }
               return;
           }
           // Heal Some Damage
           if ( command == "HEALPARTIAL" ) {
               integer boxeshealed = llList2Integer(fields,1);
               HEAL(boxeshealed);
               llOwnerSay("Partially healed. Wounds: "+(string)CURWOUNDS+" of "+(string)WOUNDS+" wound boxes. Critical: "+(string)CURCRITICAL+" of "+(string)CRITICAL+" critical wound boxes.");
               return;
           }
           if ( command == "HEALFULL" ) {
               CURWOUNDS = WOUNDS;
               CURCRITICAL = CRITICAL;
               FLAG_DEAD = FALSE;
               FLAG_INCAPACITATED = FALSE;
               if ( FLAG_ANIMATE == TRUE ) {
                   llStopAnimation(ANIM_DEAD);
                   llStopAnimation(ANIM_INCAPACITATED);
               }
               llOwnerSay("Fully healed! Wounds: "+(string)CURWOUNDS+" of "+(string)WOUNDS+" wound boxes. Critical: "+(string)CURCRITICAL+" of "+(string)CRITICAL+" critical wound boxes.");
           }
           //------------------------------------------------------------
           // Actions NOT Allowed When Dead/Incapacitated
           //------------------------------------------------------------
           if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // can't attack when dead or incapacitated
           if ( command == "ATTACHARMOR" ) { // player attached armor somewhere
               integer armorrating = llList2Integer(fields,1);
               integer attachpoint = llList2Integer(fields,2);
               string armorname = llList2String(fields,3);
               if ( armorrating >= MINARMOR && armorrating <= MAXARMOR ) {
                   llOwnerSay("Armor "+(string)armorrating+" attached to "+llList2String(ATTACHPOINTS,attachpoint));
                   WEARARMOR(attachpoint,armorrating);
               } else {
                   ERROR("ARMOR amount out of allowed range "+(string)MINARMOR+"-"+(string)MAXARMOR);
               }
               return;
           }
           if ( command == "DETACHARMOR" ) { // player attached armor somewhere
               integer armorrating = llList2Integer(fields,1);
               integer attachpoint = llList2Integer(fields,2);
               string armorname = llList2String(fields,3);
               if ( armorrating >= MINARMOR && armorrating <= MAXARMOR ) {
                   llOwnerSay("Armor "+(string)armorrating+" detached from "+llList2String(ATTACHPOINTS,attachpoint));
                   REMOVEARMOR(attachpoint,armorrating);
               } else {
                   ERROR("ARMOR amount out of allowed range "+(string)MINARMOR+"-"+(string)MAXARMOR);
               }
               return;
           }
           if ( command == "ATTACHMELEE" || command == "ATTACHRANGED" ) {
               FLAG_FISTS = FALSE;
               if ( FLAG_CONTROLS == TRUE ) {
                   llReleaseControls();
                   FLAG_CONTROLS = FALSE;
               }
               return;
           }
           if ( command == "DETACHMELEE" || command == "DETACHRANGED" ) {
               FLAG_FISTS = TRUE;
               if ( FLAG_CONTROLS == FALSE ) {
                   llReleaseControls();
                   llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION);
               }
               return;
           }
           // If Your Bullet has hit, let's fire a hitcheck regionwide at targetplayer's channel
           if ( command == "CLOSECOMBAT" || command == "RANGEDCOMBAT" || command == "TOHIT" ) {
               integer attdice = llList2Integer(fields,1);
               string hitwho = llList2String(fields,2);
               string bywho = llList2String(fields,3);
               string bywhat = llList2String(fields,4);
               //ATTACK(attdice,hitwho,bywho,bywhat);
               
               integer victimchan = (integer)("0x"+llGetSubString(hitwho,0,6));
               integer attskill = 0;
               if ( command == "RANGEDCOMBAT" || command == "TOHIT" ) {
                   attskill = GET_SKILL_RANK("Ranged Combat");
                   llRegionSay(victimchan,"RANGEDHIT"+DIV+(string)POWER+DIV+(string)attskill+DIV+(string)attdice+DIV+bywho+DIV+bywhat);
               }
               if ( command == "CLOSECOMBAT" ) {
                   attskill = GET_SKILL_RANK("Close Combat");
                   llRegionSay(victimchan,"CLOSEHIT"+DIV+(string)POWER+DIV+(string)attskill+DIV+(string)attdice+DIV+bywho+DIV+bywhat);
               }
               return;
           }
       }
   }

} </lsl>