您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
咕咕镇数据采集,目前采集已关闭,兼作助手
当前为
// ==UserScript== // @name 咕咕镇数据采集 // @namespace https://greasyfork.org/users/448113 // @version 1.4.19 // @description 咕咕镇数据采集,目前采集已关闭,兼作助手 // @author paraii // @match https://www.guguzhen.com/* // @grant GM_xmlhttpRequest // @connect www.guguzhen.com // @run-at document-body // @license MIT License // ==/UserScript== // @connect notes.orga.cat // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/js/tooltip.js // @require https://cdn.jsdelivr.net/npm/[email protected]/js/popover.js (function() { 'use strict' //////////////////////////////////////////////////////////////////////////////////////////////////// // // common utilities // //////////////////////////////////////////////////////////////////////////////////////////////////// const g_modificationVersion = '2022-06-14 03:30:00'; const g_kfUser = document.getElementsByClassName('icon-user')[0].parentNode.innerText.split(' ')[1]; const g_autoTaskEnabledStorageKey = g_kfUser + '_autoTaskEnabled'; const g_keepPkRecordStorageKey = g_kfUser + '_keepPkRecord'; const g_amuletGroupsStorageKey = g_kfUser + '_amulet_groups'; const g_equipmentExpandStorageKey = g_kfUser + '_equipment_Expand'; const g_equipmentBGStorageKey = g_kfUser + '_equipment_BG'; const g_beachIgnoreStoreMysEquipStorageKey = g_kfUser + '_beach_ignoreStoreMysEquip'; const g_beachForceExpandStorageKey = g_kfUser + '_beach_forceExpand'; const g_beachBGStorageKey = g_kfUser + '_beach_BG'; const g_userDataStorageKeyConfig = [ g_kfUser, g_autoTaskEnabledStorageKey, g_keepPkRecordStorageKey, g_amuletGroupsStorageKey, g_equipmentExpandStorageKey, g_equipmentBGStorageKey, g_beachIgnoreStoreMysEquipStorageKey, g_beachForceExpandStorageKey, g_beachBGStorageKey ]; const g_userDataStorageKeyExtra = [ 'attribute', 'cardName', 'title', 'over', 'halo_max', 'beachcheck', 'dataReward', 'keepcheck' ]; const USER_STORAGE_RESERVED_SEPARATORS = /[:;,|=+*%!#$&?<>{}^`"\\\/\[\]\r\n\t\v\s]/; const USER_STORAGE_KEY_VALUE_SEPARATOR = ':'; console.log(g_kfUser) // perform a binary search. array must be sorted, but no matter in ascending or descending order. // in this manner, you must pass in a proper comparer function for it works properly, aka, if the // array was sorted in ascending order, then the comparer(a, b) should return a negative value // while a < b or a positive value while a > b; otherwise, if the array was sorted in descending // order, then the comparer(a, b) should return a positive value while a < b or a negative value // while a > b, and in both, if a equals b, the comparer(a, b) should return 0. if you pass nothing // or null / undefined value as comparer, then you must make sure about that the array was sorted // in ascending order. // // in this particular case, we just want to check whether the array contains the value or not, we // don't even need to point out the first place where the value appears (if the array actually // contains the value), so we perform a simplest binary search and return an index (may not the // first place where the value appears) or a negative value (means value not found) to indicate // the search result. function searchElement(array, value, fnComparer) { if (array?.length > 0) { fnComparer ??= ((a, b) => a < b ? -1 : (a > b ? 1 : 0)); let li = 0; let hi = array.length - 1; while (li <= hi) { let mi = ((li + hi) >> 1); let cr = fnComparer(value, array[mi]); if (cr == 0) { return mi; } else if (cr > 0) { li = mi + 1; } else { hi = mi - 1; } } } return -1; } // perform a binary insertion. the array and comparer must exactly satisfy as it in the searchElement // function. this operation behaves sort-stable, aka, the newer inserting element will be inserted // into the position after any existed equivalent elements. function insertElement(array, value, fnComparer) { if (array != null) { fnComparer ??= ((a, b) => a < b ? -1 : (a > b ? 1 : 0)); let li = 0; let hi = array.length - 1; while (li <= hi) { let mi = ((li + hi) >> 1); let cr = fnComparer(value, array[mi]); if (cr >= 0) { li = mi + 1; } else { hi = mi - 1; } } array.splice(li, 0, value); return li; } return -1; } // it's not necessary to have newArray been sorted, but the oldArray must be sorted since we are calling // searchElement. if there are some values should be ignored in newArray, the comparer(a, b) should be // implemented as return 0 whenever parameter a equals any of values that should be ignored. function findNewObjects(newArray, oldArray, fnComparer, findIndices) { // just in case, i discovered that sometimes if we use array.length directly in for(...) statement // (either some other statements too), the statement get chances to be executed incorrectly (or just // console.log can be effected?), don't know why, but we can use a temporary variable to handle this. let newObjects = []; let nl = (newArray?.length ?? 0); for (let i = 0; i < nl; i++) { if (searchElement(oldArray, newArray[i], fnComparer) < 0) { newObjects.push(findIndices ? i : newArray[i]); } } return newObjects; } function loadUserConfigData() { return JSON.parse(localStorage.getItem(g_kfUser)); } function saveUserConfigData(json) { localStorage.setItem(g_kfUser, JSON.stringify(json)); } function getPostData(functionPattern, dataPattern) { let data = null; let sc = document.getElementsByTagName('script'); for (let i = 0; i < sc.length; i++) { let func = sc[i].innerText.match(functionPattern); if (func != null) { data = func[0].match(dataPattern)[0]; break; } } return data; } // HTTP requests var g_httpRequests = []; function httpRequestRegister(request) { if (request != null) { g_httpRequests.push(request); } } function httpRequestAbortAll() { while (g_httpRequests.length > 0) { g_httpRequests.pop().abort(); } g_httpRequests = []; } function httpRequestClearAll() { g_httpRequests = []; } // read objects from bag and store with title filter const g_postMethod = 'POST' const g_readUrl = 'https://www.guguzhen.com/fyg_read.php' const g_postUrl = 'https://www.guguzhen.com/fyg_click.php' const g_postHeader = { 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8' , 'Cookie' : document.cookie }; const g_networkTimeoutMS = 120 * 1000; function beginReadObjects(bag, store, fnFurtherProcess, fnParams) { if (bag != null || store != null) { let request = GM_xmlhttpRequest({ method: g_postMethod, url: g_readUrl, headers: g_postHeader, data: 'f=7', onload: response => { let div = document.createElement('div'); div.innerHTML = response.responseText; if (bag != null) { bag.push(div.querySelectorAll('div.alert-danger > div.content > button.fyg_mp3')); } if (store != null) { store.push(div.querySelectorAll('div.alert-success > div.content > button.fyg_mp3')); } if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } }); httpRequestRegister(request); } else if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } function beginReadObjectIds(bagIds, storeIds, key, ignoreEmptyCell, fnFurtherProcess, fnParams) { function parseObjectIds() { if (bagIds != null) { objectIdParseNodes(bagIds.pop(), bagIds, key, ignoreEmptyCell); } if (storeIds != null) { objectIdParseNodes(storeIds.pop(), storeIds, key, ignoreEmptyCell); } if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } if (bagIds != null || storeIds != null) { beginReadObjects(bagIds, storeIds, parseObjectIds, null); } else if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } function objectIdParseNodes(nodes, ids, key, ignoreEmptyCell) { for (let node of nodes) { if (node.className?.endsWith('fyg_mp3')) { let id = node.getAttribute('onclick')?.match(/\d+/)[0]; if (id != undefined) { if (objectMatchTitle(node, key)) { ids.push(parseInt(id)); } } else if (!ignoreEmptyCell) { ids.push(-1); } } } } function objectMatchTitle(node, key){ return (!(key?.length > 0) || (node.getAttribute('data-original-title') ?? node.getAttribute('title'))?.indexOf(key) >= 0); } // we wait the response(s) of the previous batch of request(s) to send another batch of request(s) // rather than simply send them all within an inside foreach - which could cause too many requests // to server simultaneously, that can be easily treated as D.D.O.S attack and therefor leads server // to returns http status 503: Service Temporarily Unavailable // * caution * the parameter 'objects' is required been sorted by their indices in ascending order const g_ConcurrentRequestCount = { min : 1 , max : 8 , default : 4 }; const g_object_move_path = { bag2store : 0 , store2bag : 1 , bag2beach : 2 , beach2bag : 3 }; const g_object_move_data = [ null, null, null, null ]; var g_maxConcurrentRequests = g_ConcurrentRequestCount.default; var g_objectMoveRequestsCount = 0; var g_objectMoveTargetSiteFull = false; function beginMoveObjects(objects, path, fnFurtherProcess, fnParams) { if (!g_objectMoveTargetSiteFull && objects?.length > 0) { g_object_move_data[g_object_move_path.bag2store] ??= (getPostData(/puti\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? ''); g_object_move_data[g_object_move_path.store2bag] ??= (getPostData(/puto\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? ''); g_object_move_data[g_object_move_path.bag2beach] ??= (getPostData(/stdel\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? ''); g_object_move_data[g_object_move_path.beach2bag] ??= (getPostData(/stpick\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? ''); if (!(g_object_move_data[path]?.length > 0)) { if (!(g_object_move_data[g_object_move_path.store2bag]?.length > 0) && g_object_move_data[g_object_move_path.bag2store]?.length > 0) { g_object_move_data[g_object_move_path.store2bag] = g_object_move_data[g_object_move_path.bag2store].replace('c=21&', 'c=22&'); } if (!(g_object_move_data[g_object_move_path.bag2store]?.length > 0) && g_object_move_data[g_object_move_path.store2bag]?.length > 0) { g_object_move_data[g_object_move_path.bag2store] = g_object_move_data[g_object_move_path.store2bag].replace('c=22&', 'c=21&'); } if (!(g_object_move_data[g_object_move_path.bag2beach]?.length > 0) && g_object_move_data[g_object_move_path.beach2bag]?.length > 0) { g_object_move_data[g_object_move_path.bag2beach] = g_object_move_data[g_object_move_path.beach2bag].replace('c=1&', 'c=7&'); } if (!(g_object_move_data[g_object_move_path.beach2bag]?.length > 0) && g_object_move_data[g_object_move_path.bag2beach]?.length > 0) { g_object_move_data[g_object_move_path.beach2bag] = g_object_move_data[g_object_move_path.bag2beach].replace('c=7&', 'c=1&'); } } if (g_object_move_data[path].length > 0) { let ids = []; while (ids.length < g_maxConcurrentRequests && objects.length > 0) { let id = objects.pop(); if (id >= 0) { ids.push(id); } } if ((g_objectMoveRequestsCount = ids.length) > 0) { while (ids.length > 0) { let request = GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: g_object_move_data[path].replace('"+id+"', ids.shift()), onload: response => { if (response.responseText != 'ok') { g_objectMoveTargetSiteFull = true; console.log(response.responseText); } if (--g_objectMoveRequestsCount == 0) { beginMoveObjects(objects, path, fnFurtherProcess, fnParams); } } }); httpRequestRegister(request); } return; } } } g_objectMoveTargetSiteFull = false; if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } // read currently mounted role card and halo informations // roleInfo = [ roleId, roleName ] // haloInfo = [ haloPoints, haloSlots, [ haloItem1, haloItem2, … ] ] function beginReadRoleAndHalo(roleInfo, haloInfo, fnFurtherProcess, fnParams) { let asyncOperations = 0; let error = 0; let requestRole; let requestHalo; if (roleInfo != null) { asyncOperations++; requestRole = GM_xmlhttpRequest({ method: g_postMethod, url: g_readUrl, headers: g_postHeader, data: 'f=9', onload: response => { let div = document.createElement('div'); div.innerHTML = response.responseText; let role = g_roleMap.get(div.querySelector('div.text-info.fyg_f24.fyg_lh60')?.children[0]?.innerText); if (role != undefined) { roleInfo.push(role.id); roleInfo.push(role.name); } asyncOperations--; }, onerror : err => { error++; asyncOperations--; }, ontimeout : err => { error++; asyncOperations--; } }); } if (haloInfo != null) { asyncOperations++; requestHalo = GM_xmlhttpRequest({ method: g_postMethod, url: g_readUrl, headers: g_postHeader, data: 'f=5', onload: response => { let haloPS = response.responseText.match(/<h3>[^\d]*(\d+)[^\d]*(\d+)[^<]+<\/h3>/); if (haloPS?.length == 3) { haloInfo.push(parseInt(haloPS[1])); haloInfo.push(parseInt(haloPS[2])); } else { haloInfo.push(0); haloInfo.push(0); } let halo = []; for (let item of response.responseText.matchAll(/halotfzt2\((\d+)\)/g)) { halo.push(item[1]); } haloInfo.push(halo); asyncOperations--; }, onerror : err => { error++; asyncOperations--; }, ontimeout : err => { error++; asyncOperations--; } }); } let timeout = 0; let timer = setInterval(() => { if (asyncOperations == 0 || error > 0 || (++timeout * 200) >= g_networkTimeoutMS) { clearInterval(timer); if (asyncOperations > 0) { requestRole?.abort(); requestHalo?.abort(); } if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } }, 200); } //////////////////////////////////////////////////////////////////////////////////////////////////// // // amulet management // //////////////////////////////////////////////////////////////////////////////////////////////////// const AMULET_STORAGE_GROUP_SEPARATOR = '|'; const AMULET_STORAGE_GROUPNAME_SEPARATOR = '='; const AMULET_STORAGE_AMULET_SEPARATOR = ','; const AMULET_TYPE_ID_FACTOR = 100000000000000; const AMULET_LEVEL_ID_FACTOR = 10000000000000; const AMULET_ENHANCEMENT_FACTOR = 1000000000000; const AMULET_BUFF_MAX_FACTOR = AMULET_ENHANCEMENT_FACTOR; const g_amuletLevelIds = { 稀有 : 0, 史诗 : 1, 传奇 : 2 }; const g_amuletTypeIds = { 苹果 : 0, 葡萄 : 1, 樱桃 : 2 }; const g_amuletLevelNames = [ '稀有', '史诗', '传奇' ]; const g_amuletTypeNames = [ '苹果', '葡萄', '樱桃' ]; const g_amuletBuffUnits = [ '点', '%', '%' ]; const g_amuletBuffTypeNames = [ '力量', '敏捷', '智力', '体魄', '精神', '意志', '物理攻击', '魔法攻击', '速度', '生命护盾回复效果', '最大生命值', '最大护盾值', '固定生命偷取', '固定反伤', '固定暴击几率', '固定技能几率', '物理防御效果', '魔法防御效果' ]; const g_amuletBuffShortMarks = [ 'STR', 'AGI', 'INT', 'VIT', 'SPR', 'MND', 'PATK', 'MATK', 'SPD', 'REC', 'HP', 'SLD', 'LCH', 'RFL', 'CRT', 'SKL', 'PDEF', 'MDEF' ]; const g_amuletBuffTypeOrders = new Map(); g_amuletBuffTypeNames.forEach((item, index) => { g_amuletBuffTypeOrders.set(item, index); }); function Amulet() { this.id = -1; this.type = -1; this.level = 0; this.enhancement = 0; this.buffCode = 0; this.text = null; this.reset = (() => { this.id = -1; this.type = -1; this.level = 0; this.enhancement = 0; this.buffCode = 0; this.text = null; }); this.isValid = (() => { return (this.type >= 0 && this.type <= 2 && this.buffCode > 0); }); this.fromCode = ((code) => { if (!isNaN(code)) { this.type = Math.trunc(code / AMULET_TYPE_ID_FACTOR) % 10; this.level = Math.trunc(code / AMULET_LEVEL_ID_FACTOR) % 10; this.enhancement = Math.trunc(code / AMULET_ENHANCEMENT_FACTOR) % 10; this.buffCode = code % AMULET_BUFF_MAX_FACTOR; } else { this.reset(); } return (this.isValid() ? this : null); }); this.fromBuffText = ((text) => { if (text?.length > 0) { let nb = text.split(' = '); if (nb.length == 2) { this.id = -1; this.type = g_amuletTypeIds[nb[0].slice(2, 4)]; this.level = g_amuletLevelIds[nb[0].slice(0, 2)]; this.enhancement = parseInt(nb[0].match(/\d+/)[0]); this.buffCode = 0; nb[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(',').forEach((buff) => { let nv = buff.trim().split(' '); this.buffCode += ((100 ** (g_amuletBuffTypeOrders.get(nv[0]) % 6)) * nv[1]); }); if (this.isValid()) { this.text = nb[1]; return this; } } } this.reset(); return null; }); this.fromNode = ((node) => { if (node?.className?.endsWith('fyg_mp3') && node.innerText.indexOf('+') >= 0) { let id = node.getAttribute('onclick'); let typeName = (node.getAttribute('data-original-title') ?? node.getAttribute('title')); let content = node.getAttribute('data-content'); if (id != null && typeName?.length > 4 && content?.length > 0 && !isNaN(this.type = g_amuletTypeIds[typeName.slice(2, 4)]) && !isNaN(this.level = g_amuletLevelIds[typeName.slice(0, 2)]) && !isNaN(this.id = parseInt(id.match(/\d+/)[0])) && !isNaN(this.enhancement = parseInt(node.innerText.match(/\d+/)[0]))) { this.buffCode = 0; this.text = ''; let attr = null; let regex = /<p[^>]*>([^<]+)<[^>]*>\+(\d+)[^<]*<\/span><\/p>/g; while ((attr = regex.exec(content))?.length == 3) { this.buffCode += ((100 ** (g_amuletBuffTypeOrders.get(attr[1]) % 6)) * attr[2]); this.text += `${this.text.length > 0 ? ', ' : ''}${attr[1]} +${attr[2]} ${g_amuletBuffUnits[this.type]}`; } if (this.isValid()) { return this; } } } this.reset(); return null; }); this.fromAmulet = ((amulet) => { if (amulet?.isValid()) { this.id = amulet.id; this.type = amulet.type; this.level = amulet.level; this.enhancement = amulet.enhancement; this.buffCode = amulet.buffCode; this.text = amulet.text; } else { this.reset(); } return (this.isValid() ? this : null); }); this.getCode = (() => { if (this.isValid()) { return (this.type * AMULET_TYPE_ID_FACTOR + this.level * AMULET_LEVEL_ID_FACTOR + this.enhancement * AMULET_ENHANCEMENT_FACTOR + this.buffCode); } return -1; }); this.getBuff = (() => { let buffs = {}; if (this.isValid()) { let code = this.buffCode; let type = this.type * 6; g_amuletBuffTypeNames.slice(type, type + 6).forEach((buff) => { let v = (code % 100); if (v > 0) { buffs[buff] = v; } code = Math.trunc(code / 100); }); } return buffs; }); this.getTotalPoints = (() => { let points = 0; if (this.isValid()) { let code = this.buffCode; for(let i = 0; i < 6; i++) { points += (code % 100); code = Math.trunc(code / 100); } } return points; }); this.formatName = (() => { if (this.isValid()) { return `${g_amuletLevelNames[this.level]}${g_amuletTypeNames[this.type]} (+${this.enhancement})`; } return null; }); this.formatBuff = (() => { if (this.isValid()) { if (this.text?.length > 0) { return this.text; } this.text = ''; let buffs = this.getBuff(); for (let buff in buffs) { this.text += `${this.text.length > 0 ? ', ' : ''}${buff} +${buffs[buff]} ${g_amuletBuffUnits[this.type]}`; } } return this.text; }); this.formatBuffText = (() => { if (this.isValid()) { return this.formatName() + ' = ' + this.formatBuff(); } return null; }); this.formatShortMark = (() => { let text = this.formatBuff()?.replaceAll(/(\+)|( 点)|( %)/g, ''); if (text?.length > 0) { for (let buff in this.getBuff()) { text = text.replaceAll(buff, g_amuletBuffShortMarks[g_amuletBuffTypeOrders.get(buff)]); } return this.formatName() + ' = ' + text; } return null; }); this.compareTo = ((other, ascType) => { if (!this.isValid()) { return 1; } else if (!other?.isValid()) { return -1; } else if (this.id >= 0 && this.id == other.id) { return 0; } let delta = other.type - this.type; if (delta != 0) { return (ascType ? -delta : delta); } let tbuffs = this.formatBuffText().split(' = ')[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(', '); let obuffs = other.formatBuffText().split(' = ')[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(', '); let bl = Math.min(tbuffs.length, obuffs.length); for (let i = 0; i < bl; i++) { let tbuff = tbuffs[i].split(' '); let obuff = obuffs[i].split(' '); if ((delta = g_amuletBuffTypeOrders.get(tbuff[0]) - g_amuletBuffTypeOrders.get(obuff[0])) != 0 || (delta = parseInt(obuff[1]) - parseInt(tbuff[1])) != 0) { return delta; } } if ((delta = obuffs.length - tbuffs.length) != 0 || (delta = other.level - this.level) != 0 || (delta = other.enhancement - this.enhancement) != 0) { return delta; } return 0; }); } function AmuletGroup(persistenceString) { this.buffSummary = { 力量 : 0, 敏捷 : 0, 智力 : 0, 体魄 : 0, 精神 : 0, 意志 : 0, 物理攻击 : 0, 魔法攻击 : 0, 速度 : 0, 生命护盾回复效果 : 0, 最大生命值 : 0, 最大护盾值 : 0, 固定生命偷取 : 0, 固定反伤 : 0, 固定暴击几率 : 0, 固定技能几率 : 0, 物理防御效果 : 0, 魔法防御效果 : 0 }; this.name = null; this.items = []; this.isValid = (() => { return (this.items.length > 0 && amuletIsValidGroupName(this.name)); }); this.count = (() => { return this.items.length; }); this.clear = (() => { this.items = []; for (let buff in this.buffSummary) { this.buffSummary[buff] = 0; } }); this.add = ((amulet) => { if (amulet?.isValid()) { let buffs = amulet.getBuff(); for (let buff in buffs) { this.buffSummary[buff] += buffs[buff]; } return insertElement(this.items, amulet, (a, b) => a.compareTo(b, true)); } return -1; }); this.remove = ((amulet) => { if (this.isValid() && amulet?.isValid()) { let i = searchElement(this.items, amulet, (a, b) => a.compareTo(b, true)); if (i >= 0) { let buffs = amulet.getBuff(); for (let buff in buffs) { this.buffSummary[buff] -= buffs[buff]; } this.items.splice(i, 1); return true; } } return false; }); this.removeId = ((id) => { if (this.isValid()) { let i = this.items.findIndex((a) => a.id == id); if (i >= 0) { let amulet = this.items[i]; let buffs = amulet.getBuff(); for (let buff in buffs) { this.buffSummary[buff] -= buffs[buff]; } this.items.splice(i, 1); return amulet; } } return null; }); this.validate = ((amulets) => { if (this.isValid()) { let mismatch = 0; let al = this.items.length; let i = 0; if (amulets?.length > 0) { amulets = amulets.slice().sort((a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode); for ( ; amulets.length > 0 && i < al; i++) { let mi = searchElement(amulets, this.items[i], (a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode); if (mi >= 0) { // remove a matched amulet from the amulet pool can avoid one single amulet matches all // the equivalent objects in the group. // let's say two (or even more) AGI +5 apples in one group is fairly normal, if we just // have only one equivalent apple in the amulet pool and we don't remove it when the // first match happens, then the 2nd apple will get matched later, the consequence would // be we can never find the mismatch which should be encountered at the 2nd apple this.items[i].fromAmulet(amulets[mi]); amulets.splice(mi, 1); } else { mismatch++; } } } if (i > mismatch) { this.items.sort((a, b) => a.compareTo(b, true)); } if (i < al) { mismatch += (al - i); } return (mismatch == 0); } return false; }); this.findIndices = ((amulets) => { let indices; let al; if (this.isValid() && (al = (amulets?.length ?? 0)) > 0) { let items = this.items.slice().sort((a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode); for (let i = 0; items.length > 0 && i < al; i++) { let mi; if (amulets[i]?.id >= 0 && (mi = searchElement(items, amulets[i], (a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode)) >= 0) { // similar to the 'validate', remove the amulet from the search list when we found // a match item in first time to avoid the duplicate founding, e.g. say we need only // one AGI +5 apple in current group and we actually have 10 of AGI +5 apples in store, // if we found the first matched itme in store and record it's index but not remove it // from the temporary searching list, then we will continuously reach this kind of // founding and recording until all those 10 AGI +5 apples are matched and processed, // this obviously ain't the result what we expected (indices ??= []).push(i); items.splice(mi, 1); } } } return indices; }); this.parse = ((persistenceString) => { this.clear(); if (persistenceString?.length > 0) { let elements = persistenceString.split(AMULET_STORAGE_GROUPNAME_SEPARATOR); if (elements.length == 2) { let name = elements[0].trim(); if (amuletIsValidGroupName(name)) { let items = elements[1].split(AMULET_STORAGE_AMULET_SEPARATOR); let il = items.length; for (let i = 0; i < il; i++) { if (this.add((new Amulet()).fromCode(parseInt(items[i]))) < 0) { this.clear(); break; } } if (this.count() > 0) { this.name = name; } } } } return (this.count() > 0); }); this.formatBuffSummary = ((linePrefix, lineSuffix, lineSeparator) => { if (this.isValid()) { let str = ''; let nl = ''; g_amuletBuffTypeNames.forEach((buff) => { let v = this.buffSummary[buff]; if (v > 0) { str += `${nl}${linePrefix}${buff} +${v} ${g_amuletBuffUnits[Math.trunc(g_amuletBuffTypeOrders.get(buff) / 6)]}${lineSuffix}`; nl = lineSeparator; } }); return str; } return ''; }); this.formatItems = ((linePrefix, erroeLinePrefix, lineSuffix, errorLineSuffix, lineSeparator) => { if (this.isValid()) { let str = ''; let nl = ''; this.items.forEach((amulet) => { str += `${nl}${amulet.id < 0 ? erroeLinePrefix : linePrefix}${amulet.formatBuffText()}` + `${amulet.id < 0 ? errorLineSuffix : lineSuffix}`; nl = lineSeparator; }); return str; } return ''; }); this.getDisplayStringLineCount = (() => { if (this.isValid()) { let lines = 0; g_amuletBuffTypeNames.forEach((buff) => { if (this.buffSummary[buff] > 0) { lines++; } }); return lines + this.items.length; } return 0; }); this.formatPersistenceString = (() => { if (this.isValid()) { let codes = []; this.items.forEach((amulet) => { codes.push(amulet.getCode()); }); return `${this.name}${AMULET_STORAGE_GROUPNAME_SEPARATOR}${codes.join(AMULET_STORAGE_AMULET_SEPARATOR)}`; } return ''; }); this.parse(persistenceString); } function AmuletGroupCollection(persistenceString) { this.items = {}; this.itemCount = 0; this.count = (() => { return this.itemCount; }); this.contains = ((name) => { return (this.items[name] != undefined); }); this.add = ((item) => { if (item?.isValid()) { if (!this.contains(item.name)) { this.itemCount++; } this.items[item.name] = item; return true; } return false; }); this.remove = ((name) => { if (this.contains(name)) { delete this.items[name]; this.itemCount--; return true; } return false; }); this.clear = (() => { for (let name in this.items) { delete this.items[name]; } this.itemCount = 0; }); this.get = ((name) => { return this.items[name]; }); this.rename = ((oldName, newName) => { if (amuletIsValidGroupName(newName)) { let group = this.items[oldName]; if (this.remove(oldName)) { group.name = newName; return this.add(group); } } return false; }); this.toArray = (() => { let groups = []; for (let name in this.items) { groups.push(this.items[name]); } return groups; }); this.parse = ((persistenceString) => { this.clear(); if (persistenceString?.length > 0) { let groupStrings = persistenceString.split(AMULET_STORAGE_GROUP_SEPARATOR); let gl = groupStrings.length; for (let i = 0; i < gl; i++) { if (!this.add(new AmuletGroup(groupStrings[i]))) { this.clear(); break; } } } return (this.count() > 0); }); this.formatPersistenceString = (() => { let str = ''; let ns = ''; for (let name in this.items) { str += (ns + this.items[name].formatPersistenceString()); ns = AMULET_STORAGE_GROUP_SEPARATOR; } return str; }); this.parse(persistenceString); } function amuletIsValidGroupName(groupName) { return (groupName?.length > 0 && groupName.length < 32 && groupName.search(USER_STORAGE_RESERVED_SEPARATORS) < 0); } function amuletSaveGroups(groups) { if (groups?.count() > 0) { localStorage.setItem(g_amuletGroupsStorageKey, groups.formatPersistenceString()); } else { localStorage.removeItem(g_amuletGroupsStorageKey); } } function amuletLoadGroups() { return new AmuletGroupCollection(localStorage.getItem(g_amuletGroupsStorageKey)); } function amuletClearGroups() { localStorage.removeItem(g_amuletGroupsStorageKey); } function amuletSaveGroup(group) { if (group?.isValid()) { let groups = amuletLoadGroups(); if (groups.add(group)) { amuletSaveGroups(groups); } } } function amuletLoadGroup(groupName) { return amuletLoadGroups().get(groupName); } function amuletDeleteGroup(groupName) { let groups = amuletLoadGroups(); if (groups.remove(groupName)) { amuletSaveGroups(groups); } } function amuletCreateGroupFromArray(groupName, amulets) { if (amulets?.length > 0 && amuletIsValidGroupName(groupName)) { let group = new AmuletGroup(null); for (let amulet of amulets) { if (group.add(amulet) < 0) { group.clear(); break; } } if (group.count() > 0) { group.name = groupName; return group; } } return null; } function amuletNodesToArray(nodes, array, key) { if (array != null) { let amulet; for (let node of nodes) { if (objectMatchTitle(node, key) && (amulet ??= new Amulet()).fromNode(node)?.isValid()) { array.push(amulet); amulet = null; } } } return array; } function beginReadAmulets(bagAmulets, storeAmulets, key, fnFurtherProcess, fnParams) { function parseAmulets() { if (bagAmulets != null) { amuletNodesToArray(bagAmulets.pop(), bagAmulets, key); } if (storeAmulets != null) { amuletNodesToArray(storeAmulets.pop(), storeAmulets, key); } if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } if (bagAmulets != null || storeAmulets != null) { beginReadObjects(bagAmulets, storeAmulets, parseAmulets, null); } else if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } function beginMoveAmulets({ groupName, amulets, path, proc, params }) { let indices = amuletLoadGroup(groupName)?.findIndices(amulets)?.sort((a, b) => b - a); let ids; while (indices?.length > 0) { (ids ??= []).push(amulets[indices.pop()].id); } beginMoveObjects(ids, path, proc, params); } function beginLoadAmuletGroupFromStore(amulets, groupName, fnFurtherProcess, fnParams) { if (amulets?.length > 0) { let store = amuletNodesToArray(amulets, [], null); beginMoveAmulets({ groupName : groupName, amulets : store, path : g_object_move_path.store2bag, proc : fnFurtherProcess, params : fnParams }); } else { beginReadAmulets(null, amulets = [], null, beginMoveAmulets, { groupName : groupName, amulets : amulets, path : g_object_move_path.store2bag, proc : fnFurtherProcess, params : fnParams }); } } function beginUnloadAmuletGroupFromBag(amulets, groupName, fnFurtherProcess, fnParams) { if (amulets?.length > 0) { let bag = amuletNodesToArray(amulets, [], null); beginMoveAmulets({ groupName : groupName, amulets : bag, path : g_object_move_path.bag2store, proc : fnFurtherProcess, params : fnParams }); } else { beginReadAmulets(amulets, null, null, beginMoveAmulets, { groupName : groupName, amulets : amulets, path : g_object_move_path.bag2store, proc : fnFurtherProcess, params : fnParams }); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // // equipment utilities // //////////////////////////////////////////////////////////////////////////////////////////////////// // of cause we should calculate the property points to classify an equipment's quality level, // but for now let's get it work first ... const g_equipmentTipClassLevel = [ 'popover-primary', 'popover-info', 'popover-success', 'popover-warning', 'popover-danger' ]; function equipmentGetNodeLevel(node) { return g_equipmentTipClassLevel.indexOf(node?.getAttribute('data-tip-class')); } function equipmentInfoParseNode(node) { if (node?.className?.split(' ').length == 3 && node.innerText.indexOf('+') < 0) { let id = node.getAttribute('onclick')?.match(/\d+/)[0]; let attr = node.getAttribute('data-content')?.match(/>\s*\d+\s?%\s*</g); let title = (node.getAttribute('data-original-title') ?? node.getAttribute('title')); let lv = title?.match(/>(\d+)</); let name = title?.substring(title.lastIndexOf('>') + 1).trim(); name = (g_equipMap.get(name)?.shortMark ?? g_equipMap.get(name.substring(2))?.shortMark); let mys = (node.getAttribute('data-content')?.match(/\[神秘属性\]/) == null ? 0 : 1); if (attr?.length > 0 && title?.length > 1 && lv?.length > 0 && name?.length > 0) { return [ name, lv[1], attr[0].match(/\d+/)[0], attr[1].match(/\d+/)[0], attr[2].match(/\d+/)[0], attr[3].match(/\d+/)[0], mys, id ]; } } return null; } function equipmentNodesToInfoArray(nodes, array) { if (array != null) { let nl = (nodes?.length ?? 0); for (let i = 0; i < nl; i++) { let e = equipmentInfoParseNode(nodes[i]); if (e != null) { array.push(e); } } } return array; } function equipmentInfoComparer(e1, e2) { let delta = g_equipMap.get(e1[0]).index - g_equipMap.get(e2[0]).index; for (let i = 1; delta == 0 && i < 7; i++) { delta = parseInt(e1[i]) - parseInt(e2[i]); } return delta; } function objectNodeComparer(e1, e2) { let eq1 = equipmentInfoParseNode(e1); if (eq1 != null) { e1.setAttribute('data-meta-index', g_equipMap.get(eq1[0]).index); } let eq2 = equipmentInfoParseNode(e2); if (eq2 != null) { e2.setAttribute('data-meta-index', g_equipMap.get(eq2[0]).index); } if (eq1 == null && eq2 == null) { return ((new Amulet()).fromNode(e1)?.compareTo((new Amulet()).fromNode(e2)) ?? 1); } else if (eq1 == null) { return 1; } else if (eq2 == null) { return -1; } return equipmentInfoComparer(eq1, eq2); } //////////////////////////////////////////////////////////////////////////////////////////////////// // // bag & store utilities // //////////////////////////////////////////////////////////////////////////////////////////////////// function beginClearBag(bag, key, fnFurtherProcess, fnParams) { function beginClearBagObjects(objects) { beginMoveObjects(objects, g_object_move_path.bag2store, fnFurtherProcess, fnParams); } let objects = []; if (bag?.length > 0) { objectIdParseNodes(bag, objects, key, true); beginClearBagObjects(objects); } else { beginReadObjectIds(objects, null, key, true, beginClearBagObjects, objects); } } function beginRestoreObjects(store, amulets, equips, fnFurtherProcess, fnParams) { function readStoreCompletion() { beginRestoreObjects(store.pop(), amulets, equips, fnFurtherProcess, fnParams); } if (store == null) { beginReadObjects(null, store = [], readStoreCompletion, null); } else { let ids = []; if (amulets?.length > 0) { let ams = amuletNodesToArray(store, [], null); for (let i = ams.length - 1; i >= 0; i--) { for (let j = amulets.length - 1; j >= 0; j--) { if (ams[i].compareTo(amulets[j]) == 0) { amulets.splice(j, 1); ids.unshift(ams[i].id); break; } } } } if (equips?.length > 0) { let eqs = equipmentNodesToInfoArray(store, []); for (let i = eqs.length - 1; i >= 0; i--) { for (let j = equips.length - 1; j >= 0; j--) { if (equipmentInfoComparer(eqs[i], equips[j]) == 0) { equips.splice(j, 1); ids.unshift(parseInt(eqs[i][7])); break; } } } } beginMoveObjects(ids, g_object_move_path.store2bag, fnFurtherProcess, fnParams); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // // generic popups // //////////////////////////////////////////////////////////////////////////////////////////////////// const g_genericPopupContainerId = 'generic-popup-container'; const g_genericPopupClass = 'generic-popup'; const g_genericPopupId = g_genericPopupClass; const g_genericPopupContentContainerId = 'generic-popup-content-container'; const g_genericPopupContentClass = 'generic-popup-content'; const g_genericPopupContentId = g_genericPopupContentClass; const g_genericPopupFixedContentId = 'generic-popup-content-fixed'; const g_genericPopupInformationTipsId = 'generic-popup-information-tips'; const g_genericPopupProgressClass = g_genericPopupClass; const g_genericPopupProgressId = 'generic-popup-progress'; const g_genericPopupProgressContentClass = 'generic-popup-content-progress'; const g_genericPopupProgressContentId = g_genericPopupProgressContentClass; const g_genericPopupTopLineDivClass = 'generic-popup-top-line-container'; const g_genericPopupTitleTextClass = 'generic-popup-title-text'; const g_genericPopupTitleTextId = g_genericPopupTitleTextClass; const g_genericPopupTitleButtonContainerId = 'generic-popup-title-button-container'; const g_genericPopupFootButtonContainerId = 'generic-popup-foot-button-container'; const g_genericPopupBackgroundColor = '#ebf2f9'; const g_genericPopupBackgroundColorAlt = '#dbe2e9'; const g_genericPopupBorderColor = '#3280fc'; const g_genericPopupTitleTextColor = '#ffffff'; const g_genericPopupStyle = `<style> .${g_genericPopupClass} { width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, .5); position: fixed; left: 0; top: 0; bottom: 0; right: 0; z-index: 9999; display: none; justify-content: center; align-items: center; } .${g_genericPopupContentClass} { width: 100%; background-color: ${g_genericPopupBackgroundColor}; box-sizing: border-box; padding: 0px 30px; color: black; } .${g_genericPopupProgressContentClass} { width: 400px; height: 200px; background-color: ${g_genericPopupBackgroundColor}; box-sizing: border-box; border: 2px solid ${g_genericPopupBorderColor}; border-radius: 5px; display: table; } #${g_genericPopupProgressContentId} { height: 100%; width: 100%; color: #0000c0; font-size: 24px; font-weight: bold; display: table-cell; text-align: center; vertical-align: middle; } .${g_genericPopupTopLineDivClass} { width: 100%; padding: 20px 0px; border-top: 2px groove #d0d0d0; } .generic-popup-title-foot-container { width: 100%; height: 40px; background-color: ${g_genericPopupBorderColor}; padding: 0px 30px; display: table; } .${g_genericPopupTitleTextClass} { height: 100%; color: ${g_genericPopupTitleTextColor}; font-size: 18px; display: table-cell; text-align: left; vertical-align: middle; } </style>`; const g_genericPopupHTML = `${g_genericPopupStyle} <div class="${g_genericPopupClass}" id="${g_genericPopupId}"> <div style="border:2px solid ${g_genericPopupBorderColor};border-radius:5px;"> <div class="generic-popup-title-foot-container"> <span class="${g_genericPopupTitleTextClass}" id="${g_genericPopupTitleTextId}"></span> <div id="${g_genericPopupTitleButtonContainerId}" style="float:right;margin-top:6px;"></div> </div> <div id="${g_genericPopupContentContainerId}"> <div class="${g_genericPopupContentClass}" id="${g_genericPopupFixedContentId}" style="display:none;"></div> <div class="${g_genericPopupContentClass}" id="${g_genericPopupContentId}"></div> </div> <div class="generic-popup-title-foot-container"> <div id="${g_genericPopupFootButtonContainerId}" style="float:right;margin-top:8px;"></div> </div> </div> </div> <div class="${g_genericPopupProgressClass}" id="${g_genericPopupProgressId}"> <div class="${g_genericPopupProgressContentClass}"><span id="${g_genericPopupProgressContentId}"></span></div> </div>`; function genericPopupInitialize() { if (document.getElementById(g_genericPopupContainerId) == null) { let genericPopupContainer = document.createElement('div'); genericPopupContainer.id = g_genericPopupContainerId; genericPopupContainer.innerHTML = g_genericPopupHTML; document.body.appendChild(genericPopupContainer); } document.getElementById(g_genericPopupContainerId).innerHTML = g_genericPopupHTML; } function genericPopupReset() { let fixedContent = document.getElementById(g_genericPopupFixedContentId); fixedContent.style.display = 'none'; fixedContent.innerHTML = ''; document.getElementById(g_genericPopupTitleTextId).innerText = ''; document.getElementById(g_genericPopupContentId).innerHTML = ''; document.getElementById(g_genericPopupTitleButtonContainerId).innerHTML = ''; document.getElementById(g_genericPopupFootButtonContainerId).innerHTML = ''; } function genericPopupSetContent(title, content) { document.getElementById(g_genericPopupTitleTextId).innerText = title; document.getElementById(g_genericPopupContentId).innerHTML = content; } function genericPopupSetFixedContent(content) { let fixedContent = document.getElementById(g_genericPopupFixedContentId); fixedContent.style.display = 'block'; fixedContent.innerHTML = content; } function genericPopupAddButton(text, width, clickProc, addToTitle) { let btn = document.createElement('button'); btn.innerText = text; btn.onclick = clickProc; if (width != null && width > 0) { width = width.toString(); btn.style.width = width + (width.endsWith('px') || width.endsWith('%') ? '' : 'px'); } else { btn.style.width = 'auto'; } document.getElementById(addToTitle ? g_genericPopupTitleButtonContainerId : g_genericPopupFootButtonContainerId).appendChild(btn); return btn; } function genericPopupAddCloseButton(width, text, addToTitle) { return genericPopupAddButton(text?.length > 0 ? text : '关闭', width, (() => { genericPopupClose(true); }), addToTitle); } function genericPopupSetContentSize(height, width, scrollable) { height = (height?.toString() ?? '100%'); width = (width?.toString() ?? '100%'); document.getElementById(g_genericPopupContentContainerId).style.width = width + (width.endsWith('px') || width.endsWith('%') ? '' : 'px'); let content = document.getElementById(g_genericPopupContentId); content.style.height = height + (height.endsWith('px') || height.endsWith('%') ? '' : 'px'); content.style.overflow = (scrollable ? 'auto' : 'hidden'); } function genericPopupShowModal(clickOutsideToClose) { genericPopupClose(false); let popup = document.getElementById(g_genericPopupId); if (clickOutsideToClose) { popup.onclick = ((event) => { if (event.target == popup) { genericPopupClose(true); } }); } else { popup.onclick = null; } popup.style.display = "flex"; } function genericPopupClose(reset) { genericPopupCloseProgressMessage(); let popup = document.getElementById(g_genericPopupId); popup.style.display = "none"; if (reset) { genericPopupReset(); } httpRequestClearAll(); } let g_genericPopupInformationTipsTimer = null; function genericPopupShowInformationTips(msg, time) { if (g_genericPopupInformationTipsTimer != null) { clearTimeout(g_genericPopupInformationTipsTimer); g_genericPopupInformationTipsTimer = null; } let msgContainer = document.getElementById(g_genericPopupInformationTipsId); if (msgContainer != null) { msgContainer.innerText = (msg?.length > 0 ? `[ ${msg} ]` : ''); if ((time = parseInt(time)) > 0) { g_genericPopupInformationTipsTimer = setTimeout(() => { g_genericPopupInformationTipsTimer = null; msgContainer.innerText = ''; }, time); } } } function genericPopupShowProgressMessage(progressMessage) { genericPopupClose(false); document.getElementById(g_genericPopupProgressContentId).innerText = (progressMessage?.length > 0 ? progressMessage : '请稍候…'); document.getElementById(g_genericPopupProgressId).style.display = "flex"; } function genericPopupCloseProgressMessage() { document.getElementById(g_genericPopupProgressId).style.display = "none"; } // // generic task-list based progress popup // const g_genericPopupTaskListId = 'generic-popup-task-list'; const g_genericPopupTaskItemId = 'generic-popup-task-item-'; const g_genericPopupTaskWaiting = '×'; const g_genericPopupTaskCompleted = '√'; const g_genericPopupTaskCompletedWithError = '!'; const g_genericPopupColorTaskIncompleted = '#c00000'; const g_genericPopupColorTaskCompleted = '#0000c0'; const g_genericPopupColorTaskCompletedWithError = 'red'; var g_genericPopupIncompletedTaskCount = 0; function genericPopupTaskListPopupSetup(title, popupWidth, tasks, fnCancelRoutine, cancelButtonText, cancelButtonWidth) { g_genericPopupIncompletedTaskCount = tasks.length; genericPopupSetContent(title, `<div style="padding:15px 0px 15px 0px;"><ul id="${g_genericPopupTaskListId}"></ul></div>`); let indicatorList = document.getElementById(g_genericPopupTaskListId); for (let i = 0; i < g_genericPopupIncompletedTaskCount; i++) { let li = document.createElement('li'); li.id = g_genericPopupTaskItemId + i; li.style.color = g_genericPopupColorTaskIncompleted; li.innerHTML = `<span>${g_genericPopupTaskWaiting}</span><span> ${tasks[i]} </span><span></span>`; indicatorList.appendChild(li); } if (fnCancelRoutine != null) { genericPopupAddButton(cancelButtonText?.length > 0 ? cancelButtonText : '取消', cancelButtonWidth, fnCancelRoutine, false); } genericPopupSetContentSize(Math.min(g_genericPopupIncompletedTaskCount * 20 + 30, window.innerHeight - 400), popupWidth, true); } function genericPopupTaskSetState(index, state) { let item = document.getElementById(g_genericPopupTaskItemId + index)?.lastChild; if (item != undefined) { item.innerText = state; } } function genericPopupTaskComplete(index, error) { let li = document.getElementById(g_genericPopupTaskItemId + index); if (li?.firstChild?.innerText == g_genericPopupTaskWaiting) { li.firstChild.innerText = (error ? g_genericPopupTaskCompletedWithError : g_genericPopupTaskCompleted); li.style.color = (error ? g_genericPopupColorTaskCompletedWithError : g_genericPopupColorTaskCompleted); g_genericPopupIncompletedTaskCount--; } } function genericPopupTaskCheckCompletion() { return (g_genericPopupIncompletedTaskCount == 0); } //////////////////////////////////////////////////////////////////////////////////////////////////// // // constants // //////////////////////////////////////////////////////////////////////////////////////////////////// const g_roles = [ { index : -1 , id : 3000 , name : '舞' , shortMark : 'WU' }, { index : -1 , id : 3001 , name : '默' , shortMark : 'MO' }, { index : -1 , id : 3002 , name : '琳' , shortMark : 'LIN' }, { index : -1 , id : 3003 , name : '艾' , shortMark : 'AI' }, { index : -1 , id : 3004 , name : '梦' , shortMark : 'MENG' }, { index : -1 , id : 3005 , name : '薇' , shortMark : 'WEI' }, { index : -1 , id : 3006 , name : '伊' , shortMark : 'YI' }, { index : -1 , id : 3007 , name : '冥' , shortMark : 'MING' }, { index : -1 , id : 3008 , name : '命' , shortMark : 'MIN' }, { index : -1 , id : 3009 , name : '希' , shortMark : 'XI' } ]; const g_roleMap = new Map(); g_roles.forEach((item, index) => { item.index = index; g_roleMap.set(item.id, item); g_roleMap.set(item.id.toString(), item); g_roleMap.set(item.name, item); g_roleMap.set(item.shortMark, item); }); const g_equipAttributes = [ { index : -1 , type : 0 , name : '物理攻击' }, { index : -1 , type : 0 , name : '魔法攻击' }, { index : -1 , type : 0 , name : '攻击速度' }, { index : -1 , type : 0 , name : '最大生命' }, { index : -1 , type : 0 , name : '最大护盾' }, { index : -1 , type : 1 , name : '附加物伤' }, { index : -1 , type : 1 , name : '附加魔伤' }, { index : -1 , type : 1 , name : '附加攻速' }, { index : -1 , type : 1 , name : '附加生命' }, { index : -1 , type : 1 , name : '附加护盾' }, { index : -1 , type : 1 , name : '附加回血' }, { index : -1 , type : 1 , name : '附加回盾' }, { index : -1 , type : 0 , name : '护盾回复' }, { index : -1 , type : 0 , name : '物理穿透' }, { index : -1 , type : 0 , name : '魔法穿透' }, { index : -1 , type : 0 , name : '暴击穿透' }, { index : -1 , type : 1 , name : '附加物穿' }, { index : -1 , type : 1 , name : '附加物防' }, { index : -1 , type : 1 , name : '附加魔防' }, { index : -1 , type : 1 , name : '物理减伤' }, { index : -1 , type : 1 , name : '魔法减伤' }, { index : -1 , type : 0 , name : '生命偷取' }, { index : -1 , type : 0 , name : '伤害反弹' }, { index : -1 , type : 1 , name : '附加魔穿' } ]; g_equipAttributes.forEach((item, index) => { item.index = index; }); const g_equipments = [ { index : -1, name : '反叛者的刺杀弓', type : 0, attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 30 }, { attribute : g_equipAttributes[15] , factor : 1 / 20 , additive : 10 }, { attribute : g_equipAttributes[13] , factor : 1 / 20 , additive : 10 }, { attribute : g_equipAttributes[16] , factor : 1 , additive : 0 } ], merge : null, shortMark : 'ASSBOW' }, { index : -1, name : '狂信者的荣誉之刃', type : 0, attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 20 }, { attribute : g_equipAttributes[2] , factor : 1 / 5 , additive : 20 }, { attribute : g_equipAttributes[15] , factor : 1 / 20 , additive : 10 }, { attribute : g_equipAttributes[13] , factor : 1 / 20 , additive : 10 } ], merge : null, shortMark : 'BLADE' }, { index : -1, name : '陨铁重剑', type : 0, attributes : [ { attribute : g_equipAttributes[5] , factor : 20 , additive : 0 }, { attribute : g_equipAttributes[5] , factor : 20 , additive : 0 }, { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 30 }, { attribute : g_equipAttributes[15] , factor : 1 / 20 , additive : 1 } ], merge : [ [ 0, 1 ], [ 2 ], [ 3 ] ], shortMark : 'CLAYMORE' }, { index : -1, name : '幽梦匕首', type : 0, attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 0 }, { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 }, { attribute : g_equipAttributes[7] , factor : 4 , additive : 0 }, { attribute : g_equipAttributes[2] , factor : 1 / 5 , additive : 25 } ], merge : null, shortMark : 'DAGGER' }, { index : -1, name : '荆棘剑盾', type : 0, attributes : [ { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 }, { attribute : g_equipAttributes[22] , factor : 1 / 15 , additive : 0 }, { attribute : g_equipAttributes[17] , factor : 1 , additive : 0 }, { attribute : g_equipAttributes[18] , factor : 1 , additive : 0 } ], merge : null, shortMark : 'SHIELD' }, { index : -1, name : '饮血长枪', type : 0, attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 50 }, { attribute : g_equipAttributes[13] , factor : 1 / 20 , additive : 10 }, { attribute : g_equipAttributes[23] , factor : 3 , additive : 0 }, { attribute : g_equipAttributes[21] , factor : 1 / 15, additive : 10 } ], merge : null, shortMark : 'SPEAR' }, { index : -1, name : '光辉法杖', type : 0, attributes : [ { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 }, { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 }, { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 }, { attribute : g_equipAttributes[14] , factor : 1 / 20 , additive : 0 } ], merge : [ [ 0, 1, 2 ], [ 3 ] ], shortMark : 'WAND' }, { index : -1, name : '探险者短弓', type : 0, attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[6] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[7] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 } ], merge : null, shortMark : 'BOW' }, { index : -1, name : '探险者短杖', type : 0, attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[6] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[14] , factor : 1 / 20 , additive : 5 }, { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 } ], merge : null, shortMark : 'STAFF' }, { index : -1, name : '探险者之剑', type : 0, attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[6] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[16] , factor : 1 , additive : 0 }, { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 } ], merge : null, shortMark : 'SWORD' }, { index : -1, name : '命师的传承手环', type : 1, attributes : [ { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 1 }, { attribute : g_equipAttributes[14] , factor : 1 / 20 , additive : 1 }, { attribute : g_equipAttributes[9] , factor : 20 , additive : 0 }, { attribute : g_equipAttributes[18] , factor : 1 , additive : 0 } ], merge : null, shortMark : 'BRACELET' }, { index : -1, name : '秃鹫手套', type : 1, attributes : [ { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 5 }, { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 5 }, { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 5 }, { attribute : g_equipAttributes[7] , factor : 2 , additive : 0 } ], merge : [ [ 0, 1, 2 ], [ 3 ] ], shortMark : 'VULTURE' }, { index : -1, name : '探险者手套', type : 1, attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[6] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[7] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 } ], merge : null, shortMark : 'GLOVES' }, { index : -1, name : '旅法师的灵光袍', type : 2, attributes : [ { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[11] , factor : 30 , additive : 0 }, { attribute : g_equipAttributes[4] , factor : 1 / 5 , additive : 25 }, { attribute : g_equipAttributes[9] , factor : 50 , additive : 0 } ], merge : null, shortMark : 'CLOAK' }, { index : -1, name : '战线支撑者的荆棘重甲', type : 2, attributes : [ { attribute : g_equipAttributes[3] , factor : 1 / 5 , additive : 20 }, { attribute : g_equipAttributes[17] , factor : 1 , additive : 0 }, { attribute : g_equipAttributes[18] , factor : 1 , additive : 0 }, { attribute : g_equipAttributes[22] , factor : 1 / 15 , additive : 10 } ], merge : null, shortMark : 'THORN' }, { index : -1, name : '复苏木甲', type : 2, attributes : [ { attribute : g_equipAttributes[3] , factor : 1 / 5 , additive : 50 }, { attribute : g_equipAttributes[19] , factor : 5 , additive : 0 }, { attribute : g_equipAttributes[20] , factor : 5 , additive : 0 }, { attribute : g_equipAttributes[10] , factor : 20 , additive : 0 } ], merge : null, shortMark : 'WOODEN' }, { index : -1, name : '探险者铁甲', type : 2, attributes : [ { attribute : g_equipAttributes[8] , factor : 20 , additive : 0 }, { attribute : g_equipAttributes[17] , factor : 1 , additive : 0 }, { attribute : g_equipAttributes[18] , factor : 1 , additive : 0 }, { attribute : g_equipAttributes[10] , factor : 10 , additive : 0 } ], merge : null, shortMark : 'PLATE' }, { index : -1, name : '探险者皮甲', type : 2, attributes : [ { attribute : g_equipAttributes[8] , factor : 25 , additive : 0 }, { attribute : g_equipAttributes[19] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[20] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[10] , factor : 6 , additive : 0 } ], merge : null, shortMark : 'LEATHER' }, { index : -1, name : '探险者布甲', type : 2, attributes : [ { attribute : g_equipAttributes[8] , factor : 25 , additive : 0 }, { attribute : g_equipAttributes[19] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[20] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[10] , factor : 6 , additive : 0 } ], merge : null, shortMark : 'CLOTH' }, { index : -1, name : '天使缎带', type : 3, attributes : [ { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[9] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[10] , factor : 5 , additive : 0 }, { attribute : g_equipAttributes[12] , factor : 1 / 30 , additive : 0 } ], merge : null, shortMark : 'RIBBON' }, { index : -1, name : '占星师的发饰', type : 3, attributes : [ { attribute : g_equipAttributes[8] , factor : 5 , additive : 0 }, { attribute : g_equipAttributes[4] , factor : 1 / 5 , additive : 0 }, { attribute : g_equipAttributes[9] , factor : 20 , additive : 0 }, { attribute : g_equipAttributes[19] , factor : 2 , additive : 0 } ], merge : null, shortMark : 'TIARA' }, { index : -1, name : '探险者头巾', type : 3, attributes : [ { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 }, { attribute : g_equipAttributes[19] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[20] , factor : 2 , additive : 0 }, { attribute : g_equipAttributes[10] , factor : 4 , additive : 0 } ], merge : null, shortMark : 'SCARF' }]; const g_defaultEquipAttributeMerge = [ [0], [1], [2], [3] ]; function defaultobjectNodeComparer(setting, eqKey, eq1, eq2) { let eqMeta = g_equipMap.get(eqKey); let delta = []; let minorAdv = 0; let majorDis = 0; eqMeta.attributes.forEach((item, index) => { let d = Math.trunc((eq1[0] * item.factor + item.additive) * eq1[index + 1]) - Math.trunc((eq2[0] * item.factor + item.additive) * eq2[index + 1]); if (setting[index + 1]) { delta.push(0); if (d > 0) { minorAdv++; } } else { delta.push(d); } }); let merge = (eqMeta.merge?.length > 1 ? eqMeta.merge : g_defaultEquipAttributeMerge); for (let indices of merge) { let sum = 0; indices.forEach((index) => { sum += delta[index]; }); if (sum > 0) { return true; } else if (sum < 0) { majorDis++; } }; return (majorDis == 0 && minorAdv > 0); } const g_equipMap = new Map(); g_equipments.forEach((item, index) => { item.index = index; g_equipMap.set(item.name, item); g_equipMap.set(item.shortMark, item); }); const g_halos = [ { index : -1 , id : 101 , name : '启程之誓' , points : 10 , shortMark : 'SHI' }, { index : -1 , id : 102 , name : '启程之心' , points : 10 , shortMark : 'XIN' }, { index : -1 , id : 103 , name : '启程之风' , points : 10 , shortMark : 'FENG' }, { index : -1 , id : 201 , name : '破壁之心' , points : 30 , shortMark : 'BI' }, { index : -1 , id : 202 , name : '破魔之心' , points : 30 , shortMark : 'MO' }, { index : -1 , id : 203 , name : '复合护盾' , points : 30 , shortMark : 'DUN' }, { index : -1 , id : 204 , name : '鲜血渴望' , points : 30 , shortMark : 'XUE' }, { index : -1 , id : 205 , name : '削骨之痛' , points : 30 , shortMark : 'XIAO' }, { index : -1 , id : 206 , name : '圣盾祝福' , points : 30 , shortMark : 'SHENG' }, { index : -1 , id : 301 , name : '伤口恶化' , points : 50 , shortMark : 'SHANG' }, { index : -1 , id : 302 , name : '精神创伤' , points : 50 , shortMark : 'SHEN' }, { index : -1 , id : 303 , name : '铁甲尖刺' , points : 50 , shortMark : 'CI' }, { index : -1 , id : 304 , name : '忍无可忍' , points : 50 , shortMark : 'REN' }, { index : -1 , id : 305 , name : '热血战魂' , points : 50 , shortMark : 'RE' }, { index : -1 , id : 306 , name : '点到为止' , points : 50 , shortMark : 'DIAN' }, { index : -1 , id : 401 , name : '沸血之志' , points : 100 , shortMark : 'FEI' }, { index : -1 , id : 402 , name : '波澜不惊' , points : 100 , shortMark : 'BO' }, { index : -1 , id : 403 , name : '飓风之力' , points : 100 , shortMark : 'JU' }, { index : -1 , id : 404 , name : '红蓝双刺' , points : 100 , shortMark : 'HONG' }, { index : -1 , id : 405 , name : '荧光护盾' , points : 100 , shortMark : 'JUE' }, { index : -1 , id : 406 , name : '后发制人' , points : 100 , shortMark : 'HOU' } ]; const g_haloMap = new Map(); g_halos.forEach((item, index) => { item.index = index; g_haloMap.set(item.id, item); g_haloMap.set(item.id.toString(), item); g_haloMap.set(item.name, item); g_haloMap.set(item.shortMark, item); }); const g_configs = [ { index : -1, id : 'maxConcurrentRequests', name : `最大并发网络请求(${g_ConcurrentRequestCount.min} - ${g_ConcurrentRequestCount.max})`, defaultValue : g_ConcurrentRequestCount.default, value : g_ConcurrentRequestCount.default, tips : '同时向服务器提交的请求的最大数量。过高的设置容易引起服务阻塞或被认定为DDOS攻击从而导致服务器停止服务(HTTP 503)。', validate : ((value) => { return (!isNaN(value = parseInt(value)) && value >= g_ConcurrentRequestCount.min && value <= g_ConcurrentRequestCount.max); }), onchange : ((value) => { if (!isNaN(value = parseInt(value)) && value >= g_ConcurrentRequestCount.min && value <= g_ConcurrentRequestCount.max) { return (g_maxConcurrentRequests = value); } return (g_maxConcurrentRequests = g_ConcurrentRequestCount.default); }) }, { index : -1, id : 'minBeachEquipLevelToAmulet', name : `海滩稀有装备转护符最小等级`, defaultValue : 1, value : 1, tips : '海滩稀有装备批量转换护符时所需达到的最小等级,小于此等级的装备不会被转换,但玩家依然可以选择手动熔炼。史诗和传奇装备则肯定会被自动转换。', validate : ((value) => { return /^\s*\d{1,3}\s*$/.test(value); }), onchange : ((value) => { if (/^\s*\d{1,3}\s*$/.test(value)) { return parseInt(value); } return 1; }) }, { index : -1, id : 'minBeachAmuletPointsToStore', name : `海滩转护符默认入仓最小加成(苹果,葡萄,樱桃)`, defaultValue : '1, 1%, 1%', value : '1, 1%, 1%', tips : '海滩装备批量转换护符时默认处于入仓列表的最小加成(‘%’可省略)。此设置仅为程序产生分类列表时作为参考,玩家可通过双击特定护符移动它的位置。', validate : ((value) => { return /^\s*\d+\s*,\s*\d+\s*%?\s*,\s*\d+\s*%?\s*$/.test(value); }), onchange : ((value) => { if (/^\s*\d+\s*,\s*\d+\s*%?\s*,\s*\d+\s*%?\s*$/.test(value)) { return value; } return '1, 1%, 1%'; }) } ]; const g_configMap = new Map(); g_configs.forEach((item, index) => { item.index = index; g_configMap.set(item.id, item); }); function initiatizeConfig() { let udata = loadUserConfigData(); if (udata == null) { udata = { dataIndex : { battleInfoNow : '' , battleInfoBefore : '' , battleInfoBack : '' }, dataBeachSift : {}, dataBind : {}, config : {} }; } if (udata.dataIndex == null) { udata.dataIndex = { battleInfoNow : '' , battleInfoBefore : '' , battleInfoBack : '' }; } if (udata.dataBeachSift == null) { udata.dataBeachSift = {}; } if (udata.dataBind == null) { udata.dataBind = {}; } if (udata.config == null) { udata.config = {}; } for (let key in udata.dataBeachSift) { if (g_equipMap.get(key) == undefined) { delete udata.dataBeachSift[key]; } } for (let key in udata.dataBind) { if (g_roleMap.get(key) == undefined) { delete udata.dataBind[key]; } } for (let key in udata.config) { if (g_configMap.get(key) == undefined) { delete udata.config[key]; } } g_configs.forEach((item) => { item.value = (item.onchange?.call(null, udata.config[item.id] ?? item.defaultValue)); }); saveUserConfigData(udata); } function wishExpireTip() { GM_xmlhttpRequest({ method: g_postMethod, url: g_readUrl, headers: g_postHeader, data: `f=19`, onload: response => { let points = response.responseText.split('#')[1]; if (parseInt(points) < 2) { let navBar = document.querySelector('.nav.navbar-nav'); for (let nav of navBar.children) { if (nav.firstChild.innerHTML.indexOf('许愿池') >= 0) { nav.firstChild.innerHTML = nav.firstChild.innerHTML.replace('许愿池', '许愿池(已过期)'); nav.firstChild.style.color = 'white'; nav.firstChild.style.backgroundColor = 'red'; break; } } } } }); } initiatizeConfig(); wishExpireTip(); //////////////////////////////////////////////////////////////////////////////////////////////////// // // page add-ins // //////////////////////////////////////////////////////////////////////////////////////////////////// if (window.location.pathname == '/fyg_index.php') { genericPopupInitialize(); let userData = loadUserConfigData(); let dataIndex = userData.dataIndex; let waitForCol = setInterval(() => { let colmd4 = document.getElementsByClassName('col-md-4'); if (colmd4?.length > 0 && colmd4[0]?.children?.length > 5) { clearInterval(waitForCol); let px = colmd4[0].children[5]; let p0 = document.createElement(px.tagName); p0.className = px.className; p0.innerText = '对玩家战斗(上次查看)'; let sp = document.createElement(px.children[0].tagName); sp.className = px.children[0].className; dataIndex.battleInfoNow = px.children[0].innerText; if (dataIndex.battleInfoNow == dataIndex.battleInfoBefore) { sp.innerText = dataIndex.battleInfoBack; } else { sp.innerText = dataIndex.battleInfoBefore; dataIndex.battleInfoBack = dataIndex.battleInfoBefore; dataIndex.battleInfoBefore = dataIndex.battleInfoNow saveUserConfigData(userData); } p0.appendChild(sp); colmd4[0].appendChild(p0); } }, 200); function doConfig() { let fixedContent = '<div style="padding:20px 10px 10px 0px;color:blue;font-size:15px;"><b>请勿随意修改配置项,' + `除非您知道它的准确用途并且设置为正确的值,否则可能会导致插件工作异常。<span id="${g_genericPopupInformationTipsId}" ` + 'style="float:right;color:red;"></span></b></div>'; let mainContent = `<style> #config-table { width:100%; } #config-table tr.config-tr { } #config-table tr.config-tr-alt { background-color:${g_genericPopupBackgroundColorAlt}; } #config-table th { width:20%; } #config-table th.config-th-name { width:60%; } #config-table th.config-th-button { width:20%; } #config-table button.config-restore-value { width:50%; } </style> <div class="${g_genericPopupTopLineDivClass}"><table id="config-table"> <tr><th class="config-th-name">配置项</th><th>值</th><th class="config-th-button"></th></tr></table><div>`; genericPopupSetFixedContent(fixedContent); genericPopupSetContent('插件设置', mainContent); let configTable = document.getElementById('config-table'); g_configs.forEach((item, index) => { let tr = document.createElement('tr'); tr.className = 'config-tr' + ((index & 1) == 0 ? ' config-tr-alt' : ''); tr.setAttribute('config-item', item.id); tr.innerHTML = `<td><div data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="${item.tips}">${item.name}<div></td> <td><div data-toggle="popover" data-placement="bottom" data-trigger="hover" data-content="${item.tips}"> <input type="text" style="display:inline-block;width:100%;" value="${item.value}" /><div></td> <td><button type="button" class="config-restore-value" title="重置为当前配置" value="${item.value}">当前</button>` + `<button type="button" class="config-restore-value" title="重置为默认配置" value="${item.defaultValue}">默认</button></td>`; tr.children[1].children[0].children[0].oninput = tr.children[1].children[0].children[0].onchange = validateInput; configTable.appendChild(tr); }); function validateInput(e) { let tr = e.target.parentNode.parentNode.parentNode; let cfg = g_configMap.get(tr.getAttribute('config-item')); tr.style.color = ((cfg.validate?.call(null, e.target.value) ?? true) ? 'black' : 'red'); } configTable.querySelectorAll('button.config-restore-value').forEach((btn) => { btn.onclick = restoreValue; }); function restoreValue(e) { let input = e.target.parentNode.parentNode.children[1].children[0].children[0]; input.value = e.target.value; input.oninput({ target : input }); genericPopupShowInformationTips('配置项已' + e.target.title, 5000); } $('#config-table div[data-toggle="popover"]').popover(); genericPopupAddButton('重置为当前配置', 0, restoreValueAll, true).setAttribute('config-restore-default-all', 0); genericPopupAddButton('重置为默认配置', 0, restoreValueAll, true).setAttribute('config-restore-default-all', 1); function restoreValueAll(e) { let defaultValue = (e.target.getAttribute('config-restore-default-all') == '1'); configTable.querySelectorAll('tr.config-tr').forEach((row) => { let id = row.getAttribute('config-item'); let cfg = g_configMap.get(id); let input = row.children[1].children[0].children[0]; input.value = (defaultValue ? cfg.defaultValue : (cfg.value ?? cfg.defaultValue)); input.oninput({ target : input }); }); genericPopupShowInformationTips('全部配置项已' + e.target.innerText, 5000); } genericPopupAddButton('保存', 80, saveConfig, false).setAttribute('config-save-config', 1); genericPopupAddButton('确认', 80, saveConfig, false).setAttribute('config-save-config', 0); function saveConfig(e) { let close = (e.target.getAttribute('config-save-config') == '0'); let udata = loadUserConfigData(); let config = (udata?.config ?? {}); let error = []; configTable.querySelectorAll('tr.config-tr').forEach((row) => { let id = row.getAttribute('config-item'); let cfg = g_configMap.get(id); let value = row.children[1].children[0].children[0].value; if (cfg.validate?.call(null, value) ?? true) { config[id] = cfg.value = row.children[2].children[0].value = (cfg.onchange?.call(null, value) ?? value); } else { error.push(cfg.name); } }); udata.config = config; saveUserConfigData(udata); if (error.length > 0) { alert('以下配置项输入内容有误,如有必要请重新设置:\n\n [ ' + error.join(' ]\n [ ') + ' ]'); } else if (close) { genericPopupClose(true); } else { genericPopupShowInformationTips('配置已保存', 5000); } } genericPopupAddCloseButton(80); genericPopupSetContentSize(Math.min(g_configs.length * 28 + 60, Math.max(window.innerHeight - 200, 400)), Math.min(600, Math.max(window.innerWidth - 100, 600)), true); genericPopupShowModal(true); } const USER_DATA_xPORT_SEPARATOR = '\n'; function importUserConfigData() { genericPopupSetContent( '导入内容', `<b><div style="color:#0000c0;padding:15px 0px 10px;"> 请将从其它系统中使用同一帐号导出的内容填入文本框中并执行导入操作</div></b> <div style="height:330px;"><textarea id="user_data_persistence_string" style="height:100%;width:100%;resize:none;"></textarea></div>`); genericPopupAddButton( '执行导入', 0, (() => { let userData = document.getElementById('user_data_persistence_string').value.split(USER_DATA_xPORT_SEPARATOR); if (userData.length > 0) { if (confirm('导入操作会覆盖已有的用户配置(护符组定义、卡片装备光环护符绑定、海滩装备筛选配置等等),要继续吗?')) { let backup = []; let importedItems = []; let illegalItems = []; g_userDataStorageKeyConfig.forEach((item, index) => { backup[index] = localStorage.getItem(item); }); userData.forEach((item) => { if ((item = item.trim()).length > 0) { let key = item.slice(0, item.indexOf(USER_STORAGE_KEY_VALUE_SEPARATOR)); if (g_userDataStorageKeyConfig.indexOf(key) >= 0) { if (illegalItems.length == 0) { localStorage.setItem(key, item.substring(key.length + 1)); importedItems.push(key); } } else { illegalItems.push(key); } } }); if (illegalItems.length > 0) { importedItems.forEach((item) => { let index = g_userDataStorageKeyConfig.indexOf(item); if (index >= 0 && backup[index] != null) { localStorage.setItem(item, backup[index]); } else { localStorage.removeItem(item); } }); alert('输入内容格式有误,有非法项目导致导入失败,请检查:\n\n [ ' + illegalItems.join(' ]\n [ ') + ' ]'); } else if (importedItems.length > 0) { alert('导入已完成:\n\n [ ' + importedItems.join(' ]\n [ ') + ' ]'); genericPopupClose(true); window.location.reload(); } else { alert('输入内容格式有误,导入失败,请检查!'); } } } else { alert('输入内容格式有误,导入失败,请检查!'); } }), true); genericPopupAddCloseButton(80); genericPopupSetContentSize(400, 600, false); genericPopupShowModal(true); } function exportUserConfigData() { genericPopupSetContent( '导出内容', `<b><div id="user_data_export_tip" style="color:#0000c0;padding:15px 0px 10px;"> 请勿修改任何导出内容,将其保存为纯文本在其它系统中使用相同的帐号执行导入操作</div></b> <div style="height:330px;"><textarea id="user_data_persistence_string" readonly="true" style="height:100%;width:100%;resize:none;"></textarea></div>`); genericPopupAddButton( '复制导出内容至剪贴板', 0, ((e) => { e.target.disabled = 'disabled'; let tipContainer = document.getElementById('user_data_export_tip'); let tipColor = tipContainer.style.color; let tipString = tipContainer.innerText; tipContainer.style.color = '#ff0000'; document.querySelector('#user_data_persistence_string').select(); if (document.execCommand('copy')) { tipContainer.innerText = '导出内容已复制到剪贴板'; } else { tipContainer.innerText = '复制失败,这可能是因为浏览器没有剪贴板访问权限,请进行手工复制(CTRL+A, CTRL+C)'; } setTimeout((() => { tipContainer.style.color = tipColor; tipContainer.innerText = tipString; e.target.disabled = ''; }), 3000); }), true); genericPopupAddCloseButton(80); let userData = []; g_userDataStorageKeyConfig.forEach((item) => { let value = localStorage.getItem(item); if (value != null) { userData.push(`${item}${USER_STORAGE_KEY_VALUE_SEPARATOR}${value}`); } }); document.getElementById('user_data_persistence_string').value = userData.join(USER_DATA_xPORT_SEPARATOR); genericPopupSetContentSize(400, 600, false); genericPopupShowModal(true); } function clearUserData() { if (confirm('这将清除所有用户配置(护符组定义、卡片装备光环护符绑定、海滩装备筛选配置等等)和数据,要继续吗?')) { g_userDataStorageKeyConfig.concat(g_userDataStorageKeyExtra).forEach((item) => { localStorage.removeItem(item); }); alert('用户配置和数据已全部清除!'); window.location.reload(); } } let waitForUserd = setInterval(() => { let userd = document.getElementById('userd'); if (userd?.children?.length > 0) { clearInterval(waitForUserd); let globalDataBtnContainer = document.createElement(userd.tagName); globalDataBtnContainer.id = 'global-data-button-container'; globalDataBtnContainer.className = userd.className; globalDataBtnContainer.style.borderTop = '2px solid #d0d0d0'; let versionLabel = document.createElement(userd.firstChild.tagName); versionLabel.innerText = '插件版本:'; versionLabel.className = userd.firstChild.className; let versionText = document.createElement(userd.firstChild.children[0].tagName); versionText.className = userd.firstChild.children[0].className; versionText.innerText = g_modificationVersion; versionLabel.appendChild(versionText); globalDataBtnContainer.appendChild(versionLabel); let configBtn = document.createElement('button'); configBtn.innerHTML = '设置'; configBtn.style.height = '35px'; configBtn.style.width = '100%'; configBtn.style.marginBottom = '1px'; configBtn.onclick = (() => { doConfig(); }); globalDataBtnContainer.appendChild(configBtn); let importBtn = document.createElement('button'); importBtn.innerHTML = '导入用户配置数据'; importBtn.style.height = '35px'; importBtn.style.width = '100%'; importBtn.style.marginBottom = '1px'; importBtn.onclick = (() => { importUserConfigData(); }); globalDataBtnContainer.appendChild(importBtn); let exportBtn = document.createElement('button'); exportBtn.innerHTML = '导出用户配置数据'; exportBtn.style.height = '35px'; exportBtn.style.width = '100%'; exportBtn.style.marginBottom = '1px'; exportBtn.onclick = (() => { exportUserConfigData(); }); globalDataBtnContainer.appendChild(exportBtn); let eraseBtn = document.createElement('button'); eraseBtn.innerHTML = '清除用户数据'; eraseBtn.style.height = '35px'; eraseBtn.style.width = '100%'; eraseBtn.style.marginBottom = '1px'; eraseBtn.onclick = (() => { clearUserData(); }); globalDataBtnContainer.appendChild(eraseBtn); userd.parentNode.appendChild(globalDataBtnContainer); } }, 200); } else if (window.location.pathname == '/fyg_equip.php') { genericPopupInitialize(); let waitForBackpacks = setInterval(() => { if (document.getElementById('backpacks')?.children?.length > 0) { clearInterval(waitForBackpacks); let panel = document.getElementsByClassName('panel panel-primary')[1]; let calcBtn = document.createElement('button'); let calcDiv = document.createElement('div'); calcBtn.innerText = '导出计算器'; calcBtn.onclick = (() => {}); panel.insertBefore(calcBtn, panel.children[0]); panel.insertBefore(calcDiv, calcBtn); const storeQueryString = '#backpacks > div.alert.alert-success.with-icon'; const storeButtonId = 'collapse-backpacks-store'; let equipmentDiv = document.createElement('div'); equipmentDiv.id = 'equipmentDiv'; equipmentDiv.innerHTML = `<p><div style="padding:0px 0px 10px 30px;float:right;"> <label for="equipment_Expand" style="margin-right:5px;cursor:pointer;">全部展开</label> <input type="checkbox" id="equipment_Expand" /></div> <div style="padding:0px 0px 10px 15px;float:right;"> <label for="equipment_BG" style="margin-right:5px;cursor:pointer;">使用深色背景</label> <input type="checkbox" id="equipment_BG" /></div></p> <p><button type="button" class="btn btn-block collapsed" data-toggle="collapse" data-target="#eq4">护符 ▼</button></p> <div class="in" id="eq4"></div> <p><button type="button" class="btn btn-block collapsed" data-toggle="collapse" data-target="#eq0">武器装备 ▼</button></p> <div class="in" id="eq0"></div> <p><button type="button" class="btn btn-block collapsed" data-toggle="collapse" data-target="#eq1">手臂装备 ▼</button></p> <div class="in" id="eq1"></div> <p><button type="button" class="btn btn-block collapsed" data-toggle="collapse" data-target="#eq2">身体装备 ▼</button></p> <div class="in" id="eq2"></div> <p><button type="button" class="btn btn-block collapsed" data-toggle="collapse" data-target="#eq3">头部装备 ▼</button></p> <div class="in" id="eq3"></div> <p><button type="button" class="btn btn-block collapsed" id="${storeButtonId}">仓库 ▼</button></p>`; let forceEquipDivOperation = true; let equipDivExpanded = {}; equipmentDiv.querySelectorAll('.btn.btn-block.collapsed').forEach((btn) => { btn.onclick = backupEquipmentDivState; }); function backupEquipmentDivState(e) { let targetDiv = equipmentDiv.querySelector(e.target.getAttribute('data-target')); if (targetDiv != null) { equipDivExpanded[targetDiv.id] = !equipDivExpanded[targetDiv.id]; } else { equipDivExpanded[e.target.id] = !equipDivExpanded[e.target.id]; } }; function collapseEquipmentDiv(expand, force) { let targetDiv; equipmentDiv.querySelectorAll('.btn.btn-block').forEach((btn) => { if (btn.getAttribute('data-toggle') == 'collapse' && (targetDiv = equipmentDiv.querySelector(btn.getAttribute('data-target'))) != null) { let exp = expand; if (equipDivExpanded[targetDiv.id] == undefined || force) { equipDivExpanded[targetDiv.id] = exp; } else { exp = equipDivExpanded[targetDiv.id]; } targetDiv.className = (exp ? 'in' : 'collapse'); targetDiv.style.height = (exp ? 'auto' : '0px'); } }); if (equipDivExpanded[storeButtonId] == undefined || force) { equipDivExpanded[storeButtonId] = expand; } if (equipDivExpanded[storeButtonId]) { $(storeQueryString).show(); } else { $(storeQueryString).hide(); } } function changeEquipmentDivStyle(bg) { $('#equipmentDiv .backpackDiv').css({ 'background-color': bg ? 'black' : '#ffe5e0' }); $('#equipmentDiv .storeDiv').css({ 'background-color': bg ? 'black' : '#ddf4df' }); $('#equipmentDiv .btn-light').css({ 'background-color': bg ? 'black' : 'white' }); $('#equipmentDiv .popover-content-show').css({ 'background-color': bg ? 'black' : 'white' }); $('#equipmentDiv .popover-title').css({ 'color': bg ? 'black' : 'white' }); $('#equipmentDiv .bg-special').css({ 'background-color': bg ? 'black' : '#8666b8', 'color': bg ? '#c0c0c0' : 'white', 'border-bottom': bg ? '1px solid grey' : 'none' }); $('#equipmentDiv .btn-equipment .pull-right').css({ 'color': bg ? 'black' : 'white' }); $('#equipmentDiv .btn-equipment .bg-danger.with-padding').css({ 'color': bg ? 'black' : 'white' }); } let equipmentExpand = equipmentDiv.querySelector('#equipment_Expand').checked = (localStorage.getItem(g_equipmentExpandStorageKey) == 'true'); equipmentDiv.querySelector('#equipment_Expand').onchange = (() => { localStorage.setItem(g_equipmentExpandStorageKey, equipmentExpand = document.querySelector('#equipment_Expand').checked); collapseEquipmentDiv(equipmentExpand, true); }); let equipmentBG = equipmentDiv.querySelector('#equipment_BG').checked = (localStorage.getItem(g_equipmentBGStorageKey) == 'true'); equipmentDiv.querySelector('#equipment_BG').onchange = (() => { localStorage.setItem(g_equipmentBGStorageKey, equipmentBG = document.querySelector('#equipment_BG').checked); changeEquipmentDivStyle(equipmentBG); }); function addCollapse() { let waitForBtn = setInterval(() => { if (document.getElementById('carding')?.innerText?.indexOf('读取中') < 0 && document.getElementById('backpacks')?.innerText?.indexOf('读取中') < 0) { let eqbtns = document.querySelectorAll('#carding > div.row > div.fyg_tc > button.fyg_mp3'); if (eqbtns?.length > 0) { clearInterval(waitForBtn); let eqstore = document.querySelectorAll('#backpacks > div.alert-success > div.content > button.fyg_mp3'); eqstore.forEach((item) => { if (item.className.split(' ').length == 3) { item.dataset.instore = 1; } }); eqbtns = Array.from(eqbtns).concat( Array.from(document.querySelectorAll('#backpacks > div.alert-danger > div.content > button.fyg_mp3')) .sort(objectNodeComparer)).concat( Array.from(eqstore).sort(objectNodeComparer)); for (let i = eqbtns.length - 1; i >= 0; i--) { if (eqbtns[i].className.split(' ').length != 3) { eqbtns.splice(i, 1); } } if (!(document.getElementsByClassName('collapsed')?.length > 0)) { document.getElementById('backpacks') .insertBefore(equipmentDiv, document.getElementById('backpacks').firstChild.nextSibling); } for (let i = eqbtns.length - 1; i >= 0; i--) { if (eqbtns[i].className.split(' ')[0] == 'popover') { eqbtns.splice(i, 1); break; } } let ineqBackpackDiv = `<div class="backpackDiv" style="padding:10px;margin-bottom:10px;"></div>` + `<div class="storeDiv" style="padding:10px;"></div>`; let eqDivs = [ equipmentDiv.querySelector('#eq0'), equipmentDiv.querySelector('#eq1'), equipmentDiv.querySelector('#eq2'), equipmentDiv.querySelector('#eq3'), equipmentDiv.querySelector('#eq4') ]; eqDivs.forEach((item) => { item.innerHTML = ineqBackpackDiv; }); let ineq = 0; eqbtns.forEach((btn) => { if (btn.innerText == '空') { return; } let btn0 = document.createElement('button'); btn0.className = 'btn btn-light'; btn0.style.minWidth = '200px'; btn0.style.marginRight = '5px'; btn0.style.marginBottom = '5px'; btn0.style.padding = '0px'; btn0.style.textAlign = 'left'; btn0.style.boxShadow = 'none'; btn0.style.lineHeight = '150%'; btn0.style.borderColor = getComputedStyle(btn).getPropertyValue('background-color'); btn0.setAttribute('onclick', btn.getAttribute('onclick')); let storeText = ''; if (btn.dataset.instore == 1) { storeText = '【仓】'; } let enhancements = btn.innerText; if (enhancements.indexOf('+') == -1) { enhancements = ''; } btn0.innerHTML = `<h3 class="popover-title" style="color:white;background-color: ${btn0.style.borderColor}"> ${storeText}${btn.dataset.originalTitle}${enhancements}</h3> <div class="popover-content-show" style="padding:10px 10px 0px 10px;">${btn.dataset.content}</div>`; if (btn0.children[1].lastChild.nodeType == 3) { //清除背景介绍文本 btn0.children[1].lastChild.remove(); } if (btn.innerText.indexOf('+') >= 0) { ineq = 4; } else { let a = g_equipments[parseInt(btn.dataset.metaIndex)]; if (a == null) { let e = equipmentInfoParseNode(btn); a = (e != null ? g_equipMap.get(e[0]) : null); } if ((ineq = (a?.type ?? 4)) < 4) { btn0.className += ' btn-equipment'; } } (storeText == '' ? eqDivs[ineq].firstChild : eqDivs[ineq].firstChild.nextSibling).appendChild(btn0); }); function inputAmuletGroupName(defaultGroupName) { let groupName = prompt('请输入护符组名称(不超过31个字符,请仅使用大、小写英文字母、数字、连字符、下划线及中文字符):', defaultGroupName); if (amuletIsValidGroupName(groupName)) { return groupName; } else if (groupName != null) { alert('名称不符合命名规则,信息未保存。'); } return null; } function refreshEquipmentPage(fnFurtherProcess) { let asyncOperations = 1; let asyncObserver = new MutationObserver(() => { asyncObserver.disconnect(); asyncOperations = 0; }); asyncObserver.observe(document.getElementById('backpacks'), { childList : true , subtree : true }); // refresh #carding & #backpacks cding(); eqbp(1); let timer = setInterval(() => { if (asyncOperations == 0) { clearInterval(timer); genericPopupClose(true); if (fnFurtherProcess != null) { fnFurtherProcess(); } } }, 200); } function queryAmulets(bag, store, key) { let count = 0; if (bag != null) { amuletNodesToArray( document.querySelectorAll('#backpacks > div.alert-danger > div.content > button.fyg_mp3'), bag, key); count += bag.length; } if (store != null) { amuletNodesToArray( document.querySelectorAll('#backpacks > div.alert-success > div.content > button.fyg_mp3'), store, key); count += store.length; } return count; } function showAmuletGroupsPopup() { function beginSaveBagAsGroup(groupName, update) { let amulets = []; queryAmulets(amulets, null, null); createAmuletGroup(groupName, amulets, update); showAmuletGroupsPopup(); } genericPopupClose(true); let bag = []; let store = []; if (queryAmulets(bag, store, null) == 0) { alert('护符信息加载异常,请检查!'); refreshEquipmentPage(null); return; } let amulets = bag.concat(store); let bagGroup = amuletCreateGroupFromArray('当前背包', bag); let groups = amuletLoadGroups(); if (bagGroup == null && groups.count() == 0) { alert('背包为空,且未找到预保存的护符组信息!'); return; } genericPopupSetContent('护符组管理', '<div id="popup_amulet_groups" style="margin-top:15px;"></div>'); let amuletContainer = document.getElementById('popup_amulet_groups'); if (bagGroup != null) { let err = !bagGroup.validate(bag); let groupDiv = document.createElement('div'); groupDiv.className = g_genericPopupTopLineDivClass; groupDiv.id = 'popup_amulet_group_bag'; groupDiv.innerHTML = `<b id="popup_amulet_group_bag_name" style="color:${err ? "red" : "blue"}; font-size:20px;">当前背包内容 [${bagGroup.count()}]</b>`; g_amuletTypeNames.slice().reverse().forEach((item) => { let btn = document.createElement('button'); btn.innerText = '清空' + item; btn.style.float = 'right'; btn.setAttribute('amulet-key', item); btn.onclick = clearSpecAmulet; groupDiv.appendChild(btn); }); function clearSpecAmulet(e) { genericPopupShowProgressMessage('处理中,请稍候…'); beginClearBag( document.querySelectorAll('#backpacks > div.alert-danger > div.content > button.fyg_mp3'), e.target.getAttribute('amulet-key'), refreshEquipmentPage, showAmuletGroupsPopup); } let saveBagGroupBtn = document.createElement('button'); saveBagGroupBtn.innerText = '保存为护符组'; saveBagGroupBtn.style.float = 'right'; saveBagGroupBtn.onclick = (() => { let groupName = inputAmuletGroupName(''); if (groupName != null) { beginSaveBagAsGroup(groupName, false); } }); groupDiv.appendChild(saveBagGroupBtn); let groupInfoDiv = document.createElement('div'); groupInfoDiv.innerHTML = `<hr><ul style="color:#000080;">${bagGroup.formatBuffSummary('<li>', '</li>', '')}</ul> <hr><ul>${bagGroup.formatItems('<li>', '<li style="color:red;">', '</li>', '</li>', '')}<ul>`; groupDiv.appendChild(groupInfoDiv); amuletContainer.appendChild(groupDiv); } let li = 0 let groupArray = groups.toArray(); let gl = (groupArray?.length ?? 0); if (gl > 0) { groupArray = groupArray.sort((a, b) => a.name < b.name ? -1 : 1); for (let i = 0; i < gl; i++) { let err = !groupArray[i].validate(amulets); let groupDiv = document.createElement('div'); groupDiv.className = g_genericPopupTopLineDivClass; groupDiv.id = 'popup_amulet_group_' + i; groupDiv.innerHTML = `<b id="popup_amulet_group_${i}_name" style="color:${err ? "red" : "blue"}; font-size:20px;">${groupArray[i].name} [${groupArray[i].count()}]</b>`; let amuletDeleteGroupBtn = document.createElement('button'); amuletDeleteGroupBtn.innerText = '删除'; amuletDeleteGroupBtn.style.float = 'right'; amuletDeleteGroupBtn.onclick = (() => { let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; if (confirm(`删除护符组 "${groupName}" 吗?`)) { amuletDeleteGroup(groupName); showAmuletGroupsPopup(); } }); groupDiv.appendChild(amuletDeleteGroupBtn); let amuletModifyGroupBtn = document.createElement('button'); amuletModifyGroupBtn.innerText = '编辑'; amuletModifyGroupBtn.style.float = 'right'; amuletModifyGroupBtn.onclick = (() => { let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; modifyAmuletGroup(groupName); }); groupDiv.appendChild(amuletModifyGroupBtn); let importAmuletGroupBtn = document.createElement('button'); importAmuletGroupBtn.innerText = '导入'; importAmuletGroupBtn.style.float = 'right'; importAmuletGroupBtn.onclick = (() => { let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; let persistenceString = prompt('请输入护符组编码(一般由工具软件自动生成,表现形式为一组由逗号分隔的数字序列)'); if (persistenceString != null) { let group = new AmuletGroup(`${groupName}${AMULET_STORAGE_GROUPNAME_SEPARATOR}${persistenceString}`); if (group.isValid()) { let groups = amuletLoadGroups(); if (groups.add(group)) { amuletSaveGroups(groups); showAmuletGroupsPopup(); } else { alert('保存失败!'); } } else { alert('输入的护符组编码无效,请检查!'); } } }); groupDiv.appendChild(importAmuletGroupBtn); let renameAmuletGroupBtn = document.createElement('button'); renameAmuletGroupBtn.innerText = '更名'; renameAmuletGroupBtn.style.float = 'right'; renameAmuletGroupBtn.onclick = (() => { let oldName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; let groupName = inputAmuletGroupName(oldName); if (groupName != null && groupName != oldName) { let groups = amuletLoadGroups(); if (!groups.contains(groupName) || confirm(`护符组 "${groupName}" 已存在,要覆盖吗?`)) { if (groups.rename(oldName, groupName)) { amuletSaveGroups(groups); showAmuletGroupsPopup(); } else { alert('更名失败!'); } } } }); groupDiv.appendChild(renameAmuletGroupBtn); let updateAmuletGroupBtn = document.createElement('button'); updateAmuletGroupBtn.innerText = '更新'; updateAmuletGroupBtn.style.float = 'right'; updateAmuletGroupBtn.onclick = (() => { let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; if (confirm(`用当前背包内容替换 "${groupName}" 护符组预定内容吗?`)) { beginSaveBagAsGroup(groupName, true); } }); groupDiv.appendChild(updateAmuletGroupBtn); let unamuletLoadGroupBtn = document.createElement('button'); unamuletLoadGroupBtn.innerText = '入仓'; unamuletLoadGroupBtn.style.float = 'right'; unamuletLoadGroupBtn.onclick = (() => { let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; genericPopupShowProgressMessage('卸载中,请稍候…'); beginUnloadAmuletGroupFromBag( document.querySelectorAll('#backpacks > div.alert-danger > div.content > button.fyg_mp3'), groupName, refreshEquipmentPage, showAmuletGroupsPopup); }); groupDiv.appendChild(unamuletLoadGroupBtn); let amuletLoadGroupBtn = document.createElement('button'); amuletLoadGroupBtn.innerText = '装备'; amuletLoadGroupBtn.style.float = 'right'; amuletLoadGroupBtn.onclick = (() => { let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0]; genericPopupShowProgressMessage('加载中,请稍候…'); beginLoadAmuletGroupFromStore( document.querySelectorAll('#backpacks > div.alert-success > div.content > button.fyg_mp3'), groupName, refreshEquipmentPage, showAmuletGroupsPopup); }); groupDiv.appendChild(amuletLoadGroupBtn); let groupInfoDiv = document.createElement('div'); groupInfoDiv.innerHTML = `<hr><ul style="color:#000080;">${groupArray[i].formatBuffSummary('<li>', '</li>', '')}</ul> <hr><ul>${groupArray[i].formatItems('<li>', '<li style="color:red;">', '</li>', '</li>', '')}<ul>`; groupDiv.appendChild(groupInfoDiv); amuletContainer.appendChild(groupDiv); li += groupArray[i].getDisplayStringLineCount(); } } if (bagGroup != null) { gl++; li += bagGroup.getDisplayStringLineCount(); } genericPopupAddButton('新建护符组', 0, modifyAmuletGroup, true); genericPopupAddButton( '导入新护符组', 0, (() => { let groupName = inputAmuletGroupName(''); if (groupName != null) { let persistenceString = prompt('请输入护符组编码(一般由工具软件自动生成,表现形式为一组由逗号分隔的数字序列)'); if (persistenceString != null) { let group = new AmuletGroup(`${groupName}${AMULET_STORAGE_GROUPNAME_SEPARATOR}${persistenceString}`); if (group.isValid()) { let groups = amuletLoadGroups(); if (!groups.contains(groupName) || confirm(`护符组 "${groupName}" 已存在,要覆盖吗?`)) { if (groups.add(group)) { amuletSaveGroups(groups); showAmuletGroupsPopup(); } else { alert('保存失败!'); } } } else { alert('输入的护符组编码无效,请检查!'); } } } }), true); genericPopupAddButton( '清空背包', 0, (() => { genericPopupShowProgressMessage('处理中,请稍候…'); beginClearBag( document.querySelectorAll('#backpacks > div.alert-danger > div.content > button.fyg_mp3'), null, refreshEquipmentPage, showAmuletGroupsPopup); }), true); genericPopupAddCloseButton(80); genericPopupSetContentSize(Math.min((li * 20) + (gl * 160) + 60, Math.max(window.innerHeight - 200, 400)), Math.min(1000, Math.max(window.innerWidth - 100, 600)), true); genericPopupShowModal(true); } function modifyAmuletGroup(groupName) { function divHeightAdjustment(div) { div.style.height = (div.parentNode.offsetHeight - div.offsetTop - 3) + 'px'; } function refreshAmuletList() { let type = amuletFilterList.value; amuletList.innerHTML = ''; amulets.forEach((am) => { if (type == -1 || am.type == type) { let item = document.createElement('li'); item.setAttribute('original-id', am.id); item.innerText = am.formatBuffText(); amuletList.appendChild(item); } }); } function refreshGroupAmuletSummary() { let count = group.count(); if (count > 0) { groupSummary.innerHTML = group.formatBuffSummary('<li>', '</li>', ''); groupSummary.style.display = 'block'; } else { groupSummary.style.display = 'none'; groupSummary.innerHTML = ''; } divHeightAdjustment(groupAmuletList.parentNode); amuletCount.innerText = count; } function refreshGroupAmuletList() { groupAmuletList.innerHTML = ''; group.items.forEach((am) => { if (am.id >= 0) { let item = document.createElement('li'); item.setAttribute('original-id', am.id); item.innerText = am.formatBuffText(); groupAmuletList.appendChild(item); } }); } function refreshGroupAmuletDiv() { refreshGroupAmuletSummary(); refreshGroupAmuletList(); } function moveAmuletItem(e) { let li = e.target; if (li.tagName == 'LI') { let from = li.parentNode; let id = li.getAttribute('original-id'); from.removeChild(li); if (from == amuletList) { let i = searchElement(amulets, id, (a, b) => a - b.id); let am = amulets[i]; amulets.splice(i, 1); groupAmuletList.insertBefore(li, groupAmuletList.children.item(group.add(am))); } else { let am = group.removeId(id); insertElement(amulets, am, (a, b) => a.id - b.id); let type = amuletFilterList.value; if (type < 0 || am.type == type) { for (var item = amuletList.firstChild; parseInt(item?.getAttribute('original-id')) <= am.id; item = item.nextSibling); amuletList.insertBefore(li, item); } } refreshGroupAmuletSummary(); groupChanged = true; } } let bag = []; let store = []; if (queryAmulets(bag, store, null) == 0) { alert('获取护符信息失败,请检查!'); return; } let amulets = bag.concat(store).sort((a, b) => a.compareTo(b)); amulets.forEach((item, index) => { item.id = index; }); let displayName = groupName; if (!amuletIsValidGroupName(displayName)) { displayName = '(未命名)'; groupName = null; } else if (displayName.length > 20) { displayName = displayName.slice(0, 19) + '…'; } let groupChanged = false; let group = amuletLoadGroup(groupName); if (!group?.isValid()) { group = new AmuletGroup(null); group.name = '(未命名)'; groupName = null; } else { group.validate(amulets); while (group.removeId(-1) != null) { groupChanged = true; } group.items.forEach((am) => { let i = searchElement(amulets, am, (a, b) => a.id - b.id); if (i >= 0) { amulets.splice(i, 1); } }); } genericPopupClose(true); let fixedContent = '<div style="padding:20px 0px 5px 0px;font-size:18px;color:blue;"><b>' + '<span>双击护符条目以进行添加或移除操作</span><span style="float:right;">共 ' + '<span id="amulet_count" style="color:#800020;">0</span> 个护符</span></b></div>'; let mainContent = '<style> ul > li:hover { background-color:#bbddff; } </style>' + '<div style="display:block;height:100%;width:100%;">' + '<div style="position:relative;display:block;float:left;height:96%;width:49%;' + 'margin-top:10px;border:1px solid #000000;">' + '<div style="display:block;width:100%;padding:10px 10px;border-bottom:2px groove #d0d0d0;' + 'margin-bottom:10px;">' + '<select id="amulet_filter" style="display:inline;width:100%;color:blue;text-align:center;">' + '</select>' + '</div>' + '<div style="position:absolute;display:block;height:1px;width:100%;overflow:scroll;">' + '<ul id="amulet_list" style="cursor:pointer;"></ul>' + '</div>' + '</div>' + '<div style="position:relative;display:block;float:right;height:96%;width:49%;' + 'margin-top:10px;border:1px solid #000000;">' + '<div id="group_summary" style="display:block;width:100%;padding:10px 5px;' + 'border-bottom:2px groove #d0d0d0;color:#000080;margin-bottom:10px;"></div>' + '<div style="position:absolute;display:block;height:1px;width:100%;overflow:scroll;">' + '<ul id="group_amulet_list" style="cursor:pointer;"></ul>' + '</div>' + '</div>' + '</div>'; genericPopupSetFixedContent(fixedContent); genericPopupSetContent('编辑护符组 - ' + displayName, mainContent); let amuletCount = document.getElementById('amulet_count'); let amuletFilterList = document.getElementById('amulet_filter'); let amuletList = document.getElementById('amulet_list'); let groupSummary = document.getElementById('group_summary'); let groupAmuletList = document.getElementById('group_amulet_list'); let op = document.createElement('option'); op.value = -1; op.innerText = '全部护符类型'; amuletFilterList.appendChild(op); for (let amuletType in g_amuletTypeIds) { op = document.createElement('option'); op.value = g_amuletTypeIds[amuletType]; op.innerText = amuletType; amuletFilterList.appendChild(op); } refreshAmuletList(); refreshGroupAmuletDiv(); amuletFilterList.onchange = refreshAmuletList; amuletList.ondblclick = groupAmuletList.ondblclick = moveAmuletItem; genericPopupAddButton( '清空护符组', 0, (() => { if (group.count() > 0) { group.items.forEach((am) => { insertElement(amulets, am, (a, b) => a.id - b.id); }); group.clear(); refreshAmuletList(); refreshGroupAmuletDiv(); groupChanged = true; } }), true); if (amuletIsValidGroupName(groupName)) { genericPopupAddButton( '另存为', 80, (() => { if (!group.isValid()) { alert('护符组内容存在错误,请检查!'); return; } let gn = inputAmuletGroupName(groupName); if (gn == null) { return; } let groups = amuletLoadGroups(); if (groups.contains(gn) && !confirm(`护符组 "${gn}" 已存在,要覆盖吗?`)) { return; } group.name = gn; if (groups.add(group)) { amuletSaveGroups(groups); showAmuletGroupsPopup(); } else { alert('保存失败!'); } }), false); } genericPopupAddButton( '确认', 80, (() => { if (!groupChanged && group.isValid()) { showAmuletGroupsPopup(); return; } else if (!group.isValid()) { alert('护符组内容存在错误,请检查!'); return; } let groups = amuletLoadGroups(); if (!amuletIsValidGroupName(groupName)) { let gn = inputAmuletGroupName(displayName); if (gn == null || (groups.contains(gn) && !confirm(`护符组 "${gn}" 已存在,要覆盖吗?`))) { return; } group.name = gn; } if (groups.add(group)) { amuletSaveGroups(groups); showAmuletGroupsPopup(); } else { alert('保存失败!'); } }), false); genericPopupAddButton( '取消', 80, (() => { if (!groupChanged || confirm('护符组内容已修改,不保存吗?')) { showAmuletGroupsPopup(); } }), false); genericPopupSetContentSize(Math.min(800, Math.max(window.innerHeight - 200, 500)), Math.min(1000, Math.max(window.innerWidth - 100, 600)), false); genericPopupShowModal(false); divHeightAdjustment(amuletList.parentNode); divHeightAdjustment(groupAmuletList.parentNode); } function createAmuletGroup(groupName, amulets, update) { let group = amuletCreateGroupFromArray(groupName, amulets); if (group != null) { let groups = amuletLoadGroups(); if (update || !groups.contains(groupName) || confirm(`护符组 "${groupName}" 已存在,要覆盖吗?`)) { if (groups.add(group)) { amuletSaveGroups(groups); genericPopupClose(true); return true; } else { alert('保存失败!'); } } } else { alert('保存异常,请检查!'); } genericPopupClose(true); return false; } function formatAmuletsString() { let bag = []; let store = []; let exportLines = []; if (queryAmulets(bag, store, null) > 0) { let amulets = bag.concat(store).sort((a, b) => a.compareTo(b)); let amuletIndex = 1; amulets.forEach((am) => { exportLines.push(`${('00' + amuletIndex).slice(-3)} - ${am.formatShortMark()}`); amuletIndex++; }); } return (exportLines.length > 0 ? exportLines.join('\n') : ''); } function exportAmulets() { genericPopupSetContent( '护符导出', `<b><div id="amulet_export_tip" style="color:#0000c0;padding:15px 0px 10px;"> 请勿修改任何导出内容,将其保存为纯文本在其它相应工具中使用</div></b> <div style="height:330px;"><textarea id="amulet_persistence_string" readonly="true" style="height:100%;width:100%;resize:none;"></textarea></div>`); genericPopupAddButton( '复制导出内容至剪贴板', 0, ((e) => { e.target.disabled = 'disabled'; let tipContainer = document.getElementById('amulet_export_tip'); let tipColor = tipContainer.style.color; let tipString = tipContainer.innerText; tipContainer.style.color = '#ff0000'; document.querySelector('#amulet_persistence_string').select(); if (document.execCommand('copy')) { tipContainer.innerText = '导出内容已复制到剪贴板'; } else { tipContainer.innerText = '复制失败,这可能是因为浏览器没有剪贴板访问权限,请进行手工复制(CTRL+A, CTRL+C)'; } setTimeout((() => { tipContainer.style.color = tipColor; tipContainer.innerText = tipString; e.target.disabled = ''; }), 3000); }), true); genericPopupAddCloseButton(80); document.getElementById('amulet_persistence_string').value = formatAmuletsString(); genericPopupSetContentSize(400, 600, false); genericPopupShowModal(true); } let amuletButtonsGroupContainer = document.getElementById('amulet_management_btn_group'); if (amuletButtonsGroupContainer == null) { amuletButtonsGroupContainer = document.createElement('div'); amuletButtonsGroupContainer.id = 'amulet_management_btn_group'; amuletButtonsGroupContainer.style.width = '100px'; amuletButtonsGroupContainer.style.float = 'right'; document.getElementById('backpacks').children[0].appendChild(amuletButtonsGroupContainer); let exportAmuletsBtn = document.createElement('button'); exportAmuletsBtn.innerText = '导出护符'; exportAmuletsBtn.style.width = '100%'; exportAmuletsBtn.style.marginBottom = '1px'; exportAmuletsBtn.onclick = (() => { exportAmulets(); }); amuletButtonsGroupContainer.appendChild(exportAmuletsBtn); let beginClearBagBtn = document.createElement('button'); beginClearBagBtn.innerText = '清空背包'; beginClearBagBtn.style.width = '100%'; beginClearBagBtn.style.marginBottom = '1px'; beginClearBagBtn.onclick = (() => { genericPopupShowProgressMessage('处理中,请稍候…'); beginClearBag( document.querySelectorAll('#backpacks > div.alert-danger > div.content > button.fyg_mp3'), null, refreshEquipmentPage, null); }); amuletButtonsGroupContainer.appendChild(beginClearBagBtn); let amuletSaveGroupBtn = document.createElement('button'); amuletSaveGroupBtn.innerText = '存为护符组'; amuletSaveGroupBtn.style.width = '100%'; amuletSaveGroupBtn.style.marginBottom = '1px'; amuletSaveGroupBtn.onclick = (() => { let groupName = inputAmuletGroupName(''); if (groupName != null) { let amulets = []; if (queryAmulets(amulets, null, null) == 0) { alert('保存失败,请检查背包内容!'); } else if (createAmuletGroup(groupName, amulets, false)) { alert('保存成功。'); } } }); amuletButtonsGroupContainer.appendChild(amuletSaveGroupBtn); let manageAmuletGroupBtn = document.createElement('button'); manageAmuletGroupBtn.innerText = '管理护符组'; manageAmuletGroupBtn.style.width = '100%'; manageAmuletGroupBtn.style.marginBottom = '1px'; manageAmuletGroupBtn.onclick = (() => { genericPopupInitialize(); showAmuletGroupsPopup(); }); amuletButtonsGroupContainer.appendChild(manageAmuletGroupBtn); let clearAmuletGroupBtn = document.createElement('button'); clearAmuletGroupBtn.innerText = '清除护符组'; clearAmuletGroupBtn.style.width = '100%'; clearAmuletGroupBtn.onclick = (() => { if (confirm('要删除全部已保存的护符组信息吗?')) { amuletClearGroups(); alert('已删除全部预定义护符组信息。'); } }); amuletButtonsGroupContainer.appendChild(clearAmuletGroupBtn); document.getElementById(storeButtonId).onclick = (() => { if ($(storeQueryString).css('display') == 'none') { $(storeQueryString).show(); } else { $(storeQueryString).hide(); } backupEquipmentDivState({ target : document.getElementById(storeButtonId) }); }); } $('#equipmentDiv .btn-equipment .bg-danger.with-padding').css({ 'max-width': '200px', 'padding': '5px 5px 5px 5px', 'white-space': 'pre-line', 'word-break': 'break-all' }); collapseEquipmentDiv(equipmentExpand, forceEquipDivOperation); changeEquipmentDivStyle(equipmentBG); forceEquipDivOperation = false; } } }, 500); } const g_bindingPopupLinkId = 'binding_popup_link'; const g_cardOnekeyLinkId = 'card_one_key_link'; const g_bindingSolutionId = 'binding_solution_div'; const g_bindingListSelectorId = 'binding_list_selector'; const g_equipOnekeyLinkId = 'equip_one_key_link'; function switchCardTemporarily(roleId) { let role = g_roleMap.get(roleId); if (role == undefined) { return; } genericPopupInitialize(); genericPopupShowProgressMessage('正在切换,请稍候…'); const upcard_data = getPostData(/upcard\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/).slice(7, -1); const halosave_data = getPostData(/halosave\(\)\{[\s\S]*\}/m, /data: ".*\+savearr\+.*"/).slice(7, -1); let roleInfo = []; let haloInfo = []; beginReadRoleAndHalo(roleInfo, haloInfo, switchToTempCard, null); function switchCardCompletion() { genericPopupClose(true); window.location.reload(); } function switchToTempCard() { if (roleInfo.length == 2 && haloInfo.length == 3) { const infoHTML = `<div style="display:block;width:100%;color:#0000c0;text-align:center;font-size:20px;padding-top:50px;"><b> <p></p><span style="width:100%;">当前卡片已经由 [ ${roleInfo[1]} ] 临时切换至 [ ${g_roleMap.get(roleId)?.name ?? 'UNKNOW'} ]</span><br><br> <p></p><span style="width:100%;">请切换至搜刮页面尽快完成搜刮操作</span><br><br> <p></p><span style="width:100%;">并返回本页面点击“恢复”按钮以恢复之前的卡片和光环设置</span></b></div>`; genericPopupSetContent(`临时装备卡片 [ ${g_roleMap.get(roleId)?.name ?? 'UNKNOW'} ]`, infoHTML); genericPopupSetContentSize(300, 600, false); genericPopupAddButton('恢复', 80, restoreCardAndHalo, false); switchCard(roleId, null, genericPopupShowModal, false); } else { alert('无法读取当前装备卡片和光环信息,卡片未切换!'); switchCardCompletion(); } } function restoreCardAndHalo() { genericPopupShowProgressMessage('正在恢复,请稍候…'); switchCard(roleInfo[0], haloInfo[2], switchCardCompletion, null); } function switchCard(newRoleId, newHaloArray, fnFurtherProcess, fnParams) { let cardData = upcard_data.replace('"+id+"', newRoleId); GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: cardData, onload: response => { if (response.responseText == 'ok' || response.responseText == '你没有这张卡片或已经装备中') { if (newHaloArray?.length > 0) { let haloData = halosave_data.replace('"+savearr+"', newHaloArray.join()); GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: haloData, onload: response => { if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } }); return; } } else { alert('无法完成卡片和光环切换,请尝试手动进行!'); switchCardCompletion(); return; } if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } }); } } function equipOnekey() { const puton_data = getPostData(/puton\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/).slice(7, -1); let g_equipmentOperationError = false; let g_equipmentFromStoreCount = 0; let g_equipmentToStoreCount = 0; let g_amuletGroupsToLoad = null; let g_originalBagObjectIds = null; let g_originalStoreObjectIds = null; let g_scheduledObjectIndices = null; let g_exchangedBagObjectIds = null; function beginPutonEquipments(bindInfo) { genericPopupTaskSetState(1, '- 检查装备...'); let puttingon = []; let equiped = equipmentNodesToInfoArray(document.querySelectorAll ('#carding > div.row > div.fyg_tc > button.fyg_mp3'), []); for (let i = 0; i < 4; i++) { if (bindInfo[i] != equiped[i].slice(0, -1).join(',')) { puttingon.push(bindInfo[i]); } } if (puttingon.length == 0) { genericPopupTaskSetState(1, ''); beginLoadAmulets(); return; } let bag = []; let store = []; beginReadObjects(bag, store, findEquipments, { bag : bag , store : store }); function findEquipments({ bag, store }) { let ids = []; equipmentNodesToInfoArray(bag.pop(), bag); for (let i = bag.length - 1; i >= 0 && puttingon.length > 0; i--) { let eq = bag[i].slice(0, -1).join(','); for (let j = puttingon.length - 1; j >= 0; j--) { if (eq == puttingon[j]) { puttingon.splice(j, 1); ids.unshift(bag[i][7]); break; } } } if (puttingon.length > 0) { ids = []; equipmentNodesToInfoArray(store.pop(), store); for (let i = store.length - 1; i >= 0 && puttingon.length > 0; i--) { let eq = store[i].slice(0, -1).join(','); for (let j = puttingon.length - 1; j >= 0; j--) { if (eq == puttingon[j]) { puttingon.splice(j, 1); ids.unshift(store[i][7]); break; } } } if (puttingon.length > 0) { console.log(puttingon); alert('有装备不存在,请重新检查绑定!'); window.location.reload(); return; } genericPopupTaskSetState(1, '- 取出仓库...'); beginMoveObjects(ids, g_object_move_path.store2bag, beginPutonEquipments, bindInfo); } else { genericPopupTaskSetState(1, '- 穿上装备...'); putonEquipments(ids, beginLoadAmulets, null); } } let putonCount; function putonEquipments(objects, fnFurtherProcess, fnParams) { if (objects?.length > 0) { let ids = []; while (ids.length < g_maxConcurrentRequests && objects.length > 0) { let id = objects.pop(); if (id >= 0) { ids.push(id); } } if ((putonCount = ids.length) > 0) { while (ids.length > 0) { let request = GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: puton_data.replace('"+id+"', ids.shift()), onload: response => { if (g_equipmentOperationError = (response.responseText != 'ok')) { console.log(response.responseText); } if (--putonCount == 0) { putonEquipments(objects, fnFurtherProcess, fnParams); } } }); httpRequestRegister(request); } return; } } genericPopupTaskSetState(1, ''); if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } } function roleSetupCleanup() { httpRequestClearAll(); g_equipmentOperationError = false; g_amuletGroupsToLoad = null; g_originalBagObjectIds = null; g_originalStoreObjectIds = null; g_scheduledObjectIndices = null; g_exchangedBagObjectIds = null; } function roleSetupCompletion() { genericPopupClose(true); roleSetupCleanup(); window.location.reload(); } function checkForRoleSetupCompletion() { if (genericPopupTaskCheckCompletion()) { // delay for the final state can be seen setTimeout(roleSetupCompletion, 200); } } function amuletLoadCompletion() { genericPopupTaskComplete(3); genericPopupTaskSetState(3, ''); checkForRoleSetupCompletion(); } // 'cause of non-equipment-object's id would change when move it from bag to store, and, // even in store the object's id still can be changed by many kind of operations. // so unfortunately, restore the scheduled objects to bag by id will fail in most time, // and so we use indices instead of id, but there always have chances to produce incorrect // results, that means you * SHOULD * check the result by yourself manually to make sure // everything is ok function beginRestoreScheduledObjects() { let ids; let sl = g_originalStoreObjectIds.length; g_scheduledObjectIndices?.sort((a, b) => b - a); while (g_scheduledObjectIndices?.length > 0) { // in some case the equipments that putted on was originally in the bag, but the replaced // equipments will always been putted in the store, so the scheduled object's indices // should be recalculated to comply with these situations let i = g_scheduledObjectIndices.pop() + g_equipmentToStoreCount - g_equipmentFromStoreCount; if (i < sl && g_originalStoreObjectIds[i] >= 0) { (ids ??= []).push(g_originalStoreObjectIds[i]); } } beginMoveObjects(ids, g_object_move_path.store2bag, amuletLoadCompletion, null); } // maximum equipments exchanging count should be 4, and it should be placed at the head of the bag cells, // on the other hand, when you move an equipment-object to store, it will be placed before non-equipment-object cells, // and this will not change the order between those non-equipment-objects, they just move backward as a whole group, // based on this fact, we can consider the indices of the scheduled non-equipment-objects is permanent function beginStoreExchangedObjects() { function beginRefreshStoredObjects() { beginReadObjectIds(null, g_originalStoreObjectIds = [], null, true, beginRestoreScheduledObjects, 0); } g_originalStoreObjectIds.sort((a, b) => a - b); g_scheduledObjectIndices = findNewObjects(g_scheduledObjectIndices, g_originalStoreObjectIds, (a, b) => a < 0 ? 0 : a - b, true); g_originalBagObjectIds.sort((a, b) => a - b); let ids = findNewObjects(g_exchangedBagObjectIds?.length > 4 ? g_exchangedBagObjectIds.slice(0, 4) : g_exchangedBagObjectIds, g_originalBagObjectIds, (a, b) => a < 0 ? 0 : a - b, false); if (ids?.length > 0) { g_equipmentToStoreCount = ids.length; beginMoveObjects(ids, g_object_move_path.bag2store, g_scheduledObjectIndices?.length > 0 ? beginRefreshStoredObjects : amuletLoadCompletion, null); } else if (g_scheduledObjectIndices?.length > 0) { beginRefreshStoredObjects(); } else { amuletLoadCompletion(); } } function beginamuletLoadGroups() { if (g_amuletGroupsToLoad?.length > 0) { genericPopupTaskSetState(3, `- 加载护符...[${g_amuletGroupsToLoad?.length}]`); beginLoadAmuletGroupFromStore(null, g_amuletGroupsToLoad.shift(), beginamuletLoadGroups, 0); } else { amuletLoadCompletion(); } } function beginLoadAmulets() { // equipment task should be completed genericPopupTaskComplete(1, g_equipmentOperationError); if (g_amuletGroupsToLoad != null) { genericPopupTaskSetState(3, '- 清理装备...'); beginClearBag(null, null, beginamuletLoadGroups, 0); } else { genericPopupTaskSetState(3, '- 恢复背包...'); beginReadObjectIds(g_exchangedBagObjectIds = [], null, null, true, beginStoreExchangedObjects, 0); } } function beginScheduleCells(bindInfo) { function beginReadScheduledStore(bindInfo) { genericPopupTaskSetState(3, ''); // use g_scheduledObjectIndices as scheduled store temporarily // (why i'd like save variable names? it's weird...) beginReadObjectIds(null, g_scheduledObjectIndices = [], null, true, beginPutonEquipments, bindInfo); } if (g_originalBagObjectIds != null) { g_equipmentFromStoreCount = 0; g_equipmentToStoreCount = 0; let ids; let freeCellsNeeded = 4; let il = g_originalBagObjectIds.length - 1; for (let i = il; i >= 0; i--) { if (g_originalBagObjectIds[i] == -1) { if (--freeCellsNeeded == 0) { genericPopupTaskSetState(3, ''); beginPutonEquipments(bindInfo); break; } } else { (ids ??= []).push(g_originalBagObjectIds[i]); if (--freeCellsNeeded == 0) { beginMoveObjects(ids, g_object_move_path.bag2store, beginReadScheduledStore, bindInfo); break; } } } } else { genericPopupTaskSetState(3, '- 调整空间...'); beginReadObjectIds(g_originalBagObjectIds = [], g_originalStoreObjectIds = [], null, false, beginScheduleCells, bindInfo); } } function beginSetupHalo(bindInfo) { let halo = []; if (bindInfo.length > 4) { bindInfo[4].split(',').forEach((item) => { let hid = g_haloMap.get(item.trim())?.id; if (hid > 0) { halo.push(hid); } }); if ((halo = halo.join(','))?.length > 0) { let halosave_data = getPostData(/halosave\(\)\{[\s\S]*\}/m, /data: ".*\+savearr\+.*"/).slice(7, -1).replace('"+savearr+"', halo); let request = GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: halosave_data, onload: response => { genericPopupTaskComplete(2, response.responseText != 'ok'); checkForRoleSetupCompletion(); } }); httpRequestRegister(request); } } if (!(halo?.length > 0)) { genericPopupTaskComplete(2); checkForRoleSetupCompletion(); } } function beginRoleSetup(bindInfo) { beginSetupHalo(bindInfo); if (bindInfo.length > 5 && bindInfo[5].length > 0) { g_amuletGroupsToLoad = bindInfo[5].split(','); if (g_amuletGroupsToLoad.length > 0) { g_originalBagObjectIds = [ -1, -1, -1, -1 ]; genericPopupTaskSetState(3, '- 清理背包...'); beginClearBag(null, null, beginScheduleCells, bindInfo); return; } } g_amuletGroupsToLoad = null; beginScheduleCells(bindInfo); } let bindingElements = document.getElementById(g_bindingListSelectorId)?.value?.split(BINDING_NAME_SEPARATOR); if (bindingElements?.length == 2) { function equipOnekeyQuit() { httpRequestAbortAll(); roleSetupCompletion(); } genericPopupInitialize(); genericPopupTaskListPopupSetup('更换中...', 300, [ '卡片', '装备', '光环', '护符' ], equipOnekeyQuit); genericPopupShowModal(false); let roleId = g_roleMap.get(bindingElements[0].trim()).id; let bindInfo = bindingElements[1].trim().split(BINDING_ELEMENT_SEPARATOR) if (roleId == g_roleMap.get(document.getElementById('carding') ?.querySelector('div.text-info.fyg_f24.fyg_lh60') ?.children[0]?.innerText)?.id) { genericPopupTaskComplete(0); beginRoleSetup(bindInfo); } else { genericPopupTaskSetState(0, '- 切换中...'); GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: getPostData(/upcard\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/).slice(7, -1).replace('"+id+"', roleId), onload: response => { genericPopupTaskSetState(0, ''); if (response.responseText == 'ok' || response.responseText == '你没有这张卡片或已经装备中') { genericPopupTaskComplete(0); beginRoleSetup(bindInfo); } else { genericPopupTaskComplete(0, true); alert('切换卡片失败!'); equipOnekeyQuit(); } } }); } } else { alert('读取绑定信息失败,无法装备!'); } } const BINDING_NAME_DEFAULT = '(未命名)'; const BINDING_SEPARATOR = ';'; const BINDING_NAME_SEPARATOR = '='; const BINDING_ELEMENT_SEPARATOR = '|'; function showBindingPopup() { let roleId = g_roleMap.get(document.querySelector('#backpacks > div.row > div.col-md-3 > span.text-info.fyg_f24') ?.innerText)?.id; let roleLv = document.querySelector('#backpacks')?.querySelectorAll('.icon.icon-angle-down.text-primary')[0] ?.innerText?.match(/\d+/)[0]; let roleHs = document.querySelector('#backpacks')?.querySelectorAll('.icon.icon-angle-down.text-primary')[2] ?.innerText?.match(/\d+/)[0]; if (roleId == undefined || roleLv == undefined || roleHs == undefined) { alert('读取卡片信息失败,无法执行绑定操作!'); return; } let bind_info = null; let udata = loadUserConfigData(); if (udata.dataBind[roleId] != null) { bind_info = udata.dataBind[roleId]; } genericPopupInitialize(); genericPopupShowProgressMessage('读取中,请稍候…'); const highlightBackgroundColor = '#80c0f0'; const fixedContent = `<div style="width:100%;color:#0000ff;padding:20px 10px 5px 0px;"><b>绑定方案名称` + `(不超过31个字符,请仅使用大、小写英文字母、数字、连字符、下划线及中文字符):` + `<span id="${g_genericPopupInformationTipsId}" style="float:right;color:red;"></span></b></div> <div style="width:100%;padding:0px 10px 20px 0px;"><input type="text" id="binding_name" maxlength="31" list="binding_list" style="display:inline-block;width:100%;"></input> <datalist id="binding_list"></datalist></div>`; const mainContent = `<style> .equipment_label { display:inline-block; width:15%; } .equipment_selector { display:inline-block; width:84%; color:#145ccd; float:right; } div > li { cursor:pointer; } div > li:hover { background-color:#bbddff; } </style> <div class="${g_genericPopupTopLineDivClass}"> <span class="equipment_label">武器装备:</span><select class="equipment_selector"></select><br><br> <span class="equipment_label">手臂装备:</span><select class="equipment_selector"></select><br><br> <span class="equipment_label">身体装备:</span><select class="equipment_selector"></select><br><br> <span class="equipment_label">头部装备:</span><select class="equipment_selector"></select></div> <div class="${g_genericPopupTopLineDivClass}" style="display:flex;position:relative;"><div id="halo_selector"></div></div> <div class="${g_genericPopupTopLineDivClass}" id="amulet_selector" style="display:block;"><div></div></div>`; genericPopupSetFixedContent(fixedContent); genericPopupSetContent(`${g_roleMap.get(roleId)?.name ?? 'UNKNOW'} - ${roleLv} 级`, mainContent); let eq_selectors = document.querySelectorAll('select.equipment_selector'); let asyncOperations = 2; let haloMax = 0; let haloGroupItemMax = 0; let bag = []; let store = []; beginReadObjects( bag, store, () => { let equipment = equipmentNodesToInfoArray(bag.pop(), []); equipmentNodesToInfoArray(store.pop(), equipment); equipmentNodesToInfoArray(document.querySelectorAll('#carding > div.row > div.fyg_tc > button.fyg_mp3'), equipment); let e = [ [], [], [], [] ]; let origin = [ [], [], [], [] ]; equipment.sort((e1, e2) => { if (e1[0] != e2[0]) { return (g_equipMap.get(e1[0]).index - g_equipMap.get(e2[0]).index); } return -equipmentInfoComparer(e1, e2); }); equipment.forEach((item) => { let eqType = g_equipMap.get(item[0]); origin[eqType.type].push(item) e[eqType.type].push([ eqType.name, `Lv.${item[1]}`, `${item[2]}%`, `${item[3]}%`, `${item[4]}%`, `${item[5]}%`, `${item[6] == 1 ? '神秘' : ''}` ]); }); for (let i = 0; i < 4; i++) { for (let j = 0; j < e[i].length; j++) { let op0 = document.createElement('option'); op0.innerText = e[i][j].join(' '); op0.value = origin[i][j].slice(0, -1).join(','); eq_selectors[i].appendChild(op0); } } asyncOperations--; }, null); let currentHalo = []; beginReadRoleAndHalo( null, currentHalo, () => { haloMax = currentHalo[0]; roleHs = currentHalo[1]; let haloInfo = `天赋点:<span style="color:#0000c0;"><span id="halo_points">0</span> / ${haloMax}</span>, 技能位:<span style="color:#0000c0;"><span id="halo_slots">0</span> / ${roleHs}</span>`; let haloSelector = document.getElementById('halo_selector'); haloSelector.innerHTML = `<style> .halo_group { display:block; width:25%; float:left; text-align:center; border-left:1px solid grey; } div > a { display:inline-block; width:90px; } div > a:hover { background-color:#bbddff; } </style> <div>${haloInfo}</div> <p></p> <div class="halo_group"></div> <div class="halo_group"></div> <div class="halo_group"></div> <div class="halo_group" style="border-right:1px solid grey;"></div>`; let haloGroups = haloSelector.querySelectorAll('.halo_group'); let group = -1; let points = -1; g_halos.forEach((item) => { if (item.points != points) { points = item.points; group++; } let a = document.createElement('a'); a.href = '#'; a.className = 'halo_item'; a.innerText = item.name + ' ' + item.points; haloGroups[group].appendChild(a); if (haloGroups[group].children.length > haloGroupItemMax) { haloGroupItemMax = haloGroups[group].children.length; } }); function selector_halo() { let hp = parseInt(haloPoints.innerText); let hs = parseInt(haloSlots.innerText); if ($(this).attr('item-selected') != 1) { $(this).attr('item-selected', 1); $(this).css('background-color', highlightBackgroundColor); hp += parseInt($(this).text().split(' ')[1]); hs++; } else { $(this).attr('item-selected', 0); $(this).css('background-color', g_genericPopupBackgroundColor); hp -= parseInt($(this).text().split(' ')[1]); hs--; } haloPoints.innerText = hp; haloSlots.innerText = hs; haloPoints.style.color = (hp <= haloMax ? '#0000c0' : 'red'); haloSlots.style.color = (hs <= roleHs ? '#0000c0' : 'red'); } haloPoints = document.getElementById('halo_points'); haloSlots = document.getElementById('halo_slots'); $('.halo_item').each(function(i, e) { $(e).on('click', selector_halo); $(e).attr('original-item', $(e).text().split(' ')[0]); }); asyncOperations--; }, null); function unbindAll() { if (confirm('这将清除本卡片全部绑定方案,继续吗?')) { let udata = loadUserConfigData(); if (udata.dataBind[roleId] != null) { delete udata.dataBind[roleId]; } saveUserConfigData(udata); bindingName.value = BINDING_NAME_DEFAULT; bindingList.innerHTML = ''; refreshBindingSelector(roleId); genericPopupShowInformationTips('解除全部绑定成功', 5000); } }; function deleteBinding() { if (validateBindingName()) { bindings = []; let found = false; $('.binding_name_item').each((index, item) => { if (item.value == bindingName.value) { bindingList.removeChild(item); found = true; } else { bindings.push(`${item.value}${BINDING_NAME_SEPARATOR}${item.innerText}`); } }); if (found) { let bn = bindingName.value; let bi = null; let udata = loadUserConfigData(); if (bindings.length > 0) { udata.dataBind[roleId] = bindings.join(BINDING_SEPARATOR); bindingName.value = bindingList.children[0].value; bi = bindingList.children[0].innerText; } else if(udata.dataBind[roleId] != null) { delete udata.dataBind[roleId]; bindingName.value = BINDING_NAME_DEFAULT; } saveUserConfigData(udata); refreshBindingSelector(roleId); representBinding(bi); genericPopupShowInformationTips(bn + ':解绑成功', 5000); } else { alert('方案名称未找到!'); } } }; function saveBinding() { if (validateBindingName()) { let equ = []; let halo = []; let sum = 0; eq_selectors.forEach((eq) => { equ.push(eq.value); }); $('.halo_item').each(function(i, e) { if ($(e).attr('item-selected') == 1) { let ee = e.innerText.split(' '); sum += parseInt(ee[1]); halo.push($(e).attr('original-item')); } }); let h = parseInt(haloMax); if (equ.length == 4 && sum <= h && halo.length <= parseInt(roleHs)) { let amuletArray = []; $('.amulet_item').each(function(i, e) { if ($(e).attr('item-selected') == 1) { amuletArray[parseInt(e.lastChild.innerText) - 1] = ($(e).attr('original-item')); } }); let bind_info = [ equ[0], equ[1], equ[2], equ[3], halo.join(','), amuletArray.join(',') ].join(BINDING_ELEMENT_SEPARATOR); let newBinding = true; bindings = []; $('.binding_name_item').each((index, item) => { if (item.value == bindingName.value) { item.innerText = bind_info; newBinding = false; } bindings.push(`${item.value}${BINDING_NAME_SEPARATOR}${item.innerText}`); }); if (newBinding) { let op0 = document.createElement('option'); op0.className = 'binding_name_item'; op0.innerText = bind_info; op0.value = bindingName.value; for (var op = bindingList.firstChild; op?.value < op0.value; op = op.nextSibling); bindingList.insertBefore(op0, op); bindings.push(`${op0.value}${BINDING_NAME_SEPARATOR}${op0.innerText}`); } let udata = loadUserConfigData(); udata.dataBind[roleId] = bindings.join(BINDING_SEPARATOR); saveUserConfigData(udata); refreshBindingSelector(roleId); genericPopupShowInformationTips(bindingName.value + ':绑定成功', 5000); } else { alert('有装备未选或光环天赋选择错误!'); } } } function isValidBindingName(bindingName) { return (bindingName?.length > 0 && bindingName.length < 32 && bindingName.search(USER_STORAGE_RESERVED_SEPARATORS) < 0); } function validateBindingName() { let valid = isValidBindingName(bindingName.value); genericPopupShowInformationTips(valid ? null : '方案名称不符合规则,请检查'); return valid; } function validateBinding() { if (validateBindingName) { let ol = bindingList.children.length; for (let i = 0; i < ol; i++) { if (bindingName.value == bindingList.children[i].value) { representBinding(bindingList.children[i].innerText); break; } } } } function representBinding(items) { if (items?.length > 0) { let elements = items.split(BINDING_ELEMENT_SEPARATOR); if (elements.length > 3) { let v = elements.slice(0, 4); eq_selectors.forEach((eq) => { for (let op of eq.childNodes) { if (v.indexOf(op.value) >= 0) { eq.value = op.value; break; } } }); } if (elements.length > 4) { let hp = 0; let hs = 0; let v = elements[4].split(','); $('.halo_item').each((index, item) => { let s = (v.indexOf($(item).attr('original-item')) < 0 ? 0 : 1); $(item).attr('item-selected', s); $(item).css('background-color', s == 0 ? g_genericPopupBackgroundColor : highlightBackgroundColor); hp += (s == 0 ? 0 : parseInt($(item).text().split(' ')[1])); hs += s; }); haloPoints.innerText = hp; haloSlots.innerText = hs; haloPoints.style.color = (hp <= haloMax ? '#0000c0' : 'red'); haloSlots.style.color = (hs <= roleHs ? '#0000c0' : 'red'); } selectedAmuletGroupCount = 0; if (elements.length > 5 && amuletCount != null) { let ac = 0; let v = elements[5].split(','); $('.amulet_item').each((index, item) => { let j = v.indexOf($(item).attr('original-item')); let s = (j < 0 ? 0 : 1); $(item).attr('item-selected', s); $(item).css('background-color', s == 0 ? g_genericPopupBackgroundColor : highlightBackgroundColor); item.lastChild.innerText = (j < 0 ? '' : j + 1); selectedAmuletGroupCount += s; ac += (s == 0 ? 0 : parseInt($(item).text().match(/\[(\d+)\]/)[1])); }); amuletCount.innerText = ac; } } } function selector_amulet() { let ac = parseInt(amuletCount.innerText); let tc = parseInt($(this).text().match(/\[(\d+)\]/)[1]); if ($(this).attr('item-selected') != 1) { $(this).attr('item-selected', 1); $(this).css('background-color', highlightBackgroundColor); this.lastChild.innerText = ++selectedAmuletGroupCount; ac += tc; } else { $(this).attr('item-selected', 0); $(this).css('background-color', g_genericPopupBackgroundColor); let i = parseInt(this.lastChild.innerText); this.lastChild.innerText = ''; ac -= tc; if (i < selectedAmuletGroupCount) { $('.amulet_item').each((index, item) => { var j; if ($(item).attr('item-selected') == 1 && (j = parseInt(item.lastChild.innerText)) > i) { item.lastChild.innerText = j - 1; } }); } selectedAmuletGroupCount--; } amuletCount.innerText = ac; } let bindingList = document.getElementById('binding_list'); let bindingName = document.getElementById('binding_name'); let haloPoints = null; let haloSlots = null; let amuletContainer = document.getElementById('amulet_selector').firstChild; let amuletCount = null; let amuletGroups = amuletLoadGroups(); let selectedAmuletGroupCount = 0; let amuletGroupCount = (amuletGroups?.count() ?? 0); if (amuletGroupCount > 0) { amuletContainer.innerHTML = '护符组:已选定 <span id="amulet_count">0</span> 个护符<span style="float:right;margin-right:5px;">加载顺序</span><p></p>'; amuletCount = document.getElementById('amulet_count'); amuletCount.style.color = '#0000c0'; let amuletArray = amuletGroups.toArray().sort((a, b) => a.name < b.name ? -1 : 1); for (let i = 0; i < amuletGroupCount; i++) { let li0 = document.createElement('li'); li0.className = 'amulet_item'; li0.setAttribute('original-item', amuletArray[i].name); li0.innerHTML = `<a href="#">${amuletArray[i].name} [${amuletArray[i].count()}]</a>` + `<span style="color:#0000c0;width:40;float:right;margin-right:5px;"></span>`; li0.onclick = selector_amulet; amuletContainer.appendChild(li0); } } else { amuletContainer.innerHTML = '<ul><li>未能读取护符组定义信息,这可能是因为您没有预先完成护符组定义。</li><p />' + '<li>将护符与角色卡片进行绑定并不是必须的,但如果您希望使用此功能,' + '则必须先定义护符组然后才能将它们与角色卡片进行绑定。</li><p />' + '<li>要定义护符组,您需要前往 [ <b style="color:#0000c0;">我的角色 → 武器装备</b> ] 页面,' + '并在其中使用将背包内容 [ <b style="color:#0000c0;">存为护符组</b> ] 功能,' + '或在 [ <b style="color:#0000c0;">管理护符组</b> ] 相应功能中进行定义。</li></ul>'; } let bindings = null; if (bind_info != null) { bindings = bind_info.split(BINDING_SEPARATOR).sort((a, b) => { a = a.split(BINDING_NAME_SEPARATOR); b = b.split(BINDING_NAME_SEPARATOR); a = a.length > 1 ? a[0] : BINDING_NAME_DEFAULT; b = b.length > 1 ? b[0] : BINDING_NAME_DEFAULT; return a < b ? -1 : 1; }); } else { bindings = []; } bindings.forEach((item) => { let elements = item.split(BINDING_NAME_SEPARATOR); let binding = elements[elements.length - 1].split(BINDING_ELEMENT_SEPARATOR); if (binding.length > 5) { let amuletGroupNames = binding[5].split(','); let ag = ''; let sp = ''; let al = amuletGroupNames.length; for (let i = 0; i < al; i++) { if (amuletGroups.contains(amuletGroupNames[i])) { ag += (sp + amuletGroupNames[i]); sp = ','; } } binding[5] = ag; elements[elements.length - 1] = binding.join(BINDING_ELEMENT_SEPARATOR); } let op0 = document.createElement('option'); op0.className = 'binding_name_item'; op0.innerText = elements[elements.length - 1]; op0.value = elements.length > 1 ? elements[0] : BINDING_NAME_DEFAULT; bindingList.appendChild(op0); }); let timer = setInterval(() => { if (asyncOperations == 0) { clearInterval(timer); httpRequestClearAll(); if (bindingList.children.length > 0) { bindingName.value = bindingList.children[0].value; representBinding(bindingList.children[0].innerText); } else { bindingName.value = BINDING_NAME_DEFAULT; } bindingName.oninput = validateBindingName; bindingName.onchange = validateBinding; genericPopupSetContentSize(Math.min((haloGroupItemMax + amuletGroupCount) * 20 + (amuletGroupCount > 0 ? 60 : 160) + 260, window.innerHeight - 200), 600, true); genericPopupAddButton('解除绑定', 0, deleteBinding, true); genericPopupAddButton('全部解绑', 0, unbindAll, true); genericPopupAddButton('绑定', 80, saveBinding, false); genericPopupAddCloseButton(80); genericPopupCloseProgressMessage(); genericPopupShowModal(true); } }, 200); }; function refreshBindingSelector(roleId) { let bindingsolutionDiv = document.getElementById(g_bindingSolutionId); let bindingList = document.getElementById(g_bindingListSelectorId); let bindings = null; let bind_info = loadUserConfigData().dataBind[roleId]; if (bind_info != null) { bindings = bind_info.split(BINDING_SEPARATOR).sort((a, b) => { a = a.split(BINDING_NAME_SEPARATOR); b = b.split(BINDING_NAME_SEPARATOR); a = a.length > 1 ? a[0] : BINDING_NAME_DEFAULT; b = b.length > 1 ? b[0] : BINDING_NAME_DEFAULT; return a < b ? -1 : 1; }); } bindingList.innerHTML = ''; if (bindings?.length > 0) { bindings.forEach((item) => { let elements = item.split(BINDING_NAME_SEPARATOR); let op0 = document.createElement('option'); op0.value = roleId + BINDING_NAME_SEPARATOR + elements[elements.length - 1]; op0.innerText = (elements.length > 1 ? elements[0] : BINDING_NAME_DEFAULT); bindingList.appendChild(op0); }); bindingsolutionDiv.style.display = 'inline-block'; } else { bindingsolutionDiv.style.display = 'none'; } } function addBindBtn() { let mountedRoleId = g_roleMap.get(document.getElementById('carding') ?.querySelector('div.text-info.fyg_f24.fyg_lh60') ?.children[0]?.innerText)?.id; let roleId = g_roleMap.get(document.querySelector('#backpacks > div.row > div.col-md-3 > span.text-info.fyg_f24') ?.innerText)?.id; function bindingLinks(e) { if (e.target.id == g_bindingPopupLinkId) { showBindingPopup(); } else if (e.target.id == g_equipOnekeyLinkId) { equipOnekey(); } } let bindingAnchor = document.querySelector('#backpacks > div.row > div.col-md-12').parentNode.nextSibling; let bindingContainer = document.createElement('div'); bindingContainer.className = 'btn-group'; bindingContainer.style.display = 'block'; bindingContainer.style.width = '100%'; bindingContainer.style.marginTop = '15px'; bindingContainer.style.fontSize = '18px'; bindingContainer.style.padding = '10px'; bindingContainer.style.borderRadius = '5px'; bindingContainer.style.color = '#0000c0'; bindingContainer.style.backgroundColor = '#ebf2f9'; bindingAnchor.parentNode.insertBefore(bindingContainer, bindingAnchor); let bindingLink = document.createElement('span'); bindingLink.setAttribute('class', 'fyg_lh30'); bindingLink.style.width = '30%'; bindingLink.style.textAlign = 'left'; bindingLink.style.display = 'inline-block'; bindingLink.innerHTML = `<a href="#" style="text-decoration:underline;" id="${g_bindingPopupLinkId}">绑定(装备 光环 护符)</a>`; bindingLink.querySelector('#' + g_bindingPopupLinkId).onclick = bindingLinks; bindingContainer.appendChild(bindingLink); let bindingsolutionDiv = document.createElement('div'); bindingsolutionDiv.id = g_bindingSolutionId; bindingsolutionDiv.style.display = 'none'; bindingsolutionDiv.style.width = '70%'; let bindingList = document.createElement('select'); bindingList.id = g_bindingListSelectorId; bindingList.style.width = '80%'; bindingList.style.color = '#0000c0'; bindingList.style.textAlign = 'center'; bindingList.style.display = 'inline-block'; bindingsolutionDiv.appendChild(bindingList); let applyLink = document.createElement('span'); applyLink.setAttribute('class', 'fyg_lh30'); applyLink.style.width = '20%'; applyLink.style.textAlign = 'right'; applyLink.style.display = 'inline-block'; applyLink.innerHTML = `<a href="#" style="text-decoration:underline;" id="${g_equipOnekeyLinkId}">应用此方案</a>`; applyLink.querySelector('#' + g_equipOnekeyLinkId).onclick = bindingLinks; bindingsolutionDiv.appendChild(applyLink); bindingContainer.appendChild(bindingsolutionDiv); refreshBindingSelector(roleId); } let backpacksObserver = new MutationObserver(() => { $('.pop_main').hide(); let page = document.getElementsByClassName('nav nav-secondary nav-justified')[0].children; let index = 0; for (let i = 0; i < 4; i++) { if (page[i].className == 'active') { index = i; } } switch (index) { case 0: { calcBtn.onclick = (() => { try { let equip = document.querySelectorAll('#carding > div.row > div.fyg_tc > button.fyg_mp3'); let bag = Array.from(document.querySelectorAll ('#backpacks > div.alert-danger > div.content > button.fyg_mp3')) .concat(Array.from(document.querySelectorAll ('#backpacks > div.alert-success > div.content > button.fyg_mp3'))); let bagdata = equipmentNodesToInfoArray(bag, []); let data = equipmentNodesToInfoArray(equip, []); bagdata = bagdata.concat(data).sort(equipmentInfoComparer); calcDiv.innerHTML = `<div class="pop_main" style="padding:0px 10px;"><a href="#">× 折叠 ×</a> <div class="pop_con"> <div style="width:200px;padding:5px;margin-top:10px;margin-bottom:10px; color:purple;border:1px solid grey;">护符:</div> <div class="pop_text"></div> <div style="width:200px;padding:5px;margin-top:10px;margin-bottom:10px; color:purple;border:1px solid grey">已装备:</div> <div class="pop_text"></div> <div class="pop_text"></div> <div class="pop_text"></div> <div class="pop_text"></div> <div style="width:200px;padding:5px;margin-top:10px;margin-bottom:10px; color:purple;border:1px solid grey;">全部装备:</div> ${new Array(bagdata.length + 1).fill('<div class="pop_text"></div>').join('')}<hr></div> <a href="#">× 折叠 ×</a></div>`; $('.pop_main a').click(() => { $('.pop_main').hide() }) let text = $('.pop_text'); let amulet = document.getElementById('backpacks').lastChild.children[1].innerText.match(/\+\d+/g); for (let i = amulet.length - 1; i >= 0; i--) { if (amulet[i][1] == '0') { amulet.splice(i, 1); } else { amulet[i] = g_amuletBuffShortMarks[i] + amulet[i]; } } text[0].innerText = `AMULET ${amulet.join(' ').replace(/\+/g, ' ')} ENDAMULET`; text[1].innerText = `${data[0].slice(0, -1).join(' ')}`; text[2].innerText = `${data[1].slice(0, -1).join(' ')}`; text[3].innerText = `${data[2].slice(0, -1).join(' ')}`; text[4].innerText = `${data[3].slice(0, -1).join(' ')}`; for (let i = 0; i < bagdata.length; i++) { text[5 + i].innerText = `${bagdata[i].slice(0, -1).join(' ')}`; } $('.pop_main').show(); } catch (err) { console.log(err); } }); if (document.getElementById('equipmentDiv') == null) { backpacksObserver.disconnect(); addCollapse(); backpacksObserver.observe(document.getElementById('backpacks'), { childList : true , characterData : true }); } break; } case 1: { let roleId = g_roleMap.get(document.querySelector('#backpacks > div.row > div.col-md-3 > span.text-info.fyg_f24') ?.innerText)?.id; if (roleId != undefined) { calcBtn.onclick = (() => { calcDiv.innerHTML = `<div class="pop_main"><div class="pop_con"> <div class="pop_text"></div><div class="pop_text"></div> </div><a href="#">× 折叠 ×</a></div>`; $('.pop_main a').click(() => { $('.pop_main').hide(); }) let text = $('.pop_text'); let cardInfos = document.querySelector('#backpacks').querySelectorAll('.icon.icon-angle-down.text-primary'); let cardInfo = [ g_roleMap.get(roleId)?.shortMark ?? 'UNKNOW', cardInfos[0].innerText.match(/\d+/)[0], cardInfos[2].innerText.match(/\d+/)[0], cardInfos[1].innerText.match(/\d+/)[0] ]; if (roleId == 3000 || roleId == 3009) { cardInfo.splice(1, 0, 'G=' + (cardInfos[3]?.innerText.match(/\d+/)[0] ?? '0')); } let points = []; for (let i = 1; i <= 6; i++) { points.push(document.getElementById('sjj' + i).innerText); } text[0].innerText = cardInfo.join(' '); text[1].innerText = points.join(' '); $('.pop_main').show(); }); backpacksObserver.disconnect(); addBindBtn(); backpacksObserver.observe(document.getElementById('backpacks'), { childList : true , characterData : true }); } else { calcBtn.onclick = (() => {}); } break; } case 2: { calcBtn.onclick = (() => { try { calcDiv.innerHTML = `<div class="pop_main"><div class="pop_con"> <div class="pop_text"></div></div> <a href="#">× 折叠 ×</a></div>`; $('.pop_main a').click(() => { $('.pop_main').hide(); }) let text = $('.pop_text'); let aura = document.querySelectorAll('#backpacks .btn.btn-primary'); let data = []; data.push(aura.length); aura.forEach((item) => { data.push(g_haloMap.get(item.childNodes[1].nodeValue.trim())?.shortMark ?? ''); }); text[0].innerText = data.join(' '); $('.pop_main').show(); } catch (err) { console.log(err); } }); break; } case 3: { calcBtn.onclick = (() => {}); break; } } }); backpacksObserver.observe(document.getElementById('backpacks'), { childList : true , characterData : true }); document.getElementById('backpacks').appendChild(document.createElement('div')); } }, 500); } else if (window.location.pathname == '/fyg_beach.php') { genericPopupInitialize(); let beachConfigDiv = document.createElement('form'); beachConfigDiv.innerHTML = `<div class="form-group form-check" style="padding:5px 15px;border-bottom:1px solid grey;"> <button type="button" style="margin-right:15px;" id="siftSettings">筛选设置</button> <label class="form-check-label" for="ignoreStoreMysEquip" title="不将海滩上的装备与已有的神秘装备做比较" style="margin-right:5px;cursor:pointer;">已有神秘装备不作为筛选依据</label> <input type="checkbox" class="form-check-input" id="ignoreStoreMysEquip" style="margin-right:15px;" /> <label class="form-check-label" for="forceExpand" style="margin-right:5px;cursor:pointer;">强制展开所有装备</label> <input type="checkbox" class="form-check-input" id="forceExpand" style="margin-right:15px;" /> <b><span id="analyze-indicator">正在分析...</span></b> <div style="float:right;"><label class="form-check-input" for="beach_BG" style="margin-right:5px;cursor:pointer;">使用深色背景</label> <input type="checkbox" class="form-check-input" id="beach_BG" /></div></div>`; let ignoreStoreMysEquip = beachConfigDiv.querySelector('#ignoreStoreMysEquip').checked = (localStorage.getItem(g_beachIgnoreStoreMysEquipStorageKey) == 'true'); beachConfigDiv.querySelector('#ignoreStoreMysEquip').onchange = (() => { localStorage.setItem(g_beachIgnoreStoreMysEquipStorageKey, ignoreStoreMysEquip = beachConfigDiv.querySelector('#ignoreStoreMysEquip').checked); document.getElementById('analyze-indicator').innerText = '正在分析...'; setTimeout(() => { expandEquipment(equipment); }, 50); }); let forceExpand = beachConfigDiv.querySelector('#forceExpand').checked = (localStorage.getItem(g_beachForceExpandStorageKey) == 'true'); beachConfigDiv.querySelector('#forceExpand').onchange = (() => { localStorage.setItem(g_beachForceExpandStorageKey, forceExpand = beachConfigDiv.querySelector('#forceExpand').checked); document.getElementById('analyze-indicator').innerText = '正在分析...'; setTimeout(() => { expandEquipment(equipment); }, 50); }); let beach_BG = beachConfigDiv.querySelector('#beach_BG').checked = (localStorage.getItem(g_beachBGStorageKey) == 'true'); beachConfigDiv.querySelector('#beach_BG').onchange = (() => { localStorage.setItem(g_beachBGStorageKey, beach_BG = beachConfigDiv.querySelector('#beach_BG').checked); changeBeachStyle('beach_copy', beach_BG); }); beachConfigDiv.querySelector('#siftSettings').onclick = (() => { let fixedContent = '<div style="padding:20px 0px 10px;"><b><ul style="font-size:15px;color:#0000c0;">' + '<li>被勾选的装备不会被展开,不会产生与已有装备的对比列表,但传奇、史诗及有神秘属性的装备除外</li>' + '<li>未勾选的属性被视为主要属性,海滩装备的任一主要属性值大于已有装备的相应值时即会被展开</li>' + '<li>被勾选的属性被视为次要属性,当且仅当海滩装备和已有装备的主要属性值完全相等时才会被对比</li></ul></b></div>'; let mainContent = `<style> #equip-table { width:100%; } #equip-table tr.equip-tr { } #equip-table tr.equip-tr-alt { background-color:${g_genericPopupBackgroundColorAlt}; } #equip-table th { width:17%; text-align:right; } #equip-table th.equip-th-equip { width:32%; text-align:left; } #equip-table td { display:table-cell; text-align:right; } #equip-table td.equip-td-equip { display:table-cell; text-align:left; } #equip-table label.equip-checkbox-label { margin-left:5px; cursor:pointer; } </style> <div class="${g_genericPopupTopLineDivClass}"><table id="equip-table"> <tr><th class="equip-th-equip">装备名称</th><th>装备属性</th><th /><th /><th /></tr></table><div>`; genericPopupSetFixedContent(fixedContent); genericPopupSetContent('海滩装备筛选设置', mainContent); let equipTable = document.getElementById('equip-table'); let equipTypeColor = [ '#000080', '#008000', '#800080', '#008080' ]; g_equipments.forEach((equip) => { let tr = document.createElement('tr'); tr.id = `equip-index-${equip.index}`; tr.className = 'equip-tr' + ((equip.index & 1) == 0 ? ' equip-tr-alt' : ''); tr.setAttribute('equip-abbr', equip.shortMark); tr.style.color = equipTypeColor[equip.type]; let attrHTML = ''; equip.attributes.forEach((item, index) => { let attrId = `${tr.id}-attr-${index}`; attrHTML += `<td><input type="checkbox" class="sift-settings-checkbox" id="${attrId}" /> <label class="equip-checkbox-label" for="${attrId}">${item.attribute.name}</label></td>`; }); let equipId = `equip-${equip.index}`; tr.innerHTML = `<td class="equip-td-equip"><input type="checkbox" class="sift-settings-checkbox" id="${equipId}" /> <label class="equip-checkbox-label" for="${equipId}">${equip.name}</label></td>${attrHTML}`; equipTable.appendChild(tr); }); let udata = loadUserConfigData(); if (udata.dataBeachSift == null) { udata.dataBeachSift = {}; saveUserConfigData(udata); } let eqchecks = document.getElementsByClassName('sift-settings-checkbox'); for (let i = 0; i < eqchecks.length; i += 5) { let abbr = eqchecks[i].parentNode.parentNode.getAttribute('equip-abbr'); if (udata.dataBeachSift[abbr] != null) { let es = udata.dataBeachSift[abbr].split(','); for (let j = 0; j < es.length; j++) { eqchecks[i + j].checked = (es[j] == 'true'); } } } genericPopupAddButton('全选', 80, (() => { $('#equip-table .sift-settings-checkbox').prop('checked', true); }), true); genericPopupAddButton('全不选', 80, (() => { $('#equip-table .sift-settings-checkbox').prop('checked', false); }), true); genericPopupAddButton( '确认', 80, (() => { let settings = {}; equipTable.querySelectorAll('tr.equip-tr').forEach((row) => { let checks = []; row.querySelectorAll('input.sift-settings-checkbox').forEach((col) => { checks.push(col.checked); }); settings[row.getAttribute('equip-abbr')] = checks.join(','); }); let udata = loadUserConfigData(); udata.dataBeachSift = settings; saveUserConfigData(udata); genericPopupClose(true); }), false); genericPopupAddCloseButton(80); genericPopupSetContentSize(Math.min(g_equipments.length * 31 + 65, Math.max(window.innerHeight - 200, 500)), Math.min(750, Math.max(window.innerWidth - 100, 600)), true); genericPopupShowModal(true); }); let panel = document.getElementsByClassName('panel panel-primary')[2] panel.insertBefore(beachConfigDiv, panel.children[1]); let batbtns = document.getElementsByClassName('panel-body')[1].children[0]; let toAmuletBtn = document.createElement('button'); toAmuletBtn.className = 'btn btn-danger'; toAmuletBtn.innerText = '批量沙滩装备转护符'; toAmuletBtn.style.marginLeft = '1px'; toAmuletBtn.onclick = equipToAmulet; batbtns.appendChild(toAmuletBtn); function equipToAmulet() { function divHeightAdjustment(div) { div.style.height = (div.parentNode.offsetHeight - div.offsetTop - 3) + 'px'; } function moveAmuletItem(e) { let li = e.target; if (li.tagName == 'LI') { let liIndex = parseInt(li.getAttribute('item-index')); let container = (li.parentNode == amuletToStoreList ? amuletToDestroyList : amuletToStoreList); for (var li0 = container.firstChild; parseInt(li0?.getAttribute('item-index')) < liIndex; li0 = li0.nextSibling); container.insertBefore(li, li0); } } function refreshBackpacks(fnFurtherProcess) { let asyncOperations = 1; let asyncObserver = new MutationObserver(() => { asyncObserver.disconnect(); asyncOperations = 0; }); asyncObserver.observe(document.getElementById('backpacks'), { childList : true , subtree : true }); stbp(); let timer = setInterval(() => { if (asyncOperations == 0) { clearInterval(timer); if (fnFurtherProcess != null) { fnFurtherProcess(); } } }, 200); } function queryObjects(bag, queryBagId, ignoreEmptyCell, beach, beachEquipLevel) { if (bag != null) { let nodes = document.getElementById('backpacks').children; if (queryBagId) { objectIdParseNodes(nodes, bag, null, ignoreEmptyCell); } else { let i = 0; for (let node of nodes) { let e = ((new Amulet()).fromNode(node) ?? equipmentInfoParseNode(node)); if (e != null) { bag.push([ i++, e ]); } } } } if (beach != null) { let nodes = document.getElementById('beachall').children; for (let node of nodes) { let lv = equipmentGetNodeLevel(node); if (lv > 1) { let e = equipmentInfoParseNode(node); if (e != null && ((lv == 2 && parseInt(e[1]) >= beachEquipLevel) || lv > 2)) { beach.push(parseInt(e[7])); } } } } } const pirl_verify_data ='124'; const pirl_data = getPostData(/pirl\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*\+pirlyz\+.*"/).slice(7, -1) .replace('"+pirlyz+"', pirl_verify_data); let equipPirlRequestsCount = 0; function pirlEquip(objects, fnFurtherProcess, fnParams) { if (objects?.length > 0) { let ids = []; while (ids.length < g_maxConcurrentRequests && objects.length > 0) { ids.push(objects.pop()); } if ((equipPirlRequestsCount = ids.length) > 0) { while (ids.length > 0) { let request = GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: pirl_data.replace('"+id+"', ids.shift()), onload: response => { if (--equipPirlRequestsCount == 0) { pirlEquip(objects, fnFurtherProcess, fnParams); } } }); httpRequestRegister(request); } return; } } if (fnFurtherProcess != null) { fnFurtherProcess(fnParams); } } let pickCount; function pickEquip() { genericPopupShowInformationTips('拾取装备...', 0); let ids = []; while (originalBeach.length > 0 && ids.length < freeCell) { ids.unshift(originalBeach.pop()); } pickCount = ids.length; beginMoveObjects(ids, g_object_move_path.beach2bag, refreshBackpacks, findPickedEquip); } function findPickedEquip() { let bag = []; queryObjects(bag, true, true, null, 0); let ids = findNewObjects(bag, originalBag, (a, b) => a - b, false); if (ids.length != pickCount) { alert('从海滩拾取装备出错无法继续,请手动处理!'); window.location.reload(); return; } genericPopupShowInformationTips('熔炼装备...', 0); pirlEquip(ids, refreshBackpacks, prepareNewAmulets); } const amuletTypeLevelColor = [ '#c0ffc0', '#ffffc0', '#ffc0c0', '#c0e0ff' ]; let minBeachAmuletPointsToStore = [ 1, 1, 1 ]; let cfg = g_configMap.get('minBeachAmuletPointsToStore')?.value?.split(','); if (cfg?.length == 3) { cfg.forEach((item, index) => { if (isNaN(minBeachAmuletPointsToStore[index] = parseInt(item))) { minBeachAmuletPointsToStore[index] = 1; } }); } function prepareNewAmulets() { newAmulets = findNewObjects(amuletNodesToArray(document.getElementById('backpacks').children, [], null), originalBag, (a, b) => a.id - b, false); if (newAmulets.length != pickCount) { alert('熔炼装备出错无法继续,请手动处理!'); window.location.reload(); return; } newAmulets.forEach((am, index) => { let li = document.createElement('li'); li.style.backgroundColor = amuletTypeLevelColor[am.level]; li.setAttribute('item-index', index); li.innerText = (am.type == 2 || am.level == 2 ? '★ ' : '') + am.formatBuffText(); (am.getTotalPoints() < minBeachAmuletPointsToStore[am.type] ? amuletToDestroyList : amuletToStoreList).appendChild(li); }); if (window.getSelection) { window.getSelection().removeAllRanges(); } else if (document.getSelection) { document.getSelection().removeAllRanges(); } genericPopupShowInformationTips((originalBeach.length > 0 ? '本批' : '全部') + '装备熔炼完成,请分类后继续', 0); btnContinue.innerText = `继续 (剩余 ${originalBeach.length} 件装备)`; btnContinue.disabled = ''; } function processNewAmulets() { btnContinue.disabled = 'disabled'; if (freeCell == 0) { scheduleFreeCell(); } else if (pickCount > 0) { let indices = []; for (let li of amuletToDestroyList.children) { indices.push(parseInt(li.getAttribute('item-index'))); } if (indices.length > 0) { let ids = []; let warning = 0; indices.sort((a, b) => a - b).forEach((i) => { let am = newAmulets[i]; if (am.type == 2 || am.level == 2) { warning++; } ids.push(am.id); }); if (warning > 0 && !confirm(`这将把 ${warning} 个“樱桃/传奇”护符转换成果核,要继续吗?`)) { btnContinue.disabled = ''; return; } amuletToDestroyList.innerHTML = ''; coresCollected += indices.length; pickCount -= indices.length; genericPopupShowInformationTips('转换果核...', 0); pirlEquip(ids, refreshBackpacks, processNewAmulets); } else { let bag = []; queryObjects(bag, true, true, null, 0); let ids = findNewObjects(bag, originalBag, (a, b) => a - b, false); if (ids.length != pickCount) { alert('将新护符放入仓库出错无法继续,请手动处理!'); window.location.reload(); return; } amuletToStoreList.innerHTML = ''; amuletsCollected += pickCount; pickCount = 0; genericPopupShowInformationTips('放入仓库...', 0); beginMoveObjects(ids, g_object_move_path.bag2store, refreshBackpacks, processNewAmulets); } } else if (originalBeach.length > 0) { pickEquip(); } else { restoreBag(); } } let amuletsCollected = 0; let coresCollected = 0; let newAmulets = null; let freeCell = 0; let originalBag = []; let originalBeach = []; let scheduledObjects = { equips : [] , amulets : [] }; let minBeachEquipLevelToAmulet = (g_configMap.get('minBeachEquipLevelToAmulet')?.value ?? 1); queryObjects(originalBag, true, false, originalBeach, minBeachEquipLevelToAmulet); if (originalBeach.length == 0) { alert('海滩无可熔炼装备!'); return; } function prepareBagList() { genericPopupShowInformationTips('请将部分背包内容入仓以提供必要的操作空间,点击“继续”开始', 0); amuletToStoreList.parentNode.parentNode.children[0].innerText = '背包内容'; amuletToDestroyList.parentNode.parentNode.children[0].innerText = '临时入仓'; queryObjects(originalBag = [], false, true, null, 0); let bag = originalBag.slice().sort((a, b) => { if (Array.isArray(a[1]) && Array.isArray(b[1])) { return equipmentInfoComparer(a[1], b[1]); } else if (Array.isArray(a[1])){ return -1; } else if (Array.isArray(b[1])){ return 1; } return a[1].compareTo(b[1], true); }); bag.forEach((item, index) => { let e = item[1]; let isEq = Array.isArray(e); let li = document.createElement('li'); li.style.backgroundColor = (isEq ? amuletTypeLevelColor[3] : amuletTypeLevelColor[e.type]); li.setAttribute('item-index', index); li.setAttribute('original-index', item[0]); li.innerText = (isEq ? `${g_equipMap.get(e[0]).name} Lv.${e[1]}` : e.formatBuffText()); amuletToStoreList.appendChild(li); }); } function scheduleFreeCell() { if (freeCell == 0 && !(amuletToDestroyList?.children?.length > 0)) { alert('背包已满,请选择至少一个背包内容暂时入仓以提供必要的操作空间。'); btnContinue.disabled = ''; return; } let indices = []; for (let li of amuletToDestroyList.children) { indices.push(parseInt(li.getAttribute('original-index'))); } indices.sort((a, b) => parseInt(a) - parseInt(b)); let ids = []; indices.forEach((i) => { let e = originalBag[i][1]; let isEq = Array.isArray(e); ids.push(isEq ? e[7] : e.id); (isEq ? scheduledObjects.equips : scheduledObjects.amulets).push(e); }); genericPopupShowInformationTips('临时放入仓库...', 0); beginMoveObjects(ids, g_object_move_path.bag2store, refreshBackpacks, refreshOriginalBag); function refreshOriginalBag() { amuletToStoreList.innerHTML = ''; amuletToDestroyList.innerHTML = ''; queryObjects(originalBag = [], true, false, null, 0); while (originalBag[originalBag.length - 1] == -1) { originalBag.pop(); freeCell++; } if (freeCell == 0) { alert('背包已满,请选择至少一个背包内容暂时放入仓库以提供必要的操作空间。'); prepareBagList(); btnContinue.disabled = ''; } else { amuletToStoreList.parentNode.parentNode.children[0].innerText = '放入仓库'; amuletToDestroyList.parentNode.parentNode.children[0].innerText = '转换果核'; originalBag.sort((a, b) => parseInt(a) - parseInt(b)); processNewAmulets(); } } } function restoreBag() { function restoreCompletion() { if (scheduledObjects.amulets.length > 0 || scheduledObjects.equips.length > 0) { alert('部分背包内容无法恢复,请手动处理!'); console.log(scheduledObjects.equips); console.log(scheduledObjects.amulets); scheduledObjects.equips = scheduledObjects.amulets = []; } document.getElementById(g_genericPopupInformationTipsId).previousSibling.innerText = `操作完成,共获得 ${amuletsCollected} 个护符, ${coresCollected} 个果核`; let countDown = 30; genericPopupShowInformationTips(`窗口将在 ${countDown} 秒后关闭`, 0); let timer = setInterval(() => { if (--countDown == 0) { clearInterval(timer); genericPopupClose(true); window.location.reload(); } else { genericPopupShowInformationTips(`窗口将在 ${countDown} 秒后关闭`, 0); } }, 1000); } if (scheduledObjects.equips.length == 0 && scheduledObjects.amulets.length == 0) { restoreCompletion(); } else { genericPopupShowInformationTips('恢复背包内容...', 0); beginRestoreObjects(null, scheduledObjects.amulets, scheduledObjects.equips, refreshBackpacks, restoreCompletion); } } let fixedContent = '<div style="width:100%;padding:10px 0px 0px 0px;font-size:16px;color:blue;"><b><span>双击条目进行分类间移动</span>' + `<span id="${g_genericPopupInformationTipsId}" style="float:right;color:red;font-size:15px;"></span></b></div>`; let mainContent = '<div style="display:block;height:96%;width:100%;">' + '<div style="position:relative;display:block;float:left;height:96%;width:48%;' + 'margin-top:10px;border:1px solid #000000;">' + '<div style="display:block;width:100%;padding:5px;border-bottom:2px groove #d0d0d0;margin-bottom:10px;">放入仓库</div>' + '<div style="position:absolute;display:block;height:1px;width:100%;overflow:scroll;">' + '<ul id="amulet_to_store_list" style="cursor:pointer;"></ul></div></div>' + '<div style="position:relative;display:block;float:right;height:96%;width:48%;' + 'margin-top:10px;border:1px solid #000000;">' + '<div style="display:block;width:100%;padding:5px;border-bottom:2px groove #d0d0d0;margin-bottom:10px;">转换果核</div>' + '<div style="position:absolute;display:block;height:1px;width:100%;overflow:scroll;">' + '<ul id="amulet_to_destroy_list" style="cursor:pointer;"></ul></div></div></div>'; genericPopupSetFixedContent(fixedContent); genericPopupSetContent('批量护符转换', mainContent); let amuletToStoreList = document.getElementById('amulet_to_store_list'); let amuletToDestroyList = document.getElementById('amulet_to_destroy_list'); amuletToStoreList.ondblclick = amuletToDestroyList.ondblclick = moveAmuletItem; while (originalBag[originalBag.length - 1] == -1) { originalBag.pop(); freeCell++; } if (freeCell > 0) { originalBag.sort((a, b) => parseInt(a) - parseInt(b)); genericPopupShowInformationTips('这会将海滩全部可熔炼装备转化为护符,请点击“继续”开始', 0); } else { prepareBagList(); } let btnContinue = genericPopupAddButton(`继续 (剩余 ${originalBeach.length} 件装备)`, 0, processNewAmulets, true); genericPopupAddButton('关闭', 80, (() => { genericPopupClose(true); window.location.reload(); }), false); genericPopupSetContentSize(400, 700, false); genericPopupShowModal(false); divHeightAdjustment(amuletToStoreList.parentNode); divHeightAdjustment(amuletToDestroyList.parentNode); } let asyncOperations = 2; let equipment = null; let equipedbtn = null; let bag = []; let store = []; beginReadObjects( bag, store, () => { equipedbtn = Array.from(bag.pop()).concat(Array.from(store.pop())); asyncOperations--; GM_xmlhttpRequest({ method: g_postMethod, url: g_readUrl, headers: g_postHeader, data: 'f=9', onload: response => { let div0 = document.createElement('div'); div0.innerHTML = response.responseText; equipedbtn = equipedbtn.concat(Array.from(div0.querySelectorAll('div.row > div.fyg_tc > button.fyg_mp3'))); equipedbtn.sort(objectNodeComparer); equipment = equipmentNodesToInfoArray(equipedbtn, []); document.getElementById('analyze-indicator').innerText = '分析完成'; asyncOperations--; } }); }, null); //分析装备并显示属性 var g_expandingEquipment = false; function expandEquipment(equipment) { if (g_expandingEquipment || !(equipedbtn?.length > 0) || !(equipment?.length > 0) || equipment[0] == -1) { document.getElementById('analyze-indicator').innerText = '分析完成'; return; } let beach_copy = document.getElementById('beach_copy'); if (beach_copy == null) { let beachall = document.getElementById('beachall'); beach_copy = beachall.cloneNode(); beachall.style.display = 'none'; beach_copy.id = 'beach_copy'; beach_copy.style.backgroundColor = beach_BG ? 'black' : 'white'; beachall.parentNode.insertBefore(beach_copy, beachall); let beachCopyObserver = new MutationObserver((mList) => { if (!g_expandingEquipment && mList?.length == 1 && mList[0].type == 'childList' && mList[0].addedNodes?.length == 1 && !(mList[0].removedNodes?.length > 0)) { let node = mList[0].addedNodes[0]; if (node.hasAttribute('role')) { node.remove(); } else if (node.className?.indexOf('popover') >= 0) { node.setAttribute('id', 'id_temp_apply_beach_BG'); changeBeachStyle('id_temp_apply_beach_BG', beach_BG); node.removeAttribute('id'); if (node.className?.indexOf('popover-') < 0) { let content = node.querySelector('.popover-content'); content.style.borderRadius = '5px'; content.style.border = (beach_BG ? '4px double white' : '4px double black'); } } } }); beachCopyObserver.observe(beach_copy, { childList : true }); } g_expandingEquipment = true; copyBeach(beach_copy); let udata = loadUserConfigData(); if (udata.dataBeachSift == null) { udata.dataBeachSift = {}; saveUserConfigData(udata); } let settings = {}; for (let abbr in udata.dataBeachSift) { let checks = udata.dataBeachSift[abbr].split(','); if (checks?.length == 5) { let setting = []; checks.forEach((checked) => { setting.push(checked.trim().toLowerCase() == 'true'); }); settings[abbr] = setting; } } const defaultSetting = [ false, false, false, false, false ]; beach_copy.querySelectorAll('.btn.fyg_mp3').forEach((btn) => { let isExpanding = false; let e = equipmentInfoParseNode(btn); if (e != null) { let eqLv = equipmentGetNodeLevel(btn); if (forceExpand || eqLv > 2 || btn.getAttribute('data-content')?.match(/\[神秘属性\]/) != null) { isExpanding = true; } let setting = (settings[e[0]] ?? defaultSetting); if (!setting[0]) { let isFind = false; for (let j = 0; j < equipment.length; j++) { if (equipment[j][0] == e[0] && !(ignoreStoreMysEquip && equipment[j][6] == 1)) { isFind = true; let e1 = [ parseInt(e[1]), parseInt(e[2]), parseInt(e[3]), parseInt(e[4]), parseInt(e[5]) ]; let e2 = [ parseInt(equipment[j][1]), parseInt(equipment[j][2]), parseInt(equipment[j][3]), parseInt(equipment[j][4]), parseInt(equipment[j][5]) ]; if (isExpanding = defaultobjectNodeComparer(setting, e[0], e1, e2)) { break; } } } if (!isFind) { isExpanding = true; } } } if (isExpanding) { let btn0 = document.createElement('button'); btn0.className = `btn btn-light ${btn.getAttribute('data-tip-class') ?? ''}`; btn0.style.minWidth = '200px'; btn0.style.padding = '0px'; btn0.style.marginBottom = '5px'; btn0.style.textAlign = 'left'; btn0.style.boxShadow = 'none'; btn0.style.lineHeight = '150%'; btn0.setAttribute('data-toggle', 'popover'); btn0.setAttribute('data-trigger', 'hover'); btn0.setAttribute('data-placement', 'bottom'); btn0.setAttribute('data-html', 'true'); btn0.setAttribute('onclick', btn.getAttribute('onclick')); let popover = document.createElement('div'); popover.innerHTML = '<style> .popover { max-width:100%; } </style>'; let eqMeta = g_equipMap.get(e[0]); equipedbtn.forEach((eqbtn) => { if (eqMeta.index == parseInt(eqbtn.dataset.metaIndex)) { let btn1 = document.createElement('button'); btn1.setAttribute('class', `btn btn-light ${eqbtn.getAttribute('data-tip-class') ?? ''}`); btn1.style.cssText = 'min-width:180px;padding:10px 5px 0px 5px;text-align:left;box-shadow:none;background-color:none;' + 'line-height:120%;border-width:3px;border-style:double;margin-right:5px;margin-bottom:5px;'; btn1.innerHTML = eqbtn.dataset.content; if (btn1.lastChild.nodeType == 3) { //清除背景介绍文本 btn1.lastChild.remove(); } if (btn1.lastChild.className.indexOf('bg-danger') != -1) { btn1.lastChild.style.cssText = 'max-width:180px;padding:3px;white-space:pre-line;word-break:break-all;'; } popover.appendChild(btn1); } }); btn0.setAttribute('data-content', popover.innerHTML); btn0.innerHTML = `<h3 class="popover-title" style="background-color:${getComputedStyle(btn0).getPropertyValue('background-color')}">` + `${btn.dataset.originalTitle}</h3>` + `<div class="popover-content-show" style="padding:10px 10px 0px 10px;">${btn.dataset.content}</div>`; beach_copy.insertBefore(btn0, btn.nextSibling); } }); $(function() { $('#beach_copy .btn[data-toggle="popover"]').popover(); }); $('#beach_copy .bg-danger.with-padding').css({ 'max-width': '200px', 'padding': '5px', 'white-space': 'pre-line', 'word-break': 'break-all' }); changeBeachStyle('beach_copy', beach_BG); document.getElementById('analyze-indicator').innerText = '分析完成'; g_expandingEquipment = false; } function changeBeachStyle(container, bg) { $(`#${container}`).css({ 'background-color': bg ? 'black' : 'white' }); $(`#${container} .popover-content-show`).css({ 'background-color': bg ? 'black' : 'white' }); $(`#${container} .btn-light`).css({ 'background-color': bg ? 'black' : 'white' }); $(`#${container} .popover-title`).css({ 'color': bg ? 'black' : 'white' }); $(`#${container} .pull-right`).css({ 'color': bg ? 'black' : 'white' }); $(`#${container} .bg-danger.with-padding`).css({ 'color': bg ? 'black' : 'white' }); } //等待海滩装备加载 let beachTimer = setInterval(() => { if ($('#beachall .btn').length != 0) { clearInterval(beachTimer); //等待装备读取完成 let equipTimer = setInterval(() => { if (asyncOperations == 0) { clearInterval(equipTimer); document.getElementById('analyze-indicator').innerText = '正在分析...'; setTimeout(() => { expandEquipment(equipment); }, 50); let beachObserver = new MutationObserver(() => { document.getElementById('analyze-indicator').innerText = '正在分析...'; setTimeout(() => { expandEquipment(equipment); }, 50); }); beachObserver.observe(document.getElementById('beachall'), { childList : true }); } }, 500); } }, 500); function copyBeach(beach_copy) { beach_copy.innerHTML = ''; Array.from(document.getElementById('beachall').children).sort(sortBeach).forEach((node) => { beach_copy.appendChild(node.cloneNode(true)); }); } function sortBeach(a, b) { let delta = equipmentGetNodeLevel(a) - equipmentGetNodeLevel(b); if (delta == 0) { if ((delta = parseInt(a.innerText.match(/\d+/)[0]) - parseInt(b.innerText.match(/\d+/)[0])) == 0) { delta = (a.getAttribute('data-original-title') < b.getAttribute('data-original-title') ? -1 : 1); } } return -delta; } document.querySelector('body').style.paddingBottom = '1000px'; } else if (window.location.pathname == '/fyg_pk.php') { let autoTaskDiv = document.createElement('div'); autoTaskDiv.style.className = 'panel-heading'; autoTaskDiv.style.float = 'right'; autoTaskDiv.style.padding = '5px 15px'; autoTaskDiv.style.color = 'white'; autoTaskDiv.innerHTML = `<label class="form-check-label" for="autoTaskEnabledCheckbox" style="margin-right:5px;cursor:pointer;">允许执行自定义任务</label> <input type="checkbox" class="form-check-input" id="autoTaskEnabledCheckbox" />`; let autoTaskEnabled = autoTaskDiv.querySelector('#autoTaskEnabledCheckbox').checked = (localStorage.getItem(g_autoTaskEnabledStorageKey) == 'true'); autoTaskDiv.querySelector('#autoTaskEnabledCheckbox').onchange = (() => { localStorage.setItem(g_autoTaskEnabledStorageKey, autoTaskEnabled = autoTaskDiv.querySelector('#autoTaskEnabledCheckbox').checked); window.location.reload(); }); let keepCheckDiv = document.createElement('div'); keepCheckDiv.style.className = 'panel-heading'; keepCheckDiv.style.float = 'right'; keepCheckDiv.style.padding = '5px 15px'; keepCheckDiv.style.color = 'white'; keepCheckDiv.innerHTML = `<label class="form-check-label" for="keepPkRecordCheckbox" style="margin-right:5px;cursor:pointer;">暂时保持战斗记录</label> <input type="checkbox" class="form-check-input" id="keepPkRecordCheckbox" />`; let keepPkRecord = keepCheckDiv.querySelector('#keepPkRecordCheckbox').checked = (localStorage.getItem(g_keepPkRecordStorageKey) == 'true'); keepCheckDiv.querySelector('#keepPkRecordCheckbox').onchange = (() => { localStorage.setItem(g_keepPkRecordStorageKey, keepPkRecord = keepCheckDiv.querySelector('#keepPkRecordCheckbox').checked); }); let panel = document.getElementsByClassName('panel panel-primary'); panel[0].insertBefore(autoTaskDiv, panel[0].children[0]); panel[1].insertBefore(keepCheckDiv, panel[1].children[0]); let div0_pk_text_more = document.createElement('div'); div0_pk_text_more.setAttribute('id', 'pk_text_more'); div0_pk_text_more.setAttribute('class', 'panel-body'); document.getElementsByClassName('panel panel-primary')[1].appendChild(div0_pk_text_more); let pkText = document.querySelector('#pk_text').innerHTML; let pkObserver = new MutationObserver(() => { if (keepPkRecord) { document.querySelector('#pk_text_more').innerHTML = pkText + document.querySelector('#pk_text_more').innerHTML; pkText = document.querySelector('#pk_text').innerHTML; $('#pk_text_more .btn[data-toggle="tooltip"]').tooltip(); } }); pkObserver.observe(document.querySelector('#pk_text'), { characterData : true , childList : true }); if (autoTaskEnabled) { let btngroup0 = document.createElement('div'); btngroup0.setAttribute('class', 'action_selector'); btngroup0.innerHTML = `<p></p><div class="btn-group" role="group"> <button type="button" class="btn btn-secondary">0</button> <button type="button" class="btn btn-secondary">10</button> <button type="button" class="btn btn-secondary">20</button> <button type="button" class="btn btn-secondary">30</button> <button type="button" class="btn btn-secondary">40</button> <button type="button" class="btn btn-secondary">50</button> <button type="button" class="btn btn-secondary">60</button> <button type="button" class="btn btn-secondary">70</button> <button type="button" class="btn btn-secondary">80</button> <button type="button" class="btn btn-secondary">90</button> <button type="button" class="btn btn-secondary">100</button> </div>`; let btngroup1 = document.createElement('div'); btngroup1.setAttribute('class', 'action_selector'); btngroup1.innerHTML = `<p></p><div class="btn-group" role="group"> <button type="button" class="btn btn-secondary">0</button> <button type="button" class="btn btn-secondary">5</button> <button type="button" class="btn btn-secondary">10</button> <button type="button" class="btn btn-secondary">15</button> <button type="button" class="btn btn-secondary">20</button> <button type="button" class="btn btn-secondary">25</button> <button type="button" class="btn btn-secondary">30</button> <button type="button" class="btn btn-secondary">35</button> <button type="button" class="btn btn-secondary">40</button> <button type="button" class="btn btn-secondary">45</button> <button type="button" class="btn btn-secondary">50</button> <button type="button" class="btn btn-secondary">55</button> <button type="button" class="btn btn-secondary">60</button> <button type="button" class="btn btn-secondary">65</button> <button type="button" class="btn btn-secondary">70</button> <button type="button" class="btn btn-secondary">75</button> <button type="button" class="btn btn-secondary">80</button> <button type="button" class="btn btn-secondary">85</button> <button type="button" class="btn btn-secondary">90</button> <button type="button" class="btn btn-secondary">95</button> <button type="button" class="btn btn-secondary">100</button> </div>`; let btngroup2 = document.createElement('div'); btngroup2.setAttribute('class', 'action_selector'); btngroup2.innerHTML = `<p></p><div class="btn-group" role="group"> <button type="button" class="btn btn-secondary">0</button> <button type="button" class="btn btn-secondary">5</button> <button type="button" class="btn btn-secondary">10</button> <button type="button" class="btn btn-secondary">15</button> <button type="button" class="btn btn-secondary">20</button> <button type="button" class="btn btn-secondary">25</button> <button type="button" class="btn btn-secondary">30</button> <button type="button" class="btn btn-secondary">35</button> <button type="button" class="btn btn-secondary">40</button> <button type="button" class="btn btn-secondary">45</button> <button type="button" class="btn btn-secondary">50</button> <button type="button" class="btn btn-secondary">55</button> <button type="button" class="btn btn-secondary">60</button> <button type="button" class="btn btn-secondary">65</button> <button type="button" class="btn btn-secondary">70</button> <button type="button" class="btn btn-secondary">75</button> <button type="button" class="btn btn-secondary">80</button> <button type="button" class="btn btn-secondary">85</button> <button type="button" class="btn btn-secondary">90</button> <button type="button" class="btn btn-secondary">95</button> <button type="button" class="btn btn-secondary">100</button> </div>`; let taskObserver = new MutationObserver(() => { if (document.getElementsByClassName('btn-secondary').length == 0) { let addbtn = setInterval(() => { let col = document.querySelector('#pklist > div > div.col-md-8'); if (col != null) { clearInterval(addbtn); let obtns = document.getElementsByClassName('btn-block dropdown-toggle fyg_lh30'); col.insertBefore(btngroup0, obtns[0]); col.insertBefore(btngroup1, obtns[1]); col.insertBefore(btngroup2, obtns[2]); if (document.getElementsByClassName('btn-outline-secondary').length == 0) { if (localStorage.getItem('dataReward') == null) { localStorage.setItem('dataReward', '{"sumShell":"0","sumExp":"0"}'); } let ok = document.createElement('div'); ok.innerHTML = `<p></p><button type="button" class="btn btn-outline-secondary">任务执行</button>`; col.appendChild(ok); function gobattle() { let times = [ 0, 0, 0 ]; let sum = 0; $('.btn-secondary').each(function(i, e) { if ($(e).attr('style') != null && $(e).css('background-color') == 'rgb(135, 206, 250)') { let a = parseInt(e.innerText); let b = $('.btn-group .btn-secondary').index(e); sum += a; if (b < 11) { times[0] = a / 10; } else if (b >= 11 && b < 32) { times[1] = a / 5; } else if (b >= 32) { times[2] = a / 5; } } }); if (sum <= parseInt(document.getElementsByClassName('fyg_colpz03')[0].innerText)) { let gox_data = getPostData(/gox\(\)\{[\s\S]*\}/m, /data: ".*"/).slice(7, -1); let dataReward = JSON.parse(localStorage.getItem('dataReward')); let sum0 = parseInt(dataReward.sumShell); let sum1 = parseInt(dataReward.sumExp); function parseGainResponse(response) { let gainText = ''; if (response.startsWith('<p>获得了</p>')) { let gain; let sp = '获得'; let regex = /<span class="fyg_f18">x\s*(\d+)\s*([^<]+)<\/span>/g; while ((gain = regex.exec(response))?.length == 3) { gainText += `${sp}${gain[2].trim()}:${gain[1]}`; sp = ', '; } let lvlUp = response.match(/角色 \[ [^\s]+ \] 卡片等级提升!/g); if (lvlUp?.length > 0) { lvlUp.forEach((e) => { gainText += `${sp}${e}`; sp = ', '; }); } } return gainText; } function func0(time) { if (time == 0) { if (times[0] != 0) { GM_xmlhttpRequest({ method: g_postMethod, url: g_readUrl, headers: g_postHeader, data: 'f=12', onload: response => { let ap = response.responseText.match(/class="fyg_colpz03" style="font-size:32px;font-weight:900;">\d+</)[0].match(/>\d+</)[0].slice(1, -1); document.getElementsByClassName('fyg_colpz03')[0].innerText = ap; let rankp = response.responseText.match(/class="fyg_colpz02" style="font-size:32px;font-weight:900;">\d+%</)[0].match(/\d+%/)[0]; document.getElementsByClassName('fyg_colpz02')[0].innerText = rankp; let div_sum = document.createElement('div'); div_sum.innerText = `贝壳总次数:经验总次数=${sum0}:${sum1}=${(sum0 == 0 || sum1 == 0) ? 'undefined' : (sum0 / sum1).toFixed(4)}`; dataReward.sumShell = sum0; dataReward.sumExp = sum1; localStorage.setItem('dataReward', JSON.stringify(dataReward)); document.getElementsByClassName('btn-outline-secondary')[0].parentNode.appendChild(div_sum); times[0] = 0; } }); } return; } GM_xmlhttpRequest({ method: g_postMethod, url: g_postUrl, headers: g_postHeader, data: gox_data, onload: response => { let gainText = parseGainResponse(response.responseText); if (gainText.length > 0) { let div_info = document.createElement('div'); div_info.innerText = gainText; document.getElementsByClassName('btn-outline-secondary')[0].parentNode.appendChild(div_info); if (gainText.indexOf('贝壳') != -1) { sum0 += 1; } if (gainText.indexOf('经验') != -1) { sum1 += 1; } func0(time - 1); } else { let div_info = document.createElement('div'); div_info.innerText = '段位进度不足或无法识别的应答信息'; document.getElementsByClassName('btn-outline-secondary')[0].parentNode.appendChild(div_info); func0(0); } } }); } function func1(time) { if (time == 0) { times[1] = 0; return; } let observerPk = new MutationObserver((mutationsList, observer) => { let isPk = 0; for (let mutation of mutationsList) { if (mutation.type == 'childList') { isPk = 1; } } if (isPk) { observerPk.disconnect(); func1(time - 1); } }); observerPk.observe(document.querySelector('#pk_text'), { characterData : true , childList : true }); jgjg(1); } function func2(time) { if (time == 0) { times[2] = 0; return; } let observerPk = new MutationObserver((mutationsList, observer) => { let isPk = 0; for (let mutation of mutationsList) { if (mutation.type == 'childList') { isPk = 1; } } if (isPk) { observerPk.disconnect(); func2(time - 1); } }); observerPk.observe(document.querySelector('#pk_text'), { characterData : true , childList : true }); jgjg(2); } func0(times[0]); let waitFor0 = setInterval(() => { if (times[0] == 0) { clearInterval(waitFor0); func1(times[1]); } }, 1000); let waitFor1 = setInterval(() => { if (times[0] == 0 && times[1] == 0) { clearInterval(waitFor1); func2(times[2]); } }, 1000); } else { alert('体力不足'); } } document.getElementsByClassName('btn-outline-secondary')[0].addEventListener('click', gobattle, false); } function selector_act() { var btnNum = $('.btn-group .btn-secondary').index(this); $('.btn-group .btn-secondary') .eq(btnNum) .css('background-color', 'rgb(135, 206, 250)') .siblings('.btn-group .btn-secondary') .css('background-color', 'rgb(255, 255, 255)'); } let btnselector = document.getElementsByClassName('btn-secondary'); for (let i = 0; i < btnselector.length; i++) { btnselector[i].addEventListener('click', selector_act, false); } } }, 1000); } }); taskObserver.observe(document.getElementsByClassName('panel panel-primary')[0], { childList : true, subtree : true, }); } } else if (window.location.pathname == '/fyg_wish.php') { let timer = setInterval(() => { let wishPoints = parseInt(document.getElementById('xys_dsn')?.innerText); if (!isNaN(wishPoints)) { clearInterval(timer); function getWishPoints() { let text = 'WISH'; for (let i = 7; i <= 13; i++) { text += (' ' + (document.getElementById('xyx_' + ('0' + i).slice(-2))?.innerText ?? '0')); } return text; } let div = document.createElement('div'); div.className = 'row'; div.innerHTML = '<div class="panel panel-info"><div class="panel-heading"> 计算器许愿点设置 (' + '<a href="#" id="copyWishPoints">点击这里复制到剪贴板</a>)</div>' + '<input type="text" class="panel-body" id="calcWishPoints" readonly="true" style="border:none;outline:none;" value="" />'; let calcWishPoints = div.querySelector('#calcWishPoints'); calcWishPoints.value = getWishPoints(); let xydiv = document.getElementById('xydiv'); xydiv.parentNode.parentNode.insertBefore(div, xydiv.parentNode.nextSibling); div.querySelector('#copyWishPoints').onclick = ((e) => { calcWishPoints.select(); if (document.execCommand('copy')) { e.target.innerText = '许愿点设置已复制到剪贴板'; } else { e.target.innerText = '复制失败,这可能是因为浏览器没有剪贴板访问权限,请进行手工复制'; } }); (new MutationObserver(() => { calcWishPoints.value = getWishPoints(); })).observe(xydiv, { subtree : true , childList : true , characterData : true }); } }, 500); } })();