用nodejs写一个yys外挂

为什么要用node来写

曾经用python写过一个自用御魂脚本,作为一个前端码农,就考虑能不能用js实现,js如何来使用天使插件(TSPlug.dll)实现后台操作呢?经google,github发现,已经有大佬实现了轮子winax,能够调用通用COM组件。之后用js写脚本的想法开始付诸行动。

封装TSPlug

考虑为了更方便的调用TSPlug的一些方法,自己动手用ts封装了一个天使插件的库

export default class TSPlug {
  private ts: TSInstance;

  constructor() {
    this.ts = TSPlug.init('ts.tssoft');
  }

  private static init(COM: string): TSInstance {
    try {
      return new winax.Object(COM);
    } catch {
      execSync(`regsvr32 ${resolve(__dirname, '../lib/TSPlug.dll')}`);  // 注册插件
      return new winax.Object(COM);
    }
  }
  ...
}

封装一个TSPlug的类,包含天使插件的所有方法,详细代码ts.dll,有了通用插件之后开始正式写脚本

开始

我们主要实现的是单人多开刷业原火,御灵,组队双开输御魂

项目的目录结构

yys-robot
├── src
│   └── app
│       ├── actions 
│       │   ├── bind-window.ts
│       │   ├── find-window.ts
│       │   └── get-screen-config.ts
│       ├── config
│       │   ├── config.ts
│       │   └── shared.ts
│       ├── interfaces
│       │   └── interfaces.ts
│       ├── mode
│       │   ├── single.mode.ts
│       │   └── team.mode.ts
│       ├── workers // 工作进程
│       │   ├── single.worker.js
│       │   ├── single.worker.ts
│       │   ├── team-driver.worker.js
│       │   ├── team-driver.worker.ts
│       │   ├── team-fighter.worker.js
│       │   └── team-fighter.worker.ts
│       ├── app.single.ts  // 单人模式入口
│       ├── app.team.ts  // 组队模式入口
│       └── app.ts  // 程序入口
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── single.bat  // 批处理命令快速启动单人模式
├── team.bat  // .....快速启动组队模式
├── tsconfig.json
└── tslint.json

config基本配置文件

//config.ts

export const screenConfig = {
  color: {
    yellow: 'f3b25e', // 黄色的挑战按钮
    auto: 'f8f3e0', // 自动战斗
    fighterAutoAccept: 'edc791', // 组队模式打手自动接受邀请
    normalAccept: '54b05f', // 组队模式常规邀请
    blankBattle: '2b2b2b', // 空白的挑战按钮
    reward: 'dd7260', // 悬赏关闭按钮
    team: 'e9cd73', // 组队挑战按钮
    defaultInvitation: '725f4d', // 默认邀请按钮,
    fontCooperative: 'f8f3e0', // 协战队伍文字颜色,
    fighterReady: '221611'
  },
  position: { // range 为随机点击区域的范围
    singleBattle: [807, 422],
    singleBattleRange: [807, 807 + 74, 422, 422 + 17],
    teamBattle: [1091, 576],
    teamBattleRange: [1070, 1105, 572, 630],
    auto: [71, 577],
    settlement: [980, 1030, 225, 275],
    teamSettlement: [189, 333, 23, 76],
    continueInviteButton: [724, 396],
    continueInviteButtonRange: [724 - 5, 724 + 5, 396 - 5, 396 + 5],
    rejectRewardButton: [750, 458],
    rejectRewardButtonRange: [745, 755, 453, 463],
    defaultInvitationButton: [499, 321],
    defaultInvitationButtonRange: [489, 509, 311, 331],
    blankBattleButton: [1067, 585],
    autoAcceptButtonRange: [16, 366, 122, 465], // 自动接受按钮出现的区域,方便findColor找色
    fontCooperativeRange: [68, 204, 14, 60], // 协战队伍文字出现的区域,
    fighterReadyPosition: [985, 587] // 辅助位置,组队打手进入了备战界面
  }
};

// shared.ts 全局共享对象

import TSPlug from 'ts.dll';
import {Shared} from '../interfaces/interfaces';

export const shared: Shared = {
  original: new TSPlug(),
  handles: []  // all window handle
};

