Rental Cube

Just a simple cube for renting a stall or store. Supports refunding rent to an avatar if they wish to end the lease early. 
//Rental Script v1.5.3
//Copyright 2003-2009 by Hank Ramos
//Options
vector rentalOffset = <0,0,10>;
float updateInterval = 60.0; //seconds
string infoNotecard = "Rent This Space Info";
//Variables
string tierName;
float rentalCost;
integer primCount;
integer rentalVolume;
float refundFee;
key renterID;
string renterName;
float rentalTime;
integer listenQueryID;
vector initPos;
vector initScale;
integer count;
integer lineCount;
key readKey;
string rentalGrade;
integer primAllotment;
//Constants
float ONE_WEEK = 604800.0;
float ONE_DAY = 86400.0;
float ONE_HOUR = 3600.0;
dispString(string value)
{
 llSetText(value, <1,1,1>, 1);
}
sendReminder(string message)
{ 
 llInstantMessage(renterID, "Your lease located in " + llGetRegionName() + " (" + (string)initPos.x + "," + (string)initPos.y + "," + (string)initPos.z + ") will expire " + message); 
}
saveData()
{
 list saveData;
 vector storageVector;
 
 saveData += renterID;
 saveData += renterName;
 saveData += llRound(rentalTime);
 storageVector = initPos * 1000;
 saveData += "<" + (string)llRound(storageVector.x) + "," + (string)llRound(storageVector.y) + "," + (string)llRound(storageVector.z) + ">";
 storageVector = initScale * 1000;
 saveData += "<" + (string)llRound(storageVector.x) + "," + (string)llRound(storageVector.y) + "," + (string)llRound(storageVector.z) + ">";
 
 llSetObjectDesc(llDumpList2String(saveData, "|"));
}
string getTimeString(integer time)
{
 integer days;
 integer hours;
 integer minutes; 
 integer seconds;
 
 days = llRound(time / 86400);
 time = time % 86400;
 
 hours = (time / 3600);
 time = time % 3600;
 minutes = time / 60;
 time = time % 60;
 seconds = time;
 
 return (string)days + " days, " + (string)hours + " hours, " + (string)minutes + " minutes"; // + ":" + (string)seconds; 
}
integer setupDialogListen()
{
 integer chatChannel = (integer)llFrand(2000000);
 llListenRemove(listenQueryID);
 listenQueryID = llListen(chatChannel, "", NULL_KEY, "");
 return chatChannel;
}
updateTimeDisp()
{ 
 dispString("Leased by: " + renterName + "\nTime Remaining: " + getTimeString(llRound(rentalTime))); 
}
dispData()
{
 llSay(0, "========================");
 llSay(0, "Rental Space Information");
 llSay(0, "========================");
 llSay(0, "This space is currently leased by " + renterName);
 llSay(0, "The current rental price is L$" + (string)((integer)rentalCost) + " per week.");
 llSay(0, "This space will be open for lease in " + getTimeString(llRound(rentalTime)) + "."); 
 llSay(0, "Memory Free: " + (string)llGetFreeMemory());
}
default
{
 state_entry()
 {
 state initialize;
 }
}
state initialize
{
 state_entry()
 {
 llSetTimerEvent(300);
 llOwnerSay("Waiting to obtain Debit Permissions.");
 llRequestPermissions(llGetOwner(), PERMISSION_DEBIT);
 }
 run_time_permissions(integer permissions)
 {
 //Only wait for payment if the owner agreed to pay out money
 if (permissions & PERMISSION_DEBIT)
 {
 state loadSettings;
 }
 } 
 on_rez(integer start_param)
 {
 llResetScript();
 } 
 timer()
 {
 llRequestPermissions(llGetOwner(), PERMISSION_DEBIT);
 }
 touch_start(integer total_number)
 {
 integer x;
 for (x = 0; x < total_number; x += 1)
 {
 if (llDetectedKey(x) == llGetOwner())
 {
 llResetScript();
 }
 }
 llSay(0, "Waiting to obtain Debit Permissions from Owner.");
 }
 state_exit()
 {
 llSetTimerEvent(0);
 llSay(0, "Initialized.");
 }
}
state loadSettings
{
 state_entry()
 {
 integer found = FALSE;
 integer x;
 
 count = 0;
 lineCount = 0;
 
 list savedList = llCSV2List(llGetObjectDesc());
 
 if (llGetListLength(savedList) == 4)
 {
 rentalGrade = llList2String(savedList, 0);
 }
 else
 {
 rentalGrade = llGetObjectDesc();
 }
 for (x = 0; x < llGetInventoryNumber(INVENTORY_NOTECARD); x += 1)
 {
 if (llGetInventoryName(INVENTORY_NOTECARD, x) == "Settings")
 {
 found = TRUE; 
 }
 }
 if (found)
 {
 llOwnerSay("Reading Settings Notecard...");
 readKey = llGetNotecardLine("Settings", lineCount); 
 }
 else
 {
 llOwnerSay("Settings Notecard Not Found.");
 llResetScript();
 }
 }
 dataserver(key requested, string data)
 {
 integer integerData;
 float floatData;
 
 if (requested == readKey) 
 { 
 if (data != EOF)
 {
 if ((llSubStringIndex(data, "#") != 0) && (data != "") && (data != " "))
 {
 integerData = (integer)data;
 floatData = (float)data;
 
 if (count == 0)
 {
 tierName = data;
 }
 else if (count == 1)
 {
 if (integerData >= 0)
 {
 rentalCost = integerData;
 }
 else
 {
 rentalCost = 0;
 }
 }
 else if (count == 2)
 {
 if (integerData >= 1)
 {
 primCount = integerData;
 }
 else
 {
 primCount = 1;
 }
 }
 else if (count == 3)
 {
 if (integerData >= 16)
 {
 rentalVolume = integerData;
 }
 else
 {
 rentalVolume = 16;
 }
 }
 else if (count == 4)
 {
 if (integerData >= 0)
 {
 refundFee = integerData;
 }
 else
 {
 refundFee = 0;
 }
 }
 else if (count == 5)
 {
 rentalOffset = (vector)data;
 }
 else if (count == 6)
 {
 infoNotecard = data;
 }
 count += 1;
 }
 lineCount += 1;
 readKey = llGetNotecardLine("Settings", lineCount);
 }
 else
 {
 llOwnerSay("===============");
 llOwnerSay("Settings Loaded");
 llOwnerSay("===============");
 llOwnerSay("Space Name: " + tierName);
 llOwnerSay("Rental Cost: L$" + (string)llRound(rentalCost));
 llOwnerSay("Prim Count: " + (string)primCount);
 llOwnerSay("Space Volume: " + (string)rentalVolume + " sqm");
 llOwnerSay("Refund Fee: L$" + (string)refundFee);
 llOwnerSay("===============");
 llOwnerSay("Ready for Service!");
 list savedList = llParseString2List(llGetObjectDesc(), ["|"], []);
 if (llGetListLength(savedList) == 5)
 {
 renterID = llList2Key(savedList, 01);
 renterName = llList2String(savedList, 1);
 rentalTime = llList2Integer(savedList, 2);
 initPos = (vector)llList2String(savedList, 3) / 1000;
 initScale = (vector)llList2String(savedList, 4) / 1000;
 state rented;
 }
 else
 {
 renterID = NULL_KEY;
 renterName = "Nobody";
 rentalTime = 0;
 initPos = llGetPos();
 initScale = llGetScale();
 state idle;
 }
 }
 }
 }
}
state idle
{
 state_entry()
 { 
 llSetObjectDesc("");
 llSetTexture("rentthisspace", ALL_SIDES);
 llSetScale(initScale);
 llSetPos(initPos);
 llSetTimerEvent(updateInterval);
 dispString(tierName + "\nLease this space for L$" + (string)llRound(rentalCost) + " per week.\n" + (string)rentalVolume + " sq meters\n" + (string)primCount + " prims\nPay this Sign to begin your lease.");
 }
 moving_end()
 {
 initPos = llGetPos();
 }
 changed(integer change)
 {
 if (change & CHANGED_SCALE)
 {
 initScale = llGetScale();
 }
 }
 touch_start(integer num_detected)
 {
 integer x;
 integer chatChannel;
 
 for (x = 0; x < num_detected; x += 1)
 {
 if (llDetectedKey(x) == llGetOwner())
 {
 llDialog(llGetOwner(), "Owner Options. Select one of the options below...", ["Info", "Reset"], setupDialogListen());
 return;
 } 
 }
 
 llSay(0, "Lease this space for L$" + (string)llRound(rentalCost) + " per week. " + (string)rentalVolume + " sq meters. " + (string)primCount + " prims. Pay this Sign to begin your lease.");
 
 for (x = 0; x < num_detected; x += 1)
 {
 llGiveInventory(llDetectedKey(x), infoNotecard);
 } 
 }
 listen(integer channel, string name, key id, string message)
 {
 if (message == "Reset")
 {
 llResetScript();
 }
 else if (message == "Info")
 {
 llListenRemove(listenQueryID);
 dispData();
 llSay(0, "Lease this space for L$" + (string)llRound(rentalCost) + " per week. " + (string)rentalVolume + " sq meters. " + (string)primCount + " prims. Pay this Sign to begin your lease.");
 llGiveInventory(id, infoNotecard);
 }
 } 
 money(key id, integer amount)
 {
 if (amount >= rentalCost)
 {
 renterID = id;
 renterName = llKey2Name(renterID);
 rentalTime = ONE_WEEK * amount / rentalCost;
 saveData();
 
 llSay(0, "Thank you " + renterName + " for leasing! Your lease will expire in " + getTimeString((integer)rentalTime) + ".");
 
 state rented;
 }
 else
 {
 llSay(0, "This space costs L$" + (string)rentalCost + " to rent. Refunding paid balance.");
 llGiveMoney(id, amount);
 }
 }
}
state rented
{
 state_entry()
 {
 llSetTexture("infosign", ALL_SIDES);
 llSetScale(<0.5, 0.5, 0.5>);
 llSetPos(initPos + rentalOffset);
 
 updateTimeDisp();
 llResetTime();
 llSetTimerEvent(updateInterval);
 }
 touch_start(integer num_detected)
 {
 integer x;
 key detectedKey;
 
 for (x = 0; x < num_detected; x += 1)
 {
 detectedKey = llDetectedKey(x);
 if (detectedKey == llGetOwner())
 {
 llDialog(detectedKey, "Lease Options. Select one of the options below...", ["Refund Time", "Info", "Release", "Reset"], setupDialogListen());
 }
 else if (detectedKey == renterID)
 {
 llDialog(detectedKey, "Lease Options. Select one of the options below...", ["Refund Time", "Info"], setupDialogListen());
 }
 else
 {
 dispData();
 llGiveInventory(detectedKey, infoNotecard);
 }
 }
 }
 money(key id, integer amount)
 {
 if ((id == renterID)||(id == llGetOwner()))
 {
 float addTime;
 
 addTime = ONE_WEEK*amount/rentalCost;
 rentalTime += addTime;
 
 llInstantMessage(id, "Adding " + getTimeString(llRound(addTime)) + " to your lease. Lease Time is Now: " + getTimeString(llRound(rentalTime)) + ".");
 saveData();
 updateTimeDisp();
 }
 else
 {
 llInstantMessage(id, "Refunding Money...");
 llGiveMoney(id, amount);
 llInstantMessage(id, "This space is currently leased by " + renterName + ". This space will be open for lease in " + getTimeString(llRound(rentalTime)) + "."); 
 }
 }
 listen(integer channel, string name, key id, string message)
 {
 integer refundAmount;
 
 llListenRemove(listenQueryID);
 if (message == "Info")
 {
 dispData();
 llGiveInventory(id, infoNotecard);
 }
 else if (message == "Refund Time")
 {
 llDialog(id, "Are you sure you want to TERMINATE your lease and refund your money, minus a L$" + (string)refundFee + " fee?", ["YES", "NO"], setupDialogListen());
 }
 else if (message == "YES")
 {
 refundAmount = llRound((rentalTime/ONE_WEEK)*rentalCost - refundFee);
 llInstantMessage(renterID, "Refunding L$" + (string)refundAmount + ", which includes a L$" + (string)refundFee + " termination fee.");
 llGiveMoney(renterID, refundAmount);
 llInstantMessage(llGetOwner(), "LEASE REFUNDED: leased by " + renterName + " located in " + llGetRegionName() + " (" + (string)initPos.x + "," + (string)initPos.y + "," + (string)initPos.z + ") has ended. Refunded L$" + (string)refundAmount + ".");
 state idle;
 }
 else if (message == "Release")
 {
 llDialog(id, "Are you sure you want to TERMINATE this lease with NO REFUND?", ["Yes", "No"], setupDialogListen());
 }
 else if (message == "Yes")
 {
 llInstantMessage(llGetOwner(), "LEASE TERMINATED: leased by " + renterName + " located in " + llGetRegionName() + " (" + (string)initPos.x + "," + (string)initPos.y + "," + (string)initPos.z + ") has ended. Refunded L$0.");
 state idle; 
 }
 else if (message == "Reset")
 {
 llResetScript();
 }
 }
 timer()
 {
 float timeElapsed = llGetAndResetTime();
 if (timeElapsed > (updateInterval * 4))
 {
 timeElapsed = updateInterval;
 }
 rentalTime -= timeElapsed;
 
 saveData();
 
 updateTimeDisp(); 
 
 //Process Reminders
 if (rentalTime <= 0)
 {
 llInstantMessage(llGetOwner(), "LEASE EXPIRED: leased by " + renterName + " located in " + llGetRegionName() + " (" + (string)initPos.x + "," + (string)initPos.y + "," + (string)initPos.z + ") has expired.");
 state idle;
 }
 if ((rentalTime <= ONE_DAY)&&(rentalTime >= ONE_DAY - (updateInterval*2)))
 {
 sendReminder("in one day.");
 } 
 else if ((rentalTime <= ONE_HOUR*12)&&(rentalTime >= ONE_HOUR*12 - (updateInterval*2)))
 {
 sendReminder("in 12 hours.");
 } 
 else if ((rentalTime <= ONE_HOUR)&&(rentalTime >= ONE_HOUR - (updateInterval*2)))
 {
 sendReminder("in one hour.");
 } 
 }
}
Here is the associated settings notecard (not a script):
#Rent This Space
#Options Notecard
#All lines beginning with "#" are comments, and are ignored
#DO NOT CHANGE the order of the options!!!
#Spot Name
This Place for Rent!
#Rental Cost
#The weekly rate to rent this space
50
#Prim Count
#The number of prims allowed by the renter in this space
20
#Rental Size
#The volume of the rental space, in square meters
100
#Refund Fee
#The fee charged to obtain a refund on the currently paid rent
20
#Rental Offset
#The relative position of the prim when rented, when compared
#to the non-rented position
#e.g. 6.75m above the rented spot: <0,0,6.75>
#e.g. 15m above and 5m to the West: <5,0,15>
<0,0,6.75>
#Info Notecard Name
#The name of the notecard to give to someone
#who clicks the cube. Note: case-sensitive
Rental Info