User:Yumi Murakami/Addiction Moderator
Yes, this was made in response to a friend complaining :) This script provides a local-time clock in your HUD, but can also be set to warn you when you've either played SL too much or you're playing at certain forbidden times. It also can share local timezone times between users.
This was on sale for a little while, but for some reason people seem to be reluctant to pay for something which tells them not to play SL. With retrospect, I probably should have been able to work that one out myself. :)
The object requires three prims linked together, which can just be basic cubes. This script should be in the root:
integer isOpen = TRUE;
integer unixTime;
integer timezoneAdjust;
integer secs;
integer mins;
integer hours;
integer days;
integer years;
integer minsSinceDay;
integer notecardState;
integer notecardLine;
integer notecardSize;
integer todayBase;
vector dockedPosition;
list bedStart;
list bedEnd;
list workStart;
list workEnd;
list maxtime;
integer timeSinceReset;
integer closeReason;
list dayNames = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
integer dayOfWeek;
integer updateStartTime;
integer updateEndTime;
list updateDays = [];
integer lastWasSelector;
integer lastSeenTime;
// For bedtimes, because they are weird, we use full UNIX timestamps instead of minssinceday.
// Toomuch = d847c272-54c0-392e-58c7-7e0791315a9f
// Toolate = 1c1ff2e5-e6bc-db1b-8d56-1078eda40562
// Working = 2af56d7b-e6d6-f3bb-bf22-e1d221c5d53b
open(key warntexture) {
dockedPosition = llGetLocalPos();
llMessageLinked(LINK_SET,1,"",NULL_KEY);
llSetLinkTexture(2,warntexture,ALL_SIDES);
llSetPrimitiveParams([PRIM_POSITION,<0,0,0>,PRIM_SIZE,<0.01,1.321,0.999>]);
isOpen = TRUE;
}
close() {
llMessageLinked(LINK_SET,0,"",NULL_KEY);
isOpen = FALSE;
llSetPrimitiveParams([PRIM_POSITION,dockedPosition,PRIM_SIZE,<0.01,0.01,0.01>]);
}
// If sleep time is in the morning, it's actually for the next day.
// (If we say "I go to bed on Monday at 1am", it's actually Tuesday when we go to bed.)
integer realSleepTime(integer sleepTime, integer wakeTime, integer dayAdjust) {
integer realSleepTime = todayBase + (sleepTime * 60) + (dayAdjust * 86400);
if (sleepTime < 720) realSleepTime += 86400;
return realSleepTime;
}
// Assume wake time is the first time it is that hour after sleep time.
integer realWakeTime(integer sleepTime, integer wakeTime, integer dayAdjust) {
if (sleepTime == ERR_GENERIC) return -1;
integer realSleepTime = todayBase + (sleepTime * 60) + (dayAdjust * 86400);
integer realWakeTime = todayBase + (wakeTime * 60) + (dayAdjust * 86400);
if (sleepTime < 720) realSleepTime += 86400;
if (realWakeTime < realSleepTime) realWakeTime += 86400;
return realWakeTime;
}
considerResetting(integer lastSeenTime,integer unixTime) {
// When do we reset? At the start of a new day. And when's the start of a new day? Wake up time, of course!
// AAAAARRRRGGGH!
integer wakeToday = realWakeTime(llList2Integer(bedStart,dayOfWeek),llList2Integer(bedEnd,dayOfWeek),0);
if (wakeToday == ERR_GENERIC) {
// They didn't specify a wakeup time.. so reset at midnight
wakeToday = todayBase;
}
if ((lastSeenTime < wakeToday) && (unixTime >= wakeToday)) { timeSinceReset=0;}
integer yesterday = (dayOfWeek - 1);
if (yesterday == ERR_GENERIC) yesterday = 6;
wakeToday = realWakeTime(llList2Integer(bedStart,yesterday),llList2Integer(bedEnd,yesterday),-1);
if ((lastSeenTime < wakeToday) && (unixTime >= wakeToday)) { timeSinceReset=0;}
}
integer tryBedBoundary(integer sleepTime, integer wakeTime, integer dayAdjust) {
if (sleepTime == ERR_GENERIC) return FALSE;
//llOwnerSay((string)dayOfWeek);
integer rst = realSleepTime(sleepTime,wakeTime,dayAdjust);
integer rwt = realWakeTime(sleepTime,wakeTime,dayAdjust);
return ((unixTime >= rst) && (unixTime < rwt));
}
integer inBedTime() {
// Did I mention that bedtimes are a complete pain in the ass to do?
// First try today's bedtime.
integer inBed = tryBedBoundary(llList2Integer(bedStart,dayOfWeek),llList2Integer(bedEnd,dayOfWeek),0);
if (inBed) return TRUE;
integer yesterday = (dayOfWeek - 1);
if (yesterday == ERR_GENERIC) yesterday = 6;
// Now try yesterday's, in case it's actually this morning
inBed = tryBedBoundary(llList2Integer(bedStart,yesterday),llList2Integer(bedEnd,yesterday),-1);
return inBed;
}
// We assume work is a bit more normal than bed..
integer inWorkTime() {
integer workToday = llList2Integer(workStart,dayOfWeek);
if (workToday != ERR_GENERIC) {
integer workEndToday = llList2Integer(workEnd,dayOfWeek);
if ((minsSinceDay >= workToday) && (minsSinceDay <= workEndToday)) return TRUE;
}
return FALSE;
}
integer parseSingleTime(string time,integer limited) {
integer isPM = FALSE;
integer is12hr = FALSE;
if (llGetSubString(time,-2,-1) == "am") {
is12hr = TRUE;
time = llGetSubString(time,0,-3);
}
if (llGetSubString(time,-2,-1) == "pm") {
isPM = TRUE;
is12hr = TRUE;
time = llGetSubString(time,0,-3);
}
integer colon = llSubStringIndex(time,":");
if (colon == ERR_GENERIC) {
llOwnerSay("Error: I was expecting '" + time + "' to be a time, but I couldn't find a colon.");
return ERR_GENERIC;
}
string hourBit = llGetSubString(time,0,colon - 1);
string minuteBit = llGetSubString(time,colon+1,-1);
integer hourInt = (integer)hourBit;
if (hourInt == 0) {
if ((hourBit != "0") && (hourBit != "00")) {
llOwnerSay("Error: I was expecting '" + time + "' to be a time, but '" + hourBit + "' (which I thought was the hours) doesn't seem to be a number.");
return ERR_GENERIC;
}
}
if (hourInt == 24) hourInt = 0;
if (hourInt > 24) {
llOwnerSay("Error: In '" + time + "', the hour is greater than 23.");
return ERR_GENERIC;
}
if ((hourInt > 12) && (is12hr)) {
llOwnerSay("Error: In '" + time +"', you've specified am or pm after a 24 hour clock time.");
return ERR_GENERIC;
}
if ((is12hr) && (limited)) {
llOwnerSay("Error: In '" + time + "', you've specified am or pm on something that's an *amount* of time, not a clock time.");
return ERR_GENERIC;
}
if (isPM) hourInt += 12;
integer minuteInt = (integer)minuteBit;
if (minuteInt == 0) {
if ((minuteBit != "0") && (minuteBit != "00")) {
llOwnerSay("Error: I was expecting '" + time + "' to be a time, but '" + minuteBit + "' (which I thought was the minutes) doesn't seem to be a number.");
return ERR_GENERIC;
}
}
if (minuteInt > 59) {
llOwnerSay("Error: In '" + time + "', the minutes are greater than 59.");
return ERR_GENERIC;
}
return (hourInt * 60) + (minuteInt);
}
integer parseTime(string time) {
integer dash = llSubStringIndex(time,"-");
if (dash == ERR_GENERIC) {
llOwnerSay("Error: I was thinking that '" + time + "' would specify a range of times, but I couldn't find a -.");
return ERR_GENERIC;
}
string firstTime = llGetSubString(time,0,dash - 1);
string secondTime = llGetSubString(time,dash+1,-1);
updateStartTime = parseSingleTime(firstTime,FALSE);
if (updateStartTime == ERR_GENERIC) return ERR_GENERIC;
updateEndTime = parseSingleTime(secondTime,FALSE);
if (updateEndTime == ERR_GENERIC) return ERR_GENERIC;
return TRUE;
}
updateTime() {
unixTime = llGetUnixTime(); // Seconds since Jan 1 1970
unixTime += timezoneAdjust * 3600;
secs = unixTime % 60;
mins = (unixTime / 60) % 60;
hours = (unixTime / 3600) % 24;
integer secsSinceDay = (unixTime % 86400);
todayBase = unixTime - secsSinceDay;
minsSinceDay = secsSinceDay / 60;
string dhrs = (string)hours;
if (llStringLength(dhrs) == 1) dhrs = "0"+dhrs;
string dmins = (string)mins;
if (llStringLength(dmins) == 1) dmins = "0"+dmins;
string dsecs = (string)secs;
if (llStringLength(dsecs) == 1) dsecs = "0"+dsecs;
llSetText(dhrs + ":" + dmins + ":" + dsecs,<1,1,1>,1.0);
// To save on lag, only do major math every minute.
integer totalDays = (unixTime / 86400);
dayOfWeek = totalDays % 7;
dayOfWeek = (dayOfWeek + 4) % 7;
timeSinceReset++;
considerResetting(lastSeenTime,unixTime);
lastSeenTime = unixTime;
if (!isOpen) {
if (inBedTime()) {
closeReason = 1;
open("1c1ff2e5-e6bc-db1b-8d56-1078eda40562");
}
if (inWorkTime()) {
closeReason = 2;
open("2af56d7b-e6d6-f3bb-bf22-e1d221c5d53b");
}
integer maxTimeToday = llList2Integer(maxtime,dayOfWeek);
if (maxTimeToday != ERR_GENERIC) {
if (timeSinceReset >= (llList2Integer(maxtime,dayOfWeek) * 60)) {
closeReason = 3;
open("d847c272-54c0-392e-58c7-7e0791315a9f");
}
}
} else {
if (closeReason == 1) {
if (!inBedTime()) close();
}
if (closeReason == 2) {
if (!inWorkTime()) close();
}
if (closeReason == 3) {
if (timeSinceReset < (llList2Integer(maxtime,dayOfWeek) * 60)) close();
}
}
}
loadNotecard() {
llOwnerSay("Loading notecard.");
bedStart = [-1,-1,-1,-1,-1,-1,-1];
bedEnd = [-1,-1,-1,-1,-1,-1,-1];
workStart = [-1,-1,-1,-1,-1,-1,-1];
workEnd = [-1,-1,-1,-1,-1,-1,-1];
maxtime = [-1,-1,-1,-1,-1,-1,-1];
notecardState = 0;
notecardLine = 0;
lastWasSelector = FALSE;
llGetNumberOfNotecardLines("Configuration");
}
default
{
state_entry()
{
close();
timeSinceReset = 0;
loadNotecard();
}
timer() {
updateTime();
}
dataserver(key request, string response) {
if (notecardState == 0) {
notecardSize = (integer)response;
notecardState = 1;
llGetNotecardLine("Configuration",0);
return;
}
if (llGetSubString(response,0,0) != ";") {
string line = llToLower(response);
integer space = llSubStringIndex(response," ");
string command;
if (space != ERR_GENERIC) {
command = llGetSubString(line,0,space - 1);
} else {
command = line;
}
if (command=="timezone") {
string rest = llGetSubString(line,space+1,-1);
string firstthree = llGetSubString(rest,0,2);
integer adjust = -1;
if (firstthree == "gmt") adjust = 0;
if (firstthree == "pst") adjust = 5;
if (firstthree == "pdt") adjust = 6;
if (firstthree == "slt") adjust = 5;
if (adjust == -1) {
llOwnerSay("Error: Couldn't understand timezone string. Must be in the format 'GMT', 'PST', or 'PDT' followed by + or - a number.");
return;
}
if (llStringLength(rest) != 3) {
string radj = llGetSubString(rest,3,-1);
if (llGetSubString(radj,0,0) == "+") radj = llGetSubString(radj,1,-1);
integer iadj = (integer)radj;
if ((iadj == 0) && (radj != "+0") && (radj != "-0")) {
llOwnerSay("Error: Couldn't understand timezone string. I was thinking '" + radj +"' would be an adjustment (-0, +2, etc) but it doesn't seem to be a number.");
}
adjust += iadj;
}
timezoneAdjust = adjust;
}
integer daySelect = llListFindList(dayNames,[command]);
if (daySelect != ERR_GENERIC) {
if (!lastWasSelector) updateDays = [];
updateDays += [daySelect]; lastWasSelector = TRUE;
}
if (command == "weekend") {
if (!lastWasSelector) updateDays = [];
updateDays += [0,6]; lastWasSelector = TRUE;
}
if (command == "weekdays") {
if (!lastWasSelector) updateDays = [];
updateDays += [1,2,3,4,5]; lastWasSelector = TRUE;
}
if (command == "weeknights") {
if (!lastWasSelector) updateDays = [];
updateDays += [0,1,2,3,4]; lastWasSelector = TRUE;
}
if (command == "weekendnights") {
if (!lastWasSelector) updateDays = [];
updateDays += [5,6]; lastWasSelector = TRUE;
}
if (command == "always") {
if (!lastWasSelector) updateDays = [];
updateDays = [0,1,2,3,4,5,6]; lastWasSelector = TRUE;
}
if (command == "bed") {
if (updateDays == []) {
updateDays = [0,1,2,3,4,5,6];
}
string rest = llGetSubString(line,space+1,-1);
integer ok = parseTime(rest);
if (ok == ERR_GENERIC) return;
integer t;
for (t=0; t<llGetListLength(updateDays); t++) {
bedStart = llListReplaceList(bedStart,[updateStartTime],llList2Integer(updateDays,t),llList2Integer(updateDays,t));
bedEnd = llListReplaceList(bedEnd,[updateEndTime],llList2Integer(updateDays,t),llList2Integer(updateDays,t));
}
lastWasSelector = FALSE;
}
if (command == "work") {
if (updateDays == []) {
updateDays = [0,1,2,3,4,5,6];
}
string rest = llGetSubString(line,space+1,-1);
integer ok = parseTime(rest);
if (ok == ERR_GENERIC) return;
integer t;
for (t=0; t<llGetListLength(updateDays); t++) {
workStart = llListReplaceList(workStart,[updateStartTime],llList2Integer(updateDays,t),llList2Integer(updateDays,t));
workEnd = llListReplaceList(workEnd,[updateEndTime],llList2Integer(updateDays,t),llList2Integer(updateDays,t));
}
lastWasSelector = FALSE;
}
if (command == "max") {
string rest = llGetSubString(line,space+1,-1);
integer ok = parseSingleTime(rest,TRUE);
if (ok == -1) return;
integer t;
for (t=0; t<llGetListLength(updateDays); t++) {
maxtime = llListReplaceList(maxtime,[ok],llList2Integer(updateDays,t),llList2Integer(updateDays,t));
}
lastWasSelector = FALSE;
}
}
notecardLine ++;
if (notecardLine < notecardSize) {
llGetNotecardLine("Configuration",notecardLine);
} else {
llOwnerSay("Notecard OK.");
llSetTimerEvent(1.0);
llListen(7,"",llGetOwner(),"");
llListen(-192781,"",NULL_KEY,"");
}
}
listen(integer channel, string name, key speaker, string message) {
if (channel == 7) {
integer result = parseSingleTime(message,FALSE);
if (result == -1) return;
result -= (timezoneAdjust * 60);
llSay(-192781,"bta" + (string)result);
llOwnerSay("Transmitted time: " + message);
} else {
if (llGetSubString(message,0,2) != "bta") return;
integer time = (integer)(llGetSubString(message,3,-1));
time += (timezoneAdjust * 60);
integer timehours = time / 60;
integer timemins = time % 60;
string dmins= (string)timemins;
if (timemins < 10) dmins = "0" + dmins;
string oldName = llGetObjectName();
llSetObjectName(name);
llOwnerSay((string)timehours + ":" + (string)timemins);
llSetObjectName(oldName);
}
}
changed(integer thechange) {
if (!(thechange & CHANGED_INVENTORY)) return;
llOwnerSay("Notecard changed, re-reading it.");
loadNotecard();
}
}
This in the first link prim:
default
{
link_message(integer sender, integer int, string str, key id) {
if (int == 1) llSetPrimitiveParams([PRIM_POSITION, <-0.02410, 0.00640, 0.22760> ,PRIM_SIZE,<0.01000, 0.67290, 0.11141>]);
if (int == 0) llSetPrimitiveParams([PRIM_POSITION,<0.0,0.0,0.0>, PRIM_SIZE,<0.01,0.01,0.01>]);
}
}
And this in the second:
list textures = ["b2611ad2-3b42-1896-5315-afa074ca70a9","320111b3-c083-f0e5-5503-932b963e4f9d","20d5b6e2-764f-3243-6c8e-c2cb1fd590dd","4ef07f25-d4df-2490-6277-3e942f3967df"];
default
{
link_message(integer sender, integer int, string str, key id) {
if (int == 1) {
llSetPrimitiveParams([PRIM_POSITION,<-0.02410, 0.00640, -0.01820>,PRIM_SIZE,<0.01000, 0.67290, 0.11141>]);
integer quote = (integer)llFrand(3.99);
llSetTexture((key)(llList2String(textures,quote)),ALL_SIDES);
}
if (int == 0) llSetPrimitiveParams([PRIM_POSITION,<0.0,0.0,0.0>, PRIM_SIZE,<0.01,0.01,0.01>]);
}
}
Original Documentation Notecard
THE SL LOCAL CLOCK / ADDICTION MODERATOR
Thanks for buying the SL addiction moderator! This notecard will explain how to get it set up and working for you as soon as possible.
To start, "Wear" the addiction moderator, and then right-click and "edit" it. Look at the "content" tab, and you can open and take a look at the "configuration" notecard. This is where you put the settings that you want to use.
-- SETTING TIMEZONE --
The first setting - and the only one you absolutely MUST set - is "timezone". After the word "timezone", put the GMT timezone offset for your country, in the format GMT+5 (or whatever the appropriate offset is). If you are using Windows you can look this up in Regional Settings. If you know your UTC offset, that's the same as a GMT offset, but you must use the name GMT in the timezone description on the notecard. If you don't know either, simply compare the real time for you to the Second Life time shown at the top of your screen, and enter the difference (in hours) between that time and your real time, in the format PST+2 (2 hours ahead of PST), or PST-3 (3 hours behind PST). You may specify PDT also, if that's the current Second Life time.
Note: Addiction moderator does NOT automatically change for Daylight Saving, because it happens at different times of the year in different countries. You'll need to manually edit the notecard when you enter Daylight Savings. Sorry!
-- ADDICTION LIMITS --
After your timezone, in the notecard you should specify the limits you want the addiction moderator to impose. You can specify any number of limits. Each limit is specified in two sections: the first section specifies the days on which the limit applies, and the second specifies what the limit is. Here's an example:
monday bed 23:00-09:00
This specifies that you want to be in bed between 23:00 and 09:00 on Monday night. (Of course, the 09:00 refers to 9am on Tuesday morning.) You can specify any day of the week. If you want to apply the same limit to several days, you can specify them in several lines:
monday wednesday friday bed 23:00-09:00
This specifies the bed limit to apply on Monday, Wednesday, and Friday. As well as naming the days, you can use the following shortcut phrases, for multiple days:
weekend (saturday and sunday) weekdays (monday to friday) weeknights (sunday to thursday) weekendnights (friday and saturday) always (always!)
Limits specified later on the notecards override earlier ones. You can take advantage of this to simplify your settings. For example:
always bed 23:00-09:00 thursday bed 02:00-09:00
This specifies that on all days other than Thursday, you want to go to bed at 11pm, but on Thursday, you can go at 2am.
Bed is not the only limit you can set. You can also set "work", the hours at which you're supposed to be working IRL. You can set these at the same time as bed, as below:
weekdays bed 23:00-07:00 work 09:00-17:00
Or you can set them independantly, like this:
weekdays work 09:00-17:00 weeknights bed 23:00-17:00
There's a third limit, too: "max". Max lets you specify the maximum amount of time to spend logged into SL on a given day. This is specified as a single time. For example:
always max 03:00
Specifies that you want to spend only 3 hours total on SL on any given day. You can mix this up with bed and work limits in the same ways described above.
Some notes about setting limits:
* You don't have to set all 3 limits if you don't want to. * Bedtimes specified in the morning will be assumed to be technically the morning of the following day. So if you specify Monday bed 02:00-09:00, it'll assume you mean that you go to bed at 2am *after* Monday, ie, technically Tuesday 2am. * If you have a "max" setting for the maximum time on SL in a day, a "day" is defined as the period between the specified wake-up time (ie, end of the bed limit) for one day and the next. If you don't specify a wake-up time for either of the days, it's assumed to be midnight.
-- USING THE MODERATOR --
Once you have set up the notecard, you can simply keep it attached. Whenever any of your addiction limits are exceeded, the moderator will cover up your SL display with a warning message and an inspiring quote. You can, of course, choose to detach it at that point, but it's hoped it'll assist and serve as a warning.
As an additional function, the moderator provides a tool for communicating times to others. If the other person is wearing an Addiction Moderator as well, you can say a local time on channel 7 (as follows: /7 09:00). Anyone else wearing a Moderator will then hear the same time, TRANSLATED INTO THEIR TIME ZONE. For example, if your timezone is GMT+6, you can say "/7 09:00" for 9am in your local time, and if someone listening is wearing a moderator with their timezone set to GMT+0, they will hear "03:00". This can be a handy way to make appointments with people without any confusion about timezones and also without having to reveal where you're located.
Have fun! Please send any comments or problem reports to Yumi Murakami by IM or notecard.