/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2021 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueEncode(list data) { list k = llList2ListStrided(data, 0, -1, 2); list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); data = []; do { data += llList2String(k, 0) + "=" + llList2String(v, 0); k = llDeleteSubList(k, 0, 0); v = llDeleteSubList(v, 0, 0); } while(llGetListLength(k) != 0); return llDumpList2String(data, "&"); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // /////////////////////////////////////////////////////////////////////////// string wasKeyValueGet(string k, string data) { if(llStringLength(data) == 0) return ""; if(llStringLength(k) == 0) return ""; list a = llParseStringKeepNulls(data, ["&", "="], []); integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); if(i != -1) return llList2String(a, 2*i+1); return ""; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// float wasFmod(float a, float p) { if(p == 0) return (float)"nan"; return a - ((integer)(a/p) * p); } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // // Original: Clive Page, Leicester University, UK. 1995-MAY-2 // /////////////////////////////////////////////////////////////////////////// list wasUnixTimeToDateTime(integer seconds) { integer mjday = (integer)(seconds/86400 + 40587); integer dateYear = 1858 + (integer)( (mjday + 321.51) / 365.25); float day = (integer)( wasFmod(mjday + 262.25, 365.25) ) + 0.5; integer dateMonth = 1 + (integer)(wasFmod(day / 30.6 + 2.0, 12.0) ); integer dateDay = 1 + (integer)(wasFmod(day,30.6)); float nsecs = wasFmod(seconds, 86400); integer dateSeconds = (integer)wasFmod(nsecs, 60); nsecs = nsecs / 60; integer dateMinutes = (integer)wasFmod(nsecs, 60); integer dateHour = (integer)(nsecs / 60); return [ dateYear, dateMonth, dateDay, dateHour, dateMinutes, dateSeconds ]; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// integer wasDateTimeToStamp( integer year, integer month, integer day, integer hour, integer minute, integer second ) { month -= 2; if (month <= 0) { month += 12; --year; } return ( (((year / 4 - year / 100 + year / 400 + (367 * month) / 12 + day) + year * 365 - 719499 ) * 24 + hour ) * 60 + minute ) * 60 + second; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// integer getYearDays(integer year) { integer leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); if(leap == TRUE) { return 366; } return 365; } /////////////////////////////////////////////////////////////////////////// // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // /////////////////////////////////////////////////////////////////////////// integer getMonthDays(integer month, integer year) { if (month == 4 || month == 6 || month == 9 || month == 11) { return 30; } if(month == 2) { integer leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); if(leap == TRUE) { return 29; } return 28; } return 31; } // for notecard reading integer line = 0; // key-value data will be read into this list list tuples = []; default { state_entry() { if(llGetInventoryType(llGetInventoryName(INVENTORY_NOTECARD, 0)) != INVENTORY_NOTECARD) { llSay(DEBUG_CHANNEL, "Sorry, could not find an inventory notecard."); return; } llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), line ); } dataserver(key id, string data) { if(data == EOF) state countdown; // invariant, length(tuples) % 2 == 0 if(data == "") { llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), ++line ); return; } integer i = llSubStringIndex(data, "#"); if(i != -1) data = llDeleteSubString(data, i, -1); list o = llParseString2List(data, ["="], []); string k = llStringTrim(llList2String(o, 0), STRING_TRIM); string v = llStringTrim(llList2String(o, 1), STRING_TRIM); if(k == "" || v == "") { llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), ++line ); return; } tuples += k; tuples += v; llGetNotecardLine( llGetInventoryName( INVENTORY_NOTECARD, 0 ), ++line ); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } on_rez(integer num) { llResetScript(); } } state countdown { state_entry() { llSetTimerEvent(1); } timer() { list alarm = llList2List( llParseString2List( wasKeyValueGet("alarm", wasKeyValueEncode(tuples)), ["-",":","T", "."],[""] ), 0, 5 ); list stamp = llList2List( llParseString2List( llGetTimestamp(), ["-",":","T", "."],[""] ), 0, 5 ); integer delta = wasDateTimeToStamp( llList2Integer(alarm, 0), llList2Integer(alarm, 1), llList2Integer(alarm, 2), llList2Integer(alarm, 3), llList2Integer(alarm, 4), llList2Integer(alarm, 5) ) - wasDateTimeToStamp( llList2Integer(stamp, 0), llList2Integer(stamp, 1), llList2Integer(stamp, 2), llList2Integer(stamp, 3), llList2Integer(stamp, 4), llList2Integer(stamp, 5) ); list count = []; // handle repeat if(delta < 0) { string repeat = wasKeyValueGet("repeat", wasKeyValueEncode(tuples)); if(repeat == "") { count += wasKeyValueGet("alarm_text", wasKeyValueEncode(tuples)); jump display; } if(repeat == "yearly") { delta = (getYearDays(llList2Integer(stamp, 0)) * 86400)-(-delta % (getYearDays(llList2Integer(stamp, 0)) * 86400)); jump set; } if(repeat == "monthly") { delta = (getMonthDays(llList2Integer(stamp, 1), llList2Integer(stamp, 0)) * 86400) - (-delta % (getMonthDays(llList2Integer(stamp, 1), llList2Integer(stamp, 0)) * 86400)); jump set; } if(repeat == "weekly") { delta = 7*86400-(-delta % (7*86400)); jump set; } if(repeat == "daily") { delta = 86400-(-delta % 86400); jump set; } if(repeat == "hourly") { delta = 3600-(-delta % 3600); jump set; } if(repeat == "minutely") { delta = 60-(-delta % 60); jump set; } } @set; // get date and remove epoch time list remaining = wasUnixTimeToDateTime(delta); remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 0)-1970 ], 0, 0); remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 1)-1 ], 1, 1); remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 2)-1 ], 2, 2); // start display integer Y = llList2Integer(remaining, 0); if(Y == 0) jump MO; count += (string)Y; if(Y > 1) { count += "years"; jump MO; } count += "year"; @MO; integer M = llList2Integer(remaining, 1); if(M == 0) jump DA; count += (string)M; if(M > 1) { count += "months"; jump DA; } count += "month"; @DA; integer D = llList2Integer(remaining, 2); if(D == 0) jump HO; count += (string)D; if(D > 1) { count += "days"; jump HO; } count += "day"; @HO; integer H = llList2Integer(remaining, 3); if(H == 0) jump MI; count += (string)H; if(H > 1) { count += "hours"; jump MI; } count += "hour"; @MI; integer I = llList2Integer(remaining, 4); if(I == 0) jump done; count += (string)I; if(I > 1) { count += "minutes"; jump done; } count += "minute"; @done; if(llGetListLength(count) == 0) { count += wasKeyValueGet("alarm_text", wasKeyValueEncode(tuples)); jump display; } count += "left"; @display; // pad with spaces so the message appears centered string result = llDumpList2String(count, " "); integer spaces = (60 - llStringLength(result))/2; string space = ""; do { space += " "; } while(--spaces >-1); // send the spaces and result to xyz text llMessageLinked(LINK_ROOT, 204000, space + result, "0"); // reschedule every minute llSetTimerEvent(60); } changed(integer change) { if(change & CHANGED_INVENTORY) llResetScript(); } on_rez(integer num) { llResetScript(); } }