// ==UserScript== // @name 咕咕镇数据采集 // @license MIT License // @namespace https://greasyfork.org/users/448113 // @version 2.7.3 // @description 咕咕镇数据采集,目前采集已关闭,兼作助手 // @author paraii // @match https://www.guguzhen.com/* // @match https://www.momozhen.com/* // @connect www.guguzhen.com // @connect www.momozhen.com // @grant GM_info // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @downloadURL none // ==/UserScript== /* eslint-env jquery */ /* jshint esversion:12 */ (async function() { 'use strict' const g_isInSandBox = true; const g_version = g_isInSandBox ? GM_info.script.version : '2.7.3 (RP)'; const g_modiTime = '2024-12-11 00:00:00'; //////////////////////////////////////////////////////////////////////////////////////////////////// // // common utilities // //////////////////////////////////////////////////////////////////////////////////////////////////// const g_navigatorSelector = 'body > div > div.row > div.panel > div.panel-body > div'; const g_kfUser = document.querySelector(g_navigatorSelector + ' > button.btn.btn-lg')?.innerText; if (!(g_kfUser?.length > 0)) { console.log(`数据采集(${g_version}): 咕咕镇版本不匹配或正在测试`); return; } console.log(`数据采集(${g_version}): ${g_kfUser}`); const g_exportInterfaceId = 'gugu-assistant-interface-export'; let g_assistantInterface = null; const g_guguzhenLogin = '/fyg_login.php'; const g_guguzhenHome = '/fyg_index.php'; const g_guguzhenBeach = '/fyg_beach.php'; const g_guguzhenPK = '/fyg_pk.php'; const g_guguzhenEquip = '/fyg_equip.php'; const g_guguzhenWish = '/fyg_wish.php'; const g_guguzhenGem = '/fyg_gem.php'; const g_guguzhenShop = '/fyg_shop.php'; const g_guguzhenLog = '/fyg_ulog.php'; const g_showSolutionPanelStorageKey = g_kfUser + '_showSolutionPanel'; const g_showConfigPanelStorageKey = g_kfUser + '_showConfigPanel'; const g_indexRallyStorageKey = g_kfUser + '_indexRally'; const g_keepPkRecordStorageKey = g_kfUser + '_keepPkRecord'; const g_expandPkRecordStorageKey = g_kfUser + '_expandPkRecord'; const g_amuletGroupCollectionStorageKey = g_kfUser + '_amulet_Groups'; const g_equipmentExpandStorageKey = g_kfUser + '_equipment_Expand'; const g_equipmentStoreExpandStorageKey = g_kfUser + '_equipment_StoreExpand'; const g_equipmentBGStorageKey = g_kfUser + '_equipment_BG'; const g_beachForceExpandStorageKey = g_kfUser + '_beach_forceExpand'; const g_beachBGStorageKey = g_kfUser + '_beach_BG'; const g_gemConfigStorageKey = g_kfUser + '_gem_Config'; const g_userDataStorageKeyConfig = [ g_kfUser, g_showSolutionPanelStorageKey, g_showConfigPanelStorageKey, g_indexRallyStorageKey, g_keepPkRecordStorageKey, g_expandPkRecordStorageKey, g_amuletGroupCollectionStorageKey, g_equipmentExpandStorageKey, g_equipmentStoreExpandStorageKey, g_equipmentBGStorageKey, g_beachForceExpandStorageKey, g_beachBGStorageKey, g_gemConfigStorageKey ]; const g_gameVersionStorageKey = g_kfUser + '_gameVersion'; const g_forgeHistoryStorageKey = g_kfUser + '_forgeHistory'; const g_userDataStorageKeyNonConfig = [ g_gameVersionStorageKey, g_forgeHistoryStorageKey ]; // deprecated const g_amuletGroupsStorageKey = g_kfUser + '_amulet_groups'; const g_autoTaskEnabledStorageKey = g_kfUser + '_autoTaskEnabled'; const g_autoTaskCheckStoneProgressStorageKey = g_kfUser + '_autoTaskCheckStoneProgress'; const g_ignoreWishpoolExpirationStorageKey = g_kfUser + '_ignoreWishpoolExpiration'; const g_stoneProgressEquipTipStorageKey = g_kfUser + '_stone_ProgressEquipTip'; const g_stoneProgressCardTipStorageKey = g_kfUser + '_stone_ProgressCardTip'; const g_stoneProgressHaloTipStorageKey = g_kfUser + '_stone_ProgressHaloTip'; const g_stoneOperationStorageKey = g_kfUser + '_stoneOperation'; const g_forgeBoxUsageStorageKey = g_kfUser + '_forgeBoxUsageStorageKey'; const g_beachIgnoreStoreMysEquipStorageKey = g_kfUser + '_beach_ignoreStoreMysEquip'; const g_userDataStorageKeyExtra = [ g_amuletGroupsStorageKey, g_autoTaskEnabledStorageKey, g_autoTaskCheckStoneProgressStorageKey, g_ignoreWishpoolExpirationStorageKey, g_stoneProgressEquipTipStorageKey, g_stoneProgressCardTipStorageKey, g_stoneProgressHaloTipStorageKey, g_stoneOperationStorageKey, g_forgeBoxUsageStorageKey, g_beachIgnoreStoreMysEquipStorageKey, 'attribute', 'cardName', 'title', 'over', 'halo_max', 'beachcheck', 'dataReward', 'keepcheck' ]; // deprecated function beginCheckGameLog(fnPostProcess, fnParams) { let mode = (g_configMap.get('checkGameUpdate')?.value ?? 0); if (mode == 2 || (mode == 1 && window.location.pathname == g_guguzhenHome)) { let ov = localStorage.getItem(g_gameVersionStorageKey); httpRequestBegin( GuGuZhenRequest.log, '', (response) => { let nv = response.responseText?.match(/