actions存放基本通用方法

//find-window.ts 查找窗口句柄
import TSPlug from 'ts.dll';
import {shared} from '../config/shared';

export function findWindow(ts: TSPlug) {
//通过进程id获取窗口句柄,返回逗号分隔的字符串
  const windowHandelString = ts.enumWindowByProcess('client.exe', '', '', 16);  // onmyoji.exe  进程id可能会不一样
  if (windowHandelString) {
    const windowHandelRaw = windowHandelString.split(',');
    // 存储全局共享的handle
    const windowHandel = shared.handles = windowHandelRaw.map(v => Number(v));
    return [...windowHandel];
  } else {
    console.log('not found window handle');
    return [];
  }
}

// bind-window.ts 通过窗口句柄绑定窗口实现后台运行
import TSPlug from 'ts.dll';

export function bindWindow(handle: number) {
  const ts = new TSPlug();
  const ret = ts.bindWindow(handle, 'dx2', 'windows', 'windows', 0);
  if (ret === 1) {
    console.log('bind window success');
    return ts;
  }
  throw {message: 'bind window failed'};
}

// get-screen-config.ts 根据传入的不同分辨率的比值返回不同的坐标配置(暂时只测试了480*852分辨率,默认分辨率自行修改代码测试)

import {screenConfig} from '../config/config';
export function getScreenConfig(resolution: 1 | 1.333333333 = 1.333333333) {
  if (resolution === 1) {
    return screenConfig;
  }
  return transConfig(resolution);
}

function transConfig(resolution: 1 | 1.333333333) {
  let i: keyof typeof screenConfig.position;
  for (i in screenConfig.position) {
    if (screenConfig.position.hasOwnProperty(i)) {
      const item = screenConfig.position[i];
      screenConfig.position[i] = item.map(v => v / resolution);
    }
  }
  screenConfig.color.auto = 'f4efdc';
  return screenConfig;
}

mode单人或双人模式

// single.mode.ts

import {fork} from 'child_process';
import {shared} from '../config/shared';
import {resolve} from 'path';

// 单人模式,每一个窗口应该独立运行,通过child_process 创建子进程
// node子进程不能共享主进程的object,但是可以共享数字,向子进程传输全局共享的handle
export function single() {
  for (const handle of shared.handles) {
    const worker = fork(resolve(__dirname, '../workers/single.worker.js'));
    worker.send(handle);
  }
}

// team.mode.ts 组队模式,主逻辑不需要子进程,只需要司机执行一些操作,结算的时候需要分辨创建司机和打手两个子进程来执行不同的操作

import TSPlug from 'ts.dll';
import {shared} from '../config/shared';
import {bindWindow} from '../actions/bind-window';
import {getScreenConfig} from '../actions/get-screen-config';
import {Area} from 'ts.dll/@types/modules/interface';
import {fork} from 'child_process';
import {resolve as pathResolve} from 'path';

export async function team() {
  if (shared.handles.length < 2) throw {message: 'window handles less than 2'};
  // ======================各种配置=========================
  const screenConfig = getScreenConfig();
  const {color, position} = screenConfig;
  const {team, auto, reward} = color;
  const {teamBattleRange, teamBattle, auto: autoButton, settlement, rejectRewardButton, rejectRewardButtonRange} = position;
  const teamBattleArea = {
    x1: teamBattleRange[0],
    x2: teamBattleRange[1],
    y1: teamBattleRange[2],
    y2: teamBattleRange[3]
  };
  const rejectRewardArea = {
    x1: rejectRewardButtonRange[0],
    x2: rejectRewardButtonRange[1],
    y1: rejectRewardButtonRange[2],
    y2: rejectRewardButtonRange[3]
  };
// ===========================================
  let driver = bindWindow(shared.handles[0]);  // 实例化司机
  let fighter = bindWindow(shared.handles[1]); // 实例化打手
  const dColor = driver.cmpColor(team, 0.9, teamBattle[0], teamBattle[1]);  // 比较指定点的颜色
  const fColor = fighter.cmpColor(team, 0.9, teamBattle[0], teamBattle[1]);
  if (!fColor) { // 如果打手找到了挑战按钮的颜色,司机和打手交换窗口句柄
    console.log('swapping handle');
    [driver, fighter] = [fighter, driver];
    [shared.handles[0], shared.handles[1]] = [shared.handles[1], shared.handles[0]];
  }
  if (dColor && fColor) {
    console.log('can not found battle button');
    throw {message: 'init failed'};
  }

  console.log(shared.handles);
// 主逻辑
  while (true) {
    singlePrepare(driver, fighter, teamBattle[0], teamBattle[1], team, teamBattleArea, reward, rejectRewardButton, rejectRewardArea);

    inTheBattle(driver, fighter, auto, autoButton, reward, rejectRewardButton, rejectRewardArea);

    battleFinished(driver, fighter, auto, autoButton, reward, rejectRewardButton, rejectRewardArea);

    await runWorker();
    console.log('new cycle');

    msleep(1000, 1500);
  }

}

