Skip to content

Instantly share code, notes, and snippets.

@p2k
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
// ==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");
});
@p2k
Copy link
Author

p2k commented Mar 21, 2016

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.

@p2k
Copy link
Author

p2k commented Mar 21, 2016

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