// Myriad_Lite_Unopposed_Test_2-v0.0.1-20131104.lsl
// Copyright (c) 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/
// CONSTANTS - DO NOT CHANGE DURING RUN
string BASENAME = "Myriad Lite Unopposed Test2"; // base name of this script without version or date
string VERSION = "0.0.1"; // Allen Kerensky's script version
string VERSIONDATE = "20131104"; // Allen Kerensky's script yyyymmdd
integer CHANMYRIAD = -999;
integer MOD_UNOPPOSED = -1;
//////////////////////////////////////////////////////////////////////////////
// CONFIGURATION FOR THIS UNOPPOSED TEST
//
// TASK
// A descriptive name for the task
string TASK="Unlock the Locked Door";
// RPEVENT_START - a string to send as an RPEVENT when player starts - tell all
// leave empty for no RPEVENT
string RPEVENT_START = "tries to defeat the lock of an electronically-locked door";
// Statistic to use for the Unopposed Test
// Power, Grace, Intellect, Spirit
// Choose the one that makes sense based on the skill required for the test
string STATISTIC = "Intellect";
// Skill to test again
// See Myriad RPG System p.53 for many sample skill ideas
string SKILL = "Electronics";
// Standard Target Numbers
// Myriad RPG System p.19
// 4 = Foolproof
// 5 = Easy
// 6 = Average
// 7 = Tricky
// 8 = Difficult
// 9 = Very Hard
// 11 = Infeasible
// 13 = Superhuman
// 15 = Godlike
// 17 = Impossible
integer TARGETNUM = 6;
// Success Actions Recipes
// These are recipes based on Myriad Lite's Link Message formats.
// See the Myriad Lite HUD Modules, specifically the WELL module for many examples.
// Replace the | in standard link messages with a ^ for these recipes
// Separate multiple actions with commas
// Keep it short to stay in LSL message length limits
string ONSUCCESS = "OWNERSAY|You have bypassed the electronic lock and the door can be opened.";
// RPEVENT_SUCCESS - a public chat message this object will say if a player succeeds.
// Leave empty for no RPEVENT event message
string RPEVENT_SUCCESS = "bypassed the door's electronic lock.";
// Success Actions Recipes
// These are recipes based on Myriad Lite's Link Message formats.
// See the Myriad Lite HUD Modules, specifically the WELL module for many examples.
// Replace the | in standard link messages with a ^ for these recipes
// Separate multiple actions with commas
// Keep it short to stay in LSL message length limits
string ONFAIL = "OWNERSAY|You did not bypass the electronic door lock.";
// RPEVENT_FAIL - a public chat message this object will say if a player fails the task
// Leave empty for no RPEVENT message
string RPEVENT_FAIL = "failed to bypass the door's electronic lock";
// RETRIES
// TRUE or FALSE, retries are allowed
integer RETRIES = TRUE;
// TIME BETWEEN TRIES
// Number of seconds between retries
integer RETRYTIME = 10; // 10 minutes is usually good
string WAITMESSAGE = "You must wait 10 seconds between retries.";
// MAX RETRIES
// Max times before
integer MAX_RETRIES = 10;
string MORETRIES = "You can retry bypassing this lock.";
string NOMORETRIES = "The lock electronics short out and can no longer be bypassed.";
//////////////////////////////////////////////////////////////////////////////
// TEMPORARY STORAGE IN RUNTIME MEMORY
// SHOULD NOT NEED EDITING BEYOND THIS POINT.
list PLAYERS = []; // [ <playeruuid>,<timestried>,<lasttrytime> ] strided list of players accessing this task
integer HANDLE; // listenremove handle for this object's dynamic channel
integer CHANNEL; // calculated dynamic channel number for this object to listen on
// 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 modifiers) {
integer highroll = 0; // clear out the highest roll
while( attribute-- ) { // roll a dice for each point of the attribute
integer roll = 1+(integer)llFrand(6.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 + modifiers; // now, return the total of highest dice roll + skill value + modifiers
}
//
// CHECK ID to see if they pass/fail, and do retry list management - calls unopposed test
//
CHECK(key id) {
integer timestried; // place to hold how many times they have tried
integer lasttime; // place to hold last time they tried
// is player in list, get data, otherwise start data
integer pos = llListFindList(PLAYERS,[id]); // find their current record in player list in memory
if ( pos >= 0 ) { // they already had a record
timestried = llList2Integer(PLAYERS,pos + 1); // get stored number of times tried
lasttime = llList2Integer(PLAYERS,pos + 2); // get time of last try
timestried++; // add one for this attempt
} else { // no record so let's create and add one
timestried = 1; // this counts as the first try
lasttime = 0; // never tried in history
PLAYERS = PLAYERS + [ id,timestried,lasttime ]; // add the record to the memory list
}
if ( timestried > MAX_RETRIES ) llInstantMessage(id,NOMORETRIES); // are they already out of tries? say so
if ( llGetUnixTime() < ( lasttime + RETRYTIME ) ) llInstantMessage(id,WAITMESSAGE); // are they trying too soon? say so
if ( timestried <= MAX_RETRIES && llGetUnixTime() >= ( lasttime + RETRYTIME ) ) { // if they have tries, and have waited long enough
integer chan = (integer)("0x"+llGetSubString(id,0,6)); // calculate their player dynamic channel
llRegionSay(chan,"UNOPPOSED_TEST2|"+TASK+"|"+STATISTIC+"|"+SKILL); // send the test request to them / their HUD
if ( RPEVENT_START != "" ) llRegionSay(CHANMYRIAD,"RPEVENT|"+llKey2Name(id)+" "+RPEVENT_START);
}
// now update the player list in memory
pos = llListFindList(PLAYERS,[id]); // where is their record, even if we just added one
PLAYERS = llListReplaceList(PLAYERS,[id,timestried,llGetUnixTime()],pos, pos + 2); // now update the data in the player list in memory
}
DEBUG(string msg) {
llSay(PUBLIC_CHANNEL,"DEBUG: "+msg);
}
ERROR(string msg) {
llSay(PUBLIC_CHANNEL,"ERROR: "+msg);
}
SETUP() {
llSetText("Easy Intellect/Electronics\nUnopposed Test2\nMyriad Lite Preview 7",<1,1,1>,1);
// Setup This Object's Listener
if ( HANDLE != 0 ) llListenRemove(HANDLE); // remove an existing handle
CHANNEL = (integer)("0x"+llGetSubString(llGetKey(),0,6)); // calculate this object's dynamic channel number
llListen(CHANNEL,"",NULL_KEY,""); // start a new listener on object's dynamic channel
}
// UNOPPOSED_TEST|<target number>|<stat name>|<skill name>|<CSV success actions>|<CSV fail actions>
// CSV success and fail actions are CSV lists action1[,action2[,...]]
// Each action is an embedded link message which uses ^ in place of the usual | used to separate fields
// Example Actions: SAY^<channel#>^<message_with_no_commas> , SHOUT^<channel#>,<message_with_no_commas>
// See the WELL module for a preset library of actions you can stack from, but any valid link message is possible
// FIXME This is a huge cheat and security hole as is without testing for valid actions by players vs. region owners
// Returns TRUE for Success and False for Fail
// UNOPPOSED_TEST2
// integer stat = amount of player's stat (0-10)
integer UNOPPOSED_TEST2(key id,integer stat,integer skill,integer mods) {
//DEBUG("UNOPPOSED_TEST2 stat=["+(string)stat+"] skill=["+(string)skill+"] mods=["+(string)mods+"]");
// FIXME check stat range - replace constants with MIN_STAT/MAX_STAT
if ( stat < 0 || stat > 10 ) { ERROR("Invalid stat amount "+(string)stat+" in unopposed test. Ending unopposed test."); return FALSE;}
// FIXME check skill range - replace constants with MIN_SKILL/MAX_SKILL
if ( skill < 0 || skill > 7 ) { ERROR("Invalid skill amount "+(string)skill+" in unopposed test. Ending unopposed test."); return FALSE;}
// FIXME check tskill
integer check = ABILITY_TEST(stat,skill,mods); // calculate the player's ability test value
integer result = FALSE;
string acts = "" ;
string rpevent = "";
if ( check >= TARGETNUM ) {
result = TRUE;
acts = ONSUCCESS;
rpevent = RPEVENT_SUCCESS;
} else { // player lost the ability test
result = FALSE;
acts = ONFAIL; // fail actions to player
rpevent = RPEVENT_FAIL; // fail action rpevent to everyone
}
integer chan = (integer)("0x"+llGetSubString(id,0,6)); // calculate dynamic channel of player being tested
llRegionSay(chan,acts);
if ( rpevent != "" ) llRegionSay(CHANMYRIAD,"RPEVENT|"+llKey2Name(llGetOwnerKey(id))+" "+rpevent);
return result;
}
default {
state_entry() {
SETUP();
}
link_message(integer sender_num,integer num,string str,key id) {
if ( str == "UNOPPOSED_TEST2|CHECK" ) {
CHECK(id);
}
}
listen(integer channel,string name,key id,string message) {
// Listen for responses from player on this objects dynamic channel
list fields = llParseString2List(message,["|"],[]); // break into list of fields based on DIVider
string cmd = llToUpper(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command
if ( cmd == "UNOPPOSED_TEST2" ) { // UNOPPOSED_TEST2|<statamount>|<skillamount>|<modfiiers>
integer stat = llList2Integer(fields,1);
integer skill = llList2Integer(fields,2);
integer mods = llList2Integer(fields,3);
integer check = UNOPPOSED_TEST2(id,stat,skill,mods);
if ( check == TRUE ) {
// do more specific things if the check succeeds - unlock the door, file, etc
llMessageLinked(LINK_THIS,MOD_UNOPPOSED,"UNOPPOSED_TEST2|SUCCESS",id);
} else {
// do more specific things if the check fails - set off trap, explode, sound alarm, etc
llMessageLinked(LINK_THIS,MOD_UNOPPOSED,"UNOPPOSED_TEST2|FAILURE",id);
}
return;
}
}
touch_start(integer num_detected) {
while (num_detected--) {
key who = llDetectedKey(num_detected); // who is clicking
CHECK(who);
}
}
}