Greasy Fork is available in English.

CBG Helper

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

Versione datata 08/09/2020. Vedi la nuova versione l'ultima versione.

// ==UserScript==
// @name         CBG Helper
// @namespace    https://yys.zhebu.work/
// @version      0.0.7
// @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;
    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 addHighlightBtn() {
        if (document.getElementById('cbghelper_showhighlight')) {
            return;
        }
        let itms = [];
        let { fastest, heads, feet } = acct_info.summary;
        if(heads.length > 0 || feet.length > 0) {
            let li = document.createElement('li');
            let x = heads.length > 0 ? heads.length : '无';
            let y = feet.length > 0? feet.length : '无';
            li.innerText = `${x}头${y}脚`;
            itms.push(li)
        }
        let fastest_spd = document.createElement('li');
        fastest_spd.innerText = `最快一速${[1, 2, 3, 4, 5, 6].reduce((total, p) => total + fastest[p]['散件'], 0).toFixed(2)}`;
        fastest_spd.id = 'cbghelper_showhighlight';
        itms.push(fastest_spd);

        let zc_spd = document.createElement('li');
        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];
        zc_spd.innerText = `招财一速${zc_spd_val.toFixed(2)}`;
        itms.push(zc_spd);

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

    function summaryPage() {
        let decimal = 2;
        let { fastest, heads, feet } = acct_info.summary;
        fastest = JSON.parse(JSON.stringify(fastest)); // make a deep copy
        let wrapper = document.createElement('div');
        let title = document.createElement('h3')
        title.innerText = "御魂亮点"
        let spd = document.createElement('section')
        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 fastest_spd = [1, 2, 3, 4, 5, 6].reduce((total, p) => total + fastest[p]['散件'], 0);
        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];

        Object.keys(fastest[2]).forEach(k => fastest[2][k] = fastest[2][k]-57 > 0 ? fastest[2][k] - 57 : 0)
        let fastest_tbl = `<table width="100%">
        <tr> <td>位置</td> ${[1, 2, 3, 4, 5, 6].map(i => `<td>${i}</td>`)} <td>4(命中)</td> </tr>
        <tr> <td>散件</td> ${[1, 2, 3, 4, 5, 6, 7].map(i => `<td>${fastest[i]['散件'].toFixed(decimal)}</td>`)} </tr>
        <tr> <td>招财猫</td> ${[1, 2, 3, 4, 5, 6, 7].map(i => `<td>${fastest[i]['招财猫'].toFixed(decimal)}</td>`)} </tr>
        <tr> <td>火灵</td> ${[1, 2, 3, 4, 5, 6, 7].map(i => `<td>${fastest[i]['火灵'].toFixed(decimal)}</td>`)} </tr>
        <tr> <td>蚌精</td> ${[1, 2, 3, 4, 5, 6, 7].map(i => `<td>${fastest[i]['蚌精'].toFixed(decimal)}</td>`)} </tr>
    </table>`;
        spd.innerHTML = `<div><span class="data-name">头:</span> ${headStr} </div>
    <div><span class="data-name">脚:</span> ${feetStr} </div>
    <div><span class="data-name">散件一速:</span> <span class="data-value">${fastest_spd.toFixed(5)}</span></div>
    <div><span class="data-name">招财一速:</span> <span class="data-value">${zc_spd_val.toFixed(5)}</span></div>`

    let title2 = document.createElement('h3')
    title2.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(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('yuhun-list')[0];
        yuhun_list.parentNode.childNodes[1].append(b)
    }

    let checkExist = setInterval(function () {
        let addDownloadBtnWrapper = function () {
            if (document.getElementsByClassName('yuhun-list').length) {
                addDownloadBtn()
            }
        }
        let addHighlightBtnWrapper = function () {
            if (document.getElementsByClassName('highlight').length) {
                addHighlightBtn()
            }
        }
        let addHighlightViewWrapper = function () {
            if (document.getElementsByClassName('content-pvp').length && acct_info.ready) {
                addHighlightView();
            }
        }
        let checkfn_list = {
            'cbghelper_download': addDownloadBtnWrapper,
            'cbghelper_showhighlight': addHighlightBtnWrapper,
            'cbghelper_highlight': addHighlightViewWrapper
        };
        let handlers = {}

        for (let eid of Object.keys(checkfn_list)) {
            if (document.getElementById(eid)) {
                if (eid in handlers) {
                    clearInterval(handlers[eid])
                    delete handlers[eid]
                }
                continue;
            } else {
                handlers[eid] = setInterval(checkfn_list[eid], 200)
            }
        }
    }, 100);


    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'];

        try {
            var message = {
                name: equip.seller_name,
                roleid: equip.seller_roleid,
                ordersn: equip.game_ordersn,
                mitama_list
            };
            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_list);

        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) {
                attrs[propName].add_val = getPropValue(equips, mitama_list, propName).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 += 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_list) {
        let fastest = {};
        let heads = [];
        let feet = [];
        let suit_imp = ["招财猫", "火灵", "蚌精"];
        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] = {'散件': 0};
            for(let name of suit_imp) {
                fastest[p][name] = 0;
            }
        }

        Object.entries(mitama_list).forEach(([key, m]) => {
            let {attrs, pos, name, qua} = m;
            let spd = 0;
            for (let [p, v] of attrs) {
                if (p === '速度') {
                    spd += parseFloat(v);
                }
            }
            if(suit_imp.includes(name)) {
                fastest[pos][name] = fastest[pos][name] > spd ? fastest[pos][name] : spd;
            }
            fastest[pos]['散件'] = fastest[pos]['散件'] > spd ? fastest[pos]['散件'] : spd;
            if (pos === 4 && attrs[0][0] === '效果命中') {
                if(spd > 15) {
                    feet.push({pos, name, value: spd});
                }
                pos = 7
                if(suit_imp.includes(name)) {
                    fastest[pos][name] = fastest[pos][name] > spd ? fastest[pos][name] : spd;
                }
                fastest[pos]['散件'] = fastest[pos]['散件'] > spd ? fastest[pos]['散件'] : spd;
            }
            if (pos === 2 && spd - 57 >= 15) {
                heads.push({pos, name, value: spd-57});
            }
        });
        acct_info.summary = {
            heads,
            feet,
            fastest
        }
        acct_info.ready = true;
    }
})();