// ==UserScript==
// @name Fair Game QoL v1
// @namespace https://fair.kaliburg.de/#
// @version 0.500
// @description Fair Game QOL Enhancements
// @author Aqualxx
// @match https://fair.kaliburg.de/
// @include *kaliburg.de*
// @run-at document-end
// @icon https://www.google.com/s2/favicons?domain=kaliburg.de
// @grant unsafeWindow
// @license MIT
// ==/UserScript==
// Script made by aqualxx#5004 and maintained by Lynn#6969
// Features include:
// Larger ladder size
// Indicators
// Keybinds
// 🍇🍇🍇
// Time estimates
//////////////////////
// Options //
//////////////////////
if (typeof unsafeWindow !== 'undefined') {
window = unsafeWindow;
}
window.qolOptions = {
expandedLadder: {
enabled: false,
size: 30
},
scrollableLadder: false,
keybinds: false,
scrollablePage: false,
promotePoints: true,
multiLeader: {
"default": "Both", // <--- Change this value to save between refreshs
"Disabled": false,
"Both": "[NUMBER xSTATUS]",
"Square": "[xSTATUS]",
"Number": "[NUMBER]",
},
}
//////////////////////////////////////
// DO NOT EDIT BEYOND HERE //
//////////////////////////////////////
window.subscribeToDomNode = function(id, callback) {
let input = $("#"+id)[0];
if (input) {
input.addEventListener("change", callback);
} else {
console.log(`Id ${id} was not found subscribing to change events`);
}
}
document.addEventListener("keyup", event => {
if (!qolOptions.keybinds) return;
if (!event.target.isEqualNode($("body")[0])) return;
if (event.key === "b") {
event.preventDefault()
//buyBias(event);
}
if (event.key === "m") {
event.preventDefault();
//buyMulti(event);
}
});
if (qolOptions.expandedLadder.enabled) {
$('#infoText').parent().parent().removeClass('col-7').addClass('col-12');
$('#infoText').parent().parent().next().hide();
}
clientData.ladderAreaSize = 1;
$('body').css("line-height", 1);
clientData.ladderPadding = qolOptions.expandedLadder.size / 2;
function addTableColumns() {
$("#ladderBody").parent().find("thead").html(`
<tr class="thead-light">
<th>#</th>
<th>Stats</th>
<th>Username</th>
<th class="text-end">Power</th>
<th class="text-end">ETA to #1</th>
<th class="text-end">ETA to You</th>
<th class="text-end">Points</th>
</tr>
`);
}
addTableColumns();
numberFormatter = new numberformat.Formatter({
format: 'hybrid',
sigfigs: 6,
flavor: 'short',
minSuffix: 1e10,
maxSmall: 0
});
window.secondsToHms = function(d) {
d = Number(d);
if (d === 0) return "0s";
var h = Math.floor(d / 3600);
var m = Math.floor(d % 3600 / 60);
let s = d % 3600 % 60;
if (h == 0 && m == 0 && s !== 0 && Math.abs(s) < 1) {
return "<1s";
} else {
s = Math.floor(s);
}
var hDisplay = h > 0 ? h + "h " : "";
var mDisplay = m > 0 ? m + "m " : "";
var sDisplay = s > 0 ? s + "s " : "";
return hDisplay + mDisplay + sDisplay;
}
// returns positive minimal positive solution or "Inf"
window.solveQuadratic = function(a, b, c) {
if (a == 0) {
return -c / b > 0 ? (-c / b).toFixed(2) : "Inf";
} else {
let discriminant = b * b - 4 * a * c;
if (discriminant < 0) return "Inf";
const root1 = (-b + Math.sqrt(discriminant)) / (2 * a);
const root2 = (-b - Math.sqrt(discriminant)) / (2 * a);
if (root1 > 0 && root2 > 0) {
return Math.min(root1, root2).toFixed(2);
} else {
let maxRoot = Math.max(root1, root2).toFixed(2);
if (maxRoot < 0) return "Inf";
else return maxRoot;
}
}
}
window.getAcc = function(ranker) {
if (ranker.rank === 1 || !ranker.growing) return 0;
return (ranker.bias + ranker.rank - 1) * ranker.multiplier;
}
window.writeNewRow = function(body, ranker) {
let row = body.insertRow();
const myAcc = getAcc(ladderData.yourRanker);
const theirAcc = getAcc(ranker);
const a = (theirAcc - myAcc) * 0.5;
const b = (ranker.growing ? ranker.power : 0) - ladderData.yourRanker.power;
const c = ranker.points - ladderData.yourRanker.points;
let timeLeft = solveQuadratic(a, b, c);
timeLeft = secondsToHms(timeLeft);
if (timeLeft == '') {
timeLeft = "Never";
}
const pointsToFirst = ladderData.firstRanker.points.sub(ranker.points);
const firstPowerDifference = ranker.power - (ladderData.firstRanker.growing ? ladderData.firstRanker.power : 0);
const pointsLeftPromote = infoData.pointsForPromote.mul(ladderData.currentLadder.number) - ranker.points;
let timeToFirst = "";
if (ladderData.firstRanker.points.lessThan(infoData.pointsForPromote.mul(ladderData.currentLadder.number))) {
// Time to reach minimum promotion points of the ladder
timeToFirst = 'L' + secondsToHms(solveQuadratic(theirAcc/2, ranker.power, -pointsLeftPromote));
} else {
// time to reach first ranker
timeToFirst = secondsToHms(solveQuadratic(theirAcc/2, firstPowerDifference, -pointsToFirst));
}
if (!ranker.growing || (ranker.rank === 1 && ladderData.firstRanker.points.greaterThan(infoData.pointsForPromote.mul(ladderData.currentLadder.number)))) timeToFirst = "";
if (ladderData.yourRanker.rank == ranker.rank) {
timeLeft = "";
}
let assholeTag = (ranker.timesAsshole < infoData.assholeTags.length) ?
infoData.assholeTags[ranker.timesAsshole] : infoData.assholeTags[infoData.assholeTags.length - 1];
let rank = (ranker.rank === 1 && !ranker.you && ranker.growing && ladderData.rankers.length >= Math.max(infoData.minimumPeopleForPromote, ladderData.currentLadder.number) &&
ladderData.firstRanker.points.cmp(infoData.pointsForPromote.mul(ladderData.currentLadder.number)) >= 0 && ladderData.yourRanker.vinegar.cmp(getVinegarThrowCost()) >= 0) ?
'<a href="#" style="text-decoration: none" onclick="throwVinegar(event)">🍇</a>' : ranker.rank;
let multiPrice = ""
if ((ranker.rank === 1 && ranker.growing) && qolOptions.multiLeader[$("#leadermultimode")[0].value]) {
multiPrice = qolOptions.multiLeader[$("#leadermultimode")[0].value]
.replace("NUMBER",`${numberFormatter.format(Math.pow(ladderData.currentLadder.number+1, ranker.multiplier+1))}`)
.replace("STATUS", `${(ranker.power >= Math.pow(ladderData.currentLadder.number+1, ranker.multiplier+1)) ? "🟩" : "🟥"}`)
}
row.insertCell(0).innerHTML = rank + " " + assholeTag;
row.insertCell(1).innerHTML = `[+${ranker.bias.toString().padStart(2,"0")} x${ranker.multiplier.toString().padStart(2,"0")}]`;
row.insertCell(2).innerHTML = `${ranker.username}`;
row.cells[2].style.overflow = "hidden";
row.insertCell(3).innerHTML = `${multiPrice} ${numberFormatter.format(ranker.power)} ${ranker.growing ? ranker.rank != 1 ? "(+" + numberFormatter.format((ranker.rank - 1 + ranker.bias) * ranker.multiplier) + ")" : "" : "(Promoted)"}`;
row.cells[3].classList.add('text-end');
row.insertCell(4).innerHTML = timeToFirst;
row.cells[4].classList.add('text-end');
row.insertCell(5).innerHTML = timeLeft;
row.cells[5].classList.add('text-end');
row.insertCell(6).innerHTML = `${numberFormatter.format(ranker.points)}`;
row.cells[6].classList.add('text-end');
if (ranker.you) {
row.classList.add('table-active');
} else if (!ranker.growing) {
row.style['background-color'] = "#C0C0C0";
} else if ((ranker.rank < ladderData.yourRanker.rank && timeLeft != 'Never') || (ranker.rank > ladderData.yourRanker.rank && timeLeft == 'Never')) {
row.style['background-color'] = "#A0EEA0";
} else if ((ranker.rank < ladderData.yourRanker.rank && timeLeft == 'Never') || (ranker.rank > ladderData.yourRanker.rank && timeLeft != 'Never')) {
row.style['background-color'] = "#EEA0A0";
}
}
window.updateLadder = function() {
let size = ladderData.rankers.length;
let rank = ladderData.yourRanker.rank;
let ladderArea = Math.floor(rank / clientData.ladderAreaSize);
let startRank = (ladderArea * clientData.ladderAreaSize) - clientData.ladderPadding;
let endRank = startRank + clientData.ladderAreaSize - 1 + (2 * clientData.ladderPadding);
let body = document.getElementById("ladderBody");
body.innerHTML = "";
if (startRank > 1) writeNewRow(body, ladderData.firstRanker);
for (let i = 0; i < ladderData.rankers.length; i++) {
let ranker = ladderData.rankers[i];
if ((ranker.rank >= startRank && ranker.rank <= endRank)) writeNewRow(body, ranker);
}
let tag1 = '<span>', tag2 = '</span>';
if (ladderData.yourRanker.vinegar.cmp(getVinegarThrowCost()) >= 0) {
tag1 = '<span style="color: plum">'
tag2 = '</span>'
}
let grapesTimeLeft = secondsToHms((getVinegarThrowCost() - ladderData.yourRanker.vinegar) / ladderData.yourRanker.grapes);
const fillsPerHour = (new Decimal(3600)).div(getVinegarThrowCost().div(ladderData.yourRanker.grapes));
if (grapesTimeLeft == '') {
if (ladderData.yourRanker.grapes > 0) {
grapesTimeLeft = "0s";
} else {
grapesTimeLeft = "infinite time";
}
}
$('#infoText').html(`<p>Grapes: ${numberFormatter.format(ladderData.yourRanker.grapes)}<\p>`+
`<p>${tag1} Vinegar: ${numberFormatter.format(ladderData.yourRanker.vinegar)}/${numberFormatter.format(getVinegarThrowCost())} (+${numberFormatter.format(ladderData.yourRanker.grapes)} per/s) ${tag2}<\p>`+
`<p>There is ${grapesTimeLeft} left until you can throw vinegar at #1, fills per hour: ` + fillsPerHour.toFixed(2) + "<\p><p><br><br><br><\p>");
$('#usernameLink').html(ladderData.yourRanker.username);
$('#usernameText').html("+" + ladderData.yourRanker.bias + " x" + ladderData.yourRanker.multiplier);
$('#rankerCount').html("Rankers: " + ladderStats.growingRankerCount + "/" + ladderData.rankers.length);
$('#ladderNumber').html("Ladder # " + ladderData.currentLadder.number);
if (qolOptions.promotePoints) {
$('#manualPromoteText').show()
$('#manualPromoteText').html("Points needed for "
+ ((ladderData.currentLadder.number === infoData.assholeLadder) ? "being an asshole" : "manually promoting")
+ ": " + numberFormatter.format(ladderStats.pointsNeededForManualPromote));
} else {
$('#manualPromoteText').hide()
}
let offCanvasBody = $('#offCanvasBody');
offCanvasBody.empty();
for (let i = 1; i <= ladderData.currentLadder.number; i++) {
let ladder = $(document.createElement('li')).prop({
class: "nav-link"
});
let ladderLinK = $(document.createElement('a')).prop({
href: '#',
innerHTML: 'Chad #' + i,
class: "nav-link h5"
});
ladderLinK.click(async function () {
changeChatRoom(i);
})
ladder.append(ladderLinK);
offCanvasBody.prepend(ladder);
}
showButtons();
}
window.showButtons = function() {
let biasButton = $('#biasButton');
let multiButton = $('#multiButton');
let biasCost = getUpgradeCost(ladderData.yourRanker.bias + 1);
if (ladderData.yourRanker.points.cmp(biasCost) >= 0) {
biasButton.prop("disabled", false);
} else {
biasButton.prop("disabled", true);
}
let multiCost = getUpgradeCost(ladderData.yourRanker.multiplier + 1);
if (ladderData.yourRanker.power.cmp(new Decimal(multiCost)) >= 0) {
multiButton.prop("disabled", false);
} else {
multiButton.prop("disabled", true);
}
const myAcc = getAcc(ladderData.yourRanker);
let nextMultiTime = ladderData.yourRanker.power.lessThan(multiCost) ? (multiCost-ladderData.yourRanker.power)/myAcc : 0;
// Payback is the time it takes for the difference between new and old growth functions to gain current points.
// Rank changes are not taken into account so this estimate is conservative.
// For multi payback you will need to solve accel_diff / 2 * t^2 - power * t - points = 0
let nextMultiPayback = 0;
if (nextMultiTime > 0) {
// If you don't have the required power calculate cost with future values
const targetPoints = ladderData.yourRanker.points.add(ladderData.yourRanker.power.times(nextMultiTime)).add(myAcc * myAcc * nextMultiTime / 2);
nextMultiPayback = solveQuadratic((ladderData.yourRanker.rank - 1 + ladderData.yourRanker.bias)/2, -multiCost, -targetPoints);
} else {
nextMultiPayback = solveQuadratic((ladderData.yourRanker.rank - 1 + ladderData.yourRanker.bias)/2, -ladderData.yourRanker.power, -ladderData.yourRanker.points);
}
nextMultiTime = secondsToHms(nextMultiTime);
nextMultiPayback = secondsToHms(nextMultiPayback);
let nextBiasTime = ladderData.yourRanker.points.lessThan(biasCost) ? solveQuadratic(myAcc/2, ladderData.yourRanker.power, -biasCost) : 0;
// For bias payback you will need to solve accel_diff / 2 * t^2 - points = 0
let nextBiasPayback = 0;
if (nextBiasTime > 0) {
// If you don't have the required points calculate cost with future value
nextBiasPayback = solveQuadratic(ladderData.yourRanker.multiplier/2, 0, -biasCost);
} else {
nextBiasPayback = solveQuadratic(ladderData.yourRanker.multiplier/2, 0, -ladderData.yourRanker.points);
}
nextBiasTime = secondsToHms(nextBiasTime);
nextBiasPayback = secondsToHms(nextBiasPayback);
$('#biasTooltip').attr('data-bs-original-title', `${nextBiasTime}/${nextBiasPayback} ` + numberFormatter.format(biasCost) + ' Points');
$('#multiTooltip').attr('data-bs-original-title', `${nextMultiTime}/${nextMultiPayback} ` + numberFormatter.format(multiCost) + ' Power');
let promoteButton = $('#promoteButton');
let assholeButton = $('#assholeButton');
let ladderNumber = $('#ladderNumber');
if (ladderData.firstRanker.you && ladderData.firstRanker.points.cmp(ladderStats.pointsNeededForManualPromote) >= 0) {
if (ladderData.currentLadder.number === infoData.assholeLadder) {
promoteButton.hide()
ladderNumber.hide()
assholeButton.show()
} else {
assholeButton.hide()
ladderNumber.hide()
promoteButton.show()
}
} else {
assholeButton.hide()
promoteButton.hide()
ladderNumber.show()
}
// Auto-Promote Button
let autoPromoteButton = $('#autoPromoteButton');
let autoPromoteTooltip = $('#autoPromoteTooltip');
let autoPromoteCost = getAutoPromoteGrapeCost(ladderData.yourRanker.rank);
if (!ladderData.yourRanker.autoPromote && ladderData.currentLadder.number >= infoData.autoPromoteLadder
&& ladderData.currentLadder.number !== infoData.assholeLadder) {
autoPromoteButton.show();
if (ladderData.yourRanker.grapes.cmp(autoPromoteCost) >= 0) {
autoPromoteButton.prop("disabled", false);
} else {
autoPromoteButton.prop("disabled", true);
}
autoPromoteTooltip.attr('data-bs-original-title', numberFormatter.format(autoPromoteCost) + ' Grapes');
} else {
autoPromoteButton.hide();
}
}
window.setLadderRows = function() {
var input = Number($("#rowsInput")[0].value);
if (isNaN(input)) {
$("#rowsInput")[0].value = '';
return;
}
if (input < 10) {
$("#rowsInput")[0].value = '10';
return;
}
qolOptions.expandedLadder.size = input;
clientData.ladderPadding = qolOptions.expandedLadder.size / 2;
}
window.expandLadder = function(enabled) {
var ladder = document.querySelector(".ladder-container");
if (!enabled && ladder) {
ladder.outerHTML = ladder.innerHTML;
return;
}
if (document.getElementsByClassName("ladder-container").length > 0) {
return;
}
if(!enabled) {
return;
}
var ladder = document.querySelector(".caption-top");
var ladderParent = ladder.parentElement;
var ladderContainer = document.createElement("div");
ladderContainer.className = "ladder-container";
ladderContainer.style.width = "100%";
ladderContainer.style.height = "64vh";
ladderContainer.style.overflow = "auto";
ladderContainer.style.border = "gray solid 2px";
ladderParent.replaceChild(ladderContainer, ladder);
ladderContainer.appendChild(ladder);
}
window.addOption = function(optionElement) {
$("#offcanvasOptions").children(".offcanvas-body")[0].appendChild(optionElement);
}
window.addOptionDevider = function() {
var optionElement = document.createElement("hr");
addOption(optionElement);
}
window.addNewSection = function(name) {
addOptionDevider();
var optionElement = document.createElement("h4");
optionElement.innerHTML = name;
addOption(optionElement);
}
window.baseOptionDiv = function(content = "") {
var newDiv = document.createElement("div");
newDiv.style = "display: block; padding: 0.5rem; font-size:1.25rem"
newDiv.innerHTML = content;
return newDiv;
}
window.ButtonOption = function(name, id) {
var newDiv = baseOptionDiv();
var button = document.createElement("button");
button.className = "btn btn-primary";
button.innerHTML = name;
button.id = id;
newDiv.appendChild(button);
return newDiv;
}
window.SliderOption = function(name, id, min, max, step, value) {
var newDiv = baseOptionDiv();
var slider = document.createElement("input");
slider.type = "range";
slider.min = min;
slider.max = max;
slider.step = step;
slider.value = value;
slider.style = "width: 100%";
slider.id = id;
var sliderLabel = document.createElement("label");
slider.oninput = function() {
sliderLabel.innerHTML = name + ": " + slider.value;
}
sliderLabel.innerHTML = name + ": " + slider.value;
newDiv.appendChild(sliderLabel);
newDiv.appendChild(slider);
return newDiv;
}
window.SelectOption = function(title, id, values) {
//values is an array of objects with display and value properties
return baseOptionDiv
(`<span>${title}</span>
<select name="fonts" id="${id}" class="form-select">
${values.map(function(value) {
return `<option value="${value.value}">${value.display}</option>`
}).join("")}
</select>`);
}
window.TextInputOption = function(title, id, placeholder, maxlength, onclick) {
return baseOptionDiv
(`<span>${title}</span>
<div class="input-group">
<input class="form-control shadow-none" id="${id}" maxlength="${maxlength}" placeholder="${placeholder}" type="text">
<button class="btn btn-primary shadow-none" id="rowsButton" onclick="${onclick}">Set</button>
</div>`);
}
window.CheckboxOption = function(title, optionID, defaultChecked=false) {
return baseOptionDiv(`<input type="checkbox" ${defaultChecked?"checked='checked'" : ""} id="${optionID}"><span style="padding: 10px">${title}</span>`);
}
// Holy crap this took me way too long
$(".navbar-toggler")[0].style['border-color'] = "rgba(0,0,0,0.5)";
$(".navbar-toggler")[0].style['width'] = "5%";
$(`<button aria-controls="offcanvasNavbar" class="navbar-toggler" data-bs-target="#offcanvasOptions" data-bs-toggle="offcanvas" type="button col" style="border-color: rgba(0, 0, 0, 0.5); width: 5%; height: 40px;"><span class="bi bi-gear-fill fs-3 mb-3"></span></button>`).insertBefore(".navbar-toggler");
$("#offcanvasNavbar").clone().attr("id", "offcanvasOptions").width("400px").insertAfter("#offcanvasNavbar");
$("#offcanvasOptions").children(".offcanvas-header").children("#offcanvasNavbarLabel").html("Options");
$("#offcanvasOptions").children(".offcanvas-body").children().remove();
addOption(SelectOption("Ladder Font", "ladderFonts", [
{display: "Default", value: ""},
{display: "BenchNine", value: "BenchNine"},
{display: "Roboto", value: "Roboto"},
{display: "Lato", value: "Lato"},
]))
addOption(TextInputOption("Ladder Rows", "rowsInput", "# of rows, min 10, default 30", "4", "setLadderRows()"))
addOption(CheckboxOption("Full scrollable ladder", "scrollableLadder", qolOptions.scrollableLadder))
addOption(CheckboxOption("Expand ladder size", "expandedLadder", qolOptions.expandedLadder.enabled))
//addOption(CheckboxOption("Keybinds", "keybinds", qolOptions.keybinds))
addOption(CheckboxOption("Make page scrollable", "scrollablePage", qolOptions.scrollablePage))
addOption(CheckboxOption("Show points for promotion", "promotePoints", qolOptions.promotePoints))
addOption(SelectOption("Leader Multi Requirement", "leadermultimode", [
{display: "[524288 x🟩]", value: "Both"},
{display: "[x🟩 / x🟥]", value: "Square"},
{display: "[524288]", value: "Number"},
{display: "Disabled", value: "Disabled"},
]))
if (qolOptions.scrollablePage) document.body.style.removeProperty('overflow-y');
if (qolOptions.scrollableLadder) expandLadder(true)
$("#leadermultimode")[0].value = qolOptions.multiLeader["default"]
$("#expandedLadder")[0].addEventListener("change", (event)=>{
let boxChecked = $("#expandedLadder")[0].checked;
qolOptions.expandedLadder.enabled = boxChecked;
if (boxChecked) {
$('#infoText').parent().parent().removeClass('col-7').addClass('col-12');
$('#infoText').parent().parent().next().hide();
} else {
$('#infoText').parent().parent().addClass('col-7').removeClass('col-12');
$('#infoText').parent().parent().next().show();
}
});
$("#scrollablePage")[0].addEventListener("change", (event)=>{
let boxChecked = $("#scrollablePage")[0].checked;
qolOptions.scrollablePage = boxChecked;
if (boxChecked) {
document.body.style.removeProperty('overflow-y');
} else {
document.body.style.setProperty('overflow-y', 'hidden');
}
});
$("#scrollableLadder")[0].addEventListener("change", (event)=>{
let boxChecked = $("#scrollableLadder")[0].checked;
qolOptions.scrollableLadder = boxChecked;
expandLadder(qolOptions.scrollableLadder)
})
function updateOptions(id, option) {
let input = $("#"+id)[0];
if (input) {
input.addEventListener("change", (event)=>{
let boxChecked = $("#"+id)[0].checked;
qolOptions[option] = boxChecked;
});
} else {
console.log(`Id ${id} was not found when linking options`);
}
}
updateOptions('promotePoints','promotePoints');
var linkTag = document.createElement('link');
linkTag.rel = "stylesheet";
linkTag.href = "https://fonts.googleapis.com/css2?family=BenchNine:wght@400&display=swap"
document.body.appendChild(linkTag);
document.querySelector("#ladderFonts").addEventListener('change',function(){
var input = document.querySelector("#ladderFonts").value;
switch (input) {
case "BenchNine":
$("table.table.table-sm.caption-top.table-borderless").css("font-family","'BenchNine', sans-serif");
break;
case "Roboto":
$("table.table.table-sm.caption-top.table-borderless").css("font-family","'Roboto', sans-serif");
break;
case "Lato":
$("table.table.table-sm.caption-top.table-borderless").css("font-family","'Lato', sans-serif");
break;
default:
$("table.table.table-sm.caption-top.table-borderless").css("font-family","");
break;
}
});