// ==UserScript==
// @name 咕咕镇数据采集
// @namespace https://greasyfork.org/users/448113
// @version 1.4.13
// @description 咕咕镇数据采集,目前采集已关闭,兼作助手
// @author paraii
// @match https://www.guguzhen.com/*
// @grant GM_xmlhttpRequest
// @connect www.guguzhen.com
// @run-at document-body
// @license MIT License
// @downloadURL none
// ==/UserScript==
// @connect notes.orga.cat
// @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/js/tooltip.js
// @require https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/js/popover.js
(function() {
'use strict'
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// common utilities
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const g_modificationVersion = '2022-06-02 05:00:00';
const g_kfUser = document.getElementsByClassName('icon-user')[0].parentNode.innerText.split(' ')[1];
const g_autoTaskEnabledStorageKey = g_kfUser + '_autoTaskEnabled';
const g_keepPkRecordStorageKey = g_kfUser + '_keepPkRecord';
const g_amuletGroupsStorageKey = g_kfUser + '_amulet_groups';
const g_equipmentExpandStorageKey = g_kfUser + '_equipment_Expand';
const g_equipmentBGStorageKey = g_kfUser + '_equipment_BG';
const g_beachIgnoreStoreMysEquipStorageKey = g_kfUser + '_beach_ignoreStoreMysEquip';
const g_beachForceExpandStorageKey = g_kfUser + '_beach_forceExpand';
const g_beachBGStorageKey = g_kfUser + '_beach_BG';
const g_userDataStorageKeyConfig = [ g_kfUser, g_autoTaskEnabledStorageKey, g_keepPkRecordStorageKey, g_amuletGroupsStorageKey,
g_equipmentExpandStorageKey, g_equipmentBGStorageKey,
g_beachIgnoreStoreMysEquipStorageKey, g_beachForceExpandStorageKey, g_beachBGStorageKey ];
const g_userDataStorageKeyExtra = [ 'attribute', 'cardName', 'title', 'over', 'halo_max', 'beachcheck', 'dataReward', 'keepcheck' ];
const USER_STORAGE_RESERVED_SEPARATORS = /[:;,|=+*%!#$&?<>{}^`"\\\/\[\]\r\n\t\v\s]/;
const USER_STORAGE_KEY_VALUE_SEPARATOR = ':';
console.log(g_kfUser)
// perform a binary search. array must be sorted, but no matter in ascending or descending order.
// in this manner, you must pass in a proper comparer function for it works properly, aka, if the
// array was sorted in ascending order, then the comparer(a, b) should return a negative value
// while a < b or a positive value while a > b; otherwise, if the array was sorted in descending
// order, then the comparer(a, b) should return a positive value while a < b or a negative value
// while a > b, and in both, if a equals b, the comparer(a, b) should return 0. if you pass nothing
// or null / undefined value as comparer, then you must make sure about that the array was sorted
// in ascending order.
//
// in this particular case, we just want to check whether the array contains the value or not, we
// don't even need to point out the first place where the value appears (if the array actually
// contains the value), so we perform a simplest binary search and return an index (may not the
// first place where the value appears) or a negative value (means value not found) to indicate
// the search result.
function searchElement(array, value, fnComparer) {
if (array?.length > 0) {
fnComparer ??= ((a, b) => a < b ? -1 : (a > b ? 1 : 0));
let li = 0;
let hi = array.length - 1;
while (li <= hi) {
let mi = ((li + hi) >> 1);
let cr = fnComparer(value, array[mi]);
if (cr == 0) {
return mi;
}
else if (cr > 0) {
li = mi + 1;
}
else {
hi = mi - 1;
}
}
}
return -1;
}
// perform a binary insertion. the array and comparer must exactly satisfy as it in the searchElement
// function. this operation behaves sort-stable, aka, the newer inserting element will be inserted
// into the position after any existed equivalent elements.
function insertElement(array, value, fnComparer) {
if (array != null) {
fnComparer ??= ((a, b) => a < b ? -1 : (a > b ? 1 : 0));
let li = 0;
let hi = array.length - 1;
while (li <= hi) {
let mi = ((li + hi) >> 1);
let cr = fnComparer(value, array[mi]);
if (cr >= 0) {
li = mi + 1;
}
else {
hi = mi - 1;
}
}
array.splice(li, 0, value);
return li;
}
return -1;
}
// it's not necessary to have newArray been sorted, but the oldArray must be sorted since we are calling
// searchElement. if there are some values should be ignored in newArray, the comparer(a, b) should be
// implemented as return 0 whenever parameter a equals any of values that should be ignored.
function findNewObjects(newArray, oldArray, fnComparer, findIndices) {
// just in case, i discovered that sometimes if we use array.length directly in for(...) statement
// (either some other statements too), the statement get chances to be executed incorrectly (or just
// console.log can be effected?), don't know why, but we can use a temporary variable to handle this.
let newObjects = [];
let nl = (newArray?.length ?? 0);
for (let i = 0; i < nl; i++) {
if (searchElement(oldArray, newArray[i], fnComparer) < 0) {
newObjects.push(findIndices ? i : newArray[i]);
}
}
return newObjects;
}
// HTTP requests
var g_httpRequests = [];
function httpRequestRegister(request) {
if (request != null) {
g_httpRequests.push(request);
}
}
function httpRequestAbortAll() {
while (g_httpRequests.length > 0) {
g_httpRequests.pop().abort();
}
g_httpRequests = [];
}
function httpRequestClearAll() {
g_httpRequests = [];
}
function objectIdParseNodes(nodes, ids, ignoreEmptyCell) {
for (let node of nodes) {
if (node.className?.endsWith("fyg_mp3")) {
let id = node.getAttribute('onclick')?.match(/\d+/)[0];
if (id != undefined) {
ids.push(id);
}
else if (!ignoreEmptyCell) {
ids.push(-1);
}
}
}
}
const g_postMethod = 'POST'
const g_readUrl = 'https://www.guguzhen.com/fyg_read.php'
const g_postUrl = 'https://www.guguzhen.com/fyg_click.php'
const g_postHeader = { 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8' , 'Cookie' : document.cookie };
const g_networkTimeoutMS = 120 * 1000;
function beginReadObjects(bagIds, storeIds, ignoreEmptyCell, fnFurtherProcess, fnParams) {
if (bagIds != null || storeIds != null) {
let request = GM_xmlhttpRequest({
method: g_postMethod,
url: g_readUrl,
headers: g_postHeader,
data: 'f=7',
onload: response => {
let div = document.createElement('div');
div.innerHTML = response.responseText;
if (bagIds != null) {
objectIdParseNodes(div.children[0].children[1].children, bagIds, ignoreEmptyCell);
}
if (storeIds != null) {
objectIdParseNodes(div.children[1].children[1].children, storeIds, ignoreEmptyCell);
}
if (fnFurtherProcess != null) {
fnFurtherProcess(fnParams);
}
}
});
httpRequestRegister(request);
}
else if (fnFurtherProcess != null) {
fnFurtherProcess(fnParams);
}
}
// we wait the response(s) of the previous batch of request(s) to send another batch of request(s)
// rather than simply send them all within an inside foreach - which could cause too many requests
// to server simultaneously, that can be easily treated as D.D.O.S attack and therefor leads server
// to returns http status 503: Service Temporarily Unavailable
// * caution * the parameter 'objects' is required been sorted by their indices in ascending order
const g_maxConcurrentRequestsCount = 5;
var g_objectMoveRequestsCount = 0;
var g_objectMoveTargetSiteFull = false;
var g_func_puti_data = null;
var g_func_puto_data = null;
function beginMoveObjects(objects, bagToStore, fnFurtherProcess, fnParams) {
if (!g_objectMoveTargetSiteFull && objects?.length > 0) {
g_func_puti_data ??= getPostData(/puti\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/).slice(7, -1);
g_func_puto_data ??= getPostData(/puto\(id\)\{[\s\S]*\}/m, /data: ".*\+id\+.*"/).slice(7, -1);
let ids = [];
while (ids.length < g_maxConcurrentRequestsCount && objects.length > 0) {
let id = objects.pop();
if (id >= 0) {
ids.push(id);
}
}
if ((g_objectMoveRequestsCount = ids.length) > 0) {
while (ids.length > 0) {
let request = GM_xmlhttpRequest({
method: g_postMethod,
url: g_postUrl,
headers: g_postHeader,
data: (bagToStore ? g_func_puti_data : g_func_puto_data).replace('"+id+"', ids.shift()),
onload: response => {
if (response.responseText != 'ok') {
g_objectMoveTargetSiteFull = true;
}
if (--g_objectMoveRequestsCount == 0) {
beginMoveObjects(objects, bagToStore, fnFurtherProcess, fnParams);
}
}
});
httpRequestRegister(request);
}
return;
}
}
g_objectMoveTargetSiteFull = false;
if (fnFurtherProcess != null) {
fnFurtherProcess(fnParams);
}
}
// read currently mounted role card and halo informations
// roleInfo = [ roleId, roleName ]
// haloInfo = [ haloPoints, haloSlots, [ haloItem1, haloItem2, … ] ]
function beginReadRoleAndHalo(roleInfo, haloInfo, fnFurtherProcess, fnParams) {
let asyncOperations = 0;
let error = 0;
let requestRole;
let requestHalo;
if (roleInfo != null) {
asyncOperations++;
requestRole = GM_xmlhttpRequest({
method: g_postMethod,
url: g_readUrl,
headers: g_postHeader,
data: 'f=9',
onload: response => {
let div = document.createElement('div');
div.innerHTML = response.responseText;
let role = g_roleMap.get(div.querySelector('div.text-info.fyg_f24.fyg_lh60')?.children[0]?.innerText);
if (role != undefined) {
roleInfo.push(role.id);
roleInfo.push(role.name);
}
asyncOperations--;
},
onerror : err => {
error++;
asyncOperations--;
},
ontimeout : err => {
error++;
asyncOperations--;
}
});
}
if (haloInfo != null) {
asyncOperations++;
requestHalo = GM_xmlhttpRequest({
method: g_postMethod,
url: g_readUrl,
headers: g_postHeader,
data: 'f=5',
onload: response => {
let haloPS = response.responseText.match(/
[^\d]*(\d+)[^\d]*(\d+)[^<]+<\/h3>/);
if (haloPS?.length == 3) {
haloInfo.push(parseInt(haloPS[1]));
haloInfo.push(parseInt(haloPS[2]));
}
else {
haloInfo.push(0);
haloInfo.push(0);
}
let halo = [];
for (let item of response.responseText.matchAll(/halotfzt2\((\d+)\)/g)) {
halo.push(item[1]);
}
haloInfo.push(halo);
asyncOperations--;
},
onerror : err => {
error++;
asyncOperations--;
},
ontimeout : err => {
error++;
asyncOperations--;
}
});
}
let timeout = 0;
let timer = setInterval(() => {
if (asyncOperations == 0 || error > 0 || (++timeout * 200) >= g_networkTimeoutMS) {
clearInterval(timer);
if (asyncOperations > 0) {
requestRole?.abort();
requestHalo?.abort();
}
if (fnFurtherProcess != null) {
fnFurtherProcess(fnParams);
}
}
}, 200);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// amulet management
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const AMULET_STORAGE_GROUP_SEPARATOR = '|';
const AMULET_STORAGE_GROUPNAME_SEPARATOR = '=';
const AMULET_STORAGE_AMULET_SEPARATOR = ',';
const AMULET_TYPE_ID_FACTOR = 100000000000000;
const AMULET_LEVEL_ID_FACTOR = 10000000000000;
const AMULET_ENHANCEMENT_FACTOR = 1000000000000;
const AMULET_BUFF_MAX_FACTOR = AMULET_ENHANCEMENT_FACTOR;
const g_amuletLevelIds = {
稀有 : 0,
史诗 : 1,
传奇 : 2
};
const g_amuletTypeIds = {
苹果 : 0,
葡萄 : 1,
樱桃 : 2
};
const g_amuletLevelNames = [ '稀有', '史诗', '传奇' ];
const g_amuletTypeNames = [ '苹果', '葡萄', '樱桃' ];
const g_amuletBuffUnits = [ '点', '%', '%' ];
const g_amuletBuffTypeNames = [ '力量', '敏捷', '智力', '体魄', '精神', '意志',
'物理攻击', '魔法攻击', '速度', '生命护盾回复效果', '最大生命值', '最大护盾值',
'固定生命偷取', '固定反伤', '固定暴击几率', '固定技能几率', '物理防御效果', '魔法防御效果' ];
const g_amuletBuffShortMarks = [ 'STR', 'AGI', 'INT', 'VIT', 'SPR', 'MND',
'PATK', 'MATK', 'SPD', 'REC', 'HP', 'SLD',
'LCH', 'RFL', 'CRT', 'SKL', 'PDEF', 'MDEF' ];
const g_amuletBuffTypeOrders = new Map();
g_amuletBuffTypeNames.forEach((item, index) => { g_amuletBuffTypeOrders.set(item, index); });
function Amulet() {
this.id = -1;
this.type = -1;
this.level = 0;
this.enhancement = 0;
this.buffCode = 0;
this.text = null;
this.reset = (() => {
this.id = -1;
this.type = -1;
this.level = 0;
this.enhancement = 0;
this.buffCode = 0;
this.text = null;
});
this.isValid = (() => {
return (this.type >= 0 && this.type <= 2 && this.buffCode > 0);
});
this.fromCode = ((code) => {
if (!isNaN(code)) {
this.type = Math.trunc(code / AMULET_TYPE_ID_FACTOR) % 10;
this.level = Math.trunc(code / AMULET_LEVEL_ID_FACTOR) % 10;
this.enhancement = Math.trunc(code / AMULET_ENHANCEMENT_FACTOR) % 10;
this.buffCode = code % AMULET_BUFF_MAX_FACTOR;
}
else {
this.reset();
}
return (this.isValid() ? this : null);
});
this.fromBuffText = ((text) => {
if (text?.length > 0) {
let nb = text.split(' = ');
if (nb.length == 2) {
this.id = -1;
this.type = g_amuletTypeIds[nb[0].slice(2, 4)];
this.level = g_amuletLevelIds[nb[0].slice(0, 2)];
this.enhancement = parseInt(nb[0].match(/\d+/)[0]);
this.buffCode = 0;
for (let buff of nb[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(', ')) {
let nv = buff.split(' ');
this.buffCode += ((100 ** (g_amuletBuffTypeOrders.get(nv[0]) % 6)) * nv[1]);
}
if (this.isValid()) {
this.text = nb[1];
return this;
}
}
}
this.reset();
return null;
});
this.fromNode = ((node) => {
if (node?.className?.endsWith("fyg_mp3") && node.innerText.indexOf('+') >= 0) {
let id = node.getAttribute('onclick');
let typeName = (node.getAttribute('data-original-title') ?? node.getAttribute('title'));
let content = node.getAttribute('data-content');
if (id != null && typeName?.length > 4 && content?.length > 0 &&
!isNaN(this.type = g_amuletTypeIds[typeName.slice(2, 4)]) &&
!isNaN(this.level = g_amuletLevelIds[typeName.slice(0, 2)]) &&
!isNaN(this.id = parseInt(id.match(/\d+/)[0])) &&
!isNaN(this.enhancement = parseInt(node.innerText.match(/\d+/)[0]))) {
this.buffCode = 0;
this.text = '';
let attr = null;
let regex = / ]*>([^<]+)<[^>]*>\+(\d+)[^<]*<\/span><\/p>/g;
while ((attr = regex.exec(content))?.length == 3) {
this.buffCode += ((100 ** (g_amuletBuffTypeOrders.get(attr[1]) % 6)) * attr[2]);
this.text += `${this.text.length > 0 ? ', ' : ''}${attr[1]} +${attr[2]} ${g_amuletBuffUnits[this.type]}`;
}
if (this.isValid()) {
return this;
}
}
}
this.reset();
return null;
});
this.fromAmulet = ((amulet) => {
if (amulet?.isValid()) {
this.id = amulet.id;
this.type = amulet.type;
this.level = amulet.level;
this.enhancement = amulet.enhancement;
this.buffCode = amulet.buffCode;
this.text = amulet.text;
}
else {
this.reset();
}
return (this.isValid() ? this : null);
});
this.getCode = (() => {
if (this.isValid()) {
return (this.type * AMULET_TYPE_ID_FACTOR +
this.level * AMULET_LEVEL_ID_FACTOR +
this.enhancement * AMULET_ENHANCEMENT_FACTOR +
this.buffCode);
}
return -1;
});
this.getBuff = (() => {
let buffs = {};
if (this.isValid()) {
let type = this.type * 6;
for (let buff of g_amuletBuffTypeNames.slice(type, type + 6)) {
let v = Math.trunc(this.buffCode / (100 ** (g_amuletBuffTypeOrders.get(buff) % 6))) % 100;
if (v > 0) {
buffs[buff] = v;
}
}
}
return buffs;
});
this.formatName = (() => {
if (this.isValid()) {
return `${g_amuletLevelNames[this.level]}${g_amuletTypeNames[this.type]} (+${this.enhancement})`;
}
return null;
});
this.formatBuff = (() => {
if (this.isValid()) {
if (this.text?.length > 0) {
return this.text;
}
this.text = '';
let buffs = this.getBuff();
for (let buff in buffs) {
this.text += `${this.text.length > 0 ? ', ' : ''}${buff} +${buffs[buff]} ${g_amuletBuffUnits[this.type]}`;
}
}
return this.text;
});
this.formatBuffText = (() => {
if (this.isValid()) {
return this.formatName() + ' = ' + this.formatBuff();
}
return null;
});
this.formatShortMark = (() => {
let text = this.formatBuff()?.replaceAll(/(\+)|( 点)|( %)/g, '');
if (text?.length > 0) {
for (let buff in this.getBuff()) {
text = text.replaceAll(buff, g_amuletBuffShortMarks[g_amuletBuffTypeOrders.get(buff)]);
}
return this.formatName() + ' = ' + text;
}
return null;
});
this.compareTo = ((other, ascType) => {
if (!this.isValid()) {
return 1;
}
else if (!other?.isValid()) {
return -1;
}
else if (this.id >= 0 && this.id == other.id) {
return 0;
}
let delta = other.type - this.type;
if (delta != 0) {
return (ascType ? -delta : delta);
}
let tbuffs = this.formatBuffText().split(' = ')[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(', ');
let obuffs = other.formatBuffText().split(' = ')[1].replaceAll(/(\+)|( 点)|( %)/g, '').split(', ');
let bl = Math.min(tbuffs.length, obuffs.length);
for (let i = 0; i < bl; i++) {
let tbuff = tbuffs[i].split(' ');
let obuff = obuffs[i].split(' ');
if ((delta = g_amuletBuffTypeOrders.get(tbuff[0]) - g_amuletBuffTypeOrders.get(obuff[0])) != 0 ||
(delta = parseInt(obuff[1]) - parseInt(tbuff[1])) != 0) {
return delta;
}
}
if ((delta = obuffs.length - tbuffs.length) != 0 ||
(delta = other.level - this.level) != 0 ||
(delta = other.enhancement - this.enhancement) != 0) {
return delta;
}
return 0;
});
}
function AmuletGroup(persistenceString) {
this.buffSummary = {
力量 : 0,
敏捷 : 0,
智力 : 0,
体魄 : 0,
精神 : 0,
意志 : 0,
物理攻击 : 0,
魔法攻击 : 0,
速度 : 0,
生命护盾回复效果 : 0,
最大生命值 : 0,
最大护盾值 : 0,
固定生命偷取 : 0,
固定反伤 : 0,
固定暴击几率 : 0,
固定技能几率 : 0,
物理防御效果 : 0,
魔法防御效果 : 0
};
this.name = null;
this.items = [];
this.isValid = (() => {
return (this.items.length > 0 && amuletIsValidGroupName(this.name));
});
this.count = (() => {
return this.items.length;
});
this.clear = (() => {
this.items = [];
for (let buff in this.buffSummary) {
this.buffSummary[buff] = 0;
}
});
this.add = ((amulet) => {
if (amulet?.isValid()) {
let buffs = amulet.getBuff();
for (let buff in buffs) {
this.buffSummary[buff] += buffs[buff];
}
return insertElement(this.items, amulet, (a, b) => a.compareTo(b, true));
}
return -1;
});
this.remove = ((amulet) => {
if (this.isValid() && amulet?.isValid()) {
let i = searchElement(this.items, amulet, (a, b) => a.compareTo(b, true));
if (i >= 0) {
let buffs = amulet.getBuff();
for (let buff in buffs) {
this.buffSummary[buff] -= buffs[buff];
}
this.items.splice(i, 1);
return true;
}
}
return false;
});
this.removeId = ((id) => {
if (this.isValid()) {
let i = this.items.findIndex((a) => a.id == id);
if (i >= 0) {
let amulet = this.items[i];
let buffs = amulet.getBuff();
for (let buff in buffs) {
this.buffSummary[buff] -= buffs[buff];
}
this.items.splice(i, 1);
return amulet;
}
}
return null;
});
this.validate = ((amulets) => {
if (this.isValid()) {
let mismatch = 0;
let al = this.items.length;
let i = 0;
if (amulets?.length > 0) {
amulets = amulets.slice().sort((a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode);
for ( ; amulets.length > 0 && i < al; i++) {
let mi = searchElement(amulets, this.items[i], (a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode);
if (mi >= 0) {
// remove a matched amulet from the amulet pool can avoid one single amulet matches all
// the equivalent objects in the group.
// let's say two (or even more) AGI +5 apples in one group is fairly normal, if we just
// have only one equivalent apple in the amulet pool and we don't remove it when the
// first match happens, then the 2nd apple will get matched later, the consequence would
// be we can never find the mismatch which should be encountered at the 2nd apple
this.items[i].fromAmulet(amulets[mi]);
amulets.splice(mi, 1);
}
else {
mismatch++;
}
}
}
if (i > mismatch) {
this.items.sort((a, b) => a.compareTo(b, true));
}
if (i < al) {
mismatch += (al - i);
}
return (mismatch == 0);
}
return false;
});
this.findIndices = ((amulets) => {
let indices;
let al;
if (this.isValid() && (al = (amulets?.length ?? 0)) > 0) {
let items = this.items.slice().sort((a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode);
for (let i = 0; items.length > 0 && i < al; i++) {
let mi;
if (amulets[i]?.id >= 0 &&
(mi = searchElement(items, amulets[i], (a, b) => a.type != b.type ? a.type - b.type : a.buffCode - b.buffCode)) >= 0) {
// similar to the 'validate', remove the amulet from the search list when we found
// a match item in first time to avoid the duplicate founding, e.g. say we need only
// one AGI +5 apple in current group and we actually have 10 of AGI +5 apples in store,
// if we found the first matched itme in store and record it's index but not remove it
// from the temporary searching list, then we will continuously reach this kind of
// founding and recording until all those 10 AGI +5 apples are matched and processed,
// this obviously ain't the result what we expected
(indices ??= []).push(i);
items.splice(mi, 1);
}
}
}
return indices;
});
this.parse = ((persistenceString) => {
this.clear();
if (persistenceString?.length > 0) {
let elements = persistenceString.split(AMULET_STORAGE_GROUPNAME_SEPARATOR);
if (elements.length == 2) {
let name = elements[0].trim();
if (amuletIsValidGroupName(name)) {
let items = elements[1].split(AMULET_STORAGE_AMULET_SEPARATOR);
let il = items.length;
for (let i = 0; i < il; i++) {
if (this.add((new Amulet()).fromCode(parseInt(items[i]))) < 0) {
this.clear();
break;
}
}
if (this.count() > 0) {
this.name = name;
}
}
}
}
return (this.count() > 0);
});
this.formatBuffSummary = ((linePrefix, lineSuffix, lineSeparator) => {
if (this.isValid()) {
let str = '';
let nl = '';
for (let buff of g_amuletBuffTypeNames) {
let v = this.buffSummary[buff];
if (v > 0) {
str += `${nl}${linePrefix}${buff} +${v} ${g_amuletBuffUnits[Math.trunc(g_amuletBuffTypeOrders.get(buff) / 6)]}${lineSuffix}`;
nl = lineSeparator;
}
}
return str;
}
return '';
});
this.formatItems = ((linePrefix, erroeLinePrefix, lineSuffix, errorLineSuffix, lineSeparator) => {
if (this.isValid()) {
let str = '';
let nl = '';
for (let amulet of this.items) {
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;
for (let buff of g_amuletBuffTypeNames) {
if (this.buffSummary[buff] > 0) {
lines++;
}
}
return lines + this.items.length;
}
return 0;
});
this.formatPersistenceString = (() => {
if (this.isValid()) {
let codes = [];
for (let amulet of this.items) {
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) {
let amulet;
for (let node of nodes) {
if ((amulet ??= new Amulet()).fromNode(node)?.isValid()) {
array.push(amulet);
amulet = null;
}
}
}
function beginReadAmulets(bagAmulets, storeAmulets, fnFurtherProcess, fnParams) {
if (bagAmulets != null || storeAmulets != null) {
let request = GM_xmlhttpRequest({
method: g_postMethod,
url: g_readUrl,
headers: g_postHeader,
data: 'f=7',
onload: response => {
let div = document.createElement('div');
div.innerHTML = response.responseText;
if (bagAmulets != null) {
amuletNodesToArray(div.children[0].children[1].children, bagAmulets);
}
if (storeAmulets != null) {
amuletNodesToArray(div.children[1].children[1].children, storeAmulets);
}
if (fnFurtherProcess != null) {
fnFurtherProcess(fnParams);
}
}
});
httpRequestRegister(request);
}
else if (fnFurtherProcess != null) {
fnFurtherProcess(fnParams);
}
}
function beginMoveAmulets({ groupName, amulets, unload, 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, unload, proc, params);
}
function beginLoadAmuletGroupFromStore(groupName, fnFurtherProcess, fnParams) {
let amulets = [];
beginReadAmulets(null, amulets, beginMoveAmulets,
{ groupName : groupName, amulets : amulets, unload : false, proc : fnFurtherProcess, params : fnParams });
}
function beginUnloadAmuletGroupFromBag(groupName, fnFurtherProcess, fnParams) {
let amulets = [];
beginReadAmulets(amulets, null, beginMoveAmulets,
{ groupName : groupName, amulets : amulets, unload : true, proc : fnFurtherProcess, params : fnParams });
}
function beginClearBag(fnFurtherProcess, fnParams) {
function beginClearBagObjects({ objects, proc, params }) {
beginMoveObjects(objects, true, proc, params);
}
let objects = [];
beginReadObjects(objects, null, true, beginClearBagObjects,
{ objects : objects, proc : fnFurtherProcess, params : fnParams });
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// generic popups
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const g_genericPopupContainerId = 'generic-popup-container';
const g_genericPopupClass = 'generic-popup';
const g_genericPopupId = g_genericPopupClass;
const g_genericPopupContentContainerId = 'generic-popup-content-container';
const g_genericPopupContentClass = 'generic-popup-content';
const g_genericPopupContentId = g_genericPopupContentClass;
const g_genericPopupFixedContentId = 'generic-popup-content-fixed';
const g_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}
`;
if (document.getElementById(g_genericPopupContainerId) == null) {
let genericPopupContainer = document.createElement('div');
genericPopupContainer.id = g_genericPopupContainerId;
genericPopupContainer.innerHTML = g_genericPopupHTML;
document.body.appendChild(genericPopupContainer);
}
function genericPopupInitialize() {
document.getElementById(g_genericPopupContainerId).innerHTML = g_genericPopupHTML;
}
function genericPopupReset() {
let fixedContent = document.getElementById(g_genericPopupFixedContentId);
fixedContent.style.display = 'none';
fixedContent.innerHTML = '';
document.getElementById(g_genericPopupTitleTextId).innerText = '';
document.getElementById(g_genericPopupContentId).innerHTML = '';
document.getElementById(g_genericPopupTitleButtonContainerId).innerHTML = '';
document.getElementById(g_genericPopupFootButtonContainerId).innerHTML = '';
}
function genericPopupSetContent(title, content) {
document.getElementById(g_genericPopupTitleTextId).innerText = title;
document.getElementById(g_genericPopupContentId).innerHTML = content;
}
function genericPopupSetFixedContent(content) {
let fixedContent = document.getElementById(g_genericPopupFixedContentId);
fixedContent.style.display = 'block';
fixedContent.innerHTML = content;
}
function genericPopupAddButton(text, width, clickProc, addToTitle) {
let btn = document.createElement('button');
btn.innerText = text;
btn.onclick = clickProc;
if (width != null && width > 0) {
width = width.toString();
btn.style.width = width + (width.endsWith('px') || width.endsWith('%') ? '' : 'px');
}
else {
btn.style.width = 'auto';
}
document.getElementById(addToTitle
? g_genericPopupTitleButtonContainerId
: g_genericPopupFootButtonContainerId).appendChild(btn);
}
function genericPopupAddCloseButton(width, text, addToTitle) {
genericPopupAddButton(text?.length > 0 ? text : '关闭', width, (() => { genericPopupClose(true); }), addToTitle);
}
function genericPopupSetContentSize(height, width, scrollable) {
height = (height?.toString() ?? '100%');
width = (width?.toString() ?? '100%');
document.getElementById(g_genericPopupContentContainerId).style.width
= width + (width.endsWith('px') || width.endsWith('%') ? '' : 'px');
let content = document.getElementById(g_genericPopupContentId);
content.style.height = height + (height.endsWith('px') || height.endsWith('%') ? '' : 'px');
content.style.overflow = (scrollable ? 'auto' : 'hidden');
}
function genericPopupShowModal(clickOutsideToClose) {
genericPopupClose(false);
let popup = document.getElementById(g_genericPopupId);
if (clickOutsideToClose) {
popup.onclick = ((event) => {
if (event.target == popup) {
genericPopupClose(true);
}
});
}
else {
popup.onclick = null;
}
popup.style.display = "flex";
}
function genericPopupClose(reset) {
genericPopupCloseProgressMessage();
let popup = document.getElementById(g_genericPopupId);
popup.style.display = "none";
if (reset) {
genericPopupReset();
}
httpRequestClearAll();
}
function genericPopupShowProgressMessage(progressMessage) {
genericPopupClose(false);
document.getElementById(g_genericPopupProgressContentId).innerText = (progressMessage?.length > 0 ? progressMessage : '请稍候…');
document.getElementById(g_genericPopupProgressId).style.display = "flex";
}
function genericPopupCloseProgressMessage() {
document.getElementById(g_genericPopupProgressId).style.display = "none";
}
//
// generic task-list based progress popup
//
const g_genericPopupTaskListId = 'generic-popup-task-list';
const g_genericPopupTaskItemId = 'generic-popup-task-item-';
const g_genericPopupTaskWaiting = '×';
const g_genericPopupTaskCompleted = '√';
const g_genericPopupTaskCompletedWithError = '!';
const g_genericPopupColorTaskIncompleted = '#c00000';
const g_genericPopupColorTaskCompleted = '#0000c0';
const g_genericPopupColorTaskCompletedWithError = 'red';
var g_genericPopupIncompletedTaskCount = 0;
function genericPopupTaskListPopupSetup(title, popupWidth, tasks, fnCancelRoutine, cancelButtonText, cancelButtonWidth) {
g_genericPopupIncompletedTaskCount = tasks.length;
genericPopupSetContent(title, `
`);
let indicatorList = document.getElementById(g_genericPopupTaskListId);
for (let i = 0; i < g_genericPopupIncompletedTaskCount; i++) {
let li = document.createElement('li');
li.id = g_genericPopupTaskItemId + i;
li.style.color = g_genericPopupColorTaskIncompleted;
li.innerHTML = `${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 = document.getElementById(g_genericPopupTaskItemId + index)?.lastChild;
if (item != undefined) {
item.innerText = state;
}
}
function genericPopupTaskComplete(index, error) {
let li = document.getElementById(g_genericPopupTaskItemId + index);
if (li?.firstChild?.innerText == g_genericPopupTaskWaiting) {
li.firstChild.innerText = (error ? g_genericPopupTaskCompletedWithError : g_genericPopupTaskCompleted);
li.style.color = (error ? g_genericPopupColorTaskCompletedWithError : g_genericPopupColorTaskCompleted);
g_genericPopupIncompletedTaskCount--;
}
}
function genericPopupTaskCheckCompletion() {
return (g_genericPopupIncompletedTaskCount == 0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// constants
//
////////////////////////////////////////////////////////////////////////////////////////////////////
const g_roles = [
{ index : -1 , id : 3000 , name : '舞' , shortMark : 'WU' },
{ index : -1 , id : 3001 , name : '默' , shortMark : 'MO' },
{ index : -1 , id : 3002 , name : '琳' , shortMark : 'LIN' },
{ index : -1 , id : 3003 , name : '艾' , shortMark : 'AI' },
{ index : -1 , id : 3004 , name : '梦' , shortMark : 'MENG' },
{ index : -1 , id : 3005 , name : '薇' , shortMark : 'WEI' },
{ index : -1 , id : 3006 , name : '伊' , shortMark : 'YI' },
{ index : -1 , id : 3007 , name : '冥' , shortMark : 'MING' },
{ index : -1 , id : 3008 , name : '命' , shortMark : 'MIN' },
{ index : -1 , id : 3009 , name : '希' , shortMark : 'XI' } ];
const g_roleMap = new Map();
g_roles.forEach((item, index) => {
item.index = index;
g_roleMap.set(item.id, item);
g_roleMap.set(item.id.toString(), item);
g_roleMap.set(item.name, item);
g_roleMap.set(item.shortMark, item);
});
const g_equipAttributes = [
{ index : -1 , type : 0 , name : '物理攻击' },
{ index : -1 , type : 0 , name : '魔法攻击' },
{ index : -1 , type : 0 , name : '攻击速度' },
{ index : -1 , type : 0 , name : '最大生命' },
{ index : -1 , type : 0 , name : '最大护盾' },
{ index : -1 , type : 1 , name : '附加物伤' },
{ index : -1 , type : 1 , name : '附加魔伤' },
{ index : -1 , type : 1 , name : '附加攻速' },
{ index : -1 , type : 1 , name : '附加生命' },
{ index : -1 , type : 1 , name : '附加护盾' },
{ index : -1 , type : 1 , name : '附加回血' },
{ index : -1 , type : 1 , name : '附加回盾' },
{ index : -1 , type : 0 , name : '护盾回复' },
{ index : -1 , type : 0 , name : '物理穿透' },
{ index : -1 , type : 0 , name : '魔法穿透' },
{ index : -1 , type : 0 , name : '暴击穿透' },
{ index : -1 , type : 1 , name : '附加物穿' },
{ index : -1 , type : 1 , name : '附加物防' },
{ index : -1 , type : 1 , name : '附加魔防' },
{ index : -1 , type : 1 , name : '物理减伤' },
{ index : -1 , type : 1 , name : '魔法减伤' },
{ index : -1 , type : 0 , name : '生命偷取' },
{ index : -1 , type : 0 , name : '伤害反弹' } ];
g_equipAttributes.forEach((item, index) => {
item.index = index;
});
const g_equipments = [
{
index : -1,
name : '反叛者的刺杀弓',
type : 0,
attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 30 },
{ attribute : g_equipAttributes[15] , factor : 1 / 20 , additive : 10 },
{ attribute : g_equipAttributes[13] , factor : 1 / 20 , additive : 10 },
{ attribute : g_equipAttributes[16] , factor : 1 , additive : 0 } ],
merge : null,
shortMark : 'ASSBOW'
},
{
index : -1,
name : '狂信者的荣誉之刃',
type : 0,
attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 20 },
{ attribute : g_equipAttributes[2] , factor : 1 / 5 , additive : 20 },
{ attribute : g_equipAttributes[15] , factor : 1 / 20 , additive : 10 },
{ attribute : g_equipAttributes[13] , factor : 1 / 20 , additive : 10 } ],
merge : null,
shortMark : 'BLADE'
},
{
index : -1,
name : '陨铁重剑',
type : 0,
attributes : [ { attribute : g_equipAttributes[5] , factor : 20 , additive : 0 },
{ attribute : g_equipAttributes[5] , factor : 20 , additive : 0 },
{ attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 30 },
{ attribute : g_equipAttributes[15] , factor : 1 / 20 , additive : 1 } ],
merge : [ [ 0, 1 ], [ 2 ], [ 3 ] ],
shortMark : 'CLAYMORE'
},
{
index : -1,
name : '幽梦匕首',
type : 0,
attributes : [ { attribute : g_equipAttributes[0] , factor : 1 / 5 , additive : 0 },
{ attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 },
{ attribute : g_equipAttributes[7] , factor : 4 , additive : 0 },
{ attribute : g_equipAttributes[2] , factor : 1 / 5 , additive : 25 } ],
merge : null,
shortMark : 'DAGGER'
},
{
index : -1,
name : '荆棘剑盾',
type : 0,
attributes : [ { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 },
{ attribute : g_equipAttributes[22] , factor : 1 / 15 , additive : 0 },
{ attribute : g_equipAttributes[17] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[18] , factor : 1 , additive : 0 } ],
merge : null,
shortMark : 'SHIELD'
},
{
index : -1,
name : '光辉法杖',
type : 0,
attributes : [ { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 },
{ attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 },
{ attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 0 },
{ attribute : g_equipAttributes[14] , factor : 1 / 20 , additive : 0 } ],
merge : [ [ 0, 1, 2 ], [ 3 ] ],
shortMark : 'WAND'
},
{
index : -1,
name : '探险者短弓',
type : 0,
attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[6] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[7] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 } ],
merge : null,
shortMark : 'BOW'
},
{
index : -1,
name : '探险者短杖',
type : 0,
attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[6] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[14] , factor : 1 / 20 , additive : 5 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 } ],
merge : null,
shortMark : 'STAFF'
},
{
index : -1,
name : '探险者之剑',
type : 0,
attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[6] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[16] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 10 } ],
merge : null,
shortMark : 'SWORD'
},
{
index : -1,
name : '命师的传承手环',
type : 1,
attributes : [ { attribute : g_equipAttributes[1] , factor : 1 / 5 , additive : 1 },
{ attribute : g_equipAttributes[14] , factor : 1 / 20 , additive : 1 },
{ attribute : g_equipAttributes[9] , factor : 20 , additive : 0 },
{ attribute : g_equipAttributes[18] , factor : 1 , additive : 0 } ],
merge : null,
shortMark : 'BRACELET'
},
{
index : -1,
name : '秃鹫手套',
type : 1,
attributes : [ { attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 5 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 5 },
{ attribute : g_equipAttributes[21] , factor : 1 / 15 , additive : 5 },
{ attribute : g_equipAttributes[7] , factor : 2 , additive : 0 } ],
merge : [ [ 0, 1, 2 ], [ 3 ] ],
shortMark : 'VULTURE'
},
{
index : -1,
name : '探险者手套',
type : 1,
attributes : [ { attribute : g_equipAttributes[5] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[6] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[7] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[8] , factor : 10 , additive : 0 } ],
merge : null,
shortMark : 'GLOVES'
},
{
index : -1,
name : '旅法师的灵光袍',
type : 2,
attributes : [ { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[11] , factor : 30 , additive : 0 },
{ attribute : g_equipAttributes[4] , factor : 1 / 5 , additive : 25 },
{ attribute : g_equipAttributes[9] , factor : 50 , additive : 0 } ],
merge : null,
shortMark : 'CLOAK'
},
{
index : -1,
name : '战线支撑者的荆棘重甲',
type : 2,
attributes : [ { attribute : g_equipAttributes[3] , factor : 1 / 5 , additive : 20 },
{ attribute : g_equipAttributes[17] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[18] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[22] , factor : 1 / 15 , additive : 10 } ],
merge : null,
shortMark : 'THORN'
},
{
index : -1,
name : '探险者铁甲',
type : 2,
attributes : [ { attribute : g_equipAttributes[8] , factor : 20 , additive : 0 },
{ attribute : g_equipAttributes[17] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[18] , factor : 1 , additive : 0 },
{ attribute : g_equipAttributes[10] , factor : 10 , additive : 0 } ],
merge : null,
shortMark : 'PLATE'
},
{
index : -1,
name : '探险者皮甲',
type : 2,
attributes : [ { attribute : g_equipAttributes[8] , factor : 25 , additive : 0 },
{ attribute : g_equipAttributes[19] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[20] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[10] , factor : 6 , additive : 0 } ],
merge : null,
shortMark : 'LEATHER'
},
{
index : -1,
name : '探险者布甲',
type : 2,
attributes : [ { attribute : g_equipAttributes[8] , factor : 25 , additive : 0 },
{ attribute : g_equipAttributes[19] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[20] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[10] , factor : 6 , additive : 0 } ],
merge : null,
shortMark : 'CLOTH'
},
{
index : -1,
name : '天使缎带',
type : 3,
attributes : [ { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[9] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[10] , factor : 5 , additive : 0 },
{ attribute : g_equipAttributes[12] , factor : 1 / 30 , additive : 0 } ],
merge : null,
shortMark : 'RIBBON'
},
{
index : -1,
name : '占星师的发饰',
type : 3,
attributes : [ { attribute : g_equipAttributes[8] , factor : 5 , additive : 0 },
{ attribute : g_equipAttributes[4] , factor : 1 / 5 , additive : 0 },
{ attribute : g_equipAttributes[9] , factor : 20 , additive : 0 },
{ attribute : g_equipAttributes[19] , factor : 2 , additive : 0 } ],
merge : null,
shortMark : 'TIARA'
},
{
index : -1,
name : '探险者头巾',
type : 3,
attributes : [ { attribute : g_equipAttributes[8] , factor : 10 , additive : 0 },
{ attribute : g_equipAttributes[19] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[20] , factor : 2 , additive : 0 },
{ attribute : g_equipAttributes[10] , factor : 4 , additive : 0 } ],
merge : null,
shortMark : 'SCARF'
}];
const g_defaultEquipAttributeMerge = [ [0], [1], [2], [3] ];
function defaultequipmentNodeComparer(setting, eqKey, eq1, eq2) {
let eqMeta = g_equipMap.get(eqKey);
let delta = [];
let minorAdv = 0;
let majorDis = 0;
eqMeta.attributes.forEach((item, index) => {
let d = Math.trunc((eq1[0] * item.factor + item.additive) * eq1[index + 1]) -
Math.trunc((eq2[0] * item.factor + item.additive) * eq2[index + 1]);
if (setting[index + 1]) {
delta.push(0);
if (d > 0) {
minorAdv++;
}
}
else {
delta.push(d);
}
});
let merge = (eqMeta.merge?.length > 1 ? eqMeta.merge : g_defaultEquipAttributeMerge);
for (let indices of merge) {
let sum = 0;
indices.forEach((index) => { sum += delta[index]; });
if (sum > 0) {
return true;
}
else if (sum < 0) {
majorDis++;
}
};
return (majorDis == 0 && minorAdv > 0);
}
const g_equipMap = new Map();
g_equipments.forEach((item, index) => {
item.index = index;
g_equipMap.set(item.name, item);
g_equipMap.set(item.shortMark, item);
});
const g_halos = [
{ index : -1 , id : 101 , name : '启程之誓' , points : 10 , shortMark : 'SHI' },
{ index : -1 , id : 102 , name : '启程之心' , points : 10 , shortMark : 'XIN' },
{ index : -1 , id : 103 , name : '启程之风' , points : 10 , shortMark : 'FENG' },
{ index : -1 , id : 201 , name : '破壁之心' , points : 30 , shortMark : 'BI' },
{ index : -1 , id : 202 , name : '破魔之心' , points : 30 , shortMark : 'MO' },
{ index : -1 , id : 203 , name : '复合护盾' , points : 30 , shortMark : 'DUN' },
{ index : -1 , id : 204 , name : '鲜血渴望' , points : 30 , shortMark : 'XUE' },
{ index : -1 , id : 205 , name : '削骨之痛' , points : 30 , shortMark : 'XIAO' },
{ index : -1 , id : 206 , name : '圣盾祝福' , points : 30 , shortMark : 'SHENG' },
{ index : -1 , id : 301 , name : '伤口恶化' , points : 50 , shortMark : 'SHANG' },
{ index : -1 , id : 302 , name : '精神创伤' , points : 50 , shortMark : 'SHEN' },
{ index : -1 , id : 303 , name : '铁甲尖刺' , points : 50 , shortMark : 'CI' },
{ index : -1 , id : 304 , name : '忍无可忍' , points : 50 , shortMark : 'REN' },
{ index : -1 , id : 305 , name : '热血战魂' , points : 50 , shortMark : 'RE' },
{ index : -1 , id : 306 , name : '点到为止' , points : 50 , shortMark : 'DIAN' },
{ index : -1 , id : 401 , name : '沸血之志' , points : 100 , shortMark : 'FEI' },
{ index : -1 , id : 402 , name : '波澜不惊' , points : 100 , shortMark : 'BO' },
{ index : -1 , id : 403 , name : '飓风之力' , points : 100 , shortMark : 'JU' },
{ index : -1 , id : 404 , name : '红蓝双刺' , points : 100 , shortMark : 'HONG' },
{ index : -1 , id : 405 , name : '荧光护盾' , points : 100 , shortMark : 'JUE' },
{ index : -1 , id : 406 , name : '后发制人' , points : 100 , shortMark : 'HOU' } ];
const g_haloMap = new Map();
g_halos.forEach((item, index) => {
item.index = index;
g_haloMap.set(item.id, item);
g_haloMap.set(item.id.toString(), item);
g_haloMap.set(item.name, item);
g_haloMap.set(item.shortMark, item);
});
function removeInvalidBindings() {
let udata = getUserData();
for (let key in udata.dataBind) {
if (g_roleMap.get(key) == undefined) {
delete udata.dataBind[key];
}
}
setUserData(udata);
}
if (localStorage.getItem(g_kfUser) == null) {
localStorage.setItem(g_kfUser, '{"dataIndex":{"battleInfoNow":"0","battleInfoBefore":"0","battleInfoBack":"0"},"dataBind":{}}');
}
function getUserData() {
return JSON.parse(localStorage.getItem(g_kfUser));
}
function setUserData(json) {
localStorage.setItem(g_kfUser, JSON.stringify(json));
}
function readEquipmentDOM(responseText) {
let div0 = document.createElement('div');
div0.innerHTML = `${responseText}`;
div0.innerHTML = `${div0.children[0].children[1].innerHTML}${div0.children[1].children[1].innerHTML}`;
return div0;
}
function getEquipmentInfo(nodes) {
let data = [];
if (nodes.length > 0) {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].className.split(' ').length != 3 || nodes[i].innerText.indexOf('+') != -1) {
continue;
}
let id = nodes[i].getAttribute('onclick')?.match(/\d+/)[0];
let attr = nodes[i].getAttribute('data-content')?.match(/>\s*\d+%\s*(\d+));
let name = g_equipMap.get(title?.substring(title.lastIndexOf('>') + 1).trim())?.shortMark;
let mys = (nodes[i].getAttribute('data-content')?.match(/\[神秘属性\]/) == null ? 0 : 1);
if (attr?.length > 0 && title?.length > 1 && lv?.length > 0 && name?.length > 0) {
data.push([ name, lv[1], attr[0].replaceAll(/[\s<>%]/g, ''), attr[1].replaceAll(/[\s<>%]/g, ''),
attr[2].replaceAll(/[\s<>%]/g, ''), attr[3].replaceAll(/[\s<>%]/g, ''), mys, id ]);
}
}
}
return data;
}
function equipmentNodeComparer(e1, e2) {
try {
let equip1 = undefined;
let title1 = (e1.getAttribute('data-original-title') ?? e1.getAttribute('title'));
if (title1?.length > 0) {
equip1 = g_equipMap.get(title1.substring(title1.lastIndexOf('>') + 1).trim());
e1.setAttribute('data-abbr', (equip1?.index ?? ''));
}
let equip2 = undefined;
let title2 = (e2.getAttribute('data-original-title') ?? e2.getAttribute('title'));
if (title2?.length > 0) {
equip2 = g_equipMap.get(title2.substring(title2.lastIndexOf('>') + 1).trim());
e2.setAttribute('data-abbr', (equip2?.index ?? ''));
}
if (!equip1 && !equip2) {
return ((new Amulet()).fromNode(e1)?.compareTo((new Amulet()).fromNode(e2)) ?? 1);
}
else if (!equip1) {
return 1;
}
else if (!equip2) {
return -1;
}
else if (equip1.index == equip2.index) {
return parseInt(title1.match(/>(\d+))[1]) - parseInt(title2.match(/>(\d+))[1]);
}
return (equip1.index - equip2.index);
}
catch (err) {
console.log(err);
}
}
function getPostData(p1, p2) {
let data = -1;
let sc = document.getElementsByTagName('script');
for (let i = 0; i < sc.length; i++) {
let func = sc[i].innerText.match(p1);
if (func != null) {
data = func[0].match(p2)[0];
break;
}
}
return data;
}
function wishExpireTip() {
GM_xmlhttpRequest({
method: g_postMethod,
url: g_readUrl,
headers: g_postHeader,
data: `f=19`,
onload: response => {
let points = response.responseText.split('#')[1];
if (parseInt(points) < 2) {
let navBar = document.querySelector('.nav.navbar-nav');
for (let nav of navBar.children) {
if (nav.firstChild.innerHTML.indexOf('许愿池') >= 0) {
nav.firstChild.innerHTML = nav.firstChild.innerHTML.replace('许愿池', '许愿池(已过期)');
nav.firstChild.style.color = 'white';
nav.firstChild.style.backgroundColor = 'red';
break;
}
}
}
}
});
}
removeInvalidBindings();
wishExpireTip();
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// page add-ins
//
////////////////////////////////////////////////////////////////////////////////////////////////////
if (window.location.pathname == '/fyg_index.php') {
let userData = getUserData();
let dataIndex = userData.dataIndex;
let waitForCol = setInterval(() => {
let colmd4 = document.getElementsByClassName('col-md-4');
if (colmd4?.length > 0 && colmd4[0]?.children?.length > 5) {
clearInterval(waitForCol);
let px = colmd4[0].children[5];
let p0 = document.createElement(px.tagName);
p0.className = px.className;
p0.innerText = '对玩家战斗(上次查看)';
let sp = document.createElement(px.children[0].tagName);
sp.className = px.children[0].className;
dataIndex.battleInfoNow = px.children[0].innerText;
if (dataIndex.battleInfoNow == dataIndex.battleInfoBefore) {
sp.innerText = dataIndex.battleInfoBack;
}
else {
sp.innerText = dataIndex.battleInfoBefore;
dataIndex.battleInfoBack = dataIndex.battleInfoBefore;
dataIndex.battleInfoBefore = dataIndex.battleInfoNow
setUserData(userData);
}
p0.appendChild(sp);
colmd4[0].appendChild(p0);
}
}, 200);
const USER_DATA_xPORT_SEPARATOR = '\n';
function importUserConfigData() {
genericPopupSetContent(
'导入内容',
`
请将从其它系统中使用同一帐号导出的内容填入文本框中并执行导入操作
`);
genericPopupAddButton(
'执行导入',
0,
(() => {
let userData = document.getElementById("user_data_persistence_string").value.split(USER_DATA_xPORT_SEPARATOR);
if (userData.length > 0) {
if (confirm('导入操作会覆盖已有的用户配置(护符组定义、卡片装备光环护符绑定、海滩装备筛选配置等等),要继续吗?')) {
let backup = [];
let importedItems = [];
let illegalItems = [];
g_userDataStorageKeyConfig.forEach((item, index) => {
backup[index] = localStorage.getItem(item);
});
userData.forEach((item) => {
if ((item = item.trim()).length > 0) {
let key = item.slice(0, item.indexOf(USER_STORAGE_KEY_VALUE_SEPARATOR));
if (g_userDataStorageKeyConfig.indexOf(key) >= 0) {
if (illegalItems.length == 0) {
localStorage.setItem(key, item.substring(key.length + 1));
importedItems.push(key);
}
}
else {
illegalItems.push(key);
}
}
});
if (illegalItems.length > 0) {
importedItems.forEach((item) => {
let index = g_userDataStorageKeyConfig.indexOf(item);
if (index >= 0 && backup[index] != null) {
localStorage.setItem(item, backup[index]);
}
else {
localStorage.removeItem(item);
}
});
alert('输入内容格式有误,有非法项目导致导入失败,请检查:\n [ ' + illegalItems.join(' ]\n [ ') + ' ]');
}
else if (importedItems.length > 0) {
alert('导入已完成:\n [ ' + importedItems.join(' ]\n [ ') + ' ]');
genericPopupClose(true);
window.location.reload();
}
else {
alert('输入内容格式有误,导入失败,请检查!');
}
}
}
else {
alert('输入内容格式有误,导入失败,请检查!');
}
}),
true);
genericPopupAddCloseButton(80);
genericPopupSetContentSize(400, 600, false);
genericPopupShowModal(true);
}
function exportUserConfigData() {
genericPopupSetContent(
'导出内容',
`
请勿修改任何导出内容,将其保存为纯文本在其它系统中使用相同的帐号执行导入操作
`);
genericPopupAddButton(
'复制导出内容至剪贴板',
0,
(() => {
let tipContainer = document.getElementById('user_data_export_tip');
let tipColor = tipContainer.style.color;
let tipString = tipContainer.innerText;
tipContainer.style.color = '#ff0000';
document.querySelector('#user_data_persistence_string').select();
if (document.execCommand('copy')) {
tipContainer.innerText = '导出内容已复制到剪贴板';
}
else {
tipContainer.innerText = '复制失败,这可能是因为浏览器没有剪贴板访问权限,请进行手工复制(CTRL+A, CTRL+C)';
}
setTimeout((() => {
tipContainer.style.color = tipColor;
tipContainer.innerText = tipString;
}), 3000);
}),
true);
genericPopupAddCloseButton(80);
let userData = [];
g_userDataStorageKeyConfig.forEach((item) => {
let value = localStorage.getItem(item);
if (value != null) {
userData.push(`${item}${USER_STORAGE_KEY_VALUE_SEPARATOR}${value}`);
}
});
document.getElementById("user_data_persistence_string").value = userData.join(USER_DATA_xPORT_SEPARATOR);
genericPopupSetContentSize(400, 600, false);
genericPopupShowModal(true);
}
function clearUserData() {
if (confirm('这将清除所有用户配置(护符组定义、卡片装备光环护符绑定、海滩装备筛选配置等等)和数据,要继续吗?')) {
g_userDataStorageKeyConfig.concat(g_userDataStorageKeyExtra).forEach((item) => {
localStorage.removeItem(item);
});
alert('用户配置和数据已全部清除!');
window.location.reload();
}
}
let waitForUserd = setInterval(() => {
let userd = document.getElementById('userd');
if (userd?.children?.length > 0) {
clearInterval(waitForUserd);
let globalDataBtnContainer = document.createElement(userd.tagName);
globalDataBtnContainer.id = 'global-data-button-container';
globalDataBtnContainer.className = userd.className;
globalDataBtnContainer.style.borderTop = '2px solid #d0d0d0';
let versionLabel = document.createElement(userd.firstChild.tagName);
versionLabel.innerText = '插件版本:';
versionLabel.className = userd.firstChild.className;
let versionText = document.createElement(userd.firstChild.children[0].tagName);
versionText.className = userd.firstChild.children[0].className;
versionText.innerText = g_modificationVersion;
versionLabel.appendChild(versionText);
globalDataBtnContainer.appendChild(versionLabel);
let importBtn = document.createElement('button');
importBtn.innerHTML = '导入用户配置数据';
importBtn.style.height = '35px';
importBtn.style.width = '100%';
importBtn.style.marginBottom = '1px';
importBtn.onclick = (() => {
importUserConfigData();
});
globalDataBtnContainer.appendChild(importBtn);
let exportBtn = document.createElement('button');
exportBtn.innerHTML = '导出用户配置数据';
exportBtn.style.height = '35px';
exportBtn.style.width = '100%';
exportBtn.style.marginBottom = '1px';
exportBtn.onclick = (() => {
exportUserConfigData();
});
globalDataBtnContainer.appendChild(exportBtn);
let eraseBtn = document.createElement('button');
eraseBtn.innerHTML = '清除用户数据';
eraseBtn.style.height = '35px';
eraseBtn.style.width = '100%';
eraseBtn.onclick = (() => {
clearUserData();
});
globalDataBtnContainer.appendChild(eraseBtn);
userd.parentNode.appendChild(globalDataBtnContainer);
}
}, 200);
}
else if (window.location.pathname == '/fyg_equip.php') {
let waitForBackpacks = setInterval(() => {
if (document.getElementById('backpacks')?.children?.length > 0) {
clearInterval(waitForBackpacks);
let panel = document.getElementsByClassName('panel panel-primary')[1];
let calcBtn = document.createElement('button');
let calcDiv = document.createElement('div');
calcBtn.innerText = '导出计算器';
calcBtn.onclick = (() => {});
panel.insertBefore(calcBtn, panel.children[0]);
panel.insertBefore(calcDiv, calcBtn);
const storeQueryString = '#backpacks > div.alert.alert-success.with-icon';
const storeButtonId = 'collapse-backpacks-store';
const storeDivkey = 'backpacksStore';
let equipmentDiv = document.createElement('div');
equipmentDiv.id = 'equipmentDiv';
equipmentDiv.innerHTML =
`
全部展开
使用深色背景
护符 ▼
武器装备 ▼
手臂装备 ▼
身体装备 ▼
头部装备 ▼
仓库 ▼
`;
let forceEquipDivOperation = true;
let equipDivExpanded = {};
let collapseBtns = equipmentDiv.querySelectorAll('.btn.btn-block.collapsed');
for (let btn of collapseBtns) {
btn.onclick = backupEquipmentDivState;
}
function backupEquipmentDivState(e) {
let target = equipmentDiv.querySelector(e?.target.getAttribute('data-target'));
if (target != null) {
equipDivExpanded[target.id] = !equipDivExpanded[target.id];
}
else if ((e?.target.id ?? storeButtonId) == storeButtonId) {
equipDivExpanded[storeDivkey] = !equipDivExpanded[storeDivkey];
}
};
function collapseEquipmentDiv(expand, force) {
let target;
let btns = equipmentDiv.querySelectorAll('.btn.btn-block');
for (let btn of btns) {
if (btn.getAttribute('data-toggle') == 'collapse' &&
(target = equipmentDiv.querySelector(btn.getAttribute('data-target'))) != null) {
let exp = expand;
if (equipDivExpanded[target.id] == undefined || force) {
equipDivExpanded[target.id] = exp;
}
else {
exp = equipDivExpanded[target.id];
}
target.className = (exp ? 'in' : 'collapse');
target.style.height = (exp ? 'auto' : '0px');
}
}
if (equipDivExpanded[storeDivkey] == undefined || force) {
equipDivExpanded[storeDivkey] = expand;
}
if (equipDivExpanded[storeDivkey]) {
$(storeQueryString).show();
} else {
$(storeQueryString).hide();
}
}
function changeEquipmentDivStyle(bg) {
$('#equipmentDiv .backpackDiv').css({
'background-color': bg ? 'black' : '#ffe5e0'
});
$('#equipmentDiv .storeDiv').css({
'background-color': bg ? 'black' : '#ddf4df'
});
$('#equipmentDiv .btn-light').css({
'background-color': bg ? 'black' : 'white'
});
$('#equipmentDiv .popover-content-show').css({
'background-color': bg ? 'black' : 'white'
});
$('#equipmentDiv .popover-title').css({
'color': bg ? 'black' : 'white'
});
$('#equipmentDiv .bg-special').css({
'background-color': bg ? 'black' : '#8666b8',
'color': bg ? '#c0c0c0' : 'white',
'border-bottom': bg ? '1px solid grey' : 'none'
});
$('#equipmentDiv .btn-equipment .pull-right').css({
'color': bg ? 'black' : 'white'
});
$('#equipmentDiv .btn-equipment .bg-danger.with-padding').css({
'color': bg ? 'black' : 'white'
});
}
let equipmentExpand = equipmentDiv.querySelector('#equipment_Expand').checked =
(localStorage.getItem(g_equipmentExpandStorageKey) == 'true');
equipmentDiv.querySelector('#equipment_Expand').onchange = (() => {
localStorage.setItem(g_equipmentExpandStorageKey,
equipmentExpand = document.querySelector('#equipment_Expand').checked);
collapseEquipmentDiv(equipmentExpand, true);
});
let equipmentBG = equipmentDiv.querySelector('#equipment_BG').checked =
(localStorage.getItem(g_equipmentBGStorageKey) == 'true');
equipmentDiv.querySelector('#equipment_BG').onchange = (() => {
localStorage.setItem(g_equipmentBGStorageKey,
equipmentBG = document.querySelector('#equipment_BG').checked);
changeEquipmentDivStyle(equipmentBG);
});
function addCollapse() {
let waitForBtn = setInterval(() => {
if (document.getElementById('carding')?.innerText?.indexOf('读取中') < 0 &&
document.getElementById('backpacks')?.innerText?.indexOf('读取中') < 0) {
let eqbtns = document.querySelector("#carding > div.row > div.fyg_tc")?.children;
if (eqbtns?.length > 0 && eqbtns[0].className.endsWith('fyg_mp3')) {
clearInterval(waitForBtn);
let eqstore = document.querySelector("#backpacks > div.alert-success > div.content").querySelectorAll(".fyg_mp3");
for (let i = 0; i < eqstore.length; i++) {
if (eqstore[i].className.split(' ').length == 3) {
eqstore[i].dataset.instore = 1;
}
}
eqbtns =
Array.from(eqbtns).concat(
Array.from(document.querySelector("#backpacks > div.alert-danger > div.content")
.querySelectorAll(".fyg_mp3")).sort(equipmentNodeComparer)).concat(
Array.from(eqstore).sort(equipmentNodeComparer));
for (let i = eqbtns.length - 1; i >= 0; i--) {
if (eqbtns[i].className.split(' ').length != 3) {
eqbtns.splice(i, 1);
}
}
if (!(document.getElementsByClassName('collapsed')?.length > 0)) {
document.getElementById('backpacks')
.insertBefore(equipmentDiv, document.getElementById('backpacks').firstChild.nextSibling);
}
for (let i = eqbtns.length - 1; i >= 0; i--) {
if (eqbtns[i].className.split(' ')[0] == 'popover') {
eqbtns.splice(i, 1);
break;
}
}
let ineqBackpackDiv =
`
` +
`
`;
let eqDivs = [ equipmentDiv.querySelector('#eq0'),
equipmentDiv.querySelector('#eq1'),
equipmentDiv.querySelector('#eq2'),
equipmentDiv.querySelector('#eq3'),
equipmentDiv.querySelector('#eq4') ];
eqDivs.forEach((item) => { item.innerHTML = ineqBackpackDiv; });
let ineq = 0;
for (let i = 0; i < eqbtns.length; i++) {
if (eqbtns[i].innerText == '空') {
continue;
}
let btn0 = document.createElement('button');
btn0.className = 'btn btn-light';
btn0.style.minWidth = '200px';
btn0.style.marginRight = '5px';
btn0.style.marginBottom = '5px';
btn0.style.padding = '0px';
btn0.style.textAlign = 'left';
btn0.style.boxShadow = 'none';
btn0.style.lineHeight = '150%';
btn0.style.borderColor = getComputedStyle(eqbtns[i]).getPropertyValue('background-color');
btn0.setAttribute('onclick', eqbtns[i].getAttribute('onclick'));
let storeText = '';
if (eqbtns[i].dataset.instore == 1) {
storeText = '【仓】';
}
let enhancements = eqbtns[i].innerText;
if (enhancements.indexOf('+') == -1) {
enhancements = '';
}
btn0.innerHTML =
`
${storeText}${eqbtns[i].dataset.originalTitle}${enhancements}
${eqbtns[i].dataset.content}
`;
if (btn0.children[1].lastChild.nodeType == 3) { //清除背景介绍文本
btn0.children[1].lastChild.remove();
}
if (eqbtns[i].innerText.indexOf('+') >= 0) {
ineq = 4;
}
else {
let a = g_equipments[parseInt(eqbtns[i].dataset.abbr)];
if (a == null) {
let title = (eqbtns[i].getAttribute('data-original-title') ?? eqbtns[i].getAttribute('title'));
a = g_equipMap.get(title?.substring(title.lastIndexOf('>') + 1).trim());
}
if ((ineq = (a?.type ?? 4)) < 4) {
btn0.className += ' btn-equipment';
}
}
(storeText == '' ? eqDivs[ineq].firstChild : eqDivs[ineq].firstChild.nextSibling).appendChild(btn0);
}
function inputAmuletGroupName(defaultGroupName) {
let groupName = prompt('请输入护符组名称(不超过31个字符,请仅使用大、小写英文字母、数字、连字符、下划线及中文字符):',
defaultGroupName);
if (amuletIsValidGroupName(groupName)) {
return groupName;
}
else if (groupName != null) {
alert('名称不符合命名规则,信息未保存。');
}
return null;
}
function refreshEquipmentPage(fnFurtherProcess) {
let asyncOperations = 1;
let asyncObserver = new MutationObserver(() => { asyncObserver.disconnect(); asyncOperations = 0; });
asyncObserver.observe(document.getElementById('backpacks'), { childList : true , subtree : true });
eqlip(1);
eqbp(1);
let timer = setInterval(() => {
if (asyncOperations == 0) {
clearInterval(timer);
genericPopupClose(true);
if (fnFurtherProcess != null) {
fnFurtherProcess();
}
}
}, 200);
}
function queryAmulets(bag, store) {
let count = 0;
if (bag != null) {
amuletNodesToArray(document.querySelector("#backpacks > div.alert-danger > div.content")
.querySelectorAll(".fyg_mp3"), bag);
count += bag.length;
}
if (store != null) {
amuletNodesToArray(document.querySelector("#backpacks > div.alert-success > div.content")
.querySelectorAll(".fyg_mp3"), store);
count += store.length;
}
return count;
}
function showAmuletGroupsPopup() {
function beginSaveBagAsGroup(groupName, update) {
let amulets = [];
queryAmulets(amulets, null);
createAmuletGroup(groupName, amulets, update);
showAmuletGroupsPopup();
}
genericPopupClose(true);
let bag = [];
let store = [];
if (queryAmulets(bag, store) == 0) {
alert('护符信息加载异常,请检查!');
refreshEquipmentPage(null);
return;
}
let amulets = bag.concat(store);
let bagGroup = amuletCreateGroupFromArray('当前背包', bag);
let groups = amuletLoadGroups();
if (bagGroup == null && groups.count() == 0) {
alert('背包为空,且未找到预保存的护符组信息!');
return;
}
genericPopupSetContent('护符组管理', '');
let amuletContainer = document.getElementById('popup_amulet_groups');
if (bagGroup != null) {
let err = !bagGroup.validate(bag);
let groupDiv = document.createElement('div');
groupDiv.className = g_genericPopupTopLineDivClass;
groupDiv.id = 'popup_amulet_group_bag';
groupDiv.innerHTML =
``;
let saveBagGroupBtn = document.createElement('button');
saveBagGroupBtn.innerText = '保存为护符组';
saveBagGroupBtn.style.float = 'right';
saveBagGroupBtn.onclick = (() => {
let groupName = inputAmuletGroupName('');
if (groupName != null) {
beginSaveBagAsGroup(groupName, false);
}
});
groupDiv.appendChild(saveBagGroupBtn);
let groupInfoDiv = document.createElement('div');
groupInfoDiv.innerHTML =
`${bagGroup.formatBuffSummary('', ' ', '')}
${bagGroup.formatItems('', ' ', ' ', '', '')}`;
groupDiv.appendChild(groupInfoDiv);
amuletContainer.appendChild(groupDiv);
}
let li = 0
let groupArray = groups.toArray();
let gl = (groupArray?.length ?? 0);
if (gl > 0) {
groupArray = groupArray.sort((a, b) => a.name < b.name ? -1 : 1);
for (let i = 0; i < gl; i++) {
let err = !groupArray[i].validate(amulets);
let groupDiv = document.createElement('div');
groupDiv.className = g_genericPopupTopLineDivClass;
groupDiv.id = 'popup_amulet_group_' + i;
groupDiv.innerHTML =
``;
let amuletDeleteGroupBtn = document.createElement('button');
amuletDeleteGroupBtn.innerText = '删除';
amuletDeleteGroupBtn.style.float = 'right';
amuletDeleteGroupBtn.onclick = (() => {
let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
if (confirm(`删除护符组 "${groupName}" 吗?`)) {
amuletDeleteGroup(groupName);
showAmuletGroupsPopup();
}
});
groupDiv.appendChild(amuletDeleteGroupBtn);
let amuletModifyGroupBtn = document.createElement('button');
amuletModifyGroupBtn.innerText = '编辑';
amuletModifyGroupBtn.style.float = 'right';
amuletModifyGroupBtn.onclick = (() => {
let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
modifyAmuletGroup(groupName);
});
groupDiv.appendChild(amuletModifyGroupBtn);
let importAmuletGroupBtn = document.createElement('button');
importAmuletGroupBtn.innerText = '导入';
importAmuletGroupBtn.style.float = 'right';
importAmuletGroupBtn.onclick = (() => {
let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
let persistenceString = prompt('请输入护符组编码(一般由工具软件自动生成,表现形式为一组由逗号分隔的数字序列)');
if (persistenceString != null) {
let group = new AmuletGroup(`${groupName}${AMULET_STORAGE_GROUPNAME_SEPARATOR}${persistenceString}`);
if (group.isValid()) {
let groups = amuletLoadGroups();
if (groups.add(group)) {
amuletSaveGroups(groups);
showAmuletGroupsPopup();
}
else {
alert('保存失败!');
}
}
else {
alert('输入的护符组编码无效,请检查!');
}
}
});
groupDiv.appendChild(importAmuletGroupBtn);
let renameAmuletGroupBtn = document.createElement('button');
renameAmuletGroupBtn.innerText = '更名';
renameAmuletGroupBtn.style.float = 'right';
renameAmuletGroupBtn.onclick = (() => {
let oldName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
let groupName = inputAmuletGroupName(oldName);
if (groupName != null && groupName != oldName) {
let groups = amuletLoadGroups();
if (!groups.contains(groupName) || confirm(`护符组 "${groupName}" 已存在,要覆盖吗?`)) {
if (groups.rename(oldName, groupName)) {
amuletSaveGroups(groups);
showAmuletGroupsPopup();
}
else {
alert('更名失败!');
}
}
}
});
groupDiv.appendChild(renameAmuletGroupBtn);
let updateAmuletGroupBtn = document.createElement('button');
updateAmuletGroupBtn.innerText = '更新';
updateAmuletGroupBtn.style.float = 'right';
updateAmuletGroupBtn.onclick = (() => {
let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
if (confirm(`用当前背包内容替换 "${groupName}" 护符组预定内容吗?`)) {
beginSaveBagAsGroup(groupName, true);
}
});
groupDiv.appendChild(updateAmuletGroupBtn);
let unamuletLoadGroupBtn = document.createElement('button');
unamuletLoadGroupBtn.innerText = '入仓';
unamuletLoadGroupBtn.style.float = 'right';
unamuletLoadGroupBtn.onclick = (() => {
let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
genericPopupShowProgressMessage('卸载中,请稍候…');
beginUnloadAmuletGroupFromBag(groupName, refreshEquipmentPage, showAmuletGroupsPopup);
});
groupDiv.appendChild(unamuletLoadGroupBtn);
let amuletLoadGroupBtn = document.createElement('button');
amuletLoadGroupBtn.innerText = '装备';
amuletLoadGroupBtn.style.float = 'right';
amuletLoadGroupBtn.onclick = (() => {
let groupName = document.getElementById(`popup_amulet_group_${i}_name`).innerText.split(' ')[0];
genericPopupShowProgressMessage('加载中,请稍候…');
beginLoadAmuletGroupFromStore(groupName, refreshEquipmentPage, showAmuletGroupsPopup);
});
groupDiv.appendChild(amuletLoadGroupBtn);
let groupInfoDiv = document.createElement('div');
groupInfoDiv.innerHTML =
`${groupArray[i].formatBuffSummary('', ' ', '')}
${groupArray[i].formatItems('', ' ', ' ', '', '')}