Files
bettergi-scripts-list/repo/js/Auto Theft NPC & 原食/main.js
this-Fish 50da8dfd95 JS : Auto Theft NPC & 原食 改用orc方式應對納塔商人交互 (#1580)
修正模擬操作會導致BUG納塔商人交互失敗
改用orc方式應對納塔商人交互
2025-08-13 15:25:33 +08:00

583 lines
14 KiB
JavaScript

const npcData = {
"Blanche": {
"name": "布兰琪",
"enable": true,
"page": 2,
"time": "night",
"path": "assets/path/布兰琪.json",
"goods": ["盐", "胡椒", "洋葱", "牛奶", "番茄", "卷心菜", "土豆", "小麦"]
},
"DongSheng": {
"name": "东升",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/东升.json",
"goods": ["洋葱", "牛奶", "卷心菜", "土豆", "小麦", "稻米", "虾仁", "豆腐", "杏仁", "盐", "胡椒", "番茄"]
},
"ChefMao": {
"name": "香菱爹",
"enable": true,
"page": 5,
"time": "any",
"path": "assets/path/卯师父.json",
"goods": ["鱼肉", "螃蟹"]
},
"UncleSun": {
"name": "奸商老孙",
"enable": true,
"page": 1,
"time": "day",
"path": "assets/path/老孙.json",
"goods": ["鱼肉", "螃蟹", "虾仁"]
},
"UncleGao": {
"name": "奸商老高",
"enable": true,
"page": 1,
"time": "day",
"path": "assets/path/老高.json",
"goods": ["鱼肉"]
},
"Aoi": {
"name": "葵",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/葵.json",
"goods": ["洋葱", "牛奶", "卷心菜", "土豆", "小麦", "稻米", "虾仁", "豆腐", "盐", "胡椒", "番茄"]
},
"ShimuraKanbei": {
"name": "志村勘兵卫",
"enable": true,
"page": 4,
"time": "any",
"path": "assets/path/志村勘兵卫.json",
"goods": ["鱼肉", "螃蟹"]
},
"Butrus": {
"name": "布特罗斯",
"enable": true,
"page": 1,
"time": "any",
"path": "assets/path/布特罗斯.json",
"goods": ["鱼肉", "螃蟹", "虾仁"]
},
"Pam": {
"name": "珀姆",
"enable": true,
"page": 1,
"time": "any",
"path": "assets/path/珀姆.json",
"goods": ["鱼肉", "螃蟹", "虾仁"]
},
"Hamawi": {
"name": "哈马维",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/哈马维.json",
"goods": ["洋葱", "牛奶", "卷心菜", "土豆", "小麦", "稻米", "虾仁", "豆腐", "盐", "胡椒", "番茄"]
},
"Enteka": {
"name": "恩忒卡",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/恩忒卡.json",
"goods": ["咖啡豆"]
}
,
"Azalai": {
"name": "阿扎莱",
"enable": true,
"page": 2,
"time": "night",
"path": "assets/path/阿扎莱.json",
"goods": ["鱼肉", "兽肉", "秃秃豆"]
},
"Boucicaut": {
"name": "布希柯",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/布希柯.json",
"goods": ["枫达", "洋葱", "牛奶", "卷心菜", "土豆", "小麦", "秃秃豆", "杏仁", "发酵果实汁", "盐", "胡椒", "番茄"]
},
"Arouet": {
"name": "阿鲁埃",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/阿鲁埃.json",
"goods": ["咖啡豆", "枫达"]
},
"Bunama": {
"name": "布纳马",
"enable": true,
"page": 2,
"time": "any",
"path": "assets/path/布纳马.json",
"goods": ["盐", "小麦", "胡椒", "洋葱", "牛奶", "番茄", "卷心菜", "土豆", "秃秃豆"]
}
}
const goodsData = {
"salt": {
"id": "salt",
"name": "盐",
"file": "assets/images/salt.png"
},
"pepper": {
"id": "pepper",
"name": "胡椒",
"file": "assets/images/pepper.png"
},
"onion": {
"id": "onion",
"name": "洋葱",
"file": "assets/images/onion.png"
},
"milk": {
"id": "milk",
"name": "牛奶",
"file": "assets/images/milk.png"
},
"tomato": {
"id": "tomato",
"name": "番茄",
"file": "assets/images/tomato.png"
},
"potato": {
"id": "potato",
"name": "土豆",
"file": "assets/images/potato.png"
},
"wheat": {
"id": "wheat",
"name": "小麦",
"file": "assets/images/wheat.png"
},
"rice": {
"id": "rice",
"name": "稻米",
"file": "assets/images/rice.png"
},
"shrimp": {
"id": "shrimp",
"name": "虾仁",
"file": "assets/images/shrimp.png"
},
"almond": {
"id": "almond",
"name": "杏仁",
"file": "assets/images/almond.png"
},
"cabbage": {
"id": "cabbage",
"name": "卷心菜",
"file": "assets/images/cabbage.png"
},
"tofu": {
"id": "tofu",
"name": "豆腐",
"file": "assets/images/tofu.png"
},
"fish": {
"id": "fish",
"name": "鱼肉",
"file": "assets/images/fish.png"
},
"crab": {
"id": "crab",
"name": "螃蟹",
"file": "assets/images/crab.png"
},
"coffeeBeans": {
"id": "coffeeBeans",
"name": "咖啡豆",
"file": "assets/images/coffeeBeans.png"
},
"glabrousBeans": {
"id": "glabrousBeans",
"name": "秃秃豆",
"file": "assets/images/glabrousBeans.png"
},
"rawMeat": {
"id": "rawMeat",
"name": "兽肉",
"file": "assets/images/rawMeat.png"
},
"fermentedJuice": {
"id": "fermentedJuice",
"name": "发酵果实汁",
"file": "assets/images/fermentedJuice.png"
},
"fonta": {
"id": "fonta",
"name": "枫达",
"file": "assets/images/fonta.png"
}
};
const translationList = {};
const enableGoods = new Set([]);
const othrtRo = {
"buy": {
"name": "购买按钮",
"file": "assets/images/buyBtn.png"
}
}
// 设置游戏时间
async function setTime(hour, minute) {
// 关于setTime
// 原作者: Tim
// 脚本名称: SetTimeMinute - 精确调整游戏时间到分钟
// 脚本版本: 1.0
// Hash: f5c2547dfc286fc643c733d630f775e8fbf12971
// 设置游戏分辨率和DPI缩放
setGameMetrics(1920, 1080, 1);
// 圆心坐标
const centerX = 1441;
const centerY = 501.6;
// 半径
const r1 = 30;
const r2 = 150;
const r3 = 300;
const stepDuration = 50;
function getPosition(r, index) {
let angle = index * Math.PI / 720;
return [Math.round(centerX + r * Math.cos(angle)), Math.round(centerY + r * Math.sin(angle))];
}
async function mouseClick(x, y) {
moveMouseTo(x, y);
await sleep(50);
leftButtonDown();
await sleep(50);
leftButtonUp();
await sleep(stepDuration);
}
async function mouseClickAndMove(x1, y1, x2, y2) {
moveMouseTo(x1, y1);
await sleep(50);
leftButtonDown();
await sleep(50);
moveMouseTo(x2, y2);
await sleep(50);
leftButtonUp();
await sleep(stepDuration);
}
async function setTime(hour, minute) {
const end = (hour + 6) * 60 + minute - 20;
const n = 3;
for (let i = - n + 1; i < 1; i++) {
let [x, y] = getPosition(r1, end + i * 1440 / n);
await mouseClick(x, y);
}
let [x1, y1] = getPosition(r2, end + 5);
let [x2, y2] = getPosition(r3, end + 20 + 0.5);
await mouseClickAndMove(x1, y1, x2, y2);
}
let h = Math.floor(hour + minute / 60);
const m = Math.floor(hour * 60 + minute) - h * 60;
h = ((h % 24) + 24) % 24;
log.info(`设置时间到 ${h}${m}`);
await keyPress("Escape");
await sleep(1000);
await click(50, 700);
await sleep(2000);
await setTime(h, m);
await sleep(1000);
await click(1500, 1000);//确认
await sleep(18000);
await keyPress("Escape");
await sleep(2000);
await keyPress("Escape");
await sleep(2000);
}
// 地图追踪
async function autoPath(locationPath) {
try {
let filePath = locationPath;
await pathingScript.runFile(filePath);
sleep(2000);
return true;
} catch (error) {
log.error(`执行 ${locationName} 路径时发生错误`);
log.error(error.message);
}
return false;
}
// 平滑过渡函数(缓动效果)
function smoothStep(t) {
return t * t * (3 - 2 * t);
}
// 模拟鼠标移动到指定位置(带曲线路径)
async function naturalMove(initX, initY, targetX, targetY, duration, wiggle = 30) {
// 生成控制点(使路径形成曲线)
const controlX = (initX + targetX) / 2 + (Math.random() * wiggle * 2 - wiggle);
const controlY = (initY + targetY) / 2 + (Math.random() * wiggle * 2 - wiggle);
const steps = Math.max(duration / 20, 10); // 计算步数
for (let i = 0; i <= steps; i++) {
const progress = i / steps;
const t = smoothStep(progress); // 使用平滑过渡
// 二次贝塞尔曲线计算
const x = (1 - t) * (1 - t) * initX + 2 * (1 - t) * t * controlX + t * t * targetX;
const y = (1 - t) * (1 - t) * initY + 2 * (1 - t) * t * controlY + t * t * targetY;
moveMouseTo(Math.trunc(x), Math.trunc(y));
// 随机延迟使移动更自然
await sleep(Math.trunc(duration / steps * (0.8 + Math.random() * 0.4)));
}
// 确保最终位置准确
moveMouseTo(targetX, targetY);
}
// 切换下一页商品
async function nextGoodsPage() {
//设置脚本环境的游戏分辨率和DPI缩放
setGameMetrics(3840, 2160, 1.5);
let [initX, initY] = [1500, 1850];
let [targetX, targetY] = [1800, 260];
moveMouseTo(initX, initY);
leftButtonDown();
await naturalMove(initX, initY, targetX, targetY, 300);
// 按住了, 防止弹太远
await sleep(520);
leftButtonUp();
}
// 快速购买
async function qucikBuy() {
//设置脚本环境的游戏分辨率和DPI缩放
setGameMetrics(3840, 2160, 1.5);
let [buyBtnX, buyBtnY] = [3200, 2045];
let [confirmBtnX, confirmBtnY] = [2025, 1570];
let [addNumX, addNumY] = [2060, 1208];
// 等待界面切换
await sleep(200);
// 查找购买按钮
let captureRegion = captureGameRegion();
let buyBtn = captureRegion.Find(othrtRo.buy.ro);
if (buyBtn.isEmpty()) {
return false;
}
// 点击购买按钮
click(buyBtn.x * 2 + buyBtn.width, buyBtn.y * 2 + buyBtn.height);
// 等待购买窗口弹出
await sleep(300);
// 增加数量至最大
leftButtonDown();
await naturalMove(addNumX, addNumY, addNumX + 666, addNumY - 233, 100);
leftButtonUp();
// 点击确认按钮
click(confirmBtnX, confirmBtnY);
// 等待购买完成
await sleep(200);
// 点击空白关闭
click(buyBtnX, buyBtnY);
await sleep(200);
return true;
}
// 跳过对话
async function spikChat(npcName) {
count = 5
await sleep(1000);
if (npcName == "布纳马") {
// 交互
for (let i = 0; i < 3; i++) {
keyPress("VK_F");
await sleep(1300);
}
// 点击有什么卖的
let captureRegion = captureGameRegion()
let resList = captureRegion.findMulti(RecognitionObject.ocrThis);
for (let i = 0; i < resList.count; i++) {
if (resList[i].text.includes("有什么卖的")) {
click(resList[i].x + 30, resList[i].y + 30); // 点击有什么卖的
// 使用完后释放资源
captureRegion.dispose();
}
}
await sleep(1300);
keyPress("VK_F");
await sleep(1300);
} else {
for (let i = 0; i < count; i++) {
keyPress("VK_F");
await sleep(1300);
}
}
await sleep(1000);
}
// 购买逻辑
async function buyGoods(npcName) {
// 设置脚本环境的游戏分辨率和DPI缩放
setGameMetrics(3840, 2160, 1.5);
let tempGoods = [...npcData[npcName].enableGoods];
// 多页购买
for (let i = 0; i < npcData[npcName].page; i++) {
log.info("购买列表: {goods}", [...tempGoods].join(", "));
// 获取一张截图
let captureRegion = captureGameRegion();
// 记录已经购买的物品
let boughtGoods = new Set([]);
// 匹配商品
for (let item of tempGoods) {
let resList = captureRegion.FindMulti(goodsData[item].ro);
for (let res of resList) {
log.info("找到物品: {i} 位置({x},{y},{h},{w})", goodsData[item].name, res.x, res.y, res.width, res.height);
// 移除已购买的物品
boughtGoods.add(item);
// 点击商品
click(res.x * 2 + res.width, res.y * 2 + res.height);
if (await qucikBuy()) {
log.info("购买成功: {item}", goodsData[item].name);
await sleep(500);
// 重新截图
captureRegion = captureGameRegion();
}
else {
log.info("购买失败: {item}, 背包已经满或商品已售罄", goodsData[item].name);
}
}
}
// 从已购买物品中移除
tempGoods = tempGoods.filter(item => !boughtGoods.has(item));
// 若不是最后一页且还有未购买的物品
if (tempGoods.length > 0 && i !== npcData[npcName].page - 1) {
log.info("切换到下一页商品");
await nextGoodsPage();
// 最后一次切换界面, 等待UI回弹
if (i === npcData[npcName].page - 2) {
log.info("等待界面回弹");
await sleep(500);
}
}
}
}
// 初始化NPC商品
async function initNpcData() {
for (let [key, npc] of Object.entries(npcData)) {
// 翻译物品名称
let npcGoods = new Set([]);
Object.entries(npc.goods).forEach(([count, item]) => {
npcGoods.add(translationList[item]);
});
// 筛选已启用的商品
npc.enableGoods = [...enableGoods].filter(item => npcGoods.has(item));
// 如果没有启用的商品, 则不启用该NPC
if (npc.enableGoods.length === 0) {
npc.enable = false;
}
}
}
// 加载识别对象
async function initRo() {
try {
// 加载识别对象
for (let [key, item] of Object.entries(goodsData)) {
// 填充中英文对照表
translationList[item.name] = item.id;
// 判断启动商品、加载识别对象
if (settings[item.id]) {
enableGoods.add(item.id);
item.ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(item.file));
item.ro.Threshold = 0.75;
item.ro.Use3Channels = true;
}
}
// 加载其他识别对象
for (let [key, item] of Object.entries(othrtRo)) {
item.ro = RecognitionObject.TemplateMatch(file.ReadImageMatSync(item.file));
item.ro.Threshold = 0.85;
}
return true;
}
catch (error) {
log.error("加载识别对象时发生错误: {error}", error.message);
throw error;
}
}
(async function () {
// ==================== 初始化识别对象 ====================
await initRo();
log.info("识别对象初始化完成");
// ==================== 初始化NPC数据 ====================
await initNpcData();
log.info("NPC数据初始化完成");
// ==================== 自动购买 ====================
for (let [key, npc] of Object.entries(npcData)) {
if (npc.enable) {
await genshin.returnMainUi();
log.info("开始购买NPC: {npcName}", npc.name);
// 设置游戏时间
if (npc.time === "night") {
await setTime(20, 0); // 设置为晚上8点
}
else if (npc.time === "day") {
await setTime(8, 0); // 设置为早上8点
}
await autoPath(npc.path);
await spikChat(npc.name);
await buyGoods(key);
// 返回主界面
await genshin.returnMainUi();
log.info("完成购买NPC: {npcName}", npc.name);
}
else {
log.info("跳过未启用的NPC: {npcName}", npc.name);
}
}
})();