// Myriad_Lite_Module_Character_Sheet-v0.0.10-20131019.lsl
// Copyright (c) 2012-2013 by Allen Kerensky (OSG/SL) All Rights Reserved.
// This work is dual-licensed under
// Creative Commons Attribution (CC BY) 3.0 Unported
// http://creativecommons.org/licenses/by/3.0/
// - or -
// Modified BSD License (3-clause)
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Myriad Lite nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
// NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The Myriad RPG System was designed, written, and illustrated by Ashok Desai
// Myriad RPG System licensed under:
// Creative Commons Attribution (CC BY) 2.0 UK: England and Wales
// http://creativecommons.org/licenses/by/2.0/uk/
// VERSION CONTROL
string BASENAME = "Myriad Lite Module Character Sheet";
string VERSION = "0.0.10"; // Allen Kerensky's script version
string VERSIONDATE = "20131019"; // Allen Kerensky's script yyyymmdd
// MODULE TO MODULE MESSAGING CONSTANTS
integer MODULE_CHARSHEET = -2;
integer LM_SENDTOATTACHMENT = 0x80000000; // FIXME move to SENDTOATTACH|<message>
//
// Prim Persistent Memory Array Support
//
DELETE_RECORD(string dbkey, string field) {
//OWNERSAY("DELETE_RECORD=["+dbkey+"] FIELD=["+field+"] START");
// scan index or prim names for empty, get number for update
integer i;
string name;
string desc;
for ( i = 2; i <= llGetNumberOfPrims(); i++) {
name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
if ( name == dbkey && desc == field ) { // record found, delete
llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,"",PRIM_DESC,"",PRIM_TEXT,"",ZERO_VECTOR,0.0]); // save to database
}
}
//OWNERSAY("DELETE_RECORD=["+dbkey+"] FIELD=["+field+"] ENDS");
}
DUMP_RECORDS() {
//OWNERSAY("DUMP_RECORDS STARTS");
// scan index or prim names for empty, get number for update
integer i;
string name = "";
string desc = "";
string text = "";
for ( i = 2; i <= llGetNumberOfPrims(); i++) {
name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
if ( name != "" && desc != "" ) {
text = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
OWNERSAY("RECORD=["+(string)i+"] NAME=["+name+"] DESC=["+desc+"] TEXT=["+text+"]");
}
}
//OWNERSAY("DUMP_RECORDS ENDS");
}
DUMP_TYPE(integer chan,string type) {
//OWNERSAY("DUMP_TYPES CHAN=["+(string)chan+"] TYPE=["+type+"] STARTS");
type = llToUpper(type);
// scan index or prim names for empty, get number for update
integer i;
string name = "";
string desc = "";
string text = "";
for ( i = 2; i <= llGetNumberOfPrims(); i++) {
name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
if ( name == type ) {
desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
text = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
if ( type == "CHARACTER" ) llSay(chan,"CHARACTER|"+desc+"="+text);
if ( type == "STATISTIC" ) llSay(chan,"STATISTIC|NAME="+desc+"|"+text);
if ( type == "SKILL" ) llSay(chan,"SKILL|NAME="+desc+"|"+text);
if ( type == "EFFECT" ) llSay(chan,"EFFECT|NAME="+desc+"|"+text);
if ( type == "RESILIENCE" ) llSay(chan,"RESILIENCE|NAME="+desc+"|"+text);
if ( type == "BOON" ) llSay(chan,"BOON|NAME="+desc+"|"+text);
if ( type == "FLAW" ) llSay(chan,"FLAW|NAME="+desc+"|"+text);
if ( type == "CAMPAIGN" ) llSay(chan,"CAMPAIGN|NAME="+desc+"|"+text);
if ( type == "SPECIE" ) llSay(chan,"SPECIE|NAME="+desc+"|"+text);
if ( type == "BACKGROUND" ) llSay(chan,"BACKGROUND|NAME="+desc+"|"+text);
if ( type == "CAREER" ) llSay(chan,"CAREER|NAME="+desc+"|"+text);
if ( type == "ITEM" ) llSay(chan,"ITEM|NAME="+desc+"|"+text);
if ( type == "STUNT" ) llSay(chan,"STUNT|NAME="+desc+"|"+text);
if ( type == "QUOTE" ) llSay(chan,"QUOTE|NAME="+desc+"|"+text);
}
}
//OWNERSAY("DUMP_TYPE ENDS");
}
ERASE_TYPE(string type) {
//OWNERSAY("ERASE TYPE ["+type+"] STARTS");
// scan index or prim names for empty, get number for update
integer i;
string name = "";
string desc = "";
for ( i = 2; i <= llGetNumberOfPrims(); i++) {
name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
if ( name == type ) {
desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
DELETE_RECORD(type,desc);
}
}
//OWNERSAY("ERASE_TYPE ENDS");
}
FORMAT_DB() {
//OWNERSAY("FORMATTING DATABASE");
integer i;
for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
if ( llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0) != "" ) {
llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,"",PRIM_DESC,"",PRIM_TEXT,"",ZERO_VECTOR,0.0]);
}
}
//OWNERSAY("DATABASE FORMAT COMPLETE");
}
string KEY_NOT_FOUND = "[KEY_NOT_FOUND]";
string GET_VAL(string dbkey,string field) {
//OWNERSAY("GET_VAL KEY=["+dbkey+"] FIELD=["+field+"]");
string out = KEY_NOT_FOUND;
integer i;
string name;
string desc;
for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
if ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) {
out = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0);
//DEBUG("GET_VAL RETURN=["+out+"]");
return out; // bail on first match?
}
}
//OWNERSAY("GET_VAL RETURN=["+out+"]");
return out;
}
LIST_TYPE(key id,string type) {
integer chan = (integer)("0x"+llGetSubString(id,0,6));
type = llToUpper(type);
// scan index or prim names for empty, get number for update
integer i;
string name = "";
string desc = "";
list out = [];
for ( i = 2; i <= llGetNumberOfPrims(); i++) {
name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0);
if ( name == type ) {
desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0);
out = out + [desc];
}
}
out = llListSort(out,1,TRUE);
if ( type == "STATISTIC" ) llSay(chan,"STATISTICS|"+llList2CSV(out));
if ( type == "SKILL" ) llSay(chan,"SKILLS|"+llList2CSV(out));
if ( type == "EFFECT" ) llSay(chan,"EFFECTS|"+llList2CSV(out));
if ( type == "RESILIENCE" ) llSay(chan,"RESILIENCES|"+llList2CSV(out));
if ( type == "BOON" ) llSay(chan,"BOONS|"+llList2CSV(out));
if ( type == "FLAW" ) llSay(chan,"FLAWS|"+llList2CSV(out));
if ( type == "CAMPAIGN" ) llSay(chan,"CAMPAIGNS|"+llList2CSV(out));
if ( type == "SPECIE" ) llSay(chan,"SPECIES|"+llList2CSV(out));
if ( type == "BACKGROUND" ) llSay(chan,"BACKGROUNDS|"+llList2CSV(out));
if ( type == "CAREER" ) llSay(chan,"CAREERS|"+llList2CSV(out));
if ( type == "ITEM" ) llSay(chan,"ITEMS|"+llList2CSV(out));
}
SET_VAL(string dbkey, string field, string val) {
//OWNERSAY("SET_VAL KEY=["+dbkey+"] FIELD=["+field+"] VAL=["+val+"]");
integer i;
string name;
string desc;
integer written = FALSE;
for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM);
desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM);
if ( ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) && written == FALSE ) {
//OWNERSAY("SET_VAL UPDATE RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]");
llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]);
written = TRUE; // we did an update, remember it
}
}
if ( written == TRUE ) {
//OWNERSAY("SET_VAL UPDATE COMPLETE.");
return;
}
// data hasn't been written, scan for free prim
for ( i = 2; i <= llGetNumberOfPrims(); i++ ) {
name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM);
desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM);
if ( ( name == "" && desc == "" ) && written == FALSE ) {
//OWNERSAY("SET_VAL INSERT RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]");
llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]);
written = TRUE; // we did an update, remember it
}
}
if ( written == TRUE ) {
//OWNERSAY("SET_VAL INSERT COMPLETED.");
return;
}
//OWNERSAY("SET_VAL NO FREE RECORD FOUND TO INSERT INTO! DATA LOST");
}
// FLAG FUNCTIONS
// INCAPACITATED - lost wounds
// DEAD - lost critical wounds
// IDEA: INDECISION - lost resolve?
// LMIM: SET_FLAG|<name>=<TRUE|FALSE>
// PPMA: FLAG,<flagname>,<TRUE|FALSE>
// LMOUT: FLAG|<flagname>=<TRUE|FALSE>
// LMERR: FIXME
integer GET_FLAG(string flag) {
string val = GET_VAL("FLAG",llToUpper(flag));
if ( val == KEY_NOT_FOUND ) {
ERROR("Flag ["+flag+"] does not exist.");
}
if ( val != "0" && val != "1") {
ERROR("Flag: "+flag+" invalid value ["+val+"]");
}
integer bool = (integer)val;
return bool;
}
// SET_FLAG
// LMIM: SET_FLAG|<flagname>=<TRUE|FALSE>
// PPMA: FLAG,<flagname>,<TRUE|FALSE>
// LMOUT: SET_FLAG|<flagname>=<TRUE|FALSE>
// LMERR: FIXME
//SET_FLAG(string flag,integer value) {
// if ( flag == "" ) return; // FIXME add error
// if ( value != TRUE && value != FALSE ) return; // FIXME add err
// SET_VAL("FLAG",llToUpper(flag),(string)value);
//}
//
// GET_MYRIAD
// Requires a MYRIAD CONSTANT NAME
// Returns the amount of that Myriad rules constant or defaultif the player does not currently have that constant
//
integer GET_MYRIAD(string setting) {
integer retval;
string value = GET_VAL("MYRIAD",setting);
if ( value == KEY_NOT_FOUND ) {
if ( setting == "MINSTAT" ) retval = 1;
if ( setting == "MAXSTAT" ) retval = 10;
if ( setting == "MINSKILL" ) retval = 0;
if ( setting == "MAXSKILL" ) retval = 7;
if ( setting == "MINEFFECT" ) retval = 0;
if ( setting == "MAXEFFECT" ) retval = 5;
if ( setting == "MINRESILIENCE" ) retval = 0;
if ( setting == "MAXRESILIENCE" ) retval = 20;
if ( setting == "MINBOON" ) retval = 0;
if ( setting == "MAXBOON" ) retval = 5;
if ( setting == "MINFLAW" ) retval = 0;
if ( setting == "MAXFLAW" ) retval = 5;
if ( setting == "MINRP" ) retval = 0;
if ( setting == "MAXRP" ) retval = 10;
if ( setting == "MINITEM" ) retval = 0;
if ( setting == "MAXITEM" ) retval = 100;
if ( setting == "MINARMOR" ) retval = 0;
if ( setting == "MAXARMOR" ) retval = 5;
if ( setting == "MINDAMAGE" ) retval = 1;
if ( setting == "MAXDAMAGE" ) retval = 5;
ERROR("Unable to locate Myriad setting "+setting+" returning "+(string)retval);
} else {
retval = (integer)value;
}
return retval;
}
///////////////////
// ASPECTS
///////////////////
// STATISTICS
// LMIN: GET_STATISTIC|<name>
// PPMA: STATISTIC,<name>,<rank>
// LMOUT: STATISTIC|<name>=<rank>
// LMERR: FIXME
integer GET_STATISTIC(string stat) {
// FIXME how to verify stat?
integer rank = (integer)GET_VAL("STATISTIC",stat);
if ( rank < GET_MYRIAD("MINSTAT") ) { rank = GET_MYRIAD("MINSTAT"); SET_STATISTIC(stat,rank); }
if ( rank > GET_MYRIAD("MAXSTAT") ) { rank = GET_MYRIAD("MAXSTAT"); SET_STATISTIC(stat,rank); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"STATISTIC|"+stat+"="+(string)rank,llGetOwner()); // REMOVEME
return rank;
}
// LMIN: SET_STATISTIC|<name>=<rank>
// PPMA: STATISTIC,<name>,<rank>
// LMOUT: SET_STATISTIC|<name>,<rank>
// LMERR: FIXME
SET_STATISTIC(string stat,integer rank) {
// FIXME how to verify stat names are valid?
if ( rank < GET_MYRIAD("MINSTAT") ) rank = GET_MYRIAD("MINSTAT");
if ( rank > GET_MYRIAD("MAXSTAT") ) rank = GET_MYRIAD("MAXSTAT");
SET_VAL("STATISTIC",stat,(string)rank);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_STATISTIC|"+stat+"="+(string)rank,llGetOwner()); // REMOVEME
}
// SKILLS
// LMIN: GET_SKILL|<name>
// PPMA: SKILL,<name>,<rank>
// LMOUT: SKILL|<name>=<rank>
// LMERR: FIXME
integer GET_SKILL(string skill) {
integer rank = (integer)GET_VAL("SKILL",skill);
if ( rank < GET_MYRIAD("MINSKILL") ) { rank = GET_MYRIAD("MINSKILL"); SET_SKILL(skill,rank); }
if ( rank > GET_MYRIAD("MAXSKILL") ) { rank = GET_MYRIAD("MAXSKILL"); SET_SKILL(skill,rank); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SKILL|"+skill+"="+(string)rank,llGetOwner()); // REMOVEME
return rank;
}
// LMIN: SET_SKILL|<name>=<rank>
// PPMA: SKILL,<name>,<rank>
// LMOUT: SET_SKILL|<name>=<rank>
// LMERR: FIXME
SET_SKILL(string skill,integer rank) {
// TODO how to verify skill names are valid?
if ( rank < GET_MYRIAD("MINSKILL") ) rank = GET_MYRIAD("MINSKILL");
if ( rank > GET_MYRIAD("MAXSKILL") ) rank = GET_MYRIAD("MAXSKILL");
SET_VAL("SKILL",skill,(string)rank);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SKILL|"+skill+"="+(string)rank,llGetOwner()); // REMOVEME
}
// SFX
// LMIN: GET_EFFECT|<name>
// PPMA: EFFECT,<name>,<rank>
// LMOUT: EFFECT|<name>=<rank>
// LMERR: FIXME
integer GET_EFFECT(string name) {
integer rank = (integer)GET_VAL("EFFECT",name);
if ( rank < GET_MYRIAD("MINEFFECT") ) { rank = GET_MYRIAD("MINEFFECT"); SET_EFFECT(name,rank); }
if ( rank > GET_MYRIAD("MAXEFFECT") ) { rank = GET_MYRIAD("MAXEFFECT"); SET_EFFECT(name,rank); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"EFFECT|"+name+"="+(string)rank,llGetOwner()); // REMOVEME
return rank;
}
// LMIN: SET_EFFECT|<name>=<rank>
// PPMA: EFFECT,<name>,<rank>
// LMOUT: SET_EFFECT|<name>=<rank>
// LMERR: FIXME
SET_EFFECT(string name,integer rank) {
// TODO how to verify effect name?
if ( rank < GET_MYRIAD("MINEFFECT") ) rank = GET_MYRIAD("MINEFFECT");
if ( rank > GET_MYRIAD("MAXEFFECT") ) rank = GET_MYRIAD("MAXEFFECT");
SET_VAL("EFFECT",name,(string)rank);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_EFFECT|"+name+"="+(string)rank,llGetOwner()); // REMOVEME
}
// RESILIENCES
// LMIN: GET_RESILIENCE|<name>
// PPMA: RESILIENCE,<name>,<amount>
// LMOUT: RESILIENCE|<name>=<amount>
// LMERR: FIXME
integer GET_RESILIENCE(string name) {
integer amount = (integer)GET_VAL("RESILIENCE",name);
if ( amount < GET_MYRIAD("MINRESILIENCE") ) { amount = GET_MYRIAD("MINRESILIENCE"); SET_RESILIENCE(name,amount); }
if ( amount > GET_MYRIAD("MAXRESILIENCE") ) { amount = GET_MYRIAD("MAXRESILIENCE"); SET_RESILIENCE(name,amount); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"RESILIENCE|"+name+"="+(string)amount,llGetOwner()); // REMOVEME
return amount;
}
// LMIN: SET_RESILIENCE|<name>=<amount>
// PPMA: RESILIENCE,<name>,<amount>
// LMOUT: SET_RESILIENCE|<name>=<amount>
// LMERR: FIXME
SET_RESILIENCE(string name,integer amount) {
// TODO how to verify resilience names are valid?
if ( amount < GET_MYRIAD("MINRESILIENCE") ) amount = GET_MYRIAD("MINRESILIENCE");
if ( amount > GET_MYRIAD("MAXRESILIENCE") ) amount = GET_MYRIAD("MAXRESILIENCE");
SET_VAL("RESILIENCE",name,(string)amount);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_RESILIENCE|"+name+"="+(string)amount,llGetOwner()); // REMOVEME
}
// BOONS
// LMIN: GET_BOON|<name>
// PPMA: BOON,<name>,<rank>
// LMOUT: BOON|<name>=<rank>
// LMERR: FIXME
integer GET_BOON(string boon) {
integer rank = (integer)GET_VAL("BOON",boon);
if ( rank < GET_MYRIAD("MINBOON") ) { rank = GET_MYRIAD("MINBOON"); SET_BOON(boon,rank); }
if ( rank > GET_MYRIAD("MAXBOON") ) { rank = GET_MYRIAD("MAXBOON"); SET_BOON(boon,rank); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"BOON|"+boon+"="+(string)rank,llGetOwner()); // REMOVEME
return rank;
}
// LMIN: SET_BOON|<name>=<rank>
// PPMA: BOON,<name>,<rank>
// LMOUT: SET_BOON|<name>=<rank>
// LMERR: FIXME
SET_BOON(string boon,integer rank) {
// TODO how to verify boon names are valid?
if ( rank < GET_MYRIAD("MINBOON") ) rank = GET_MYRIAD("MINBOON");
if ( rank > GET_MYRIAD("MAXBOON") ) rank = GET_MYRIAD("MAXBOON");
SET_VAL("BOON",boon,(string)rank);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_BOON|"+boon+"="+(string)rank,llGetOwner()); // REMOVEME
}
// FLAWS
// LMIN: GET_FLAW|<name>
// PPMA: FLAW,<name>,<rank>
// LMOUT: FLAW|<name>=<rank>
// LMERR: FIXME
integer GET_FLAW(string flaw) {
integer rank = (integer)GET_VAL("FLAW",flaw);
if ( rank < GET_MYRIAD("MINFLAW") ) { rank = GET_MYRIAD("MINFLAW"); SET_FLAW(flaw,rank); }
if ( rank > GET_MYRIAD("MAXFLAW") ) { rank = GET_MYRIAD("MAXFLAW"); SET_FLAW(flaw,rank); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"FLAW|"+flaw+"="+(string)rank,llGetOwner()); // REMOVEME
return rank;
}
// LMIN: SET_FLAW|<name>=<rank>
// PPMA: FLAW,<name>,<rank>
// LMOUT: SET_FLAW|<name>=<rank>
// LMERR: FIXME
SET_FLAW(string flaw,integer rank) {
// TODO how to verify flaw names are valid?
if ( rank < GET_MYRIAD("MINFLAW") ) rank = GET_MYRIAD("MINFLAW");
if ( rank > GET_MYRIAD("MAXFLAW") ) rank = GET_MYRIAD("MAXFLAW");
SET_VAL("FLAW",flaw,(string)rank);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_FLAW|"+flaw+"="+(string)rank,llGetOwner());
}
// SPECIES
// LMIN: GET_SPECIE|<name>
// PPMA: SPECIE,<name>,<data>
// LMOUT: SPECIE|<name>=<data>
// LMERR: FIXME
string GET_SPECIE(string name) {
// FIXME how to verify requested species name?
string speciedata = GET_VAL("SPECIE",name);
if ( speciedata == KEY_NOT_FOUND ) speciedata = "None";
// FIXME how to verify species data
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SPECIE|"+name+"="+speciedata,llGetOwner()); // REMOVEME
return speciedata;
}
// FIXME LIST_SPECIES
//string LIST_SPECIES() {
// return "";
//}
// LMIN: SET_SPECIE|<name>=<data>
// PPMA: SPECIE,<name>,<data>
// LMOUT: SET_SPECIE|<name>=<data>
// LMERR: FIXME
SET_SPECIE(string aspecies,string data) {
// FIXME how to verify a good species name?
// FIXME how to verify data
SET_VAL("SPECIE",aspecies,data);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_SPECIE|"+aspecies+"="+data,llGetOwner()); // REMOVEME
}
// BACKGROUND
// LMIN: GET_BACKGROUND|<name>=<data>
// PPMA: BACKGROUND,<name>,<data>
// LMOUT: BACKGROUND|<name>=<data>
// LMERR: FIXME
string GET_BACKGROUND(string name) {
string bkgnddata = GET_VAL("BACKGROUND",name);
if ( bkgnddata == KEY_NOT_FOUND ) bkgnddata = "None";
// FIXME how to verify background data?
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"BACKGROUND|"+name+"="+bkgnddata,llGetOwner());
return bkgnddata;
}
// FIXME LIST_BACKGROUNDS
//string LIST_BACKGROUNDS() {
// return "";
//}
// LMIN: SET_BACKGROUND|<name>=<data>
// PPMA: BACKGROUND,<name>,<data>
// LMOUT: SET_BACKGROUND|<name>,<data>
// LMERR: FIXME
SET_BACKGROUND(string abkgnd,string data) {
// FIXME how to verify background?
SET_VAL("BACKGROUND",abkgnd,data);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_BACKGROUND|"+abkgnd+"="+data,llGetOwner()); // REMOVEME
}
// CAREER
// LMIN: GET_CAREER|<name>
// PPMA: CAREER,<name>,<data>
// LMOUT: CAREER|<name>=<data>
// LMERR: FIXME
string GET_CAREER(string name) {
string careerdata = GET_VAL("CAREER",name);
if ( careerdata == KEY_NOT_FOUND ) careerdata = "None";
// FIXME how to verify career?
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CAREER|"+name+"="+careerdata,llGetOwner());
return careerdata;
}
// FIXME LIST_CAREERS
//string LIST_CAREERS() {
// return "";
//}
// LMIN: SET_CAREER|<name>=<data>
// PPMA: CAREER,<name>,<data>
// LMOUT: SET_CAREER|<name>=<data>
// LMERR: FIXME
SET_CAREER(string acareer,string data) {
// FIXME how to verify name
// FIXME how to verify data
SET_VAL("CAREER",acareer,data);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CAREER|"+acareer+"="+data,llGetOwner());
}
// EQUIPMENT
// LMIN: GET_ITEM|<name>
// PPMA: ITEM,<name>,<amount>
// LMOUT: ITEM|<name>=<amount>
// LMERR: FIXME
integer GET_ITEM(string name) {
// FIXME verify equipment name is valid?
integer amt = (integer)GET_VAL("ITEM",name);
if ( amt < GET_MYRIAD("MINITEM") ) { amt = GET_MYRIAD("MINITEM"); SET_ITEM(name,amt); }
if ( amt > GET_MYRIAD("MAXITEM") ) { amt = GET_MYRIAD("MAXITEM"); SET_ITEM(name,amt); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"ITEM|"+name+"="+(string)amt,llGetOwner());
return amt;
}
// LMIN: SET_ITEM|<name>=<amount>
// PPMA: ITEM,<name>,<amount>
// LMOUT: SET_ITEM|<name>=<amount>
// LMERR: FIXME
SET_ITEM(string name,integer amt) {
// TODO how to verify the equipment name is valid?
if ( amt < GET_MYRIAD("MINITEM") ) amt = GET_MYRIAD("MINITEM");
if ( amt > GET_MYRIAD("MAXITEM") ) amt = GET_MYRIAD("MAXITEM");
SET_VAL("ITEM",name,(string)amt);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_ITEM|"+name+"="+(string)amt,llGetOwner());
}
///////////
/// Player Only aspects
///////////
// NAME
// LMIN: GET_NAME
// PPMA: CHARACTER,NAME,<firstname> <lastname>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|NAME=<firstname> <lastname>
// RSERR: Undefined
// RETURN: NONE
GET_NAME(key id) {
string name = GET_VAL("CHARACTER","NAME");
if ( name == KEY_NOT_FOUND ) {
OWNERSAY("Name not found. Resetting to default name Myriad Player");
name = "Myriad Player"; // set a default name if none is in the database
SET_NAME(name);
}
string out = "CHARACTER|NAME="+name;
if ( id != NULL_KEY ) {
integer chan = (integer)("0x"+llGetSubString(id,0,6));
llRegionSay(chan,out);
}
// FIXME how to verify good name?
//llMessageLinked(LINK_THIS,MODULE_CHARSHEET,out,llGetOwner()); // REMOVEME
//return name;
}
// LMIN: SET_NAME|NAME=<firstname> <lastname>
// PPMA: CHARACTER,NAME,<firstname> <lastname>
// LMOUT: SET_CHARACTER|NAME=<firstname> <lastname>
// LMERR: Undefined
SET_NAME(string aname) {
if ( aname == "" ) { // set NAME empty = delete
SET_VAL("CHARACTER","NAME","Myriad Player");
OWNERSAY("Name deleted. Resetting name to Myriad Player.");
return;
}
// FIXME how to verify a good name?
SET_VAL("CHARACTER","NAME",aname);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|NAME="+aname,llGetOwner()); // REMOVEME
}
// NICKNAME
// LMIN: GET_NICKNAME
// PPMA: CHARACTER,NICKNAME,<nickname>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|NICKNAME=<nickname>
// RSERR: Undefined
// RETURN: NONE
GET_NICKNAME(key id) {
string nickname = GET_VAL("CHARACTER","NICKNAME");
string out = "CHARACTER|NICKNAME="+nickname;
if ( id != NULL_KEY ) {
integer chan = (integer)("0x"+llGetSubString(id,0,6));
llRegionSay(chan,out);
}
// FIXME how to verify good name?
//llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|NICKNAME="+nickname,llGetOwner()); // REMOVEME
//return nickname;
}
// LMIN: SET_CHARACTER|NICKNAME=<nickname>
// PPMA: CHARACTER,NICKNAME,<nickname>
// LMOUT: SET_CHARACTER|NICKNAME=<nickname>
// LMERR: Undefined
SET_NICKNAME(string anick) {
if ( anick == "" ) { // set NICK empty = delete
DELETE_RECORD("CHARACTER","NICKNAME");
OWNERSAY("Nickname deleted");
return;
}
// FIXME how to verify a good name?
SET_VAL("CHARACTER","NICKNAME",anick);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|NICKNAME="+anick,llGetOwner()); // REMOVEME
}
// TITLE
// LMIN: GET_TITLE
// PPMA: CHARACTER,TITLE,<title>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|TITLE=<title>
// RSERR: Undefined
// RETURN: NONE
GET_TITLE(key id) {
string title = GET_VAL("CHARACTER","TITLE");
string out = "CHARACTER|TITLE="+title;
if ( id != NULL_KEY ) {
integer chan = (integer)("0x"+llGetSubString(id,0,6));
llRegionSay(chan,out);
}
// FIXME how to verify good name?
//llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|TITLE="+title,llGetOwner()); // REMOVEME
//return title;
}
// LMIN: SET_TITLE|TITLE=<title>
// PPMA: CHARACTER,TITLE,<title>
// LMOUT: SET_CHARACTER|TITLE=<title>
// LMERR: Undefined
SET_TITLE(string atitle) {
if ( atitle == "" ) { // set TITLE empty = delete
DELETE_RECORD("CHARACTER","TITLE");
OWNERSAY("Title deleted");
return;
}
// FIXME how to verify a good name?
SET_VAL("CHARACTER","TITLE",atitle);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|TITLE="+atitle,llGetOwner()); // REMOVEME
}
// FACTION
// LMIN: GET_FACTION
// PPMA: CHARACTER,FACTION,<factionname>
// LMOUT: NONE
// LMERR: Undefined
// RSOUT: CHARACTER|FACTION=<factionname>
// RSERR: Undefined
// RETURN: NONE
GET_FACTION(key id) {
string faction = GET_VAL("CHARACTER","FACTION");
// FIXME how to verify good faction name?
string out = "CHARACTER|FACTION="+faction;
if ( id != NULL_KEY ) {
integer chan = (integer)("0x"+llGetSubString(id,0,6));
llRegionSay(chan,out);
}
//llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|FACTION="+faction,llGetOwner()); // REMOVEME
//return faction;
}
// LMIN: SET_FACTION|FACTION=<factionname>
// PPMA: CHARACTER,FACTION,<factionname>
// LMOUT: SET_CHARACTER|FACTION=<factionname>
// LMERR: Undefined
SET_FACTION(string afaction) {
if ( afaction == "" ) { // set FACTION empty = delete
DELETE_RECORD("CHARACTER","FACTION");
OWNERSAY("Faction deleted");
return;
}
// FIXME how to verify a good name?
SET_VAL("CHARACTER","FACTION",afaction);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_CHARACTER|FACTION="+afaction,llGetOwner()); // REMOVEME
}
// RP - GENERATOR POINTBUY
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
integer GET_RP() {
integer rp = (integer)GET_VAL("CHARACTER","RP");
if ( rp < GET_MYRIAD("MINRP") ) { rp = GET_MYRIAD("MINRP"); SET_RP(rp); }
if ( rp > GET_MYRIAD("MAXRP") ) { rp = GET_MYRIAD("MAXRP"); SET_RP(rp); }
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"PROGRESS|RP="+(string)rp,llGetOwner()); // REMOVEME
return rp;
}
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
SET_RP(integer rpamt) {
if ( rpamt < GET_MYRIAD("MINRP") ) rpamt = GET_MYRIAD("MINRP");
if ( rpamt > GET_MYRIAD("MAXRP") ) rpamt = GET_MYRIAD("MAXRP");
SET_VAL("CHARACTER","RP",(string)rpamt);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"PROGRESS|RP="+(string)rpamt,llGetOwner()); // REMOVEME
}
// STUNTS
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
string GET_STUNT(string name) {
string stunt = GET_VAL("STUNT",name);
// TODO how to verify stunt?
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"STUNT|"+name+"="+stunt,llGetOwner()); // REMOVEME
return stunt;
}
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
SET_STUNT(string name,string stunt) {
// TODO how to verify stunt?
SET_VAL("STUNT",name,stunt);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_STUNT|"+name+"="+stunt,llGetOwner());
}
// QUOTES
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
string GET_QUOTE(string name) {
string quote = GET_VAL("QUOTE",name);
// TODO how to verify quote?
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"QUOTE|"+name+"="+quote,llGetOwner()); // REMOVEME
return quote;
}
// LMIN:
// PPMA:
// LMOUT:
// LMERR:
SET_QUOTE(string name,string quote) {
// TODO how to verify quote?
SET_VAL("QUOTE",name,quote);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"SET_QUOTE|"+name+"="+quote,llGetOwner());
}
DEBUG(string debugmsg) {
if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"DEBUG|"+debugmsg,llGetOwner());
}
DUMP_SHEET(key id) { // dump current character sheet
key objectowner = llList2Key(llGetObjectDetails(id,[OBJECT_OWNER]),0);
key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
if ( objectowner != regionowner ) {
ERROR("DUMP_SHEET called by object or player who is not region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
return;
}
DEBUG("Dumping character sheet to "+llKey2Name(id)+" ("+(string)id+")");
integer chan = (integer)("0x"+llGetSubString((string)id,0,6));
DUMP_TYPE(chan,"CHARACTER"); // FIXME
DUMP_TYPE(chan,"STATISTIC"); // dump list of stats and values
DUMP_TYPE(chan,"RESILIENCE"); // dump list of resiliences and values
DUMP_TYPE(chan,"BOON"); // dump list of boons and values
DUMP_TYPE(chan,"FLAW"); // dump list of flaws and values
DUMP_TYPE(chan,"EFFECT"); // dump list of mortal sfx and values
DUMP_TYPE(chan,"SKILL"); // dump list of skills and values
DUMP_TYPE(chan,"ITEM"); // dump list of equipment
DUMP_TYPE(chan,"STUNT"); // dump list of stunts
DUMP_TYPE(chan,"QUOTE"); // dump list of quotes
llSay(chan,"CHARACTER_LOADED");
}
ERASE_SHEET(key id) { // erase current character sheet
key objectowner = llList2Key(llGetObjectDetails(id,[OBJECT_OWNER]),0);
key regionowner = llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0);
if ( objectowner != regionowner || objectowner != llGetOwner() || id != llGetOwner() ) {
ERROR("ERASE_SHEET called by object or player who is not player or region owner. ["+(string)objectowner+"]["+(string)regionowner+"]");
return;
}
DEBUG("Eraseing character sheet");
ERASE_TYPE("CHARACTER"); // FIXME
ERASE_TYPE("STATISTIC"); // dump list of stats and values
ERASE_TYPE("RESILIENCE"); // dump list of resiliences and values
ERASE_TYPE("BOON"); // dump list of boons and values
ERASE_TYPE("FLAW"); // dump list of flaws and values
ERASE_TYPE("EFFECT"); // dump list of mortal sfx and values
ERASE_TYPE("SKILL"); // dump list of skills and values
ERASE_TYPE("ITEM"); // dump list of equipment
ERASE_TYPE("STUNT"); // dump list of stunts
ERASE_TYPE("QUOTE"); // dump list of quotes
OWNERSAY("Character Sheet erased.");
}
ERROR(string errmsg) {
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"ERROR|"+errmsg,llGetOwner());
}
// MEMORY
GET_MEMORY() {
OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory());
}
// GETVERSION
GET_VERSION() {
OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE);
}
OWNERSAY(string msg) {
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"OWNERSAY|"+msg,llGetOwner());
}
PARSE(string message,key id) {
// debugged in chat and link message events
// First - handle type 1 messages that do not require breaking down
list tokens = llParseString2List(message,["|"],[]);
string cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
string data = llList2String(tokens,1);
list subtokens = llParseString2List(data,["="],[]);
string attrib = llList2String(subtokens,0);
integer idata = llList2Integer(subtokens,1);
string sdata = llList2String(subtokens,1);
// REMOVEME all GET_* command blocks once PPMA is integrated in all HUD scripts
if ( cmd == "memory" ) { GET_MEMORY(); return;} // dump script memory
if ( cmd == "reset" ) { RESET(); } // reset on request
if ( cmd == "version") { GET_VERSION(); return; } // respond with version info when requested
if ( cmd == "get_status" ) { GET_STATUS(id); return; } // respond to character sheet status requests
if ( cmd == "set_status" ) { SET_STATUS(sdata); return; } // save character status
if ( cmd == "dump_sheet" ) { DUMP_SHEET(id); return; } // id is UUID of character builder, sent over from HUD script
if ( cmd == "dump_records") { DUMP_RECORDS(); return; } // dump prim persistent memory array
if ( cmd == "erase_sheet" ) { ERASE_SHEET(id); return;} // erase character sheet
if ( cmd == "format_db" ) { FORMAT_DB(); return;}
if ( cmd == "region_setting" && llToLower(attrib) == "progression" ) { SET_PROGRESSION(sdata); return;}
// ASPECTS
if ( cmd == "get_statistic" ) { GET_STATISTIC(attrib); return;}
if ( cmd == "set_statistic" ) { SET_STATISTIC(attrib,idata); return;}
if ( cmd == "get_skill" ) { GET_SKILL(attrib); return;}
if ( cmd == "set_skill" ) { SET_SKILL(attrib,idata); return;}
if ( cmd == "get_effect" ) { GET_EFFECT(attrib); return;}
if ( cmd == "set_effect" ) { SET_EFFECT(attrib,idata); return;}
if ( cmd == "get_resilience" ) { GET_RESILIENCE(attrib); return;}
if ( cmd == "set_resilience" ) { SET_RESILIENCE(attrib,idata); return;}
if ( cmd == "get_boon" ) { GET_BOON(attrib); return;}
if ( cmd == "set_boon" ) { SET_BOON(attrib,idata); return;}
if ( cmd == "get_flaw" ) { GET_FLAW(attrib); return;}
if ( cmd == "set_flaw" ) { SET_FLAW(attrib,idata); return;}
if ( cmd == "get_specie" ) { GET_SPECIE(attrib); return;}
if ( cmd == "set_specie" ) { SET_SPECIE(attrib,sdata); return;}
if ( cmd == "get_background" ) { GET_BACKGROUND(attrib); return;}
if ( cmd == "set_background" ) { SET_BACKGROUND(attrib,sdata); return;}
if ( cmd == "get_career" ) { GET_CAREER(attrib); return;}
if ( cmd == "set_career" ) { SET_CAREER(attrib,sdata); return;}
if ( cmd == "get_item" ) { GET_ITEM(attrib); return;}
if ( cmd == "set_item" ) { SET_ITEM(attrib,idata); return;}
if ( cmd == "get_name" ) { GET_NAME(id); return;}
if ( cmd == "set_name" ) { SET_NAME(sdata); return;}
if ( cmd == "get_nickname" ) { GET_NICKNAME(id); return;}
if ( cmd == "set_nickname" ) { SET_NICKNAME(sdata); return;}
if ( cmd == "get_title" ) { GET_TITLE(id); return;}
if ( cmd == "set_title" ) { SET_TITLE(sdata); return;}
if ( cmd == "get_faction") { GET_FACTION(id); return;}
if ( cmd == "set_faction" ) { SET_FACTION(sdata); return;}
if ( cmd == "get_stunt" ) { GET_STUNT(sdata); return;}
if ( cmd == "set_stunt" ) { SET_STUNT(attrib,sdata); return;}
if ( cmd == "get_quote" ) { GET_QUOTE(sdata); return;}
if ( cmd == "set_quote" ) { SET_QUOTE(attrib,sdata); return;}
if ( cmd == "get_rp" ) { GET_RP(); return;}
if ( cmd == "set_rp" ) { SET_RP(idata); return;}
// LIST FUNCTIONS
if ( cmd == "list_statistics" ) { LIST_TYPE(id,"STATISTIC"); return;}
if ( cmd == "list_skills" ) { LIST_TYPE(id,"SKILL"); return;}
if ( cmd == "list_effects" ) { LIST_TYPE(id,"EFFECT"); return;}
if ( cmd == "list_resiliences" ) { LIST_TYPE(id,"RESILIENCE"); return;}
if ( cmd == "list_boons" ) { LIST_TYPE(id,"BOON"); return;}
if ( cmd == "list_flaws" ) { LIST_TYPE(id,"FLAW"); return;}
if ( cmd == "list_species" ) { LIST_TYPE(id,"SPECIE"); return;}
if ( cmd == "list_backgrounds" ) { LIST_TYPE(id,"BACKGROUND"); return;}
if ( cmd == "list_careers" ) { LIST_TYPE(id,"CAREER"); return;}
if ( cmd == "list_items" ) { LIST_TYPE(id,"ITEM"); return;}
}
RESET() { // do any final work, then reset
llResetScript();
}
string PROGRESSION; // Progression Method: LEVEL, GRADUAL, or RANDOM
GET_PROGRESSION() {
integer CHANMYRIAD = -999; // REMOVEME
llRegionSay(CHANMYRIAD,"GET_REGION_SETTING|PROGRESSION"); // ask for this first, asking for estate triggers find_notecard function
}
SET_PROGRESSION(string method) {
method = llToUpper(method);
if ( method == "LEVEL-BASED" || method == "GRADUAL" || method == "RANDOM" ) {
PROGRESSION = method;
DEBUG("Character progression method set to: "+method);
} else {
ERROR("Unknown progression method: "+method+"! Roleplay progress will not be counted.");
}
}
GET_STATUS(key id) {
DEBUG("Sending character sheet status to "+llKey2Name(id)+" ("+(string)id+")");
integer chan = (integer)("0x"+llGetSubString((string)id,0,6));
string status = GET_VAL("CHARACTER","STATUS");
if ( status != "NONE" && status != "DRAFT" && status != "COMPLETE" ) {
status = "NONE";
}
llSay(chan,"CHARACTER|STATUS="+status);
llMessageLinked(LINK_THIS,MODULE_CHARSHEET,"CHARACTER|STATUS="+status,llGetOwner());
}
SET_STATUS(string status) {
if ( status == "NONE" || status == "DRAFT" || status == "COMPLETE" ) {
SET_VAL("CHARACTER","STATUS",status);
} else {
ERROR("SET_STATUS: Invalid character status: "+status);
}
}
SETUP() {
GET_PROGRESSION();
OWNERSAY("Character Sheet module active.");
}
default {
link_message(integer sender_num,integer num,string str,key id) {
if ( num == MODULE_CHARSHEET || num == LM_SENDTOATTACHMENT ) return; // ignore link messages not sent to us specifically
list tokens = llParseString2List(str,["|"],[]);
string cmd = llToLower(llStringTrim(llList2String(tokens,0),STRING_TRIM));
if ( cmd == "debug" || cmd == "error" || cmd == "help" || cmd == "ownersay" || cmd == "rpevent" ) return; // ignore WELL commands
// debug after message we're ignoring
DEBUG("EVENT link_message sender_num=["+(string)sender_num+"] num=["+(string)num+"] str=["+str+"] id=["+(string)id+"]");
PARSE(str,id); // parse incoming message
}
state_entry() {
SETUP();
}
}
// END