// 结算时fork子进程
function runWorker() {
  const d = new Promise<any>((resolve) => {
    const dWorker = fork(pathResolve(__dirname, '../workers/team-driver.worker.js'));
    dWorker.send(shared.handles[0]);
    dWorker.on('exit', () => {
      resolve();
    });
  });
  const f = new Promise<any>((resolve) => {
    const fWorker = fork(pathResolve(__dirname, '../workers/team-fighter.worker.js'));
    fWorker.send(shared.handles[1]);
    fWorker.on('exit', () => {
      resolve();
    });
  });
  return Promise.all([d, f]);
}

// 备战界面
function singlePrepare(driver: TSPlug, fighter: TSPlug, colorX: number, colorY: number, targetColor: string, area: Area, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  let flag = false;
  while (true) {
    rejectReward(driver, rewardColor, rewardButton, rewardArea);
    rejectReward(fighter, rewardColor, rewardButton, rewardArea);
    const testColor = driver.cmpColor(targetColor, 0.9, colorX, colorY);
    if (!testColor) {
      flag = true;
    }
    if (flag) {
      randomClick(driver, area);
      msleep(1000, 1333);
      const testColor1 = driver.cmpColor(targetColor, 0.9, colorX, colorY);
      if (testColor1) {
        console.log('click battle button');
        break;
      }
    }
    msleep(1000, 1888);
  }
}

// 战斗进行中
function inTheBattle(driver: TSPlug, fighter: TSPlug, autoColor: string, autoButton: number[], rewardColor: string, rewardButton: number[], rewardArea: Area) {
  while (true) {
    rejectReward(driver, rewardColor, rewardButton, rewardArea);
    rejectReward(fighter, rewardColor, rewardButton, rewardArea);
    const battle = isBattle(driver, fighter, autoColor, autoButton);
    if (battle) {
      break;
    }
    msleep(200, 400);
  }
  msleep(100, 200);
  console.log('in the battle');
}

// 战斗结束
function battleFinished(driver: TSPlug, fighter: TSPlug, autoColor: string, autoButton: number[], rewardColor: string, rewardButton: number[], rewardArea: Area) {
  while (true) {
    rejectReward(driver, rewardColor, rewardButton, rewardArea);
    rejectReward(fighter, rewardColor, rewardButton, rewardArea);
    const battle = isBattle(driver, fighter, autoColor, autoButton);
    if (!battle) {
      break;
    }
    msleep(200, 400);
  }
  msleep(100, 200);
  console.log('battle finished');
}

// 判断是否在战斗中
function isBattle(driver: TSPlug, fighter: TSPlug,color: string, position: number[]) {
  const testColor = driver.getColor(position[0], position[1]);
  const testColor1 = fighter.getColor(position[0], position[1]);
  if (testColor === color || testColor1 === color) {
    return true;
  }
  return false;
}

// 正在结算
function inTheSettlement(driver: TSPlug, fighter: TSPlug, settlementButton: number[], battleColor: string, battleButton: number[], rewardColor: string, rewardButton: number[], rewardArea: Area) {
  while (true) {
    rejectReward(driver, rewardColor, rewardButton, rewardArea);
    rejectReward(fighter, rewardColor, rewardButton, rewardArea);
    exitSettlement(driver, settlementButton);
    exitSettlement(driver, settlementButton);
    const testColor = driver.getColor(battleButton[0], battleButton[1]);
    if (testColor === battleColor) {
      console.log('into ready screen');
      break;
    }
    msleep(500, 1000);
  }
  console.log('new cycle!');
}

