Greasy Fork

Greasy Fork is available in English.

Real Profit Calculator

Calculates real NoRNG profit and exp gain for MWI battle emulator.

// ==UserScript==
// @name         Real Profit Calculator
// @namespace    http://tampermonkey.net/
// @version      1.4
// @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
    }

    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 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 = `实际期望利润: ${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 = `实际期望利润: ${formattedProfit}`;

            const targetDiv = document.getElementById('noRngProfitPreview').parentElement.parentElement;
            targetDiv.parentNode.insertBefore(displayDiv, targetDiv.nextSibling);
        }
    }

    function addRealExpBlock() {
        // "<div class="row"><b data-i18n="common:simulationResults.xpPerHour">每小时经验值</b></div>"
        let realExpTitle = document.createElement('div');
        realExpTitle.className = "row";
        realExpTitle.innerHTML = `<b id="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 addCommunityBuffDialog() {
        const dialogHtml = `
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">社区增益</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">经验等级</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">战斗掉落等级</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">哞卡</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">关闭</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.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 mapSelected = document.querySelector("#selectZone").value;
        const isEliteMap = mapSelected.includes("elite");
        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;
        // 判断经验项链加成
        if (document.querySelector("#selectEquipment_neck").value === "/items/necklace_of_wisdom") {
            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 += `社区经验等级: ${globalBuffConfig.expLevel}`;
        if (globalBuffConfig.mooPass) {
            if (descText.length > 0) descText += ", ";
            descText += "哞卡";
        }
        if (descText.length > 0) {
            descText = `(${descText})`;
        }
        document.getElementById("realExpGainTitle").textContent = `实际每小时经验值 ${descText}`;
    }

    const obConfig = {characterData: true, subtree: true, childList: true};
    const ProfitNode = document.getElementById('noRngProfitPreview');
    const ExpNode = document.getElementById('simulationResultExperienceGain');

    addRealExpBlock();
    addCommunityBuffDialog();
    loadBuffStatus();

    new MutationObserver(() => {calculateProfit();}).observe(ProfitNode, obConfig);
    const expObserver = new MutationObserver(() => {
        calculateExp();
    });
    expObserver.observe(ExpNode, obConfig)
})();