// 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); } } }