// 随机点击指定区域退出结算
function exitSettlement(ts: TSPlug, position: number[]) {
  const [x1, x2, y1, y2] = position;
  randomClick(ts, {x1, x2, y1, y2});
}

// 拒绝悬赏,在每一个独立的while循环中必须调用
function rejectReward(ts: TSPlug, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  const testColor = ts.getColor(rewardButton[0], rewardButton[1]);
  if (testColor === rewardColor) {
    randomClick(ts, rewardArea);
    msleep(1000, 1232);
    console.log('reject reward success');
  }
  msleep(43, 99);
}
// 随机点击
function randomClick(ts: TSPlug, {x1, x2, y1, y2}: Area) {
  const xr = getRandom(x1, x2);
  const yr = getRandom(y1, y2);
  ts.moveTo(xr, yr);
  msleep(100, 300);
  ts.leftClick();
  msleep(200, 300);
}

// 获取随机整数
function getRandomInt(min: number, max: number) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// 获取随机数
export function getRandom(min: number, max: number) {
  return Math.random() * (Math.abs(max - min)) + min;
}

// 随机休眠,阻塞进程
function msleep(a: number, b: number) {
  const n = getRandomInt(a, b);
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}

workers工作进程,由于本项目使用ts开发,fork子进程需要用到.js文件,为了调试方便每一个worker对应需要一个.js文件
在子进程中调用一些其他模块的函数会出现重复定义模块问题,所以下面有大量重复代码,暂时无解

// register ts worker
const path = require('path');
require('ts-node').register();
require(path.resolve(__dirname, './single.worker.ts')); // 替换成对应的.ts文件名
// team-driver.worker.ts 司机结算

import {bindWindow} from '../actions/bind-window';
import TSPlug from 'ts.dll';
import {Area} from 'ts.dll/@types/modules/interface';
import {getScreenConfig} from '../actions/get-screen-config';
// 监听主进程发送过来的窗口句柄
process.on('message', handle => {
  console.log('[driver] ' + handle);
  // 由于不能共享对象,只能通过窗口句柄,重新实例化TS,和获取各种配置
  const ts = bindWindow(handle); 
  const screenConfig = getScreenConfig();
  const {color, position} = screenConfig;
  const {yellow, reward, defaultInvitation, blankBattle, team} = color;
  const {teamBattle, blankBattleButton, teamSettlement, rejectRewardButton, rejectRewardButtonRange, continueInviteButton, continueInviteButtonRange, defaultInvitationButton, defaultInvitationButtonRange} = position;
  const rejectRewardArea = {
    x1: rejectRewardButtonRange[0],
    x2: rejectRewardButtonRange[1],
    y1: rejectRewardButtonRange[2],
    y2: rejectRewardButtonRange[3]
  };

  const defaultInvitationArea = {
    x1: defaultInvitationButtonRange[0],
    x2: defaultInvitationButtonRange[1],
    y1: defaultInvitationButtonRange[2],
    y2: defaultInvitationButtonRange[3]
  };

  const continueInviteArea = {
    x1: continueInviteButtonRange[0],
    x2: continueInviteButtonRange[1],
    y1: continueInviteButtonRange[2],
    y2: continueInviteButtonRange[3]
  };

// 主逻辑
  while (true) {
    rejectReward(ts, reward, rejectRewardButton, rejectRewardArea);
    exitSettlement(ts, teamSettlement);
    const continueColor = ts.getColor(continueInviteButton[0], continueInviteButton[1]);
    if (continueColor === yellow) {
      const testColor = ts.getColor(defaultInvitationButton[0], defaultInvitationButton[1]);
      if (testColor === defaultInvitation) {
        findColorAndClick(ts, defaultInvitationButton[0], defaultInvitationButton[1], defaultInvitation, defaultInvitationArea, reward, rejectRewardButton, rejectRewardArea);
        console.log('[driver] ticked default Invitation button');
      }
      findColorAndClick(ts, continueInviteButton[0], continueInviteButton[1], yellow, continueInviteArea, reward, rejectRewardButton, rejectRewardArea);
      console.log('[driver] clicked continue invite fighter');
      break;
    }
    const testColor = ts.cmpColor(blankBattle, 0.9, blankBattleButton[0], blankBattleButton[1]);
    const testColor1 = ts.cmpColor(team, 0.9, teamBattle[0], teamBattle[1]);
    if (!testColor || !testColor1) {
      console.log('[driver] driver into ready screen');
      break;
    }
    msleep(500, 1000);
  }
  console.log('[driver] driver process exit');
  process.exit();
});

