辅助计算PCR手游的所需体力,总次数
/* eslint-disable require-jsdoc */ // ==UserScript== // @name PCR图书馆辅助计算器 // @namespace http://tampermonkey.net/ // @version 3.1.6 // @description 辅助计算PCR手游的所需体力,总次数 // @author winrey,colin,hymbz // @license MIT // @icon https://pcredivewiki.tw/static/images/unit/icon_unit_108831.png // @supportURL https://github.com/winrey/pcr-wiki-helper/issues // @homepage https://github.com/winrey/pcr-wiki-helper // @run-at document-start // @connect cdn.jsdelivr.net // @match *://pcredivewiki.tw/* // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_info // @grant GM.getValue // @grant GM.setClipboard // @grant GM.setValue // @grant GM.deleteValue // @grant GM.info // @grant GM_addStyle // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://cdn.jsdelivr.net/gh/winrey/pcr-wiki-helper@eea66a67d2a0f3794d905fd6447b66329dc34d2e/js/solver.js // @require https://cdn.jsdelivr.net/gh/winrey/pcr-wiki-helper@21de4a7a288c0cdf5d3cea248eee31301d36c105/js/html2canvas.min.js // ==/UserScript== (function() { 'use strict'; const sleep = (time) => new Promise((r) => setTimeout(r, time)); $(document).ready(function() { GM_addStyle(` .helper--calc-r###lt-cell.helper--show-deleted-btn::after { content: '\u2716'; position: absolute; bottom: 70px; background-color: #ff0000; color: #fff; line-height: 0.9rem; border-radius: 30%; padding: 3px; opacity: 50%; cursor: pointer; z-index: 10000; } .helper--calc-r###lt-cell.helper--show-deleted-btn.multiSelect-no::after { content: '\u00A0\u00A0\u00A0'; } .helper--calc-r###lt-cell.helper--show-deleted-btn.multiSelect-yes::after { content: '\u2714'; } .Unique.helper-cell.mapDrop-item.mr-2.p-2.text-center{ background-color: rgba(255,193,7,.5); border-radius: 0.7vw; } table.table-bordered.mapDrop-table.helper .r###lt-cell-td{ vertical-align: middle; text-align: justify; text-align-last: justify; } .topToView{ z-index: 96000; white-space: break-spaces; } div.ClpMeue{ visibility: hidden; transition: all 0.3s; position: relative; top: 3rem; opacity:0; max-width: 0px; } button#toImage { position: absolute; font-size: 0.2375rem; padding: 0.075rem 0.05rem; bottom: 4.4rem; } button#toText{ position: absolute; font-size: 0.2375rem; padding: 0.075rem 0.05rem; bottom: 2.8rem; } div.ClpMeue.active{ visibility: visible; display: inline-block; opacity:1; } .mapDrop-table .helper-oddTri { right: .3rem; top: 1.6rem; color: black; } .mapDrop-table .mapDrop-item { max-width: 106px; } .mapDrop-table .helper--calc-r###lt-cell{ width: 70px; height: 70px; margin: 0 auto; position: relative; } .mapDrop-table .helper--calc-r###lt-cell.un--wanted{ opacity: 0.4; } .mapDrop-table .helper-block { top: 1.6rem; right: .12rem; } #helper--bottom-btn-group { position: fixed; right: calc(60px + 1% - 2px); bottom: 90px; overflow: visible; display: flex; flex-wrap: wrap; flex-direction: column; } .helper--nav-to-level.helper--important::after { content: "独"; color: #e60c0c; font-size: .5em; position: relative; top: -0.8em; right: 1em; } span.dropsProgress.hide{ display: none; } #helper--modal-content:not(.helper--drop) input[item-name], #helper--modal-content:not(.helper--drop) input[orig-item-name] { display: none; } #helper--modal-content input[item-name], #helper--modal-content input[orig-item-name]{ width: 6em; } a.singleSelect{ display: none } a.singleSelect.ready{ display: inline } .switch-multiSelectBtnState { display: none; width: 70px; height: 32px; border: solid 2px #ddd; border-radius: 30px; background-color: #FFF; position: relative; padding-left: 2.6rem; -webkit-transition: background-color 0.3s; transition: background-color 0.3s; -webkit-user-select: none; font-size: 14px; left: 5.1rem; } .switch-multiSelectBtnState.ready { display: inline; } .switch-handler.ready{ display: inline-block; } .switch-multiSelectBtnState.selected-completedBtn{ pointer-events: none; } .switch-multiSelectBtnState::before { right: 4rem; content: '多选'; position: absolute; width: 2rem; font-size: 15px; top: -0.1rem; margin-right: -1rem; transition: width 0s 0s,margin-right .3s ,top 0.3s,content 0s 1s; } .switch-multiSelectBtnState.active.selected-completedBtn::before { right: 3rem; content: '已选完成'; font-size: 14px; top: -0.08rem; margin-right: 0rem; border-radius: 11%; width: 4rem; border: 2px outset #f5d68e; pointer-events: all; line-height: 15.9px; transition: box-shadow 0.3s } .switch-multiSelectBtnState.selected-completedBtn:hover::before { box-shadow: -0.7px 1px 5.1px #000; } .switch-handler { position: relative; left: 2.5rem; top: 0.25rem; width: 1rem; height: 1rem; background-color: #FFF; border-radius: 100% 100%; -webkit-box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.52); box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.52); -webkit-transition: all 0.3s; transition: all 0.3s; display: none; } .switch-multiSelectBtnState.active { border-color: #4cd964; background-color: #4cd964; } .switch-handler.ready.active { left: 3.9rem; } .switch-handler::after { color: #000000; content: '关'; position: relative; bottom: 0.25rem; left: -0.6rem; padding-left: 1rem; -webkit-transition: color .3s 0.1s; } .switch-handler.active::after { content: ' '; color: #fff0; position: relative; left: -4rem; } .switch-handler.active::before { content: '开'; color: #f5f5f5; right: 1.4rem; padding-right: 1.4rem; } .switch-handler::before { content: '\u00A0\u00A0\u00A0'; color: #fff0; width: 2.6rem; bottom: 0.2rem; -webkit-transition: color .3s .1s; position: relative; } .form-control.active{ transition:border linear .2s,box-shadow linear .5s; -moz-transition:border linear .2s,-moz-box-shadow linear .5s; -webkit-transition:border linear .2s,-webkit-box-shadow linear .5s; outline:none; border-color: rgba(12, 255, 0, 0.75); box-shadow:0 0 8px rgba(59, 224, 9, 0.75); -moz-box-shadow:0 0 8px rgba(93,149,242,.5); -webkit-box-shadow:0 0 8px rgba(93,149,242,3); } `); /** * @global * @namespace onceItemchange * @type {object[]} * @property {string} onceItemchange[].id - 碎片id. * @property {number} onceItemchange[].count - 碎片数量. */ const onceItemchange = []; let vue; /** * 点击“存储队伍”按钮 */ const saveTeamData = () => { // findOnePCRelem(`.sticky-top>button.pcbtn.primary`, '儲存隊伍').click(); vue.saveTeam(); const d = document.querySelector('a[href="##"]'); d && d.click(); }; /** * 自动切换到地图掉落模式 * */ async function autoSwitch2MapList() { vue = document.querySelector('div.container').__vue__; // findOnePCRelem(`.armory-function.p-1.pb-3>button`, '地圖掉落模式').click(); vue.isLoading = true; await sleep(20); vue.changeDisplayMode(3); vue.pageSize = 1000; await sleep(1000); } // eslint-disable-next-line require-jsdoc function toPage(num) { const $table = $('.mapDrop-table:not(.helper)'); const $pages = $($table.find('tr').toArray().pop()); const $frist = $($pages.find('li').toArray()[num || 1]); $frist.children()[0].click(); } async function getMapData() { function rowParser($tr, page, index) { function parseItem($item) { const url = $($item.find('a')[0]).attr('href'); const name = $($item.find('img')[0]).attr('title'); const img = $($item.find('img')[0]).attr('src'); const odd = parseInt($($item.find('h6.dropOdd')[0]).text()) / 100; // %不算在parseInt内 const count = parseInt( (!/無需|溢/.test($($item.find('.py-1')[0]).text()) && $($item.find('.py-1')[0]).text()) || 0, ); const id = /\d+/.exec(img)[0]; return {url, name, img, odd, count, id}; } const children = $tr.children().map(function() { return $(this); }); const name = children[0].text(); const requirement = parseInt(children[1].text()); const items = $(children[2].children()[0]) .children() .toArray() .map((v) => parseItem($(v))); return {name: name, requirement: requirement, items: items, page: page, index: index}; } function next($table) { const $pages = $($table.find('tr').toArray().pop()); const $next = $($pages.find('li').toArray().pop()); if ($next.hasClass('disabled')) return false; $next.children()[0].click(); return true; } let $table = $('.mapDrop-table:not(.helper)'); const data = []; toPage(1); let page = 1; await sleep(20); do { await sleep(20); $table = $('.mapDrop-table:not(.helper)'); // 判断简易计算 const start = $table.find('thead>tr').length; let pageData = $table.find('tbody>tr'); const dataTd = $table.find('tbody>td'); pageData = pageData.toArray().map($); pageData = pageData.slice(0, -1); // 最后一行是分页栏 if (start === 1) { pageData = pageData.slice(start, -1); // 最后一行是分页栏 } else { // pageData = pageData // .filter(function (i, v) { return v !== this && v % 2 === 0 || false }.bind(pageData.length - 1)) //结果过滤偶数 //edit "Comments "by cool_delete const paeD = (_i, v) => ~~/建議:\s(\d+)/.exec($(dataTd.get(v)).text())[1]; // 结果过滤0数 pageData = pageData.filter(paeD); } pageData = pageData.map((m, i) => rowParser(m, page, i)); data.push.apply(data, pageData); page += 1; } while (next($table)); toPage(1); return data; } function getCost(name) { if (name === '1-1') return 6; if (name.startsWith('1-')) return 8; if (name.startsWith('2-')) return 8; if (name.startsWith('3-')) return 8; if (name.startsWith('4-')) return 9; if (name.startsWith('5-')) return 9; if (name.startsWith('6-')) return 9; return 10; } function calcR###lt(data) { data = data.map((chan) => { const sum = (...arr) => [].concat(...arr).reduce((acc, val) => acc + val, 0); chan.exception = sum(chan.items.map((v) => v.count * v.odd)); chan.max = Math.max.apply( null, chan.items.map((v) => v.count / v.odd), ); chan.min = Math.min.apply( null, chan.items.filter((v) => v.count).map((v) => v.count / v.odd), ); chan.effective = sum.apply( null, chan.items.map((v) => (v.count ? v.odd : 0)), ); return chan; }); const model = { optimize: 'cost', opType: 'min', constraints: (() => { const equis = {}; data.forEach((c) => c.items.forEach((e) => (equis[e.name] = {min: e.count}))); return equis; })(), variables: (() => { const challs = {}; data.forEach((c) => { const cMap = {}; c.items.forEach((item) => (cMap[item.name] = item.odd)); cMap.cost = getCost(c.name); challs[c.name] = cMap; }); return challs; })(), }; console.log('model', model); const lp_r###lt = solver.Solve(model); console.log(lp_r###lt); for (const k in lp_r###lt) { if (!k.includes('-')) continue; const target = data.find((c) => c.name === k); if (target) target.times = lp_r###lt[k] || 0; } return { total: lp_r###lt.r###lt, map: data.sort((a, b) => b.times - a.times).sort((a, b) => b.effective - a.effective), }; } const BOUNS_KEY = '___bouns'; function askBouns() { const bouns = parseInt( prompt('请输入目前倍数(N3或N2,非活动期可取消)').split('').reverse().join('') || '1', ) || 1; sessionStorage.setItem(BOUNS_KEY, bouns); return bouns; } function getBouns() { let bouns = parseInt(sessionStorage.getItem('___bouns')); if (!bouns) { bouns = askBouns(); } return bouns; } function showR###lt(data) { const bouns = getBouns(); const table = genTable(data.map.filter((m) => m.times)); const comment = $.parseHTML('<a href>说明</a>'); const commentLines = []; commentLines.push( '推荐使用方法:按照列表顺序刷图,数量不要超过「适用」和「推荐」两者的最小值,完成后修改数量,重新根据新情景计算。', ); commentLines.push(''); commentLines.push( '注意:如果您尚缺好感,可考虑以30体/次为倍数单位扫荡刷图,能最大化获取发情蛋糕。', ); commentLines.push(''); commentLines.push('---表头说明---'); commentLines.push( '『章节』关卡编号。点击编号可以自动跳转到图书馆原表中关卡详细介绍。点击『章节』能切换排序', ); commentLines.push( '『独』标识。代表当前结果中仅有该图能出的装备碎片。赶进度的话刷满黄色碎片数。', ); commentLines.push('『需求』关卡需求。图中所需装备总数。'); commentLines.push('『效率』装备效率。图中所有有效装备掉落的概率和。'); commentLines.push('『适用』有效次数。预计能保持「效率」不变的次数。'); commentLines.push( '『推荐』推荐次数。假设概率固定,由考虑体力的线性规划算法计算出的总最优刷图次数。', ); commentLines.push('『最大』最大次数。最近该图需要的最高次数。'); $(comment[0]).click((e) => { tips('说明', commentLines.join('\n')); e.preventDefault(); e.stopPropagation(); }); const quickModifyBtn = $.parseHTML(`<a href="##" style='margin-left: 1rem;'>快速修改</a>`); $(quickModifyBtn[0]).click(async () => { const modifyState = !document.querySelector('.singleSelect.ready'); [...document.querySelectorAll('span.dropsProgress')].reduce( (t, i) => i.classList.toggle('hide', modifyState), document.querySelector('span.dropsProgress'), ); // 点击快速修改 如果找不到输入框就没法设置 vue.showFastStock(); document.querySelector('#popBox.modal.fade.show') && document.querySelector('#popBox.modal.fade.show').click(); document .getElementById('helper--modal-content') .classList.toggle('helper--drop', modifyState); deleteItem(modifyState); modifyState && document .querySelector('span.switch-multiSelectBtnState') .addEventListener(`click`, multiItemChange); modifyState && document.querySelector('span.switch-handler').addEventListener(`click`, (e) => { multiSelectState( switchMultBtnState( 'active', !document.querySelector('span.switch-multiSelectBtnState.active'), ), ); e.stopImmediatePropagation(); }); modifyState && (document.querySelector('.singleSelect.ready').parentElement.scrollLeft = 3000); return false; }); const reCalcBtn = $.parseHTML( `<a href class=singleSelect style='margin-left: 1rem;'title='修改所有装备后 点击自动保存和计算'>重新计算</a>`, ); const multipleBtn = $.parseHTML( `<span class=switch-multiSelectBtnState title='切换后能快速满足碎片数'></span><span class="switch-handler"></span>`, ); $(reCalcBtn[0]).click(() => { TranslateRefresh_the_interface(); handleClickCalcBtn(); return false; }); showModalByDom( `总体力需求:${Math.round( data.total / bouns, )} 当前倍率:${bouns} `, comment, quickModifyBtn, reCalcBtn, multipleBtn, table, ); } function createModal(...content) { const containerStyle = ` top: 0; left: 0; width: 100%; height: 100%; position: fixed; z-index: 10000; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: all ease-in-out 0.5s; `; const maskStyle = ` top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); position: absolute; z-index: 11000; `; const boxStyle = ` min-width: 80%; z-index: 12000; `; const contentStyle = ` width: 100%; margin-bottom: 10px; max-height: 80vh; overflow: scroll; `; const html = ` <div id="helper--modal" style="${containerStyle}"> <div id="helper--modal-mask" style="${maskStyle}"></div> <div class="breadcrumb" style="${boxStyle}"> <div id="helper--modal-content" style="${contentStyle}">${content.join( '', )}</div> <button id="helper--modal-close" type="button" class="pcbtn mr-3"> 关闭 </button> <button id="helper--modal-Clipboard" type="button" class="pcbtn mr-3" title='导出到粘贴板'> >📋 </button> <div class = 'ClpMeue'> <button id="toText" type="button" class="pcbtn mr-3" title='导出全部数据'>全局</button> <button id="toImage" type="button" class="pcbtn mr-3" title='当前计算结果的截图'>截图</button></div> </div> </div> `; $('#app').after(html); $('#helper--modal-close').click(() => hideModal()); $('#helper--modal-mask').click(() => hideModal()); $('#helper--modal-Clipboard').click(showToClpMeue); $('button#toText').click(() => { return showToClpMeue(), txtToClipboard(); }); $('button#toImage').click(() => { return showToClpMeue(), clpSetImage(); }); } const showToClpMeue = () => { document.querySelector('div.ClpMeue').classList.toggle('active'); }; function genItemsGroup(items) { items = boundLocatStrong(items); // ${item.Unique?`唯一`:``} const html = ` <div class="d-flex flex-nowrap justify-content-center"> ${items .map( (item) => ` <div class="p-2 text-center mapDrop-item mr-2 helper-cell ${item.Unique && 'Unique' || '' } ${(!item.count && `un--wanted`) || '' }"> <div class='helper--calc-r###lt-cell ${(!item.count && `un--wanted`) || '' }' onclick ${`data-item-count=${item.count}`} data-item_id=${item.id} data-item_name=${item.name} title="满足了" > <a href="${item.url}" class="" target="_blank" > <img width="70" title="${item.name + ` `}${(item.information && item.information) || `` }${(item.Unique && ` 该图限定`) || ``}" src="${item.img}" class="aligncenter" > <span class="oddTri helper-oddTri"> <i class="dropOdd text-center helper-block "> ${Math.round(item.odd * 100)}%</i></span> </a> </div> <span class="text-center py-1 d-block" ${!item.count && `style="opacity:0.4"`} title="${item.information}" data-total-need=${item.count} > ${(item.count && `总需` + item.count) || `已满`} </span> <span><input type="number" class="form-control" item-name="${item.name }" value="${item.has || 0 }"title="边框需要变荧光绿才算保存成功" onclick="this.select()"></span> <span><input type="number" class="form-control" orig-item-name="${item.name }" placeholder="增量" title="适用合成单次刷图 可每次输入掉落数量,回车确认并跳转下个物品"></span> <span class= 'dropsProgress ${(item.count && ' ') || 'hide'} '>进度:${item.has || 0 }</span> </div> `, ) .join('')} </div> `; return html; } function boundLocatStrong(items) { for (const item of items) { try { const p = ~~new RegExp('"equipment_id":' + item.id + ',"count":([^,]+),') .exec(localStorage.itemList)[1] .replace(/^\"|\"$/g, ''); item.information = `有` + p + ' 缺' + item.count; item.has = p; } catch (e) { item.count = 0; } } return items; } const changeItemCount = (e) => { // 快速完成 const singleItem = () => { const $this = $(e.target); const count = $this[0].dataset.itemCount; if (!count) return; const ID = $this[0].dataset.item_id; const name = $this[0].dataset.item_name; if (confirm(`${name}的数量达到了${count}。页面刷新后自动计算`)) { itemCountChage(ID, count); GM.setValue(`mount`, `(()=>{ setTimeout(handleClickCalcBtn,2000) })()`); location.reload(); } }; const multiItem = () => { e.target.classList.toggle( `multiSelect-yes`, !(e.target.classList[e.target.classList.length - 1] == `multiSelect-yes`), ); const cls = document.querySelector('.switch-multiSelectBtnState').classList; cls.toggle( `selected-completedBtn`, document.querySelectorAll('.multiSelect-yes').length != 0, ); }; (e.target.classList[e.target.classList.length - 1] == `helper--show-deleted-btn` && !singleItem()) || multiItem(); }; const multiItemChange = () => { const cell = document.querySelectorAll('.multiSelect-yes'); if ( cell.length && confirm(`你目前选了${cell.length}个装备,开始修改,点击确定刷新页面自动计算`) ) { for (const dom of [...cell]) { itemCountChage(dom.dataset.itemId, dom.dataset.itemCount); } GM.setValue(`mount`, `(()=>{ setTimeout(handleClickCalcBtn,2000) })()`); location.reload(); } }; function itemCountChage(equipment_id, count) { const p = new RegExp('"equipment_id":' + equipment_id + ',"count":([^,]+)', 'g'); const t = new RegExp(`\\d+`, 'g'); localStorage.setItem( `itemList`, localStorage.itemList.replace(p, (match, p1) => { return match.substr(0, 30) + p1.replace(t, count); // match[match.length-1]match.length-3 }), ); } function uniqueItem(mapData) { const itmes = []; for (let i = 0; i < mapData.length; i++) { itmes.push(...mapData[i].items); } for (const t of itmes) { itmes[t.name] = (itmes[t.name] && itmes[t.name] + 1) || 1; } for (let i = 0; i < mapData.length; i++) { for (const item of mapData[i].items) { if (item.count > 0 && itmes[item.name] < 2) { mapData[i].IsuniqueItem = true; item.Unique = true; } } } } function sortColumn(e) { // -1>a,b 1>b,a//greedy const trList = [...e.target.closest('table').querySelectorAll(`tbody>tr`)]; const greedy = () => { trList .sort((a) => { return (~~a.dataset.isUniqueItem && -1) || (~~a.dataset.isUniqueItem && 1) || 0; }) .sort((a, b) => { return ( (~~a.dataset.isUniqueItem && ~~b.dataset.isUniqueItem && ~~b.children[2].dataset.dropEffective - ~~a.children[2].dataset.dropEffective) || 0 ); }); return 1; }; const dropEffective = () => { trList.sort((a, b) => { return ~~b.children[2].dataset.dropEffective - ~~a.children[2].dataset.dropEffective || 0; }); return 0; }; e.target.dataset.sortType = (!~~e.target.dataset.sortType && greedy()) || dropEffective(); // 切换状态保存 const tbody = e.target.closest('table').querySelector('tbody'); tbody.innerHTML = ''; for (const t of trList) { tbody.appendChild(t); } } async function txtToClipboard() { const 数据条目 = '20'; // (条) const trList = [ ...document.querySelectorAll( `table.table.table-bordered.mapDrop-table.helper>tbody tr:nth-child(-n+${数据条目})`, ), ]; const howMuchSpace = (sum = 12, a = []) => { return sum < 1 && (sum = 0), (a.length = sum), a.fill(space, 0, sum).join(``); }; const surroundedByaBar = (text, Rows = 6, horizontal = text.length + 8) => { Rows = (Rows & 1 && Rows) || Rows + 1; // only odd let str = enter; const blank = (horizontal - text.length) / 2; const half = Math.ceil(Rows / 2); for (let i = 1; i <= Rows; i++) { str += '|'; for (let k = 0; k < horizontal; k++) { ((Rows === i || 1 === i) && (str += '-')) || i === half || (str += ' '); // 中间行k已到居中文本位置 i === half && ((blank <= k && k < blank + text.length - 2 && ((k = text.length + blank - 1), (str += text))) || (str += ' ')); } str += '|' + enter; } str.substr(0, str.length - 2); return str; }; const space = ' '; const enter = '\r\n'; let text = `\u200E ${howMuchSpace(3)}章节${howMuchSpace(6)}效率${howMuchSpace( 6, )}各效率${enter}`; for (const t of trList) { text += howMuchSpace(5); for (let b = 1; b < 9; b += 2) { const lent = t.childNodes[b].innerText.length; text += t.childNodes[b].innerText + howMuchSpace(10 - lent); } text = text.trim(); text += enter; } // 设置dom移除监听 负责在生成链接后设置粘贴板 vue.isLoading = true; const toClip=async () => { GM.setClipboard( `7天内打开链接,装备、角色数据完整保留,但将于${((d) => `${d.getMonth() + 1}月${d.getDate()}号`)( new Date(new Date().getTime() + 7 * 86400000), )}失效!${enter}请尽快打开链接:${surroundedByaBar( vue.exportNotice || 'network error,copy Text below', )}${enter}${howMuchSpace(4)}${enter}${howMuchSpace( 4, )}并点击储存队伍${enter}${enter}${enter}${howMuchSpace( 4, )}如果链接失效,可复制""(不含引号)内的字符"到文字汇入队伍的输入框${enter}"${vue.zipMyTeam()}"`, ); tips('备份成功', '复制完成,请尽快拷贝到其他地方保存'); }; backupTream(toClip); } function backupTream(callback) { const uuid = vue.uuid(); const armory = _.concat([vue.teamList], [vue.itemList]); let teamList = JSON.stringify(armory); teamList = vue.b64EncodeUnicode(teamList); const url = '/static/php/mysqlAdd.php'; const data = {teamList: teamList, uuid: uuid}; $.ajax({ type: 'POST', url: url, data: data, success: function(data) { const res = data.toString().trim(); if (res == '200') { vue.isLoading = false; vue.exportNotice = 'https://pcredivewiki.tw/Armory?s=' + uuid; callback(); } }, }); } async function clpSetImage() { vue.isLoading = true; await sleep(200); const shareContent = document.querySelector( 'table.table.table-bordered.mapDrop-table.helper tbody', ); const width = shareContent.offsetWidth; const height = shareContent.offsetHeight; const canvas = document.createElement('canvas'); const scale = 1; canvas.width = width * scale; canvas.height = height * scale; const content = canvas.getContext('2d'); content.scale(scale, scale); const opts = { scale: scale, width: width, height: height, canvas: canvas, allowTaint: true, width: width, heigth: height, y: window.pageYOffset + shareContent.getBoundingClientRect().top, }; // 局部元素带滚动时候 截图需要指定window的x,y 元素的getBoundingClientRect挺重要的 html2canvas(shareContent, opts).then((canvas) => { const content = canvas.getContext('2d'); content.mozImageSmoothingEnabled = false; content.webkitImageSmoothingEnabled = false; content.msImageSmoothingEnabled = false; content.ImgSmoothingEnabled = false; canvas.toBlob((blob) => { navigator.clipboard.write([new ClipboardItem({'image/png': blob})]); vue.isLoading = false; tips('图片复制成功', '复制完成,可直接复制到微信或qq。'); }); }); } const deleteItem = (switchOn) => { switchMultBtnState(`ready`, switchOn); for (const i of $('table .p-2.text-center.mapDrop-item.mr-2>div.helper--calc-r###lt-cell')) { (~~i.dataset.itemCount && switchOn && !!$(i).addClass('helper--show-deleted-btn')) || $(i).removeClass('helper--show-deleted-btn'); } !switchOn && multiSelectState(); }; const switchMultBtnState = (cls, switchOn = false) => { const state = ['ready', 'active', 'selected-completedBtn']; !switchOn && cls == state[1] && document.querySelector('a.singleSelect').classList.toggle(state[0], !switchOn); if ( !switchOn && cls == state[0] && !state.forEach((i) => { document.querySelector('span.switch-multiSelectBtnState').classList.toggle(i, switchOn); document.querySelector('span.switch-handler').classList.toggle(i, switchOn); document.querySelector('a.singleSelect').classList.toggle(i, switchOn); }) ) { return switchOn; } if ( !switchOn && cls == (state.shift() && state)[0] && !state.forEach((i) => { document.querySelector('span.switch-multiSelectBtnState').classList.toggle(i, switchOn); document.querySelector('span.switch-handler').classList.toggle(i, switchOn); }) ) { return switchOn; } document.querySelector('span.switch-multiSelectBtnState').classList.toggle(cls, switchOn); document.querySelector('span.switch-handler').classList.toggle(cls, switchOn); switchOn && cls == state[1] ? document.querySelector('a.singleSelect').classList.toggle(state[0], !switchOn) : document.querySelector('a.singleSelect').classList.toggle(state[0], switchOn); return switchOn; }; const multiSelectState = (switchOn = false) => { for (const i of $('table .p-2.text-center.mapDrop-item.mr-2>div.helper--calc-r###lt-cell')) { const c = ~~i.dataset.itemCount; c && i.classList.toggle('multiSelect-no', switchOn); c && !switchOn && i.classList.toggle('multiSelect-yes', switchOn); } }; const toDetailsTheMap = (map) => { const genUri = () => { /* 日后地图更新 打开https://pcredivewiki.tw/Map 打开控制台按下Exc 在console中输入 ` $$('.btn.btn-info.p-3') .map(el => (el.innerText.replace(/\d+\./,'')+'N')) .reduce((sum, value) =>{return sum .push(value),sum},[]).join('","') ` 不含反引号 输出后模仿格式(注意前后引号!!)复制到下面maps中 */ console.log(`如果地图更新的话看我,点右边的超链接`); const levelsForMapUir = new Map(); const maps = [ '朱諾平原N', '帕拉斯高原N', '赫柏丘陵N', '維斯塔溪谷N', '刻瑞斯森林N', '佛洛拉湖畔N', '墨提斯大瀑布N', '伊麗絲樹海N', '弗麗嘉雪原N', '洛麗泰海岸N', '蓋奴亞荒漠N', '波諾#亞砂丘N', '朵羅西亞溼地N', '尤金#亞熱地N', '塔利亞火山N', '泰美斯銀嶺N', '菲得斯冰原N', '法艾頓草原N', '法艾頓草原‧南部N', '卡斯塔利亞樹林‧西部N', '卡斯塔利亞樹林‧東部N', '馬提爾德岩峰‧南部N', '馬提爾德岩峰‧北部N', '雷蒂烏斯群峰‧西麓N', '雷蒂烏斯群峰‧東麓N', '佩特羅大森林‧西部N', '佩特羅大森林‧東部N', '迪茲塔爾河蝕岸‧北部N', '迪茲塔爾河蝕岸‧南部N', '弗泰拉斷崖‧北部N', '弗泰拉斷崖‧南部N', '法斯奇亞森林‧南部N', '法斯奇亞森林‧東部N', '迪克斯提亞岩崖‧西壁N', '迪克斯提亞岩崖‧東壁N', '維娜湖畔道‧南部N', '維娜湖畔道‧北部N', '拉圖斯斷崖‧南部N', '拉圖斯斷崖‧北部N', 'スカプ山系・西麓N', 'スカプ山系・東麓N', 'ペクトス氷峰・西壁N', 'ペクトス氷峰・東壁N', ]; let i = 1; for (const m of maps) { levelsForMapUir.set(i, `https://pcredivewiki.tw/Map/Detail/${encodeURI(m)}`); i += 1; } return levelsForMapUir; }; const mapIndex = map.split('-'); const p = mapIndex.shift() >> 0; const d = genUri(); (d.has(p) && GM.setValue(`toMap`, mapIndex.shift() >> 0) && unsafeWindow.open(d.get(p))) || alert( `地图可能更新了,请按下F12 ,再按下Esc,找到‘如果地图更新的话看我,点右边的超链接’字样,按提示修改脚本`, ); }; function genTable(mapData) { uniqueItem(mapData); const bouns = getBouns(); // const html = ` <table width="1000px" class="table table-bordered mapDrop-table helper"> <thead> <th style="min-width: 71px; vertical-align: baseline;cursor: pointer;" data-sort-type='0' title='点击可转换成贪心排序'>章节</th> <th style="min-width: 67px; vertical-align: baseline;">效率</th> <th style="min-width: 67px; vertical-align: baseline;">各次数</th> <th> 掉落一覽 </th> </thead> <tbody> ${mapData.map((m) => ` <tr data-is-unique-item=${(m.IsuniqueItem && 1) || 0}> <td class='r###lt-cell-td'> <a href="#" class="helper--nav-to-level ${m.IsuniqueItem && 'helper--important'}" data-pag:e="${m.page}" data-index="${m.index }" title="查看对手阵容"> ${m.name} </a> </td> <td data-drop-effective=${Math.round( m.effective * 100, )} class='r###lt-cell-td'> ${Math.round(m.effective * 100)}% </td> <td>适用<br/> ${Math.ceil(m.min / bouns)}<br/> 推荐<br/> ${Math.ceil(m.times / bouns)}<br/> 最大<br/> ${Math.ceil(m.max / bouns)} </td> <td align="center"> ${genItemsGroup(m.items)} </td> </tr> `).join('')} </tbody> </table> `.trim(); const table = $.parseHTML(html).pop(); // 0是一堆逗号,我也不造这是什么鬼 $(table) .find('a.helper--nav-to-level') .click(function(e) { const $this = $(e.currentTarget); // hideModal(); toDetailsTheMap($this.text()); /* setTimeout(() => { const $table = $(".mapDrop-table:not(.helper)"); const elem = $table.find("tr")[index]; elem.scrollIntoView({ behavior: "smooth", block: "center", inline: "center", }) }, 200) */ }); $(table) .find('.p-2.text-center.mapDrop-item.mr-2>div.helper--calc-r###lt-cell') .click(changeItemCount); const inputEntry = async (e) => { // 只有回车触发更改 if (e.type !== 'blur' && e.keyCode != 13) { return; } if (isWanted(e)) { const itemName = e.target.getAttribute('item-name'); const id = e.target.parentElement.parentElement.children[0].dataset.item_id; const newNum = e.srcElement.valueAsNumber; // 通过图书馆的快速修改功能来进行库存的修改 // const inputDoms=[...document.querySelectorAll(`#app table img[title="${itemName}"]`)] // const Refresh_the_interface = () => { // let i=inputDoms.shift().closest('div').querySelector('input') // i.valueAsNumber = newNum; // inputDoms.length&& // window.requestAnimationFrame(Refresh_the_interface)|| // i.dispatchEvent(new KeyboardEvent("keyup", { key: "Enter", keyCode: 13 })) // }; // window.requestAnimationFrame(Refresh_the_interface) // 卡顿原因自pcr的vue计算dom挤在一个宏任务了 无法优化 singleFragmentGlobalSave(id, newNum); e.target.classList.toggle('active', 1); // 在修改库存后,修改结果页的库存显示 // table.querySelectorAll(`input[item-name=${itemName}]`).forEach(dom => { // }) // 在输入掉落数时同步所有相同装备下的 input 的 value const c = [...table.querySelectorAll(`input[item-name=${itemName}]`)]; c.reduce((t, i) => { i.value = newNum; const itemSpanDom = i.closest('div').querySelector('span.text-center'); const totalNeed = itemSpanDom.getAttribute('data-total-need'); itemSpanDom.innerText = newNum < totalNeed ? `总需${totalNeed}` :(i.closest('div').classList.toggle('un--wanted', true), i.closest('div').querySelector('div').classList.toggle('un--wanted', true), '已满'); itemSpanDom.setAttribute('title', `有${newNum} 缺${Math.max(totalNeed - newNum, 0)}`); i.closest('div') .querySelector('img') .setAttribute('title', `有${newNum} 缺${Math.max(totalNeed - newNum, 0)}`); i.closest('div').querySelector('span.dropsProgress').innerText = `进度:${newNum}`; }, c[0]); } // 如有下个物品,跳转焦点 jump(e); }; const fnChanged = _.debounce(inputEntry, 300); table.querySelectorAll('input[item-name]').forEach((inputDom) => { inputDom.addEventListener('input', fnChanged); inputDom.addEventListener('keyup', fnChanged); inputDom.addEventListener('blur', inputEntry); }); const deltaInputEntry = async (e) => { // 只有回车触发更改 if (e.keyCode != 13) { return; } // 修改上方总数量并触发修改事件 -> delegate to inputEntry() const delta = +e.srcElement.value; const origInputDom = e.target.closest('div').querySelector('input[item-name]'); origInputDom.value = +origInputDom.value + delta; origInputDom.dispatchEvent(new KeyboardEvent('keyup', {key: 'Enter', keyCode: 13})); e.target.value = ''; }; table.querySelectorAll('input[orig-item-name]').forEach((inputDom) => { inputDom.addEventListener('input', _.debounce(deltaInputEntry, 500)); inputDom.addEventListener('keyup', _.debounce(deltaInputEntry, 500)); inputDom.addEventListener('blur', deltaInputEntry); }); const isWanted = (e) => !e.target .closest('div') .classList.contains('un--wanted'); const jump = (e) => { const nextItemDiv = e.target.closest('div').nextElementSibling && e.target .closest('div') .nextElementSibling.querySelector('div') .classList.contains('un--wanted') && e.target.closest('div').nextElementSibling; if (nextItemDiv) { nextItemDiv .querySelector('input[orig-item-name]') .dispatchEvent(new KeyboardEvent('keyup', {key: 'Enter', keyCode: 13})); } else { (e.target.closest('div').nextElementSibling && !e.target .closest('div') .nextElementSibling.querySelector('input[orig-item-name]') .focus()) || e.target.closest('tr').querySelector('div').querySelector('div:not(.un--wanted)').querySelector('input[orig-item-name]').focus(); } return nextItemDiv; }; return table; } function singleFragmentGlobalSave(id, newNum) { // 直接存到local,vue中关闭再处理 itemCountChage(id, newNum); onceItemchange.push({id: id, count: newNum}); } async function hideModal() { document.querySelector('#popBox.modal.fade.show') && document.querySelector('#popBox.modal.fade.show').click(); $('#helper--modal').css('opacity', 0); $('#helper--modal').css('pointer-events', 'none'); window.requestAnimationFrame(Refresh_the_interface); } const Refresh_the_interface = () => { const i = onceItemchange.shift(); i && vue.changeStock(i.count, i.id); onceItemchange.length && window.requestAnimationFrame(Refresh_the_interface); }; const TranslateRefresh_the_interface = () => { const i = onceItemchange.shift(); i && vue.changeStock(i.count, i.id); onceItemchange.length && TranslateRefresh_the_interface(); }; function showModal(...content) { $('#helper--modal').css('opacity', 1); $('#helper--modal').css('pointer-events', ''); if (content && content.length) { debugger; $('#helper--modal-content').html(content.join('')); } } async function showModalByDom(...dom) { $('#helper--modal').css('opacity', 1); $('#helper--modal').css('pointer-events', ''); if (dom.length) { $('#helper--modal-content').html(''); for (const i in dom) $('#helper--modal-content').append(dom[i]); } document .querySelector('table.table.table-bordered.mapDrop-table.helper th') .addEventListener(`click`, sortColumn); } async function handleClickCalcBtn() { await autoSwitch2MapList(); await sleep(300); saveTeamData(); // 自动调整至旧版数量 // const tempDom = document.querySelector('button[title="設計圖數量為舊版數量"]'); // if(![...tempDom.classList].includes('active')) // tempDom.click(); // await sleep(100); document.getElementById('helper--modal-content').classList.remove('helper--drop'); // if (selectNumInOnePage() != '1000') { // selectNumInOnePage(1000); // } await sleep(100); const data = await getMapData(); console.log('data', data); const r###lt = calcR###lt(data); console.log('r###lt', r###lt); showR###lt(r###lt); vue.pageSize = 10; vue.isLoading = false; document.querySelector('#popBox.modal.fade.show') && document.querySelector('#popBox.modal.fade.show').click(), changeBtnGroup(); } async function handleFastModifyBtn() { const $table = $('.mapDrop-table:not(.helper)'); if ($table && $table.find('thead button').length) { $table.find('thead button')[0].click(); } else { alert('现在还不是地图掉落页面呢~'); } } function btnFactory(content, colorRotate, onClick) { const btn = $.parseHTML(` <div class="armory-function" style="padding: 0; padding-top: 1vh; overflow: visible; filter: hue-rotate(${colorRotate}deg);"> <button class="pcbtn primary" style="border-radius: 50%;"> ${content} </button> </div> `); $(btn).click(onClick); return btn; } function tips(title, text) { vue.copyText('errrcolin'); vue.popMsg.title = title; vue.popMsg.content = text; const options = { attributes: true, attributeFilter: ['class'], }; $('div#popBox')[0].classList.toggle('topToView', 1); const mb = new MutationObserver(function(mutationRecord, observer) { if (mutationRecord[0].target.classList.contains('show')) return; observer.disconnect(); $('div#popBox')[0].classList.toggle('topToView', 0); }); mb.observe($('div#popBox')[0], options); $('div#popBox button').focus(); } function createBtnGroup() { const group = $.parseHTML(` <div id="helper--bottom-btn-group" class="scroll-fixed-bottom"></div> `); const fastModifyBtn = btnFactory('快速<br>修改', 270, handleFastModifyBtn); const bounsBtn = btnFactory('修改<br>倍数', 180, askBouns); const calcBtn = btnFactory('计算<br>结果', 90, handleClickCalcBtn); $(group).append(calcBtn); $(group).append(fastModifyBtn); $(group).append(bounsBtn); $('#app .container').append(group); } function changeBtnGroup() { const group = $('#helper--bottom-btn-group'); group.html(''); const fastModifyBtn = btnFactory('快速<br>修改', 188, handleFastModifyBtn); const bounsBtn = btnFactory('修改<br>倍数', 216, askBouns); const lastR###ltBtn = btnFactory('上次<br>结果', 144, () => showModal()); const calcBtn = btnFactory('重新<br>计算', 72, handleClickCalcBtn); group.append(calcBtn); group.append(bounsBtn); group.append(fastModifyBtn); group.append(lastR###ltBtn); } function appendName(mapName) { document.querySelector('nav.navbar.navbar-expand-md.navbar-dark.fixed-top').style.visibility = 'hidden'; // 隐藏导航条 document.querySelector('.main>div').__vue__.showMonster(); // 显示魔物 const toName = ( name, ElementFindByNameDotParent = document.querySelector('#H' + name.mapName).parentElement, ) => { ElementFindByNameDotParent.scrollIntoView({block: 'center'}), (ElementFindByNameDotParent.style.border = '3px solid #db1f77'); }; [...document.querySelectorAll('.item-title')].forEach( (ele) => (ele.id = 'H' + ele.outerText.split('-').pop()), ); // 添加id方便toName toName({mapName}); } createBtnGroup(); createModal(); (async () => { try { const before = await GM.getValue('mount', 0); const after = await GM.getValue('toMap', 0); before && eval(before); await sleep(2000); unsafeWindow.location.href.includes('https://pcredivewiki.tw/Map/Detail') && after && appendName(after); } catch (e) { console.log(`错误: ` + e); } finally { await GM.deleteValue('mount'); await GM.deleteValue('toMap'); } })(); }); })();