CBG Helper

A helper tool for Onmyoji player to look for good account.

当前为 2021-10-30 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         CBG Helper
// @namespace    https://yys.zhebu.work/
// @version      0.1.5
// @description  A helper tool for Onmyoji player to look for good account.
// @author       CJ
// @match        https://yys.cbg.163.com/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    var acct_info = {};
    var FRAC_N = 5
    var url_match = "api/get_equip_detail";
    var _open = XMLHttpRequest.prototype.open;
    var suit_imp = ["散件", "招财猫", "火灵", "蚌精", "共潜"];
    window.XMLHttpRequest.prototype.open = function (method, URL) {
        var _onreadystatechange = this.onreadystatechange,
            _this = this;

        _this.onreadystatechange = function () {
            // catch only completed 'api/search/universal' requests
            if (_this.readyState === 4 && _this.status === 200 && ~URL.indexOf(url_match)) {
                try {
                    //////////////////////////////////////
                    // THIS IS ACTIONS FOR YOUR REQUEST //
                    //             EXAMPLE:             //
                    //////////////////////////////////////
                    var data = JSON.parse(_this.responseText); // {"fields": ["a","b"]}

                    data = floatify(data)

                    // rewrite responseText
                    Object.defineProperty(_this, 'responseText', {value: JSON.stringify(data)});
                    Object.defineProperty(_this, 'response', {value: JSON.stringify(data)});
                    /////////////// END //////////////////
                } catch (e) {}

                console.log('Caught! :)', method, URL/*, _this.responseText*/);
            }
            // call original callback
            if (_onreadystatechange) _onreadystatechange.apply(this, arguments);
        };

        // detect any onreadystatechange changing
        Object.defineProperty(this, "onreadystatechange", {
            get: function () {
                return _onreadystatechange;
            },
            set: function (value) {
                _onreadystatechange = value;
            }
        });

        return _open.apply(_this, arguments);
    };

    function addExtendedHighlight() {
        if (document.getElementById('cbghelper_exthighlight') || !acct_info.hasOwnProperty("summary")) {
            return;
        }
        let { fastest, heads, feet, hero_info } = acct_info.summary;
        let itms = [];
        let build_item = function (label, id) {
            let li = document.createElement('li');
            li.innerText = label;
            return li
        };
        //collection of heros
        let total = hero_info['ssr']['all'] + hero_info['sp']['all'];
        let got_total = hero_info['ssr']['got'] + hero_info['sp']['got'];
        if (total === got_total) {
            itms.push(build_item('SSR/SP全收集'));
        } else if (hero_info['ssr']['all'] === hero_info['ssr']['got']) {
            itms.push(build_item('SSR全收集'));
        }
        if (hero_info['x']['all'] === hero_info['x']['got']) {
            itms.push(build_item('联动全收集'));
        }
        //number of heads and feet
        if(heads.length > 0 || feet.length > 0) {
            let x = heads.length > 0 ? heads.length : '无';
            let y = feet.length > 0? feet.length : '无';
            let label = `${x}头${y}脚`;
            itms.push(build_item(label))
        }
        //fastest speed
        let fastest_spd_label = `最快一速${[1, 2, 3, 4, 5, 6].reduce((total, p) => total + fastest[p]['散件'], 0).toFixed(2)}`;
        let fastest_spd = build_item(fastest_spd_label)
        fastest_spd.id = 'cbghelper_exthighlight';
        itms.push(fastest_spd);
        //fastest zhaocai speed
        let zc_spd_val = [1, 2, 3, 4, 5, 6].reduce((total, p) => total + fastest[p]['招财猫'], 0);
        let spd_inc = [1, 2, 3, 4, 5, 6].map(p => fastest[p]['散件'] - fastest[p]['招财猫'], 0);
        spd_inc.sort((a, b) => b - a);
        zc_spd_val += spd_inc[0] + spd_inc[1];
        let zc_spd_label = `招财一速${zc_spd_val.toFixed(2)}`;
        itms.push(build_item(zc_spd_label));

        let highlight = document.getElementsByClassName('highlight')[0];
        for (let li of itms) {
            highlight.appendChild(li);
        }
    }

    function summaryPage() {
        let wrapper = document.createElement('div');
        if (!acct_info.hasOwnProperty('summary')) {
            wrapper.appendChild(document.createTextNode("数据加载出错,请尝试刷新页面"))
            return wrapper;
        }
        let decimal = 2;
        let { fastest, heads, feet, fullspd_cnt } = acct_info.summary;
        let fullspd_suit = Object.fromEntries(suit_imp.map(name => [name, 0]));
        fastest = JSON.parse(JSON.stringify(fastest)); // make a deep copy
        let suit_stats = {};
        for (let p of [1,2,3,4,5,6]) {
            for (let name in fullspd_cnt[p]) {
                if(fullspd_suit[name] === 0) {
                    continue;
                }
                if(name in suit_stats) {
                    suit_stats[name].push(p);
                } else {
                    suit_stats[name] = [p];
                }
            }
        }
        for (let name in suit_stats) {
            if (suit_stats[name].length >= 4) {
                if (name in fullspd_suit) {
                    continue;
                } else {
                    fullspd_suit[name] = 0;
                }
            }
        }
        let fast_suit_speed = function(name) {
            let suit_fastest = Object.fromEntries([1, 2, 3, 4, 5, 6].map(p => [p, name in fastest[p]? fastest[p][name]: 0]));
            let suit_spd_val = [1, 2, 3, 4, 5, 6].reduce((total, p) => total + suit_fastest[p], 0);
            let spd_inc = [1, 2, 3, 4, 5, 6].map(p => fastest[p]['散件'] - suit_fastest[p]);
            spd_inc.sort((a, b) => b - a);
            suit_spd_val += spd_inc[0] + spd_inc[1];
            return suit_spd_val;
        }
        Object.keys(fullspd_suit).forEach(name => {
            fullspd_suit[name] = fast_suit_speed(name);
        })

        let sortByValue = function (a, b) { return b.value - a.value}
        let headStr = heads.length > 0 ? heads.sort(sortByValue).map(itm => `<span class="data-value">${itm.name}: ${(itm.value).toFixed(decimal)}</span>`.trim()).join(", ") : "无";
        let feetStr = feet.length > 0 ? feet.sort(sortByValue).map(itm => `<span class="data-value">${itm.name}: ${(itm.value).toFixed(decimal)}</span>`.trim()).join(", ") : "无";
        let td_val = function (pos, name) {
            let fullspd = fullspd_cnt[pos][name] > 0;
            let spd = name in fastest[pos]? fastest[pos][name].toFixed(decimal): 0;
            let res = `<span${fullspd? "":" class=disabled"}>${spd}</span> `
            if (fullspd) {
                res += `<span>(${fullspd_cnt[pos][name]})</span>`
            }
            return res;
        }
        Object.keys(fastest[2]).forEach(k => fastest[2][k] = fastest[2][k]-57 > 0 ? fastest[2][k] - 57 : 0)
        let speed_summary = function (name) {
            return `<tr> <td>${name}</td> ${[1, 2, 3, 4, 5, 6, 7].map(i => `<td>${td_val(i, name)}</td>`)} </tr>`;
        }
        let fastest_tbl = `<table width="100%">
            <tr> <th>位置</th> ${[1, 2, 3, 4, 5, 6].map(i => `<th>${i}</th>`)} <th>4(命中)</th> </tr>
            ${ Object.keys(fullspd_suit).map(name => speed_summary(name)).join(" ") }
        </table>`;
        let suit_table = `<table width="100%">
            <tr> <th>御魂名称</th> <th>套装一速</th></tr>
            ${ Object.keys(fullspd_suit).map(name => `<tr> <th>${name}</th> <td>${fullspd_suit[name].toFixed(5)}</td></tr>\n`).join("") }
        </table>`;

        let title = document.createElement('h3')
        title.innerText = "御魂亮点"
        let spd = document.createElement('section')
        spd.innerHTML = `<div><span class="data-name">头:</span> ${headStr} </div>
        <div><span class="data-name">脚:</span> ${feetStr} </div>`;


        let title2 = document.createElement('h3');
        title2.innerText = "套装一速(非独立)";
        let suit = document.createElement('section');
        suit.innerHTML = suit_table;

        let title3 = document.createElement('h3');
        title3.innerText = "各位置一速(满速个数)";

        let fastest_sec = document.createElement('section');
        fastest_sec.innerHTML = fastest_tbl;
        if(fastest_sec.firstChild.nodeType === Node.TEXT_NODE) {
            fastest_sec.firstChild.textContent = '';
        }

        wrapper.appendChild(title);
        wrapper.appendChild(spd);
        wrapper.appendChild(title2);
        wrapper.appendChild(suit);
        wrapper.appendChild(title3);
        wrapper.appendChild(fastest_sec);
        return wrapper;
    }

    function addHighlightView() {
        if (document.getElementById('cbghelper_highlight')) {
            return;
        }
        let div = document.createElement('div');
        div.id = 'cbghelper_highlight';
        div.appendChild(summaryPage());
        let wrapper = document.getElementsByClassName('content-pvp')[0];
        wrapper.appendChild(div)
    }

    function addDownloadBtn() {
        if (document.getElementById('cbghelper_download')) {
            return;
        }
        let b = document.createElement('a');
        b.innerText = "(💾保存为JSON)";
        b.onclick = function () {
            console.log("To save data!");
            saveToJsonHelper();
        }
        b.id = "cbghelper_download"
        b.style.cursor = "pointer";
        let yuhun_list = document.getElementsByClassName('content-top-left')[0];
        yuhun_list.getElementsByTagName('h3')[1].appendChild(b);
    }

    function addDownloadBtnWrapper () {
        if (document.getElementsByClassName('yuhun-list').length) {
            addDownloadBtn();
        }
    }
    function addExtHighlightWrapper () {
        if (document.getElementsByClassName('highlight').length) {
            addExtendedHighlight();
        }
    }
    function addHighlightViewWrapper() {
        if (document.getElementsByClassName('content-pvp').length && acct_info.ready) {
            addHighlightView();
        }
    }

    function init() {
        let checkfn_list = {
            'cbghelper_download': addDownloadBtnWrapper,
            'cbghelper_exthighlight': addExtHighlightWrapper,
            'cbghelper_highlight': addHighlightViewWrapper
        };
        let handlers = {};

        let checkExist = setInterval(function () {
            if (!document.URL.startsWith("https://yys.cbg.163.com/cgi/mweb/equip")) {
                return;
            }
            for (let eid of Object.keys(checkfn_list)) {
                if (document.getElementById(eid) && eid in handlers) {
                    clearInterval(handlers[eid]);
                    delete handlers[eid];
                } else if (document.getElementById(eid) || eid in handlers) {
                    continue;
                } else {
                    handlers[eid] = setInterval(checkfn_list[eid], 200);
                }
            }
        }, 100);
    };

    init();

    //----------------------
    const floatify = function (data) {
        let equip = data['equip'];
        let acct_detail = JSON.parse(equip['equip_desc']);
        let mitama_list = acct_detail['inventory'];
        let hero_list = acct_detail['heroes'];
        let hero_info = acct_detail['hero_history'];

        try {
            var message = {
                name: equip.seller_name,
                roleid: equip.seller_roleid,
                ordersn: equip.game_ordersn,
                mitama_list
            };
            var event = new CustomEvent("SaveLastAccount", { detail: message });
            window.dispatchEvent(event);
            acct_info.latest = message;
        } catch (error) {}

        Object.entries(mitama_list).forEach(([key, value]) => {
            mitama_list[key] = floatify_mitama(value);
        });
        Object.entries(hero_list).forEach(([key, value]) => {
            hero_list[key] = floatify_hero(value, mitama_list);
        });
        acct_detail['inventory'] = mitama_list;
        equip['equip_desc'] = JSON.stringify(acct_detail);
        data['equip'] = equip;

        acctHighlight(mitama_list, hero_info);

        return data;
    }

    function getPropValue(mitama_set, mitama_list, propName) {
        let res = 0;
        for (let mitama_id of mitama_set) {
            var { attrs, single_attr=[] } = mitama_list[mitama_id];
            for (let [p, v] of attrs) {
                if (p === propName) {
                    res += parseFloat(v);
                }
            }
            if (single_attr.length > 0 && single_attr[0] === propName) {
                res += parseFloat(single_attr[1])
            }
        }
        return res
    }

    function floatify_hero(hero_data, mitama_list) {
        var { attrs, equips } = hero_data
        Object.keys(attrs).forEach(propName => {
            if (propName === '速度' && parseFloat(attrs[propName].add_val) > 0) {
                if(hero_data.heroId === 255 && hero_data.awake === 1) { //觉醒阎魔+10速度
                    attrs[propName].add_val = 10.0
                } else {
                    attrs[propName].add_val = 0.0
                }
                attrs[propName].add_val += getPropValue(equips, mitama_list, propName);
                attrs[propName].add_val = attrs[propName].add_val.toFixed(FRAC_N)
            }
            if (propName === '暴击' && parseFloat(attrs[propName].add_val) > 0) {
                let suit_cp = ["针女","三味","网切","伤魂鸟","破势","镇墓兽","青女房"];
                attrs[propName].add_val = getPropValue(equips, mitama_list, propName);
                let suit_names = equips.map(x => mitama_list[x].name);
                let suit_count = {};
                for (let n of suit_names) {
                    if (n in suit_count) {
                        suit_count[n] += 1;
                    } else {
                        suit_count[n] = 1;
                    }
                }
                Object.keys(suit_count).forEach(n => {
                    if (suit_count[n] >= 2 && suit_cp.includes(n)) {
                        attrs[propName].add_val += suit_count[n] === 6? 30:15;
                    }
                })
                attrs[propName].add_val = attrs[propName].add_val.toFixed(2) + "%";
            }
        })

        return hero_data;
    }

    function floatify_mitama(mitama) {
        var { rattr, attrs } = mitama;
        mitama["attrs"] = [attrs[0], ...calAttrs(rattr)];
        return mitama;
    }

    function calAttrs(rattrs, format = true) {
        var enAttrNames = ['attackAdditionRate',
                           'attackAdditionVal',
                           'critPowerAdditionVal',
                           'critRateAdditionVal',
                           'debuffEnhance',
                           'debuffResist',
                           'defenseAdditionRate',
                           'defenseAdditionVal',
                           'maxHpAdditionRate',
                           'maxHpAdditionVal',
                           'speedAdditionVal']

        var cnAttrNames = ['攻击加成', '攻击', '暴击伤害', '暴击',
                           '效果命中', '效果抵抗', '防御加成',
                           '防御', '生命加成', '生命', '速度']

        var basePropValue = {
            '攻击加成': 3, '攻击': 27, '暴击伤害': 4, '暴击': 3,
            '效果抵抗': 4, '效果命中': 4, '防御加成': 3,
            '防御': 5, '生命加成': 3, '生命': 114, '速度': 3
        }

        var percentProp = {
            '攻击加成': true, '攻击': false, '暴击伤害': true, '暴击': true,
            '效果抵抗': true, '效果命中': true, '防御加成': true,
            '防御': false, '生命加成': true, '生命': false, '速度': false
        }

        var e2cNameMap = Object.assign({}, ...enAttrNames.map((n, index) => ({ [n]: cnAttrNames[index] })));
        var res = Object();
        for (let rattr of rattrs) {
            var [prop, v] = rattr;
            prop = e2cNameMap[prop];
            if (prop in res) {
                res[prop] += v;
            } else {
                res[prop] = v;
            }
        }

        return Object.keys(res).sort().map(p => {
            var v = res[p] * basePropValue[p]
            if (format) {
                v = v.toFixed(FRAC_N);
                if (percentProp[p]) {
                    v += "%";
                }
            }

            return [p, v];
        })
    }

    function soulToJson(soulItem) {
        const { attrs, level, qua, rattr, uuid, name, pos, single_attr = [] } = soulItem;
        var born = parseInt(uuid.substring(0, 8), 16);
        let soulDict = {
            '固有属性': single_attr.length ? single_attr[0] : null,
            '生成时间': born,
            '御魂等级': level,
            '御魂星级': qua,
            '御魂ID': uuid,
            '御魂类型': name,
            '位置': pos
        };
        let PROPNAMES = ['攻击', '攻击加成', '防御',
                         '防御加成', '暴击', '暴击伤害', '生命', '生命加成', '效果命中',
                         '效果抵抗', '速度'];
        PROPNAMES.map(function (e, i) {
            soulDict[e] = 0;
        });

        let percent = ['攻击加成', '防御加成', '暴击', '暴击伤害', '生命加成', '效果命中', '效果抵抗'];
        for (let [p, v] of [attrs[0], ...calAttrs(rattr, false)]) {
            v = parseFloat(v)
            if (percent.includes(p)) {
                v = v / 100;
            }
            soulDict[p] += v;
        }
        if (single_attr.length) {
            const [p, v] = single_attr;
            soulDict[p] += parseFloat(v) / 100;
        }

        return soulDict;
    }

    function saveToJson(soulLists) {
        var fileContent = 'data:text/json;charset=utf-8,'
        let soulListJson = Object.values(soulLists).map(soulToJson);
        soulListJson.unshift('yuhun_ocr2.0');
        fileContent += JSON.stringify(soulListJson);

        var encodedUri = encodeURI(fileContent);
        var link = document.createElement('a');
        link.setAttribute('href', encodedUri);
        link.setAttribute('download', 'yuhun.json');
        link.innerHTML = 'Click Here to download your data';
        document.body.appendChild(link); // Required for FF

        link.click();
        link.parentNode.removeChild(link);
    }

    function saveToJsonHelper() {
        saveToJson(acct_info.latest.mitama_list);
    }

    function acctHighlight(mitama_list, hero_info) {
        let fastest = {};
        let fullspd_cnt = {}
        let heads = [];
        let feet = [];
        let all_pos = [1,2,3,4,5,6];

        for(let p of [1,2,3,4,5,6,7]){ //7 for 命中@4
            fastest[p] = {};
            fullspd_cnt[p] = {};
            for(let name of suit_imp) {
                fastest[p][name] = 0;
                fullspd_cnt[p][name] = 0;
            }
        }

        Object.entries(mitama_list).forEach(([key, m]) => {
            let {attrs, pos, name, qua, rattr} = m;
            let spd = 0, spdpt = 0;
            for (let [p, v] of attrs) {
                if (p === '速度') {
                    spd += parseFloat(v);
                }
            }
            for (let rattr_entry of rattr) {
                var [prop, v] = rattr_entry;
                if(prop === 'speedAdditionVal') {
                    spdpt += 1
                }
            }
            if (spdpt < 1 || (pos === 2 && spd < 57)) {
                return;
            }
            if (spdpt === 6 && (pos !== 2 || spd > 70)) {
                fullspd_cnt[pos]['散件'] += 1
                if(name in fullspd_cnt[pos]) {
                    fullspd_cnt[pos][name] += 1;
                } else {
                    fullspd_cnt[pos][name] = 1;
                }
                if (pos === 2) {
                    heads.push({pos, name, value: spd-57});
                } else if (pos === 4 && attrs[0][0] === '效果命中') {
                    fullspd_cnt[7]['散件'] += 1;
                    if(name in fullspd_cnt[pos]) {
                        fullspd_cnt[7][name] += 1;
                    } else {
                        fullspd_cnt[7][name] = 1;
                    }
                    feet.push({pos, name, value: spd});
                }
            }
            if(name in fastest[pos]) {
                fastest[pos][name] = fastest[pos][name] > spd ? fastest[pos][name] : spd;
            } else {
                fastest[pos][name] = spd;
            }
            fastest[pos]['散件'] = fastest[pos]['散件'] > spd ? fastest[pos]['散件'] : spd;
            if (pos === 4 && attrs[0][0] === '效果命中') {
                pos = 7;
                if(name in fastest[pos]) {
                    fastest[pos][name] = fastest[pos][name] > spd ? fastest[pos][name] : spd;
                } else {
                    fastest[pos][name] = spd;
                }
                fastest[pos]['散件'] = fastest[pos]['散件'] > spd ? fastest[pos]['散件'] : spd;
            }
        });
        acct_info.summary = {
            heads,
            feet,
            fastest,
            fullspd_cnt,
            hero_info
        }
        acct_info.ready = true;
    }
})();