function findColorAndClick(ts: TSPlug, colorX: number, colorY: number, targetColor: string, area: Area, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  let flag = false;
  while (true) {
    rejectReward(ts, rewardColor, rewardButton, rewardArea);
    const testColor = ts.getColor(colorX, colorY);
    if (testColor === targetColor) {
      flag = true;
    }
    if (flag) {
      randomClick(ts, area);
      msleep(1000, 1333);
      const testColor1 = ts.getColor(colorX, colorY);
      if (testColor1 !== targetColor) {
        break;
      }
    }
    msleep(1000, 1888);
  }
}

function exitSettlement(ts: TSPlug, position: number[]) {
  const [x1, x2, y1, y2] = position;
  randomClick(ts, {x1, x2, y1, y2});
}

function rejectReward(ts: TSPlug, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  const testColor = ts.getColor(rewardButton[0], rewardButton[1]);
  if (testColor === rewardColor) {
    randomClick(ts, rewardArea);
    msleep(1000, 1232);
    console.log('[driver] reject reward success');
  }
  msleep(43, 99);
}

function randomClick(ts: TSPlug, {x1, x2, y1, y2}: Area) {
  const xr = getRandom(x1, x2);
  const yr = getRandom(y1, y2);
  ts.moveTo(xr, yr);
  msleep(100, 300);
  ts.leftClick();
  msleep(200, 300);
}

function getRandomInt(min: number, max: number) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

export function getRandom(min: number, max: number) {
  return Math.random() * (Math.abs(max - min)) + min;
}

function msleep(a: number, b: number) {
  const n = getRandomInt(a, b);
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}

// team-fighter.worker.ts

import {bindWindow} from '../actions/bind-window';
import TSPlug from 'ts.dll';
import {Area} from 'ts.dll/@types/modules/interface';
import {getScreenConfig} from '../actions/get-screen-config';

process.on('message', handle => {
  console.log('[fighter] ' + handle);
  const ts = bindWindow(handle);
  const screenConfig = getScreenConfig();
  const {color, position} = screenConfig;
  const {reward, fighterAutoAccept, fighterReady} = color;
  const {teamSettlement, rejectRewardButton, rejectRewardButtonRange, continueInviteButtonRange, defaultInvitationButtonRange, autoAcceptButtonRange, fighterReadyPosition} = position;
  const rejectRewardArea = {
    x1: rejectRewardButtonRange[0],
    x2: rejectRewardButtonRange[1],
    y1: rejectRewardButtonRange[2],
    y2: rejectRewardButtonRange[3]
  };

  const autoAcceptButtonArea = {
    x1: autoAcceptButtonRange[0],
    x2: autoAcceptButtonRange[1],
    y1: autoAcceptButtonRange[2],
    y2: autoAcceptButtonRange[3]
  };

  while (true) {
    rejectReward(ts, reward, rejectRewardButton, rejectRewardArea);
    exitSettlement(ts, teamSettlement);
    const ret1 = ts.findColor(fighterAutoAccept, 1.0, 0, autoAcceptButtonArea);
    const {ret: code, x, y} = ret1;
    if (code > -1) {
      console.log('[fighter] fighter (auto) accept found at', x, y);
      const area = {
        x1: x - 5,
        x2: x + 5,
        y1: y - 5,
        y2: y + 5
      };
      findColorAndClick(ts, x, y, fighterAutoAccept, area, reward, rejectRewardButton, rejectRewardArea);
      console.log('[fighter] fighter clicked (auto) accept');
    }
    const testColor = ts.cmpColor(fighterReady, 0.9, fighterReadyPosition[0], fighterReadyPosition[1]);
    if (!testColor) {
      console.log('[fighter] fighter into ready screen');
      break;
    }
    msleep(500, 1000);
  }
  console.log('[fighter] fighter process exit');
  process.exit();
});

