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