// ==UserScript==
// @name BvS Vacation Reminder
// @namespace TheSpy
// @description Alerts you when your vacation is ready.
// @include http*://*animecubed.com/billy/bvs/pages/main.html
// @include http*://*animecubed.com/billy/bvs/village.html
// @include http*://*animecubed.com/billy/bvs/villagebeach.html
// @include http*://*animecubedgaming.com/billy/bvs/pages/main.html
// @include http*://*animecubedgaming.com/billy/bvs/village.html
// @include http*://*animecubedgaming.com/billy/bvs/villagebeach.html
// @version 1.12
// @history 1.12 New domain - animecubedgaming.com - Channel28
// @history 1.11 Now https compatible (Updated by Channel28)
// @history 1.10 Added grant permissions (Updated by Channel28)
// @history 1.09 Fixed a DOM issue (temporary fix)
// @history 1.08 The floating bar now only shows on the following pages: main, village, beach
// @history 1.07 Fixed wrong date times for last vacation
// @history 1.06 Date and time are in seperate rows, added vacation stamina, added new year compatibility
// @history 1.05 Added multi character support (forgot it earlier)
// @history 1.04 Updated with game update, added floating bar
// @history 1.03 Fixed an 'Apocalypse Calendar' bug
// @history 1.02 Added 'Apocalypse Calendar' support, supports new year as well
// @history 1.01 Initial release
// @licence MIT; http://www.opensource.org/licenses/mit-license.php
// @copyright 2010-2011, TheSpy
// @grant GM_log
// @grant GM_addStyle
// ==/UserScript==
const MINUTE = 60 * 1000; //ms
const HOUR = 60 * MINUTE; //ms
const DAY = 24 * HOUR; //ms
/*
DOM Storage wrapper class (credits: http://userscripts.org/users/dtkarlsson)
Constructor:
var store = new DOMStorage({"session"|"local"}, [<namespace>]);
Set item:
store.setItem(<key>, <value>);
Get item:
store.getItem(<key>[, <default value>]);
Remove item:
store.removeItem(<key>);
Get all keys in namespace as array:
var array = store.keys();
*/
function DOMStorage(type, namespace)
{
var my = this;
if (typeof(type) != "string")
type = "session";
switch (type) {
case "local": my.storage = localStorage; break;
case "session": my.storage = sessionStorage; break;
default: my.storage = sessionStorage;
}
if (!namespace || typeof(namespace) != "string")
namespace = "Greasemonkey";
my.ns = namespace + ".";
my.setItem = function(key, val) {
try {
my.storage.setItem(escape(my.ns + key), val);
}
catch (e) {
GM_log(e);
}
},
my.getItem = function(key, def) {
try {
var val = my.storage.getItem(escape(my.ns + key));
if (val)
return val;
else
return def;
}
catch (e) {
return def;
}
}
// Kludge, avoid Firefox crash
my.removeItem = function(key) {
try {
my.storage.setItem(escape(my.ns + key), null);
}
catch (e) {
GM_log(e);
}
}
// Return array of all keys in this namespace
my.keys = function() {
var arr = [];
var i = 0;
do {
try {
var key = unescape(my.storage.key(i));
if (key.indexOf(my.ns) == 0 && my.storage.getItem(key))
arr.push(key.slice(my.ns.length));
}
catch (e) {
break;
}
i++;
} while (true);
return arr;
}
}
// UI (credits: http://userscripts.org/users/dtkarlsson)
function Window(id, storage)
{
var my = this;
my.id = id;
my.offsetX = 0;
my.offsetY = 0;
my.moving = false;
// Window dragging events
my.drag = function(event) {
if (my.moving) {
my.element.style.left = (event.clientX - my.offsetX)+'px';
my.element.style.top = (event.clientY - my.offsetY)+'px';
event.preventDefault();
}
}
my.stopDrag = function(event) {
if (my.moving) {
my.moving = false;
var x = parseInt(my.element.style.left);
var y = parseInt(my.element.style.top);
if(x < 0) x = 0;
if(y < 0) y = 0;
storage.setItem(my.id + ".coord.x", x);
storage.setItem(my.id + ".coord.y", y);
my.element.style.opacity = 1;
window.removeEventListener('mouseup', my.stopDrag, true);
window.removeEventListener('mousemove', my.drag, true);
}
}
my.startDrag = function(event) {
if (event.button != 0) {
my.moving = false;
return;
}
my.offsetX = event.clientX - parseInt(my.element.style.left);
my.offsetY = event.clientY - parseInt(my.element.style.top);
my.moving = true;
my.element.style.opacity = 0.75;
event.preventDefault();
window.addEventListener('mouseup', my.stopDrag, true);
window.addEventListener('mousemove', my.drag, true);
}
my.element = document.createElement("div");
my.element.id = id;
document.body.appendChild(my.element);
my.element.addEventListener('mousedown', my.startDrag, true);
if (storage.getItem(my.id + ".coord.x"))
my.element.style.left = storage.getItem(my.id + ".coord.x") + "px";
else
my.element.style.left = "6px";
if (storage.getItem(my.id + ".coord.y"))
my.element.style.top = storage.getItem(my.id + ".coord.y") + "px";
else
my.element.style.top = "6px";
}
// Parse server time clock
// (credits: http://userscripts.org/users/dtkarlsson)
function parseServerTime()
{
var clock = document.getElementById("clock");
if (clock)
delayedParseServerTime(clock);
}
// Try to parse server time clock periodically. The clock is updated by a timer script
// so it is not available immediately on page load
// (credits: http://userscripts.org/users/dtkarlsson)
function delayedParseServerTime(element)
{
var match = element.textContent.match(/0?(\d+):0?(\d+):0?(\d+) (.M)/);
if (match) {
var hours = parseInt(match[1]);
var minutes = parseInt(match[2]);
var seconds = parseInt(match[3]);
hours = hours % 12;
if (match[4] == "PM")
hours += 12;
var server = new Date();
server.setHours(hours);
server.setMinutes(minutes);
server.setSeconds(seconds);
server.setMilliseconds(0);
// Make sure offset is < 0 and > -12h
var offset = server.getTime() - utcNow();
if (offset > 0)
offset -= DAY;
if (offset < -DAY / 2)
offset += DAY;
var oldOffset = getOffset();
if (Math.abs(oldOffset - offset) < 10000)
offset = Math.round((offset + oldOffset) / 2);
vacationSettings.setItem("offset", offset);
vacationSettings.setItem("sync", utcNow());
} else {
// Try again in 0.25s
setTimeout(function() {delayedParseServerTime(element);}, 250);
}
}
// Parse vacation time
function parseVacationTime()
{
var match = document.evaluate("//b/font[contains(translate(@color, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '0000a1')]/text()", document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.nodeValue.match(/ (\d+)\/(\d+) \(.+ (\d+):(\d+)\)/);
if (match) {
var month = parseInt(match[1]) - 1;
var day = parseInt(match[2]);
var hour = parseInt(match[3]);
var minute = parseInt(match[4]);
var date = new Date();
date.setTime(serverNow());
var vacationDate = new Date(date.getFullYear(), month, day, hour, minute);
var vacationTime = vacationDate.getTime();
// new year issues, substract one year
if(serverNow() < vacationTime) {
vacationDate = new Date(date.getFullYear() - 1, month, day, hour, minute);
vacationTime = vacationDate.getTime();
}
vacationSettings.setItem(playerName() + ".vacation", vacationTime);
var calendar = document.evaluate("//b[contains(.,'Apocalypse Calendar')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
vacationSettings.setItem(playerName() + ".calendar", calendar.snapshotLength > 0 ? 1 : 0);
var island = document.evaluate("//b[contains(.,'Neo-Monster Island')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
vacationSettings.setItem(playerName() + ".staminabonus", island.snapshotLength > 0 ? 1 : 0);
}
else {
}
}
// Helper function for getting clock offset from localStorage
function getOffset()
{
var offset;
try {
offset = vacationSettings.getItem("offset");
return parseInt(offset);
}
catch (e) {
GM_log(e);
return;
}
}
// Current time in ms since 1970-01-01 UTC
function utcNow()
{
var d = new Date();
return d.getTime() + d.getTimezoneOffset() * 60000;
}
// Current server time in ms
function serverNow()
{
return utcNow() + parseInt(vacationSettings.getItem("offset"));
}
// ...
function vacationTime() {
return parseInt(vacationSettings.getItem(playerName() + ".vacation"));
}
// ...
function vacationStaminaBonus() {
return parseInt(vacationSettings.getItem(playerName() + ".staminabonus"));
}
// ...
function calendarUpgrade() {
return parseInt(vacationSettings.getItem(playerName() + ".calendar"));
}
// ...
function vacationStaminaBonusString() {
var staminabonus;
var days = (serverNow() - vacationTime()) / (1000 * 60 * 60 * 24);
if(calendarUpgrade() == 1) {
days *= 1.5;
}
if(days >= 14) {
staminabonus = 111;
}
else {
staminabonus = 5 * Math.floor(days);
}
if(vacationStaminaBonus() == 1) {
staminabonus *= 1.5;
}
return Math.floor(staminabonus);
}
// ...
function vacationString() {
if(vacationTime() <= 0) {
return "Unknown";
}
if(calendarUpgrade() == 1) {
time = 14 * 24 * 60 * 2 / 3;
}
else {
time = 14 * 24 * 60;
}
var dif = time - Math.floor((serverNow() - vacationTime()) / (1000 * 60));
if(dif > 0) {
var m = dif % 60;
dif = (dif - m) / 60;
var h = dif % 24;
dif = (dif - h) / 24;
var d = dif;
return d + "d " + h + "h " + m + "m";
}
else {
return "Ready";
}
}
// ...
function twoDigits(n) {
if (n < 10)
return "0" + n;
else
return "" + n;
}
function timeString(time) {
time = parseInt(time);
if(isNaN(time)) {
return "Unknown";
}
var date = new Date();
date.setTime(time);
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var hour = date.getHours();
var minute = date.getMinutes();
var ampm = (hour >= 12 ? "PM" : "AM");
hour %= 12;
if(hour == 0) {
hour = 12;
}
return year + "/" + twoDigits(month) + "/" + twoDigits(day) + "<br/>" + twoDigits(hour) + ":" + twoDigits(minute) + " " + ampm;
}
// ...
function playerName() {
var player;
try {
player = document.evaluate("//input[@name='player' and @type='hidden']", document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.value;
}
catch(e) {
player = "none";
}
return player;
}
// ...
function FloatingVacation() {
var my = this;
my.window = new Window("floatingvacation", vacationSettings);
// Set up floating vacation layer
GM_addStyle("#floatingvacation {border: 2px solid #111E01; position: fixed; z-index: 100; color: #FFFFFF; background-color: #434411; padding: 4px; text-align: center; cursor: move;}");
GM_addStyle("#floatingvacation dl {margin: 0; padding: 0;}");
GM_addStyle("#floatingvacation dt {margin: 0; padding: 0; font-size: 12px;}");
GM_addStyle("#floatingvacation dd {margin: 0; padding: 0; font-size: 24px;}");
my.draw = function() {
if(!isNaN(vacationTime())) {
my.window.element.innerHTML = "<dl><dt>Last known vacation</dt><dd id='vacationlast'>Unknown</dd><dt>Vacation status</dt><dd id='vacationleft'>Unknown</dd><dt>Vacation stamina</dt><dd id='vacationstamina'>Unknown</dd></dl>";
}
else {
my.window.element.innerHTML = "<dl><dt>Last known vacation</dt><dd id='vacationlast'>Unknown</dd></dl>";
}
}
my.update = function() {
var nodevacationlast = document.getElementById("vacationlast");
if (nodevacationlast) {
nodevacationlast.innerHTML = timeString(vacationTime());
}
var vacationleft = document.getElementById("vacationleft");
if (vacationleft) {
vacationleft.innerHTML = vacationString();
}
var vacationstamina = document.getElementById("vacationstamina");
if (vacationstamina) {
vacationstamina.innerHTML = vacationStaminaBonusString();
}
setTimeout(my.update, 1000);
}
my.draw();
my.update();
}
if(playerName() != "none") {
var vacationSettings = new DOMStorage("local", "BvSVacation");
var floatingVacation = new FloatingVacation();
}
if (/billy.bvs.pages.main\b/.test(location.href) || /billy.bvs.arena/.test(location.href)) {
parseServerTime();
}
else if (/billy.bvs.villagebeach/.test(location.href)) {
parseVacationTime();
}