您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Calculates real NoRNG profit and exp gain for MWI battle emulator.
// ==UserScript== // @name Real Profit Calculator // @namespace http://tampermonkey.net/ // @version 1.60 // @description Calculates real NoRNG profit and exp gain for MWI battle emulator. // @author guch8017 // @match https://shykai.github.io/MWICombatSimulatorTest/dist/* // @license MIT // ==/UserScript== (function () { 'use strict'; let globalBuffConfig = { expLevel: 20, battleDropLevel: 20, mooPass: true } let language = "en"; const eliteMapExpBonus = { "/actions/combat/smelly_planet_elite": 0.1, "/actions/combat/swamp_planet_elite": 0.1, "/actions/combat/aqua_planet_elite": 0.1, "/actions/combat/jungle_planet_elite": 0.1, "/actions/combat/gobo_planet_elite": 0.15, "/actions/combat/planet_of_the_eyes_elite": 0.15, "/actions/combat/sorcerers_tower_elite": 0.15, "/actions/combat/bear_with_it_elite": 0.15, "/actions/combat/golem_cave_elite": 0.2, "/actions/combat/twilight_zone_elite": 0.2, "/actions/combat/infernal_abyss_elite": 0.2, } const i18nText = { "en": { "i18n-realProfitTitle": "Real No RNG Profit", "i18n-realExpGainTitle": "Real XP Per Hour", "i18n-communityBuff": "Community Buffs", "i18n-expLevel": "Experience Level", "i18n-battleDropLevel": "Combat Drop Level", "i18n-mooPass": "Moo Pass", "i18n-close": "Close", }, "zh": { "i18n-realProfitTitle": "实际期望利润", "i18n-realExpGainTitle": "实际每小时经验值", "i18n-communityBuff": "社区增益", "i18n-expLevel": "经验等级", "i18n-battleDropLevel": "战斗掉落等级", "i18n-mooPass": "哞卡", "i18n-close": "关闭", } } const enhancementLevelTotalMultiplierTable = [0, 1, 2.1, 3.3, 4.6, 6, 7.5, 9.1, 10.8, 12.600000000000001, 14.500000000000002, 16.5, 18.6, 20.8, 23.1, 25.5, 28, 30.6, 33.300000000000004, 36.1, 39]; function parseNumber(text) { const numericText = text.replace(/[^\d.,\-]/g, '').replace(/,/g, ''); return parseFloat(numericText); } function getDrinkConcentration(level) { return 0.1 + 0.002 * (enhancementLevelTotalMultiplierTable[level] || 0); } function getWisdomNeckBonus(level) { return 0.03 + 0.003 * (enhancementLevelTotalMultiplierTable[level] || 0); } function getBuffRate(level) { if (level === 0) return 0; return 0.2 + 0.005 * (level - 1); } async function calculateProfit() { const expenseText = document.getElementById('script_expense')?.textContent || ''; const noRngProfitText = document.getElementById('noRngProfitPreview')?.textContent || ''; const expense = parseNumber(expenseText); const noRngProfit = parseNumber(noRngProfitText); const buffRate = getBuffRate(globalBuffConfig.battleDropLevel); if (!isNaN(expense) && !isNaN(noRngProfit)) { const realProfit = ((noRngProfit + expense) * (1 + buffRate)) - expense; const formattedProfit = realProfit.toLocaleString(undefined, { maximumFractionDigits: 3 }); const existingDiv = document.getElementById('realProfitDisplay'); if (existingDiv) { existingDiv.textContent = `${i18nText[language]["i18n-realProfitTitle"]}: ${formattedProfit}`; return; } const displayDiv = document.createElement('div'); displayDiv.id = 'realProfitDisplay'; displayDiv.style.backgroundColor = '#FFD700'; displayDiv.style.color = 'black'; displayDiv.style.fontWeight = 'bold'; displayDiv.style.padding = '4px'; displayDiv.textContent = `${i18nText[language]["i18n-realProfitTitle"]}: ${formattedProfit}`; const targetDiv = document.getElementById('noRngProfitPreview').parentElement.parentElement; targetDiv.parentNode.insertBefore(displayDiv, targetDiv.nextSibling); } } function addRealExpBlock() { let realExpTitle = document.createElement('div'); realExpTitle.className = "row"; realExpTitle.innerHTML = `<b id="realExpGainTitle" i18n-id="i18n-realExpGainTitle">实际每小时经验值</b>`; // <div id="simulationResultExperienceGain" class="mb-2" style="background-color: rgb(205, 255, 221); color: black;"><div class="row"><div class="col-md-6" data-i18n="common:total">合计</div><div class="col-md-6 text-end">2299</div></div><div class="row"><div class="col-md-6" data-i18n="leaderboardCategoryNames.stamina">耐力</div><div class="col-md-6 text-end">448</div></div><div class="row"><div class="col-md-6" data-i18n="leaderboardCategoryNames.attack">攻击</div><div class="col-md-6 text-end">655</div></div><div class="row"><div class="col-md-6" data-i18n="leaderboardCategoryNames.power">力量</div><div class="col-md-6 text-end">655</div></div><div class="row"><div class="col-md-6" data-i18n="leaderboardCategoryNames.defense">防御</div><div class="col-md-6 text-end">541</div></div></div> let realExpDiv = document.createElement('div'); realExpDiv.id = "simulationResultExperienceGainReal"; realExpDiv.className = "mb-2"; realExpDiv.style.backgroundColor = "rgb(205, 255, 221)"; realExpDiv.style.color = "black"; const targetDiv = document.getElementById('simulationResultExperienceGain'); targetDiv.parentNode.insertBefore(realExpDiv, targetDiv.nextSibling); targetDiv.parentNode.insertBefore(realExpTitle, targetDiv.nextSibling); } function addi18nListener() { setTimeout(() => { const i18nBtnList = document.querySelector("body > div.language-switcher").children; i18nBtnList[0].addEventListener("click", () => { console.log("Language changed to English"); language = "en"; applyi18n(); }); i18nBtnList[1].addEventListener("click", () => { console.log("Language changed to Chinese"); language = "zh"; applyi18n(); }); }, 1000); } function applyi18n() { const i18nElements = document.querySelectorAll("[i18n-id]"); i18nElements.forEach(element => { const i18nId = element.getAttribute("i18n-id"); if (i18nText[language] && i18nText[language][i18nId]) { element.textContent = i18nText[language][i18nId]; } else { console.warn(`Missing translation for ${i18nId} in language ${language}`); } }); } function addCommunityBuffDialog() { const dialogHtml = ` <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" i18n-id="i18n-communityBuff">社区增益</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" id="buttonCloseCommBuff1"></button> </div> <div class="modal-body"> <div class="container-fluid"> <div id="commBuffList"> <div class="row mb-2"> <div class="col-md-4 offset-md-3 align-self-center" i18n-id="i18n-expLevel">经验等级</div> <div class="col-md-3"> <input class="form-control" type="number" placeholder="0" min="0" max="20" step="1" id="commExpLevel"> </div> </div> <div class="row mb-2"> <div class="col-md-4 offset-md-3 align-self-center" i18n-id="i18n-battleDropLevel">战斗掉落等级</div> <div class="col-md-3"> <input class="form-control" type="number" placeholder="0" min="0" max="20" step="1" id="commBattleDropLevel"> </div> </div> <div class="row mb-2"> <div class="col-md-4 offset-md-3 align-self-center" i18n-id="i18n-mooPass">哞卡</div> <div class="col-md-3"> <input class="form-check-input" type="checkbox" id="commMooPass"> </div> </div> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="buttonCloseCommBuff2" i18n-id="i18n-close">关闭</button> </div> </div> </div> `; const dialogDiv = document.createElement('div'); dialogDiv.className = "modal"; dialogDiv.id = "communityBuffModal"; dialogDiv.tabIndex = "-1"; dialogDiv.style.display = "none"; dialogDiv.ariaHidden = "true"; dialogDiv.innerHTML = dialogHtml; const targetDiv = document.getElementById('houseRoomsModal'); targetDiv.parentNode.insertBefore(dialogDiv, targetDiv.nextSibling); // <button id="buttonImportExport" class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#importExportModal" data-i18n="common:controls.importExport">导入/导出</button> const button = document.createElement('button'); button.id = "buttonCommunityBuff"; button.className = "btn btn-primary"; button.type = "button"; button.textContent = "社区增益"; button.setAttribute("i18n-id", "i18n-communityBuff"); button.onclick = showCommunityBuffDialog; const buttonDiv = document.createElement('div'); buttonDiv.className = "col-md-auto"; buttonDiv.appendChild(button); const targetButton = document.getElementById('buttonImportExport'); targetButton.parentNode.parentNode.insertBefore(buttonDiv, targetButton.parentNode.nextSibling); document.getElementById('buttonCloseCommBuff1').onclick = hideCommunityBuffDialog; document.getElementById('buttonCloseCommBuff2').onclick = hideCommunityBuffDialog; } function saveBuffStatus() { const expLevel = parseInt(document.getElementById('commExpLevel').value); const battleDropLevel = parseInt(document.getElementById('commBattleDropLevel').value); const mooPass = document.getElementById('commMooPass').checked; globalBuffConfig.expLevel = expLevel; globalBuffConfig.battleDropLevel = battleDropLevel; globalBuffConfig.mooPass = mooPass; localStorage.setItem("rpc_buff_config", JSON.stringify(globalBuffConfig)); } function loadBuffStatus() { const buffConfig = localStorage.getItem("rpc_buff_config"); if (buffConfig) { let { expLevel, battleDropLevel, mooPass } = JSON.parse(buffConfig); if (expLevel === undefined) expLevel = 20; if (battleDropLevel === undefined) battleDropLevel = 20; if (mooPass === undefined) mooPass = true; document.getElementById('commExpLevel').value = expLevel; document.getElementById('commBattleDropLevel').value = battleDropLevel; document.getElementById('commMooPass').checked = mooPass; globalBuffConfig.expLevel = expLevel; globalBuffConfig.battleDropLevel = battleDropLevel; globalBuffConfig.mooPass = mooPass; } else { document.getElementById('commExpLevel').value = 20; document.getElementById('commBattleDropLevel').value = 20; document.getElementById('commMooPass').checked = true; } } function showCommunityBuffDialog() { const dialog = document.getElementById('communityBuffModal'); if (dialog) { dialog.style.display = "block"; dialog.className = "modal show"; dialog.ariaModal = "true"; dialog.role = "dialog"; dialog.removeAttribute("aria-hidden"); document.body.classList.add("modal-open"); document.body.style.overflow = "hidden"; const modalBackdrop = document.createElement('div'); modalBackdrop.className = "modal-backdrop show"; document.body.appendChild(modalBackdrop); } else { console.error("Dialog not found"); } } function hideCommunityBuffDialog() { const dialog = document.getElementById('communityBuffModal'); if (dialog) { saveBuffStatus(); dialog.style.display = "none"; dialog.className = "modal"; dialog.removeAttribute("aria-modal"); dialog.removeAttribute("role"); document.body.classList.remove("modal-open"); document.body.style.overflow = ""; const modalBackdrop = document.querySelector('.modal-backdrop'); if (modalBackdrop) { modalBackdrop.remove(); } } else { console.error("Dialog not found"); } } function clearRealExp() { const simulationResultExperienceGain = document.getElementById('simulationResultExperienceGainReal'); if (simulationResultExperienceGain) { simulationResultExperienceGain.innerHTML = ""; } } function addRealExp(key, value) { const simulationResultExperienceGain = document.getElementById('simulationResultExperienceGainReal'); if (simulationResultExperienceGain) { const row = document.createElement('div'); row.className = "row"; const col1 = document.createElement('div'); col1.className = "col-md-6"; col1.textContent = key; const col2 = document.createElement('div'); col2.className = "col-md-6 text-end"; col2.textContent = value; row.appendChild(col1); row.appendChild(col2); simulationResultExperienceGain.appendChild(row); } } async function calculateExp() { let expBonus = 0; // 判断精英地图或地下城加成 const isDungeon = document.getElementById("simDungeonToggle").checked; const mapSelected = document.querySelector("#selectZone").value; const isEliteMap = mapSelected.includes("elite"); if (isDungeon) { expBonus = 0.2; } else if (isEliteMap) { let mapBonus = eliteMapExpBonus[mapSelected]; if (mapBonus === undefined) { console.error(`No bonus found for elite map: ${mapSelected}`); } else { expBonus = mapBonus; } } // 判断咖啡加成,同时计算暴饮带来的额外增益 let coffeeBonus = 0; for (let drinkId = 0; drinkId < 3; drinkId++) { const drink = document.querySelector(`#selectDrink_${drinkId}`); if (drink && drink.value === "/items/wisdom_coffee") { coffeeBonus += 0.12; } } const pouchSel = document.querySelector("#selectEquipment_pouch"); if (pouchSel && pouchSel.value === "/items/guzzling_pouch") { const pouchEnhanceLv = Number.parseInt(document.querySelector("#inputEquipmentEnhancementLevel_pouch").value) || 0; const pouchConcentration = getDrinkConcentration(pouchEnhanceLv); coffeeBonus *= pouchConcentration; } expBonus += coffeeBonus; // 判断经验项链加成 const neckSel = document.querySelector("#selectEquipment_neck").value; if (neckSel === "/items/necklace_of_wisdom" || neckSel === "/items/philosophers_necklace") { const neckEnhanceLv = Number.parseInt(document.querySelector("#inputEquipmentEnhancementLevel_neck").value) || 0; expBonus += getWisdomNeckBonus(neckEnhanceLv); } // 判断房屋加成 let houseTotalLv = 0; for (const houseLv of document.querySelectorAll('input[data-house-hrid]')) { houseTotalLv += Number.parseInt(houseLv.value) || 0; } expBonus += houseTotalLv * 0.0005; // 读取经验值 const expRateBonus = getBuffRate(globalBuffConfig.expLevel); const mooPassBonus = globalBuffConfig.mooPass ? 0.05 : 0; clearRealExp(); for (let node of document.querySelector("#simulationResultExperienceGain").childNodes) { let node0 = node.childNodes[1]; let expCurrent = Number.parseInt(node0.innerText); let expNew = Math.floor(expCurrent / (1 + expBonus) * (1 + expBonus + expRateBonus + mooPassBonus)); addRealExp(node.childNodes[0].innerText, expNew); } let descText = ""; if (globalBuffConfig.expLevel > 0) descText += `${i18nText[language]["i18n-expLevel"]}: ${globalBuffConfig.expLevel}`; if (globalBuffConfig.mooPass) { if (descText.length > 0) descText += ", "; descText += i18nText[language]["i18n-mooPass"]; } if (descText.length > 0) { descText = `(${descText})`; } document.getElementById("realExpGainTitle").textContent = `${i18nText[language]["i18n-realExpGainTitle"]} ${descText}`; } const obConfig = { characterData: true, subtree: true, childList: true }; const ProfitNode = document.getElementById('noRngProfitPreview'); const ExpNode = document.getElementById('simulationResultExperienceGain'); addRealExpBlock(); addCommunityBuffDialog(); loadBuffStatus(); addi18nListener(); language = localStorage.getItem("i18nextLng") || "en"; applyi18n(); new MutationObserver(() => { calculateProfit(); }).observe(ProfitNode, obConfig); const expObserver = new MutationObserver(() => { calculateExp(); }); expObserver.observe(ExpNode, obConfig) })();