Condition`)[1];
+ type = (type === 'Shield') ? 'Armor' : (type === 'Staff' ? 'Weapon' : type)
+ return await Promise.all(Object.keys(enchant[slot]).map(i => (async item => { try {
+ const threshold = enchant[slot][item];
+ const current = enchanted[item] ?? 0;
+ const enc = enchant_data[item2Enchant[item]][type];
+ if (!enc) return;
+ if (current && current >= threshold) return;
+ const failed = $doc(await $ajax.insert(`?s=Forge&ss=en`, `select_item=${id}&enchantment=${enc}`))?.innerText;
+ if (failed) {
+ console.error(failed, ':', data.t, ':', enc, '=', current, '>?', threshold, '=', current >= threshold);
+ }
+ } catch (err) { console.error(err) }; })(i)));
+ } catch (err) { console.error(err) }; })(e)));
+ } catch (err) { console.error(err) }; return false; }
+
+ async function asyncCheckRepair(isGrindFestStandalone) { try {
+ const option = g('option');
+ if (!option.repair) {
+ return true;
}
- document.title = time;
- if (time <= g('option').riddleAnswerTime) {
- riddleSubmit(gE('#riddleanswer').value || answers[parseInt(Math.random() * 3)]);
+ await waitPause();
+ $async.logSwitch(arguments);
+ let eqps;
+ const threshold = isGrindFestStandalone ? option.repairValueGF : option.repairValue;
+ if (!threshold) { // skip because default repair has been checked before idleArena>GF
+ $async.logSwitch(arguments);
+ return true;
}
- };
- for (let i = 0; i < 30; i++) {
- setTimeout(checkTime, i * _1s);
- }
-
- function riddleSubmit(answer) {
- if (!window.opener) {
- gE('#riddleanswer').value = answer;
- gE('#riddleanswer+img').click();
+ if (hvVersion < 91) {
+ const url = `?s=Forge&ss=re`;
+ const doc = $doc(await $ajax.insert(url));
+ const eqpdoc = await $ajax.insert(gE('#mainpane>script[src]', doc).src)
+ const json = JSON.parse(eqpdoc.match(/{.*}/)[0]);
+ eqps = await Promise.all(Array.from(gE('.eqp>[id]', 'all', doc)).map(async eqp => { try {
+ const id = eqp.id.match(/\d+/)[0];
+ const condition = 1 * json[id].d.match(/Condition: \d+ \/ \d+ \((\d+)%\)/)[1];
+ if (condition > threshold) {
+ return;
+ }
+ const after = $doc(await $ajax.insert(url, `select_item=${id}`));
+ return gE('.messagebox_error', )?.innerText ? undefined : json[id].t;
+ } catch (err) { console.error(err) } }));
} else {
- $ajax.fetch(window.location.href, `riddleanswer=${answer}`).then(() => { // 待续
- window.opener.document.location.href = window.location.href;
- window.close();
- }).catch(e=>console.error(e));
+ const url = `?s=Bazaar&ss=am&screen=repair&filter=equipped`;
+ const doc = $doc(await $ajax.insert(url));
+ if (gE('#riddlecounter', doc) || gE('#battle_main', doc)) {
+ $async.logSwitch(arguments);
+ return undefined;
+ }
+ const token = gE('#equipform>input[name="postoken"]', doc).value;
+ eqps = await Promise.all(Array.from(gE('#equiplist>table>tbody>tr:not(.eqselall):not(.eqtplabel)', 'all', doc)).map(async eqp => { try {
+ const id = gE('input', eqp).value;
+ const condition = 1 * gE('td:last-child', eqp).textContent.replace('%', '');
+ if (condition > threshold) {
+ return;
+ }
+ const after = $doc(await $ajax.insert(url, `&eqids[]=${id}&postoken=${token}&replace_charms=on`));
+ return gE(`#e${id}`, after) ? gE('.lc', eqp).childNodes[2].textContent : undefined;
+ } catch (err) { console.error(err) } }));
+ }
+ eqps = eqps.filter(e=>e);
+ if (eqps.length) {
+ console.log('equips need repair:\n', eqps.join('\n '));
+ switch(option.lang * 1) {
+ case 0:
+ popup(`装备需要修理:\n${eqps.join('\n ')}`);
+ break
+ case 1:
+ popup(`裝備需要修理:\n${eqps.join('\n ')}`);
+ break
+ case 2:
+ default:
+ popup(`Equips need repair:\n${eqps.join('\n ')}`);
+ break
+ }
+ document.title = `[R!]` + document.title;
}
- }
- }
+ $async.logSwitch(arguments);
+ return !eqps.length;
+ } catch (err) { console.error(err) }; return false; }
- // 战斗外//
- function checkIsHV() {
- if (window.location.host !== 'e-hentai.org') { // is in HV
- setValue('url', window.location.origin);
- return true;
- }
- setValue('lastEH', time(0));
- const isEngage = window.location.href === 'https://e-hentai.org/news.php?encounter';
- let encounter = getEncounter();
- let href = getValue('url') ?? (document.referrer.match('hentaiverse.org') ? new URL(document.referrer).origin : 'https://hentaiverse.org');
- const eventpane = gE('#eventpane');
- const now = time(0);
- let url;
- if (eventpane) { // 新一天或遭遇战
- url = gE('#eventpane>div>a')?.href.split('/')[3];
- if(url === undefined){ // 新一天
- encounter = [];
+ async function asyncCheckEquStorage() { try {
+ const option = g('option');
+ if (!option.equStorage) {
+ return true;
}
- encounter.unshift({ href: url, time: now });
- setEncounter(encounter);
- } else {
- if (encounter.length) {
- if (now - encounter[0]?.time > 0.5 * _1h) { // 延长最新一次的time, 避免因漏记录导致连续来回跳转
- encounter[0].time = now;
- setEncounter(encounter);
+ await waitPause();
+ $async.logSwitch(arguments);
+ let count;
+ if (hvVersion < 91) {
+ const url = `?s=Character&ss=in`;
+ const doc = $doc(await $ajax.insert(url));
+ if (gE('#riddlecounter', doc) || gE('#battle_main', doc)) {
+ $async.logSwitch(arguments);
+ return false;
}
- for (let e of encounter) {
- if (e.encountered) {
- continue;
- }
- url = e.href;
- break;
+ count = gE('#eqinv_bot>div>div>div', doc).innerText.match(/: (\d+) \/ \d+/)[1];
+ } else {
+ const url = `?s=Bazaar&ss=am`;
+ const doc = $doc(await $ajax.insert(url));
+ if (gE('#riddlecounter', doc) || gE('#battle_main', doc)) {
+ $async.logSwitch(arguments);
+ return false;
+ }
+ count = gE('#equipblurb>table>tbody>tr>td:nth-child(2)', doc).innerText;
+ }
+ $async.logSwitch(arguments);
+ return count * 1 <= option.equStorageValue;
+ } catch (err) { console.error(err) }; return false; }
+
+ async function checkBattleReady(method, condition = {}) {
+ await waitPause();
+ if (condition.checkEncounter) {
+ const encounter = getEncounter();
+ if (encounter[0]?.url && !encounter[0]?.encountered) {
+ console.log(getEncounter());
+ return;
}
}
- }
-
- if (!url) {
- if (isEngage && !getValue('battle')) {
- // 自动跳转,同时先刷新遭遇时间,延长下一次遭遇
- $ajax.openNoFetch(getValue('lastHref'));
+ const option = g('option');
+ const stamina = getValue('stamina', true);
+ const [low, lowNR, cost, ratio] = [condition.staminaLow??option.staminaLow, option.staminaLowWithReNat??0, Math.round((condition.staminaCost??0) * 100) / 100, stamina.punish ? stamina.ratio??1 : 1]
+ const checked = await checkStamina(low, cost);
+ const [staminaChecked, stmNR] = [checked.checked, checked.stmNR];
+ const [neat, neatNR] = [stamina.current-low, stmNR-lowNR];
+ console.log(
+ 'stamina check succeed:', staminaChecked === 1, ... staminaChecked === -1 ? ['with nature recover', lowNR, 'stmNR:', stmNR, '(', ... neatNR>=0 ? ['+', neatNR] : ['-', -neatNR], ')'] : [],
+ '\nlow:', low, ... cost ? ['cost:', cost, ... stamina.punish ? ['*', ratio, '=', Math.round(cost*ratio*10000)/10000] : [], 'current:', stamina.current, '(', neat>=0?'+':'-', neat, ')'] : [],
+ '\nstamina:', stamina,
+ );
+ if (staminaChecked === 1) { // succeed
+ document.title = document.title.replace(`[S!]`, '');
+ return true;
}
- return;
- }
-
- // 减少因在恒定世界处于战斗中时打开eh触发了遭遇而导致的错失
- // 缓存当前链接,等战斗结束时再自动打开,下次打开链接时:
- // 1. 若新的遭遇未出现,进入已缓存的战斗链接
- // 2. 若新的遭遇已出现,则前一次已超时失效错过,重新获取新的一次
- if (!isEngage) { // 战斗外,非自动跳转
- eventpane.style.cssText += 'color:red;' // 链接标红提醒
- } else if (getValue('battle')) { //战斗中
- eventpane.style.cssText += 'color:gray;' // 链接置灰提醒
- } else { // 战斗外,自动跳转
- $ajax.openNoFetch(`${href}/${url}`);
- }
- }
-
- function setEncounter(encounter) {
- return g('encounter', setValue('encounter', encounter));
- }
-
- function getEncounter() {
- const getToday = (encounter) => encounter.filter(e => time(2, e.time) === time(2));
- const current = g('encounter') ?? [];
- let encounter = getValue('encounter', true) ?? [];
- if (JSON.stringify(current) === JSON.stringify(encounter)) {
- return getToday(encounter);
- }
- let dict = {};
- for (let e of current) {
- dict[e.href ?? `newDawn`] = e;
- }
- for (let e of encounter) {
- const key = e.href ?? `newDawn`;
- dict[key] ??= e;
- dict[key].time = Math.max(dict[key].time, e.time);
- dict[key].encountered = (e.encountered || dict[key].encountered) ? Math.max(dict[key].encountered ?? 0, e.encountered ?? 0) : undefined;
- }
- return getToday(Object.values(dict)).sort((x, y) => x.time < y.time ? 1 : x.time > y.time ? -1 : 0);
- }
-
- function quickSite() { // 快捷站点
- const quickSiteBar = gE('body').appendChild(cE('div'));
- quickSiteBar.className = 'quickSiteBar';
- quickSiteBar.innerHTML = '
<<
贴吧
Forums';
- if (g('option').quickSite) {
- g('option').quickSite.forEach((site) => {
- quickSiteBar.innerHTML = `${quickSiteBar.innerHTML}
${(site.fav) ? `
` : ''}${site.name}`;
- });
- }
- gE('.quickSiteBarToggle', quickSiteBar).onclick = function () {
- const spans = gE('span', 'all', quickSiteBar);
- for (let i = 1; i < spans.length; i++) {
- spans[i].style.display = (this.textContent === '<<') ? 'none' : 'block';
+ if (staminaChecked === 0) { // failed currently
+ const now = time(0);
+ setTimeout(method, Math.floor(now / _1h + 1) * _1h - now);
+ // popup('Failed stamina check for now.');
+ if (!document.title.includes(`[S!]`)) {
+ document.title = `[S!]` + document.title;
+ }
+ } else { // case -1: // failed with nature recover
+ switch(option.lang * 1) {
+ case 0:
+ popup('当日精力不足(含自然恢复)');
+ break
+ case 1:
+ popup('當日精力不足(含自然恢復)');
+ break
+ case 2:
+ default:
+ popup('Failed stamina check with nature recover.');
+ break
+ }
+ document.title = `[S!!]` + document.title;
+ }
+ }
+
+ async function getCurrentStamina() { try {
+ // await waitPause();
+ $async.logSwitch(arguments);
+ const doc = $doc(await $ajax.insert(window.location.href));
+ if (gE('#riddlecounter', doc) || gE('#battle_main', doc)) {
+ $async.logSwitch(arguments);
+ return [ undefined, undefined ];
+ }
+ const current = gE('#stamina_readout .fc4.far>div', doc).textContent.match(/\d+/)[0] * 1;
+ const punish = !!gE('#stamina_readout .fc4.far', doc).parentNode.title;
+ $async.logSwitch(arguments);
+ return [current, punish ? punish : undefined];
+ } catch(e) { console.error(e); }}
+
+ async function checkStamina(low, cost) {
+ // await waitPause();
+ $async.logSwitch(arguments);
+ const stamina = getValue('stamina', true);
+ const option = g('option');
+ let now = time(0);
+ let hours = Math.floor(now / _1h);
+ let [current, punish] = await getCurrentStamina();
+ const stmNR = current + 24 - (hours % 24);
+ cost ??= 0;
+ if (punish && option.staminaRatio) {
+ cost *= stamina.ratio
+ }
+ const stmNRChecked = !cost || stmNR - cost >= option.staminaLowWithReNat;
+ const result = { checked: stmNRChecked ? (current - cost >= (low ?? option.staminaLow)) ? 1 : 0 : -1, stmNR: stmNR };
+ $async.logSwitch(arguments);
+ if (result.checked === 1 || isIsekai || !option.restoreStamina) return result;
+ const items = g('items');
+ $async.logSwitch(arguments);
+ if (!items) return result;
+ const recoverItems = { 11401: true, 11402: false }
+ const isPerk = stamina.perk?.includes(getCurrentUser());
+ for (let id in recoverItems) {
+ if (!items[id]) continue;
+ const recover = recoverItems[id] ? isPerk ? 20 : 10 : 5;
+ if (current + recover >= 100) continue; // check if overflow by (20 or 10) -> (5)
+ const recovered = gE('#stamina_readout .fc4.far>div', $doc(await $ajax.insert(window.location.href, 'recover=stamina'))).textContent.match(/\d+/)[0] * 1;
+ goto();
+ break;
}
- this.textContent = (this.textContent === '<<') ? '>>' : '<<';
- };
- }
-
- function autoSwitchIsekai() {
- if (!g('option').isekai) {
- // 若不启用自动跳转
- return;
+ $async.logSwitch(arguments);
+ return result;
}
- window.location.href = `${href.slice(0, href.indexOf('.org') + 4)}/${isIsekai ? '' : 'isekai/'}`;
- }
- async function asyncSetAbilityData() { try {
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await asyncSetAbilityData();
- }
- logSwitchAsyncTask(arguments);
- const html = await $ajax.fetch('?s=Character&ss=ab');
- const doc = $doc(html);
- let ability = {};
- await Promise.all(Array.from(gE('#ability_treelist>div>img', 'all', doc)).map(async img => { try {
- const _ = img.getAttribute('onclick')?.match(/(\?s=(.*)tree=(.*))'/);
- const [href, type] = _ ? [_[1], _[3]] : ['?s=Character&ss=ab&tree=general', 'general'];
- switch(type){
- case 'deprecating1':
- case 'deprecating2':
- case 'elemental':
- case 'forbidden':
- case 'divine':
- break;
- default:
- return;
+ async function updateEncounter(engage) { try {
+ if (!g('option').encounter && !g('option').encounterDisplay) {
+ console.log("skip encounter check");
+ return false;
}
- const html = await $ajax.fetch(href);
- const doc = $doc(html);
- const slots = Array.from(gE('.ability_slotbox>div>div', 'all', doc)).forEach(slot => {
- const id = slot.id.match(/_(\d*)/)[1];
- const parent = slot.parentNode.parentNode.parentNode;
- ability[id] = {
- name: gE('.fc2', parent).innerText,
- type: type,
- level: Array.from(gE('.aw1,.aw2,.aw3,.aw4,.aw5,.aw6,.aw7,.aw8,.aw9,.aw10', parent).children).map(div => div.style.cssText.indexOf('f.png') === -1 ? 0 : 1).reduce((x, y) => x + y),
+ // await waitPause();
+ // $async.logSwitch(arguments);
+ const encounter = getEncounter();
+ const count = encounter.filter(e => e.url).length;
+ const option = g('option');
+ const now = time(0);
+ const last = encounter[0]?.time ?? getValue('lastEH', true) ?? 0; // 上次遭遇 或 上次打开EH 或 0
+ let cd;
+ if (encounter.filter(e => e.url && (e.encountered || (time(0) - e.time >= 30 * _1m))).length >= 24) {
+ cd = Math.floor(encounter[0].time / _1d + 1) * _1d - now;
+ } else if (!last) {
+ cd = 0;
+ } else {
+ cd = _1h / 2 + last - now;
+ }
+ cd = Math.max(0, cd);
+ const ui = gE('.encounterUI') ?? (() => {
+ const ui = (gE('.hvAAPauseUI') ?? gE('body')).appendChild(cE('a'));
+ ui.className = 'encounterUI';
+ ui.title = `${time(3, last)}\nEncounter Time: ${count}`;
+ if (!gE('#battle_main')) {
+ ui.href = 'https://e-hentai.org/news.php?encounter';
}
- });
- } catch (e) {console.error(e)}}));
- setValue('ability', ability);
- logSwitchAsyncTask(arguments);
- } catch (e) {console.error(e)}}
-
- async function asyncSetEnergyDrinkHathperk() { try {
- if (isIsekai || !g('option').restoreStamina) {
- return;
- }
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await asyncSetEnergyDrinkHathperk();
- }
- logSwitchAsyncTask(arguments);
- const html = await $ajax.fetch('https://e-hentai.org/hathperks.php');
- if(!html) {
- return;
- }
- const doc = $doc(html);
- const perks = gE('.stuffbox>table>tbody>tr', 'all', doc);
- if (!perks) {
- return;
- }
- setValue('staminaHathperk', perks[25].innerHTML.includes('Obtained'));
- logSwitchAsyncTask(arguments);
- } catch (e) {console.error(e)}}
-
- async function asyncSetStamina() { try {
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await asyncSetStamina();
- }
- logSwitchAsyncTask(arguments);
- const html = await $ajax.fetch(window.location.href);
- setValue('staminaTime', Math.floor(time(0) / 1000 / 60 / 60));
- setValue('stamina', gE('#stamina_readout .fc4.far>div', $doc(html)).textContent.match(/\d+/)[0] * 1);
- logSwitchAsyncTask(arguments);
- } catch (e) {console.error(e)}}
-
- async function asyncGetItems() { try {
- if (!g('option').checkSupply && (isIsekai || !g('option').restoreStamina)) {
- return;
- }
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await asyncGetItems();
- }
- logSwitchAsyncTask(arguments);
- const html = await $ajax.fetch('?s=Character&ss=it');
- const items = {};
- for (let each of gE('.nosel.itemlist>tbody', $doc(html)).children) {
- const name = each.children[0].children[0].innerText;
- const id = each.children[0].children[0].getAttribute('id').split('_')[1];
- const count = each.children[1].innerText;
- items[id] = [name, count];
- }
- g('items', items);
- logSwitchAsyncTask(arguments);
- } catch (e) {console.error(e)}}
-
- async function asyncCheckSupply() { try {
- if (!g('option').checkSupply) {
- return true;
- }
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await asyncCheckSupply();
- }
- logSwitchAsyncTask(arguments);
- const items = g('items');
- const thresholdList = g('option').checkItem;
- const checkList = g('option').isCheck;
- const needs = [];
- for (let id in checkList) {
- const item = items[id];
- if (!item) {
- continue;
- }
- const [name, count] = item;
- const threshold = thresholdList[id] ?? 0;
- if ((count ?? 0) >= threshold) {
- continue;
- }
- needs.push(`\n${name}(${count}<${threshold})`);
- }
- if (needs.length) {
- console.log(`Needs supply:${needs}`);
- document.title = `[C!]` + document.title;
- }
- logSwitchAsyncTask(arguments);
- return !needs.length;
- } catch (e) {console.error(e)} return false; }
-
- async function asyncCheckRepair() { try {
- if (!g('option').repair) {
- return true;
- }
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await asyncCheckRepair();
- }
- logSwitchAsyncTask(arguments);
- const doc = $doc(await $ajax.fetch('?s=Forge&ss=re'));
- const json = JSON.parse((await $ajax.fetch(gE('#mainpane>script[src]', doc).src)).match(/{.*}/)[0]);
- const eqps = (await Promise.all(Array.from(gE('.eqp>[id]', 'all', doc)).map(async eqp => { try {
- const id = eqp.id.match(/\d+/)[0];
- const condition = 1 * json[id].d.match(/Condition: \d+ \/ \d+ \((\d+)%\)/)[1];
- if (condition > g('option').repairValue) {
+ return ui;
+ })();
+ const waitCD = option.encounterWaitCD;
+ const missed = count - encounter.filter(e => e.encountered && e.url).length;
+ if (count === 24) {
+ ui.style.cssText += 'color:orange!important;';
+ } else if (cd <= waitCD) {
+ ui.style.cssText += 'color:red!important;';
+ } else {
+ ui.style.cssText += 'color:unset!important;';
+ }
+ ui.innerHTML = `${formatTime(cd).slice(0, 2).map(cdi => cdi.toString().padStart(2, '0')).join(`:`)}[${encounter.length ? (count >= 24 ? `☯` : count) : `✪`}${missed ? `-${missed}` : ``}]`;
+ if (engage) {
+ if (!cd) {
+ await waitPause();
+ onEncounter();
+ // $async.logSwitch(arguments);
+ return true;
+ }
+ if (cd < 30 * _1m && encounter[0]?.url && !encounter[0].encountered) {
+ await waitPause();
+ $ajax.openNoFetch(encounter[0].url);
+ // $async.logSwitch(arguments);
+ return true;
+ }
+ }
+ let interval = cd > _1h ? _1m : (!option.encounterQuickCheck || cd > _1m) ? _1s : 80;
+ interval = (option.encounterQuickCheck && cd > _1m) ? (interval - cd % interval) / 4 : interval; // 让倒计时显示更平滑
+ setTimeout(() => updateEncounter(engage), interval);
+ // $async.logSwitch(arguments);
+ return engage && cd <= waitCD;
+ } catch (err) { console.error(err) } }
+
+ async function onEncounter() { try {
+ const option = g('option');
+ while (
+ !(await $ajax.insert(location))
+ || (option.checkURLBeforeNewRound && !(await $ajax.insert(option.checkURLBeforeNewRound)))
+ ) { // perhaps network connect not available
+ await pauseAsync(option.checkURLBeforeNewRoundRetry);
+ }
+ $async.logSwitchStrict('updateEncounter', true);
+ if (getValue('disabled') || getValue('battle') || !await checkBattleReady(onEncounter, { staminaLow: option.staminaEncounter })) {
+ $async.logSwitchStrict('updateEncounter', false);
return;
}
- return gE('.messagebox_error', $doc(await $ajax.fetch(`?s=Forge&ss=re`, `select_item=${id}`)))?.innerText ? undefined : id;
- } catch (e) {console.error(e)}}))).filter(e => e);
- if (eqps.length) {
- console.log('eqps need repair: ', eqps);
- document.title = `[R!]` + document.title;
- }
- logSwitchAsyncTask(arguments);
- return !eqps.length;
- } catch (e) {console.error(e)}; return false; }
-
- function checkStamina(low, cost) {
- let stamina = getValue('stamina');
- const lastTime = getValue('staminaTime');
- let timeNow = Math.floor(time(0) / _1h);
- stamina += lastTime ? timeNow - lastTime : 0;
- const stmNR = stamina + 24 - (timeNow % 24);
- cost ??= 0;
- const stmNRChecked = !cost || stmNR - cost >= g('option').staminaLowWithReNat;
- console.log('stamina:', stamina,'\nstamina with nature recover:', stmNR, '\nnext arena stamina cost: ', cost.toString());
- if (stamina - cost >= (low ?? g('option').staminaLow) && stmNRChecked) {
- return 1;
- }
- let checked = 0;
- if (!stmNRChecked) {
- checked = -1;
- }
- if (isIsekai || !g('option').restoreStamina) {
- return checked;
- }
- const items = g('items');
- if (!items) {
- return checked;
- }
- const recover = items[11402] ? 5 : items[11401] ? getValue('staminaHathperk') ? 20 : 10 : 0;
- if (recover && stamina <= (100 - recover)) {
- $ajax.open(window.location.href, 'recover=stamina');
- return checked;
- }
- }
-
- async function updateEncounter(engage, isInBattle) { try {
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await updateEncounter(engage, isInBattle);
- }
- const encounter = getEncounter();
- const encountered = encounter.filter(e => e.encountered && e.href);
- const count = encounter.filter(e => e.href).length;
-
- const now = time(0);
- const last = encounter[0]?.time ?? getValue('lastEH', true) ?? 0; // 上次遭遇 或 上次打开EH 或 0
- let cd;
- if (encountered.length >= 24) {
- cd = Math.floor(encounter[0].time / _1d + 1) * _1d - now;
- } else if (!last) {
- cd = 0;
- } else {
- cd = _1h / 2 + last - now;
- }
- cd = Math.max(0, cd);
- const ui = gE('.encounterUI') ?? (() => {
- const ui = gE('body').appendChild(cE('a'));
- ui.className = 'encounterUI';
- ui.title = `${time(3, last)}\nEncounter Time: ${count}`;
- if (!isInBattle) {
- ui.href = 'https://e-hentai.org/news.php?encounter';
- }
- return ui;
- })();
-
- const missed = count - encountered.length;
- if (count === 24) {
- ui.style.cssText += 'color:orange!important;';
- } else if (!cd) {
- ui.style.cssText += 'color:red!important;';
- } else {
- ui.style.cssText += 'color:unset!important;';
- }
- ui.innerHTML = `${formatTime(cd).slice(0, 2).map(cdi => cdi.toString().padStart(2, '0')).join(`:`)}[${encounter.length ? (count >= 24 ? `☯` : count) : `✪`}${missed ? `-${missed}` : ``}]`;
- if (engage && !cd) {
- onEncounter();
- return true;
- }
- let interval = cd > _1h ? _1m : (!g('option').encounterQuickCheck || cd > _1m) ? _1s : 80;
- interval = (g('option').encounterQuickCheck && cd > _1m) ? (interval - cd % interval) / 4 : interval; // 让倒计时显示更平滑
- setTimeout(() => updateEncounter(engage), interval);
- } catch (e) {console.error(e)}}
-
- function onEncounter() {
- if (getValue('disabled') || getValue('battle') || !checkBattleReady(onEncounter, { staminaLow: g('option').staminaEncounter })) {
- return;
- }
- setEncounter(getEncounter()); // 离开页面前保存
- if(!window.top.location.href.endsWith(`?s=Battle`)){
- setValue('lastHref', window.top.location.href);
- }
- $ajax.openNoFetch('https://e-hentai.org/news.php?encounter');
- }
-
- async function startUpdateArena(idleStart, startIdleArena=true) { try {
- const now = time(0);
- if (!idleStart) {
- await updateArena();
- }
- let timeout = g('option').idleArenaTime * _1s;
- if (idleStart) {
- timeout -= time(0) - idleStart;
- }
- if(startIdleArena){
- setTimeout(idleArena, timeout);
- }
- const last = getValue('arena', true)?.date ?? now;
- setTimeout(startUpdateArena, Math.max(0, Math.floor(last / _1d + 1) * _1d - now));
- } catch (e) {console.error(e)}}
-
- async function updateArena(forceUpdateToken = false) { try {
- if(getValue('disabled')){
- await pauseAsync(_1s);
- return await updateArena(forceUpdateToken);
- }
- let arena = getValue('arena', true) ?? {};
- const isToday = arena.date && time(2, arena.date) === time(2);
- if (forceUpdateToken || !isToday || !arena.isOptionUpdated) {
- arena.token = {};
- arena.sites ??= [
- '?s=Battle&ss=gr',
- '?s=Battle&ss=ar',
- '?s=Battle&ss=ar&page=2',
- '?s=Battle&ss=rb'
- ]
- await Promise.all(arena.sites.map(async site => { try {
- const doc = $doc(await $ajax.fetch(site));
- if (site === '?s=Battle&ss=gr') {
- arena.token.gr = gE('img[src*="startgrindfest.png"]', doc).getAttribute('onclick').match(/init_battle\(1, '(.*?)'\)/)[1];
- return;
+ setEncounter(getEncounter()); // 离开页面前保存
+ if (!window.top.location.href.endsWith(`?s=Battle`)) {
+ setValue('lastUrl', window.top.location.href);
+ }
+ if (option.enchantEncounter) await asyncCheckEnchant();
+ $ajax.openNoFetch('https://e-hentai.org/news.php?encounter');
+ $async.logSwitchStrict('updateEncounter', false);
+ } catch (err) { console.error(err) } }
+
+ async function startUpdateArena(idleStart, startIdleArena = true) { try {
+ $async.logSwitchStrict('startUpdateArena', true);
+ const now = time(0);
+ if (!idleStart) {
+ await updateArena();
+ }
+ let timeout = g('option').idleArenaTime * _1s;
+ if (idleStart) {
+ timeout -= time(0) - idleStart;
+ }
+ if (startIdleArena) {
+ setTimeout(idleArena, timeout);
+ }
+ const last = getValue('arena', true)?.date ?? now;
+ setTimeout(startUpdateArena, Math.max(0, Math.floor(last / _1d + 1) * _1d - now));
+ $async.logSwitchStrict('startUpdateArena', false);
+ } catch (err) { console.error(err) } }
+
+ async function updateArena(forceUpdateToken = false) { try {
+ await waitPause();
+ $async.logSwitch(arguments);
+ let arena = getValue('arena', true) ?? {};
+ const isToday = arena.date && time(2, arena.date) === time(2);
+ if (forceUpdateToken || !isToday || !arena.isOptionUpdated) {
+ arena.token = {};
+ await Promise.all(['gr', 'ar', 'rb'].map(s => (async site => {
+ try {
+ const doc = $doc(await $ajax.insert(`?s=Battle&ss=${site}`));
+ getStartBattleButtons(doc).forEach(btn => { arena.token[btn.id] = btn.token ?? null; });
+ } catch (err) { console.error(err) }
+ })(s)));
+ }
+ if (!isToday) {
+ arena.date = time(0);
+ arena.gr = g('option').idleArenaGrTime;
+ arena.arrayDone = [];
+ }
+ if (!isToday || !arena.isOptionUpdated) {
+ arena.array = g('option').idleArenaValue?.split(',') ?? [];
+ arena.array.reverse();
+ }
+ arena.arrayDone = arena.arrayDone.filter(id => id === 'gr' || !Object.keys(arena.token).includes(id.toString()));
+ $async.logSwitch(arguments);
+ return setValue('arena', arena);
+ } catch (err) { console.error(err) } }
+
+ async function idleArena() { try { // 闲置竞技场
+ let id;
+ let arena = getValue('arena', true);
+ const option = g('option');
+ const writeArenaStart = function () {
+ console.log('Arena Start', id);
+ document.title = _alert(-1, '闲置竞技场开始', '閒置競技場開始', 'Idle Arena start');
+ if (id !== 'gr') {
+ arena.arrayDone.push(id);
+ } else {
+ arena.gr--;
}
- gE('img[src*="startchallenge.png"]', 'all', doc).forEach((_) => {
- const temp = _.getAttribute('onclick').match(/init_battle\((\d+),\d+,'(.*?)'\)/);
- arena.token[temp[1]] = temp[2];
- });
- } catch (e) {console.error(e)}}));
- }
- if(!isToday){
- arena.date = time(0);
- arena.gr = g('option').idleArenaGrTime;
- arena.arrayDone = [];
- }
- if (!isToday || !arena.isOptionUpdated) {
- arena.array = g('option').idleArenaValue.split(',') ?? [];
- arena.array.reverse();
- }
- return setValue('arena', arena);
- } catch (e) {console.error(e)}}
-
- function checkBattleReady(method, condition = {}) {
- if (getValue('disabled')) {
- setTimeout(method, _1s);
- return;
- }
- if (condition.checkEncounter && getEncounter()[0]?.href && !getEncounter()[0]?.encountered) {
- Debug.log(getEncounter());
- return;
- }
- const staminaChecked = checkStamina(condition.staminaLow, condition.staminaCost);
- console.log("staminaChecked", condition.staminaLow, condition.staminaCost, staminaChecked);
- if(staminaChecked === 1){ // succeed
- return true;
- }
- if(staminaChecked === 0){ // failed until today ends
- setTimeout(method, Math.floor(time(0) / _1h + 1) * _1h - time(0));
- document.title = `[S!!]` + document.title;
- } else { // case -1: // failed with nature recover
- document.title = `[S!]` + document.title;
- }
- }
-
- async function idleArena() { try { // 闲置竞技场
- let arena = getValue('arena', true);
- console.log('arena:', getValue('arena', true));
- if (arena.array.length === 0) {
- setTimeout(autoSwitchIsekai, (g('option').isekaiTime * (Math.random() * 20 + 90) / 100) * _1s);
- return;
- }
- logSwitchAsyncTask(arguments);
- const array = [...arena.array];
- let id;
- const RBundone = [];
- while (array.length > 0) {
- id = array.pop() * 1;
- id = isNaN(id) ? 'gr' : id;
- if(arena.arrayDone?.includes(id)){
- id = undefined;
- continue;
+ setValue('arena', arena);
}
- if (id in arena.token) {
- break;
+ if (arena.array.length === 0) {
+ setTimeout(autoSwitchIsekai, (option.isekaiTime * (Math.random() * 20 + 90) / 100) * _1s);
+ return;
}
- if (id >= 105) {
- arena.token = (await updateArena(true)).token;
+ $async.logSwitch(arguments);
+ const array = [...arena.array];
+ const RBundone = [];
+ while (array.length > 0) {
+ id = array.pop() * 1;
+ id = isNaN(id) ? 'gr' : id;
+ if (arena.arrayDone?.includes(id)) {
+ id = undefined;
+ continue;
+ }
if (id in arena.token) {
break;
}
+ if (id >= 105) {
+ arena.token = (await updateArena(true)).token;
+ if (id in arena.token) {
+ break;
+ }
+ }
+ id = undefined;
}
- id = undefined;
- }
- if (!id) {
- setValue('arena', arena);
- logSwitchAsyncTask(arguments);
- return;
- }
- let staminaCost = {
- 1: 2, 3: 4, 5: 6, 8: 8, 9: 10,
- 11: 12, 12: 15, 13: 20, 15: 25, 16: 30,
- 17: 35, 19: 40, 20: 45, 21: 50, 23: 55,
- 24: 60, 26: 65, 27: 70, 28: 75, 29: 80,
- 32: 85, 33: 90, 34: 95, 35: 100,
- 105: 1, 106: 1, 107: 1, 108: 1, 109: 1, 110: 1, 111: 1, 112: 1,
- gr: arena.gr
- }
- let stamina = getValue('stamina');
- const lastTime = getValue('staminaTime');
- let timeNow = Math.floor(time(0) / 1000 / 60 / 60);
- stamina += lastTime ? timeNow - lastTime : 0;
- for (let key in staminaCost) {
- staminaCost[key] *= (isIsekai ? 2 : 1) * (stamina >= 60 ? 0.03 : 0.02)
- }
- staminaCost.gr += 1
-
- let href, cost;
- let token = arena.token[id];
- const key = id;
- if (key === 'gr') {
- if (arena.gr <= 0) {
+ if (!id) {
+ console.log('No Arena Id Available', arena);
setValue('arena', arena);
- idleArena();
- arena.arrayDone.push('gr');
+ $async.logSwitch(arguments);
return;
}
- arena.gr--;
- href = 'gr';
- key = 1;
- cost = staminaCost.gr;
- } else if (key >= 105) {
- href = 'rb';
- } else if (key >= 19) {
- href = 'ar&page=2';
- } else {
- href = 'ar';
- }
- cost ??= staminaCost[key];
- if (!checkBattleReady(idleArena, { staminaCost: cost, checkEncounter: true })) {
- logSwitchAsyncTask(arguments);
- return;
- }
- document.title = _alert(-1, '闲置竞技场开始', '閒置競技場開始', 'Idle Arena start');
- if(key !== 'gr'){
- arena.arrayDone.push(key);
- }
- setValue('arena', arena);
- $ajax.open(`?s=Battle&ss=${href}`, `initid=${String(key)}&inittoken=${token}`);
- logSwitchAsyncTask(arguments);
- } catch (e) {console.error(e)}}
-
- // 战斗中//
- function onBattle() { // 主程序
- let battle = getValue('battle', true);
- if (!battle || !battle.roundAll) { // 修复因多个页面/世界同时读写造成缓存数据异常的情况
- battle = JSON.parse(JSON.stringify(g('battle')));
- battle.monsterStatus = battle.monsterStatus.map(ms => {
- return {
- order: ms.order,
- hp: ms.hp
+ let staminaCost = {
+ 1: 2, 3: 4, 5: 6, 8: 8, 9: 10,
+ 11: 12, 12: 15, 13: 20, 15: 25, 16: 30,
+ 17: 35, 19: 40, 20: 45, 21: 50, 23: 55,
+ 24: 60, 26: 65, 27: 70, 28: 75, 29: 80,
+ 32: 85, 33: 90, 34: 95, 35: 100,
+ 105: 1, 106: 1, 107: 1, 108: 1, 109: 1, 110: 1, 111: 1, 112: 1,
+ gr: 0
+ }
+ let stamina = getValue('stamina', true);
+ [stamina.current, stamina.punish] = await getCurrentStamina();
+ stamina.time = time(0);
+ for (let id in staminaCost) {
+ staminaCost[id] *= (isIsekai ? 2 : 1) * (stamina.current >= 60 ? 0.03 : 0.02)
+ }
+
+ let query;
+ if (id !== 'gr') {
+ query = id >= 105 ? 'rb' : 'ar';
+ } else {
+ if (arena.gr <= 0) {
+ setValue('arena', arena);
+ idleArena();
+ arena.arrayDone.push('gr');
+ return;
}
- })
- battle.monsterStatus.sort(objArrSort('order'));
- };
- Debug.log('onBattle', `\n`, JSON.stringify(battle, null, 4));
- //人物状态
- if (gE('#vbh')) {
- g('hp', gE('#vbh>div>img').offsetWidth / 500 * 100);
- g('mp', gE('#vbm>div>img').offsetWidth / 210 * 100);
- g('sp', gE('#vbs>div>img').offsetWidth / 210 * 100);
- g('oc', gE('#vcp>div>div') ? (gE('#vcp>div>div', 'all').length - gE('#vcp>div>div#vcr', 'all').length) * 25 : 0);
- } else {
- g('hp', gE('#dvbh>div>img').offsetWidth / 418 * 100);
- g('mp', gE('#dvbm>div>img').offsetWidth / 418 * 100);
- g('sp', gE('#dvbs>div>img').offsetWidth / 418 * 100);
- g('oc', gE('#dvrc').childNodes[0].textContent * 1);
- }
-
- // 战斗战况
- if (!gE('.hvAALog')) {
- const div = gE('#hvAABox2').appendChild(cE('div'));
- div.className = 'hvAALog';
- }
- const status = [
- '
物理物理Physical',
- '
火火Fire',
- '
冰冰Cold',
- '
雷雷Elec',
- '
风風Wind',
- '
圣聖Divine',
- '
暗暗Forbidden',
- ];
- function getBattleTypeDisplay(isTitle) {
- const battleInfoList = {
- 'gr': {
- name: ['压榨', '壓榨', 'Grindfest'],
- title: 'GF',
- },
- 'iw': {
- name: ['道具', '道具', 'Item World'],
- title: 'IW',
- },
- 'ar': {
- name: ['竞技', '競技', 'Arena'],
- title: 'AR',
- list: [
- ['第一滴血', '第一滴血', 'First Blood', 1, 2],
- ['经验曲线', '經驗曲綫', 'Learning Curves', 10, 4],
- ['毕业典礼', '畢業典禮', 'Graduation', 20, 6],
- ['荒凉之路', '荒涼之路', 'Road Less Traveled', 30, 8],
- ['浪迹天涯', '浪跡天涯', 'A Rolling Stone', 40, 10],
- ['鲜肉一族', '鮮肉一族', 'Fresh Meat', 50, 12],
- ['乌云密布', '烏雲密佈', 'Dark Skies', 60, 15],
- ['风暴成形', '風暴成形', 'Growing Storm', 70, 20],
- ['力量流失', '力量流失', 'Power Flux', 80, 25],
- ['杀戮地带', '殺戮地帶', 'Killzone', 90, 30],
- ['最终阶段', '最終階段', 'Endgame', 100, 35],
- ['无尽旅程', '無盡旅程', 'Longest Journey', 110, 40],
- ['梦陨之时', '夢隕之時', 'Dreamfall', 120, 45],
- ['流亡之途', '流亡之途', 'Exile', 130, 50],
- ['封印之力', '封印之力', 'Sealed Power', 140, 55],
- ['崭新之翼', '嶄新之翼', 'New Wings', 150, 60],
- ['弑神之路', '弑神之路', 'To Kill a God', 165, 65],
- ['死亡前夜', '死亡前夜', 'Eve of Death', 180, 70],
- ['命运三女神与树', '命運三女神與樹', 'The Trio and the Tree', 200, 75],
- ['世界末日', '世界末日', 'End of Days', 225, 80],
- ['永恒黑暗', '永恆黑暗', 'Eternal Darkness', 250, 85],
- ['与龙共舞', '與龍之舞', 'A Dance with Dragons', 300, 90],
- ['额外游戏内容', '額外游戲内容', 'Post Game Content', 400, 95],
- ['神秘小马领域', '神秘小馬領域', 'Secret Pony Level', 500, 100],
- ],
- condition: (bt) => bt[4] === battle.roundAll,
- content: (bt) => bt[3],
- },
- 'rb': {
- name: ['浴血', '浴血', 'Ring of Blood'],
- title: 'RB',
- list: [
- ['九死一树', '九死一樹', 'Triple Trio and the Tree', 250, 'Yggdrasil'],
- ['飞天意面怪', '飛行義大利麵怪物', 'Flying Spaghetti Monster', 200],
- ['隐形粉红独角兽', '隱形粉紅獨角獸', 'Invisible Pink Unicorn', 150],
- ['现实生活', '現實生活', 'Real Life', 100],
- ['长门有希', '長門有希', 'Yuki Nagato', 75],
- ['朝仓凉子', '朝倉涼子', 'Ryouko Asakura', 75],
- ['朝比奈实玖瑠', '朝比奈實玖瑠', 'Mikuru Asahina', 75],
- ['泉此方', '泉此方', 'Konata', 75],
- ],
- condition: (bt) => monsterNames.indexOf(bt[4] ?? bt[2]) !== -1,
- content: (bt) => bt[3],
- },
- 'ba': {
- name: ['遭遇', '遭遇', 'Random Encounter'],
- title: 'BA',
- content: (_) => getEncounter().filter(e => e.encountered).length,
- },
- 'tw': {
- name: ['塔楼', '塔樓', 'The Tower'],
- title: 'TW',
- list: [
- ['PFUDOR×20', 'PFUDOR×20', 'PFUDOR×20', 40],
- ['IWBTH×15', 'IWBTH×15', 'IWBTH×15', 34],
- ['任天堂×10', '任天堂×10', 'Nintendo×10', 27],
- ['地狱×7', '地獄×7', 'Hell×7', 20],
- ['噩梦×4', '噩夢×4', 'Nightmare×4', 14],
- ['困难×2', '困難×2', 'Hard×2', 7],
- ['普通×1', '普通×1', 'Normal×1', 1],
- ],
- condition: (bt) => bt[3] && bt[3] <= battle.tower,
- content: (_) => battle.tower,
- end: battle.tower > 40 ? `+${(battle.tower - 40) * 5}%DMG&HP` : '',
- }
- }
- const type = battle.roundType;
- let subtype, title;
- const monsterNames = Array.from(gE('div.btm3>div>div', 'all')).map(monster => monster.innerHTML);
- const lang = g('lang') * 1;
- const info = battleInfoList[type];
- switch (type) {
- case 'ar':
- case 'rb':
- case 'tw':
- case 'ba':
- for (let sub of (info.list ?? [[]])) {
- if (info.condition && !info.condition(sub)) {
- continue;
- }
- title = `${info.title}${info.content(sub)}`;
- if (!sub[lang]) {
+ query = 'gr';
+ }
+ query = `?s=Battle&ss=${query}`;
+ if (id === 'gr' && ((option.checkSupplyGF && !checkSupply(true)) || (option.repairValueGF && !await asyncCheckRepair(true)))) {
+ console.log('Check gr Battle Ready Failed in supply/repair', 'id:', id, arena);
+ $async.logSwitch(arguments);
+ return;
+ }
+ const cost = staminaCost[id];
+ if (!await checkBattleReady(idleArena, { staminaCost: cost, checkEncounter: option.encounter, staminaLow: id === 'gr' ? option.staminaGrindFest : undefined })) {
+ console.log('Check Battle Ready Failed', 'id:', id, arena);
+ $async.logSwitch(arguments);
+ return;
+ }
+ let token = arena.token[id];
+ if (hvVersion < 91) {
+ token = `&inittoken=${token}`;
+ } else {
+ token = `&postoken=${gE('#initform>input[name="postoken"]', $doc(await $ajax.insert(query))).value}`;
+ }
+ await waitPause();
+ writeArenaStart();
+ while(option.checkURLBeforeNewRound && !(await $ajax.insert(option.checkURLBeforeNewRound))) {
+ await pauseAsync(option.checkURLBeforeNewRoundRetry);
+ }
+ await asyncCheckEnchant(id === 'gr');
+ while(!(await $ajax.insert(query, `initid=${id === 'gr' ? 1 : id}${token}`))) {
+ await pauseAsync(option.checkURLBeforeNewRoundRetry);
+ }
+ stamina.lastCost = id === 'gr' ? undefined : cost;
+ setValue('stamina', stamina);
+ if (option.altBattleFirst && await $ajax.insert(location.replace('hentaiverse.org', 'alt.hentaiverse.org').replace('alt.alt', 'alt'))) {
+ console.log('Arena Fetch Done.', 'altBattleFirst:', option.altBattleFirst, 'Arena goto alt', arena);
+ gotoAlt(true);
+ } else {
+ console.log('Arena Fetch Done.', 'altBattleFirst:', option.altBattleFirst, 'Arena goto', arena);
+ goto();
+ }
+ $async.logSwitch(arguments);
+ } catch (err) { console.error(err) } }
+
+ // 战斗中//
+ function onBattleRound() { // 主程序
+ if (!gE('#battle_main')) return;
+ let battle = getValue('battle', true);
+ if (!battle || !battle.roundAll) { // 修复因多个页面/世界同时读写造成缓存数据异常的情况
+ battle = JSON.parse(JSON.stringify(g('battle')));
+ battle.monsterStatus = battle.monsterStatus.map(ms => {
+ return {
+ order: ms.order,
+ hp: ms.hp
+ }
+ })
+ battle.monsterStatus.sort(objArrSort('order'));
+ };
+ $debug.log('onBattle', `\n`, battle);
+ //人物状态
+ if (gE('#vbh')) {
+ g('hp', gE('#vbh>div>img').offsetWidth / 496 * 100);
+ g('mp', gE('#vbm>div>img').offsetWidth / 207 * 100);
+ g('sp', gE('#vbs>div>img').offsetWidth / 207 * 100);
+ g('oc', gE('#vcp>div>div') ? (gE('#vcp>div>div', 'all').length - gE('#vcp>div>div#vcr', 'all').length) * 25 : 0);
+ } else {
+ g('hp', gE('#dvbh>div>img').offsetWidth / 418 * 100);
+ g('mp', gE('#dvbm>div>img').offsetWidth / 418 * 100);
+ g('sp', gE('#dvbs>div>img').offsetWidth / 418 * 100);
+ g('oc', gE('#dvrc').childNodes[0].textContent * 1);
+ }
+
+ // 战斗战况
+ if (!gE('.hvAALog')) {
+ const div = gE('#hvAABox2').appendChild(cE('div'));
+ div.className = 'hvAALog';
+ }
+
+ function getBattleTypeDisplay(isTitle) {
+ const battleInfoList = {
+ 'gr': {
+ name: ['压榨', '壓榨', 'Grindfest'],
+ title: 'GF',
+ },
+ 'iw': {
+ name: ['道具', '道具', 'Item World'],
+ title: 'IW',
+ },
+ 'ar': {
+ name: ['竞技', '競技', 'Arena'],
+ title: 'AR',
+ list: [
+ ['第一滴血', '第一滴血', 'First Blood', 1, 2],
+ ['经验曲线', '經驗曲綫', 'Learning Curves', 10, 4],
+ ['毕业典礼', '畢業典禮', 'Graduation', 20, 6],
+ ['荒凉之路', '荒涼之路', 'Road Less Traveled', 30, 8],
+ ['浪迹天涯', '浪跡天涯', 'A Rolling Stone', 40, 10],
+ ['鲜肉一族', '鮮肉一族', 'Fresh Meat', 50, 12],
+ ['乌云密布', '烏雲密佈', 'Dark Skies', 60, 15],
+ ['风暴成形', '風暴成形', 'Growing Storm', 70, 20],
+ ['力量流失', '力量流失', 'Power Flux', 80, 25],
+ ['杀戮地带', '殺戮地帶', 'Killzone', 90, 30],
+ ['最终阶段', '最終階段', 'Endgame', 100, 35],
+ ['无尽旅程', '無盡旅程', 'Longest Journey', 110, 40],
+ ['梦陨之时', '夢隕之時', 'Dreamfall', 120, 45],
+ ['流亡之途', '流亡之途', 'Exile', 130, 50],
+ ['封印之力', '封印之力', 'Sealed Power', 140, 55],
+ ['崭新之翼', '嶄新之翼', 'New Wings', 150, 60],
+ ['弑神之路', '弑神之路', 'To Kill a God', 165, 65],
+ ['死亡前夜', '死亡前夜', 'Eve of Death', 180, 70],
+ ['命运三女神与树', '命運三女神與樹', 'The Trio and the Tree', 200, 75],
+ ['世界末日', '世界末日', 'End of Days', 225, 80],
+ ['永恒黑暗', '永恆黑暗', 'Eternal Darkness', 250, 85],
+ ['与龙共舞', '與龍之舞', 'A Dance with Dragons', 300, 90],
+ ['额外游戏内容', '額外游戲内容', 'Post Game Content', 400, 95],
+ ['神秘小马领域', '神秘小馬領域', 'Secret Pony Level', 500, 100],
+ ],
+ condition: (bt) => bt[4] === battle.roundAll,
+ content: (bt) => bt[3],
+ },
+ 'rb': {
+ name: ['浴血', '浴血', 'Ring of Blood'],
+ title: 'RB',
+ list: [
+ ['九死一树', '九死一樹', 'Triple Trio and the Tree', 250, 'Yggdrasil'],
+ ['飞天意面怪', '飛行義大利麵怪物', 'Flying Spaghetti Monster', 200],
+ ['隐形粉红独角兽', '隱形粉紅獨角獸', 'Invisible Pink Unicorn', 150],
+ ['现实生活', '現實生活', 'Real Life', 100],
+ ['长门有希', '長門有希', 'Yuki Nagato', 75],
+ ['朝仓凉子', '朝倉涼子', 'Ryouko Asakura', 75],
+ ['朝比奈实玖瑠', '朝比奈實玖瑠', 'Mikuru Asahina', 75],
+ ['泉此方', '泉此方', 'Konata', 75],
+ ],
+ condition: (bt) => monsterNames.indexOf(bt[4] ?? bt[2]) !== -1,
+ content: (bt) => bt[3],
+ },
+ 'ba': {
+ name: ['遭遇', '遭遇', 'Random Encounter'],
+ title: 'BA',
+ content: (_) => getEncounter().filter(e => e.encountered).length,
+ },
+ 'tw': {
+ name: ['塔楼', '塔樓', 'The Tower'],
+ title: 'TW',
+ list: [
+ ['PFUDOR×20', 'PFUDOR×20', 'PFUDOR×20', 40],
+ ['IWBTH×15', 'IWBTH×15', 'IWBTH×15', 34],
+ ['任天堂×10', '任天堂×10', 'Nintendo×10', 27],
+ ['地狱×7', '地獄×7', 'Hell×7', 20],
+ ['噩梦×4', '噩夢×4', 'Nightmare×4', 14],
+ ['困难×2', '困難×2', 'Hard×2', 7],
+ ['普通×1', '普通×1', 'Normal×1', 1],
+ ],
+ condition: (bt) => bt[3] && bt[3] <= battle.tower,
+ content: (_) => battle.tower,
+ end: battle.tower > 40 ? `+${(battle.tower - 40) * 5}%DMG&HP` : '',
+ }
+ }
+ const type = battle.roundType;
+ let subtype, title;
+ const monsterNames = Array.from(gE(`${monsterStateKeys.name}>div>div`, 'all')).map(monster => monster.innerHTML);
+ const lang = g('lang') * 1;
+ const info = battleInfoList[type];
+ switch (type) {
+ case 'ar':
+ case 'rb':
+ case 'tw':
+ case 'ba':
+ for (let sub of (info.list ?? [[]])) {
+ if (info.condition && !info.condition(sub)) {
+ continue;
+ }
+ title = `${info.title}${info.content(sub)}`;
+ if (!sub[lang]) {
+ break;
+ }
+ subtype = `${sub[lang] ? `
${sub[lang]}` : ``}${info.end ? `
${info.end}` : ``}`;
break;
}
- subtype = `${sub[lang] ? `
${sub[lang]}` : ``}${info.end ? `
${info.end}` : ``}`;
break;
- }
- break;
- case 'iw':
- case 'gr':
- title = `${info.title}`;
- break;
- default:
- break;
+ case 'iw':
+ case 'gr':
+ title = `${info.title}`;
+ break;
+ default:
+ break;
+ }
+ return isTitle ? title : `${(info?.name ?? ['未知', '未知', 'Unknown'])[lang]}:[${title}]${subtype ?? ''}`;
}
- return isTitle ? title : `${(info?.name ?? ['未知', '未知', 'Unknown'])[lang]}:[${title}]${subtype ?? ''}`;
- }
- const currentTurn = (battle.turn ?? 0) + 1;
-
- gE('.hvAALog').innerHTML = [
- `
攻击模式攻擊模式Attack Mode: ${status[g('attackStatus')]}`,
- `${isIsekai ? '
异世界異世界Isekai' : '
恒定世界恆定世界Persistent'}`, // 战役模式显示
- `${getBattleTypeDisplay()}`, // 战役模式显示
- `R${battle.roundNow}/${battle.roundAll}:T${currentTurn}`,
- `TPS: ${g('runSpeed')}`,
- `
敌人敌人Monsters: ${g('monsterAlive')}/${g('monsterAll')}`,
- ].join(`
`);
- if (!battle.roundAll) {
- pauseChange();
- Debug.shiftLog();
- }
- document.title = `${getBattleTypeDisplay(true)}:R${battle.roundNow}/${battle.roundAll}:T${currentTurn}@${g('runSpeed')}tps,${g('monsterAlive')}/${g('monsterAll')}`;
- setValue('battle', battle);
- if (!battle.monsterStatus || battle.monsterStatus.length !== g('monsterAll')) {
- fixMonsterStatus();
- }
- countMonsterHP();
- displayMonsterWeight();
- displayPlayStatePercentage();
-
- if (getValue('disabled')) { // 如果禁用
- document.title = _alert(-1, 'hvAutoAttack暂停中', 'hvAutoAttack暫停中', 'hvAutoAttack Paused');
- gE('#hvAABox2>button').innerHTML = '
继续繼續Continue';
- return;
- }
- battle = getValue('battle', true);
- g('battle').turn = currentTurn;
- battle.turn = currentTurn;
- setValue('battle', battle);
+ const currentTurn = (battle.turn ?? 0) + 1;
- killBug(); // 解决 HentaiVerse 可能出现的 bug
+ gE('.hvAALog').innerHTML = [
+ `
攻击模式攻擊模式Attack Mode: ${attackStatusType[g('attackStatus')]}`,
+ `${isIsekai ? '
异世界異世界Isekai' : '
恒定世界恆定世界Persistent'}`, // 战役模式显示
+ `${getBattleTypeDisplay()}`, // 战役模式显示
+ `R${battle.roundNow}/${battle.roundAll}:T${currentTurn}`,
+ `TPS: ${g('runSpeed')}`,
+ `
敌人敌人Monsters: ${g('monsterAlive')}/${g('monsterAll')}`,
+ ].join(`
`);
+ if (!battle.roundAll) {
+ pauseChange();
+ $debug.shiftLog();
+ }
+ document.title = `${getBattleTypeDisplay(true)}:R${battle.roundNow}/${battle.roundAll}:T${currentTurn}@${g('runSpeed')}tps,${g('monsterAlive')}/${g('monsterAll')}`;
+ setValue('battle', battle);
+ if (!battle.monsterStatus || battle.monsterStatus.length !== g('monsterAll')) {
+ fixMonsterStatus();
+ }
+ countMonsterHP();
+ displayMonsterWeight();
+ displayPlayStatePercentage();
- if (g('option').autoFlee && checkCondition(g('option').fleeCondition)) {
- gE('1001').click();
- SetExitBattleTimeout('Flee');
- return;
- }
- var taskList = {
- 'Pause': autoPause,
- 'Rec': autoRecover,
- 'Def': autoDefend,
- 'Scroll': useScroll,
- 'Channel': useChannelSkill,
- 'Buff': useBuffSkill,
- 'Infus': useInfusions,
- 'Debuff': useDeSkill,
- 'Focus': autoFocus,
- 'SS': autoSS,
- 'Skill': autoSkill,
- 'Atk': attack,
- };
- const names = g('option').battleOrderName?.split(',') ?? [];
- for (let i = 0; i < names.length; i++) {
- if(taskList[names[i]]()){
+ if (getValue('disabled')) { // 如果禁用
+ document.title = _alert(-1, 'hvAutoAttack暂停中', 'hvAutoAttack暫停中', 'hvAutoAttack Paused');
+ gE('#hvAABox2>button').innerHTML = `
继续繼續Continue${(g('option').pauseHotkey && g('option').pauseHotkeyStr) ? `(${g('option').pauseHotkeyStr})` : '' }`;
return;
}
- delete taskList[names[i]];
- }
- for (let name in taskList) {
- if (taskList[name]()) {
+ battle = getValue('battle', true);
+ g('battle').turn = currentTurn;
+ battle.turn = currentTurn;
+ setValue('battle', battle);
+ killBug(); // 解决 HentaiVerse 可能出现的 bug
+
+ if (g('option').autoFlee && checkCondition(g('option').fleeCondition)) {
+ gE('1001').click();
+ setExitBattleTimeout('Flee');
return;
}
- }
- }
-
- function getMonsterID(s) {
- if (s.order !== undefined) {
- return (s.order + 1) % 10;
- } // case is monsterStatus
- return (s + 1) % 10; // case is order
- }
-
- /**
- * 按照技能范围,获取包含原目标且范围内最终权重(finweight)之和最低的范围的中心目标
- * @param {int} id id from g('battle').monsterStatus.sort(objArrSort('finWeight'));
- * @param {int} range radius, 0 for single-target and all-targets, 1 for treble-targets, ..., n for (2n+1) targets
- * @param {(target) => bool} excludeCondition target with id
- * @returns
- */
- function getRangeCenterID(target, range = undefined, isWeaponAttack = false, excludeCondition = undefined) {
- if (!range) {
- return getMonsterID(target);
- }
- const centralExtraWeight = -1 * Math.log10(1 + (isWeaponAttack ? (g('option').centralExtraRatio / 100) ?? 0 : 0));
- let order = target.order;
- let newOrder = order;
- // sort by order to fix id
- let msTemp = JSON.parse(JSON.stringify(g('battle').monsterStatus));
- msTemp.sort(objArrSort('order'));
- let unreachableWeight = g('option').unreachableWeight;
- let minRank;
- for (let i = order - range; i <= order + range; i++) {
- if (i < 0 || i >= msTemp.length || msTemp[i].isDead) {
- continue; // 无法选中
+ const taskList = {
+ 'Cure': ()=>autoRecover(true),
+ 'Pause': autoPause,
+ 'SSDisable': ()=>autoSS(true),
+ 'Rec': ()=>autoRecover(false),
+ 'Scroll': useScroll,
+ 'Infus': useInfusions,
+ 'Def': autoDefend,
+ 'Channel': useChannelSkill,
+ 'Buff': useBuffSkill,
+ 'Debuff': useDeSkill,
+ 'Focus': autoFocus,
+ 'SS': ()=>autoSS(false),
+ 'Skill': autoSkill,
+ 'Atk': attack,
+ };
+ const option = g('option');
+ const names = option.battleOrderDefaultOnly ? [] : option.battleOrderName?.split(',') ?? [];
+ for (let i = 0; i < names.length; i++) {
+ if (taskList[names[i]]()) {
+ onStepInDone();
+ return;
+ }
+ delete taskList[names[i]];
}
- let rank = 0;
- for (let j = i - range; j <= (i + range); j++) {
- let cew = j === i ? centralExtraWeight : 0; // cew <= 0, 增加未命中权重,降低命中权重
- let mon = msTemp[j];
- if (j < 0 || j >= msTemp.length // 超出范围
- || mon.isDead // 死亡目标
- || (excludeCondition && excludeCondition(mon))) { // 特殊排除判定
- rank += unreachableWeight - cew;
- continue;
+ for (let name in taskList) {
+ if (taskList[name]()) {
+ onStepInDone();
+ return;
}
- rank += mon.finWeight + cew; // 中心目标会受到副手及冲击攻击时,相当于有效生命值降低
}
- if (rank < minRank) {
- newOrder = i;
+ }
+
+ function getBuffTurnFromImg(buff) {
+ if (!buff) {
+ return 0;
+ }
+ buff = buff.getAttribute('onmouseover').match(/\(.*,.*,(\s*)(.*?)\)$/)[2];
+ if (!buff) {
+ buff = 0;
+ } else if (buff ==='permanent') {
+ buff = Infinity;
+ } else {
+ buff *= 1;
}
+ return buff;
}
- return getMonsterID(newOrder);
- }
- function autoPause() {
- if (g('option').autoPause && checkCondition(g('option').pauseCondition)) {
- pauseChange();
- return true;
+ function getMonsterID(s) {
+ if (s.order !== undefined) {
+ return (s.order + 1) % 10;
+ } // case is monsterStatus
+ return (s + 1) % 10; // case is order
}
- return false;
- }
- function autoDefend() {
- if (g('option').defend && checkCondition(g('option').defendCondition)) {
- gE('#ckey_defend').click();
- return true;
+ function getMonster(id) {
+ return gE(`#mkey_${id % 10}`);
}
- return false;
- }
- function pauseChange() { // 暂停状态更改
- if (getValue('disabled')) {
- if (gE('.pauseChange')) {
- gE('.pauseChange').innerHTML = '暂停暫停Pause';
- }
- document.title = getValue('disabled');
- delValue(0);
- if (!gE('#navbar')) { // in battle
- onBattle();
+ function getBuff(buff, id) {
+ if (buff?.match(`^{.*}$`)) {
+ for (const b of buff.replace(/[\{\}\s]/g, '').split(',')) {
+ const found = getBuff(b, id);
+ if (found) return found;
+ }
+ return undefined;
+ }
+ if (id === undefined) {
+ return gE(`#pane_effects>img[src*="${buff}"]`);
+ }
+ return gE(`${monsterStateKeys.buffs}>img[src*="${buff}"]`, getMonster(id));
+ }
+
+ function isOn(id) { // 是否可以施放技能/使用物品
+ if (id * 1 > 10000) { // 使用物品
+ return gE(`.bti3>div[onmouseover*="(${id})"]`);
+ } // 施放技能
+ return gE(id) && (gE(id).style.opacity * 1 !== 0.5);
+ }
+
+ /**
+ * 按照技能范围,获取包含原目标且范围内最终权重(finweight)之和最低的范围的中心目标
+ * @param {int} id id from g('battle').monsterStatus.sort(objArrSort('finWeight'));
+ * @param {int} range radius, 0 for single-target and all-targets, 1 for treble-targets, ..., n for (2n+1) targets
+ * @param {(target) => number} excludeWeightRatio target with id
+ * @returns
+ */
+ function getRangeCenter(target, range, isWeaponAttack, excludeWeightRatio, forceUseIndex) {
+ let msTemp = JSON.parse(JSON.stringify(g('battle').monsterStatus));
+ msTemp.sort(objArrSort('order'));
+ let minWeight = Number.MAX_SAFE_INTEGER;
+ // 0. 范围大于等于全体时,直接释放全体
+ if (!range || range >= msTemp.length) {
+ return { id: getMonsterID(target), weight: minWeight };
+ }
+ const option = g('option');
+ const centralExtraWeight = -1 * Math.log10(1 + (isWeaponAttack ? option.centralExtraRatio / 100 : 0));
+ let order = target.order;
+ let newOrder = order;
+ // sort by order to fix id
+ let unreachableWeight = option.unreachableWeight;
+ // 1. 以选中目标为中心,优先向上
+ // 2. 超过顶部则向下找
+ // 3. 死亡、超过底下的将被溢出抛弃
+ const up = Math.floor(range / 2);
+ const down = range - up - 1;
+ const top = order < range ? 0 : Math.max(order - down, 0);
+ const bottom = Math.min(order + up, msTemp.length-1);
+ for (let i = top; i <= bottom; i++) {
+ let center = i;
+ if (msTemp[center].isDead) continue;
+ let weight = 0;
+ let overflow = Math.max(up-center,0);
+ const [min, max] = [center - up + overflow, center + down + overflow];
+ for (let inRange = min; inRange <= max; inRange++) {
+ let cew = inRange === center ? centralExtraWeight : 0; // cew <= 0, 增加未命中权重,降低命中权重
+ let mon = msTemp[inRange];
+ if (inRange < 0 || inRange >= msTemp.length || mon.isDead) { // 超出范围 或 死亡目标
+ weight += unreachableWeight;
+ continue;
+ }
+ if (excludeWeightRatio) {
+ // 特殊排除(ratio === 1则直接排除,00) {
+ continue;
+ }
+ }
+ weight += cew; // 中心目标会受到副手及冲击攻击时,相当于有效生命值降低
+ weight += forceUseIndex ? -1 : mon.finWeight; // 强制使用顺序而非权重时,全部使用统一的权重而非怪物状态
+ }
+ if (weight < minWeight) {
+ newOrder = center;
+ minWeight = weight;
+ break;
+ }
}
- } else {
- if (gE('.pauseChange')) {
- gE('.pauseChange').innerHTML = '继续繼續Continue';
+ return { id: getMonsterID(newOrder), weight: minWeight };
+ }
+
+ function autoPause() {
+ const option = g('option');
+ if (option.autoPause && checkCondition(option.pauseCondition)) {
+ pauseChange();
+ return true;
}
- setValue('disabled', document.title);
- document.title = _alert(-1, 'hvAutoAttack暂停中', 'hvAutoAttack暫停中', 'hvAutoAttack Paused');
+ return false;
}
- }
- function SetExitBattleTimeout(alarm){
- setAlarm(alarm);
- if(alarm === 'SkipDefeated') return;
- delValue(1);
- if(g('option').ExitBattleWaitTime) {
- setTimeout(() => {
- $ajax.open(getValue('lastHref'));
- }, g('option').ExitBattleWaitTime * _1s);
- return;
+ function autoDefend() {
+ const option = g('option');
+ if (option.defend && checkCondition(option.defendCondition)) {
+ gE('#ckey_defend').click();
+ return true;
+ }
+ return false;
}
- $ajax.open(getValue('lastHref'));
- }
- function reloader() {
- let obj; let a; let cost;
- const battleUnresponsive = {
- 'Alert': { Method: setAlarm },
- 'Reload': { Method: goto },
- 'Alt': { Method: gotoAlt }
- }
- function clearBattleUnresponsive(){
- Object.keys(battleUnresponsive).forEach(t=>clearTimeout(battleUnresponsive[t].Timeout));
- }
- const eventStart = cE('a');
- eventStart.id = 'eventStart';
- eventStart.onclick = function () {
- a = unsafeWindow.info;
- for(let t in g('option').battleUnresponsive) {
- if (g('option').battleUnresponsive[t]) {
- battleUnresponsive[t].Timeout = setTimeout(battleUnresponsive[t].Method, Math.max(1, g('option').battleUnresponsiveTime[t]) * _1s);
- }
- }
- if (g('option').recordUsage) {
- obj = {
- mode: a.mode,
- };
- if (a.mode === 'items') {
- obj.item = gE(`#pane_item div[id^="ikey"][onclick*="skill('${a.skill}')"]`).textContent;
- } else if (a.mode === 'magic') {
- obj.magic = gE(a.skill).textContent;
- cost = gE(a.skill).getAttribute('onmouseover').match(/\('.*', '.*', '.*', (\d+), (\d+), \d+\)/);
- obj.mp = cost[1] * 1;
- obj.oc = cost[2] * 1;
- }
- }
- };
- gE('body').appendChild(eventStart);
- const eventEnd = cE('a');
- eventEnd.id = 'eventEnd';
- eventEnd.onclick = function () {
- const timeNow = time(0);
- g('runSpeed', (1000 / (timeNow - g('timeNow'))).toFixed(2));
- g('timeNow', timeNow);
- const monsterDead = gE('img[src*="nbardead"]', 'all').length;
- g('monsterAlive', g('monsterAll') - monsterDead);
- const bossDead = gE('div.btm1[style*="opacity"] div.btm2[style*="background"]', 'all').length;
- g('bossAlive', g('bossAll') - bossDead);
- const battleLog = gE('#textlog>tbody>tr>td', 'all');
- if (g('option').recordUsage) {
- obj.log = battleLog;
- recordUsage(obj);
- }
- if (g('monsterAlive') && !gE('#btcp')) {
- clearBattleUnresponsive();
- onBattle();
+ function setExitBattleTimeout(alarm) {
+ setAlarm(alarm);
+ const option = g('option');
+ if (alarm === 'Defeat' && !option.autoSkipDefeated) {
return;
}
- if (g('option').dropMonitor) {
- dropMonitor(battleLog);
+ delValue(1);
+ setTimeoutOrExecute(() => $ajax.openNoFetch(getValue('lastUrl')), option.ExitBattleWaitTime * _1s);
+ }
+
+ function reloader() {
+ const battleUnresponsive = {
+ 'Alert': { method: setAlarm },
+ 'Reload': { method: goto },
+ 'Alt': { method: gotoAlt }
}
- if (g('option').recordUsage) {
- recordUsage2();
+ function clearBattleUnresponsive() {
+ Object.keys(battleUnresponsive).forEach(t => {
+ if (!battleUnresponsive[t].timeout) return;
+ clearTimeout(battleUnresponsive[t].timeout)
+ });
}
- if (g('battle').roundNow !== g('battle').roundAll) { // Next Round
- if(g('option').NewRoundWaitTime){
- setTimeout(onNewRound, g('option').NewRoundWaitTime * _1s);
- } else {
- onNewRound();
+ async function onBattleUnresponsive(method) { try {
+ await waitPause();
+ method();
+ } catch (err) { console.error(err) } }
+
+ let obj, a, cost;
+ const eventStart = cE('a');
+ eventStart.id = 'eventStart';
+ eventStart.onclick = function () {
+ const option = g('option');
+ a = unsafeWindow.info;
+ for (let t in option.battleUnresponsive) {
+ if (option.battleUnresponsive[t]) {
+ battleUnresponsive[t].timeout = setTimeout(() => onBattleUnresponsive(battleUnresponsive[t].method), Math.max(1, option.battleUnresponsiveTime[t]) * _1s);
+ }
}
- return;
-
- async function onNewRound(){
- try {
- const html = await $ajax.fetch(window.location.href);
+ if (option.recordUsage) {
+ obj = {
+ mode: a.mode,
+ };
+ if (a.mode === 'items') {
+ obj.item = gE(`#pane_item div[id^="ikey"][onclick*="skill('${a.skill}')"]`).textContent;
+ } else if (a.mode === 'magic') {
+ obj.magic = gE(a.skill).textContent;
+ cost = gE(a.skill).getAttribute('onmouseover').match(/\('.*', '.*', '.*', (\d+), (\d+), \d+\)/);
+ obj.mp = cost[1] * 1;
+ obj.oc = cost[2] * 1;
+ }
+ }
+ };
+ gE('body').appendChild(eventStart);
+
+ const eventEnd = cE('a');
+ eventEnd.id = 'eventEnd';
+ eventEnd.onclick = function () {
+ const option = g('option');
+ const timeNow = time(0);
+ g('runSpeed', (1000 / (timeNow - g('timeNow'))).toFixed(2));
+ g('timeNow', timeNow);
+ const monsterDead = gE('img[src*="nbardead"]', 'all').length;
+ g('monsterAlive', g('monsterAll') - monsterDead);
+ const bossDead = gE(`${monsterStateKeys.obj}[style*="opacity"] ${monsterStateKeys.lv}[style*="background"]`, 'all').length;
+ g('bossAlive', g('bossAll') - bossDead);
+ const battleLog = gE('#textlog>tbody>tr>td', 'all');
+ if (option.recordUsage) {
+ obj.log = battleLog;
+ recordUsage(obj);
+ }
+ if (g('monsterAlive') && !gE('#btcp')) {
+ clearBattleUnresponsive();
+ onBattleRound();
+ return;
+ }
+ if (option.dropMonitor) {
+ dropMonitor(battleLog);
+ }
+ if (option.recordUsage) {
+ recordUsage2();
+ }
+ onRoundEnd();
+ async function onRoundEnd() { try {
+ await waitPause();
+ // $async.logSwitch(arguments);
+ if (g('monsterAlive') > 0) { // Defeat
+ setExitBattleTimeout('Defeat');
+ clearBattleUnresponsive();
+ } else if (g('battle').roundNow === g('battle').roundAll) { // Victory
+ setExitBattleTimeout('Victory');
+ clearBattleUnresponsive();
+ } else { // Next Round
+ setTimeoutOrExecute(onNewRound, option.NewRoundWaitTime * _1s);
+ }
- gE('#pane_completion').removeChild(gE('#btcp'));
+ async function onNewRound() { try {
+ await waitPause();
+ // $async.logSwitch(arguments);
+ if (gE('#btcp')?.innerHTML.includes("finishbattle.png")) {
+ goto();
+ // $async.logSwitch(arguments);
+ return;
+ }
clearBattleUnresponsive();
+ let urlChecked;
+ if (option.checkURLBeforeNewRound) {
+ while (!urlChecked) {
+ try {
+ urlChecked = await $ajax.insert(option.checkURLBeforeNewRound);
+ } catch(e) {
+ await waitPause();
+ await pauseAsync(option.checkURLBeforeNewRoundRetry);
+ } finally {
+ if (!!urlChecked) {
+ // console.log('Done url check:', !!urlChecked, option.checkURLBeforeNewRound)
+ } else {
+ console.error('Failed connect ', option.checkURLBeforeNewRound);
+ }
+ }
+ }
+ }
+ const html = await $ajax.insert(window.location.href);
const doc = $doc(html)
if (gE('#riddlecounter', doc)) {
- if (g('option').riddlePopup && !window.opener) {
+ console.log('url check:', option.checkURLBeforeNewRound, '\n', urlChecked)
+ if (option.riddlePopup && !window.opener) {
window.open(window.location.href, 'riddleWindow', 'resizable,scrollbars,width=1241,height=707');
+ // $async.logSwitch(arguments);
return;
}
+ console.log(window.location.href)
goto();
+ // $async.logSwitch(arguments);
return;
}
- ['#battle_right', '#battle_left'].forEach(selector=>{ gE('#battle_main').replaceChild(gE(selector, doc), gE(selector)); })
- unsafeWindow.battle = new unsafeWindow.Battle();
- unsafeWindow.battle.clear_infopane();
- Debug.log('______________newRound', true);
+ if (option.nativeNewRound) {
+ onStepInDone();
+ gE('#btcp').click();
+ // $async.logSwitch(arguments);
+ return;
+ }
+ gE('#pane_completion').removeChild(gE('#btcp'));
+ ['#battle_right', '#battle_left'].forEach(selector => { gE('#battle_main').replaceChild(gE(selector, doc), gE(selector)); })
+ unsafeWindow.battle = undefined;
+ await loadUnsafeWindowBattle();
+ $debug.log('______________newRound', true);
newRound(true);
- onBattle();
- } catch(e) { e=>console.error(e) }
- }
- }
-
- if (g('monsterAlive') > 0) { // Defeat
- SetExitBattleTimeout(g('option').autoSkipDefeated ? 'SkipDefeated' : 'Defeat');
- }
- if (g('battle').roundNow === g('battle').roundAll) { // Victory
- SetExitBattleTimeout('Victory');
- }
- clearBattleUnresponsive();
- };
- gE('body').appendChild(eventEnd);
- window.sessionStorage.delay = g('option').delay;
- window.sessionStorage.delay2 = g('option').delay2;
- const fakeApiCall = cE('script');
- fakeApiCall.textContent = `api_call = ${function (b, a, d) {
- const delay = window.sessionStorage.delay * 1;
- const delay2 = window.sessionStorage.delay2 * 1;
- window.info = a;
- b.open('POST', `${MAIN_URL}json`);
- b.setRequestHeader('Content-Type', 'application/json');
- b.withCredentials = true;
- b.onreadystatechange = d;
- b.onload = function () {
- document.getElementById('eventEnd').click();
+ onStepInDone();
+ onBattleRound();
+ // $async.logSwitch(arguments);
+ } catch (err) { console.error(err) } }
+ // $async.logSwitch(arguments);
+ } catch (err) { console.error(err) } }
};
- document.getElementById('eventStart').click();
- if (a.mode === 'magic' && a.skill >= 200) {
- if (delay <= 0) {
+ gE('body').appendChild(eventEnd);
+
+ window.sessionStorage.delay = g('option').delay;
+ window.sessionStorage.delay2 = g('option').delay2;
+ const fakeApiCall = cE('script');
+ fakeApiCall.textContent = `api_call = ${function (b, a, d) {
+ const delay = window.sessionStorage.delay * 1;
+ const delay2 = window.sessionStorage.delay2 * 1;
+ window.info = a;
+ unsafeWindow = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
+ b.open('POST', `${unsafeWindow.MAIN_URL}json`);
+ b.setRequestHeader('Content-Type', 'application/json');
+ b.withCredentials = true;
+ b.onreadystatechange = d;
+ b.onload = function () {
+ document.getElementById('eventEnd').click();
+ };
+ document.getElementById('eventStart').click();
+ if (a.mode === 'magic' && a.skill >= 200) {
+ if (delay <= 0) {
+ b.send(JSON.stringify(a));
+ } else {
+ setTimeout(() => {
+ b.send(JSON.stringify(a));
+ }, delay * (Math.random() * 50 + 50) / 100);
+ }
+ } else if (delay2 <= 0) {
b.send(JSON.stringify(a));
} else {
setTimeout(() => {
b.send(JSON.stringify(a));
- }, delay * (Math.random() * 50 + 50) / 100);
+ }, delay2 * (Math.random() * 50 + 50) / 100);
}
- } else if (delay2 <= 0) {
- b.send(JSON.stringify(a));
- } else {
- setTimeout(() => {
- b.send(JSON.stringify(a));
- }, delay2 * (Math.random() * 50 + 50) / 100);
- }
- }.toString()}`;
- gE('head').appendChild(fakeApiCall);
- const fakeApiResponse = cE('script');
- fakeApiResponse.textContent = `api_response = ${function (b) {
- if (b.readyState === 4) {
- if (b.status === 200) {
- const a = JSON.parse(b.responseText);
- if (a.login !== undefined) {
- top.window.location.href = login_url;
- } else {
- if (a.error || a.reload) {
- window.location.href = window.location.search;
- }
- return a;
- }
- } else {
+ }.toString()}`;
+ gE('head').appendChild(fakeApiCall);
+ const fakeApiResponse = cE('script');
+ fakeApiResponse.textContent = `api_response = ${function (b) {
+ if (b.readyState !== 4) {
+ return false;
+ }
+ if (b.status !== 200) {
+ window.location.href = window.location.search;
+ return false;
+ }
+ const a = JSON.parse(b.responseText);
+ if (a.login !== undefined) {
+ top.window.location.href = login_url;
+ return false;
+ }
+ if (a.error || a.reload) {
window.location.href = window.location.search;
}
+ return a;
+ }.toString()}`;
+ gE('head').appendChild(fakeApiResponse);
+ }
+
+ async function loadUnsafeWindowBattle() { try {
+ while (!unsafeWindow.battle) {
+ await pauseAsync(300);
+ unsafeWindow.battle = new unsafeWindow.Battle();
}
- return false;
- }.toString()}`;
- gE('head').appendChild(fakeApiResponse);
- }
+ unsafeWindow.battle.clear_infopane();
+ } catch(e) { console.error(e) }}
- function newRound(isNew) { // New Round
- let battle = isNew ? {} : getValue('battle', true);
- if (!battle) {
- battle = JSON.parse(JSON.stringify(g('battle') ?? {}));
- battle.monsterStatus?.sort(objArrSort('order'));
- };
- setValue('battle', battle);
- if (window.location.hash !== '') {
- goto();
- }
- g('monsterAll', gE('div.btm1', 'all').length);
- const monsterDead = gE('img[src*="nbardead"]', 'all').length;
- g('monsterAlive', g('monsterAll') - monsterDead);
- g('bossAll', gE('div.btm2[style^="background"]', 'all').length);
- const bossDead = gE('div.btm1[style*="opacity"] div.btm2[style*="background"]', 'all').length;
- g('bossAlive', g('bossAll') - bossDead);
- const battleLog = gE('#textlog>tbody>tr>td', 'all');
- if (!battle.roundType) {
- const temp = battleLog[battleLog.length - 1].textContent;
+ function newRound(isNew) { // New Round
+ let battle = isNew ? {} : getValue('battle', true);
+ if (isNew) {
+ setValue('skillOTOS', {});
+ }
+ if (!battle) {
+ battle = JSON.parse(JSON.stringify(g('battle') ?? {}));
+ battle.monsterStatus?.sort(objArrSort('order'));
+ };
+ setValue('battle', battle);
+ if (window.location.hash !== '') {
+ goto();
+ }
+ g('monsterAll', gE(monsterStateKeys.obj, 'all').length);
+ const monsterDead = gE('img[src*="nbardead"]', 'all').length;
+ g('monsterAlive', g('monsterAll') - monsterDead);
+ g('bossAll', gE(`${monsterStateKeys.lv}[style^="background"]`, 'all').length);
+ const bossDead = gE(`${monsterStateKeys.obj}[style*="opacity"] ${monsterStateKeys.lv}[style*="background"]`, 'all').length;
+ g('bossAlive', g('bossAll') - bossDead);
const types = {
- 'ar': {
+ ar: {
reg: /^Initializing arena challenge/,
extra: (i) => i <= 35,
},
- 'rb': {
+ rb: {
reg: /^Initializing arena challenge/,
extra: (i) => i >= 105,
},
- 'iw': { reg: /^Initializing Item World/ },
- 'gr': { reg: /^Initializing Grindfest/ },
- 'tw': { reg: /^Initializing The Tower/ },
- 'ba': {
- reg: /^Initializing random encounter/,
- extra: (_) => {
- const encounter = getEncounter();
- if (encounter[0] && encounter[0].time >= time(0) - 0.5 * _1h) {
- encounter[0].encountered = time(0);
- setEncounter(encounter);
- }
- return true;
- }
- },
+ iw: { reg: /^Initializing Item World/ },
+ gr: { reg: /^Initializing Grindfest/ },
+ tw: { reg: /^Initializing The Tower/ },
+ ba: { reg: /^Initializing random encounter/ },
}
- battle.tower = (temp.match(/\(Floor (\d+)\)/) ?? [null])[1] * 1;
- const id = (temp.match(/\d+/) ?? [null])[0] * 1;
- battle.roundType = undefined;
- for (let name in types) {
- const type = types[name];
- if (!temp.match(type.reg)) {
- continue;
- }
- if (type.extra && !type.extra(id)) {
- continue;
+ const battleLog = gE('#textlog>tbody>tr>td', 'all');
+ if (!battle.roundType) {
+ const temp = battleLog[battleLog.length - 1].textContent;
+ battle.tower = (temp.match(/\(Floor (\d+)\)/) ?? [null])[1] * 1;
+ const id = (temp.match(/\d+/) ?? [null])[0] * 1;
+ battle.roundType = undefined;
+ for (let name in types) {
+ const type = types[name];
+ if (!temp.match(type.reg)) {
+ continue;
+ }
+ if (type.extra && !type.extra(id)) {
+ continue;
+ }
+ battle.roundType = name;
+ break;
}
- battle.roundType = name;
- break;
}
- }
- if (/You lose \d+ Stamina/.test(battleLog[0].textContent)) {
- const staminaLostLog = getValue('staminaLostLog', true) || {};
- staminaLostLog[time(3)] = battleLog[0].textContent.match(/You lose (\d+) Stamina/)[1] * 1;
- setValue('staminaLostLog', staminaLostLog);
- const losedStamina = battleLog[0].textContent.match(/\d+/)[0] * 1;
- if (losedStamina >= g('option').staminaLose) {
- setAlarm('Error');
- if (!_alert(1, '当前Stamina过低\n或Stamina损失过多\n是否继续?', '當前Stamina過低\n或Stamina損失過多\n是否繼續?', 'Continue?\nYou either have too little Stamina or have lost too much')) {
- pauseChange();
- return;
+ if (battle.roundType === 'ba' || document.body.innerHTML.match(/Initializing random encounter/)) {
+ const encounter = getEncounter();
+ if (encounter[0]) {
+ encounter[0].encountered = time(0);
+ setEncounter(encounter);
}
}
- }
- const roundPrev = battle.roundNow;
-
- if (battleLog[battleLog.length - 1].textContent.match('Initializing')) {
- const monsterStatus = [];
- let order = 0;
- const monsterNames = Array.from(gE('div.btm3>div>div', 'all')).map(monster => monster.innerText);
- const monsterLvs = Array.from(gE('div.btm2>div>div', 'all')).map(monster => monster.innerText);
- const monsterDB = getValue('monsterDB', true) ?? {};
- const monsterMID = getValue('monsterMID', true) ?? {};
- const oldDB = JSON.stringify(monsterDB);
- const oldMID = JSON.stringify(monsterMID);
- for (let i = battleLog.length - 2; i > battleLog.length - 2 - g('monsterAll'); i--) {
- let mid = battleLog[i].textContent.match(/MID=(\d+)/)[1] * 1;
- let name = battleLog[i].textContent.match(/MID=(\d+) \((.*)\) LV/)[2];
- let lv = battleLog[i].textContent.match(/LV=(\d+)/)[1] * 1;
- let hp = battleLog[i].textContent.match(/HP=(\d+)$/)[1] * 1;
- if (isNaN(hp)) {
- hp = getHPFromMonsterDB(monsterDB, monsterNames[order], monsterLvs[order]) ?? monsterStatus[monsterStatus.length - 1].hp;
- }
- if (name && lv && mid) {
- monsterDB[name] ??= {};
- if (monsterDB[name].mid && monsterDB[name].mid !== mid) { // 名称被其他mid被占用
- monsterMID[monsterDB[name].mid] = JSON.parse(JSON.stringify(monsterDB[name])); // 将之前mid的数据进行另外备份
- monsterDB[name] = {}; // 重置该名称的数据
+ const roundPrev = battle.roundNow;
+
+ if (battleLog[battleLog.length - 1].textContent.match('Initializing')) {
+ const monsterStatus = [];
+ let order = 0;
+ const monsterNames = Array.from(gE(`${monsterStateKeys.name}>div>div`, 'all')).map(monster => monster.innerText);
+ const monsterLvs = Array.from(gE(`${monsterStateKeys.lv}>div>div`, 'all')).map(monster => monster.innerText);
+ const monsterDB = getValue('monsterDB', true) ?? {};
+ const monsterMID = getValue('monsterMID', true) ?? {};
+ for (let i = battleLog.length - 2; i > battleLog.length - 2 - g('monsterAll'); i--) {
+ let mid = battleLog[i].textContent.match(/MID=(\d+)/)[1] * 1;
+ let name = battleLog[i].textContent.match(/MID=(\d+) \((.*)\) LV/)[2];
+ let lv = battleLog[i].textContent.match(/LV=(\d+)/)[1] * 1;
+ let hp = battleLog[i].textContent.match(/HP=(\d+)$/)[1] * 1;
+ if (isNaN(hp)) {
+ hp = getHPFromMonsterDB(monsterDB, monsterNames[order], monsterLvs[order]) ?? monsterStatus[monsterStatus.length - 1].hp;
}
- if (monsterMID[mid]) {
- monsterDB[name] = JSON.parse(JSON.stringify(monsterMID[mid])); // 将之前备份的mid的数据进行恢复
- delete monsterMID[mid];
+ if (name && lv && mid) {
+ monsterDB[name] ??= {};
+ if (monsterDB[name].mid && monsterDB[name].mid !== mid) { // 名称被其他mid被占用
+ monsterMID[monsterDB[name].mid] = JSON.parse(JSON.stringify(monsterDB[name])); // 将之前mid的数据进行另外备份
+ monsterDB[name] = {}; // 重置该名称的数据
+ }
+ if (monsterMID[mid]) {
+ monsterDB[name] = JSON.parse(JSON.stringify(monsterMID[mid])); // 将之前备份的mid的数据进行恢复
+ delete monsterMID[mid];
+ }
+ monsterDB[name].mid = mid;
+ monsterDB[name][lv] = hp;
}
- monsterDB[name].mid = mid;
- monsterDB[name][lv] = hp;
+ monsterStatus[order] = {
+ order: order,
+ hp,
+ };
+ order++;
}
- monsterStatus[order] = {
- order: order,
- hp,
- };
- order++;
- }
- if(g('option').cacheMonsterHP){
- if (oldDB !== JSON.stringify(monsterDB)) {
+ if (g('option').cacheMonsterHP) {
setValue('monsterDB', monsterDB);
- }
- if (oldMID !== JSON.stringify(monsterMID)) {
setValue('monsterMID', monsterMID);
}
- }
- battle.monsterStatus = monsterStatus;
+ battle.monsterStatus = monsterStatus;
- const round = battleLog[battleLog.length - 1].textContent.match(/\(Round (\d+) \/ (\d+)\)/);
- if (round && battle.roundType !== 'ba') {
- battle.roundNow = round[1] * 1;
- battle.roundAll = round[2] * 1;
- } else {
+ const round = battleLog[battleLog.length - 1].textContent.match(/\(Round (\d+) \/ (\d+)\)/);
+ if (round && battle.roundType !== 'ba') {
+ battle.roundNow = round[1] * 1;
+ battle.roundAll = round[2] * 1;
+ } else {
+ battle.roundNow = 1;
+ battle.roundAll = 1;
+ }
+ } else if (!battle.monsterStatus || battle.monsterStatus.length !== gE(monsterStateKeys.lv, 'all').length) {
battle.roundNow = 1;
battle.roundAll = 1;
}
- } else if (!battle.monsterStatus || battle.monsterStatus.length !== gE('div.btm2', 'all').length) {
- battle.roundNow = 1;
- battle.roundAll = 1;
- }
- if(roundPrev !== battle.roundNow) {
- battle.turn = 0;
+ if (roundPrev !== battle.roundNow) {
+ battle.turn = 0;
+ setValue('skillOTOS', {});
+ }
+ battle.roundLeft = battle.roundAll - battle.roundNow;
+ setValue('battle', battle);
}
- battle.roundLeft = battle.roundAll - battle.roundNow;
- setValue('battle', battle);
-
- g('skillOTOS', {
- OFC: 0,
- FRD: 0,
- T3: 0,
- T2: 0,
- T1: 0,
- });
- }
- function killBug() { // 在 HentaiVerse 发生导致 turn 损失的 bug 时发出警告并移除问题元素: https://ehwiki.org/wiki/HentaiVerse_Bugs_%26_Errors#Combat
- const bugLog = gE('#textlog > tbody > tr > td[class="tlb"]', 'all');
- const isBug = /(Slot is currently not usable)|(Item does not exist)|(Inventory slot is empty)|(You do not have a powerup gem)/;
- for (let i = 0; i < bugLog.length; i++) {
- if (bugLog[i].textContent.match(isBug)) {
- bugLog[i].className = 'tlbWARN';
- setTimeout(() => { // 间隔时间以避免持续刷新
- window.location.href = window.location;// 刷新移除问题元素
- }, 700);
- } else {
- bugLog[i].className = 'tlbQRA';
+ function killBug() { // 在 HentaiVerse 发生导致 turn 损失的 bug 时发出警告并移除问题元素: https://ehwiki.org/wiki/HentaiVerse_Bugs_%26_Errors#Combat
+ const bugLog = gE('#textlog > tbody > tr > td[class="tlb"]', 'all');
+ const isBug = /(Slot is currently not usable)|(Item does not exist)|(Inventory slot is empty)|(You do not have a powerup gem)/;
+ for (let i = 0; i < bugLog.length; i++) {
+ if (bugLog[i].textContent.match(isBug)) {
+ bugLog[i].className = 'tlbWARN';
+ setTimeout(() => { // 间隔时间以避免持续刷新
+ window.location.reload();// 刷新移除问题元素
+ }, 700);
+ } else {
+ bugLog[i].className = 'tlbQRA';
+ }
}
}
- }
- function countMonsterHP() { // 统计敌人血量
- let i, j;
- const monsterHp = gE('div.btm4>div.btm5:nth-child(1)', 'all');
- let battle = getValue('battle', true);
- const monsterStatus = battle.monsterStatus;
- const hpArray = [];
- for (i = 0; i < monsterHp.length; i++) {
- if (gE('img[src*="nbardead.png"]', monsterHp[i])) {
- monsterStatus[i].isDead = true;
- monsterStatus[i].hpNow = Infinity;
- } else {
- monsterStatus[i].isDead = false;
- monsterStatus[i].hpNow = Math.floor(monsterStatus[i].hp * parseFloat(gE('img', monsterHp[i]).style.width) / 120 + 1);
- hpArray.push(monsterStatus[i].hpNow);
+ function countMonsterHP() { // 统计敌人血量
+ let i, j;
+ const monsterHp = gE(`${monsterStateKeys.bars}:nth-child(1)`, 'all');
+ const monsterMp = gE(`${monsterStateKeys.bars}:nth-child(2)`, 'all');
+ const monsterSp = gE(`${monsterStateKeys.bars}:nth-child(3)`, 'all');
+ let battle = getValue('battle', true);
+ const monsterStatus = battle.monsterStatus;
+ const hpArray = [];
+ for (i = 0; i < monsterHp.length; i++) {
+ if (gE('img[src*="nbardead.png"]', monsterHp[i])) {
+ monsterStatus[i].isDead = true;
+ monsterStatus[i].hpNow = Infinity;
+ } else {
+ monsterStatus[i].isDead = false;
+ monsterStatus[i].hpNow = Math.floor(monsterStatus[i].hp * parseFloat(gE('img:first-child', monsterHp[i]).style.width) / 120 + 1);
+ monsterStatus[i].mpNow = parseFloat(gE('img:first-child', monsterMp[i]).style.width) / 120;
+ monsterStatus[i].spNow = parseFloat(gE('img:first-child', monsterSp[i]).style.width) / 120;
+ hpArray.push(monsterStatus[i].hpNow);
+ }
}
- }
- battle.monsterStatus = monsterStatus;
-
- const skillLib = {
- Sle: {
- name: 'Sleep',
- img: 'sleep',
- },
- Bl: {
- name: 'Blind',
- img: 'blind',
- },
- Slo: {
- name: 'Slow',
- img: 'slow',
- },
- Im: {
- name: 'Imperil',
- img: 'imperil',
- },
- MN: {
- name: 'MagNet',
- img: 'magnet',
- },
- Si: {
- name: 'Silence',
- img: 'silence',
- },
- Dr: {
- name: 'Drain',
- img: 'drainhp',
- },
- We: {
- name: 'Weaken',
- img: 'weaken',
- },
- Co: {
- name: 'Confuse',
- img: 'confuse',
- },
- CM: {
- name: 'Coalesced Mana',
- img: 'coalescemana',
- },
- Stun: {
- name: 'Stunned',
- img: 'wpn_stun',
- },
- PA: {
- name: 'Penetrated Armor',
- img: 'wpn_ap',
- },
- BW: {
- name: 'Bleeding Wound',
- img: 'wpn_bleed',
- },
- };
- const monsterBuff = gE('div.btm6', 'all');
- const hpMin = Math.min.apply(null, hpArray);
- const yggdrasilExtraWeight = g('option').YggdrasilExtraWeight;
- const unreachableWeight = g('option').unreachableWeight;
- const baseHpRatio = g('option').baseHpRatio ?? 1;
- // 权重越小,优先级越高
- for (i = 0; i < monsterStatus.length; i++) { // 死亡的排在最后(优先级最低)
- if (monsterStatus[i].isDead) {
- monsterStatus[i].finWeight = unreachableWeight;
- continue;
- }
- let weight = baseHpRatio * Math.log10(monsterStatus[i].hpNow / hpMin); // > 0 生命越低权重越低优先级越高
- monsterStatus[i].hpWeight = weight;
- if (yggdrasilExtraWeight && ('Yggdrasil' === gE('div.btm3>div>div', monsterBuff[i].parentNode).innerText || '世界树 Yggdrasil' === gE('div.btm3>div>div', monsterBuff[i].parentNode).innerText)) { // 默认设置下,任何情况都优先击杀群体大量回血的boss"Yggdrasil"
- weight += yggdrasilExtraWeight; // yggdrasilExtraWeight.defalut -1000
- }
- for (j in skillLib) {
- if (gE(`img[src*="${skillLib[j].img}"]`, monsterBuff[i])) {
- weight += g('option').weight[j];
- }
- }
- monsterStatus[i].finWeight = weight;
- }
- monsterStatus.sort(objArrSort('finWeight'));
- battle.monsterStatus = monsterStatus;
- g('battle', battle);
- }
+ battle.monsterStatus = monsterStatus;
- function autoRecover() { // 自动回血回魔
- if (!g('option').item) {
- return false;
- }
- if (!g('option').itemOrderValue) {
- return false;
- }
- const name = g('option').itemOrderName.split(',');
- const order = g('option').itemOrderValue.split(',');
- for (let i = 0; i < name.length; i++) {
- if (g('option').item[name[i]] && checkCondition(g('option')[`item${name[i]}Condition`]) && isOn(order[i])) {
- isOn(order[i]).click();
- return true;
+ const skillLib = {
+ Sle: {
+ name: 'Sleep',
+ img: 'sleep',
+ },
+ Bl: {
+ name: 'Blind',
+ img: 'blind',
+ },
+ Slo: {
+ name: 'Slow',
+ img: 'slow',
+ },
+ Im: {
+ name: 'Imperil',
+ img: 'imperil',
+ },
+ MN: {
+ name: 'MagNet',
+ img: 'magnet',
+ },
+ Si: {
+ name: 'Silence',
+ img: 'silence',
+ },
+ Dr: {
+ name: 'Drain',
+ img: 'drainhp',
+ },
+ ET: {
+ name: 'Ether Theft',
+ img: 'drainmp',
+ },
+ ST: {
+ name: 'Spirit Theft',
+ img: 'drainsp',
+ },
+ We: {
+ name: 'Weaken',
+ img: 'weaken',
+ },
+ Co: {
+ name: 'Confuse',
+ img: 'confuse',
+ },
+ Po: {
+ name: 'Spreading Poison',
+ img: 'poison'
+ },
+ CM: {
+ name: 'Coalesced Mana',
+ img: 'coalescemana',
+ },
+ Stun: {
+ name: 'Stunned',
+ img: 'wpn_stun',
+ },
+ PA: {
+ name: 'Penetrated Armor',
+ img: 'wpn_ap',
+ },
+ BW: {
+ name: 'Bleeding Wound',
+ img: 'wpn_bleed',
+ },
+ AW: {
+ name: 'Absorbing Ward',
+ img: 'absorb',
+ },
+
+ FoS: {
+ name: 'Fury of the Sisters',
+ img: 'trio_furyofthesisters',
+ },
+ LoF: {
+ name: 'Lamentations of the Future',
+ img: 'trio_skuld',
+ },
+ SoP: {
+ name: 'Screams of the Past',
+ img: 'trio_urd',
+ },
+ WoP: {
+ name: 'Wailings of the Present',
+ img: 'trio_verdandi',
+ },
+
+ SS: {
+ name: 'Searing Skin',
+ img: 'firedot',
+ elem: 2,
+ },
+ FL: {
+ name: 'Freezing Limbs',
+ img: 'coldslow',
+ elem: 1,
+ },
+ TA: {
+ name: 'Turbulent Air',
+ img: 'windmiss',
+ elem: 4,
+ },
+ DB: {
+ name: 'Deep Burns',
+ img: 'elecweak',
+ elem: 3,
+ },
+ BD: {
+ name: 'Breached Defense',
+ img: 'holybreach',
+ elem: 6,
+ },
+ BA: {
+ name: 'Blunted Attack',
+ img: 'darknerf',
+ elem: 5,
+ },
+
+ BS: {
+ name: 'Burning Soul',
+ img: 'soulfire',
+ },
+ RS: {
+ name: 'Ripened Soul',
+ img: 'ripesoul',
+ },
+ };
+ const monsterBuff = gE(monsterStateKeys.buffs, 'all');
+ const hpMin = Math.min.apply(null, hpArray);
+ const option = g('option');
+ const yggdrasilExtraWeight = option.YggdrasilExtraWeight;
+ const unreachableWeight = option.unreachableWeight;
+ const baseHpRatio = option.baseHpRatio;
+ // 权重越小,优先级越高
+ for (i = 0; i < monsterStatus.length; i++) { // 死亡的排在最后(优先级最低)
+ if (monsterStatus[i].isDead) {
+ monsterStatus[i].finWeight = unreachableWeight;
+ continue;
+ }
+ let weight = baseHpRatio * Math.log10(monsterStatus[i].hpNow / hpMin); // > 0 生命越低权重越低优先级越高
+ const name = gE(`${monsterStateKeys.name}>div>div`, monsterBuff[i].parentNode).innerText;
+ if (yggdrasilExtraWeight && ('Yggdrasil' === name || '世界树 Yggdrasil' === name)) { // 默认设置下,任何情况都优先击杀群体大量回血的boss"Yggdrasil"
+ weight += yggdrasilExtraWeight; // yggdrasilExtraWeight.defalut -1000
+ }
+ const known = {};
+ for (j in skillLib) {
+ if (!gE(`img[src*="/${skillLib[j].img}"]`, monsterBuff[i])) {
+ continue;
+ }
+ known[skillLib[j].img] = skillLib[j];
+ if (skillLib[j].elem && skillLib[j].elem !== g('attackStatus')) {
+ weight += option.weight[`${j}1`] ?? 0;
+ continue;
+ }
+ weight += option.weight[j] ?? 0;
+ }
+
+ let unknown = gE(`img`, 'all', monsterBuff[i]);
+ if (unknown?.length) {
+ unknown = Array.from(unknown).filter(buff => {
+ const img = buff.src.match(/\/y\/e\/(.*)\.png/)[1];
+ return !(Object.keys(known).includes(img));
+ }).map(buff=>`${buff.getAttribute('onmouseover').match(/^battle.set_infopane_effect\('(.+)', *'.*',.+\)/)[1]}: ${buff.src.match(/\/y\/e\/(.*)\.png/)[1]}`);
+ if (unknown.length) {
+ console.log('unsupported buff weight:', unknown);
+ }
+ }
+ monsterStatus[i].finWeight = weight;
}
+ monsterStatus.sort(objArrSort('finWeight'));
+ battle.monsterStatus = monsterStatus;
+ g('battle', battle);
}
- return false;
- }
- function useScroll() { // 自动使用卷轴
- if (!g('option').scrollSwitch) {
- return false;
- }
- if (!g('option').scroll) {
- return false;
- }
- if (!checkCondition(g('option').scrollCondition)) {
- return false;
- }
- if (!g('option').scrollRoundType) {
- return false;
- }
- if (!g('option').scrollRoundType[g('battle').roundType]) {
+ function autoRecover(isCureOnly) { // 自动回血回魔
+ const option = g('option');
+ if (!option.item) {
+ return false;
+ }
+ const name = splitOrders(option.itemOrderName, ['FC', 'HE', 'LE', 'HG', 'HP', 'Cure', 'MG', 'MP', 'ME', 'SG', 'SP', 'SE', 'Mystic', 'CC', 'ED']);
+ const order = splitOrders(option.itemOrderValue, [313, 11199, 11501, 10005, 11195, 311, 10006, 11295, 11299, 10007, 11395, 11399, 10008, 11402, 11401]);
+ const cures = [313, 11199, 11501, 10005, 11195, 311];
+ for (let i = 0; i < name.length; i++) {
+ let id = order[i];
+ if (isCureOnly && !cures.includes(id)){
+ continue;
+ }
+ if (option.item[name[i]] && checkCondition(option[`item${name[i]}Condition`]) && isOn(id)) {
+ (gE(`.bti3>div[onmouseover*="(${id})"]`) ?? gE(id)).click();
+ return true;
+ }
+ }
return false;
}
- const scrollLib = {
- Go: {
- name: 'Scroll of the Gods',
- id: 13299,
- mult: '3',
- img1: 'absorb',
- img2: 'shadowveil',
- img3: 'sparklife',
- },
- Av: {
- name: 'Scroll of the Avatar',
- id: 13199,
- mult: '2',
- img1: 'haste',
- img2: 'protection',
- },
- Pr: {
- name: 'Scroll of Protection',
- id: 13111,
- mult: '1',
- img1: 'protection',
- },
- Sw: {
- name: 'Scroll of Swiftness',
- id: 13101,
- mult: '1',
- img1: 'haste',
- },
- Li: {
- name: 'Scroll of Life',
- id: 13221,
- mult: '1',
- img1: 'sparklife',
- },
- Sh: {
- name: 'Scroll of Shadows',
- id: 13211,
- mult: '1',
- img1: 'shadowveil',
- },
- Ab: {
- name: 'Scroll of Absorption',
- id: 13201,
- mult: '1',
- img1: 'absorb',
- },
- };
- const scrollFirst = (g('option').scrollFirst) ? '_scroll' : '';
- let isUsed;
- for (const i in scrollLib) {
- if (g('option').scroll[i] && gE(`.bti3>div[onmouseover*="${scrollLib[i].id}"]`) && checkCondition(g('option')[`scroll${i}Condition`])) {
+
+ function useScroll() { // 自动使用卷轴
+ const option = g('option');
+ if (!option.scrollSwitch) {
+ return false;
+ }
+ if (!option.scroll) {
+ return false;
+ }
+ if (!option.scrollRoundType) {
+ return false;
+ }
+ if (!option.scrollRoundType[g('battle').roundType]) {
+ return false;
+ }
+ if (!checkCondition(option.scrollCondition)) {
+ return false;
+ }
+ const scrollLib = {
+ Go: {
+ name: 'Scroll of the Gods',
+ id: 13299,
+ mult: '3',
+ img1: 'absorb',
+ img2: 'shadowveil',
+ img3: 'sparklife',
+ },
+ Av: {
+ name: 'Scroll of the Avatar',
+ id: 13199,
+ mult: '2',
+ img1: 'haste',
+ img2: 'protection',
+ },
+ Pr: {
+ name: 'Scroll of Protection',
+ id: 13111,
+ mult: '1',
+ img1: 'protection',
+ },
+ Sw: {
+ name: 'Scroll of Swiftness',
+ id: 13101,
+ mult: '1',
+ img1: 'haste',
+ },
+ Li: {
+ name: 'Scroll of Life',
+ id: 13221,
+ mult: '1',
+ img1: 'sparklife',
+ },
+ Sh: {
+ name: 'Scroll of Shadows',
+ id: 13211,
+ mult: '1',
+ img1: 'shadowveil',
+ },
+ Ab: {
+ name: 'Scroll of Absorption',
+ id: 13201,
+ mult: '1',
+ img1: 'absorb',
+ },
+ };
+ const scrollFirst = (option.scrollFirst) ? '_scroll' : '';
+ for (const i in scrollLib) {
+ if (!option.scroll[i]) {
+ continue;
+ }
+ if (!gE(`.bti3>div[onmouseover*="(${scrollLib[i].id})"]`)) {
+ continue;
+ }
+ if (!checkCondition(option[`scroll${i}Condition`])) {
+ continue;
+ }
for (let j = 1; j <= scrollLib[i].mult; j++) {
- if (gE(`#pane_effects>img[src*="${scrollLib[i][`img${j}`]}${scrollFirst}"]`)) {
- isUsed = true;
- break;
+ if (getBuff(scrollLib[i][`img${j}`] + scrollFirst)) {
+ continue;
}
- isUsed = false;
- }
- if (!isUsed) {
- gE(`.bti3>div[onmouseover*="${scrollLib[i].id}"]`).click();
+ gE(`.bti3>div[onmouseover*="(${scrollLib[i].id})"]`).click();
return true;
}
}
- }
- return false;
- }
-
- function useChannelSkill() { // 自动施法Channel技能
- if (!g('option').channelSkillSwitch) {
- return false;
- }
- if (!g('option').channelSkill) {
return false;
}
- if (!gE('#pane_effects>img[src*="channeling"]')) {
+
+ function useChannelSkill() { // 自动施法Channel技能
+ const option = g('option');
+ if (!option.channelSkillSwitch) {
+ return false;
+ }
+ if (!getBuff('channeling')) {
+ return false;
+ }
+ const skillLib = {
+ SS: {
+ name: 'Spirit Shield',
+ id: '423',
+ img: 'spiritshield',
+ },
+ SL: {
+ name: 'Spark of Life',
+ id: '422',
+ img: 'sparklife',
+ },
+ Pr: {
+ name: 'Protection',
+ id: '411',
+ img: 'protection',
+ },
+ Ab: {
+ name: 'Absorb',
+ id: '421',
+ img: 'absorb',
+ },
+ SV: {
+ name: 'Shadow Veil',
+ id: '413',
+ img: 'shadowveil',
+ },
+ Re: {
+ name: 'Regen',
+ id: '312',
+ img: 'regen',
+ },
+ Ha: {
+ name: 'Haste',
+ id: '412',
+ img: 'haste',
+ },
+ He: {
+ name: 'Heartseeker',
+ id: '431',
+ img: 'heartseeker',
+ },
+ AF: {
+ name: 'Arcane Focus',
+ id: '432',
+ img: 'arcanemeditation',
+ },
+
+ CF: {
+ name: 'Cloak of the Fallen',
+ id: getBuff('sparklife') ? undefined : 422,
+ img: 'fallenshield',
+ }
+ };
+ if (option.channelSkill) {
+ const skillPack = splitOrders(option.buffSkillOrderValue, ['SS', 'SL', 'Pr', 'Ab', 'SV', 'Re', 'Ha', 'He', 'AF']);
+ for (const buff of skillPack) {
+ const current = getBuffTurnFromImg(getBuff(skillLib[buff].img));
+ const threshold = option.channelThreshold ? option.channelThreshold[buff] : 0;
+ if (threshold > 0 && current >= threshold) continue;
+ if (!option.channelSkill[buff] || getBuff(skillLib[buff].img)) continue;
+ if (!isOn(skillLib[buff].id)) continue;
+ gE(skillLib[buff].id).click();
+ return true;
+ }
+ }
+ if (option.channelSkill2) {
+ const order = splitOrders(option.channelSkill2OrderValue);
+ for (const id of order) {
+ const buff = Object.keys(skillLib).find(s => skillLib[s].id * 1 === 1 * id);
+ if (buff) {
+ const current = getBuffTurnFromImg(getBuff(skillLib[buff].img));
+ const threshold = option.channelThreshold ? option.channelThreshold[buff] : 0;
+ if (threshold > 0 && current > threshold) continue;
+ }
+ if (!isOn(id)) continue;
+ gE(id).click();
+ return true;
+ }
+ }
+ if (option.channelRebuff) {
+ let minBuff, minTime;
+ for (const buff in skillLib) {
+ let current = getBuffTurnFromImg(getBuff(skillLib[buff].img));
+ const threshold = option.channelThreshold ? option.channelThreshold[buff] : 0;
+ if (threshold > 0 && current > threshold) continue;
+
+ current = isNaN(current) ? 0 : current;
+ if (getBuff(skillLib[buff].img)?.src.match(/_scroll.png$/) || (minTime && current >= minTime)) {
+ continue;
+ }
+ const id = skillLib[buff].id;
+ if (!current && (!option.buffSkillSwitch || !option.buffSkill[buff])) continue;
+
+ if (!isOn(id)) continue;
+ minBuff = id;
+ minTime = current;
+ }
+ if (minBuff) {
+ gE(minBuff).click();
+ return true;
+ }
+ }
return false;
}
- const skillLib = {
- Pr: {
- name: 'Protection',
- id: '411',
- img: 'protection',
- },
- SL: {
- name: 'Spark of Life',
- id: '422',
- img: 'sparklife',
- },
- SS: {
- name: 'Spirit Shield',
- id: '423',
- img: 'spiritshield',
- },
- Ha: {
- name: 'Haste',
- id: '412',
- img: 'haste',
- },
- AF: {
- name: 'Arcane Focus',
- id: '432',
- img: 'arcanemeditation',
- },
- He: {
- name: 'Heartseeker',
- id: '431',
- img: 'heartseeker',
- },
- Re: {
- name: 'Regen',
- id: '312',
- img: 'regen',
- },
- SV: {
- name: 'Shadow Veil',
- id: '413',
- img: 'shadowveil',
- },
- Ab: {
- name: 'Absorb',
- id: '421',
- img: 'absorb',
- },
- };
- let i; let
- j;
- const skillPack = g('option').buffSkillOrderValue.split(',');
- if (g('option').channelSkill) {
+
+ function useBuffSkill() { // 自动施法BUFF技能
+ const skillLib = {
+ SS: {
+ name: 'Spirit Shield',
+ id: '423',
+ img: 'spiritshield',
+ },
+ SL: {
+ name: 'Spark of Life',
+ id: '422',
+ img: 'sparklife',
+ },
+ Pr: {
+ name: 'Protection',
+ id: '411',
+ img: 'protection',
+ },
+ Ab: {
+ name: 'Absorb',
+ id: '421',
+ img: 'absorb',
+ },
+ SV: {
+ name: 'Shadow Veil',
+ id: '413',
+ img: 'shadowveil',
+ },
+ Re: {
+ name: 'Regen',
+ id: '312',
+ img: 'regen',
+ },
+ Ha: {
+ name: 'Haste',
+ id: '412',
+ img: 'haste',
+ },
+ He: {
+ name: 'Heartseeker',
+ id: '431',
+ img: 'heartseeker',
+ },
+ AF: {
+ name: 'Arcane Focus',
+ id: '432',
+ img: 'arcanemeditation',
+ },
+ };
+ const option = g('option');
+ if (!option.buffSkillSwitch) {
+ return false;
+ }
+ if (!option.buffSkill) {
+ return false;
+ }
+ if (!checkCondition(option.buffSkillCondition)) {
+ return false;
+ }
+ let i;
+ const skillPack = splitOrders(option.buffSkillOrderValue, ['SS', 'SL', 'Pr', 'Ab', 'SV', 'Re', 'Ha', 'He', 'AF']);
for (i = 0; i < skillPack.length; i++) {
- j = skillPack[i];
- if (g('option').channelSkill[j] && !gE(`#pane_effects>img[src*="${skillLib[j].img}"]`) && isOn(skillLib[j].id)) {
- gE(skillLib[j].id).click();
+ let buff = skillPack[i];
+ if (!option.buffSkill[buff]) continue;
+ if (!isOn(skillLib[buff].id)) continue;
+ if (!checkCondition(option[`buffSkill${buff}Condition`])) continue;
+ const current = getBuffTurnFromImg(getBuff(skillLib[buff].img));
+ const threshold = option.buffSkillThreshold ? option.buffSkillThreshold[buff] : 0;
+ if (threshold >= 0 && current > threshold) continue;
+ gE(skillLib[buff].id).click();
+ return true;
+ }
+ const draughtPack = {
+ HD: {
+ id: 11191,
+ img: 'healthpot',
+ },
+ MD: {
+ id: 11291,
+ img: 'manapot',
+ },
+ SD: {
+ id: 11391,
+ img: 'spiritpot',
+ },
+ FV: {
+ id: 19111,
+ img: 'flowers',
+ },
+ BG: {
+ id: 19131,
+ img: 'gum',
+ },
+ };
+ for (i in draughtPack) {
+ if (!getBuff(draughtPack[i].img) && option.buffSkill && option.buffSkill[i] && checkCondition(option[`buffSkill${i}Condition`]) && gE(`.bti3>div[onmouseover*="(${draughtPack[i].id})"]`)) {
+ gE(`.bti3>div[onmouseover*="(${draughtPack[i].id})"]`).click();
return true;
}
}
+ return false;
}
- if (g('option').channelSkill2 && g('option').channelSkill2OrderValue) {
- const order = g('option').channelSkill2OrderValue.split(',');
- for (i = 0; i < order.length; i++) {
- if (isOn(order[i])) {
- gE(order[i]).click();
- return true;
- }
+
+ function useInfusions() { // 自动使用魔药
+ const option = g('option');
+ if (!option.infusionSwitch) return false;
+ if (!checkCondition(option.infusionCondition)) {
+ return false;
}
- }
- const buff = gE('#pane_effects>img', 'all');
- if (buff.length > 0) {
- const name2Skill = {
- 'Protection': 'Pr',
- 'Spark of Life': 'SL',
- 'Spirit Shield': 'SS',
- 'Hastened': 'Ha',
- 'Arcane Focus': 'AF',
- 'Heartseeker': 'He',
- 'Regen': 'Re',
- 'Shadow Veil': 'SV',
- };
- for (i = 0; i < buff.length; i++) {
- const spellName = buff[i].getAttribute('onmouseover').match(/'(.*?)'/)[1];
- const buffLastTime = buff[i].getAttribute('onmouseover').match(/\(.*,.*, (.*?)\)$/)[1] * 1;
- if (isNaN(buffLastTime) || buff[i].src.match(/_scroll.png$/)) {
- continue;
- } else {
- if (spellName === 'Cloak of the Fallen' && !gE('#pane_effects>img[src*="sparklife"]') && isOn('422')) {
- gE('422').click();
- return true;
- } if (spellName in name2Skill && isOn(skillLib[name2Skill[spellName]].id)) {
- gE(skillLib[name2Skill[spellName]].id).click();
- return true;
- }
+
+ const onUse = function(status) {
+ if (getBuff(infusionLib[status].img)) return false;
+ const itemBtn = gE(`.bti3>div[onmouseover*="(${infusionLib[status].id})"]`);
+ if (!itemBtn) return false;
+ itemBtn.click();
+ return true;
+ }
+ const infusionLib = [ null, {
+ id: 12101,
+ img: 'fireinfusion',
+ name: 'Flames',
+ }, {
+ id: 12201,
+ img: 'coldinfusion',
+ name: 'Frost',
+ }, {
+ id: 12301,
+ img: 'elecinfusion',
+ name: 'Lightning',
+ }, {
+ id: 12401,
+ img: 'windinfusion',
+ name: 'Storms',
+ }, {
+ id: 12501,
+ img: 'holyinfusion',
+ name: 'Divinity',
+ }, {
+ id: 12601,
+ img: 'darkinfusion',
+ name: 'Darkness',
+ }];
+
+ if (option.infusionDefaultOnly) {
+ const attackStatus = g('attackStatus');
+ if (attackStatus === 0) return false;
+ return onUse(attackStatus);
+ }
+ if (!option.infusion) return false;
+ const order = splitOrders(option.infusionOrderName, ['Divinity', 'Darkness', 'Flames', 'Frost', 'Lightning', 'Storms']);
+ for (const name of order) {
+ const condition = option[`infusion${name}Condition`];
+ if (!checkCondition(condition)) continue;
+ if (onUse(infusionLib.findIndex(i => i?.name === name))) {
+ return true;
}
}
- }
- return false;
- }
-
- function useBuffSkill() { // 自动施法BUFF技能
- const skillLib = {
- Pr: {
- name: 'Protection',
- id: '411',
- img: 'protection',
- },
- SL: {
- name: 'Spark of Life',
- id: '422',
- img: 'sparklife',
- },
- SS: {
- name: 'Spirit Shield',
- id: '423',
- img: 'spiritshield',
- },
- Ha: {
- name: 'Haste',
- id: '412',
- img: 'haste',
- },
- AF: {
- name: 'Arcane Focus',
- id: '432',
- img: 'arcanemeditation',
- },
- He: {
- name: 'Heartseeker',
- id: '431',
- img: 'heartseeker',
- },
- Re: {
- name: 'Regen',
- id: '312',
- img: 'regen',
- },
- SV: {
- name: 'Shadow Veil',
- id: '413',
- img: 'shadowveil',
- },
- Ab: {
- name: 'Absorb',
- id: '421',
- img: 'absorb',
- },
- };
- if (!g('option').buffSkillSwitch) {
- return false;
- }
- if (!g('option').buffSkill) {
- return false;
- }
- if (!checkCondition(g('option').buffSkillCondition)) {
return false;
}
- let i;
- const skillPack = g('option').buffSkillOrderValue.split(',');
- for (i = 0; i < skillPack.length; i++) {
- let buff = skillPack[i];
- if (g('option').buffSkill[buff] && checkCondition(g('option')[`buffSkill${buff}Condition`]) && !gE(`#pane_effects>img[src*="${skillLib[buff].img}"]`) && isOn(skillLib[buff].id)) {
- gE(skillLib[buff].id).click();
+
+ function autoFocus() {
+ const option = g('option');
+ if (option.focus && checkCondition(option.focusCondition)) {
+ gE('#ckey_focus').click();
return true;
}
+ return false;
}
- const draughtPack = {
- HD: {
- id: 11191,
- img: 'healthpot',
- },
- MD: {
- id: 11291,
- img: 'manapot',
- },
- SD: {
- id: 11391,
- img: 'spiritpot',
- },
- FV: {
- id: 19111,
- img: 'flowers',
- },
- BG: {
- id: 19131,
- img: 'gum',
- },
- };
- for (i in draughtPack) {
- if (!gE(`#pane_effects>img[src*="${draughtPack[i].img}"]`) && g('option').buffSkill && g('option').buffSkill[i] && checkCondition(g('option')[`buffSkill${i}Condition`]) && gE(`.bti3>div[onmouseover*="${draughtPack[i].id}"]`)) {
- gE(`.bti3>div[onmouseover*="${draughtPack[i].id}"]`).click();
+
+ function autoSS(isDisableOnly) {
+ const textSP = gE('#vrs') ?? gE('#dvrs');
+ const spValue = textSP.childNodes[0].textContent * 1;
+ if (spValue <= 1) {
+ return false;
+ }
+ const option = g('option');
+ const enabled = gE('#ckey_spirit[src*="spirit_a"]');
+ if (
+ (!isDisableOnly && option.turnOnSS && checkCondition(option.turnOnSSCondition) && !enabled)
+ ||
+ (option.turnOffSS && checkCondition(option.turnOffSSCondition) && enabled)
+ ) {
+ gE('#ckey_spirit').click();
return true;
}
- }
- return false;
- }
-
- function useInfusions() { // 自动使用魔药
- if (g('attackStatus') === 0) {
- return false;
- }
- if (!g('option').infusionSwitch) {
return false;
}
- if (!checkCondition(g('option').infusionCondition)) {
- return false;
- }
-
- const infusionLib = [null, {
- id: 12101,
- img: 'fireinfusion',
- }, {
- id: 12201,
- img: 'coldinfusion',
- }, {
- id: 12301,
- img: 'elecinfusion',
- }, {
- id: 12401,
- img: 'windinfusion',
- }, {
- id: 12501,
- img: 'holyinfusion',
- }, {
- id: 12601,
- img: 'darkinfusion',
- }];
- if (gE(`.bti3>div[onmouseover*="${infusionLib[g('attackStatus')].id}"]`) && !gE(`#pane_effects>img[src*="${infusionLib[[g('attackStatus')]].img}"]`)) {
- gE(`.bti3>div[onmouseover*="${infusionLib[g('attackStatus')].id}"]`).click();
- return true;
- }
- return false;
- }
- function autoFocus() {
- if (g('option').focus && checkCondition(g('option').focusCondition)) {
- gE('#ckey_focus').click();
- return true;
+ async function clickMonster(id) {
+ if (!unsafeWindow.battle) {
+ console.log('loadUnsafeWindowBattle before click monster');
+ await loadUnsafeWindowBattle();
+ }
+ getMonster(id).click();
}
- return false;
- }
- function autoSS() {
- if ((g('option').turnOnSS && checkCondition(g('option').turnOnSSCondition) && !gE('#ckey_spirit[src*="spirit_a"]')) || (g('option').turnOffSS && checkCondition(g('option').turnOffSSCondition) && gE('#ckey_spirit[src*="spirit_a"]'))) {
- gE('#ckey_spirit').click();
- return true;
- }
- return false;
- }
+ /**
+ * INNAT / WEAPON SKILLS
+ *
+ * 优先释放先天和武器技能
+ */
+ function autoSkill() {
+ const option = g('option');
+ if (!option.skillSwitch) {
+ return false;
+ }
+ if (!gE('#ckey_spirit[src*="spirit_a"]')) {
+ return false;
+ }
- /**
- * INNAT / WEAPON SKILLS
- *
- * 优先释放先天和武器技能
- */
- function autoSkill() {
- if (!g('option').skillSwitch) {
- return false;
- }
- if (!gE('#ckey_spirit[src*="spirit_a"]')) {
+ const skillOrder = splitOrders(option.skillOrderValue, ['OFC', 'FRD', 'T3', 'T2', 'T1']);
+ const fightStyle = g('fightingStyle'); // 1二天 2单手 3双手 4双持 5法杖
+ const skillLib = {
+ OFC: '1111',
+ FRD: '1101',
+ T3: fightStyle ? `2${fightStyle}03` : undefined,
+ T2: fightStyle ? `2${fightStyle}02` : undefined,
+ T1: fightStyle ? `2${fightStyle}01` : undefined,
+ };
+ const skillOC = { // default as 2
+ '1101': 4,
+ '1111': 8,
+ '2101': 4,
+ '2201': 1,
+ '2203': 4,
+ '2403': 3
+ }
+ const rangeSkills = {
+ 2101: 5,
+ 2302: 5,
+ 2303: 5,
+ 2403: 5,
+ // 1101: 20, 全体
+ // 1111: 20,
+ }
+ const optionSkills = option.skill;
+ if (!optionSkills) {
+ return;
+ }
+ const monsterStatus = g('battle').monsterStatus;
+ for (let i in skillOrder) {
+ let skill = skillOrder[i];
+ if (!skill || !optionSkills[skill]) {
+ return;
+ }
+ let id = skillLib[skill];
+ if (!isOn(id)) {
+ continue;
+ }
+ if (g('oc') < (id in skillOC ? skillOC[id] : 2)) {
+ continue;
+ }
+ const skillOTOS = getValue('skillOTOS', true) ?? {};
+ skillOTOS[skill] ??= 0;
+ if (option.skillOTOS && option.skillOTOS[skill] && skillOTOS[skill] >= 1) {
+ continue;
+ }
+ skillOTOS[skill]++;
+ setValue('skillOTOS', skillOTOS);
+ let target = checkCondition(option[`skill${skill}Condition`], monsterStatus);
+ if (!target) {
+ continue;
+ }
+ gE(id).click();
+ clickMonster(getRangeCenter(target, rangeSkills[id] ?? 1).id);
+ return true;
+ }
return false;
}
- const skillOrder = (g('option').skillOrderValue || 'OFC,FRD,T3,T2,T1').split(',');
- const skillLib = {
- OFC: {
- id: '1111',
- oc: 8,
- },
- FRD: {
- id: '1101',
- oc: 4,
- },
- T3: {
- id: `2${g('option').fightingStyle}03`,
- oc: 2,
- },
- T2: {
- id: `2${g('option').fightingStyle}02`,
- oc: 2,
- },
- T1: {
- id: `2${g('option').fightingStyle}01`,
- oc: 2,
- },
- };
- const rangeSkills = {
- 2101: 2,
- 2403: 2,
- 1111: 4,
- }
- const monsterStatus = g('battle').monsterStatus;
- for (let i in skillOrder) {
- let skill = skillOrder[i];
- let range = 0;
- if (!checkCondition(g('option')[`skill${skill}Condition`])) {
- continue;
- }
- if (!isOn(skillLib[skill].id)) {
- continue;
- }
- if (g('oc') < skillLib[skill].oc) {
- continue;
- }
- if (g('option').skillOTOS && g('option').skillOTOS[skill] && g('skillOTOS')[skill] >= 1) {
- continue;
- }
- g('skillOTOS')[skill]++;
- gE(skillLib[skill].id).click();
- if (skillLib[skill].id in rangeSkills) {
- range = rangeSkills[skillLib[skill].id];
- }
- if (!g('option').mercifulBlow || g('option').fightingStyle !== '2' || skill !== 'T3') {
- continue;
- }
- // Merciful Blow
- for (let j = 0; j < monsterStatus.length; j++) {
- if (monsterStatus[j].hpNow / monsterStatus[j].hp < 0.25 && gE(`#mkey_${getMonsterID(monsterStatus[j])} img[src*="wpn_bleed"]`)) {
- gE(`#mkey_${getRangeCenterID(monsterStatus[j])}`).click();
- return true;
+ function useDeSkill() { // 自动施法DEBUFF技能
+ const option = g('option');
+ const monsterStatus = g('battle').monsterStatus;
+ if (!option.debuffSkillSwitch || !checkCondition(option.debuffSkillCondition, monsterStatus)) { // 总开关是否开启
+ return false;
+ }
+
+ // 先处理特殊的 “先给全体上buff”
+ let skillPack = splitOrders(option.debuffSkillOrderAllValue, ['Sle', 'Bl', 'We', 'Si', 'Slo', 'Dr', 'Im', 'MN', 'Co']);
+ for (let i = 0; i < skillPack.length; i++) {
+ if (option[`debuffSkill${skillPack[i]}All`]) { // 是否启用
+ if (checkCondition(option[`debuffSkill${skillPack[i]}AllCondition`], monsterStatus)) { // 检查条件
+ continue;
+ }
}
+ skillPack.splice(i, 1);
+ i--;
}
- }
- gE(`#mkey_${getRangeCenterID(monsterStatus[0])}`).click();
- return true;
- }
+ const toAllCount = skillPack.length;
- function useDeSkill() { // 自动施法DEBUFF技能
- if (!g('option').debuffSkillSwitch) { // 总开关是否开启
- return false;
- }
- // 先处理特殊的 “先给全体上buff”
- let skillPack = ['We', 'Im'];
- for (let i = 0; i < skillPack.length; i++) {
- if (g('option')[`debuffSkill${skillPack[i]}All`]) { // 是否启用
- continue;
+ if (option.debuffSkill) { // 是否有启用的buff(不算两个特殊的)
+ skillPack = skillPack.concat(splitOrders(option.debuffSkillOrderValue, ['Sle', 'Bl', 'We', 'Si', 'Slo', 'Dr', 'Im', 'MN', 'Co']));
}
- if (!checkCondition(g('option')[`debuffSkill${skillPack[i]}AllCondition`])) { // 检查条件
- continue;
+ for (let i in skillPack) {
+ let buff = skillPack[i];
+ const isToAll = i < toAllCount;
+ if (!isToAll) { // 非先全体
+ if (!buff || !option.debuffSkill[buff] || !checkCondition(option[`debuffSkill${buff}Condition`], monsterStatus)) { // 检查条件
+ continue;
+ }
+ }
+ let succeed = useDebuffSkill(buff, isToAll);
+ // 前 toAllCount 个都是先给全体上的
+ if (succeed) {
+ return true;
+ }
}
- skillPack.splice(i, 1);
- i--;
- }
- skillPack.sort((x, y) => g('option').debuffSkillOrderValue.indexOf(x) - g('option').debuffSkillOrderValue.indexOf(y))
- let toAllCount = skillPack.length;
- if (g('option').debuffSkill) { // 是否有启用的buff(不算两个特殊的)
- skillPack = skillPack.concat(g('option').debuffSkillOrderValue.split(','));
+ return false;
}
- for (let i in skillPack) {
- let buff = skillPack[i];
- if (i >= toAllCount && !skillPack[i]) { // 检查buff是否启用
- continue;
+
+ function useDebuffSkill(buff, isAll = false) {
+ const skillLib = {
+ Sle: {
+ name: 'Sleep',
+ id: '222',
+ img: 'sleep',
+ range: { 4207: [1, 1, 2, 3] },
+ },
+ Bl: {
+ name: 'Blind',
+ id: '231',
+ img: 'blind',
+ range: { 4206: [1, 1, 2, 3] },
+ },
+ Slo: {
+ name: 'Slow',
+ id: '221',
+ img: 'slow',
+ range: { 4213: [1, 1, 2, 2, 2, 3] },
+ },
+ Im: {
+ name: 'Imperil',
+ id: '213',
+ img: 'imperil',
+ range: { 4204: [1, 1, 2, 3] },
+ },
+ MN: {
+ name: 'MagNet',
+ id: '233',
+ img: 'magnet',
+ range: { 4212: [1, 1, 1, 2, 2, 3] },
+ },
+ Si: {
+ name: 'Silence',
+ id: '232',
+ img: 'silence',
+ range: { 4211: [1, 1, 2, 3] },
+ },
+ Dr: {
+ name: 'Drain',
+ id: '211',
+ img: 'drainhp',
+ },
+ We: {
+ name: 'Weaken',
+ id: '212',
+ img: 'weaken',
+ range: { 4202: [1, 1, 2, 3] },
+ },
+ Co: {
+ name: 'Confuse',
+ id: '223',
+ img: 'confuse',
+ range: { 4207: [1, 1, 2, 3] },
+ },
+ };
+ if (!isOn(skillLib[buff].id)) { // 技能不可用
+ return false;
+ }
+ // 获取范围
+ let range = 1;
+ let ab;
+ const ability = getValue('ability', true);
+ for (ab in skillLib[buff].range) {
+ const ranges = skillLib[buff].range[ab];
+ if (!ranges) {
+ continue;
+ }
+ range = ranges[ability ? ability[ab] ?? 0 : 0];
+ break;
+ }
+ // 获取目标
+ const option = g('option');
+ const excludedRatio = 0.9
+ let exclusiveBuffs;
+ if (isAll && option.debuffAllExclusive) {
+ exclusiveBuffs = Object.keys(option.debuffAllExclusive);
+ exclusiveBuffs = exclusiveBuffs?.includes(buff) ? exclusiveBuffs : undefined
+ }
+ let isDebuffed = (target, b) => {
+ if (b || !exclusiveBuffs) {
+ const current = getBuffTurnFromImg(getBuff(skillLib[b ?? buff].img, getMonsterID(target)));
+ const threshold = option.debuffSkillThreshold ? option.debuffSkillThreshold[b ?? buff] : 0;
+ return threshold >= 0 && current > threshold;
+ }
+ for (const exclusive of exclusiveBuffs) {
+ if (isDebuffed(target, exclusive)) return excludedRatio;
+ }
+ return 0;
+ };
+ let debuffByIndex = isAll && option[`debuffSkill${buff}AllByIndex`];
+ let monsterStatus = g('battle').monsterStatus;
+ if (debuffByIndex) {
+ monsterStatus = JSON.parse(JSON.stringify(monsterStatus));
+ monsterStatus.sort(objArrSort('order'));
+ }
+ let max = isAll ? monsterStatus.length : 1;
+ let id;
+ let minWeight = Number.MAX_SAFE_INTEGER;
+ const condition = option[`debuffSkill${buff}${isAll ? 'All' : ''}Condition`];target => checkCondition(condition, [target]);
+ const excludeCondition = target => checkCondition(condition, [target]) ? isDebuffed(target) : excludedRatio;
+ for (let i = 0; i < max; i++) {
+ let target = buff === 'Dr' ? monsterStatus[max - i - 1] : monsterStatus[i];
+ target = checkCondition(condition, [target]);
+ if (!target || target.isDead || isDebuffed(target)) {
+ continue;
+ }
+ const center = getRangeCenter(target, range, false, excludeCondition, debuffByIndex);
+ if (!id || center.weight < minWeight) {
+ minWeight = center.weight;
+ id = center.id;
+ if (!isAll) break; // 只有覆盖全体才需要遍历全部
+ }
}
- if (!checkCondition(g('option')[`debuffSkill${buff}Condition`])) { // 检查条件
- continue;
+ if (id === undefined) {
+ return false;
}
- let succeed = useDebuffSkill(skillPack[i], i < toAllCount);
- // 前 toAllCount 个都是先给全体上的
- if (succeed) {
+ const imgs = gE('img', 'all', gE(monsterStateKeys.buffs, getMonster(id)));
+ // 已有buff小于6个
+ // 未开启debuff失败警告
+ // buff剩余持续时间大于等于警报时间
+ if (imgs.length < 6) {
+ gE(skillLib[buff].id).click();
+ clickMonster(id);
return true;
+ } else if (!option.debuffSkillTurnAlert || (option.debuffSkillTurn && getBuffTurnFromImg(imgs[imgs.length - 1]) >= option.debuffSkillTurn[buff])){
+ return false;
}
- }
- return false;
- }
- function useDebuffSkill(buff, isAll = false) {
- const skillLib = {
- Sle: {
- name: 'Sleep',
- id: '222',
- img: 'sleep',
- },
- Bl: {
- name: 'Blind',
- id: '231',
- img: 'blind',
- },
- Slo: {
- name: 'Slow',
- id: '221',
- img: 'slow',
- },
- Im: {
- name: 'Imperil',
- id: '213',
- img: 'imperil',
- range: { 4204: [0, 0, 0, 1] },
- },
- MN: {
- name: 'MagNet',
- id: '233',
- img: 'magnet',
- },
- Si: {
- name: 'Silence',
- id: '232',
- img: 'silence',
- },
- Dr: {
- name: 'Drain',
- id: '211',
- img: 'drainhp',
- },
- We: {
- name: 'Weaken',
- id: '212',
- img: 'weaken',
- range: { 4202: [0, 0, 0, 1] },
- },
- Co: {
- name: 'Confuse',
- id: '223',
- img: 'confuse',
- },
- };
-
- if (!isOn(skillLib[buff].id)) { // 技能不可用
- return false;
- }
- const monsterStatus = g('battle').monsterStatus;
- let isDebuffed = (target) => gE(`img[src*="${skillLib[buff].img}"]`, gE(`#mkey_${getMonsterID(target)}>.btm6`));
- let primaryTarget;
- let max = isAll ? monsterStatus.length : 1;
- for (let i = 0; i < max; i++) {
- let target = buff === 'Dr' ? monsterStatus[max - i - 1] : monsterStatus[i];
- if (monsterStatus[i].isDead) {
- continue;
- }
- if (isDebuffed(target)) { // 检查是否已有该buff
- continue;
- }
- primaryTarget = target;
- break;
- }
- if (primaryTarget === undefined) {
- return false;
+ _alert(0, '无法正常施放DEBUFF技能,请尝试手动打怪', '無法正常施放DEBUFF技能,請嘗試手動打怪', 'Can not cast de-skills normally, continue the script?\nPlease try attack manually.');
+ pauseChange();
+ return true;
}
- let range = 0;
- let ab;
- for (ab in skillLib[buff].range) {
- const ranges = skillLib[buff].range[ab][skillLib[buff].skill * 1];
- if (!ranges) {
- continue;
+ function getCurrentAttackStatus() {
+ if (g('attackStatusCurrent') === undefined) {
+ attack(true);
}
- range = ranges[getValue('ability', true)[ab].level];
- break;
- }
- let id = getRangeCenterID(primaryTarget, range, isDebuffed);
- const imgs = gE('img', 'all', gE(`#mkey_${id}>.btm6`));
- // 已有buff小于6个
- // 未开启debuff失败警告
- // buff剩余持续时间大于等于警报时间
- if (imgs.length < 6 || !g('option').debuffSkillTurnAlert || (g('option').debuffSkillTurn && imgs[imgs.length - 1].getAttribute('onmouseover').match(/\(.*,.*, (.*?)\)$/)[1] * 1 >= g('option').debuffSkillTurn[buff])) {
- gE(skillLib[buff].id).click();
- gE(`#mkey_${id}`).click();
- return true;
+ const current = g('attackStatusCurrent');
+ g('attackStatusCurrent', undefined);
+ return current;
}
- _alert(0, '无法正常施放DEBUFF技能,请尝试手动打怪', '無法正常施放DEBUFF技能,請嘗試手動打怪', 'Can not cast de-skills normally, continue the script?\nPlease try attack manually.');
- pauseChange();
- return true;
- }
-
- function attack() { // 自动打怪
- // 如果
- // 1. 开启了自动以太水龙头
- // 2. 目标怪在魔力合流状态中
- // 3. 未获得以太水龙头*2 或 *1
- // 4. 满足条件
- // 使用物理普通攻击,跳过Offensive Magic
- // 否则按照属性攻击模式释放Spell > Offensive Magic
-
- const updateAbility = {
- 4301: { //火
- 111: [0, 1, 1, 2, 2, 2, 2, 2],
- 112: [0, 0, 2, 2, 2, 2, 3, 3],
- 113: [0, 0, 0, 0, 3, 4, 4, 4]
- },
- 4302: { //冰
- 121: [0, 1, 1, 2, 2, 2, 2, 2],
- 122: [0, 0, 2, 2, 2, 2, 3, 3],
- 123: [0, 0, 0, 0, 3, 4, 4, 4]
- },
- 4303: { //雷
- 131: [0, 1, 1, 2, 2, 2, 2, 2],
- 132: [0, 0, 2, 2, 2, 2, 3, 3],
- 133: [0, 0, 0, 0, 3, 4, 4, 4]
- },
- 4304: { //雷
- 141: [0, 1, 1, 2, 2, 2, 2, 2],
- 142: [0, 0, 2, 2, 2, 2, 3, 3],
- 143: [0, 0, 0, 0, 3, 4, 4, 4]
- },
- //暗
- 4401: { 161: [0, 1, 2] },
- 4402: { 162: [0, 2, 3] },
- 4403: { 163: [0, 3, 4, 4] },
- //圣
- 4501: { 151: [0, 1, 2] },
- 4502: { 152: [0, 2, 3] },
- 4503: { 153: [0, 3, 4, 4] },
+ function attack(selectStatusOnly=false) { // 自动打怪
+ let range = g('fightingStyle') === '1' ? 3 : 1;
+ const option = g('option');
+ const monsters = g('battle').monsterStatus;
+ let attackStatusOrder = option.attackStatusOrderValue?.split(',').map(x=>x*1) ?? [];
+ attackStatusOrder = attackStatusOrder.concat([0,6,5,1,2,4,3].filter(x=> !(attackStatusOrder.includes(x))));
+ if (option.attackStatusSwitch) {
+ for (const status of attackStatusOrder) {
+ if (!option.attackStatusSwitch[status]) continue;
+ const current = g('attackStatusCurrent');
+ g('attackStatusCurrent', status);
+ if (checkCondition(option[`attackStatusSwitchCondition${status}`], monsters) && onAttack(range, status, selectStatusOnly)) {
+ return true;
+ }
+ g('attackStatusCurrent', current);
+ }
+ }
+ g('attackStatusCurrent', 0);
+ return onAttack(range, g('attackStatus'), selectStatusOnly);
}
- let range = 0;
- // Spell > Offensive Magic
- const attackStatus = g('attackStatus');
- const monsterStatus = g('battle').monsterStatus;
- if (attackStatus === 0) {
- if (g('option').fightingStyle === '1') { // 二天一流
- range = 1;
- }
- } else {
- if (g('option').etherTap && gE(`#mkey_${getMonsterID(monsterStatus[0])}>div.btm6>img[src*="coalescemana"]`) && (!gE('#pane_effects>img[onmouseover*="Ether Tap (x2)"]') || gE('#pane_effects>img[src*="wpn_et"][id*="effect_expire"]')) && checkCondition(g('option').etherTapCondition)) {
- `pass`
- }
- else {
- const skill = 1 * (() => {
- let lv = 3;
- for (let condition of [g('option').highSkillCondition, g('option').middleSkillCondition, undefined]) {
- let id = `1${attackStatus}${lv--}`;
- if (checkCondition(condition) && isOn(id)) return id;
- }
- })();
- gE(skill)?.click();
- for (let ab in updateAbility) {
- const ranges = updateAbility[ab][skill];
- if (!ranges) {
- continue;
+ function onAttack(range, attackStatus, selectStatusOnly=false) {
+ const updateAbility = {
+ 4301: { //火
+ 111: [3, 4, 4, 5, 5, 5, 5, 5],
+ 112: [4, 4, 6, 6, 6, 6, 7, 7],
+ 113: [7, 7, 7, 7, 8, 9, 9, 10]
+ },
+ 4302: { //冰
+ 121: [3, 4, 4, 5, 5, 5, 5, 5],
+ 122: [4, 4, 6, 6, 6, 6, 7, 7],
+ 123: [7, 7, 7, 7, 8, 9, 9, 10]
+ },
+ 4303: { //雷
+ 131: [3, 4, 4, 5, 5, 5, 5, 5],
+ 132: [4, 4, 6, 6, 6, 6, 7, 7],
+ 133: [7, 7, 7, 7, 8, 9, 9, 10]
+ },
+ 4304: { //雷
+ 141: [3, 4, 4, 5, 5, 5, 5, 5],
+ 142: [4, 4, 6, 6, 6, 6, 7, 7],
+ 143: [7, 7, 7, 7, 8, 9, 9, 10]
+ },
+ //暗
+ 4401: { 161: [3, 4, 5] },
+ 4402: { 162: [5, 6, 7] },
+ 4403: { 163: [7, 8, 9, 10] },
+ //圣
+ 4501: { 151: [3, 4, 5] },
+ 4502: { 152: [5, 6, 7] },
+ 4503: { 153: [7, 8, 9, 10] },
+ }
+
+ // 如果
+ // 1. 开启了自动以太水龙头
+ // 2. 目标怪在魔力合流状态中
+ // 3. 未获得以太水龙头*2 或 *1
+ // 4. 满足条件
+ // 使用物理普通攻击,跳过Offensive Magic
+ // 否则按照属性攻击模式释放Spell > Offensive Magic
+
+ const option = g('option');
+ const monsters = g('battle').monsterStatus;
+ let target = monsters[0];
+ const tryAttack = (skill) => {
+ if (!target || target.isDead) {
+ return false;
+ }
+ if (selectStatusOnly) {
+ return true;
+ }
+ if (skill) {
+ gE(skill)?.click();
+ }
+ clickMonster(getRangeCenter(target, range, !attackStatus).id);
+ return true;
+ };
+ // 1. physical
+ if (attackStatus === 0) {
+ return tryAttack();
+ }
+
+ // 2. etherTap
+ if (option.etherTap && getBuff('coalescemana', getMonsterID(target))
+ && (!gE('#pane_effects>img[onmouseover*="Ether Tap (x2)"]') || getBuff(`wpn_et"][id*="effect_expire`))
+ && checkCondition(option.etherTapCondition)) {
+ return tryAttack();
+ }
+ // 2.5 try check skill condition
+ const skill = 1 * (() => {
+ let lv = 3;
+ for (let condition of [option.highSkillCondition, option.middleSkillCondition, option.lowSkillCondition]) {
+ let id = `1${attackStatus}${lv--}`;
+ target = checkCondition(condition, monsters);
+ if (target && isOn(id)) {
+ return id;
}
- range = ranges[getValue('ability', true)[ab]?.level ?? 0];
- break;
}
+ })();
+ // 3. no skill available
+ if (!skill) {
+ return tryAttack();
+ }
+ // 4. cast skill
+ for (let ab in updateAbility) {
+ const ranges = updateAbility[ab][skill];
+ if (!ranges) {
+ continue;
+ }
+ const ability = getValue('ability', true);
+ range = ranges[ability ? ability[ab] ?? 0 : 0];
+ break;
+ }
+ if (!tryAttack(skill)) {
+ return false;
}
+ const skillOTOS = getValue('skillOTOS', true) ?? {};
+ skillOTOS[skill] ??= 0;
+ skillOTOS[skill]++;
+ setValue('skillOTOS', skillOTOS);
+ return true;
}
- gE(`#mkey_${getRangeCenterID(monsterStatus[0], range, !attackStatus)}`).click();
- return true;
- }
- function getHPFromMonsterDB(mdb, name, lv) {
- let hp = (mdb && mdb[name]) ? mdb[name][lv] : undefined;
- // TODO: 根据lv模糊推测
- return hp;
- }
+ function getHPFromMonsterDB(mdb, name, lv) {
+ let hp = (mdb && mdb[name]) ? mdb[name][lv] : undefined;
+ // TODO: 根据lv模糊推测
+ return hp;
+ }
- function fixMonsterStatus() { // 修复monsterStatus
- // document.title = _alert(-1, 'monsterStatus错误,正在尝试修复', 'monsterStatus錯誤,正在嘗試修復', 'monsterStatus Error, trying to fix');
- const monsterStatus = [];
- const monsterNames = Array.from(gE('div.btm3>div>div', 'all')).map(monster => monster.innerText);
- const monsterLvs = Array.from(gE('div.btm2>div>div', 'all')).map(monster => monster.innerText);
- const monsterDB = getValue('monsterDB', true);
- gE('div.btm2', 'all').forEach((monster, order) => {
- monsterStatus.push({
- order: order,
- hp: getHPFromMonsterDB(monsterDB, monsterNames[order], monsterLvs[order]) ?? ((monster.style.background === '') ? 1000 : 100000),
+ function fixMonsterStatus() { // 修复monsterStatus
+ // document.title = _alert(-1, 'monsterStatus错误,正在尝试修复', 'monsterStatus錯誤,正在嘗試修復', 'monsterStatus Error, trying to fix');
+ const monsterStatus = [];
+ const monsterNames = Array.from(gE(`${monsterStateKeys.name}>div>div`, 'all')).map(monster => monster.innerText);
+ const monsterLvs = Array.from(gE(`${monsterStateKeys.lv}>div>div`, 'all')).map(monster => monster.innerText);
+ const monsterDB = getValue('monsterDB', true);
+ gE(monsterStateKeys.lv, 'all').forEach((monster, order) => {
+ monsterStatus.push({
+ order: order,
+ hp: getHPFromMonsterDB(monsterDB, monsterNames[order], monsterLvs[order]) ?? ((monster.style.background === '') ? 1000 : 100000),
+ });
});
- });
- const battle = getValue('battle', true);
- battle.monsterStatus = monsterStatus;
- setValue('battle', battle);
- }
+ const battle = getValue('battle', true);
+ battle.monsterStatus = monsterStatus;
+ setValue('battle', battle);
+ }
- function displayMonsterWeight() {
+ function displayMonsterWeight() {
- const status = g('battle').monsterStatus.filter(m => !m.isDead);
- let rank = 0;
+ const status = g('battle').monsterStatus.filter(m => !m.isDead);
+ let rank = 0;
- const weights = [];
- status.forEach(s => {
- if (weights.indexOf(s.finWeight) !== -1) {
- return;
+ const weights = [];
+ status.forEach(s => {
+ if (weights.indexOf(s.finWeight) !== -1) {
+ return;
+ }
+ weights.push(s.finWeight);
+ })
+ const sec = Math.max(1, weights.length - 1);
+ const max = 360 * 2 / 3;
+ const colorTextList = [];
+ const weightBG = g('option').weightBackground
+ if (weightBG) {
+ status.forEach(s => {
+ const rank = weights.indexOf(s.finWeight);
+ let colorText = (weightBG[rank + 1] ?? [])[0];
+ colorTextList[rank] = colorText;
+ });
}
- weights.push(s.finWeight);
- })
- const sec = Math.max(1, weights.length - 1);
- const max = 360 * 2 / 3;
- const colorTextList = [];
- if (g('option').weightBackground) {
status.forEach(s => {
const rank = weights.indexOf(s.finWeight);
- let colorText = (g('option').weightBackground[rank + 1] ?? [])[0];
- colorTextList[rank] = colorText;
- });
- }
- status.forEach(s => {
- const rank = weights.indexOf(s.finWeight);
- const id = getMonsterID(s);
- if (!gE(`#mkey_${id}`) || !gE(`#mkey_${id}>.btm3`)) {
- return;
- }
- if (g('option').displayWeightBackground) {
- if (g('option').weightBackground) {
+ const id = getMonsterID(s);
+ if (!getMonster(id) || !gE(monsterStateKeys.name, getMonster(id))) {
+ return;
+ }
+ if (g('option').displayWeightBackground && weightBG) {
let colorText = colorTextList[rank];
let remainAttemp = 10; // 避免无穷递归
- while(remainAttemp > 0 && colorText && colorText.indexOf(``, colorTextList[i]);
+ while (remainAttemp > 0 && colorText && colorText.indexOf(``, colorTextList[i]);
}
remainAttemp--;
}
try {
colorText = eval(colorText.replace('', rank).replace('', weights.length));
}
- catch {
- }
- gE(`#mkey_${id}`).style.cssText += `background: ${colorText};`;
+ catch { }
+ getMonster(id).style.cssText += `background: ${colorText};`;
}
- }
- gE(`#mkey_${id}>.btm3`).style.cssText += 'display: flex; flex-direction: row;'
- if (g('option').displayWeight) {
- gE(`#mkey_${id}>.btm3`).innerHTML += `[${rank}|-${-rank + weights.length - 1}|${s.finWeight.toPrecision(s.finWeight >= 1 ? 5 : 4)}]
`;
- }
- });
- }
+ gE(monsterStateKeys.name, getMonster(id)).style.cssText += 'display: flex; flex-direction: row;'
+ if (g('option').displayWeight) {
+ gE(monsterStateKeys.name, getMonster(id)).innerHTML += `[${rank}|-${-rank + weights.length - 1}|${s.finWeight.toPrecision(s.finWeight >= 1 ? 5 : 4)}]
`;
+ }
+ });
+ }
- function displayPlayStatePercentage() {
- const barHP = gE('#vbh') ?? gE('#dvbh');
- const barMP = gE('#vbm') ?? gE('#dvbm');
- const barSP = gE('#vbs') ?? gE('#dvbs');
- const barOC = gE('#dvbc');
- const textHP = gE('#vrhd') ?? gE('#dvrhd');
- const textMP = gE('#vrm') ?? gE('#dvrm');
- const textSP = gE('#vrs') ?? gE('#dvrs');
- const textOC = gE('#dvrc');
-
- const percentages = [barHP, barMP, barSP, barOC].filter(bar => bar).map(bar => Math.floor((gE('div>img', bar).offsetWidth / bar.offsetWidth) * 100));
- [textHP, textMP, textSP, textOC].filter(bar => bar).forEach((text, i) => {
- const value = text.innerHTML * 1;
- const percentage = value ? percentages[i] : 0;
- const inner = `[${percentage.toString()}%]`;
- const percentageDiv = gE('div', text);
- if (percentageDiv) {
- percentageDiv.innerHTML = inner;
- return;
- }
- text.innerHTML += `${inner}
`
- });
- }
+ `
+ const inner = `[${percentages[i].toString()}%]`;
+ if (percentageDiv) {
+ percentageDiv.innerHTML = inner;
+ percentageDiv.style.cssText = style;
+ return;
+ }
+ text.innerHTML += `${inner}
`
+ });
+ }
- function dropMonitor(battleLog) { // 掉落监测
- const drop = getValue('drop', true) || {
- '#startTime': time(3),
- '#EXP': 0,
- '#Credit': 0,
- };
- let item; let name; let amount; let
- regexp;
- for (let i = 0; i < battleLog.length; i++) {
- if (/^You gain \d+ (EXP|Credit)/.test(battleLog[i].textContent)) {
- regexp = battleLog[i].textContent.match(/^You gain (\d+) (EXP|Credit)/);
- if (regexp) {
- drop[`#${regexp[2]}`] += regexp[1] * 1;
- }
- } else if (gE('span', battleLog[i])) {
- item = gE('span', battleLog[i]);
- name = item.textContent.match(/^\[(.*?)\]$/)[1];
- if (item.style.color === 'rgb(255, 0, 0)') {
- const quality = ['Crude', 'Fair', 'Average', 'Superior', 'Exquisite', 'Magnificent', 'Legendary', 'Peerless'];
- for (let j = g('option').dropQuality; j < quality.length; j++) {
- if (name.match(quality[j])) {
- name = `Equipment of ${name.match(/^\w+/)[0]}`;
- drop[name] = (name in drop) ? drop[name] + 1 : 1;
- break;
- }
- }
- } else if (item.style.color === 'rgb(186, 5, 180)') {
- regexp = name.match(/^(\d+)x (Crystal of \w+)$/);
+ function dropMonitor(battleLog) { // 掉落监测
+ const drop = getValue('drop', true) || {
+ '#startTime': time(3),
+ '#EXP': 0,
+ '#Credit': 0,
+ };
+ let item, name, amount, regexp;
+ for (let i = 0; i < battleLog.length; i++) {
+ if (/^You gain \d+ (EXP|Credit)/.test(battleLog[i].textContent)) {
+ regexp = battleLog[i].textContent.match(/^You gain (\d+) (EXP|Credit)/);
if (regexp) {
- name = regexp[2];
- amount = regexp[1] * 1;
+ drop[`#${regexp[2]}`] += regexp[1] * 1;
+ }
+ } else if (gE('span', battleLog[i])) {
+ item = gE('span', battleLog[i]);
+ name = item.textContent.match(/^\[(.*?)\]$/)[1];
+ if (item.style.color === 'rgb(255, 0, 0)') {
+ const quality = ['Crude', 'Fair', 'Average', 'Superior', 'Exquisite', 'Magnificent', 'Legendary', 'Peerless'];
+ for (let j = g('option').dropQuality; j < quality.length; j++) {
+ if (name.match(quality[j])) {
+ name = `Equipment of ${name.match(/^\w+/)[0]}`;
+ drop[name] = (name in drop) ? drop[name] + 1 : 1;
+ break;
+ }
+ }
+ } else if (item.style.color === 'rgb(186, 5, 180)') {
+ regexp = name.match(/^(\d+)x (Crystal of \w+)$/);
+ if (regexp) {
+ name = regexp[2];
+ amount = regexp[1] * 1;
+ } else {
+ name = name.match(/^(Crystal of \w+)$/)[1];
+ amount = 1;
+ }
+ drop[name] = (name in drop) ? drop[name] + amount : amount;
+ } else if (item.style.color === 'rgb(168, 144, 0)') {
+ drop['#Credit'] = drop['#Credit'] + name.match(/\d+/)[0] * 1;
} else {
- name = name.match(/^(Crystal of \w+)$/)[1];
- amount = 1;
+ drop[name] = (name in drop) ? drop[name] + 1 : 1;
}
- drop[name] = (name in drop) ? drop[name] + amount : amount;
- } else if (item.style.color === 'rgb(168, 144, 0)') {
- drop['#Credit'] = drop['#Credit'] + name.match(/\d+/)[0] * 1;
- } else {
- drop[name] = (name in drop) ? drop[name] + 1 : 1;
+ } else if (battleLog[i].textContent === 'You are Victorious!') {
+ break;
}
- } else if (battleLog[i].textContent === 'You are Victorious!') {
- break;
+ }
+ const battle = g('battle');
+ if (g('option').recordEach && battle.roundNow === battle.roundAll) {
+ const old = getValue('dropOld', true) || [];
+ drop.__name = getValue('battleCode', true).name;
+ drop['#endTime'] = time(3);
+ old.push(drop);
+ setValue('dropOld', old);
+ delValue('drop');
+ } else {
+ setValue('drop', drop);
}
}
- const battle = g('battle');
- if (g('option').recordEach && battle.roundNow === battle.roundAll) {
- const old = getValue('dropOld', true) || [];
- drop.__name = getValue('battleCode');
- drop['#endTime'] = time(3);
- old.push(drop);
- setValue('dropOld', old);
- delValue('drop');
- } else {
- setValue('drop', drop);
- }
- }
- function recordUsage(parm) {
- const stats = getValue('stats', true) || {
- self: {
- _startTime: time(3),
- _turn: 0,
- _round: 0,
- _battle: 0,
- _monster: 0,
- _boss: 0,
- evade: 0,
- miss: 0,
- focus: 0,
- },
- restore: { // 回复量
- },
- items: { // 物品使用次数
- },
- magic: { // 技能使用次数
- },
- damage: { // 技能攻击造成的伤害
- },
- hurt: { // 受到攻击造成的伤害
- mp: 0,
- oc: 0,
- _avg: 0,
- _count: 0,
- _total: 0,
- _mavg: 0,
- _mcount: 0,
- _mtotal: 0,
- _pavg: 0,
- _pcount: 0,
- _ptotal: 0,
- },
- proficiency: { // 熟练度
- },
- };
- let text; let magic; let point; let
- reg;
- const battle = g('battle');
- if (g('monsterAlive') === 0) {
- stats.self._turn += battle.turn;
- stats.self._round += 1;
- if (battle.roundNow === battle.roundAll) {
- stats.self._battle += 1;
+ function matchDamageInfoFromLogText(text, isSkipUnmatched = true) {
+ const regList = [
+ /you for (\d+) (\w+) damage/,
+ /and take (\d+) (\w+) damage/,
+ /You take (\d+) (\w+) damage/,
+ /hits you, causing (\d+) points of (\w+) damage/
+ ];
+ for (let reg of regList) {
+ let match = text.match(reg);
+ if (!match) {
+ continue;
+ }
+ return match;
+ }
+ if (!isSkipUnmatched) {
+ console.log(`Can't match damage info from: `, text);
}
}
- if (parm.mode === 'magic') {
- magic = parm.magic;
- stats.magic[magic] = (magic in stats.magic) ? stats.magic[magic] + 1 : 1;
- stats.hurt.mp += parm.mp;
- stats.hurt.oc += parm.oc;
- } else if (parm.mode === 'items') {
- stats.items[parm.item] = (parm.item in stats.items) ? stats.items[parm.item] + 1 : 1;
- } else {
- stats.self[parm.mode] = (parm.mode in stats.self) ? stats.self[parm.mode] + 1 : 1;
- }
- const debug = false;
- let log = false;
- for (let i = 0; i < parm.log.length; i++) {
- if (parm.log[i].className === 'tls') {
- break;
+
+ function recordUsage(parm) {
+ const filter = g('option').record;
+ if (!filter) {
+ return;
}
- text = parm.log[i].textContent;
- if (debug) {
- console.log(text);
- }
- if (text.match(/you for \d+ \w+ damage/)) {
- reg = text.match(/you for (\d+) (\w+) damage/);
- magic = reg[2].replace('ing', '');
- point = reg[1] * 1;
- stats.hurt[magic] = (magic in stats.hurt) ? stats.hurt[magic] + point : point;
- stats.hurt._count++;
- stats.hurt._total += point;
- stats.hurt._avg = Math.round(stats.hurt._total / stats.hurt._count);
- if (magic.match(/pierc|crush|slash/)) {
- stats.hurt._pcount++;
- stats.hurt._ptotal += point;
- stats.hurt._pavg = Math.round(stats.hurt._ptotal / stats.hurt._pcount);
- } else {
- stats.hurt._mcount++;
- stats.hurt._mtotal += point;
- stats.hurt._mavg = Math.round(stats.hurt._mtotal / stats.hurt._mcount);
- }
- } else if (text.match(/^[\w ]+ [a-z]+s [\w+ -]+ for \d+( .*)? damage/) || text.match(/^You .* for \d+ .* damage/)) {
- reg = text.match(/for (\d+)( .*)? damage/);
- magic = text.match(/^[\w ]+ [a-z]+s [\w+ -]+ for/) ? text.match(/^([\w ]+) [a-z]+s [\w+ -]+ for/)[1].replace(/^Your /, '') : text.match(/^You (\w+)/)[1];
- point = reg[1] * 1;
- stats.damage[magic] = (magic in stats.damage) ? stats.damage[magic] + point : point;
- } else if (text.match(/Vital Theft hits .*? for \d+ damage/)) {
- magic = 'Vital Theft';
- point = text.match(/Vital Theft hits .*? for (\d+) damage/)[1] * 1;
- stats.damage[magic] = (magic in stats.damage) ? stats.damage[magic] + point : point;
- } else if (text.match(/You (evade|parry|block) the attack|misses the attack against you|(casts|uses) .* misses the attack/)) {
- stats.self.evade++;
- } else if (text.match(/(resists your spell|Your spell is absorbed|(evades|parries) your (attack|spell))|Your attack misses its mark|Your spell fails to connect/)) {
- stats.self.miss++;
- } else if (text.match(/You gain the effect Focusing/)) {
- stats.self.focus++;
- } else if (text.match(/^Recovered \d+ points of/) || text.match(/You are healed for \d+ Health Points/) || text.match(/You drain \d+ HP from/)) {
- magic = (parm.mode === 'defend') ? 'defend' : text.match(/You drain \d+ HP from/) ? 'drain' : parm.magic || parm.item;
- point = text.match(/\d+/)[0] * 1;
- stats.restore[magic] = (magic in stats.restore) ? stats.restore[magic] + point : point;
- } else if (text.match(/(restores|drain) \d+ points of/)) {
- reg = text.match(/^(.*) restores (\d+) points of (\w+)/) || text.match(/^You (drain) (\d+) points of (\w+)/);
- magic = reg[1];
- point = reg[2] * 1;
- stats.restore[magic] = (magic in stats.restore) ? stats.restore[magic] + point : point;
- } else if (text.match(/absorbs \d+ points of damage from the attack into \d+ points of \w+ damage/)) {
- reg = text.match(/(.*) absorbs (\d+) points of damage from the attack into (\d+) points of (\w+) damage/);
- point = reg[2] * 1;
- magic = parm.log[i - 1].textContent.match(/you for (\d+) (\w+) damage/)[2].replace('ing', '');
- stats.hurt[magic] = (magic in stats.hurt) ? stats.hurt[magic] + point : point;
- point = reg[3] * 1;
- magic = `${reg[1].replace('Your ', '')}_${reg[4]}`;
- stats.hurt[magic] = (magic in stats.hurt) ? stats.hurt[magic] + point : point;
- } else if (text.match(/You gain .* proficiency/)) {
- reg = text.match(/You gain ([\d.]+) points of (.*?) proficiency/);
- magic = reg[2];
- point = reg[1] * 1;
- stats.proficiency[magic] = (magic in stats.proficiency) ? stats.proficiency[magic] + point : point;
- stats.proficiency[magic] = stats.proficiency[magic].toFixed(3) * 1;
- } else if (text.trim() === '' || text.match(/You (gain |cast |use |are Victorious|have reached Level|have obtained the title|do not have enough MP)/) || text.match(/Cooldown|has expired|Spirit Stance|gains the effect|insufficient Spirit|Stop beating dead ponies| defeat |Clear Bonus|brink of defeat|Stop \w+ing|Spawned Monster| drop(ped|s) |defeated/)) {
- // nothing;
- } else if (debug) {
- log = true;
- setAudioAlarm('Error');
- console.log(text);
+ const stats = getValue('stats', true) || {};
+ stats.self ??= { _startTime: time(3) };
+ stats.self._turn = filter.turn ? stats.self._turn ?? 0 : undefined;
+ stats.self._round = filter.round ? stats.self._round ?? 0 : undefined;
+ stats.self._battle = filter.battle ? stats.self._battle ?? 0 : undefined;
+ stats.self._monster = filter.monster ? stats.self._monster ?? 0 : undefined;
+ stats.self._boss = filter.boss ? stats.self._boss ?? 0 : undefined;
+ stats.self.evade = filter.evade ? stats.self.evade ?? 0 : undefined;
+ stats.self.miss = filter.miss ? stats.self.miss ?? 0 : undefined;
+ stats.self.focus = filter.focus ? stats.self.focus ?? 0 : undefined;
+ stats.self.mp = filter.mp ? stats.self.mp ?? 0 : undefined;
+ stats.self.oc = filter.oc ? stats.self.oc ?? 0 : undefined;
+ stats.restore = filter.restore ? stats.restore ?? {} : undefined; // 回复量
+ stats.items = filter.items ? stats.items ?? {} : undefined; // 物品使用次数
+ stats.magic = filter.magic ? stats.magic ?? {} : undefined; // 技能使用次数
+ stats.damage = filter.damage ? stats.damage ?? {} : undefined; // 技能攻击造成的伤害
+ stats.proficiency = filter.proficiency ? stats.proficiency ?? {} : undefined; // 熟练度
+ stats.hurt = filter.hurt ? stats.hurt ?? {} : undefined; // 受到攻击造成的伤害
+ if (filter.hurt) {
+ stats.hurt._avg = filter.hurtavg ? stats.hurt._avg ?? 0 : undefined;
+ stats.hurt._count = filter.hurtcount ? stats.hurt._count ?? 0 : undefined;
+ stats.hurt._total = filter.hurttotal ? stats.hurt._total ?? 0 : undefined;
+ stats.hurt._mavg = filter.hurtmavg ? stats.hurt._mavg ?? 0 : undefined;
+ stats.hurt._mcount = filter.hurtmcount ? stats.hurt._mcount ?? 0 : undefined;
+ stats.hurt._mtotal = filter.hurtmtotal ? stats.hurt._mtotal ?? 0 : undefined;
+ stats.hurt._pavg = filter.hurtpavg ? stats.hurt._pavg ?? 0 : undefined;
+ stats.hurt._pcount = filter.hurtpcount ? stats.hurt._pcount ?? 0 : undefined;
+ stats.hurt._ptotal = filter.hurtptotal ? stats.hurt._ptotal ?? 0 : undefined;
+ }
+ let text, magic, point, reg;
+ const battle = g('battle');
+ if (g('monsterAlive') === 0) {
+ if (filter.turn) {
+ stats.self._turn += battle.turn;
+ }
+ if (filter.round) {
+ stats.self._round += 1;
+ }
+ if (filter.battle) {
+ if (battle.roundNow === battle.roundAll) {
+ stats.self._battle += 1;
+ }
+ }
}
+ if (parm.mode === 'magic') {
+ magic = parm.magic;
+ if (filter.magic) {
+ stats.magic[magic] = (magic in stats.magic) ? stats.magic[magic] + 1 : 1;
+ }
+ if (filter.mp) {
+ stats.self.mp += parm.mp;
+ }
+ if (filter.oc) {
+ stats.self.oc += parm.oc;
+ }
+ } else if (parm.mode === 'items') {
+ if (filter.items) {
+ stats.items[parm.item] = (parm.item in stats.items) ? stats.items[parm.item] + 1 : 1;
+ }
+ } else {
+ if (filter[parm.mode]) {
+ stats.self[parm.mode] = (parm.mode in stats.self) ? stats.self[parm.mode] + 1 : 1;
+ }
+ }
+
+ const debug = false;
+ let log = false;
+ for (let i = 0; i < parm.log.length; i++) {
+ if (parm.log[i].className === 'tls') {
+ break;
+ }
+ text = parm.log[i].textContent;
+ if (debug) {
+ console.log(text);
+ }
+ if (reg = matchDamageInfoFromLogText(text)) {
+ magic = reg[2].replace('ing', '');
+ point = reg[1] * 1;
+ if (filter.hurt) {
+ stats.hurt[magic] = (magic in stats.hurt) ? stats.hurt[magic] + point : point;
+ if (filter.hurtcount || filter.hurtavg) {
+ stats.hurt._count++;
+ }
+ if (filter.hurttotal || filter.hurtavg) {
+ stats.hurt._total += point;
+ }
+ if (filter.hurtavg) {
+ stats.hurt._avg = Math.round(stats.hurt._total / stats.hurt._count);
+ }
+ if (magic.match(/pierc|crush|slash/)) {
+ if (filter.hurtpcount || filter.hurtpavg) {
+ stats.hurt._pcount++;
+ }
+ if (filter.hurtptotal || filter.hurtpavg) {
+ stats.hurt._ptotal += point;
+ }
+ if (filter.hurtpavg) {
+ stats.hurt._pavg = Math.round(stats.hurt._ptotal / stats.hurt._pcount);
+ }
+ } else {
+ if (filter.hurtmcount || filter.hurtmavg) {
+ stats.hurt._mcount++;
+ }
+ if (filter.hurtmtotal || filter.hurtmavg) {
+ stats.hurt._mtotal += point;
+ }
+ if (filter.hurtmavg) {
+ stats.hurt._mavg = Math.round(stats.hurt._mtotal / stats.hurt._mcount);
+ }
+ }
+ }
+ if (filter.evade && text.match(/You ((partially )*(evade|parry|block)( and )*)+ the attack/)) {
+ stats.self.evade++;
+ }
+ } else if (text.match(/^[\w ]+ [a-z]+s [\w+ -]+ for \d+( .*)? damage/) || text.match(/^You .* for \d+ .* damage/)) {
+ if (filter.damage) {
+ reg = text.match(/for (\d+)( .*)? damage/);
+ magic = text.match(/^[\w ]+ [a-z]+s [\w+ -]+ for/) ? text.match(/^([\w ]+) [a-z]+s [\w+ -]+ for/)[1].replace(/^Your /, '') : text.match(/^You (\w+)/)[1];
+ point = reg[1] * 1;
+ stats.damage[magic] = (magic in stats.damage) ? stats.damage[magic] + point : point;
+ }
+ } else if (text.match(/Vital Theft hits .*? for \d+ damage/)) {
+ if (filter.damage) {
+ magic = 'Vital Theft';
+ point = text.match(/Vital Theft hits .*? for (\d+) damage/)[1] * 1;
+ stats.damage[magic] = (magic in stats.damage) ? stats.damage[magic] + point : point;
+ }
+ } else if (text.match(/You (evade|parry|block) the attack|misses the attack against you|(casts|uses) .* misses the attack/)) {
+ if (filter.evade) {
+ stats.self.evade++;
+ }
+ } else if (text.match(/(resists your spell|Your spell is absorbed|(evades|parries) your (attack|spell))|Your attack misses its mark|Your spell fails to connect/)) {
+ if (filter.miss) {
+ stats.self.miss++;
+ }
+ } else if (text.match(/You gain the effect Focusing/)) {
+ if (filter.focus) {
+ stats.self.focus++;
+ }
+ } else if (text.match(/^Recovered \d+ points of/) || text.match(/You are healed for \d+ Health Points/) || text.match(/You drain \d+ HP from/)) {
+ if (filter.restore) {
+ magic = (parm.mode === 'defend') ? 'defend' : text.match(/You drain \d+ HP from/) ? 'drain' : parm.magic || parm.item;
+ point = text.match(/\d+/)[0] * 1;
+ stats.restore[magic] = (magic in stats.restore) ? stats.restore[magic] + point : point;
+ }
+ } else if (text.match(/(restores|drain) \d+ points of/)) {
+ if (filter.restore) {
+ reg = text.match(/^(.*) restores (\d+) points of (\w+)/) || text.match(/^You (drain) (\d+) points of (\w+)/);
+ magic = reg[1];
+ point = reg[2] * 1;
+ stats.restore[magic] = (magic in stats.restore) ? stats.restore[magic] + point : point;
+ }
+ } else if (text.match(/absorbs \d+ points of damage from the attack into \d+ points of \w+ damage/)) {
+ if (filter.hurt) {
+ reg = text.match(/(.*) absorbs (\d+) points of damage from the attack into (\d+) points of (\w+) damage/);
+ point = reg[2] * 1;
+ magic = matchDamageInfoFromLogText(parm.log[i - 1].textContent, false)[2].replace('ing', '');
+ stats.hurt[magic] = (magic in stats.hurt) ? stats.hurt[magic] + point : point;
+ point = reg[3] * 1;
+ magic = `${reg[1].replace('Your ', '')}_${reg[4]}`;
+ stats.hurt[magic] = (magic in stats.hurt) ? stats.hurt[magic] + point : point;
+ }
+ } else if (text.match(/You gain .* proficiency/)) {
+ if (filter.proficiency) {
+ reg = text.match(/You gain ([\d.]+) points of (.*?) proficiency/);
+ magic = reg[2];
+ point = reg[1] * 1;
+ stats.proficiency[magic] = (magic in stats.proficiency) ? stats.proficiency[magic] + point : point;
+ stats.proficiency[magic] = stats.proficiency[magic].toFixed(3) * 1;
+ }
+ } else if (text.trim() === '' || text.match(/You (gain |cast |use |are Victorious|have reached Level|have obtained the title|do not have enough MP)/) || text.match(/Cooldown|has expired|Spirit Stance|gains the effect|insufficient Spirit|Stop beating dead ponies| defeat |Clear Bonus|brink of defeat|Stop \w+ing|Spawned Monster| drop(ped|s) |defeated/)) {
+ // nothing;
+ } else if (debug) {
+ log = true;
+ setAudioAlarm('Error');
+ console.log(text);
+ }
+ }
+ if (debug && log) {
+ console.table(stats);
+ pauseChange();
+ }
+ setValue('stats', stats);
}
- if (debug && log) {
- console.table(stats);
- pauseChange();
- }
- setValue('stats', stats);
- }
- function recordUsage2() {
- const stats = getValue('stats', true);
- stats.self._monster += g('monsterAll');
- stats.self._boss += g('bossAll');
- const battle = g('battle');
- if (g('option').recordEach && battle.roundNow === battle.roundAll) {
- const old = getValue('statsOld', true) || [];
- stats.__name = getValue('battleCode');
- stats.self._endTime = time(3);
- old.push(stats);
- setValue('statsOld', old);
- delValue('stats');
- } else {
+ function recordUsage2() {
+ const filter = g('option').record;
+ if (!filter) {
+ return;
+ }
+ const stats = getValue('stats', true);
+ if (filter.monster) {
+ stats.self._monster += g('monsterAll');
+ }
+ if (filter.boss) {
+ stats.self._boss += g('bossAll');
+ }
+ const battle = g('battle');
+ if (g('option').recordEach && battle.roundNow === battle.roundAll) {
+ const old = getValue('statsOld', true) || [];
+ stats.__name = getValue('battleCode', true).name;
+ stats.self._endTime = time(3);
+ old.push(stats);
+ setValue('statsOld', old);
+ delValue('stats');
+ return;
+ }
setValue('stats', stats);
}
+ } catch (err) {
+ console.error(err);
+ document.title = err;
}
-} catch (e) {
- console.log(e);
- document.title = e;
-}
+})();