Myriad_Lite_Module_Skill_Ranged_Combat-v0.0.0-20120501.lsl
// Myriad_Lite_Module_Skill_Ranged_Combat-v0.0.0-20120501.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.0.0"; // Allen Kerensky's script version
string VERSIONDATE = "20120501"; // Allen Kerensky's script yyyymmdd
integer MINSTAT = 1; // min value for statistics
integer MAXSTAT = 5; // max human value for a statistic/attribute
integer MINSKILL = 1; // min value for skill rank
integer MAXSKILL = 5; // max value for skill rank
integer MINDAMAGE = 1; // min attack dice for weapon
integer MAXDAMAGE = 5; // max attack dice for weapon
integer CHANMYRIAD = -999; // chat sent to ALL Myriad players in region
string DIV = "|"; // message field divider
// Module to Module Messaging Constants
//integer MODULE_HUD = -1;
//integer MODULE_CHARSHEET = -2;
//integer MODULE_ARMOR = -3;
//integer MODULE_BAM = -4;
//integer MODULE_RUMORS = -5;
//integer MODULE_CLOSE = -6;
integer MODULE_RANGED = -7;
//integer 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
list STATISTICS = [];
list SKILLS = []; // skills [ string SkillName, integer SkillRank ]
integer FLAG_INCAPACITATED; // incapacitated by wounds?
integer FLAG_DEAD; // killed by critical wounds?
// 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
}
// COMBATOFF - turn off fist fighter
COMBATOFF() {
//llMessageLinked(LINK_THIS,MODULE_RANGED,"COMBATOFF",llGetOwner());
}
// COMBATON - turn on fist fighter
COMBATON() {
//llMessageLinked(LINK_THIS,MODULE_RANGED,"COMBATON",llGetOwner());
}
// DEBUG - show debug chat with wearer name for sorting
DEBUG(string dmessage) {
if ( FLAG_DEBUG == TRUE ) { // are we debugging?
llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") Module Ranged: "+dmessage);
}
}
// 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");
}
// ERROR - show errors on debug channel with wearer name for sorting
ERROR(string emessage) {
llSay(DEBUG_CHANNEL,"ERROR ("+llKey2Name(PLAYERID)+") Module Ranged: "+emessage);
}
// 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
}
// GETSTAT
integer GETSTAT(string name) {
integer pos = llListFindList(STATISTICS,[name]);
if ( pos >= 0 ) {
return llList2Integer(STATISTICS,pos + 1);
}
return 0;
}
// MEMORY
MEMORY() {
DEBUG("Free Memory: "+(string)llGetFreeMemory());
}
// 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
}
// RESET - shut down running animations then reset the script to reload character sheet
RESET() {
llResetScript(); // now reset
}
// RPEVENT
RPEVENT(string rpevent) {
llRegionSay(CHANMYRIAD,"RPEVENT|"+NAME+" ("+PLAYERNAME+") "+rpevent);
}
// SETUP - begin bringing the HUD online
SETUP() {
PLAYERID = llGetOwner(); // remember the owner's UUID
PLAYERNAME = llKey2Name(PLAYERID); // remember the owner's legacy name
}
// VERSION
SHOW_VERSION() {
DEBUG("Version: "+VERSION+" Date: "+VERSIONDATE);
}
// DEFAULT STATE - load character sheet
default {
// attach - when attached or detached from inventory or during login
attach(key id) {
id = NULL_KEY; // LSLINT
RESET(); // force to go through state entry
}
link_message(integer sender_num,integer sender,string message,key id) {
if ( sender == MODULE_RANGED ) return; // ignore our own messages
DEBUG("Link Message: "+message);
sender_num = 0; // LSLINT
id = NULL_KEY; // LSLINT
list fields = llParseString2List(message,[DIV],[]); // break line of text into = delimited fields
string command = llToLower(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 ( command == "set_statistic" ) {
string statname = llList2String(subfields,0); // find the boon name
integer statrank = llList2Integer(subfields,1); // find the boon rank value
// TODO how to verify stat names are valid?
if ( statrank >= MINSTAT && statrank <= MAXSTAT ) { // rank valid?
STATISTICS = [statname,statrank] + STATISTICS; // add statistic to list
} else { // invalid, report it
ERROR("STATISTIC "+statname+" rank "+(string)statrank+" value out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
}
}
if ( command == "set_skill" ) {
string skillname = llList2String(subfields,0); // find the skill name
integer skillrank = llList2Integer(subfields,1); // find the skill rank
// TODO how to verify skill names are valid?
if ( skillrank >= MINSKILL && skillrank <= MAXSKILL ) { // skill rank valid?
if ( skillname == "Ranged Combat" ) {
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 ( command == "combatoff" ) { COMBATOFF(); return; }
if ( command == "combaton" ) { COMBATON(); return; }
if ( command == "debugoff" ) { DEBUGOFF(); return; }
if ( command == "debugon" ) { DEBUGON(); return; }
if ( command == "reset" ) { RESET(); return; }
if ( command == "memory" ) { MEMORY(); return; }
if ( command == "version" ) { SHOW_VERSION(); return; }
if ( command == "dead" ) { FLAG_DEAD = TRUE; return; }
if ( command == "alive" ) { FLAG_DEAD = FALSE; return; }
if ( command == "incapacitated" ) { FLAG_INCAPACITATED = TRUE; return; }
if ( command == "revived" ) { FLAG_INCAPACITATED = FALSE; return; }
if ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // can't mess with attachments while down
if ( command == "attachranged" ) { // holding a ranged weapon rather than using fists
//
return;
}
if ( command == "detachranged" ) { // are we going back to fists?
//
return;
}
if ( command == "rangedhit" ) { // 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
skillamount = GET_SKILL_RANK("Ranged Combat"); // get ranged 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+"!");
RPEVENT("struck by "+llKey2Name(owner)+"'s "+item+" in ranged combat!");
}
llMessageLinked(LINK_THIS,MODULE_RANGED,"HIT|"+(string)attackdice,PLAYERID); // apply the hit
}
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 == "rangedcombat" ) {
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
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;
} // end if RANGEDCOMBAT/TOHIT
} // end of link_message event
// 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
}
// STATE ENTRY - called on Reset
state_entry() {
SETUP(); // show credits and start character sheet load
}
} // end state
// END