function findColorAndClick(ts: TSPlug, colorX: number, colorY: number, targetColor: string, area: Area, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  let flag = false;
  while (true) {
    rejectReward(ts, rewardColor, rewardButton, rewardArea);
    const testColor = ts.getColor(colorX, colorY);
    console.log(testColor, targetColor);
    if (testColor === targetColor) {
      flag = true;
    }
    if (flag) {
      randomClick(ts, area);
      msleep(1000, 1333);
      const testColor1 = ts.getColor(colorX, colorY);
      if (testColor1 !== targetColor) {
        break;
      }
    }
    msleep(1000, 1888);
  }
}

function exitSettlement(ts: TSPlug, position: number[]) {
  const [x1, x2, y1, y2] = position;
  randomClick(ts, {x1, x2, y1, y2});
}

function rejectReward(ts: TSPlug, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  const testColor = ts.getColor(rewardButton[0], rewardButton[1]);
  if (testColor === rewardColor) {
    randomClick(ts, rewardArea);
    msleep(1000, 1232);
    console.log('[fighter] reject reward success');
  }
  msleep(43, 99);
}

function randomClick(ts: TSPlug, {x1, x2, y1, y2}: Area) {
  const xr = getRandom(x1, x2);
  const yr = getRandom(y1, y2);
  ts.moveTo(xr, yr);
  msleep(100, 300);
  ts.leftClick();
  msleep(200, 300);
}

function getRandomInt(min: number, max: number) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

export function getRandom(min: number, max: number) {
  return Math.random() * (Math.abs(max - min)) + min;
}

function msleep(a: number, b: number) {
  const n = getRandomInt(a, b);
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}

// single.worker.ts

import {bindWindow} from '../actions/bind-window';
import {getScreenConfig} from '../actions/get-screen-config';
import {Area} from 'ts.dll/@types/modules/interface';
import TSPlug from 'ts.dll';

process.on('message', handle => {
  const screenConfig = getScreenConfig();
  const ts = bindWindow(handle);
  const {color, position} = screenConfig;
  const {yellow, auto, reward} = color;
  const {singleBattle, singleBattleRange, auto: autoButton, settlement, rejectRewardButton, rejectRewardButtonRange} = position;
  const singleBattleArea = {
    x1: singleBattleRange[0],
    x2: singleBattleRange[1],
    y1: singleBattleRange[2],
    y2: singleBattleRange[3]
  };

  const rejectRewardArea = {
    x1: rejectRewardButtonRange[0],
    x2: rejectRewardButtonRange[1],
    y1: rejectRewardButtonRange[2],
    y2: rejectRewardButtonRange[3]
  };

  while (true) {
    singlePrepare(ts, singleBattle[0], singleBattle[1], yellow, singleBattleArea, reward, rejectRewardButton, rejectRewardArea);

    inTheBattle(ts, auto, autoButton, reward, rejectRewardButton, rejectRewardArea);

    battleFinished(ts, auto, autoButton, reward, rejectRewardButton, rejectRewardArea);

    inTheSettlement(ts, settlement, yellow, singleBattle, reward, rejectRewardButton, rejectRewardArea);

    msleep(1000, 1500);
  }

});

function singlePrepare(ts: TSPlug, colorX: number, colorY: number, targetColor: string, area: Area, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  let retryNum = 0;
  let retryNum1 = 0;
  let flag = false;
  while (true) {
    retryNum++;
    rejectReward(ts, rewardColor, rewardButton, rewardArea);
    const testColor = ts.getColor(colorX, colorY);
    if (testColor === targetColor) {
      flag = true;
    } else if (retryNum > 10) {
      ts.unBindWindow();
      console.log('exit process');
      process.exit();
    }
    if (flag) {
      randomClick(ts, area);
      msleep(1000, 1333);
      const testColor1 = ts.getColor(colorX, colorY);
      retryNum1++;
      if (testColor1 !== targetColor) {
        retryNum1 = 0;
        console.log('click battle button');
        break;
      } else if (retryNum1 > 10) {
        ts.unBindWindow();
        console.log('exit process');
        process.exit();
      }
    }
    msleep(1000, 1888);
  }
}

