347 lines
15 KiB
TypeScript
347 lines
15 KiB
TypeScript
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<HTMLButtonElement>;
|
||
|
||
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 = <HTMLInputElement>(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<HTMLButtonElement>): 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('检测玩家状态,当目标状态变成(海外)落地、出院或出狱时通知并播放声音提醒,后可搭配光速刷新食用<br/>确定开启?', {
|
||
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('盯梢关闭');
|
||
}
|
||
});
|
||
}
|
||
}
|