js: 七圣召唤七日历练: 支持在打牌失败时自动逐个尝试备用策略 (#1995)

This commit is contained in:
Patrick-Ze
2025-09-26 23:04:11 +08:00
committed by GitHub
parent 60a20d1f41
commit 2c56de636b
12 changed files with 372 additions and 63 deletions

View File

@@ -10,6 +10,7 @@
| ---- | ---- |
| 默认牌组 | 填写你游戏中'雷神柯莱刻晴'队所在的牌组名 |
| 临时牌组 | 填写用来导入分享码的牌组名 |
| 自动尝试备用策略 | 打牌失败时自动逐个尝试备用策略。如果你不想亲自打那些脚本打不过的挑战,也不在乎多次尝试的耗时,可以勾选此项 |
| 密码 | 随便填 |
**注意:**
@@ -25,3 +26,14 @@
如果你发现默认策略打不赢或打得太慢,欢迎提交你编写的策略。
## 如何添加或删除备用策略
脚本自动将`牌组策略`文件夹下以数字加小数点开头的txt文件识别为备用策略例如`10.莫娜砂糖琴.txt`
如果启用了备用策略功能,打牌失败时,按照数字从小到大的顺序自动逐个尝试。
目前脚本内置了8个备用策略都是BetterGI内建的策略
如果想要添加更多的备用策略,参考`_策略模板.txt`完成编辑后,将文件命名为数字加`.`开头就行。
如果觉得尝试次数不用这么多直接删除备用策略对应的txt文件即可。

View File

@@ -1,6 +1,8 @@
// 存储挑战玩家信息
let textArray = [];
let skipNum = 0;
let allStrategy = {};
let fallbackStrategyList = [];
// 切换到指定的队伍
async function switchCardTeam(Name, shareCode) {
@@ -48,7 +50,7 @@ async function switchCardTeam(Name, shareCode) {
}
let userDefault = false;
if (shareCode) {
if (Name !== settings.defaultPartyName && shareCode) {
captureRegion = captureGameRegion();
let res = captureRegion.find(RecognitionObject.ocr(1140, 732, 83, 55));
if (res.text === "确认") {
@@ -57,18 +59,18 @@ async function switchCardTeam(Name, shareCode) {
click(731, 998); // 编辑牌组
}
await sleep(800);
click(1756, 48); // ... 按钮
click(1756, 48); // ... 按钮
await sleep(200);
click(1546, 178); // 使用分享码
await sleep(500);
click(960, 520); // 输入区域
click(1546, 178); // 使用分享码
await sleep(500);
click(960, 520); // 输入区域
await sleep(1000);
log.info("输入分享码 {0}", shareCode);
await inputText(shareCode);
await sleep(500);
click(1166, 750); // 导入
click(1166, 750); // 导入
await stopNow();
click(1720, 1020); // 保存
click(1720, 1020); // 保存
await stopNow();
captureRegion = captureGameRegion();
res = captureRegion.find(RecognitionObject.ocr(770, 516, 381, 43));
@@ -221,6 +223,7 @@ async function checkChallengeResults() {
const region2 = RecognitionObject.ocr(1520, 170, 160, 40); // 退出位置
let capture = captureGameRegion();
let res1 = capture.find(region1);
let success = false;
log.info(`结果识别:${res1.text}`);
if (res1.text.includes("对局失败")) {
log.info("对局失败");
@@ -228,16 +231,13 @@ async function checkChallengeResults() {
click(754, 915); //退出挑战
await sleep(4000);
await autoConversation();
await sleep(1000);
return;
} else if (res1.text.includes("对局胜利")) {
log.info("对局胜利");
await sleep(1000);
click(754, 915); //退出挑战
await sleep(4000);
await autoConversation();
await sleep(1000);
return;
success = true;
} else {
log.info("挑战异常中断,对局失败");
await sleep(1000);
@@ -256,9 +256,9 @@ async function checkChallengeResults() {
click(754, 915); //退出挑战
await sleep(4000);
await autoConversation();
await sleep(1000);
return;
}
await sleep(1000);
return success;
}
//通过f和空格自动对话对话标志消失时停止await autoConversation();
@@ -441,46 +441,35 @@ function isTextMatch(target, source) {
return true;
}
// 获取某个角色的专用牌组的分享码。如无专用策略,则返回 null
function getShareCodeOfCharStrategy(charName) {
// 扫描所有带有分享码的牌组策略
function scanCardStrategy() {
const allFilesRaw = file.ReadPathSync("牌组策略");
let strategyMap = {};
for (const filePath of allFilesRaw) {
if (filePath.endsWith(".txt")) {
const parts = filePath.split("\\");
const fileName = parts.slice(-1)[0];
const baseName = fileName.split(".").slice(0, -1).join(".");
strategyMap[baseName] = filePath;
}
}
const content = file.readTextSync("牌组策略/雷神柯莱刻晴.txt");
let result = { team: settings.defaultPartyName, shareCode: null, defaultContent: content, content: content };
const matchFile = strategyMap[charName];
if (matchFile) {
const content = file.readTextSync(matchFile);
let shareCode = null;
for (const line of content.split("\n")) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith("//") && trimmedLine.includes("shareCode=")) {
const parts = trimmedLine.split("=");
if (parts[1]) {
shareCode = parts.slice(1, parts.length).join("=");
break;
const content = file.readTextSync(filePath);
let shareCode = null;
for (const line of content.split("\n")) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith("//") && trimmedLine.includes("shareCode=")) {
const parts = trimmedLine.split("=");
if (parts[1]) {
shareCode = parts.slice(1, parts.length).join("=");
break;
}
}
}
}
if (shareCode) {
result.team = settings.overwritePartyName;
result.shareCode = shareCode;
result.content = content;
log.debug("charName={0}, shareCode={1}", charName, shareCode);
} else {
log.error("策略文件中未找到有效的shareCode: {0}", matchFile);
if (shareCode) {
const fileName = filePath.split("\\").slice(-1)[0];
const baseName = fileName.split(".").slice(0, -1).join(".");
log.debug("baseName={0}, shareCode={1}", baseName, shareCode);
strategyMap[baseName] = { shareCode: shareCode, content: content };
} else {
log.warn("策略文件中未找到有效的shareCode: {0}", filePath);
}
}
}
log.info("使用牌组{0}与{1}对战", result.team, charName);
return result;
return strategyMap;
}
//检查是否有对应的挑战对手
@@ -514,16 +503,25 @@ async function searchAndClickTexts() {
if (index !== -1) {
// 找到匹配项,点击对应位置
log.info(`找到匹配文本: ${resText} (原存储文本: ${textArray[index].text})`);
const charName = textArray[index].text;
log.info(`找到匹配文本: ${resText} (原存储文本: ${charName})`);
skipNum = 0;
// 点击存储的位置
await keyMouseScript.runFile(`assets/ALT点击.json`);
await sleep(500);
res.click();
await sleep(500);
await keyMouseScript.runFile(`assets/ALT释放.json`);
const strategy = getShareCodeOfCharStrategy(textArray[index].text);
await Playcards(strategy);
let success = false;
const strategy = allStrategy[charName];
if (strategy) {
log.info("使用角色专用策略与{0}对战", charName);
success = await Playcards(strategy, settings.overwritePartyName, res);
}
if (!success) {
log.info("使用默认策略与{0}对战", charName);
success = await Playcards(allStrategy["雷神柯莱刻晴"], settings.defaultPartyName, res);
}
for (const strategyName of fallbackStrategyList) {
if (!success) {
log.info("使用备用策略{0}与{1}对战", strategyName, charName);
success = await Playcards(allStrategy[strategyName], settings.overwritePartyName, res);
}
}
// 从数组中移除已处理的文本
textArray.splice(index, 1);
@@ -587,22 +585,32 @@ async function waitOrCheckMaxCoin(wait_time_ms) {
}
}
//函数:对话和打牌
async function Playcards(strategy) {
// true和false对应打牌成功或失败
async function Playcards(strategy, teamName, pos) {
// 点击存储的位置
await keyMouseScript.runFile(`assets/ALT点击.json`);
await sleep(500);
pos.click();
await sleep(500);
await keyMouseScript.runFile(`assets/ALT释放.json`);
await sleep(800); //略微俯视,避免名字出现在选项框附近,导致错误点击
moveMouseBy(0, 1030);
await sleep(1000);
await autoConversation();
log.info("对话完成");
await sleep(1500);
const success = await switchCardTeam(strategy.team, strategy.shareCode);
const content = success ? strategy.content : strategy.defaultContent;
const success = await switchCardTeam(teamName, strategy.shareCode);
if (!success) {
keyPress("ESCAPE");
await sleep(2000);
return success;
}
click(1610, 900); //点击挑战
await waitOrCheckMaxCoin(8000);
await dispatcher.runTask(new SoloTask("AutoGeniusInvokation", { strategy: content }));
await dispatcher.runTask(new SoloTask("AutoGeniusInvokation", { strategy: strategy.content }));
await sleep(3000);
await checkChallengeResults();
await sleep(1000);
const win = await checkChallengeResults();
return win;
}
//前往一号桌
@@ -723,10 +731,22 @@ async function gotoTable6() {
async function main() {
//主流程
const nowTime = new Date();
log.info(`前往猫尾酒馆`);
await gotoTavern();
await captureAndStoreTexts();
allStrategy = scanCardStrategy();
if (settings.useFallbackStrategy) {
fallbackStrategyList = Object.keys(allStrategy).filter((key) => {
return /^(\d+)\./.test(key);
});
fallbackStrategyList.sort((a, b) => {
return parseInt(a) - parseInt(b);
});
log.info("已启用{0}个备用策略: {1}", fallbackStrategyList.length, fallbackStrategyList.join(", "));
} else {
log.info("未启用备用策略");
}
if (textArray.length != 0) {
await detectCardPlayer();
await searchAndClickTexts();

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "打牌一条龙",
"version": "2.4",
"version": "2.5",
"description": "完成每周的七圣召唤七日历练来客挑战。详见README.md",
"tags": [
"七圣召唤"

View File

@@ -9,6 +9,11 @@
"type": "input-text",
"label": "临时使用的游戏牌组名称(此牌组会被打牌策略的分享码覆盖)"
},
{
"name": "useFallbackStrategy",
"type": "checkbox",
"label": "打牌失败时自动逐个尝试备用策略"
},
{
"name": "passWord",
"type": "input-text",

View File

@@ -0,0 +1,37 @@
// shareCode=AXGxuSUMA4FR83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=莫娜|水{技能3消耗=1水骰子+2任意,技能2消耗=3水骰子,技能1消耗=3水骰子}
角色2=砂糖|风{技能3消耗=1风骰子+2任意,技能2消耗=3风骰子,技能1消耗=3风骰子}
角色3=琴|风{技能3消耗=1风骰子+2任意,技能2消耗=3风骰子,技能1消耗=4风骰子}
---
策略定义:
莫娜 使用 技能2
砂糖 使用 技能2
砂糖 使用 技能2
砂糖 使用 技能1
砂糖 使用 技能2
砂糖 使用 技能2
砂糖 使用 技能2
砂糖 使用 技能2
砂糖 使用 技能1
琴 使用 技能2
琴 使用 技能2
琴 使用 技能2
琴 使用 技能1
琴 使用 技能2
琴 使用 技能2
琴 使用 技能2
莫娜 使用 技能2
莫娜 使用 技能3
琴 使用 技能2
莫娜 使用 技能1
// 砂糖可能活着
// 后续的内容不太可能执行到,只是为了一些特殊对局
砂糖 使用 技能2
砂糖 使用 技能2
砂糖 使用 技能1
琴 使用 技能2
琴 使用 技能2
琴 使用 技能2
琴 使用 技能1

View File

@@ -0,0 +1,23 @@
// shareCode=AnHBuSEMAYER83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=刻晴|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=4雷骰子}
角色2=雷电将军|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=4雷骰子}
角色3=甘雨|冰{技能4消耗=1冰骰子+2任意,技能3消耗=3冰骰子,技能2消耗=5冰骰子,技能1消耗=3冰骰子}
---
策略定义:
雷电将军 使用 技能2
雷电将军 使用 技能3
雷电将军 使用 技能1
甘雨 使用 技能3
甘雨 使用 技能1
刻晴 使用 技能2
刻晴 使用 技能1
刻晴 使用 技能3
刻晴 使用 技能2
刻晴 使用 技能3
// 为了保证长对局死亡后有角色切换,加一些行动
雷电将军 使用 技能2
甘雨 使用 技能3
甘雨 使用 技能2
刻晴 使用 技能2

View File

@@ -0,0 +1,35 @@
// shareCode=A3EBuS4MBIGx83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=雷神|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=4雷骰子}
角色2=阿贝多|岩{技能3消耗=1岩骰子+2任意,技能2消耗=3岩骰子,技能1消耗=3岩骰子}
角色3=丘丘王|岩{技能3消耗=1岩骰子+2任意,技能2消耗=3岩骰子,技能1消耗=3岩骰子}
---
策略定义:
雷神 使用 技能2
阿贝多 使用 技能2
阿贝多 使用 技能3
阿贝多 使用 技能1
雷神 使用 技能3
雷神 使用 技能1
丘丘王 使用 技能1
丘丘王 使用 技能2
丘丘王 使用 技能2
丘丘王 使用 技能1
丘丘王 使用 技能2
丘丘王 使用 技能2
丘丘王 使用 技能1
丘丘王 使用 技能2
丘丘王 使用 技能2
丘丘王 使用 技能1
阿贝多 使用 技能1
阿贝多 使用 技能3
阿贝多 使用 技能3
阿贝多 使用 技能1
阿贝多 使用 技能3
阿贝多 使用 技能3
阿贝多 使用 技能1
// 后续的内容不太可能执行到,只是为了一些特殊对局
阿贝多 使用 技能3
阿贝多 使用 技能3
阿贝多 使用 技能1

View File

@@ -0,0 +1,26 @@
// shareCode=AXFBuSEMAYFx83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=重云|冰{技能3消耗=1冰骰子+2任意,技能2消耗=3冰骰子,技能1消耗=3冰骰子}
角色2=雷神|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=4雷骰子}
角色3=申鹤|冰{技能3消耗=1冰骰子+2任意,技能2消耗=3冰骰子,技能1消耗=3冰骰子}
---
策略定义:
雷神 使用 技能2
雷神 使用 技能3
雷神 使用 技能1
申鹤 使用 技能1
申鹤 使用 技能2
申鹤 使用 技能3
申鹤 使用 技能1
申鹤 使用 技能3
申鹤 使用 技能3
申鹤 使用 技能3
申鹤 使用 技能3
申鹤 使用 技能3
重云 使用 技能2
重云 使用 技能1
重云 使用 技能3
重云 使用 技能3
重云 使用 技能3
重云 使用 技能1

View File

@@ -0,0 +1,39 @@
// shareCode=AnGSuXAMAoGh83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=迪希雅|火{技能3消耗=1火骰子+2任意,技能2消耗=3火骰子,技能1消耗=4火骰子}
角色2=白术|草{技能3消耗=1草骰子+2任意,技能2消耗=3草骰子,技能1消耗=4草骰子}
角色3=皇女|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=3雷骰子}
---
策略定义:
迪希雅 使用 技能2
白术 使用 技能2
皇女 使用 技能2
白术 使用 技能2
白术 使用 技能1
迪希雅 使用 技能2
迪希雅 使用 技能1
皇女 使用 技能2
皇女 使用 技能3
皇女 使用 技能1
// 后续的内容不太可能执行到,只是为了一些特殊对局
迪希雅 使用 技能2
白术 使用 技能2
皇女 使用 技能2
白术 使用 技能2
白术 使用 技能1
迪希雅 使用 技能2
迪希雅 使用 技能1
皇女 使用 技能2
皇女 使用 技能3
皇女 使用 技能1

View File

@@ -0,0 +1,35 @@
// shareCode=AnGhuTAMAYGx83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=皇女|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=3雷骰子}
角色2=柯莱|草{技能3消耗=1草骰子+2任意,技能2消耗=3草骰子,技能1消耗=3草骰子}
角色3=莫娜|水{技能3消耗=1水骰子+2任意,技能2消耗=3水骰子,技能1消耗=3水骰子}
---
策略定义:
皇女 使用 技能2
柯莱 使用 技能2
柯莱 使用 技能2
柯莱 使用 技能1
莫娜 使用 技能2
皇女 使用 技能2
皇女 使用 技能3
皇女 使用 技能1
// 后续的内容不太可能执行到,只是为了一些特殊对局
柯莱 使用 技能2
皇女 使用 技能2
柯莱 使用 技能2
柯莱 使用 技能1
莫娜 使用 技能2
皇女 使用 技能2
皇女 使用 技能3
皇女 使用 技能1

View File

@@ -0,0 +1,44 @@
// shareCode=AXGxuTQMAoGh83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=莫娜|水{技能3消耗=1水骰子+2任意,技能2消耗=3水骰子,技能1消耗=3水骰子}
角色2=白术|草{技能3消耗=1草骰子+2任意,技能2消耗=3草骰子,技能1消耗=4草骰子}
角色3=皇女|雷{技能3消耗=1雷骰子+2任意,技能2消耗=3雷骰子,技能1消耗=3雷骰子}
---
策略定义:
莫娜 使用 技能2
皇女 使用 技能2
白术 使用 技能2
莫娜 使用 技能2
皇女 使用 技能2
白术 使用 技能2
白术 使用 技能1
莫娜 使用 技能2
皇女 使用 技能2
莫娜 使用 技能1
皇女 使用 技能1
// 后续的内容不太可能执行到,只是为了一些特殊对局
莫娜 使用 技能2
皇女 使用 技能2
白术 使用 技能2
莫娜 使用 技能2
皇女 使用 技能2
白术 使用 技能2
白术 使用 技能1
莫娜 使用 技能2
皇女 使用 技能2
莫娜 使用 技能1
皇女 使用 技能1

View File

@@ -0,0 +1,33 @@
// shareCode=F3HCuXAMGIEB83oQCCGR9HsQCDGh9bQQDEEx9rUQDFFB97YQDGFR+LcQDHFh+bgQDIEB
角色定义:
角色1=芙宁娜
角色2=卡维
角色3=吞星之鲸
---
策略定义:
芙宁娜 使用 技能2
卡维 使用 技能2
卡维 使用 技能2
卡维 使用 技能1
卡维 使用 技能3
卡维 使用 技能3
吞星之鲸 使用 技能2
吞星之鲸 使用 技能2
//
吞星之鲸 使用 技能1
吞星之鲸 使用 技能2
吞星之鲸 使用 技能2
吞星之鲸 使用 技能1
芙宁娜 使用 技能3
芙宁娜 使用 技能1
芙宁娜 使用 技能3
芙宁娜 使用 技能3
芙宁娜 使用 技能3