import WuhuBase from "../WuhuBase"; import WuhuConfig from "../WuhuConfig"; import CommonUtils from "../utils/CommonUtils"; import Log from "../Log"; import Alert from "../utils/Alert"; import Global from "../Global"; import Device from "../../enum/Device"; import ATTACK_HELPER_CSS from "../../static/css/attack_helper.css"; import ActionButtonUtils from "../utils/ActionButtonUtils"; import TornStyleBlock from "../utils/TornStyleBlock"; import TornStyleSwitch from "../utils/TornStyleSwitch"; import DialogMsgBox from "../utils/DialogMsgBox"; import FetchUtils from "../utils/FetchUtils"; import MathUtils from "../utils/MathUtils"; import LoopHelper from "../utils/LoopHelper"; import TRAVEL_STATE from "../../enum/TravelState"; enum FIGHT_STAGE { READY = 'ready', IN_PROGRESS_OR_ERROR = 'in_progress_or_error', FINISHED = 'finished', END = 'end', OTHER = 'other' } /** * 战斗助手 * TODO 页面加载已经在进行中的战斗时的正确判断 */ export default class AttackHelper extends WuhuBase { className = 'AttackHelper'; private currentStage: FIGHT_STAGE = FIGHT_STAGE.OTHER; constructor() { super(); window.setTimeout(() => this.urlMatch(), 0); } private urlMatch(): void { if (window.location.href.contains(/loader\.php\?sid=attack/)) { this.fightingPageHandle(); } // 错误的攻击页面转跳 else if (window.location.href.includes('loader2.php') && WuhuConfig.get('attRelocate')) { const spl = window.location.href.trim().split('='); const uid = spl[spl.length - 1]; if (CommonUtils.getInstance().isValidUid(uid)) { window.location.href = 'https://www.torn.com/loader.php?sid=attack&user2ID=' + uid; } else { Log.error('[AttackHelper] UID格式不正确'); } } } private fightingPageHandle(): void { // 光速刷新按钮 ActionButtonUtils.getInstance().add('光速刷新', () => this.doAttackReload()); // 盯梢 this.watchTarget(); new MutationObserver((_, observer) => { let btnList = document.querySelectorAll('div[class^="dialogButtons___"] button') as NodeListOf; if (btnList.length === 0) { if (this.currentStage === FIGHT_STAGE.READY && WuhuConfig.get('quickFinishAtt') === 3) { document.body.classList.remove('wh-move-btn'); Log.info('移除body class wh-move-btn'); observer.disconnect(); } // 错误或正在打 this.currentStage = FIGHT_STAGE.IN_PROGRESS_OR_ERROR; Log.info('[attackHelper] currentStage', this.currentStage); return; } btnList.forEach(btn => { let btnText = btn.innerText.toLowerCase(); if (btnText.includes('start') || btnText.includes('join')) { // 开始 this.quickStartFight(); } else if (btnText.includes('continue')) { // 结束end this.currentStage = FIGHT_STAGE.END; observer.disconnect(); } else if (btnText.includes('leave')) { // 无意识状态FINISHED this.quickFinishFight(btnList); } Log.info('[attackHelper] currentStage', this.currentStage); }) }) .observe(document.querySelector('#react-root'), { childList: true, subtree: true }); } // 战斗页面快速刷新 private doAttackReload(): void { if (!window.ReactDOM) { new Alert('光速刷新失败:未找到React对象'); Log.error('光速刷新失败:未找到React对象'); return; } if (!document.querySelector('#react-root #attacker')) { Log.error('dom元素未找到selector: [#react-root #attacker]'); return; } let script = document.querySelector('script[src*="/builds/attack/"]'); let url = script.src; if (!url.contains(/runtime\..+\.js/)) { Log.error('脚本源[' + url + '] 不匹配规则'); return; } window.ReactDOM.unmountComponentAtNode(document.querySelector('#react-root')); script.remove(); let node = document.createElement('script'); node.src = url; node.type = 'text/javascript'; document.head.appendChild(node); } // 光速拔刀 private quickStartFight(): void { if (this.currentStage === FIGHT_STAGE.READY) { return; } else { this.currentStage = FIGHT_STAGE.READY; } if (WuhuConfig.get('quickAttIndex') === 6) return; /** * pc #defender * mobile #attacker */ const btn = (document.querySelector('#attacker button') || document.querySelector('#defender button')); Log.info('操作按钮', { btn }); if (!btn.innerText.toLowerCase().includes('fight')) { Log.info('未找到攻击按钮, 光速拔刀跳过'); new Alert('未找到攻击按钮, 光速拔刀跳过'); } else { // 判断是否存在脚踢 const hasKick = !!document.querySelector('#weapon_boots'); // modal层 // const modal: HTMLElement = document.querySelector('div[class^="modal___"]'); let device = Global.getInstance().device; Log.info(`当前设备类型是${ device }`); // 区分设备 switch (device) { case Device.PC: { Log.info(`开始调整按钮位置`); // 隐藏modal层 // modal.style.display = 'none'; // 根据选择的武器调整css let css_top = '0'; switch (WuhuConfig.get('quickAttIndex')) { // weapon_second case 1: { css_top = '97px'; break; } // weapon_melee case 2: { css_top = '194px'; break; } // weapon_temp case 3: { css_top = '291px'; break; } // weapon_fists case 4: // weapon_boots case 5: { css_top = '375px'; break; } } CommonUtils.addStyle(ATTACK_HELPER_CSS); CommonUtils.addStyle(`.wh-move-btn #defender div[class^="modal___"]{top: ${ css_top };}`); document.body.classList.add('wh-move-btn'); break; } case Device.MOBILE: { Log.info(`开始调整按钮位置`); // 加入css let css_top = '0'; let slot_height = '76px'; // 判断有没有脚踢 if (hasKick) { // 根据选择的武器调整 switch (WuhuConfig.get('quickAttIndex')) { case 1: { // weapon_second css_top = '76px'; break; } case 2: { // weapon_melee css_top = '152px'; break; } case 3: { // weapon_temp css_top = '228px'; break; } case 4: { // weapon_fists css_top = '304px'; break; } case 5: { // weapon_boots css_top = '380px'; break; } } } else { const slot = document.querySelector('#weapon_main') as HTMLElement; const height = slot.offsetHeight + 1; // TODO 待验证 slot_height = height + 'px'; // 根据选择的武器调整 switch (WuhuConfig.get('quickAttIndex')) { case 1: { // weapon_second css_top = `${ height }px`; break; } case 2: { // weapon_melee css_top = `${ height * 2 }px`; break; } case 3: { // weapon_temp css_top = `${ height * 3 }px`; break; } case 4: { // weapon_fists css_top = `${ height * 4 }px`; break; } case 5: { // weapon_boots css_top = `${ height * 5 }px`; break; } } } const css_rule = ATTACK_HELPER_CSS.replace('CSSVAR', css_top).replace('CSSVAR', slot_height); // ` // .wh-move-btn #attacker div[class^="modal___"]{display: block;width: 0;top: ${ css_top };left:0;height:0;} // .wh-move-btn #attacker div[class^="dialog___"]{border:0;width:80px;height:${ slot_height };} // .wh-move-btn #attacker div[class^="colored___"]{display:block;padding:0;} // .wh-move-btn #attacker div[class^="title___"]{height:0;} // .wh-move-btn #attacker button{width:100%;margin:0;height:63px;white-space:normal;} // `; CommonUtils.addStyle(css_rule); document.body.classList.toggle('wh-move-btn'); btn.onclick = () => { if (WuhuConfig.get('quickFinishAtt') !== 3) { btn.remove(); // 停止自动刷新 // stop_reload = true; } else { document.body.classList.toggle('wh-move-btn'); } }; break; } case Device.TABLET: { break; } } } } // 光速跑路 private quickFinishFight(btnList: NodeListOf): void { if (this.currentStage === FIGHT_STAGE.FINISHED) { return; } else { this.currentStage = FIGHT_STAGE.FINISHED; } if (WuhuConfig.get('quickFinishAtt') === 3) { document.body.classList.remove('wh-move-btn'); Log.info('移除body class wh-move-btn'); return; } const user_btn_select = ['leave', 'mug', 'hosp'][WuhuConfig.get('quickFinishAtt')]; // const wrap = document.querySelector('#react-root'); Log.info('光速跑路选项选中:', user_btn_select); // const btn_arr: HTMLButtonElement[] = document.querySelectorAll('div[class^="dialogButtons___"] button') as unknown as HTMLButtonElement[]; if (btnList.length > 1) btnList.forEach(btn => { const flag = btn.innerText.toLowerCase().includes(user_btn_select); Log.info('按钮内容:', btn.innerText, ',是否包含选中:', flag); if (!flag) btn.style.display = 'none'; }); } // 盯梢模式 private watchTarget(): void { Log.info('获取目标id'); let targetId = window.location.href.split('user2ID=')[1]; if (!CommonUtils.getInstance().isValidUid(targetId)) { Log.error('目标id获取错误', targetId); throw new Error('目标id获取错误:' + targetId); } let loop = new LoopHelper(async () => { let userProfile; try { userProfile = await FetchUtils.getInstance().getProfile(targetId); } catch { Log.error('盯梢模式无法获取目标id'); throw new Error('盯梢模式无法获取目标id'); } await CommonUtils.getInstance().sleep(MathUtils.getInstance().getRandomInt(20, 50)); if ((userProfile.userStatus.status === 'ok' && CommonUtils.getInstance().getTravelStage() === TRAVEL_STATE.IN_TORN) || (userProfile.userStatus.status === 'abroad' && CommonUtils.getInstance().getTravelStage() === TRAVEL_STATE.ABROAD)) { watchSwitch.getInput().checked = false; window.setTimeout(async () => { new Alert('目标已落地/出院/出狱!', { timeout: 10, force: true, sysNotify: true }); await CommonUtils.getInstance().audioPlay(); await CommonUtils.getInstance().sleep(300); await CommonUtils.getInstance().audioPlay(); await CommonUtils.getInstance().sleep(300); await CommonUtils.getInstance().audioPlay(); await CommonUtils.getInstance().sleep(300); }, 0); } }); let block = new TornStyleBlock('盯梢模式').insert2Dom(); let watchSwitch = new TornStyleSwitch('开启'); block.append(watchSwitch.getBase()); watchSwitch.getInput().addEventListener('change', () => { if (watchSwitch.getInput().checked) { new DialogMsgBox('检测玩家状态,当目标状态变成(海外)落地、出院或出狱时通知并播放声音提醒,后可搭配光速刷新食用
确定开启?', { callback: () => { if (CommonUtils.getInstance().getTravelStage() === TRAVEL_STATE.FLYING) { new Alert('失败!已取消'); watchSwitch.getInput().checked = false; return; } Log.info('盯梢开启, 目标id' + targetId); loop.start(parseInt(WuhuConfig.get('WatchTargetFreq'))); }, cancel: () => watchSwitch.getInput().checked = false }); } else { loop.stop(); Log.info('盯梢关闭'); } }); } }