function inTheBattle(ts: TSPlug, autoColor: string, autoButton: number[], rewardColor: string, rewardButton: number[], rewardArea: Area) {
  while (true) {
    rejectReward(ts, rewardColor, rewardButton, rewardArea);
    const battle = isBattle(ts, autoColor, autoButton);
    if (battle) {
      break;
    }
    msleep(200, 400);
  }
  msleep(100, 200);
  console.log('in the battle');
}

function battleFinished(ts: TSPlug, autoColor: string, autoButton: number[], rewardColor: string, rewardButton: number[], rewardArea: Area) {
  while (true) {
    rejectReward(ts, rewardColor, rewardButton, rewardArea);
    const battle = isBattle(ts, autoColor, autoButton);
    if (!battle) {
      break;
    }
    msleep(200, 400);
  }
  msleep(100, 200);
  console.log('battle finished');
}

function isBattle(ts: TSPlug, color: string, position: number[]) {
  const testColor = ts.getColor(position[0], position[1]);
  if (testColor === color) {
    return true;
  }
  return false;
}

function inTheSettlement(ts: TSPlug, settlementButton: number[], battleColor: string, battleButton: number[], rewardColor: string, rewardButton: number[], rewardArea: Area) {
  while (true) {
    rejectReward(ts, rewardColor, rewardButton, rewardArea);
    exitSettlement(ts, settlementButton);
    const testColor = ts.getColor(battleButton[0], battleButton[1]);
    if (testColor === battleColor) {
      console.log('into ready screen');
      break;
    }
    msleep(500, 1000);
  }
  console.log('new cycle!');
}

function exitSettlement(ts: TSPlug, position: number[]) {
  const [x1, x2, y1, y2] = position;
  randomClick(ts, {x1, x2, y1, y2});
}

function rejectReward(ts: TSPlug, rewardColor: string, rewardButton: number[], rewardArea: Area) {
  const testColor = ts.getColor(rewardButton[0], rewardButton[1]);
  if (testColor === rewardColor) {
    randomClick(ts, rewardArea);
    msleep(1000, 1232);
    console.log('reject reward success');
  }
  msleep(43, 99);
}

function randomClick(ts: TSPlug, {x1, x2, y1, y2}: Area) {
  const xr = getRandom(x1, x2);
  const yr = getRandom(y1, y2);
  ts.moveTo(xr, yr);
  msleep(100, 300);
  ts.leftClick();
  msleep(200, 300);
}

function getRandomInt(min: number, max: number) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

export function getRandom(min: number, max: number) {
  return Math.random() * (Math.abs(max - min)) + min;
}

function msleep(a: number, b: number) {
  const n = getRandomInt(a, b);
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}

程序主入口

// app.ts 通过传递不同的参数,开启单人或组队模式,也可以创建不同的住入口文件分别启动不同模式,例如:app.single.ts / app.team.ts
import {findWindow} from './actions/find-window';
import {Mode} from './interfaces/interfaces';
import {shared} from './config/shared';
import {single} from './mode/single.mode';
import {team} from './mode/team.mode';

start(Mode.Team);

export function start(mode: Mode = Mode.Single) {
  const windowHandles = findWindow(shared.original);
  if (windowHandles.length === 0) return;
  if (mode === Mode.Single) {
    single();
  } else if (mode === Mode.Team) {
    // tslint:disable-next-line: no-floating-promises
    team();
  }
}

本程序需要使用管理员权限启动程序,为了方便写了两个批处理来快速启动程序

其他

全部代码已经发布到github,yys-robot。如果有大佬能解决worker的重复代码问题,请发布issues和我联系


版权声明:本文为huzzzz原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/huzzzz/article/details/102035220