Last active
March 22, 2016 00:05
A UserScript which adds URL save/load functionality with history back/forward to the Tree of Savior Skill Simulator on tosbase.com
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name ToS Skill Simulator Extension | |
// @namespace https://gist.github.com/p2k/8bee422d9ed7705b4e1b | |
// @version 1.2 | |
// @description Adds automatic URL save/load functionality plus history support | |
// @author p2k | |
// @match http://www.tosbase.com/tools/skill-simulator/ | |
// @match http://www.tosbase.com/tools/skill-simulator/#* | |
// @grant none | |
// ==/UserScript== | |
/* jshint -W097 */ | |
'use strict'; | |
var version = 1; | |
var lastHash; | |
var build = []; | |
var re_selectClass = /selectClass\((\d+), '([^']+)'\);/; | |
var re_increaseSkill = /increaseSkill\((\d+), '([^']+)', 1\);/; | |
function encodeNibbles (n) { | |
var s = ""; | |
for (var i = 0; i < n.length; i += 2) { | |
if (i+1 < n.length) | |
s += String.fromCharCode((n[i] << 4) | n[i+1]); | |
else | |
s += String.fromCharCode(n[i] << 4); | |
} | |
return s; | |
} | |
function decodeNibbles (s) { | |
var n = []; | |
for (var i = 0; i < s.length; i++) { | |
n.push((s.charCodeAt(i) & 0xf0) >> 4); | |
n.push(s.charCodeAt(i) & 0x0f); | |
} | |
return n; | |
} | |
function updateHash () { | |
var i, j, e, b, p, h = [version]; | |
for (i = 0; i < build.length; i++) { | |
e = build[i]; | |
h.push(e.cls+1); | |
b = 0; | |
for (j = 0; j < 12; j++) { | |
if (e.sk[j] > 0) | |
b |= 1 << (11-j); | |
} | |
h.push((b & 0x0f00) >> 8); | |
h.push((b & 0xf0) >> 4); | |
h.push(b & 0x0f); | |
for (j = 0; j < 12; j++) { | |
if (e.sk[j] > 0) | |
h.push(e.sk[j]); | |
} | |
} | |
if (h.length == 1) | |
h.pop(); | |
lastHash = "#" + btoa(encodeNibbles(h)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); | |
location.hash = lastHash; | |
} | |
function loadHash() { | |
if ((!location.hash && !lastHash) || location.hash == lastHash) | |
return; | |
var i = 1, c, b, e, f, j, newBuild = [], elt, | |
h = location.hash; | |
lastHash = h; | |
if (h && h.length > 1) { | |
h = decodeNibbles(atob(h.substr(1).replace(/_/g, "/").replace(/-/g, "+"))); | |
if (h[0] > version) | |
return; | |
try { | |
while (i < h.length) { | |
c = h[i++] - 1; | |
if (c < 0) | |
break; | |
e = {cls: c, sk: [0,0,0,0,0,0,0,0,0,0,0,0]}; | |
newBuild.push(e); | |
b = (h[i++] << 8) | (h[i++] << 4) | h[i++]; | |
for (j = 0; j < 12; j++) { | |
if (b & (1 << (11-j))) | |
e.sk[j] = h[i++]; | |
} | |
} | |
} | |
catch (e) {} | |
} | |
for (i = 0; i < newBuild.length; i++) { | |
e = newBuild[i]; | |
f = (i < build.length ? build[i] : undefined); | |
if (f && f.cls != e.cls) { | |
elt = $("#skill_sim_rank" + (i+1) + "_classes .skill_sim_class_button").eq(f.cls); | |
selectClass(i+1, elt.data("class-name")); | |
build.splice(i, build.length - i); | |
f = undefined; | |
} | |
if (!f) { | |
elt = $("#skill_sim_rank" + (i+1) + "_classes .skill_sim_class_button").eq(e.cls); | |
selectClass(i+1, elt.data("class-name")); | |
upgradeButtons(); | |
} | |
if (f) | |
build[i] = e; | |
else | |
build.push(e); | |
} | |
for (i = 0; i < build.length; i++) { | |
e = build[i]; | |
for (j = 0; j < 12; j++) { | |
elt = $("#skill_sim_rank" + (i+1) + "_skills div.skill_sim_tooltip_trigger").eq(j); | |
if (elt.length == 0) | |
break; | |
b = e.sk[j] - $("#skill_sim_skill_value_" + (i+1) + "_" + elt.data("skill-name")).data("level"); | |
if (b > 0) | |
increaseSkill(i+1, elt.data("skill-name"), b); | |
else if (b < 0) | |
decreaseSkill(i+1, elt.data("skill-name"), -b); | |
} | |
} | |
if (i < build.length) { | |
elt = $("#skill_sim_rank" + (i+1) + "_classes .skill_sim_class_button").eq(build[i].cls); | |
selectClass(i+1, elt.data("class-name")); | |
build.splice(i, build.length - i); | |
} | |
} | |
function updateUpperSkillLevel (rank, className, skillName) { | |
while (++rank < 8) { | |
var rankClassName = $("#skill_sim_rank" + rank + "_classes .skill_sim_class_button.active").data("class-name"); | |
if (rankClassName == className) { | |
var elt = $("#skill_sim_skill_value_" + rank + "_" + skillName); | |
if (elt.length == 1) { | |
var skillIndex = $("#skill_sim_rank" + rank + "_skills .skill_sim_skill_value").index(elt); | |
if (skillIndex != -1) | |
build[rank-1].sk[skillIndex] = elt.data("level"); | |
} | |
} | |
} | |
} | |
function upgradeButtons () { | |
$(".skill_sim_class_button[onclick]").each(function (i) { | |
var elt = $(this); | |
var ev = elt.attr("onclick"); | |
ev = re_selectClass.exec(ev); | |
var rank = parseInt(ev[1]); | |
elt.attr("data-class-name", ev[2]); | |
elt.removeAttr("onclick"); | |
elt.click(function () { | |
if (rank <= build.length) | |
build.splice(rank-1, build.length-rank+1); | |
else | |
build.push({cls: i, sk: [0,0,0,0,0,0,0,0,0,0,0,0]}); | |
selectClass(rank, elt.data("class-name")); | |
upgradeButtons(); | |
updateHash(); | |
}); | |
}); | |
$("div.skill_sim_tooltip_trigger[onclick]").each(function (i) { | |
var elt = $(this); | |
var ev = elt.attr("onclick"); | |
ev = re_increaseSkill.exec(ev); | |
var rank = parseInt(ev[1]), | |
skillName = ev[2], | |
className = elt.parents("td > div").find(".skill_sim_class_button.active").data("class-name"); | |
elt.attr("data-skill-name", skillName); | |
elt.removeAttr("onclick"); | |
elt.removeAttr("oncontextmenu"); | |
elt.click(function () { | |
increaseSkill(rank, skillName, 1); | |
build[rank-1].sk[i] = $("#skill_sim_skill_value_" + rank + "_" + skillName).data("level"); | |
updateUpperSkillLevel(rank, className, skillName); | |
updateHash(); | |
}); | |
elt.contextmenu(function () { | |
decreaseSkill(rank, skillName, 1); | |
build[rank-1].sk[i] = $("#skill_sim_skill_value_" + rank + "_" + skillName).data("level"); | |
updateUpperSkillLevel(rank, className, skillName); | |
updateHash(); | |
return false; | |
}); | |
}); | |
} | |
$(window).ready(function () { | |
upgradeButtons(); | |
loadHash(); | |
window.addEventListener("hashchange", loadHash); | |
var elt = $("#main #content h1"); | |
elt.text(elt.text() + "+"); | |
$("#main #content h2").text("Share as public build"); | |
$("#main #content button[name=save]").parent().parent().prepend("Notice: This will upload your build to the website;<br/>copy the URL from your address bar to share privately.<br/><br/>"); | |
$("#main #content button[name=save]").text("Share"); | |
}); |
Version 1.2 fixes issues with downwards skill point transfers.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Requires Greasemonkey (Firefox) or Tampermonkey (Chrome, Safari) to run. Click on "Raw" to install. If you see "Skill Simulator+" as title on the Skill Simulator site, the script is working.