您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays probabilities of different Flask of Rainbow Fountain Water results at the Rainbow Pool at grundos.cafe.
// ==UserScript== // @name GC Flask Calculator // @namespace http://devipotato.net/ // @version 1 // @description Displays probabilities of different Flask of Rainbow Fountain Water results at the Rainbow Pool at grundos.cafe. // @author DeviPotato (Devi on GC, devi on Discord) // @license MIT // @match https://www.grundos.cafe/rainbowpool/neopetcolours/* // @icon https://www.google.com/s2/favicons?sz=64&domain=grundos.cafe // @grant GM.setValue // @grant GM.getValue // @run-at document-end // ==/UserScript== (async function() { const carbonatedRemovedColors = ["Blue", "Green", "Red", "Yellow", "White", "Purple", "Brown", "Pink", "Orange", "Invisible"]; function urlParam(name){ var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href); if (results==null) { return null; } return decodeURI(results[1]) || 0; } function querySelectorIncludesText (selector, text){ return Array.from(document.querySelectorAll(selector)) .filter(el => el.textContent.includes(text)); } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } async function countColors(species) { let colorData = JSON.parse(await GM.getValue("colorData", "{}")) let normal = 0; let carbonated = 0; for (const color in colorData[species]) { normal += colorData[species][color]; if(!carbonatedRemovedColors.includes(color)) { carbonated += 1; } } return { normal, carbonated }; } async function calculateColorChances(species, color) { let colorData = JSON.parse(await GM.getValue("colorData", "{}")) let capsColor = capitalizeFirstLetter(color); let totalNormalColors = 0; let totalCarbonatedColors = 0; for (const color in colorData[species]) { totalNormalColors += colorData[species][color]; if(!carbonatedRemovedColors.includes(color)) { totalCarbonatedColors += 1; } } let normalChance = colorData[species][capsColor] / totalNormalColors; let specificNormalChance = 1 / totalNormalColors; let carbonatedChance = (carbonatedRemovedColors.includes(capsColor)?0:1/totalCarbonatedColors); return { normalChance, specificNormalChance, carbonatedChance } } function calculateMultiflaskChances(colorChances, flasks) { let normalChance = 1 - Math.pow(1 - colorChances.normalChance, flasks); let specificNormalChance = 1 - Math.pow(1 - colorChances.specificNormalChance, flasks); let carbonatedChance = 1 - Math.pow(1 - colorChances.carbonatedChance, flasks); return { normalChance, specificNormalChance, carbonatedChance } } function colorStats(colorChances, colorName, isAlt=false) { let hasAlts = colorChances.normalChance != colorChances.specificNormalChance; let container = document.createElement("div"); container.classList.add("flask_statscontainer"); let stats = document.createElement("div"); stats.classList.add("flask_stats"); container.append(stats); let form = document.createElement("div"); form.classList.add("multiflask_form"); form.textContent = "Number of flasks: "; container.appendChild(form); let flaskCount = document.createElement("input"); flaskCount.type = "text"; flaskCount.inputmode = "numeric"; flaskCount.pattern = "[0-9]*"; flaskCount.value = 1; flaskCount.classList.add("multiflask_count","form-control"); flaskCount.size = 5; //workaround to prevent auto-focus on mobile flaskCount.disabled = true; flaskCount.autofocus = false; form.appendChild(flaskCount); form.appendChild(document.createTextNode(" ")); let calculateButton = document.createElement("input"); calculateButton.type = "button"; calculateButton.classList.add("multiflask_button","form-control"); calculateButton.value = "Calculate"; form.appendChild(calculateButton); flaskCount.addEventListener("input", (event) => { if (isNaN(flaskCount.value) || !Number.isInteger(flaskCount.value)) { flaskCount.value = flaskCount.value.replace(/\D/g, ''); } }, false); flaskCount.addEventListener("keydown", (event) => { if(event.key === "Enter") { calculateButton.click(); } }, false); calculateButton.addEventListener("click", async () => { if(flaskCount.value == 0) flaskCount.value = 1; let multiflaskChances = calculateMultiflaskChances(colorChances, flaskCount.value); let hasAlts = colorChances.normalChance != colorChances.specificNormalChance; stats.innerHTML = formatFlaskStats(multiflaskChances, colorName, isAlt); }, false); stats.innerHTML = formatFlaskStats(colorChances, colorName, isAlt); return container; } //workaround to prevent auto-focus on mobile function enableFlaskBoxes() { let boxes = document.querySelectorAll(".multiflask_count"); boxes.forEach(box => { box.disabled = false; }) } function miniStats(colorChances, isAlt=false) { let miniStats = document.createElement("span"); miniStats.classList.add("flask_ministats"); miniStats.innerHTML = `(${(colorChances.normalChance*100).toFixed(2)}% | ✨${(colorChances.carbonatedChance*100).toFixed(2)}%)` return miniStats; } function formatFlaskStats(colorChances, colorName, isAlt=false) { let hasAlts = colorChances.normalChance != colorChances.specificNormalChance; if(isAlt) { return `Normal Flasks: <b>${(colorChances.normalChance*100).toFixed(2)}%</b> ${hasAlts?`(any ${colorName}) | <b>${(colorChances.specificNormalChance*100).toFixed(2)}%</b> (this version)`:""}<br> ✨Carbonated Flasks: <b>${(colorChances.carbonatedChance*100).toFixed(2)}%</b> (base ${colorName}) | <b>${(0).toFixed(2)}%</b> (this version)` } else { return `Normal Flasks: <b>${(colorChances.normalChance*100).toFixed(2)}%</b> ${hasAlts?`(any ${colorName}) | <b>${(colorChances.specificNormalChance*100).toFixed(2)}%</b> (this version)`:""}<br> ✨Carbonated Flasks: <b>${(colorChances.carbonatedChance*100).toFixed(2)}%</b>` } } function formatColorTotals(totals) { return `There are <b>${totals.normal}</b> possible results for normal flasks and <b>${totals.carbonated}</b> possible results for ✨carbonated flasks.` } //count the colors and update data, return if a new color was found async function parseColors(content, species) { let colorData = JSON.parse(await GM.getValue("colorData", "{}")) if(!colorData[species]) { colorData[species] = {}; } let newColors = false; let colorElements = content.querySelectorAll('.flex-column.small-gap'); colorElements.forEach(element => { let colorName = element.getElementsByTagName("span")[0].innerText.trim(); if(element.innerHTML.includes("https://grundoscafe.b-cdn.net/items/100012.gif")) { if(colorData[species] && colorData[species][colorName]==1) { newColors = true; // there is an alt icon for a color we don't know the alts for } } if(!colorData[species][colorName]) { newColors = true; // this is a color we did not know about before colorData[species][colorName] = 1; } }) await GM.setValue("colorData", JSON.stringify(colorData)); return newColors; } async function parseAlts(content, species) { let colorData = JSON.parse(await GM.getValue("colorData", "{}")) if(!colorData[species]) { colorData[species] = {}; } let colorElements = content.querySelectorAll('.flex-column.small-gap'); let totals = {}; let newColors = false; colorElements.forEach(element => { let colorName = element.getElementsByTagName("span")[0].innerText.trim(); let imgSrc = element.getElementsByTagName("img")[0].src; if(colorName == "Royal" || colorName == "Usuki" || colorName == "Quiguki") { if(imgSrc.includes("boy")) { colorName = colorName + "boy"; } else if(imgSrc.includes("girl")) { colorName = colorName + "girl"; } } totals[colorName] = (totals[colorName] || 0) + 1; }) for (const color in totals) { let newTotal = totals[color] + 1; if(colorData[species] && colorData[species][color] != newTotal) { newColors = true; // this is a different total of alts than we had before colorData[species][color] = newTotal; } } await GM.setValue("colorData", JSON.stringify(colorData)); return newColors; } async function attachMiniStats(elements, species) { for(let element of elements) { let colorName = element.getElementsByTagName("span")[0].innerText.trim(); let imgSrc = element.getElementsByTagName("img")[0].src; let isAlt = imgSrc.includes("alt") || imgSrc.includes("classic") if(colorName == "Royal" || colorName == "Usuki" || colorName == "Quiguki") { if(imgSrc.includes("boy")) { colorName = colorName + "boy"; } else if(imgSrc.includes("girl")) { colorName = colorName + "girl"; } } let colorChances = await calculateColorChances(species, colorName) element.querySelector("span").after(miniStats(colorChances,isAlt)); } } // thank you twiggies! async function fetchPage(url) { try { const response = await fetch(url); if(response.ok) { const html = await response.text(); const node = await new DOMParser().parseFromString(html, "text/html"); // show RE if the fetched page contains a RE if (node.getElementById('page_event').innerHTML.trim() != '') { document.getElementById('page_event').innerHTML += node.getElementById('page_event').innerHTML } return node; } else { throw new Error(`${response.status} ${response.statusText}`); } } catch(err) { console.log(`Encountered error fetching url ${url}:`) console.log(err) } } async function fetchColors(species) { const colorPage = await fetchPage(`https://www.grundos.cafe/rainbowpool/neopetcolours/?species=${species}`); return await parseColors(colorPage, species); } async function fetchAlts(species) { const altPage = await fetchPage(`https://www.grundos.cafe/rainbowpool/neopetcolours/?species=${species}&altsonly=true`); return await parseAlts(altPage, species); } if(document.querySelectorAll(".errorpage").length==0) { if(urlParam("species")) { let species = urlParam("species"); // for all color/all alt color pages if(!urlParam("colour")) { let isAlts = urlParam("altsonly")=="true"; let newColors = false; let colorElements = document.querySelectorAll('.flex-column.small-gap'); if(!isAlts) { newColors = await parseColors(document, species); if(newColors) await fetchAlts(species); } else { newColors = await parseAlts(document, species); if(newColors) await fetchColors(species) } let colorData = JSON.parse(await GM.getValue("colorData", "{}")); let flaskTotals = document.createElement("p"); let totals = await countColors(species); flaskTotals.innerHTML = formatColorTotals(totals); querySelectorIncludesText("strong","Colours")[0].after(flaskTotals); await attachMiniStats(colorElements, species); } // for specific color pages else { let colorData = JSON.parse(await GM.getValue("colorData", "{}")) if(!colorData[species]) { await fetchColors(species); await fetchAlts(species); colorData = JSON.parse(await GM.getValue("colorData", "{}")) } if(colorData[species]) { let color = urlParam("colour"); let capsSpecies = species=="jubjub"?"JubJub":capitalizeFirstLetter(species); let capsColor = capitalizeFirstLetter(color); capsColor = capsColor.split("_")[0] if(!colorData[species][capsColor]) { await fetchColors(species); await fetchAlts(species); colorData = JSON.parse(await GM.getValue("colorData", "{}")) } let colorChances = await calculateColorChances(species, capsColor) if(!color.includes("alt") && !color.includes("classic")) { let altHeaders = querySelectorIncludesText("strong",`Alternate`); let classicHeaders = querySelectorIncludesText("strong",`Classic`); let allAlts = [...altHeaders, ...classicHeaders]; if(colorData[species][capsColor] != allAlts.length+1) { await fetchColors(species); await fetchAlts(species); colorChances = await calculateColorChances(species, capsColor) } querySelectorIncludesText("strong",`${capsColor} ${capsSpecies}`)[0].after(colorStats(colorChances,capsColor,false)); allAlts.forEach(header => { header.after(colorStats(colorChances,capsColor,true)); }) } else { querySelectorIncludesText("strong",capsSpecies)[0].after(colorStats(colorChances,capsColor,true)); } } //workaround to prevent auto-focus on mobile setTimeout(() => { enableFlaskBoxes(); }, 300) } } // color page of all species else if(urlParam("colour")) { let colorData = JSON.parse(await GM.getValue("colorData", "{}")) let color = urlParam("colour"); let capsColor = capitalizeFirstLetter(color); let speciesElements = document.querySelectorAll('.flex-column.small-gap'); let missingData = false; for(let element of speciesElements) { let species = element.getElementsByTagName("span")[0].innerText.trim().toLowerCase(); if(colorData[species] && colorData[species][capsColor]) { let colorChances = await calculateColorChances(species, capsColor) element.querySelector("span").after(miniStats(colorChances)); } else { missingData = true; let noData = document.createElement("span"); noData.innerHTML = `(???%)` element.querySelector("span").after(noData); } } if(missingData) { let warning = document.createElement("p"); warning.innerHTML = `Colour data for some of these species is missing or out of date. Please visit their pages to see their flask chances.` querySelectorIncludesText("strong","Available")[0].after(warning); } } } })();