您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fair Game QOL Enhancements
// ==UserScript== // @name Lynns Fair Game QoL Extension // @namespace https://fair.kaliburg.de/# // @version 0.1.4 // @description Fair Game QOL Enhancements // @author Lynn // @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 and maintained by Lynn#6969! // Features include: // - Scrollable, bigger chat // - Chat mentions (with sound) // - Ladder Switch // - Chat switch // Todo Features: // - Time to next multi // - Time to next bias // - Top vinegar loss (on ladder 1) // - Follow person if (typeof unsafeWindow !== 'undefined') { window = unsafeWindow; } 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`); } } const sleep = timeout => { return new Promise(resolve => { setTimeout(resolve, timeout); }); }; (async () => { mentionSound = new Audio( 'https://assets.mixkit.co/sfx/download/mixkit-software-interface-start-2574.wav' ); //Waiting for the base script to load while (true) { await sleep(100); try { if (addOption) { break; } } catch (e) {} } console.log("[FairGame] Initializing Lynn's QOL"); //Options addNewSection("Lynn's Chad tweaks"); addOption(CheckboxOption("Invert Chad", "invertChad")); addOption(CheckboxOption("Scrollable Chad", "scrollableChad")); addOption(TextInputOption("Saved Chad Message Count", "chadMessageCount", "# of chad messages, max 9999", "4")) chadMessageCount.value = 50; addOption(CheckboxOption("Mention Sound", "mentionSounds")); addOption(CheckboxOption("Highlight Mentions", "highlightMentions", true)); addNewSection("Lynn's Ladder tweaks"); addOption(CheckboxOption("Use Lynns Ladder Code", "useLynnsLadderCode")); addNewSection("Lynn's Data tweaks"); addOption(CheckboxOption("Save Data", "saveData")); //Load options if (localStorage.getItem("lynnsQOLData") != null) { window.lynnsQOLData = JSON.parse(localStorage.getItem("lynnsQOLData")); $("#saveData").prop("checked", lynnsQOLData.saveData); $("#mentionSounds").prop("checked", lynnsQOLData.mentionSound); $("#highlightMentions").prop("checked", lynnsQOLData.mentionHighlight); $("#invertChad").prop("checked", lynnsQOLData.invertChad); $("#scrollableChad").prop("checked", lynnsQOLData.scrollableChad); $("#chadMessageCount").val(lynnsQOLData.chadMessageCount); $("#useLynnsLadderCode").prop("checked", lynnsQOLData.useLynnsLadderCode); } function saveData() { //if we want to save data if ($("#saveData").prop("checked")) { var saveData = { mentionSound: $("#mentionSounds").prop("checked"), mentionHighlight: $("#highlightMentions").prop("checked"), saveData: $("#saveData").prop("checked"), invertChad: $("#invertChad").prop("checked"), scrollableChad: $("#scrollableChad").prop("checked"), chadMessageCount: $("#chadMessageCount").val(), useLynnsLadderCode: $("#useLynnsLadderCode").prop("checked") }; localStorage.setItem("lynnsQOLData", JSON.stringify(saveData)); } } subscribeToDomNode("invertChad", saveData); subscribeToDomNode("scrollableChad", saveData); subscribeToDomNode("mentionSounds", saveData); subscribeToDomNode("highlightMentions", saveData); subscribeToDomNode("saveData", saveData); subscribeToDomNode("useLynnsLadderCode", window.updateLadder); subscribeToDomNode("chadMessageCount", saveData); subscribeToDomNode("chadMessageCount", window.updateChad); subscribeToDomNode("useLynnsLadderCode", saveData); window.expandChat = function () { if($("#scrollableChad").prop("checked")) { var chatTable = $("#messagesBody").parent() var chatParent = chatTable.parent(); var chatContainer = document.createElement("div"); chatContainer.className = "chat-container"; chatContainer.style.width = "100%"; chatContainer.style.height = "64vh"; chatContainer.style.overflow = "auto"; chatContainer.style.border = "gray solid 2px"; chatParent[0].replaceChild(chatContainer, chatTable[0]); chatContainer.appendChild(chatTable[0]); } else { if($(".chat-container")[0]) { $(".chat-container")[0].outerHTML = $(".chat-container")[0].innerHTML; } } }; window.maxLadderReached = ladderData.currentLadder.number; window.displayLadderNavigation = function () { if (ladderData.currentLadder.number > window.maxLadderReached) { window.maxLadderReached = ladderData.currentLadder.number; } const nextLadder = () => { if (ladderData.currentLadder.number + 1 <= window.maxLadderReached) { changeLadder(ladderData.currentLadder.number + 1); } } const prevLadder = () => { if (ladderData.currentLadder.number > 1) { changeLadder(ladderData.currentLadder.number - 1); } } const nextButton = document.createElement('button'); nextButton.classList.add("btn", "btn-outline-secondary"); nextButton.innerHTML = ">"; nextButton.onclick = nextLadder; const prevButton = document.createElement('button'); prevButton.classList.add("btn", "btn-outline-secondary"); prevButton.innerHTML = "<"; prevButton.onclick = prevLadder; if (ladderData.currentLadder.number <= 1) prevButton.disabled = true; if (ladderData.currentLadder.number >= maxLadderReached) nextButton.disabled = true; const ladderNum = document.createElement('span'); ladderNum.innerHTML = ` Ladder # ${ladderData.currentLadder.number} `; $('#ladderNumber').empty().append(prevButton).append(ladderNum).append(nextButton); } window.displayChatNavigation = function () { window.currentChat = ladderData.currentLadder.number; const nextChad = () => { if (window.currentChat + 1 <= window.maxLadderReached) { window.currentChat++; changeChatRoom(window.currentChat); updateChat(); document.getElementsByClassName("chat-number")[0].innerHTML = "Chad #" + window.currentChat; prevButton.disabled = window.currentChat <= 1; nextButton.disabled = window.currentChat >= maxLadderReached; } } const prevChad = () => { if (window.currentChat > 1) { window.currentChat--; changeChatRoom(window.currentChat); updateChat(); document.getElementsByClassName("chat-number")[0].innerHTML = "Chad #" + window.currentChat; prevButton.disabled = window.currentChat <= 1; nextButton.disabled = window.currentChat >= maxLadderReached; } } const nextButton = document.createElement('button'); nextButton.classList.add("btn", "btn-outline-secondary"); nextButton.innerHTML = ">"; nextButton.onclick = nextChad; const prevButton = document.createElement('button'); prevButton.classList.add("btn", "btn-outline-secondary"); prevButton.innerHTML = "<"; prevButton.onclick = prevChad; if (window.currentChat <= 1) prevButton.disabled = true; if (window.currentChat >= maxLadderReached) nextButton.disabled = true; const chatNum = document.createElement('span'); chatNum.classList.add("chat-number"); chatNum.innerHTML = ` Chad # ${ladderData.currentLadder.number} `; var container = document.createElement('div'); var container2 = document.createElement('div'); container.className = "row col-2 text-center"; container.appendChild(container2); container2.className = "h5"; container2.appendChild(prevButton); container2.appendChild(chatNum); container2.appendChild(nextButton); $('#helpLink')[0].parentElement.parentElement.insertBefore(container, $('#helpLink')[0].parentElement); rankerCount.parentElement.className = "col-1 text-start"; helpLink.parentElement.className = "col-1 text-end"; } window.handleChatUpdates = function (message) { if (message) { if (ladderData.yourRanker.username != "") { if (message.message.includes("@" + ladderData.yourRanker.username) && $("#mentionSounds").is(":checked")) { mentionSound.play(); } } chatData.messages.unshift(message); while (chatData.messages.length > chadMessageCount.value) chatData.messages.pop(); // <-- Change limit here } updateChat(); }; window.mention = function(name) { name = name.text var messageBox = document.getElementById("messageInput"); if(messageBox.value.length > 0) { messageBox.value = messageBox.value + " @" + name + " "; } else { messageBox.value = "@" + name + " "; } messageBox.focus(); } let oldUpdateChat = window.updateChat; window.updateChat = function () { //go through the chat messages and check if they contain a mention of the user if (ladderData.yourRanker.username != "") { for (var i = 0; i < chatData.messages.length; i++) { if (chatData.messages[i].message.includes("@" + ladderData.yourRanker.username) && $("#highlightMentions").is(":checked")) { //if they do, and this message was not touched yet, highlight the mention if (chatData.messages[i].highlighted == false || chatData.messages[i].highlighted == undefined) { //make a copy of the message chatData.messages[i].message1 = chatData.messages[i].message; //replace all occurences of the mention with a highlighted version chatData.messages[i].message = chatData.messages[i].message1.replaceAll("@" + ladderData.yourRanker.username, "<a style=\"color: red\">@" + ladderData.yourRanker.username + "</a>"); chatData.messages[i].highlighted = true; } } //if the message was already highlighted, but the user no longer wishes to see it highlighted, then unhighlight it else if (chatData.messages[i].highlighted == true && !$("#highlightMentions").is(":checked")) { chatData.messages[i].message = chatData.messages[i].message1; chatData.messages[i].highlighted = false; } //check if the username has an onclick event if (!chatData.messages[i].username.startsWith("<a onclick='mention(this)'>")) { //if it doesn't, add one chatData.messages[i].username = "<a onclick='mention(this)'>" + chatData.messages[i].username + "</a>"; } } } //copy the chat data var chatDataCopy = JSON.parse(JSON.stringify(chatData)); if ($("#invertChad").is(":checked")) { //reverse the chat data, because we want to display the newest messages on the bottom chatData.messages.reverse(); } //update the chat oldUpdateChat(); //restore the chat data chatData = chatDataCopy; var chatContainer = document.getElementsByClassName("chat-container")[0]; if(chatContainer) { if($("#invertChad").prop("checked")) chatContainer.scrollTop = chatContainer.scrollHeight + 1000; else chatContainer.scrollTop = 0; } } function newLine(element) { var newLine = document.createElement("br"); element.appendChild(newLine); } function newValue(element, text, id) { var tt = document.createTextNode(text); var subText = document.createElement("span"); subText.id = id; element.appendChild(tt); element.appendChild(subText); } function insertControls() { var controls = document.createElement("div"); controls.id = "controls"; var oldControls = document.querySelector(".col-7 > div:nth-child(2)").previousElementSibling; oldControls.parentNode.appendChild(controls); //insert new value display for time to next multi newValue(controls, "Time to next multi: ", "timeToNextMulti"); //next line newLine(controls); //insert new value display for time to next bias newValue(controls, "Time to next bias: ", "timeToNextBias"); newLine(controls); newValue(controls, "Top vinegar loss: ", "topVinegarLoss"); newLine(controls); newValue(controls, "Highest Multi: ", "highestMulti"); newLine(controls); newValue(controls, "Highest Bias: ", "highestBias"); //next line newLine(controls); //Toggle box for follow me on ladder var followMe = document.createElement("input"); followMe.type = "checkbox"; followMe.id = "followMe"; followMe.checked = true; controls.appendChild(followMe); //label for follow me var followMeLabel = document.createElement("label"); followMeLabel.htmlFor = "followMe"; followMeLabel.innerHTML = "Follow me on ladder"; followMeLabel.id = "followMeLabel"; controls.appendChild(followMeLabel); window.controlsInserted = true; } insertControls(); window.idToFollow = -1; //..........Custom Row Colours let yourRankerCol = "#dea6de" //You let promotedCol = "#a9a9a9" //Promoted let youNeverCatchThemCol = "#ff8985" //You Never Catch Them let youCanCatchThemCol = "#94f099" //You Catch Them let theyCanCatchYouCol = "#fbc689" //They Catch You let theyNeverCatchYouCol = "#9ce3e8" //They Never Catch You window.originalWriteNewRow = window.writeNewRow; window.lynnsWriteNewRow = function(body, ranker) { let row = body.insertRow(); row.id = "ranker-" + ranker.accountId; 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 = `<a onclick="window.idToFollow = ${ranker.accountId}">${ranker.username}</a>`; 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'); row.style['background-color'] = yourRankerCol; } else if (!ranker.growing) { row.style['background-color'] = promotedCol; } else if ((ranker.rank < ladderData.yourRanker.rank && timeLeft == 'Never - ')) { row.style['background-color'] = youNeverCatchThemCol; } else if ((ranker.rank < ladderData.yourRanker.rank && timeLeft != 'Never - ')) { row.style['background-color'] = youCanCatchThemCol; } else if ((ranker.rank > ladderData.yourRanker.rank && timeLeft != 'Never - ')) { row.style['background-color'] = theyCanCatchYouCol; } else if ((ranker.rank > ladderData.yourRanker.rank && timeLeft == 'Never - ')) { row.style['background-color'] = theyNeverCatchYouCol; } } let oldUpdateLadder = updateLadder; window.updateLadder = ()=>{ if($("#useLynnsLadderCode")[0].checked) { window.writeNewRow = window.lynnsWriteNewRow; } else { window.writeNewRow = window.originalWriteNewRow; } oldUpdateLadder(); displayLadderNavigation(); if(window.chatNavDisplayed !== "yes") { window.chatNavDisplayed = "yes"; displayChatNavigation(); } infoText.style.height = "70px"; if(window.idToFollow == -1 && ladderData.yourRanker.accountId > 0) { window.idToFollow = ladderData.yourRanker.accountId; } var myPoints = ladderData.yourRanker.points; var myPower = ladderData.yourRanker.power; if(window.lastPoints) { var change = myPoints - window.lastPoints; var changePower = myPower - window.lastPower; //round to 2 decimal places change = Math.round(change * 100) / 100; changePower = Math.round(changePower * 100) / 100; var costOfMulti = getUpgradeCost(ladderData.yourRanker.multiplier + 1); var costOfBias = getUpgradeCost(ladderData.yourRanker.bias + 1); var ticksToNextMulti = Math.ceil((costOfMulti - myPower) / changePower); var ticksToNextBias = Math.ceil((costOfBias - myPoints) / change); //clamp to 0 ticksToNextMulti = Math.max(0, ticksToNextMulti); ticksToNextBias = Math.max(0, ticksToNextBias); var ticksToNextMultiString = secondsToHms(ticksToNextMulti); var ticksToNextBiasString = secondsToHms(ticksToNextBias); if(window.controlsInserted) { $("#timeToNextMulti")[0].innerHTML = ticksToNextMultiString; $("#timeToNextBias")[0].innerHTML = ticksToNextBiasString; //color the text { if(ticksToNextMulti == 0) { $("#timeToNextMulti")[0].style.color = "green"; } else if(ticksToNextMulti < 10) { $("#timeToNextMulti")[0].style.color = "orange"; } else { $("#timeToNextMulti")[0].style.color = "red"; } if(ticksToNextBias == 0) { $("#timeToNextBias")[0].style.color = "green"; } else if(ticksToNextBias < 10) { $("#timeToNextBias")[0].style.color = "orange"; } else { $("#timeToNextBias")[0].style.color = "red"; } } //check if we want to follow me on the ladder if($("#followMe")[0].checked && $(".ladder-container")[0]) { var followedRanker = $("#ranker-" + window.idToFollow)[0]; if(followedRanker) { //scroll ladder-container to your ranker minus half the height of the ladder-container $(".ladder-container")[0].scrollTop = followedRanker.offsetTop - $(".ladder-container")[0].offsetHeight/2; } } try{ $("#followMeLabel")[0].innerHTML = "Follow " + ladderData.rankers.find((ranker)=> {return ranker.accountId == window.idToFollow}).username + " (" + window.idToFollow + ")"; }catch(e){ $("#followMeLabel")[0].innerHTML = "Follow " + window.idToFollow; } } } window.lastPoints = myPoints; window.lastPower = myPower; //Update stats for the top ranker let vinDecay = 0.9975; if(window.topRankerID != ladderData.rankers[0].accountId || isNaN(window.topRankerTickCount)) { window.topRankerTickCount = -1; window.topRankerID = ladderData.rankers[0].accountId; } window.topRankerTickCount++; if(ladderData.currentLadder.number == 1 || true /** TODO: Remove the true in next round */) { let vinLoss = Math.pow(vinDecay, window.topRankerTickCount); $("#topVinegarLoss")[0].innerHTML = ((1-vinLoss) * 100).toFixed(2) + "%"; //color the text red if the loss is 0% and green if it is 100% with a smooth transition var hue=((1-vinLoss)*100).toString(10); var color = ["hsl(",hue,",50%,50%)"].join(""); $("#topVinegarLoss")[0].style.color = color; } else { $("#topVinegarLoss")[0].style.color = "black"; $("#topVinegarLoss")[0].innerHTML = "No decay in ladder > 1"; } //calculate the top bias and multi excluding your own let topBias = 0; let topMulti = 0; for(let i = 1; i < ladderData.rankers.length; i++) { if(ladderData.rankers[i].accountId == ladderData.yourRanker.accountId) continue; topBias = Math.max(topBias, ladderData.rankers[i].bias); topMulti = Math.max(topMulti, ladderData.rankers[i].multiplier); } $("#highestBias")[0].innerHTML = `${topBias.toFixed(0)} (Yours: ${ladderData.yourRanker.bias.toFixed(0)})`; $("#highestMulti")[0].innerHTML = `${topMulti.toFixed(0)} (Yours: ${ladderData.yourRanker.multiplier.toFixed(0)})`; }; //wait until we know our username while (ladderData.yourRanker.username == "") { await sleep(100); } subscribeToDomNode("invertChad", updateChat); subscribeToDomNode("scrollableChad", expandChat); subscribeToDomNode("highlightMentions", updateChat); updateChat(); expandChat(); if(window.lynnsMods == undefined) { window.lynnsMods = [ "Base", ]; } })();