mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-15 03:23:22 +08:00
体力计划[支持幽境危战] (#2978)
This commit is contained in:
@@ -43,8 +43,8 @@
|
||||
**字段详解**
|
||||
|
||||
| 位置 | 字段 | 是否必填 | 说明 | 示例 |
|
||||
|:--:|:-----------:|:------:|:--------------------------------------:|:-----------------------:|
|
||||
| 1 | 类型 | **必填** | 秘境/地脉(后期支持地脉冗余字段) | 秘境/地脉 |
|
||||
|:--:|:-----------:|:------:|:-------------------------------------------:|:-----------------------:|
|
||||
| 1 | 类型 | **必填** | 秘境/地脉/幽境 | 秘境/地脉/幽境 |
|
||||
| 2 | 周几执行(0-6) | 可选 | 0=周日,1=周一,...,6=周六;可多选按`/`分割,不填=每天都可执行 | 0/3,3 |
|
||||
| 3 | 执行顺序 | 可选 | 数字越大越优先执行(同时间点先跑优先级高的) | 9 / 5 / 1 |
|
||||
| | | | ***`秘境类型后几位参数说明`***
|
||||
@@ -65,6 +65,12 @@
|
||||
| 12 | 使用冒险家之书 | 可选 | 启用随便填个值 |
|
||||
| 13 | 详细通知 | 可选 | 启用随便填个值 |
|
||||
| 14 | 战斗超时 | 可选 | 可选,默认 120 |
|
||||
| | | | ***`幽境类型后几位参数说明`***
|
||||
| 4 | 指定刷取战场 | 可选 | (1-3)不填使用BetterGI默认的配置 | 1 |
|
||||
| 5 | 队伍名称 | 可选 | BetterGI 中已保存的队伍名称(用于切换队伍)不填使用BetterGI默认的配置 | 速刷 / 雷国 / 国家队 |
|
||||
| 6 | 是否指定使用自定义树脂 | 可选 | 启用随便填个值 不填使用BetterGI默认的配置 | |
|
||||
| 7 | 自定义树脂顺序 | 可选 | 原粹树脂,浓缩树脂,须臾树脂,脆弱树脂`/`分割 不填使用BetterGI默认的配置 | 原粹树脂 / 浓缩树脂 / 须臾树脂 |
|
||||
| 8 | 自定义树脂次数 | 可选 | 和自定义树脂顺序对应 `/`分割 不填使用BetterGI默认的配置 | 1 / 2 / 3 |
|
||||
|
||||
**配置示例**
|
||||
|
||||
@@ -72,7 +78,9 @@
|
||||
秘境|0/3|9|速刷|苍白的遗荣|3|1, # 优先级最高,周日,周三刷3轮遗荣
|
||||
秘境||5|国家队|炽烈的炎之魔女|5||, # 优先级次之,每天刷5轮魔女
|
||||
秘境|0|2|雷国|无想之刃狭间|2|2|浓缩树脂/原粹树脂, # 只在周日刷,优先使用浓缩树脂后使用原粹树脂,优先级较低
|
||||
地脉||1||蒙德|1|启示之花|||||||120 #优先级较低 刷1轮蒙德经验书
|
||||
地脉||1||蒙德|1|启示之花|||||||120, #优先级较低 刷1轮蒙德经验书
|
||||
幽境||1|||, #幽境 默认本体配置
|
||||
幽境||1|1|队伍|1|浓缩树脂/原粹树脂|1/1 #幽境 默认自定义树脂配置 切换到 队伍 运行->浓缩树脂 1次->原粹树脂 1次
|
||||
```
|
||||
|
||||
(注意:最后一条也可以不带逗号)
|
||||
@@ -85,15 +93,17 @@
|
||||
"uid",
|
||||
[
|
||||
{
|
||||
"order": 1,
|
||||
// 顺序值
|
||||
"order": 1,
|
||||
// 执行日期
|
||||
"days": [
|
||||
0
|
||||
],
|
||||
// 执行日期
|
||||
// 类型支持。 秘境、地脉、幽境
|
||||
"runType": "秘境",
|
||||
// 预留地脉 类型支持。 秘境、地脉
|
||||
// 秘境信息对象,
|
||||
"autoFight": {
|
||||
//树脂开启和使用顺序
|
||||
"physical": [
|
||||
{
|
||||
"order": 0,
|
||||
@@ -116,41 +126,76 @@
|
||||
"open": false
|
||||
}
|
||||
],
|
||||
//树脂开启和使用顺序
|
||||
"domainName": undefined,
|
||||
//秘境名称
|
||||
"domainName": undefined,
|
||||
//队伍名称
|
||||
"partyName": undefined,
|
||||
//队伍名称
|
||||
"sundaySelectedValue": undefined,
|
||||
//周日|限时选择的值
|
||||
"domainRoundNum": undefined//副本轮数
|
||||
}
|
||||
// 秘境信息对象,
|
||||
"autoLeyLineOutcrop": {
|
||||
"count": 0,
|
||||
//刷几轮
|
||||
"country": "",
|
||||
//国家
|
||||
"leyLineOutcropType": "启示之花",
|
||||
//地脉类型 启示之花/藏金之花
|
||||
"useAdventurerHandbook": false,
|
||||
//使用冒险家之书
|
||||
"friendshipTeam": "",
|
||||
//好感队伍名称
|
||||
"team": "",
|
||||
//队伍名称
|
||||
"timeout": 120,
|
||||
//战斗超时
|
||||
"isGoToSynthesizer": false,
|
||||
//合成浓缩树脂
|
||||
"useFragileResin": false,
|
||||
//使用脆弱树脂
|
||||
"useTransientResin": false,
|
||||
//使用须臾树脂
|
||||
"isNotification": false
|
||||
//详细通知
|
||||
}
|
||||
"sundaySelectedValue": undefined,
|
||||
//副本轮数
|
||||
"domainRoundNum": undefined
|
||||
},
|
||||
//地脉信息对象
|
||||
"autoLeyLineOutcrop": {
|
||||
//刷几轮
|
||||
"count": 0,
|
||||
//国家
|
||||
"country": "",
|
||||
//地脉类型 启示之花/藏金之花
|
||||
"leyLineOutcropType": "启示之花",
|
||||
//使用冒险家之书
|
||||
"useAdventurerHandbook": false,
|
||||
//好感队伍名称
|
||||
"friendshipTeam": "",
|
||||
//队伍名称
|
||||
"team": "",
|
||||
//战斗超时
|
||||
"timeout": 120,
|
||||
//合成浓缩树脂
|
||||
"isGoToSynthesizer": false,
|
||||
//使用脆弱树脂
|
||||
"useFragileResin": false,
|
||||
//使用须臾树脂
|
||||
"useTransientResin": false,
|
||||
//详细通知
|
||||
"isNotification": false
|
||||
},
|
||||
//危战信息对象
|
||||
"autoStygianOnslaught": {
|
||||
//自定义树脂
|
||||
"physical": [
|
||||
{
|
||||
"order": 0,
|
||||
"name": "原粹树脂",
|
||||
"open": true,
|
||||
"count": 0
|
||||
},
|
||||
{
|
||||
"order": 1,
|
||||
"name": "浓缩树脂",
|
||||
"open": false,
|
||||
"count": 0
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"name": "须臾树脂",
|
||||
"open": false,
|
||||
"count": 0
|
||||
},
|
||||
{
|
||||
"order": 3,
|
||||
"name": "脆弱树脂",
|
||||
"open": false,
|
||||
"count": 0
|
||||
}
|
||||
],
|
||||
// 是否指定使用自定义树脂
|
||||
"specifyResinUse": false,
|
||||
//指定刷取战场 1-3
|
||||
"bossNum": undefined,
|
||||
//队伍名称
|
||||
"fightTeamName": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -220,7 +265,7 @@
|
||||
]
|
||||
```
|
||||
|
||||
### 如果你不想研究语法 请部署[bettergi-scripts-tools](https://github.com/Kirito520Asuna/bettergi-scripts-tools) v0.0.4以上版本
|
||||
### 如果你不想研究语法 请部署[bettergi-scripts-tools](https://github.com/Kirito520Asuna/bettergi-scripts-tools)(点击前往部署) v0.0.4以上版本
|
||||
|
||||
***`话不多说直接上图:`***
|
||||
|
||||
@@ -253,9 +298,11 @@
|
||||
| 版本 | 密钥 |
|
||||
|-------|---------------------|
|
||||
| 0.0.1 | oiJbmjU2R0NniiwiZxh |
|
||||
| 0.0.2 | oiJbmjU2R0NniiwiZxh |
|
||||
|
||||
## 版本历史(简要)
|
||||
|
||||
### 0.0.2 2026.03.07
|
||||
- 新增幽境支持
|
||||
### 0.0.1 2026.01.30
|
||||
|
||||
- 基本功能完成
|
||||
|
||||
@@ -34,7 +34,7 @@ const config = {
|
||||
current: 0,//当前体力
|
||||
names: ["原粹树脂", "浓缩树脂", "须臾树脂", "脆弱树脂"]
|
||||
},
|
||||
runTypes: ['秘境', '地脉']
|
||||
runTypes: ['秘境', '地脉', '幽境']
|
||||
},
|
||||
//
|
||||
path: {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import {config, initConfig, initSettings, LoadType} from './config/config';
|
||||
import {ocrUid} from './utils/uid';
|
||||
import {getDayOfWeek, outDomainUI, throwError} from './utils/tool';
|
||||
import {getDayOfWeek, outDomainUI, outStygianOnslaughtUI, throwError,toMainUi} from './utils/tool';
|
||||
import {pullJsonConfig, pushAllCountryConfig, pushAllJsonConfig} from './utils/bgi_tools';
|
||||
import {ocrPhysical} from "./utils/physical";
|
||||
import {findStygianOnslaught} from "./utils/activity";
|
||||
|
||||
/**
|
||||
* 自动执行秘境任务的异步函数
|
||||
@@ -12,13 +13,17 @@ import {ocrPhysical} from "./utils/physical";
|
||||
async function autoDomain(autoFight) {
|
||||
log.info(`{0}`, "开始执行秘境任务")
|
||||
log.warn(`{0}`, "非体力耗尽情况下(受本体限制),等待退出秘境时间较长")
|
||||
// 创建秘境参数对象,初始化值为0
|
||||
let domainParam = new AutoDomainParam();
|
||||
//关闭榨干原粹树脂
|
||||
domainParam.specifyResinUse = true
|
||||
//定死做预留冗余 先不实现 不能指定次数 只能指定启用
|
||||
let physical_domain = autoFight?.physical
|
||||
// || [
|
||||
// {order: 0, name: "原粹树脂", count: 1, open: true},
|
||||
// {order: 1, name: "浓缩树脂", count: 0, open: false},
|
||||
// {order: 2, name: "须臾树脂", count: 0, open: false},
|
||||
// {order: 3, name: "脆弱树脂", count: 0, open: false},
|
||||
// {order: 0, name: config.user.physical.names[0], count: 1, open: true},
|
||||
// {order: 1, name: config.user.physical.names[1], count: 0, open: false},
|
||||
// {order: 2, name: config.user.physical.names[2], count: 0, open: false},
|
||||
// {order: 3, name: config.user.physical.names[3], count: 0, open: false},
|
||||
// ]
|
||||
|
||||
if ((!physical_domain) || physical_domain.filter(item => item?.open).length === 0) {
|
||||
@@ -31,10 +36,10 @@ async function autoDomain(autoFight) {
|
||||
|
||||
physical_domain.sort((a, b) => a.order - b.order)
|
||||
// 不包含原粹树脂的和
|
||||
const noOriginalSum = physical_domain.filter(item => item?.name.trim() !== "原粹树脂")
|
||||
const noOriginalSum = physical_domain.filter(item => item?.name.trim() !== config.user.physical.names[0])
|
||||
.filter(item => item?.open).length;//求和
|
||||
// 只包含原粹树脂的和
|
||||
const originalSum = physical_domain.filter(item => item?.name?.trim() === "原粹树脂")
|
||||
const originalSum = physical_domain.filter(item => item?.name?.trim() === config.user.physical.names[0])
|
||||
.filter(item => item?.open).length;
|
||||
const resinPriorityList = physical_domain.filter(item => item?.open).map(item => item?.name?.trim())
|
||||
// /** 树脂使用优先级列表 */
|
||||
@@ -53,15 +58,13 @@ async function autoDomain(autoFight) {
|
||||
config.user.physical.current = physicalOcr.current
|
||||
config.user.physical.min = physicalOcr.min
|
||||
const physical = config.user.physical
|
||||
if (physical.current < physical.min && noOriginalSum <= 0 && originalSum > 0) {
|
||||
if (domainParam.specifyResinUse && physical.current < physical.min && noOriginalSum <= 0 && originalSum > 0) {
|
||||
throwError(`体力不足,当前体力${physical.current},最低体力${physical.min},请手动补充体力后重试`)
|
||||
}
|
||||
// 创建秘境参数对象,初始化值为0
|
||||
let domainParam = new AutoDomainParam();
|
||||
|
||||
//关闭分解
|
||||
domainParam.autoArtifactSalvage = false
|
||||
//关闭榨干原粹树脂
|
||||
domainParam.specifyResinUse = true
|
||||
|
||||
//配置树脂使用优先级
|
||||
if (resinPriorityList.length > 0) {
|
||||
domainParam.SetResinPriorityList(...resinPriorityList)
|
||||
@@ -117,7 +120,6 @@ async function autoDomain(autoFight) {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function autoLeyLineOutcrop(autoLeyLineOutcrop) {
|
||||
//todo :地脉花
|
||||
// autoLeyLineOutcrop = {
|
||||
// "count": 0,
|
||||
// "country": "country_cb3d792be8db",
|
||||
@@ -175,6 +177,117 @@ async function autoLeyLineOutcrop(autoLeyLineOutcrop) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动执行幽境危战任务的异步函数
|
||||
* @param autoStygianOnslaught
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function autoStygianOnslaught(autoStygianOnslaught) {
|
||||
// autoStygianOnslaught = {
|
||||
// /**boss 名字 1~3 */
|
||||
// bossNum: 1,
|
||||
// /**结束后是否自动分解圣遗物*/
|
||||
// autoArtifactSalvage: false,
|
||||
// /**指定树脂的使用次数*/
|
||||
// specifyResinUse: false,
|
||||
// /**自定义使用树脂优先级*/
|
||||
// resinPriorityList: [""],
|
||||
// /** 使用原粹树脂刷取副本次数*/
|
||||
// originalResinUseCount: 0,
|
||||
// /** 使用浓缩树脂刷取副本次数*/
|
||||
// condensedResinUseCount: 0,
|
||||
// /** 使用须臾树脂刷取副本次数*/
|
||||
// transientResinUseCount: 0,
|
||||
// /** 使用脆弱树脂刷取副本次数*/
|
||||
// fragileResinUseCount: 0,
|
||||
// /**指定战斗队伍*/
|
||||
// fightTeamName: undefined
|
||||
// }
|
||||
log.debug(`autoStygianOnslaught ={0}`, JSON.stringify(autoStygianOnslaught))
|
||||
log.info(`{0}`, "开始执行幽境任务")
|
||||
let param = new AutoStygianOnslaughtParam()
|
||||
param.specifyResinUse = autoStygianOnslaught?.specifyResinUse || param.specifyResinUse
|
||||
|
||||
//定死做预留冗余 先不实现 不能指定次数 只能指定启用
|
||||
let physical_domain = autoStygianOnslaught?.physical||[]
|
||||
// || [
|
||||
// {order: 0, name: config.user.physical.names[0], count: 1, open: true},
|
||||
// {order: 1, name: config.user.physical.names[1], count: 0, open: false},
|
||||
// {order: 2, name: config.user.physical.names[2], count: 0, open: false},
|
||||
// {order: 3, name: config.user.physical.names[3], count: 0, open: false},
|
||||
// ]
|
||||
|
||||
if (param.specifyResinUse && ((!physical_domain) || physical_domain.filter(item => item?.open).length === 0)) {
|
||||
const names = config.user.physical.names;
|
||||
physical_domain = []
|
||||
names.forEach((name, index) => {
|
||||
physical_domain.push({order: index, name: name, open: index === 0, count: 1})
|
||||
})
|
||||
}
|
||||
|
||||
physical_domain?.sort((a, b) => a.order - b.order)
|
||||
// 不包含原粹树脂的和
|
||||
const noOriginalSum = physical_domain.filter(item => item?.name.trim() !== config.user.physical.names[0])
|
||||
.filter(item => item?.open).length;//求和
|
||||
// 只包含原粹树脂的和
|
||||
const originalSum = physical_domain.filter(item => item?.name?.trim() === config.user.physical.names[0])
|
||||
.filter(item => item?.open).length;
|
||||
const physical_domain_filter = physical_domain.filter(item => item?.open);
|
||||
const resinPriorityList = physical_domain_filter.map(item => item?.name?.trim())
|
||||
// /** 树脂使用优先级列表 */
|
||||
// resinPriorityList: string[];
|
||||
// /** 使用原粹树脂次数 */
|
||||
// originalResinUseCount: number;
|
||||
// /** 使用浓缩树脂次数 */
|
||||
// condensedResinUseCount: number;
|
||||
// /** 使用须臾树脂次数 */
|
||||
// transientResinUseCount: number;
|
||||
// /** 使用脆弱树脂次数 */
|
||||
// fragileResinUseCount: number;
|
||||
await sleep(1000)
|
||||
//流程->返回主页 打开地图 返回主页
|
||||
const physicalOcr = await ocrPhysical(true, true)
|
||||
config.user.physical.current = physicalOcr.current
|
||||
config.user.physical.min = physicalOcr.min
|
||||
const physical = config.user.physical
|
||||
if (physical.current < physical.min && noOriginalSum <= 0 && originalSum > 0) {
|
||||
throwError(`体力不足,当前体力${physical.current},最低体力${physical.min},请手动补充体力后重试`)
|
||||
}
|
||||
|
||||
|
||||
param.bossNum = autoStygianOnslaught?.bossNum > 0 && autoStygianOnslaught?.bossNum <= 3 ? autoStygianOnslaught.bossNum : param.bossNum
|
||||
param.fightTeamName = autoStygianOnslaught?.fightTeamName?.trim() !== "" ? autoStygianOnslaught.fightTeamName.trim() : param.fightTeamName
|
||||
if (resinPriorityList.length > 0) {
|
||||
param.SetResinPriorityList(...resinPriorityList)
|
||||
param.originalResinUseCount = physical_domain_filter.find(item => item?.name?.trim() === config.user.physical.names[0] && item?.open)?.count || 0
|
||||
param.condensedResinUseCount = physical_domain_filter.find(item => item?.name?.trim() === config.user.physical.names[1] && item?.open)?.count || 0
|
||||
param.transientResinUseCount = physical_domain_filter.find(item => item?.name?.trim() === config.user.physical.names[2] && item?.open)?.count || 0
|
||||
param.fragileResinUseCount = physical_domain_filter.find(item => item?.name?.trim() === config.user.physical.names[3] && item?.open)?.count || 0
|
||||
}
|
||||
|
||||
await sleep(1000)
|
||||
try {
|
||||
// 复活重试
|
||||
for (let i = 0; i < config.run.retry_count; i++) {
|
||||
try {
|
||||
await dispatcher.RunAutoStygianOnslaughtTask(param)
|
||||
// 其他场景不重试
|
||||
break;
|
||||
} catch (e) {
|
||||
const errorMessage = e.message
|
||||
if (errorMessage.includes("复活")) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
log.info(`{0}`, "执行完成")
|
||||
// 退出危战
|
||||
await outStygianOnslaughtUI()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动执行列表处理函数
|
||||
* @param {Array} autoRunOrderList - 包含自动配置的数组
|
||||
@@ -187,6 +300,8 @@ async function autoRunList(autoRunOrderList) {
|
||||
await autoDomain(item.autoFight);
|
||||
} else if (item.runType === config.user.runTypes[1]) {
|
||||
await autoLeyLineOutcrop(item.autoLeyLineOutcrop);
|
||||
} else if (item.runType === config.user.runTypes[2]) {
|
||||
await autoStygianOnslaught(item.autoStygianOnslaught);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,13 +361,15 @@ async function loadMode(Load, autoOrderSet, runConfig) {
|
||||
runType: runType, // 运行类型
|
||||
days: days, // 执行日期(数组)
|
||||
autoFight: undefined, // 秘境信息对象
|
||||
autoLeyLineOutcrop: undefined // 地脉信息对象
|
||||
autoLeyLineOutcrop: undefined, // 地脉信息对象
|
||||
autoStygianOnslaught: undefined // 幽境信息对象
|
||||
}
|
||||
|
||||
|
||||
if (!config.user.runTypes.includes(runType)) {
|
||||
throwError(`运行类型${runType}输入错误`)
|
||||
} else if (config.user.runTypes[0] === runType) {
|
||||
}
|
||||
else if (config.user.runTypes[0] === runType) {
|
||||
// 创建秘境信息对象
|
||||
let autoFight = {
|
||||
domainName: undefined,//秘境名称
|
||||
@@ -297,7 +414,8 @@ async function loadMode(Load, autoOrderSet, runConfig) {
|
||||
autoFight.sundaySelectedValue = sundaySelectedValue // 周日|限时选择的值
|
||||
|
||||
autoOrder.autoFight = autoFight // 将秘境信息对象添加到秘境顺序对象中
|
||||
} else if (config.user.runTypes[1] === runType) {
|
||||
}
|
||||
else if (config.user.runTypes[1] === runType) {
|
||||
//"|队伍名称|国家|刷几轮|花类型|好感队|是否使用脆弱树脂|是否使用须臾树脂|是否前往合成台合成浓缩树脂|是否使用冒险之证|发送详细通知|战斗超时时间,..."
|
||||
let autoLeyLineOutcrop = {
|
||||
count: 0, // 刷几次(0=自动/无限)
|
||||
@@ -345,6 +463,71 @@ async function loadMode(Load, autoOrderSet, runConfig) {
|
||||
|
||||
autoOrder.autoLeyLineOutcrop = autoLeyLineOutcrop // 将地脉信息对象添加到顺序对象中
|
||||
}
|
||||
else if (config.user.runTypes[2] === runType) {
|
||||
let autoStygianOnslaught = {
|
||||
bossNum: undefined,//boss1-3
|
||||
fightTeamName: "",//队伍名称
|
||||
specifyResinUse: undefined,//自定义树脂使用
|
||||
physical: [
|
||||
{order: 0, name: config.user.physical.names[1], open: true, count: 1},
|
||||
{order: 1, name: config.user.physical.names[0], open: true, count: 1},
|
||||
{order: 2, name: config.user.physical.names[2], open: false, count: 1},
|
||||
{order: 3, name: config.user.physical.names[3], open: false, count: 1}
|
||||
],//副本轮数
|
||||
}
|
||||
if (index <= arr.length - 1) {
|
||||
const bossNum = parseInteger(arr[index]);
|
||||
if (bossNum && bossNum > 0 && bossNum <= 3) {
|
||||
autoStygianOnslaught.bossNum = bossNum
|
||||
}
|
||||
}
|
||||
index++
|
||||
if (index <= arr.length - 1) {
|
||||
const fightTeamName = arr[index];
|
||||
if (fightTeamName && fightTeamName.trim() !== "") {
|
||||
autoStygianOnslaught.fightTeamName = fightTeamName
|
||||
}
|
||||
}
|
||||
index++
|
||||
if (index <= arr.length - 1) {
|
||||
autoStygianOnslaught.specifyResinUse = arr[index].trim() !== ""
|
||||
}
|
||||
if (autoStygianOnslaught.specifyResinUse) {
|
||||
index++
|
||||
let line = 0
|
||||
if (index <= arr.length - 1) {
|
||||
if (arr[index]?.trim() !== "") {
|
||||
const physical = []
|
||||
const physicals = arr[index].trim().split("/");
|
||||
for (let i = 0; i < physicals.length; i++) {
|
||||
const item = physicals[i];
|
||||
physical.push({order: i, name: item, open: true, count: 1})
|
||||
}
|
||||
line = physical.length
|
||||
autoStygianOnslaught.physical = physical
|
||||
}
|
||||
}
|
||||
index++
|
||||
if (index <= arr.length - 1) {
|
||||
if (line > 0 && arr[index]?.trim() !== "") {
|
||||
const counts = arr[index].trim().split("/")
|
||||
.map(item => {
|
||||
let count = parseInteger(item) || 1;
|
||||
return count
|
||||
});
|
||||
autoStygianOnslaught.physical.forEach((item, index) => {
|
||||
try {
|
||||
item.count = counts[index] || 1;
|
||||
} catch (e) {
|
||||
log.warn(`解析${item.name}数量失败`)
|
||||
throwError(`解析${item.name}数量失败`)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
autoOrder.autoStygianOnslaught = autoStygianOnslaught
|
||||
}
|
||||
|
||||
// 将秘境顺序对象添加到列表中
|
||||
autoOrderSet.add(autoOrder)
|
||||
@@ -479,10 +662,40 @@ async function main() {
|
||||
let runConfig = config.run.config;
|
||||
//"队伍名称|秘境名称/刷取物品名称|刷几轮|限时/周日|周几执行(0-6)不填默认执行|执行顺序,..."
|
||||
const autoRunOrderList = await initRunOrderList(runConfig);
|
||||
const list = autoRunOrderList.filter(item =>
|
||||
let list = autoRunOrderList.filter(item =>
|
||||
(item.runType === config.user.runTypes[0] && item?.autoFight.domainRoundNum > 0)
|
||||
|| (item.runType === config.user.runTypes[1] && item?.autoLeyLineOutcrop.count > 0)
|
||||
|| (item.runType === config.user.runTypes[1] && item?.autoLeyLineOutcrop.count > 0)||(item.runType === config.user.runTypes[2])
|
||||
)
|
||||
|
||||
const hasStygianOnslaught = list.some(item => item.runType === config.user.runTypes[2]);
|
||||
if (hasStygianOnslaught) {
|
||||
log.info(`{0}`,`检查幽境危战紊乱爆发期开放`)
|
||||
try {
|
||||
await toMainUi()
|
||||
const isStygianOnslaught = await findStygianOnslaught();
|
||||
if (isStygianOnslaught) {
|
||||
//圣遗物秘境名称
|
||||
const holyRelicDomainNames = config.domainList.filter(item => !item.hasOrder).map(item => item.name);
|
||||
const filter = list.find(item => item.runType === config.user.runTypes[1] && holyRelicDomainNames.includes(item.autoFight.domainName));
|
||||
if (filter) {
|
||||
// 幽境危战添加秘境顺序前
|
||||
list.forEach(item => {
|
||||
if (item.runType === config.user.runTypes[2]) {
|
||||
item.order = Math.max(filter.order + 1, item.order)
|
||||
}
|
||||
})
|
||||
list.sort((item1, item2) => item2.order - item1.order)
|
||||
}
|
||||
log.info(`{0}`,`幽境危战紊乱爆发期已开启`)
|
||||
} else {
|
||||
log.info(`{0}`,`幽境危战紊乱爆发期已结束`)
|
||||
list = list.filter(item => item.runType !== config.user.runTypes[2])
|
||||
}
|
||||
} finally {
|
||||
await toMainUi()
|
||||
}
|
||||
}
|
||||
|
||||
if (list?.length > 0) {
|
||||
//循环跑
|
||||
while (true) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "自动体力计划",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.2",
|
||||
"description": "",
|
||||
"settings_ui": "settings.json",
|
||||
"main": "main.js",
|
||||
"bgi_version": "0.57.2",
|
||||
"bgi_version": "0.58.0",
|
||||
"key": "oiJbmjU2R0NniiwiZxh",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
369
repo/js/AutoPlan/utils/activity.js
Normal file
369
repo/js/AutoPlan/utils/activity.js
Normal file
@@ -0,0 +1,369 @@
|
||||
const ocrRegionConfig = {
|
||||
activity: {x: 267, y: 197, width: 226, height: 616},//活动识别区域坐标和尺寸
|
||||
remainingTime: {x: 497, y: 202, width: 1417, height: 670},//剩余时间识别区域坐标和尺寸
|
||||
}
|
||||
const xyConfig = {
|
||||
top: {x: 344, y: 273},
|
||||
bottom: {x: 342, y: 791},
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 滚动页面的异步函数
|
||||
* @param {number} totalDistance - 总滚动距离
|
||||
* @param {boolean} [isUp=false] - 是否向上滚动,默认为false(向下滚动)
|
||||
* @param {number} [waitCount=6] - 每隔多少步等待一次
|
||||
* @param {number} [stepDistance=30] - 每步滚动的距离
|
||||
* @param {number} [delayMs=1] - 等待的延迟时间(毫秒)
|
||||
*/
|
||||
async function scrollPage(totalDistance, isUp = false, waitCount = 6, stepDistance = 30, delayMs = 1000) {
|
||||
let ms = 600
|
||||
await sleep(ms);
|
||||
leftButtonDown(); // 按下左键
|
||||
await sleep(ms);
|
||||
// 计算总步数
|
||||
let steps = Math.floor(totalDistance / stepDistance);
|
||||
// 开始循环滚动
|
||||
for (let j = 0; j < steps; j++) {
|
||||
// 计算剩余距离
|
||||
let remainingDistance = totalDistance - j * stepDistance;
|
||||
// 确定本次移动距离
|
||||
let moveDistance = remainingDistance < stepDistance ? remainingDistance : stepDistance;
|
||||
// 如果是向上滚动,则移动距离取反
|
||||
if (isUp) {
|
||||
//向上活动
|
||||
moveDistance = -moveDistance
|
||||
}
|
||||
// 执行鼠标移动
|
||||
moveMouseBy(0, -moveDistance);
|
||||
// 取消注释后会在每一步后等待
|
||||
// await sleep(delayMs);
|
||||
// 每隔waitCount步等待一次
|
||||
if (j % waitCount === 0) {
|
||||
await sleep(delayMs);
|
||||
}
|
||||
}
|
||||
// 滚动完成后释放左键
|
||||
await sleep(ms);
|
||||
leftButtonUp();
|
||||
await sleep(ms);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据活动页面进行滚动操作
|
||||
* @param {boolean} isUp - 滚动方向,true表示向上滚动,false表示向下滚动
|
||||
* @param {number} total - 滚动总量
|
||||
* @param {number} waitCount - 等待次数
|
||||
* @param {number} stepDistance - 每次滚动的步长距离
|
||||
* @param {number} scrollPageCount - 滚动页面次数,默认从config中获取
|
||||
*/
|
||||
async function scrollPagesByActivity(isUp = false, total = 90, waitCount = 6, stepDistance = 30, scrollPageCount = config.scrollPageCount) {
|
||||
// 根据滚动方向设置坐标位置
|
||||
// 如果是向上滚动,使用顶部坐标;否则使用底部坐标
|
||||
let x = isUp ? xyConfig.top.x : xyConfig.bottom.x; // 根据滚动方向获取x坐标
|
||||
let y = isUp ? xyConfig.top.y : xyConfig.bottom.y; // 根据滚动方向获取y坐标
|
||||
// 记录滑动方向
|
||||
log.info(`活动页面-${isUp ? '向上' : '向下'}滑动`);
|
||||
// 注释:坐标信息已注释掉,避免日志过多
|
||||
// log.info(`坐标:${x},${y}`);
|
||||
// 根据配置的滑动次数执行循环
|
||||
for (let i = 0; i < scrollPageCount; i++) {
|
||||
// 移动到坐标位置
|
||||
await moveMouseTo(x, y)
|
||||
//80 18次滑动偏移量 46次测试未发现偏移
|
||||
await scrollPage(total, isUp, waitCount, stepDistance)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到活动页面最顶部(优化版)
|
||||
* 通过连续检测顶部活动名称相同来确认已到顶,更加健壮
|
||||
* @param {Object} ocrRegion - OCR识别区域,默认为活动列表区域
|
||||
* @throws {Error} 如果超过最大尝试次数仍未检测到稳定顶部,则抛出错误
|
||||
*/
|
||||
async function scrollPagesByActivityToTop(ocrRegion = ocrRegionConfig.activity) {
|
||||
let ms = 800; // 等待时间,单位毫秒
|
||||
let topActivityName = null; // 上一次检测到的顶部活动名称
|
||||
let sameTopCount = 0; // 连续出现相同顶部名称的次数
|
||||
const requiredSameCount = 1; // 需要连续几次相同才确认到顶(推荐 2~3)
|
||||
let attemptIndex = 0; // 总尝试次数计数器
|
||||
const maxAttempts = config.toTopCount; // 可配置,默认为15次
|
||||
|
||||
log.info("开始滚动到活动页面顶部...");
|
||||
|
||||
while (attemptIndex < maxAttempts) {
|
||||
attemptIndex++;
|
||||
log.info(`第 {attemptIndex} 次尝试回顶`, attemptIndex);
|
||||
|
||||
// 移动鼠标到安全位置,避免干扰截图
|
||||
await moveMouseTo(0, 20);
|
||||
|
||||
// 截图 + OCR 识别活动列表区域
|
||||
let captureRegion = null;
|
||||
try {
|
||||
captureRegion = captureGameRegion();
|
||||
const ocrObject = RecognitionObject.Ocr(
|
||||
ocrRegion.x,
|
||||
ocrRegion.y,
|
||||
ocrRegion.width,
|
||||
ocrRegion.height
|
||||
);
|
||||
// 可选:提升识别率
|
||||
// ocrObject.threshold = 0.8;
|
||||
|
||||
let resList = captureRegion.findMulti(ocrObject);
|
||||
// captureRegion.dispose();
|
||||
|
||||
// 如果完全没识别到任何活动,可能是页面异常或已在顶(极少情况)
|
||||
if (resList.length === 0) {
|
||||
log.warn("顶部OCR未识别到任何活动条目,可能是页面为空或识别失败");
|
||||
// 再尝试一次向上滚大距离
|
||||
// await scrollPagesByActivity(true); // true = 向上
|
||||
await scrollPagesByActivity(true, 80 * 4, 6, 60, 1);
|
||||
await sleep(ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 取当前识别到的最顶部活动名称(resList[0] 通常是列表最上面的)
|
||||
const currentTopName = resList[0].text.trim();
|
||||
|
||||
log.info(`当前检测到的顶部活动: {currentTopName}`, currentTopName);
|
||||
|
||||
// 判断是否与上一次相同
|
||||
if (currentTopName === topActivityName) {
|
||||
sameTopCount++;
|
||||
log.debug(`顶部活动连续相同 ${sameTopCount} 次`);
|
||||
|
||||
if (sameTopCount >= requiredSameCount) {
|
||||
log.info(`已连续 {sameTopCount} 次检测到相同顶部活动,确认回到页面最顶部!`, sameTopCount);
|
||||
return; // 成功回到顶部
|
||||
}
|
||||
} else {
|
||||
// 顶部名称变了,说明还在向上滚动,重置计数
|
||||
topActivityName = currentTopName;
|
||||
sameTopCount = 1; // 这次算第一次
|
||||
}
|
||||
|
||||
// 未达到稳定状态,继续向上滚动一页(可根据实际情况调整滚动距离)
|
||||
// 这里使用更大滚动距离确保能快速回顶
|
||||
// await scrollPagesByActivity(true); // true = 向上
|
||||
await scrollPagesByActivity(true, 80 * 4, 6, 60, 1);
|
||||
|
||||
await sleep(ms); // 给页面滚动和渲染留时间
|
||||
} finally {
|
||||
// 确保资源被正确释放
|
||||
if (captureRegion) {
|
||||
captureRegion.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 超过最大尝试次数仍未稳定
|
||||
throw new Error(`回到活动页面顶部失败:尝试 ${attemptIndex} 次后仍未检测到稳定顶部活动`);
|
||||
}
|
||||
|
||||
/**
|
||||
* OCR识别活动剩余时间的函数
|
||||
* @param {Object} ocrRegion - OCR识别的区域坐标和尺寸
|
||||
* @param {string} activityName - 活动名称
|
||||
* @param {string} key - 要识别的关键词,默认为
|
||||
* @returns {string|null} 返回识别到的剩余时间文本,若未识别到则返回null
|
||||
*/
|
||||
async function OcrKey(activityName, key, ocrRegion = ocrRegionConfig.remainingTime) {
|
||||
if (!key) {
|
||||
return null
|
||||
}
|
||||
let captureRegion = captureGameRegion(); // 获取游戏区域截图
|
||||
try {
|
||||
let list = new Array()
|
||||
const ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); // 创建OCR识别对象
|
||||
// ocrObject.threshold = 1.0;
|
||||
let resList = captureRegion.findMulti(ocrObject); // 在指定区域进行OCR识别
|
||||
for (let res of resList) {
|
||||
log.debug(`[info][{key}]{activityName}--{time}`, key, activityName, res.text); // 记录日志
|
||||
if (res.text.includes(key)) { // 检查识别结果是否包含关键词
|
||||
log.debug(`[{key}][命中]{activityName}--{time}`, key, activityName, res.text); // 记录日志
|
||||
list.push(res.text.trim())
|
||||
// return res.text // 返回识别到的文本
|
||||
}
|
||||
}
|
||||
if (list.length > 0) {
|
||||
return list.join('<-->')
|
||||
}
|
||||
// 没有识别到剩余时间
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
captureRegion.dispose(); // 释放截图资源
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ... existing code ...
|
||||
|
||||
async function scrollFindActivity(name, key, value,activityKey = "F5") {
|
||||
const ms = 1000;
|
||||
// 1. 打开活动页面(默认 F5)
|
||||
await keyPress(activityKey);
|
||||
await sleep(ms * 2);
|
||||
// 2. 先强制滚动到最顶部(非常重要!)
|
||||
try {
|
||||
await scrollPagesByActivityToTop();
|
||||
await sleep(ms);
|
||||
} catch (e) {
|
||||
log.warn("回到顶部失败,但继续尝试执行");
|
||||
}
|
||||
let findActivity = {
|
||||
name: false,
|
||||
key: false,
|
||||
value: false
|
||||
};
|
||||
|
||||
if (!name) {
|
||||
log.warn("未指定活动名称,无法查找");
|
||||
return findActivity;
|
||||
}
|
||||
|
||||
let lastPageBottomName = null;
|
||||
let sameBottomCount = 0;
|
||||
const sameBottomCountMax = 1;
|
||||
let scannedPages = 0;
|
||||
const maxPages = 25;
|
||||
let previousPageActivities = new Set();
|
||||
|
||||
// 4. 主循环:逐页向下扫描
|
||||
while (scannedPages < maxPages) {
|
||||
scannedPages++;
|
||||
log.info(`正在扫描第 ${scannedPages} 页`);
|
||||
|
||||
await moveMouseTo(0, 20);
|
||||
|
||||
let captureRegion = null;
|
||||
try {
|
||||
captureRegion = captureGameRegion();
|
||||
|
||||
const ocrObject = RecognitionObject.Ocr(
|
||||
ocrRegionConfig.activity.x,
|
||||
ocrRegionConfig.activity.y,
|
||||
ocrRegionConfig.activity.width,
|
||||
ocrRegionConfig.activity.height
|
||||
);
|
||||
let resList = captureRegion.findMulti(ocrObject);
|
||||
|
||||
if (resList.length === 0) {
|
||||
log.info("当前页未识别到任何活动,视为已到页面底部");
|
||||
break;
|
||||
}
|
||||
|
||||
// ============ 重复页检测 ============
|
||||
const currentPageNames = new Set();
|
||||
for (let res of resList) {
|
||||
currentPageNames.add(res.text.trim());
|
||||
}
|
||||
|
||||
if (previousPageActivities.size > 0) {
|
||||
let overlapCount = 0;
|
||||
for (let actName of currentPageNames) {
|
||||
if (previousPageActivities.has(actName)) overlapCount++;
|
||||
}
|
||||
const overlapRatio = overlapCount / previousPageActivities.size;
|
||||
|
||||
if (overlapRatio >= 0.7) {
|
||||
log.info(`检测到当前页与上一页高度重复(重合率 ${Math.round(overlapRatio * 100)}%),已到达底部,停止扫描`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
previousPageActivities = currentPageNames;
|
||||
// ===================================
|
||||
|
||||
let currentPageBottomName = null;
|
||||
let foundTarget = false;
|
||||
|
||||
// 遍历当前页所有识别到的活动条目
|
||||
for (let res of resList) {
|
||||
const activityName = res.text.trim();
|
||||
currentPageBottomName = activityName; // 更新底部活动名
|
||||
|
||||
// 【关键修改】检查是否是目标活动名称(精确匹配)
|
||||
if (activityName.includes(name)) {
|
||||
findActivity.name = true;
|
||||
log.info(`找到目标活动:${activityName}`);
|
||||
await click(res.x, res.y);
|
||||
await sleep(ms);
|
||||
foundTarget = true;
|
||||
|
||||
// 如果没有指定 key,找到活动名称就直接返回成功
|
||||
if (!key) {
|
||||
log.info(`已找到指定活动 [${activityName}],无需匹配关键字`);
|
||||
findActivity.key = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果指定了 key,进行关键字匹配
|
||||
const text = await OcrKey(activityName, key);
|
||||
if (text && text.includes(key)) {
|
||||
log.info(`已找到指定活动 [${activityName}] 且包含关键字 [${key}]`);
|
||||
findActivity.key = true;
|
||||
if (value) {
|
||||
findActivity.value = text.includes(value)
|
||||
}
|
||||
if (findActivity.value) {
|
||||
log.info(`已找到指定活动 [${activityName}] 且包含关键字 [${key}] 和值 [${value}]`);
|
||||
}
|
||||
} else {
|
||||
log.info(`活动 [${activityName}] 不包含关键字 [${key}],继续查找`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到目标活动,直接退出主循环
|
||||
if (foundTarget) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 5. 判断是否已到达页面底部(单一判断逻辑)
|
||||
if (currentPageBottomName && currentPageBottomName === lastPageBottomName) {
|
||||
sameBottomCount++;
|
||||
if (sameBottomCount >= sameBottomCountMax) {
|
||||
log.info(`连续 ${sameBottomCountMax} 次检测到相同底部活动,已确认到达页面最底部,扫描结束`);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
sameBottomCount = 0;
|
||||
}
|
||||
lastPageBottomName = currentPageBottomName;
|
||||
|
||||
// 6. 向下滑动一页,继续下一轮
|
||||
await scrollPagesByActivity(false);
|
||||
await sleep(ms);
|
||||
} finally {
|
||||
if (captureRegion) {
|
||||
captureRegion.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
return findActivity;
|
||||
}
|
||||
|
||||
|
||||
async function findStygianOnslaught() {
|
||||
const findActivity = {
|
||||
name: "幽境危战",
|
||||
key: "紊乱爆发期",
|
||||
value: "已结束",
|
||||
}
|
||||
const findResult = await scrollFindActivity(findActivity.name, findActivity.key, findActivity.value);
|
||||
if (findResult.name && findResult.key && findResult.value) {
|
||||
// 幽境危战 紊乱爆发期 已结束
|
||||
return false
|
||||
}
|
||||
//正常模式
|
||||
return true
|
||||
}
|
||||
|
||||
export {
|
||||
findStygianOnslaught
|
||||
}
|
||||
@@ -213,6 +213,55 @@ async function outDomainUI() {
|
||||
|
||||
}
|
||||
|
||||
const isInOutStygianOnslaughtUI = async () =>{
|
||||
const text = "退出挑战";
|
||||
const ocrRegion = {
|
||||
x: 509,
|
||||
y: 259,
|
||||
w: 901,
|
||||
h: 563
|
||||
}
|
||||
const find = await findText(text, ocrRegion.x, ocrRegion.y, ocrRegion.w, ocrRegion.h)
|
||||
log.debug("识别结果:{1}", find)
|
||||
return find && find.includes(text)
|
||||
}
|
||||
async function outStygianOnslaughtUI() {
|
||||
log.info(`{0}`,"退出挑战");
|
||||
const ocrRegion = {
|
||||
x: 509,
|
||||
y: 259,
|
||||
w: 901,
|
||||
h: 563
|
||||
}
|
||||
let ms = 300
|
||||
let index = 1
|
||||
let tryMax = false
|
||||
let inMainUI = false
|
||||
await sleep(ms);
|
||||
while (!await isInOutStygianOnslaughtUI()) {
|
||||
if (isInMainUI()) {
|
||||
inMainUI = true
|
||||
break
|
||||
}
|
||||
await sleep(ms);
|
||||
await keyPress("ESCAPE");
|
||||
await sleep(ms * 2);
|
||||
if (index > 3) {
|
||||
log.error(`多次尝试匹配退出秘境界面失败 假定已经退出处理`);
|
||||
tryMax = true
|
||||
break
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
if ((!tryMax) && (!inMainUI) && await isInOutStygianOnslaughtUI()) {
|
||||
try {
|
||||
//点击确认按钮
|
||||
await findTextAndClick('退出挑战', ocrRegion.x, ocrRegion.y, ocrRegion.w, ocrRegion.h)
|
||||
} catch (e) {
|
||||
// log.error(`多次尝试点击确认失败 假定已经退出处理`);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 在指定区域内查找文本内容
|
||||
* @param {string} text - 要查找的文本内容
|
||||
@@ -343,6 +392,8 @@ export {
|
||||
toMainUi,
|
||||
isInOutDomainUI,
|
||||
outDomainUI,
|
||||
isInOutStygianOnslaughtUI,
|
||||
outStygianOnslaughtUI,
|
||||
findTextAndClick,
|
||||
throwError,
|
||||
}
|
||||
Reference in New Issue
Block a user