(.*?)<\/h3>/)?.[1]?.trim(); if (nv?.length > 0) { if (nv != ov) { localStorage.setItem(g_gameVersionStorageKey, nv); ov ??= nv; } if (fnPostProcess != null) { fnPostProcess(ov, nv, fnParams); } } }); } } const USER_STORAGE_RESERVED_SEPARATORS = /[:;,|=+*%!#$&?<>{}^`"\\\/\[\]\r\n\t\v\s]/; const USER_STORAGE_KEY_VALUE_SEPARATOR = ':'; const g_userMessageContainerId = 'user-message-container'; const g_userMessageDivId = 'user-message-div'; const g_userMessageBtnId = 'user-message-btn'; let g_msgCount = 0; function addUserMessage(msgs, noNotification) { if (msgs?.length > 0) { let div = document.getElementById(g_userMessageDivId); if (div == null) { function clearNotification() { g_msgCount = 0; let btn = document.getElementById(g_userMessageBtnId); if (btn != null) { btn.style.display = 'none'; } } let div_row = document.createElement('div'); div_row.className = 'row'; div_row.id = g_userMessageContainerId; document.querySelector('div.row.fyg_lh60.fyg_tr').parentElement.appendChild(div_row); let div_pan = document.createElement('div'); div_pan.className = 'panel panel-info'; div_row.appendChild(div_pan); let div_head = document.createElement('div'); div_head.className = 'panel-heading'; div_head.innerText = '页面消息'; div_pan.appendChild(div_head); let div_op = document.createElement('div'); div_op.style.float = 'right'; div_head.appendChild(div_op); let link_mark = document.createElement('a'); link_mark.style.marginRight = '20px'; link_mark.innerText = '〇 已读'; link_mark.href = '###'; link_mark.onclick = (() => { clearNotification(); let m = document.getElementById(g_userMessageDivId).children; for (let e of m) { let name = e.firstElementChild; if (name.getAttribute('item-readed') != 'true') { name.setAttribute('item-readed', 'true'); name.style.color = 'grey'; name.innerText = name.innerText.substring(1); } } }); div_op.appendChild(link_mark); let link_clear = document.createElement('a'); link_clear.style.marginRight = '20px'; link_clear.innerText = '〇 清空'; link_clear.href = '###'; link_clear.onclick = (() => { clearNotification(); document.getElementById(g_userMessageDivId).innerHTML = ''; }); div_op.appendChild(link_clear); let link_top = document.createElement('a'); link_top.innerText = '〇 回到页首 ▲'; link_top.href = '###'; link_top.onclick = (() => { document.body.scrollIntoView(true); }); div_op.appendChild(link_top); div = document.createElement('div'); div.className = 'panel-body'; div.id = g_userMessageDivId; div_pan.appendChild(div); } if (!noNotification) { let btn = document.getElementById(g_userMessageBtnId); if (btn == null) { let navBar = document.querySelector(g_navigatorSelector); btn = navBar.firstElementChild.cloneNode(true); btn.id = g_userMessageBtnId; btn.className += ' btn-danger'; btn.setAttribute('onclick', `window.location.href='#${g_userMessageContainerId}'`); navBar.appendChild(btn); } btn.innerText = `查看消息(${g_msgCount += msgs.length})`; btn.style.display = 'inline-block'; } let timeStamp = getTimeStamp(); timeStamp = timeStamp.date + ' ' + timeStamp.time; let alt = (div.firstElementChild?.className?.length > 0); msgs.forEach((msg) => { let div_info = document.createElement('div'); div_info.className = (alt = !alt ? 'alt' : ''); div_info.style.backgroundColor = (alt ? '#f0f0f0' : ''); div_info.style.padding = '5px'; div_info.innerHTML = `★【${timeStamp}】${msg[0]}:` + `
${msg[1]}
`; div.insertBefore(div_info, div.firstElementChild); }); } } function addUserMessageSingle(title, msg, noNotification) { addUserMessage([[title, msg]], noNotification) } function getTimeStamp(date, dateSeparator, timeSeparator) { date ??= new Date(); dateSeparator ??= '-'; timeSeparator ??= ':'; return { date : `${('000' + date.getFullYear()).slice(-4)}${dateSeparator}${('0' + (date.getMonth() + 1)) .slice(-2)}${dateSeparator}${('0' + date.getDate()).slice(-2)}`, time : `${('0' + date.getHours()).slice(-2)}${timeSeparator}${('0' + date.getMinutes()) .slice(-2)}${timeSeparator}${('0' + date.getSeconds()).slice(-2)}` }; } function timeToMS(time) { return ((parseInt(time.substring(0, 2)) * 3600 + parseInt(time.substring(3, 5)) * 60 + parseInt(time.substring(6))) * 1000); } function loadUserConfigData() { return JSON.parse(localStorage.getItem(g_kfUser)); } function saveUserConfigData(json) { localStorage.setItem(g_kfUser, JSON.stringify(json)); } // generic configuration items represented using checkboxes function setupConfigCheckbox(checkbox, configKey, fnPostProcess, fnParams) { checkbox.checked = (localStorage.getItem(configKey) == 'true'); checkbox.onchange = ((e) => { localStorage.setItem(configKey, e.currentTarget.checked); if (fnPostProcess != null) { fnPostProcess(e.currentTarget.checked, fnParams); } }); return checkbox.checked; } // HTTP requests const AJAXRequestAPI = { auto : 0, sandBox : 1, jQuery : 2, webAPI : 3 }; const GuGuZhenRequest = { read : { method : 'POST' , url : '/fyg_read.php' }, update : { method : 'POST' , url : '/fyg_click.php' }, login : { method : 'GET' , url : g_guguzhenLogin }, user : { method : 'GET' , url : g_guguzhenHome }, beach : { method : 'GET' , url : g_guguzhenBeach }, pk : { method : 'GET' , url : g_guguzhenPK }, equip : { method : 'GET' , url : g_guguzhenEquip }, wish : { method : 'GET' , url : g_guguzhenWish }, gem : { method : 'GET' , url : g_guguzhenGem }, shop : { method : 'GET' , url : g_guguzhenShop }, log : { method : 'GET' , url : g_guguzhenLog } }; const MoMoZhenRequest = GuGuZhenRequest; const g_postHeader = { 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8' /*, 'Cookie' : document.cookie*/ }; let g_ajaxRequestAPI = (g_isInSandBox ? AJAXRequestAPI.sandBox : AJAXRequestAPI.jQuery); let g_httpRequests = []; function httpRequestBegin(request, queryString, fnLoad, fnError, fnTimeout) { let requestObj; if (g_isInSandBox && g_ajaxRequestAPI == AJAXRequestAPI.sandBox) { requestObj = GM_xmlhttpRequest({ method: request.method, url: window.location.origin + request.url, headers: g_postHeader, data: queryString, onload: fnLoad, onerror: fnError, ontimeout: fnTimeout }); } else if (g_ajaxRequestAPI == AJAXRequestAPI.webAPI) { requestObj = new XMLHttpRequest(); requestObj.onload = (fnLoad != null ? (e) => fnLoad(e.currentTarget) : undefined); requestObj.onerror = (fnError != null ? (e) => fnError(e.currentTarget) : undefined); requestObj.ontimeout = (fnTimeout != null ? (e) => fnTimeout(e.currentTarget) : undefined); requestObj.open(request.method, window.location.origin + request.url); for (let name in g_postHeader) { requestObj.setRequestHeader(name, g_postHeader[name]); } requestObj.send(queryString); } else { requestObj = $.ajax({ type: request.method, url: window.location.origin + request.url, data: queryString, contentType: g_postHeader['Content-Type'], global: false, success: (fnLoad != null ? (result, status, xhr) => fnLoad(xhr) : undefined), error: (fnError != null || fnTimeout != null ? (xhr, status, error) => { if (status == 'error' && fnError != null) { fnError(xhr); } else if (status == 'timeout' && fnTimeout != null) { fnTimeout(xhr); } } : undefined) }); } g_httpRequests.push(requestObj); return requestObj; } function httpRequestAbortAll() { while (g_httpRequests.length > 0) { g_httpRequests.pop().abort(); } g_httpRequests = []; } function httpRequestClearAll() { g_httpRequests = []; } // request data const g_httpRequestMap = new Map(); function getRequestInfoAsync(name, location, forceRefresh) { return new Promise((resolve) => { if (forceRefresh) { g_httpRequestMap.delete(name); } let r = g_httpRequestMap.get(name); if (r != null || !(name?.length > 0) || location == null) { resolve(r); } else { beginGetRequestMap( location, async () => { resolve(await getRequestInfoAsync(name, null)); }); } }); } function beginGetRequestMap(location, fnPostProcess, fnParams) { function searchScript(text) { let regex = //gms; let script; while ((script = regex.exec(text))?.length > 0) { searchFunction(script[0]); } if (fnPostProcess != null) { fnPostProcess(fnParams); } } function searchFunction(text) { let regex = /^\s*function\s+(.+?)\s*\(.+?\{(.+?)((^\s*function)|(<\/script>))/gms; let func; while ((func = regex.exec(text))?.length == 6 && func[1]?.length > 0 && func[2]?.length > 0) { let request = searchRequest(func[2]); if (request != null) { g_httpRequestMap.set(func[1], request); } if (func[3] != '<\/script>') { regex.lastIndex -= func[3].length; } else { break; } } } function searchRequest(text) { let method = text.match(/^\s*type\s*:\s*"(.+)"\s*,\s*$/m); let url = text.match(/^\s*url\s*:\s*"(.+)"\s*,\s*$/m); let data = text.match(/^\s*data\s*:\s*"(.+),\s*$/m); if (method?.length > 1 && url?.length > 1 && data?.length > 1) { return { request : { method : method[1], url : (url[1].startsWith('/') ? '' : '/') + url[1] }, data : data[1].endsWith('"') ? data[1].slice(0, -1) : data[1] }; } return null; } if (location == null) { searchScript(document.documentElement.innerHTML); } else { httpRequestBegin(location, '', (response) => { searchScript(response.responseText); }); } } beginGetRequestMap(); // read objects from bag and store with title filter function beginReadObjects(bag, store, fnPostProcess, fnParams) { if (bag != null || store != null) { httpRequestBegin( GuGuZhenRequest.read, 'f=7', (response) => { let div = document.createElement('div'); div.innerHTML = response.responseText; if (bag != null) { div.querySelectorAll('div.alert-danger > button.btn.fyg_mp3')?.forEach((e) => { bag.push(e); }); } if (store != null) { div.querySelectorAll('div.alert-success > button.btn.fyg_mp3')?.forEach((e) => { store.push(e); }); } if (fnPostProcess != null) { fnPostProcess(fnParams); } }); } else if (fnPostProcess != null) { fnPostProcess(fnParams); } } function beginReadObjectIds(bagIds, storeIds, key, ignoreEmptyCell, fnPostProcess, fnParams) { function parseObjectIds() { if (bagIds != null) { objectIdParseNodes(bag, bagIds, key, ignoreEmptyCell); } if (storeIds != null) { objectIdParseNodes(store, storeIds, key, ignoreEmptyCell); } if (fnPostProcess != null) { fnPostProcess(fnParams); } } let bag = (bagIds != null ? [] : null); let store = (storeIds != null ? [] : null); if (bag != null || store != null) { beginReadObjects(bag, store, parseObjectIds, null); } else if (fnPostProcess != null) { fnPostProcess(fnParams); } } function objectIdParseNodes(nodes, ids, key, ignoreEmptyCell) { for (let node of nodes) { if (node.className?.indexOf('fyg_mp3') >= 0) { let click = node.getAttribute('onclick'); let id = click?.match(/\d+/g); if (id?.length > 0) { id = id[click?.match(/omenu/)?.length > 0 ? id.length - 1 : 0]; if (id != null) { if (objectMatchTitle(node, key)) { ids.push(parseInt(id)); continue; } } } 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 ConcurrentRequestCount = { min : 1 , max : 8 , default : 4 }; const ObjectMovePath = { bag2store : 0 , store2bag : 1 , store2beach : 2 , beach2store : 3 }; const ObjectMoveRequestLocation = [ { location : GuGuZhenRequest.equip , name : 'puti' }, { location : GuGuZhenRequest.equip , name : 'puto' }, { location : GuGuZhenRequest.beach , name : 'stdel' }, { location : GuGuZhenRequest.beach , name : 'stpick' } ]; const ObjectMoveRequest = [ null, null, null, null ]; let g_maxConcurrentRequests = ConcurrentRequestCount.default; async function beginMoveObjects(objects, path, fnProgress, fnPostProcess, fnParams) { ObjectMoveRequest[path] ??= await getRequestInfoAsync(ObjectMoveRequestLocation[path].name, ObjectMoveRequestLocation[path].location); if (ObjectMoveRequest[path] == null) { console.log('missing function:', ObjectMoveRequestLocation[path].name); addUserMessageSingle('装备护符', '无法获取服务请求格式,可能的原因是咕咕镇版本不匹配或正在测试。'); if (fnPostProcess != null) { fnPostProcess(fnParams); } return; } let total = objects?.length; let count = 0; let moving = 0; let error = false; let abort = false; for (let i = 0; i < g_maxConcurrentRequests && beginMove(); i++); function beginMove() { if (!abort) { let id = objects?.pop(); if (id >= 0) { moving++; httpRequestBegin( ObjectMoveRequest[path].request, ObjectMoveRequest[path].data.replace('"+id+"', id), (response) => { if (path != ObjectMovePath.store2beach && response.responseText != 'ok') { addUserMessageSingle('装备护符', `装备或护符移动失败。
${response.responseText}
`); error = true; } else { count++; } moving--; abort = (abort || (fnProgress != null && !fnProgress(total, count, error))); beginMove(); }); return true; } } if (moving == 0 && fnPostProcess != null) { moving = -1; fnPostProcess(fnParams); } return false; } } const g_beach_pirl_verify_data = '85797'; const g_store_pirl_verify_data = '124'; const g_objectPirlRequest = g_httpRequestMap.get('pirl'); function beginPirlObjects(storePirl, objects, fnProgress, fnPostProcess, fnParams) { let requestData = g_objectPirlRequest.data.replace('"+pirlyz+"', storePirl ? g_store_pirl_verify_data : g_beach_pirl_verify_data); let total = objects?.length; let count = 0; let perling = 0; let error = false; let abort = false; for (let i = 0; i < g_maxConcurrentRequests && beginPirl(); i++); function beginPirl() { if (!abort) { let id = objects?.pop(); if (id >= 0) { perling++; httpRequestBegin( g_objectPirlRequest.request, requestData.replace('"+id+"', id), (response) => { if (!/\d+/.test(response.responseText) && response.responseText.indexOf('销毁') < 0) { addUserMessageSingle('销毁护符', `销毁护符失败。
${response.responseText}
`); error = true; } else { count++; } perling--; abort = (abort || (fnProgress != null && !fnProgress(total, count, error))); beginPirl(); }); return true; } } if (perling == 0 && fnPostProcess != null) { perling = -1; fnPostProcess(fnParams); } return false; } } // roleInfo = { isRoleInfo, meta, level, [ equips ] } function beginReadCurrentRole(roleInfo, force, fnPostProcess, fnParams) { function parseCarding(carding) { if (roleInfo != null) { let roleName = carding.querySelector('div.text-info.fyg_f18.fyg_lh60')?.children[0]; let unique = roleName?.querySelector('#unique'); let meta = g_roleMap.get((unique ?? roleName)?.innerText); if (roleInfo.isRoleInfo = (meta != null)) { roleInfo.meta = meta; roleInfo.level = parseInt(carding.querySelector ('div.text-info.fyg_f18.fyg_lh60')?.children[1]?.innerText?.match(/\d+/)?.[0] ?? 0); roleInfo.equips = carding.querySelectorAll('div.row > div.fyg_tc > button.btn.fyg_mp3.fyg_tc'); } } } let div = (force ? null : document.getElementById('carding')); if (div == null) { httpRequestBegin( GuGuZhenRequest.read, 'f=9', (response) => { div = document.createElement('div'); div.innerHTML = response.responseText; parseCarding(div); if (fnPostProcess != null) { fnPostProcess(fnParams); } }); return; } parseCarding(div); if (fnPostProcess != null) { fnPostProcess(fnParams); } } function readCurrentRoleAsync(force) { return new Promise((resolve) => { let roleInfo = {}; beginReadCurrentRole(roleInfo, force, () => { resolve(roleInfo.isRoleInfo ? roleInfo : null); }); }); } // cardInfo = { isCardInfo, meta, level, quality, haloSlots, growth, [ points ] } function beginReadCardInfo(card, cardInfo, force, fnPostProcess, fnParams) { function parseInfo(infoDiv) { if (cardInfo != null) { let infos = infoDiv.querySelectorAll('div.col-md-3.alert.alert-primary > span'); if (cardInfo.isCardInfo = (infos?.length > 0)) { cardInfo.meta = meta; cardInfo.level = parseInt(infos[0]?.innerText?.match(/\d+/)?.[0] ?? 0); cardInfo.quality = parseInt(infos[1]?.innerText?.match(/\d+/)?.[0] ?? 0); cardInfo.haloSlots = parseInt(infos[2]?.innerText?.match(/\d+/)?.[0] ?? 0); cardInfo.growth = parseInt(infos[3]?.innerText?.match(/\d+/)?.[0] ?? 0); cardInfo.points = []; cardInfo.repointLeft = parseInt(infoDiv.querySelector('div.btn-group > span.with-padding.bg-warning')?.innerText ?? 0); infoDiv.querySelectorAll('input.form-control[type="number"]')?.forEach((v) => { cardInfo.points.push(parseInt(v.value)); }); } } } let meta = g_roleMap.get(card); if (meta != null) { let div = (force ? null : document.getElementById('backpacks')); if (div != null && div.querySelector('#sjj1') != null) { let name = div.querySelector('div.row > div.col-md-3 > span.text-info.fyg_f24'); if (g_roleMap.get((name?.querySelector('#unique') ?? name)?.innerText)?.id != meta.id) { div = null; } } if (div == null) { httpRequestBegin( GuGuZhenRequest.read, 'f=18&zid=' + meta.id, (response) => { div ??= document.createElement('div'); div.innerHTML = response.responseText; parseInfo(div); if (fnPostProcess != null) { fnPostProcess(fnParams); } }); return; } parseInfo(div); } if (fnPostProcess != null) { fnPostProcess(fnParams); } } function readCardInfoAsync(card, force) { return new Promise((resolve) => { let cardInfo = {}; beginReadCardInfo(card, cardInfo, force, () => { resolve(cardInfo.isCardInfo ? cardInfo : null); }); }); } // haloInfo = { isHaloInfo, points, slots, items = [ selected1, selected2, ... ] } function beginReadHaloInfo(haloInfo, fnPostProcess, fnParams) { httpRequestBegin( GuGuZhenRequest.read, 'f=5', (response) => { if (haloInfo != null) { haloInfo.isHaloInfo = true; let div = document.createElement('div'); div.innerHTML = response.responseText; let haloPS = div.querySelector('div.alert.alert-info > h3')?.innerText?.match(/(\d+).+?(\d+)/); if (haloPS?.length == 3) { haloInfo.points = parseInt(haloPS[1]); haloInfo.slots = parseInt(haloPS[2]); } else { haloInfo.points = 0; haloInfo.slots = 0; } haloInfo.items = []; for (let id of (div.querySelector('script')?.innerHTML?.matchAll(/halotfzt2\((\d+)\)/g) ?? [])) { haloInfo.items.push(id[1]); } if (g_halos[0].description == null) { div.querySelectorAll('div.row > div.col-md-3 > div')?.forEach((h) => { let meta = g_haloMap.get(h.getAttribute('onclick')?.match(/\((\d+)\)/)?.[1]); if (meta != null) { meta.description = (h.title ?? h.getAttribute('data-original-title') ?? ''); } }); } } if (fnPostProcess != null) { fnPostProcess(fnParams); } }); } function readHaloInfoAsync() { return new Promise((resolve) => { let haloInfo = {}; beginReadHaloInfo(haloInfo, () => { resolve(haloInfo.isHaloInfo ? haloInfo : null); }); }); } // points = [ extraBagCells, wishPt_0, ..., wishPt_13 ], misc = [ ? yyyy ? , ? mmdd ? ] const g_wishpoolLength = 14; function beginReadWishpool(points, misc, fnPostProcess, fnParams) { httpRequestBegin( GuGuZhenRequest.read, 'f=19', (response) => { let a = response.responseText.split('#'); if (a.length >= g_wishpoolLength + 3) { if (misc != null) { misc[0] = a[0]; misc[1] = a[1]; } if (points != null) { let bagCells = Math.floor((parseInt(a[8]) + parseInt(a[9]) + parseInt(a[10]) + parseInt(a[11])) / 100); bagCells = Math.min(10, Math.max(bagCells, parseInt(a[2]))); points[0] = bagCells.toString() for (let i = 1; i <= g_wishpoolLength; i++) { points[i] = a[i + 2]; } } } if (fnPostProcess != null) { fnPostProcess(fnParams); } }); } function readWishpoolAsync(misc) { return new Promise((resolve) => { let points = []; beginReadWishpool(points, misc, () => { resolve(points.length == (g_wishpoolLength + 1) ? points : null); }); }); } // giftInfo = { isGiftInfo, peek, gift } function beginReadGiftZone(giftInfo, force, fnPostProcess, fnParams) { function parseGifts(giftsDiv) { if (giftInfo != null) { let btns = giftsDiv.querySelectorAll('button.btn.btn-lg.btn-block.fyg_lh60'); if (giftInfo.isGiftInfo = (btns?.length == 12)) { giftInfo.peek = giftsDiv.lastElementChild?.innerText?.match(/SVIP透视.+?“(.+?)”/)?.[1]; let gs = []; for (let btn of btns) { let g = btn.innerText.replaceAll(/\s/g, ''); if ((gs[g] = (gs[g] ?? 0) + 1) > 2) { giftInfo.gift = g; break; } } } } } let div = (force ? null : document.getElementById('gifsall')); if (div == null) { httpRequestBegin( GuGuZhenRequest.read, 'f=10', (response) => { div = document.createElement('div'); div.innerHTML = response.responseText; parseGifts(div); if (fnPostProcess != null) { fnPostProcess(fnParams); } }); return; } parseGifts(div); if (fnPostProcess != null) { fnPostProcess(fnParams); } } function readGiftZoneAsync(force) { return new Promise((resolve) => { let giftInfo = {}; beginReadGiftZone(giftInfo, force, () => { resolve(giftInfo.isGiftInfo ? giftInfo : null); }); }); } // userInfo = { isUserInfo, kfUser, grade, level, seashell, bvip, svip } function beginReadUserInfo(userInfo, fnPostProcess, fnParams) { httpRequestBegin( GuGuZhenRequest.user, '', (response) => { if (userInfo != null) { userInfo.isUserInfo = true; userInfo.kfUser = g_kfUser; userInfo.grade = (response.responseText.match(/\s*段位\s*<.+?>\s*(.+?)\s*<.+?<\/p>/)?.[1] ?? ''); userInfo.level = parseInt(response.responseText.match(/\s*等级\s*<.+?>\s*(\d+).*?<.+?<\/p>/)?.[1] ?? 0); userInfo.seashell = parseInt(response.responseText.match(/\s*贝壳\s*<.+?>\s*(\d+).*?<.+?<\/p>/)?.[1] ?? 0); userInfo.bvip = ((response.responseText.match(/\s*BVIP\s*<.+?>\s*(.+?)\s*<.+?<\/p>/)?.[1] ?? '').length > 0); userInfo.svip = ((response.responseText.match(/\s*SVIP\s*<.+?>\s*(.+?)\s*<.+?<\/p>/)?.[1] ?? '').length > 0); } if (fnPostProcess != null) { fnPostProcess(fnParams); } }); } function readUserInfoAsync() { return new Promise((resolve) => { let userInfo = {}; beginReadUserInfo(userInfo, () => { resolve(userInfo.isUserInfo ? userInfo : null); }); }); } function setupAddinExportInterface() { if (g_assistantInterface == null && (g_assistantInterface = document.getElementById(g_exportInterfaceId)?.assistantInterface) == null) { g_assistantInterface = { config : { get : (id) => g_configMap.get(id)?.value, refresh : readConfig, modify : modifyConfig }, timestamp : { get : getTimeStamp, timeToMS : timeToMS }, pageMessage : { add : addUserMessage, addSingle : addUserMessageSingle }, httpRequest : { GuGuZhenRequest : GuGuZhenRequest, MoMoZhenRequest : MoMoZhenRequest, begin : httpRequestBegin, abortAll : httpRequestAbortAll, clearAll : httpRequestClearAll, getInfoAsync : getRequestInfoAsync }, readInfo : { role : beginReadCurrentRole, card : beginReadCardInfo, halo : beginReadHaloInfo, wish : beginReadWishpool, gift : beginReadGiftZone, user : beginReadUserInfo, async : { role : readCurrentRoleAsync, card : readCardInfoAsync, halo : readHaloInfoAsync, wish : readWishpoolAsync, gift : readGiftZoneAsync, user : readUserInfoAsync } }, equip : { bag : { // TODO }, store : { // TODO }, amulet : { // TODO }, equip : { meta : { list : g_equipments, map : g_equipMap, }, // TODO }, property : { meta : { list : g_properties, map : g_propertyMap, }, read : beginReadProperties, // TODO }, }, genericPopup : { informationTipsId : g_genericPopupInformationTipsId, topLineDivClass : g_genericPopupTopLineDivClass, backgroundColor : g_genericPopupBackgroundColor, backgroundColorAlt : g_genericPopupBackgroundColorAlt, initialize : genericPopupInitialize, reset : genericPopupReset, contentContainer : genericPopupGetContentContainer, setContent : genericPopupSetContent, setFixedContent : genericPopupSetFixedContent, addButton : genericPopupAddButton, addCloseButton : genericPopupAddCloseButton, setContentSize : genericPopupSetContentSize, showModal : genericPopupShowModal, close : genericPopupClose, onClickOutside : genericPopupOnClickOutside, querySelector : genericPopupQuerySelector, querySelectorAll : genericPopupQuerySelectorAll, showInformationTips : genericPopupShowInformationTips, showProgressMessage : genericPopupShowProgressMessage, updateProgressMessage : genericPopupUpdateProgressMessage, closeProgressMessage : genericPopupCloseProgressMessage, taskPopup : { setup : genericPopupTaskListPopupSetup, setState : genericPopupTaskSetState, complete : genericPopupTaskComplete, abort : genericPopupTaskAbort, checkCompletion : genericPopupTaskCheckCompletion } }, binding : { SOLUTION_NAME_SEPARATOR : SOLUTION_NAME_SEPARATOR, list : readBindingSolutionList, switchByName : switchSolutionByName }, gift : { open : openGifts }, util : { search : searchElement, insert : insertElement, findNew : findNewObjects } }; let exp = document.createElement('div'); exp.id = g_exportInterfaceId; exp.style.display = 'none'; exp.assistantInterface = g_assistantInterface; document.body.appendChild(exp); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // // amulet management // //////////////////////////////////////////////////////////////////////////////////////////////////// const AMULET_STORAGE_GROUP_SEPARATOR = '|'; const AMULET_STORAGE_GROUPNAME_SEPARATOR = '='; const AMULET_STORAGE_AMULET_SEPARATOR = ','; const AMULET_STORAGE_CODEC_SEPARATOR = '-'; const AMULET_STORAGE_ITEM_SEPARATOR = ' '; const AMULET_STORAGE_ITEM_NV_SEPARATOR = '+'; const g_amuletDefaultLevelIds = { start : 0, end : 2, 稀有 : 0, 史诗 : 1, 传奇 : 2 }; const g_amuletDefaultTypeIds = { start : 2, end : 6, 星铜苹果 : 0, 蓝银葡萄 : 1, 紫晶樱桃 : 2 }; const g_amuletDefaultLevelNames = [ '稀有', '史诗', '传奇' ]; const g_amuletDefaultTypeNames = [ '星铜苹果', '蓝银葡萄', '紫晶樱桃' ]; const g_amuletBuffs = [ { index : -1 , name : '力量' , type : 0 , maxValue : 80 , unit : '点' , shortMark : 'STR' }, { index : -1 , name : '敏捷' , type : 0 , maxValue : 80 , unit : '点' , shortMark : 'AGI' }, { index : -1 , name : '智力' , type : 0 , maxValue : 80 , unit : '点' , shortMark : 'INT' }, { index : -1 , name : '体魄' , type : 0 , maxValue : 80 , unit : '点' , shortMark : 'VIT' }, { index : -1 , name : '精神' , type : 0 , maxValue : 80 , unit : '点' , shortMark : 'SPR' }, { index : -1 , name : '意志' , type : 0 , maxValue : 80 , unit : '点' , shortMark : 'MND' }, { index : -1 , name : '物理攻击' , type : 1 , maxValue : 10 , unit : '%' , shortMark : 'PATK' }, { index : -1 , name : '魔法攻击' , type : 1 , maxValue : 10 , unit : '%' , shortMark : 'MATK' }, { index : -1 , name : '速度' , type : 1 , maxValue : 10 , unit : '%' , shortMark : 'SPD' }, { index : -1 , name : '生命护盾回复效果' , type : 1 , maxValue : 10 , unit : '%' , shortMark : 'REC' }, { index : -1 , name : '最大生命值' , type : 1 , maxValue : 10 , unit : '%' , shortMark : 'HP' }, { index : -1 , name : '最大护盾值' , type : 1 , maxValue : 10 , unit : '%' , shortMark : 'SLD' }, { index : -1 , name : '固定生命偷取' , type : 2 , maxValue : 10 , unit : '%' , shortMark : 'LCH' }, { index : -1 , name : '固定反伤' , type : 2 , maxValue : 10 , unit : '%' , shortMark : 'RFL' }, { index : -1 , name : '固定暴击几率' , type : 2 , maxValue : 10 , unit : '%' , shortMark : 'CRT' }, { index : -1 , name : '技能几率加成' , type : 2 , maxValue : 10 , unit : '%' , shortMark : 'SKL' }, { index : -1 , name : '物理防御效果' , type : 2 , maxValue : 10 , unit : '%' , shortMark : 'PDEF' }, { index : -1 , name : '魔法防御效果' , type : 2 , maxValue : 10 , unit : '%' , shortMark : 'MDEF' }, { index : -1 , name : '全属性' , type : 2 , maxValue : 10 , unit : '点' , shortMark : 'AAA' }, { index : -1 , name : '暴击抵抗' , type : 2 , maxValue : 5 , unit : '%' , shortMark : 'CRTR' }, { index : -1 , name : '技能抵抗' , type : 2 , maxValue : 5 , unit : '%' , shortMark : 'SKLR' } ]; const g_amuletBuffMap = new Map(); g_amuletBuffs.forEach((item, index) => { item.index = index; g_amuletBuffMap.set(item.index, item); g_amuletBuffMap.set(item.index.toString(), item); g_amuletBuffMap.set(item.name, item); g_amuletBuffMap.set(item.shortMark, item); }); let g_amuletLevelIds = g_amuletDefaultLevelIds; let g_amuletTypeIds = g_amuletDefaultTypeIds; let g_amuletLevelNames = g_amuletDefaultLevelNames; let g_amuletTypeNames = g_amuletDefaultTypeNames; // deprecated function amuletLoadTheme(theme) { if (theme.dessertlevel?.length > 0 && theme.dessertname?.length > 0) { g_amuletLevelNames = theme.dessertlevel; g_amuletTypeNames = theme.dessertname; g_amuletLevelIds = { start : 0, end : theme.dessertlevel[0].length }; g_amuletTypeIds = { start : theme.dessertlevel[0].length, end : theme.dessertlevel[0].length + theme.dessertname[0].length }; for (let i = g_amuletLevelNames.length - 1; i >= 0; i--) { g_amuletLevelIds[g_amuletLevelNames[i].slice(0, g_amuletLevelIds.end - g_amuletLevelIds.start)] = i; } for (let i = g_amuletTypeNames.length - 1; i >= 0; i--) { g_amuletTypeIds[g_amuletTypeNames[i].slice(0, g_amuletTypeIds.end - g_amuletTypeIds.start)] = i; } } } // deprecated function Amulet() { this.isAmulet = true; this.id = -1; this.type = -1; this.level = 0; this.enhancement = 0; this.buffs = []; this.buffCode = null; this.text = null; this.reset = (() => { this.id = -1; this.type = -1; this.level = 0; this.enhancement = 0; this.buffs = []; this.buffCode = null; this.text = null; }); this.isValid = (() => { return (this.type >= 0 && this.type < g_amuletDefaultTypeNames.length); }); this.addItem = ((item, buff) => { if (this.isValid()) { let meta = g_amuletBuffMap.get(item); if (meta?.type == this.type && (buff = parseInt(buff)) > 0) { this.buffs[meta.index] = (this.buffs[meta.index] ?? 0) + buff; this.buffCode = null; return true; } else { this.reset(); } } return false; }); this.fromCode = ((code) => { this.reset(); let e = code?.split(AMULET_STORAGE_CODEC_SEPARATOR); if (e?.length == 4) { this.type = parseInt(e[0]); this.level = parseInt(e[1]); this.enhancement = parseInt(e[2]); e[3].split(AMULET_STORAGE_ITEM_SEPARATOR).forEach((item) => { let nv = item.split(AMULET_STORAGE_ITEM_NV_SEPARATOR); this.addItem(nv[0], nv[1]); }); this.getCode(); } return (this.isValid() ? this : null); }); this.fromBuffText = ((text) => { this.reset(); let nb = text?.split(' = '); if (nb?.length == 2) { this.type = (g_amuletTypeIds[nb[0].slice(g_amuletTypeIds.start, g_amuletTypeIds.end)] ?? g_amuletDefaultTypeIds[nb[0].slice(g_amuletDefaultTypeIds.start, g_amuletDefaultTypeIds.end)]); this.level = (g_amuletLevelIds[nb[0].slice(g_amuletLevelIds.start, g_amuletLevelIds.end)] ?? g_amuletDefaultLevelIds[nb[0].slice(g_amuletDefaultLevelIds.start, g_amuletDefaultLevelIds.end)]); this.enhancement = parseInt(nb[0].match(/\d+/)[0]); this.buffCode = 0; nb[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(',').forEach((buff) => { let nv = buff.trim().split(' '); this.addItem(nv[0], nv[1]); }); if (this.isValid()) { this.text = nb[1]; this.getCode(); return this; } } this.reset(); return null; }); this.fromNode = ((node) => { if (node?.nodeType == Node.ELEMENT_NODE) { if (this.fromBuffText(node.getAttribute('amulate-string')) != null && !isNaN(this.id = parseInt(node.getAttribute('onclick').match(/\d+/)?.[0]))) { return this; } else if (node.className?.indexOf('fyg_mp3') >= 0) { this.reset(); let typeName = (node.getAttribute('data-original-title') ?? node.getAttribute('title') ?? ''); if (!/Lv.+?>\d+(.+?)<\/span>/)?.[1]; if (unique != null) { typeName = unique; } let id = node.getAttribute('onclick'); let content = node.getAttribute('data-content'); if (id?.length > 0 && content?.length > 0 && !isNaN(this.type = (g_amuletTypeIds[typeName.slice(g_amuletTypeIds.start, g_amuletTypeIds.end)] ?? g_amuletDefaultTypeIds[typeName.slice(g_amuletDefaultTypeIds.start, g_amuletDefaultTypeIds.end)])) && !isNaN(this.level = (g_amuletLevelIds[typeName.slice(g_amuletLevelIds.start, g_amuletLevelIds.end)] ?? g_amuletDefaultLevelIds[typeName.slice(g_amuletDefaultLevelIds.start, g_amuletDefaultLevelIds.end)])) && !isNaN(this.id = parseInt(id.match(/\d+/)?.[0])) && !isNaN(this.enhancement = parseInt(node.innerText))) { this.text = ''; let attr = null; let regex = /(.+?)<.*?>\+(\d+).*?<\/span><\/p>/g; while ((attr = regex.exec(content))?.length == 3) { let buffMeta = g_amuletBuffMap.get(attr[1]); if (buffMeta != null) { if (!this.addItem(attr[1], attr[2])) { break; } this.text += `${this.text.length > 0 ? ', ' : ''}${attr[1]} +${attr[2]} ${buffMeta.unit}`; } } if (this.isValid()) { node.setAttribute('amulet-string', this.formatBuffText()); this.getCode(); return this; } } } } } this.reset(); return null; }); this.fromAmulet = ((amulet) => { this.reset(); if (amulet?.isValid()) { this.id = amulet.id; this.type = amulet.type; this.level = amulet.level; this.enhancement = amulet.enhancement; this.buffs = amulet.buffs.slice(); this.buffCode = amulet.buffCode; this.text = amulet.text; } return (this.isValid() ? this : null); }); this.getCode = (() => { if (this.isValid()) { if (!(this.buffCode?.length > 0)) { let bc = []; this.buffs.forEach((e, i) => { bc.push(i + AMULET_STORAGE_ITEM_NV_SEPARATOR + e); }); this.buffCode = bc.join(AMULET_STORAGE_ITEM_SEPARATOR); } return (this.type + AMULET_STORAGE_CODEC_SEPARATOR + this.level + AMULET_STORAGE_CODEC_SEPARATOR + this.enhancement + AMULET_STORAGE_CODEC_SEPARATOR + this.buffCode); } return null; }); this.getBuff = (() => { return this.buffs; }); this.getTotalPoints = (() => { let points = 0; this.buffs?.forEach((e) => { points += e; }); 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; } let bi = []; this.buffs.forEach((e, i) => { let meta = g_amuletBuffMap.get(i); bi.push(`${meta.name} +${e} ${meta.unit}`); }); this.text = bi.join(', '); } 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) { this.buffs.forEach((e, i) => { let meta = g_amuletBuffMap.get(i); text = text.replaceAll(meta.name, meta.exportAlias ?? meta.shortMark); }); return this.formatName() + ' = ' + text; } return null; }); this.compareMatch = ((other, ascType) => { if (!this.isValid()) { return 1; } else if (!other?.isValid()) { return -1; } let delta = other.type - this.type; if (delta != 0) { return (ascType ? -delta : delta); } return (other.buffCode > this.buffCode ? -1 : (other.buffCode < this.buffCode ? 1 : 0)); }); this.compareTo = ((other, ascType) => { if (!this.isValid()) { return 1; } else if (!other?.isValid()) { return -1; } 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_amuletBuffMap.get(tbuff[0])?.index ?? g_amuletBuffs.length) - (g_amuletBuffMap.get(obuff[0])?.index ?? g_amuletBuffs.length)) != 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.isAmuletGroup = true; this.name = null; this.items = []; this.buffSummary = []; this.isValid = (() => { return (this.items.length > 0 && amuletIsValidGroupName(this.name)); }); this.count = (() => { return this.items.length; }); this.clear = (() => { this.items = []; this.buffSummary = []; }); this.add = ((amulet) => { if (amulet?.isValid()) { amulet.buffs.forEach((e, i) => { this.buffSummary[i] = (this.buffSummary[i] ?? 0) + e; }); 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) { amulet.buffs.forEach((e, i) => { this.buffSummary[i] -= e; if (this.buffSummary[i] <= 0) { delete this.buffSummary[i]; } }); 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]; amulet.buffs.forEach((e, i) => { this.buffSummary[i] -= e; if (this.buffSummary[i] <= 0) { delete this.buffSummary[i]; } }); this.items.splice(i, 1); return amulet; } } return null; }); this.merge = ((group) => { group?.items?.forEach((am) => { this.add(am); }); return this; }); 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.compareMatch(b)); for ( ; amulets.length > 0 && i < al; i++) { let mi = searchElement(amulets, this.items[i], (a, b) => a.compareMatch(b)); 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) { let items = this.items.slice().sort((a, b) => a.compareMatch(b)); 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.compareMatch(b))) >= 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(items[i])) < 0) { this.clear(); break; } } if (this.count() > 0) { this.name = name; } } } } return (this.count() > 0); }); this.formatBuffSummary = ((linePrefix, lineSuffix, lineSeparator, ignoreMaxValue) => { if (this.isValid()) { let str = ''; let nl = ''; g_amuletBuffs.forEach((buff) => { let v = this.buffSummary[buff.index]; if (v > 0) { str += `${nl}${linePrefix}${buff.name} +${ignoreMaxValue ? v : Math.min(v, buff.maxValue)} ${buff.unit}${lineSuffix}`; nl = lineSeparator; } }); return str; } return ''; }); this.formatBuffShortMark = ((keyValueSeparator, itemSeparator, ignoreMaxValue) => { if (this.isValid()) { let str = ''; let sp = ''; g_amuletBuffs.forEach((buff) => { let v = this.buffSummary[buff.index]; if (v > 0) { str += `${sp}${buff.exportAlias ?? buff.shortMark}${keyValueSeparator}` + `${ignoreMaxValue ? v : Math.min(v, buff.maxValue)}`; sp = itemSeparator; } }); 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_amuletBuffs.forEach((buff) => { if (this.buffSummary[buff.index] > 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.isAmuletGroupCollection = true; this.items = {}; this.itemCount = 0; this.count = (() => { return this.itemCount; }); this.contains = ((name) => { return this.items.hasOwnProperty(name); }); 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 = (() => { this.items = {}; 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 = (() => { return Object.values(this.items); }); this.toNameArray = (() => { return Object.keys(this.items); }); 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_amuletGroupCollectionStorageKey, groups.formatPersistenceString()); } else { localStorage.removeItem(g_amuletGroupCollectionStorageKey); } } function amuletLoadGroups() { return new AmuletGroupCollection(localStorage.getItem(g_amuletGroupCollectionStorageKey)); } function amuletClearGroups() { localStorage.removeItem(g_amuletGroupCollectionStorageKey); } 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) { array ??= []; 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, fnPostProcess, fnParams) { function parseAmulets() { if (bagAmulets != null) { amuletNodesToArray(bag, bagAmulets, key); } if (storeAmulets != null) { amuletNodesToArray(store, storeAmulets, key); } if (fnPostProcess != null) { fnPostProcess(fnParams); } } let bag = (bagAmulets != null ? [] : null); let store = (storeAmulets != null ? [] : null); if (bag != null || store != null) { beginReadObjects(bag, store, parseAmulets, null); } else if (fnPostProcess != null) { fnPostProcess(fnParams); } } function beginLoadAmuletGroupsDiff(groupNames, fnProgress, fnPostProcess, fnParams) { let bag, store, loading; if (groupNames?.length > 0) { loading = []; groupNames.forEach((gn) => { let g = amuletLoadGroup(gn); if (g?.isValid()) { loading = loading.concat(g.items); } }); if (loading.length > 0) { if (fnProgress != null) { fnProgress(5, 1, loading.length); } beginReadAmulets(bag = [], store = [], null, beginUnload); return; } } if (fnPostProcess != null) { fnPostProcess(fnParams); } function beginUnload() { let ids = []; if (bag.length > 0) { let indices = findNewObjects(bag, loading.sort((a, b) => a.compareMatch(b)), true, true, (a, b) => a.compareMatch(b)).sort((a, b) => b - a); while (indices?.length > 0) { ids.push(bag[indices.pop()].id); } } if (fnProgress != null) { fnProgress(5, 2, ids.length); } beginMoveObjects( ids, ObjectMovePath.bag2store, fnProgress == null ? null : (total, count) => { fnProgress(1, count / total * 0.2 + 0.4, total - count); return true; }, beginLoad, ids.length); } function beginLoad(unloadedCount) { if (loading.length > 0 && store.length > 0) { if (unloadedCount == 0) { let indices = amuletCreateGroupFromArray('_', loading)?.findIndices(store)?.sort((a, b) => b - a); let ids = []; while (indices?.length > 0) { ids.push(store[indices.pop()].id); } if (fnProgress != null) { fnProgress(5, 4, ids.length); } beginMoveObjects( ids, ObjectMovePath.store2bag, fnProgress == null ? null : (total, count) => { fnProgress(1, count / total * 0.2 + 0.8, total - count); return true; }, fnPostProcess, fnParams); } else { if (fnProgress != null) { fnProgress(5, 3, loading.length); } beginReadAmulets(null, store = [], null, beginLoad, 0); } } else if (fnPostProcess != null) { fnPostProcess(fnParams); } } } function beginMoveAmulets({ groupName, amulets, path, prog, 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, prog, proc, params); } function beginLoadAmuletGroupFromStore(amulets, groupName, fnProgress, fnPostProcess, fnParams) { if (amulets?.length > 0) { let store = amuletNodesToArray(amulets); beginMoveAmulets({ groupName : groupName, amulets : store, path : ObjectMovePath.store2bag, prog : fnProgress, proc : fnPostProcess, params : fnParams }); } else { beginReadAmulets(null, amulets = [], null, beginMoveAmulets, { groupName : groupName, amulets : amulets, path : ObjectMovePath.store2bag, prog : fnProgress, proc : fnPostProcess, params : fnParams }); } } function beginUnloadAmuletGroupFromBag(amulets, groupName, fnProgress, fnPostProcess, fnParams) { if (amulets?.length > 0) { let bag = amuletNodesToArray(amulets); beginMoveAmulets({ groupName : groupName, amulets : bag, path : ObjectMovePath.bag2store, prog : fnProgress, proc : fnPostProcess, params : fnParams }); } else { beginReadAmulets(amulets, null, null, beginMoveAmulets, { groupName : groupName, amulets : amulets, path : ObjectMovePath.bag2store, prog : fnProgress, proc : fnPostProcess, params : fnParams }); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // // property utilities // //////////////////////////////////////////////////////////////////////////////////////////////////// const g_equipmentDefaultLevelName = [ '普通', '幸运', '稀有', '史诗', '传奇' ]; const g_equipmentLevelStyleClass = [ 'primary', 'info', 'success', 'warning', 'danger' ]; const g_equipmentLevelPoints = [ 200, 321, 419, 516, 585 ]; const g_equipmentLevelBGColor = [ '#e0e8e8', '#c0e0ff', '#c0ffc0', '#ffffc0', '#ffd0d0' ]; const g_properties = [ { index : -1 , id : 3001 , name : '体能刺激药水' }, { index : -1 , id : 3002 , name : '锻造材料箱' }, { index : -1 , id : 3003 , name : '灵魂药水' }, { index : -1 , id : 3004 , name : '随机装备箱' }, { index : -1 , id : 3005 , name : '宝石原石' }, { index : -1 , id : 3301 , name : '蓝锻造石' }, { index : -1 , id : 3302 , name : '绿锻造石' }, { index : -1 , id : 3303 , name : '金锻造石' }, { index : -1 , id : 3309 , name : '苹果核' }, { index : -1 , id : 3310 , name : '光环天赋石' } ]; const g_propertyMap = new Map(); g_properties.forEach((item, index) => { item.index = index; item.alias = item.name; g_propertyMap.set(item.id, item); g_propertyMap.set(item.id.toString(), item); g_propertyMap.set(item.name, item); }); // deprecated function propertyLoadTheme(theme) { if (theme.itemsname?.length > 0) { theme.itemsname.forEach((item, index) => { if (!g_propertyMap.has(item) && index < g_properties.length) { g_properties[index].alias = item; g_propertyMap.set(item, g_properties[index]); } }); } } // deprecated function propertyInfoParseNode(node) { function formatPropertyString(p) { return `${p.meta.index},${p.level},${p.amount}`; } function parsePropertyString(s) { let a = s.split(','); return { isProperty : true, meta : g_properties[parseInt(a[0])], level : parseInt(a[1]), amount : parseInt(a[2]) }; } if (node?.nodeType == Node.ELEMENT_NODE) { let s = node.getAttribute('property-string'); if (s?.length > 0) { return parsePropertyString(s); } else if (node.className?.split(' ').length >= 2 && node.className.indexOf('fyg_mp3') >= 0) { let title = (node.getAttribute('data-original-title') ?? node.getAttribute('title')); let unique = title?.match(/