// ==UserScript==
// @name 咕咕镇数据采集
// @license MIT License
// @namespace https://greasyfork.org/users/448113
// @version 1.8.16
// @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 */
(function() {
'use strict'
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// common utilities
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const g_modificationVersion = '2023-02-24 00:00:00';
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;
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_indexRallyStorageKey = g_kfUser + '_indexRally';
const g_keepPkRecordStorageKey = g_kfUser + '_keepPkRecord';
const g_amuletGroupsStorageKey = 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_indexRallyStorageKey, g_keepPkRecordStorageKey, g_amuletGroupsStorageKey,
g_equipmentExpandStorageKey, g_equipmentStoreExpandStorageKey, g_equipmentBGStorageKey,
g_beachForceExpandStorageKey, g_beachBGStorageKey, g_gemConfigStorageKey ];
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_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' ];
const USER_STORAGE_RESERVED_SEPARATORS = /[:;,|=+*%!#$&?<>{}^`"\\\/\[\]\r\n\t\v\s]/;
const USER_STORAGE_KEY_VALUE_SEPARATOR = ':';
console.log('数据采集: ' + g_kfUser);
const g_userMessageDivId = 'user-message-div';
const g_userMessageBtnId = 'user-message-btn';
var g_msgCount = 0;
function addUserMessage(msgs) {
if (msgs?.length > 0) {
let div = document.getElementById(g_userMessageDivId);
if (div == null) {
let div_row = document.createElement('div');
div_row.className = 'row';
document.querySelector('div.row.fyg_lh60.fyg_tr').parentNode.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 = (() => {
g_msgCount = 0;
document.getElementById(g_userMessageBtnId).style.display = 'none';
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 = (() => {
g_msgCount = 0;
document.getElementById(g_userMessageBtnId).style.display = 'none';
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);
}
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_userMessageDivId}'`);
navBar.appendChild(btn);
}
btn.innerText = `查看消息(${g_msgCount += msgs.length})`;
btn.style.display = 'inline-block';
let timeStamp = new Date();
timeStamp = timeStamp.toLocaleDateString('zh-CN') + ' ' + timeStamp.toLocaleTimeString('zh-CN');
let alt = !(div.lastElementChild?.className?.length > 0);
msgs.forEach((msg) => {
let div_info = document.createElement('div');
div_info.className = (alt ? 'alt' : '');
div_info.style.backgroundColor = (alt ? '#f0f0f0' : '');
div_info.style.padding = '5px';
div_info.innerHTML =
`★【${timeStamp}】${msg[0]}:
${msg[1]}
`;
div.appendChild(div_info);
alt = !alt;
});
}
}
// 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) {
let newObjects = [];
oldArray = oldArray?.slice();
for (let i = newArray?.length - 1; i >= 0; i--) {
let index = searchElement(oldArray, newArray[i], fnComparer);
if (index < 0) {
newObjects.unshift(findIndices ? i : newArray[i]);
}
else {
oldArray.splice(index, 1);
}
}
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 sc = document.getElementsByTagName('script');
for (let i = sc?.length - 1; i >= 0 ; i--) {
let func = sc[i].innerText.match(functionPattern);
if (func != null) {
return func[0].match(dataPattern)[0];
}
}
return null;
}
function getPostDataSafeId() {
let sc = document.getElementsByTagName('script');
for (let i = sc?.length - 1; i >= 0 ; i--) {
let index = sc[i].innerText.indexOf('&safeid=');
if (index >= 0) {
return sc[i].innerText.substring(index + 8, index + 14);
}
}
return null;
}
// 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.target.checked);
if (fnPostProcess != null) {
fnPostProcess(e.target.checked, fnParams);
}
});
return checkbox.checked;
}
// HTTP requests
const g_use_GM_xmlhttpRequest = true;
const HTTPRequestMethod = 'POST';
const HTTPResqestURL = {
read : window.location.origin + '/fyg_read.php',
update : window.location.origin + '/fyg_click.php',
user : window.location.origin + g_guguzhenHome };
const g_postHeader = { 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8' /*, 'Cookie' : document.cookie*/ };
const g_networkTimeoutMS = 120 * 1000;
var g_httpRequests = [];
function httpRequestBegin(requestUrl, queryString, fnLoad, fnError, fnTimeout) {
let request;
if (g_use_GM_xmlhttpRequest) {
request = GM_xmlhttpRequest({
method: HTTPRequestMethod,
url: requestUrl,
headers: g_postHeader,
data: queryString,
onload: fnLoad,
onerror: fnError,
ontimeout: fnTimeout
});
}
else {
request = new XMLHttpRequest();
request.onload = request.onerror = request.ontimeout = httpRequestEventHandler;
request.open(HTTPRequestMethod, requestUrl);
for (let name in g_postHeader) {
request.setRequestHeader(name, g_postHeader[name]);
}
request.send(queryString);
function httpRequestEventHandler(e) {
switch (e.type) {
case 'load':
if (fnLoad != undefined) {
fnLoad(e.currentTarget);
}
break;
case 'error':
if (fnError != undefined) {
fnError(e.currentTarget);
}
break;
case 'timeout':
if (fnTimeout != undefined) {
fnTimeout(e.currentTarget);
}
break;
}
}
}
g_httpRequests.push(request);
return 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
function beginReadObjects(bag, store, fnPostProcess, fnParams) {
if (bag != null || store != null) {
httpRequestBegin(
HTTPResqestURL.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 != undefined) {
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 ObjectMoveParam = [ null, null, null, null ];
var g_maxConcurrentRequests = ConcurrentRequestCount.default;
var g_objectMoveRequestsCount = 0;
var g_objectMoveTargetSiteFull = false;
function beginMoveObjects(objects, path, fnPostProcess, fnParams) {
if (!g_objectMoveTargetSiteFull && objects?.length > 0) {
ObjectMoveParam[ObjectMovePath.bag2store] ??= (getPostData(/puti\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? '');
ObjectMoveParam[ObjectMovePath.store2bag] ??= (getPostData(/puto\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? '');
ObjectMoveParam[ObjectMovePath.store2beach] ??= (getPostData(/stdel\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? '');
ObjectMoveParam[ObjectMovePath.beach2store] ??= (getPostData(/stpick\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/)?.slice(7, -1) ?? '');
if (!(ObjectMoveParam[path]?.length > 0)) {
if (!(ObjectMoveParam[ObjectMovePath.store2bag]?.length > 0) &&
ObjectMoveParam[ObjectMovePath.bag2store]?.length > 0) {
ObjectMoveParam[ObjectMovePath.store2bag] =
ObjectMoveParam[ObjectMovePath.bag2store].replace('c=21&', 'c=22&');
}
if (!(ObjectMoveParam[ObjectMovePath.bag2store]?.length > 0) &&
ObjectMoveParam[ObjectMovePath.store2bag]?.length > 0) {
ObjectMoveParam[ObjectMovePath.bag2store] =
ObjectMoveParam[ObjectMovePath.store2bag].replace('c=22&', 'c=21&');
}
if (!(ObjectMoveParam[ObjectMovePath.store2beach]?.length > 0) &&
ObjectMoveParam[ObjectMovePath.beach2store]?.length > 0) {
ObjectMoveParam[ObjectMovePath.store2beach] =
ObjectMoveParam[ObjectMovePath.beach2store].replace('c=1&', 'c=7&');
}
if (!(ObjectMoveParam[ObjectMovePath.beach2store]?.length > 0) &&
ObjectMoveParam[ObjectMovePath.store2beach]?.length > 0) {
ObjectMoveParam[ObjectMovePath.beach2store] =
ObjectMoveParam[ObjectMovePath.store2beach].replace('c=7&', 'c=1&');
}
}
if (ObjectMoveParam[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) {
httpRequestBegin(
HTTPResqestURL.update,
ObjectMoveParam[path].replace('"+id+"', ids.shift()),
(response) => {
if (path != ObjectMovePath.store2beach && response.responseText != 'ok') {
g_objectMoveTargetSiteFull = true;
console.log(response.responseText);
}
if (--g_objectMoveRequestsCount == 0) {
beginMoveObjects(objects, path, fnPostProcess, fnParams);
}
});
}
return;
}
}
}
g_objectMoveTargetSiteFull = false;
if (fnPostProcess != null) {
fnPostProcess(fnParams);
}
}
const g_beach_pirl_verify_data = '85797';
const g_store_pirl_verify_data = '124';
var g_pirl_data = null;
var g_objectPirlRequestsCount = 0;
function beginPirlObjects(storePirl, objects, fnPostProcess, fnParams) {
if (objects?.length > 0) {
g_pirl_data ??= (getPostData(/pirl\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*\+pirlyz\+.*"/)?.slice(7, -1) ?? '');
if (g_pirl_data.length > 0) {
let ids = [];
while (ids.length < g_maxConcurrentRequests && objects.length > 0) {
ids.push(objects.pop());
}
if ((g_objectPirlRequestsCount = ids.length) > 0) {
while (ids.length > 0) {
httpRequestBegin(
HTTPResqestURL.update,
g_pirl_data.replace('"+pirlyz+"', storePirl ? g_store_pirl_verify_data : g_beach_pirl_verify_data)
.replace('"+id+"', ids.shift()),
(response) => {
if (!/\d+/.test(response.responseText.trim()) && response.responseText.indexOf('果核') < 0) {
console.log(response.responseText);
}
if (--g_objectPirlRequestsCount == 0) {
beginPirlObjects(storePirl, objects, fnPostProcess, fnParams);
}
});
}
return;
}
}
}
if (fnPostProcess != null) {
fnPostProcess(fnParams);
}
}
// read currently mounted role card and halo informations
// roleInfo = [ roleId, roleName ]
// haloInfo = [ haloPoints, haloSlots, [ haloItem1, haloItem2, ... ] ]
function beginReadRoleAndHalo(roleInfo, haloInfo, fnPostProcess, fnParams) {
function onError() {
error++;
asyncOperations--;
}
let asyncOperations = 0;
let error = 0;
let requestRole;
let requestHalo;
if (roleInfo != null) {
asyncOperations++;
requestRole = httpRequestBegin(
HTTPResqestURL.read,
'f=9',
(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,
onError);
}
if (haloInfo != null) {
asyncOperations++;
requestHalo = httpRequestBegin(
HTTPResqestURL.read,
'f=5',
(response) => {
let haloPS = response.responseText.match(/.+?(\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,
onError);
}
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 (fnPostProcess != null) {
fnPostProcess(fnParams);
}
}
}, 200);
}
function beginReadWishpool(points, misc, fnPostProcess, fnParams) {
httpRequestBegin(
HTTPResqestURL.read,
'f=19',
(response) => {
let a = response.responseText.split('#');
if (misc != null) {
misc[0] = a[0];
misc[1] = a[1];
}
if (points != null) {
for (let i = a.length - 1; i >= 2; i--) {
points[i - 2] = a[i];
}
}
if (fnPostProcess != null) {
fnPostProcess(fnParams);
}
});
}
// userInfo = [ kfUser, grade, level, seashell, bvip, svip ]
function beginReadUserInfo(userInfo, fnPostProcess, fnParams) {
httpRequestBegin(
HTTPResqestURL.user,
'',
(response) => {
if (userInfo != null) {
userInfo.push(g_kfUser);
userInfo.push(response.responseText.match(/
\s*段位\s*<.+?>\s*(.+?)\s*<.+?<\/p>/)?.[1] ?? '');
userInfo.push(response.responseText.match(/\s*等级\s*<.+?>\s*(\d+).*?<.+?<\/p>/)?.[1] ?? '');
userInfo.push(response.responseText.match(/\s*贝壳\s*<.+?>\s*(\d+).*?<.+?<\/p>/)?.[1] ?? '');
userInfo.push(response.responseText.match(/\s*BVIP\s*<.+?>\s*(.+?)\s*<.+?<\/p>/)?.[1] ?? '');
userInfo.push(response.responseText.match(/\s*SVIP\s*<.+?>\s*(.+?)\s*<.+?<\/p>/)?.[1] ?? '');
}
if (fnPostProcess != null) {
fnPostProcess(fnParams);
}
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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_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' }
];
const g_amuletBuffMap = new Map();
g_amuletBuffs.forEach((item, index) => {
item.index = index;
g_amuletBuffMap.set(item.index, item);
g_amuletBuffMap.set(item.name, item);
g_amuletBuffMap.set(item.shortMark, item);
});
var g_amuletLevelIds = g_amuletDefaultLevelIds;
var g_amuletTypeIds = g_amuletDefaultTypeIds;
var g_amuletLevelNames = g_amuletDefaultLevelNames;
var g_amuletTypeNames = g_amuletDefaultTypeNames;
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;
}
}
}
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(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.buffCode += ((100 ** (g_amuletBuffMap.get(nv[0]).index % 6)) * parseInt(nv[1]));
});
if (this.isValid()) {
this.text = nb[1];
return this;
}
}
}
this.reset();
return null;
});
this.fromNode = ((node) => {
if (node?.nodeType == Node.ELEMENT_NODE && node.className?.indexOf('fyg_mp3') >= 0) {
let typeName = (node.getAttribute('data-original-title') ?? node.getAttribute('title'));
if (!/Lv.+?>\d+ 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.buffCode = 0;
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) {
this.buffCode += ((100 ** (buffMeta.index % 6)) * attr[2]);
this.text += `${this.text.length > 0 ? ', ' : ''}${attr[1]} +${attr[2]} ${buffMeta.unit}`;
}
}
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_amuletBuffs.slice(type, type + 6).forEach((buff) => {
let v = (code % 100);
if (v > 0) {
buffs[buff.name] = 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_amuletBuffMap.get(buff).unit}`;
}
}
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_amuletBuffMap.get(buff).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);
});
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_amuletBuffMap.get(obuff[0]).index) != 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.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)) > 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(parseInt(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.name];
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.name];
if (v > 0) {
str += `${sp}${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.name] > 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) {
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 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, fnPostProcess, fnParams) {
if (amulets?.length > 0) {
let store = amuletNodesToArray(amulets);
beginMoveAmulets({ groupName : groupName, amulets : store, path : ObjectMovePath.store2bag,
proc : fnPostProcess, params : fnParams });
}
else {
beginReadAmulets(null, amulets = [], null, beginMoveAmulets,
{ groupName : groupName, amulets : amulets, path : ObjectMovePath.store2bag,
proc : fnPostProcess, params : fnParams });
}
}
function beginUnloadAmuletGroupFromBag(amulets, groupName, fnPostProcess, fnParams) {
if (amulets?.length > 0) {
let bag = amuletNodesToArray(amulets);
beginMoveAmulets({ groupName : groupName, amulets : bag, path : ObjectMovePath.bag2store,
proc : fnPostProcess, params : fnParams });
}
else {
beginReadAmulets(amulets, null, null, beginMoveAmulets,
{ groupName : groupName, amulets : amulets, path : ObjectMovePath.bag2store,
proc : fnPostProcess, params : fnParams });
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// property utilities
//
////////////////////////////////////////////////////////////////////////////////////////////////////
function propertyInfoParseNode(node) {
if (node?.nodeType == Node.ELEMENT_NODE && node.className?.split(' ').length >= 2 && node.className.indexOf('fyg_mp3') >= 0) {
let title = (node.getAttribute('data-original-title') ?? node.getAttribute('title'))?.split(' ');
let name = title?.[0]?.trim();
if (g_propertyMap.has(name)) {
let text = node.getAttribute('data-content');
let lv = node.getAttribute('data-tip-class');
let id = node.getAttribute('onclick')?.match(/\d+/g);
if (text?.length > 0 && lv?.length > 0) {
return [ name, lv, text, (title?.[1]?.match(/\d+/)?.[0] ?? '0'), (id?.[1] ?? '-1') ];
}
}
}
return null;
}
function propertyNodesToInfoArray(nodes, array) {
array ??= [];
for (let i = nodes?.length - 1; i >= 0; i--) {
let e = propertyInfoParseNode(nodes[i]);
if (e != null) {
array.unshift(e);
}
}
return array;
}
function propertyInfoComparer(e1, e2) {
return g_propertyMap.get(e1[0]).index - g_propertyMap.get(e2[0]).index;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// equipment utilities
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const g_equipmentDefaultLevelName = [ '普通', '幸运', '稀有', '史诗', '传奇' ];
const g_equipmentLevelPoints = [ 200, 321, 419, 516, 585 ];
const g_equipmentLevelBGColor = [ '#e0e8e8', '#c0e0ff', '#c0ffc0', '#ffffc0', '#ffd0d0' ];
const g_equipmentLevelStyleClass = [ 'primary', 'info', 'success', 'warning', 'danger' ];
var g_equipmentLevelName = g_equipmentDefaultLevelName;
function equipmentInfoParseNode(node, ignoreIllegalAttributes) {
if (node?.nodeType == Node.ELEMENT_NODE && node.className?.split(' ').length >= 2 && node.className.indexOf('fyg_mp3') >= 0) {
let typeName = (node?.getAttribute('data-original-title') ?? node?.getAttribute('title'));
if (/Lv.+?>\d+') + 1).trim();
name = (g_equipMap.get(name)?.shortMark ?? g_equipMap.get(name.substring(g_equipmentLevelName[0].length))?.shortMark);
if (name?.length > 0) {
let attr = node.getAttribute('data-content')?.match(/>\s*\d+\s?%\s*(\d+));
if ((ignoreIllegalAttributes || attr?.length > 0) && lv?.length > 0) {
let mys = (/\[神秘属性\]/.test(node.getAttribute('data-content')) ? 1 : 0);
let id = (node.getAttribute('onclick')?.match(/\d+/)?.[0] ?? -1);
return [ name, lv[1],
(attr?.[0]?.match(/\d+/)?.[0] ?? '0'),
(attr?.[1]?.match(/\d+/)?.[0] ?? '0'),
(attr?.[2]?.match(/\d+/)?.[0] ?? '0'),
(attr?.[3]?.match(/\d+/)?.[0] ?? '0'),
mys, id ];
}
}
}
}
return null;
}
function equipmentNodesToInfoArray(nodes, array, ignoreIllegalAttributes) {
array ??= [];
for (let i = nodes?.length - 1; i >= 0; i--) {
let e = equipmentInfoParseNode(nodes[i], ignoreIllegalAttributes);
if (e != null) {
array.unshift(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; i < 7 && delta == 0; delta = parseInt(e1[i]) - parseInt(e2[i++]));
return delta;
}
function objectGetLevel(e, ignoreIllegalAttributes) {
let eq = (Array.isArray(e) ? e : equipmentInfoParseNode(e, ignoreIllegalAttributes));
if (eq?.length >= 7) {
let p = parseInt(eq[2]) + parseInt(eq[3]) + parseInt(eq[4]) + parseInt(eq[5]) + (parseInt(eq[6]) * 100);
for (var i = g_equipmentLevelPoints.length - 1; i > 0 && p < g_equipmentLevelPoints[i]; i--);
return i;
}
else if ((eq ??= propertyInfoParseNode(e))?.length >= 5) {
return g_equipmentLevelStyleClass.indexOf(eq[1].split('-')[1]);
}
else if ((eq = (e?.buffCode > 0 ? e : (new Amulet()).fromNode(e)))?.isValid()) {
return (eq.level + 2)
}
return -1;
}
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);
}
function objectIsEmptyNode(node) {
return (/空/.test(node?.innerText) || !(node?.getAttribute('data-content')?.length > 0));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// bag & store utilities
//
////////////////////////////////////////////////////////////////////////////////////////////////////
function findAmuletIds(container, amulets, ids, maxCount) {
ids ??= [];
let cl = (container?.length ?? 0);
if (cl > 0 && amulets?.length > 0) {
maxCount ??= cl;
let ams = amuletNodesToArray(container);
for (let i = ams.length - 1; i >= 0 && amulets.length > 0 && ids.length < maxCount; 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;
}
}
}
}
return ids;
}
function findEquipmentIds(container, equips, ids, maxCount) {
ids ??= [];
let cl = (container?.length ?? 0);
if (cl > 0 && equips?.length > 0) {
maxCount ??= cl;
let eqs = equipmentNodesToInfoArray(container);
for (let i = eqs.length - 1; i >= 0 && equips.length > 0 && ids.length < maxCount; 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;
}
}
}
}
return ids;
}
function beginClearBag(bag, key, fnPostProcess, fnParams) {
function beginClearBagObjects(objects) {
beginMoveObjects(objects, ObjectMovePath.bag2store, fnPostProcess, 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, fnPostProcess, fnParams) {
function readStoreCompletion() {
beginRestoreObjects(store, amulets, equips, fnPostProcess, fnParams);
}
if (store == null) {
beginReadObjects(null, store = [], readStoreCompletion, null);
}
else {
let ids = findAmuletIds(store, amulets);
findEquipmentIds(store, equips, ids);
beginMoveObjects(ids, ObjectMovePath.store2bag, fnPostProcess, fnParams);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// generic popups
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const g_genericPopupContainerId = 'generic-popup-container';
const g_genericPopupClass = 'generic-popup';
const g_genericPopupId = g_genericPopupClass;
const g_genericPopupWindowClass = 'generic-popup-window';
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 =
``;
const g_genericPopupHTML =
`${g_genericPopupStyle}
`;
var g_genericPopupContainer = null;
function genericPopupInitialize() {
if (g_genericPopupContainer == null) {
g_genericPopupContainer = document.createElement('div');
g_genericPopupContainer.id = g_genericPopupContainerId;
document.body.appendChild(g_genericPopupContainer);
}
g_genericPopupContainer.innerHTML = g_genericPopupHTML;
}
function genericPopupReset(initialize) {
if (initialize) {
g_genericPopupContainer.innerHTML = g_genericPopupHTML;
}
else {
let fixedContent = g_genericPopupContainer.querySelector('#' + g_genericPopupFixedContentId);
fixedContent.style.display = 'none';
fixedContent.innerHTML = '';
g_genericPopupContainer.querySelector('#' + g_genericPopupTitleTextId).innerText = '';
g_genericPopupContainer.querySelector('#' + g_genericPopupContentId).innerHTML = '';
g_genericPopupContainer.querySelector('#' + g_genericPopupTitleButtonContainerId).innerHTML = '';
g_genericPopupContainer.querySelector('#' + g_genericPopupFootButtonContainerId).innerHTML = '';
}
}
function genericPopupSetContent(title, content) {
g_genericPopupContainer.querySelector('#' + g_genericPopupTitleTextId).innerText = title;
g_genericPopupContainer.querySelector('#' + g_genericPopupContentId).innerHTML = content;
}
function genericPopupSetFixedContent(content) {
let fixedContent = g_genericPopupContainer.querySelector('#' + 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';
}
g_genericPopupContainer.querySelector('#' + (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%');
g_genericPopupContainer.querySelector('#' + g_genericPopupContentContainerId).style.width
= width + (width.endsWith('px') || width.endsWith('%') ? '' : 'px');
let content = g_genericPopupContainer.querySelector('#' + 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 = g_genericPopupContainer.querySelector('#' + g_genericPopupId);
if (clickOutsideToClose) {
popup.onclick = ((event) => {
if (event.target == popup) {
genericPopupClose(true);
}
});
}
else {
popup.onclick = null;
}
popup.style.display = "flex";
}
function genericPopupClose(reset, initialize) {
genericPopupCloseProgressMessage();
let popup = g_genericPopupContainer.querySelector('#' + g_genericPopupId);
popup.style.display = "none";
if (reset) {
genericPopupReset(initialize);
}
httpRequestClearAll();
}
function genericPopupOnClickOutside(fnProcess, fnParams) {
let popup = g_genericPopupContainer.querySelector('#' + g_genericPopupId);
if (fnProcess != null) {
popup.onclick = ((event) => {
if (event.target == popup) {
fnProcess(fnParams);
}
});
}
else {
popup.onclick = null;
}
}
function genericPopupQuerySelector(selectString) {
return g_genericPopupContainer.querySelector(selectString);
}
function genericPopupQuerySelectorAll(selectString) {
return g_genericPopupContainer.querySelectorAll(selectString);
}
let g_genericPopupInformationTipsTimer = null;
function genericPopupShowInformationTips(msg, time) {
if (g_genericPopupInformationTipsTimer != null) {
clearTimeout(g_genericPopupInformationTipsTimer);
g_genericPopupInformationTipsTimer = null;
}
let msgContainer = g_genericPopupContainer.querySelector('#' + 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);
g_genericPopupContainer.querySelector('#' + g_genericPopupProgressContentId).innerText
= (progressMessage?.length > 0 ? progressMessage : '请稍候...');
g_genericPopupContainer.querySelector('#' + g_genericPopupProgressId).style.display = "flex";
}
function genericPopupUpdateProgressMessage(progressMessage) {
g_genericPopupContainer.querySelector('#' + g_genericPopupProgressContentId).innerText
= (progressMessage?.length > 0 ? progressMessage : '请稍候...');
}
function genericPopupCloseProgressMessage() {
g_genericPopupContainer.querySelector('#' + 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, ``);
let indicatorList = g_genericPopupContainer.querySelector('#' + 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 = `${g_genericPopupTaskWaiting} ${tasks[i]} `;
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 = g_genericPopupContainer.querySelector('#' + g_genericPopupTaskItemId + index)?.lastChild;
if (item != undefined) {
item.innerText = (state ?? '');
}
}
function genericPopupTaskComplete(index, error) {
let li = g_genericPopupContainer.querySelector('#' + 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 : '舞' , hasG : true , shortMark : 'WU' },
{ index : -1 , id : 3001 , name : '默' , hasG : false , shortMark : 'MO' },
{ index : -1 , id : 3002 , name : '琳' , hasG : false , shortMark : 'LIN' },
{ index : -1 , id : 3003 , name : '艾' , hasG : false , shortMark : 'AI' },
{ index : -1 , id : 3004 , name : '梦' , hasG : false , shortMark : 'MENG' },
{ index : -1 , id : 3005 , name : '薇' , hasG : false , shortMark : 'WEI' },
{ index : -1 , id : 3006 , name : '伊' , hasG : false , shortMark : 'YI' },
{ index : -1 , id : 3007 , name : '冥' , hasG : false , shortMark : 'MING' },
{ index : -1 , id : 3008 , name : '命' , hasG : false , shortMark : 'MIN' },
{ index : -1 , id : 3009 , name : '希' , hasG : true , shortMark : 'XI' },
{ index : -1 , id : 3010 , name : '霞' , hasG : true , shortMark : 'XIA' },
{ index : -1 , id : 3011 , name : '绮' , hasG : false , shortMark : 'QI' }
];
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_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);
});
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]);
}
});
}
}
const g_gemWorks = [
{
index : -1,
name : '赶海',
nameRegex : { line : 1 , regex : /^\d+贝壳$/ },
progressRegex : { line : 1 , regex : /(\d+)/ },
completionProgress : 1000000,
unitRegex : { line : 4 , regex : /(\d+)/ },
unitSymbol : '贝壳'
},
{
index : -1,
name : '随机装备箱',
nameRegex : { line : 1 , regex : /^随机装备箱$/ },
progressRegex : { line : 0 , regex : /(\d+\.?\d*)%?/ },
completionProgress : 100,
unitRegex : { line : 4 , regex : /(\d+\.?\d*)%?/ },
unitSymbol : '%'
},
{
index : -1,
name : '灵魂药水',
nameRegex : { line : 1 , regex : /^灵魂药水$/ },
progressRegex : { line : 0 , regex : /(\d+\.?\d*)%?/ },
completionProgress : 100,
unitRegex : { line : 4 , regex : /(\d+\.?\d*)%?/ },
unitSymbol : '%'
},
{
index : -1,
name : '宝石原石',
nameRegex : { line : 1 , regex : /^宝石原石/ },
progressRegex : { line : 0 , regex : /(\d+\.?\d*)%?/ },
completionProgress : 100,
unitRegex : { line : 4 , regex : /(\d+\.?\d*)%?/ },
unitSymbol : '%'
}
];
const g_gemMinWorktimeMinute = 8 * 60;
const g_gemPollPeriodMinute = { min : 1 , max : 8 * 60 , default : 60 };
const g_gemFailurePollPeriodSecond = 30;
const g_gemWorkMap = new Map();
g_gemWorks.forEach((item, index) => {
item.index = index;
item.alias = item.name;
g_gemWorkMap.set(item.name, item);
});
function readGemWorkCompletionCondition() {
let cond = g_configMap.get('gemWorkCompletionCondition');
let conds = cond.value.split(',');
let len = Math.min(conds.length, g_gemWorks.length);
let error = 0;
for (let i = len - 1; i >= 0; i--) {
if ((conds[i] = conds[i].trim()).length > 0) {
let comp = conds[i].match(new RegExp(`^${g_gemWorks[i].progressRegex.regex.source}$`))?.[1];
if (!isNaN(comp = Number.parseFloat(comp))) {
g_gemWorks[i].completionProgress = comp;
}
else {
error++;
}
}
}
return error;
}
const g_equipAttributes = [
{ index : 0 , type : 0 , name : '物理攻击' },
{ index : 1 , type : 0 , name : '魔法攻击' },
{ index : 2 , type : 0 , name : '攻击速度' },
{ index : 3 , type : 0 , name : '最大生命' },
{ index : 4 , type : 0 , name : '最大护盾' },
{ index : 5 , type : 1 , name : '附加物伤' },
{ index : 6 , type : 1 , name : '附加魔伤' },
{ index : 7 , type : 1 , name : '附加攻速' },
{ index : 8 , type : 1 , name : '附加生命' },
{ index : 9 , type : 1 , name : '附加护盾' },
{ index : 10 , type : 1 , name : '附加回血' },
{ index : 11 , type : 1 , name : '附加回盾' },
{ index : 12 , type : 0 , name : '护盾回复' },
{ index : 13 , type : 0 , name : '物理穿透' },
{ index : 14 , type : 0 , name : '魔法穿透' },
{ index : 15 , type : 0 , name : '暴击穿透' },
{ index : 16 , type : 1 , name : '附加物穿' },
{ index : 17 , type : 1 , name : '附加物防' },
{ index : 18 , type : 1 , name : '附加魔防' },
{ index : 19 , type : 1 , name : '物理减伤' },
{ index : 20 , type : 1 , name : '魔法减伤' },
{ index : 21 , type : 0 , name : '生命偷取' },
{ index : 22 , type : 0 , name : '伤害反弹' },
{ index : 23 , type : 1 , name : '附加魔穿' },
{ index : 24 , type : 1 , name : '技能概率' },
{ index : 25 , type : 1 , name : '暴击概率' },
{ index : 26 , type : 1 , name : '力量生命' },
{ index : 27 , type : 1 , name : '体魄生命' },
{ index : 28 , type : 1 , name : '意志生命' },
{ index : 29 , type : 1 , name : '体魄物减' },
{ index : 30 , type : 1 , name : '意志魔减' }
];
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 : 2 , 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 : 1 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 1 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 1 },
{ attribute : g_equipAttributes[7] , factor : 2 , additive : 0 } ],
merge : [ [ 0, 1, 2 ], [ 3 ] ],
shortMark : 'VULTURE'
},
{
index : -1,
name : '海星戒指',
type : 1,
attributes : [ { attribute : g_equipAttributes[16] , factor : 1 / 2 , additive : 0 },
{ attribute : g_equipAttributes[23] , factor : 1 / 2 , additive : 0 },
{ attribute : g_equipAttributes[25] , factor : 4 / 5 , additive : 0 },
{ attribute : g_equipAttributes[24] , factor : 4 / 5 , additive : 0 } ],
merge : null,
shortMark : 'RING'
},
{
index : -1,
name : '噬魔戒指',
type : 1,
attributes : [ { attribute : g_equipAttributes[23] , factor : 1 / 2 , additive : 0 },
{ attribute : g_equipAttributes[24] , factor : 4 / 5 , additive : 0 },
{ attribute : g_equipAttributes[26] , factor : 2 / 25 , additive : 0 },
{ attribute : g_equipAttributes[3] , factor : 7 / 100 , additive : 0 } ],
merge : null,
shortMark : 'DEVOUR'
},
{
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 : 60 , 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[4] , factor : 1 / 5 , additive : 50 },
{ attribute : g_equipAttributes[9] , factor : 100 , additive : 0 },
{ attribute : g_equipAttributes[18] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[20] , factor : 5 , additive : 0 } ],
merge : null,
shortMark : 'CAPE'
},
{
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 : 'WOOD'
},
{
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[29] , factor : 1 / 120 , additive : 0.05 },
{ attribute : g_equipAttributes[30] , factor : 1 / 120 , additive : 0.05 },
{ attribute : g_equipAttributes[27] , factor : 1 / 30 , additive : 0 },
{ attribute : g_equipAttributes[28] , 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_equipMap = new Map();
g_equipments.forEach((item, index) => {
item.index = index;
item.alias = item.name;
g_equipMap.set(item.name, item);
g_equipMap.set(item.shortMark, item);
});
const g_oldEquipNames = [
[ '荆棘盾剑', '荆棘剑盾' ],
[ '饮血魔剑', '饮血长枪' ],
[ '探险者手环', '探险者手套' ],
[ '秃鹫手环', '秃鹫手套' ],
[ '复苏战衣', '复苏木甲' ],
[ '萌爪耳钉', '天使缎带' ],
[ '占星师的耳饰', '占星师的发饰' ],
[ '探险者耳环', '探险者头巾' ]
];
const g_defaultEquipAttributeMerge = [ [0], [1], [2], [3] ];
function defaultEquipmentNodeComparer(setting, eqKey, eq1, eq2) {
let eqMeta = g_equipMap.get(eqKey);
let delta = [];
let majorAdv = 0;
let majorEq = 0;
let majorDis = 0;
let minorAdv = 0;
eqMeta.attributes.forEach((attr, index) => {
let eq1Base = eq1[0] * attr.factor + attr.additive;
let eq2Base = eq2[0] * attr.factor + attr.additive;
let d = Math.trunc(eq1Base * (eq1[index + 1] / 10)) / 10 - Math.trunc(eq2Base * (eq2[index + 1] / 10)) / 10;
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) {
majorAdv++;
}
else if (sum < 0) {
majorDis++;
}
else {
majorEq++;
}
};
return { majorAdv : majorAdv, majorEq : majorEq, majorDis : majorDis, minorAdv : minorAdv };
}
function formatEquipmentAttributes(e, itemSeparator) {
let text = '';
if (e?.length > 5) {
itemSeparator ??= ', ';
let sp = '';
g_equipMap.get(e[0])?.attributes.forEach((attr, index) => {
let eBase = e[1] * attr.factor + attr.additive;
text += `${sp}${attr.attribute.name} +${Math.trunc(eBase * (e[index + 2] / 10)) / 10}${attr.attribute.type == 0 ? '%' : ''}`;
sp = itemSeparator;
});
}
return text;
}
var g_useOldEquipName = false;
var g_useThemeEquipName = false;
function equipLoadTheme(theme) {
if (theme.level?.length > 0) {
g_equipmentLevelName = theme.level;
}
if (g_useOldEquipName) {
g_oldEquipNames.forEach((item) => {
if (!g_equipMap.has(item[1])) {
let eqMeta = g_equipMap.get(item[0]);
if (eqMeta != undefined) {
eqMeta.alias = item[1];
g_equipMap.set(eqMeta.alias, eqMeta);
}
}
});
}
if (g_useThemeEquipName) {
for(let item in theme) {
if (/^[a-z]+\d+$/.test(item) && theme[item].length >= 5 && theme[item][3]?.length > 0 && !g_equipMap.has(theme[item][3])) {
let eqMeta = g_equipMap.get(theme[item][2]);
if (eqMeta != undefined) {
eqMeta.alias = theme[item][3];
g_equipMap.set(eqMeta.alias, eqMeta);
}
}
}
}
}
const g_halos = [
{ index : -1 , id : 101 , name : '启程之誓' , points : 0 , shortMark : 'SHI' },
{ index : -1 , id : 102 , name : '启程之心' , points : 0 , shortMark : 'XIN' },
{ index : -1 , id : 103 , name : '启程之风' , points : 0 , shortMark : 'FENG' },
{ index : -1 , id : 104 , name : '等级挑战' , points : 0 , shortMark : 'TIAO' },
{ index : -1 , id : 105 , name : '等级压制' , points : 0 , shortMark : 'YA' },
{ index : -1 , id : 201 , name : '破壁之心' , points : 20 , shortMark : 'BI' },
{ index : -1 , id : 202 , name : '破魔之心' , points : 20 , shortMark : 'MO' },
{ index : -1 , id : 203 , name : '复合护盾' , points : 20 , shortMark : 'DUN' },
{ index : -1 , id : 204 , name : '鲜血渴望' , points : 20 , shortMark : 'XUE' },
{ index : -1 , id : 205 , name : '削骨之痛' , points : 20 , shortMark : 'XIAO' },
{ index : -1 , id : 206 , name : '圣盾祝福' , points : 20 , shortMark : 'SHENG' },
{ index : -1 , id : 207 , name : '恶意抽奖' , points : 20 , shortMark : 'E' },
{ index : -1 , id : 301 , name : '伤口恶化' , points : 30 , shortMark : 'SHANG' },
{ index : -1 , id : 302 , name : '精神创伤' , points : 30 , shortMark : 'SHEN' },
{ index : -1 , id : 303 , name : '铁甲尖刺' , points : 30 , shortMark : 'CI' },
{ index : -1 , id : 304 , name : '忍无可忍' , points : 30 , shortMark : 'REN' },
{ index : -1 , id : 305 , name : '热血战魂' , points : 30 , shortMark : 'RE' },
{ index : -1 , id : 306 , name : '点到为止' , points : 30 , shortMark : 'DIAN' },
{ index : -1 , id : 307 , name : '午时已到' , points : 30 , shortMark : 'WU' },
{ index : -1 , id : 308 , name : '纸薄命硬' , points : 30 , shortMark : 'ZHI' },
{ 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' },
{ index : -1 , id : 407 , name : '钝化锋芒' , points : 100 , shortMark : 'DUNH' },
{ index : -1 , id : 408 , name : '自信回头' , points : 100 , shortMark : 'ZI' }
];
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 : `最大并发网络请求(${ConcurrentRequestCount.min} - ${ConcurrentRequestCount.max})`,
defaultValue : ConcurrentRequestCount.default,
value : ConcurrentRequestCount.default,
tips : '同时向服务器提交的请求的最大数量。过高的设置容易引起服务阻塞或被认定为DDOS攻击从而导致服务器停止服务(HTTP 503)。',
validate : ((value) => {
return (!isNaN(value = parseInt(value)) && value >= ConcurrentRequestCount.min && value <= ConcurrentRequestCount.max);
}),
onchange : ((value) => {
if (!isNaN(value = parseInt(value)) && value >= ConcurrentRequestCount.min && value <= ConcurrentRequestCount.max) {
return (g_maxConcurrentRequests = value);
}
return (g_maxConcurrentRequests = ConcurrentRequestCount.default);
})
},
{
index : -1,
id : 'openCardSequence',
name : `一键翻牌次序(整数 ±(1 - 12) 组成的无重复序列或留空)`,
defaultValue : '',
value : '',
tips : '一键翻牌时的开牌次序,此设置留空时(空格不会被判定为空而会判定为随机元素)将不显示一键翻牌相关面板。序列中禁止出现绝对值重复的元素,' +
'负数表示排除某张牌,可出现在任意位置且不占位,排除个数须不大于3。由于最多只需翻开9张牌必定会出现三同色,所以正数及随机元素个数须不大于9,' +
'不足9时程序将在开牌时按需随机补足。单个元素也可设置为随机,只需将该元素设置为无内容、仅含空格和/或单个“?”即可。例:“?”=“,”=“ ”=全随机;' +
'“-12”=排除绮,其余全随机;“-1,12,,6,-2, , ? ,+5”=排除舞和默,依次翻开:绮、随机、薇、随机、随机、梦、随机到底。' +
'开牌过程中任意时刻出现三同色或错误即终止。',
validate : ((value) => {
let seq = value.split(',');
let abs;
let inc = 0;
let exc = 0;
let dup = [];
for (let e of seq) {
if ((e = e.trim()).length == 0 || e == '?') {
if (++inc > 9) {
return false;
}
continue;
}
else if ((!/^(\+|-)?\d+$/.test(e) || (abs = Math.abs(e = parseInt(e))) < 1 || abs > 12 || dup[abs] != null) ||
(e < 0 && ++exc > 3) || (e > 0 && ++inc > 9)) {
return false;
}
dup[abs] = true;
}
return true;
}),
onchange : ((value) => {
let seq = value.split(',');
let abs;
let inc = 0;
let exc = 0;
let dup = [];
for (let e of seq) {
if ((e = e.trim()).length == 0 || e == '?') {
if (++inc > 9) {
return '';
}
continue;
}
else if ((!/^(\+|-)?\d+$/.test(e) || (abs = Math.abs(e = parseInt(e))) < 1 || abs > 12 || dup[abs] != null) ||
(e < 0 && ++exc > 3) || (e > 0 && ++inc > 9)) {
return '';
}
dup[abs] = true;
}
return value;
})
},
{
index : -1,
id : 'minBeachEquipLevelToAmulet',
name : `沙滩装备转护符最小等级(绿,黄,红)`,
defaultValue : '1,1,1',
value : '1,1,1',
tips : '沙滩装备批量转换护符时各色装备所需达到的最小等级,小于对应等级的装备不会被转换,但玩家依然可以选择手动熔炼。',
validate : ((value) => {
return /^\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*$/.test(value);
}),
onchange : ((value) => {
if (/^\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*$/.test(value)) {
return value;
}
return '1,1,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%';
})
},
{
index : -1,
id : 'clearBeachAfterBatchToAmulet',
name : `批量转护符完成后自动清理沙滩(0:否,1:是)`,
defaultValue : 0,
value : 0,
tips : '沙滩装备批量转换护符完成后自动清理沙滩上残存的装备。装备一但被清理将转换为贝壳及果核且不可恢复,当此选项开启时请务必确保在使用批量转护符' +
'功能之前拾取所有欲保留的装备。',
validate : ((value) => {
return /^0|1$/.test(value);
}),
onchange : ((value) => {
if (/^0|1$/.test(value)) {
return parseInt(value);
}
return 0;
})
},
{
index : -1,
id : 'gemPollPeriod',
name : `宝石工坊挂机轮询周期(0 - ${Math.min(g_gemPollPeriodMinute.max, g_gemMinWorktimeMinute)},单位:分钟,0:禁用挂机)`,
defaultValue : g_gemPollPeriodMinute.default,
value : g_gemPollPeriodMinute.default,
tips : '宝石工坊挂机程序向服务器发出进度轮询请求以分钟为单位的间隔时间。0表示禁用挂机程序,同时工会面板也将不会显示。此项设置越小对服务器造成的压力越大,' +
'除非您一直盯着宝石工坊的页面(这种情况建议手动按需刷新),否则您不会因较小的设置值获得任何额外的优势,因为挂机程序会根据工作进度动态调整轮询时间' +
'间隔以便及时收工重开。所以如果您的需求只是单纯的自动收工重开则建议设置为最大值。唯一例外是当您的设备时钟走时明显偏慢,小时误差达到分钟级别时可根' +
'据情况适当调小设置,推荐公式为“期望时间 - 期望时间内可能产生的最大正误差”。如果您的设备时钟偏快则在设为最大值的情况下无需进行任何额外调整。',
validate : ((value) => {
return (!isNaN(value = parseInt(value)) && value <= g_gemPollPeriodMinute.max);
}),
onchange : ((value) => {
if (!isNaN(value = parseInt(value)) && value <= g_gemPollPeriodMinute.max) {
return value;
}
return g_gemPollPeriodMinute.default;
})
},
{
index : -1,
id : 'gemWorkCompletionCondition',
name : `宝石工坊完成条件(按工作台顺序,“%”可省略,以逗号分隔)`,
defaultValue : '',
value : '',
tips : '宝石工坊运作过程中挂机程序对各工作台进行完成判定的条件。按照工作台次序以“,”隔开,也可以单项或全部留空表示使用默认值(目前贝壳1000000,其它100)。' +
'在满足基本收工条件后(目前为工作满8小时),此设置将被挂机程序直接用于自动收工判定。请注意,设置程序只进行格式检查,并不会对有效值范围做出任何假设,' +
'所以一个错误的设置可能会令挂机判定程序失效从而影响自动收工功能的正确运行。',
validate : ((value) => {
let conds = value.split(',');
if (conds.length > g_gemWorks.length) {
return false;
}
for (let i = conds.length - 1; i >= 0; i--) {
if ((conds[i] = conds[i].trim()).length > 0 && !(new RegExp(`^${g_gemWorks[i].progressRegex.regex.source}$`)).test(conds[i])) {
return false;
}
}
return true;
}),
onchange : ((value) => {
let conds = value.split(',');
if (conds.length > g_gemWorks.length) {
return '';
}
for (let i = conds.length - 1; i >= 0; i--) {
if ((conds[i] = conds[i].trim()).length > 0 && !(new RegExp(`^${g_gemWorks[i].progressRegex.regex.source}$`)).test(conds[i])) {
return '';
}
}
return value;
})
}
];
const g_configMap = new Map();
g_configs.forEach((item, index) => {
item.index = index;
g_configMap.set(item.id, item);
});
function readConfig() {
let udata = loadUserConfigData();
g_configs.forEach((item) => {
item.value = (item.onchange?.call(null, udata.config[item.id] ?? item.defaultValue));
});
return udata;
}
function modifyConfig(configIds, title, reload) {
title ??= `插件设置 - v${GM_info?.script?.version ?? g_modificationVersion}`;
let udata = readConfig();
let configs = (configIds?.length > 0 ? [] : g_configs);
if (configIds?.length > 0) {
for (let id of configIds) {
let cfg = g_configMap.get(id);
if (cfg != null) {
configs.push(cfg);
}
}
}
genericPopupInitialize();
let fixedContent =
'请勿随意修改配置项,' +
`除非您知道它的准确用途并且设置为正确的值,否则可能导致插件工作异常
';
let mainContent =
`