@bettergi/utils 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/game.d.ts +25 -0
- package/dist/game.js +28 -7
- package/dist/ocr.js +45 -17
- package/package.json +4 -3
package/README.md
CHANGED
package/dist/game.d.ts
CHANGED
|
@@ -53,4 +53,29 @@ export declare const setTimeTo: (hour: number, minute: number, options?: ClockOp
|
|
|
53
53
|
* @param options 时钟参数
|
|
54
54
|
*/
|
|
55
55
|
export declare const setTime: (period: "night" | "morning" | "noon" | "evening", options?: ClockOptions) => Promise<void>;
|
|
56
|
+
/** tab 翻页配置 */
|
|
57
|
+
type TabNavigationOptions = {
|
|
58
|
+
/** 标签页图标宽度(默认:96) */
|
|
59
|
+
tabIconWidth?: number;
|
|
60
|
+
/** 翻页按钮垂直偏移(默认:540) */
|
|
61
|
+
verticalOffset?: number;
|
|
62
|
+
/** 翻页按钮左侧内边距(默认:72) */
|
|
63
|
+
paddingLeft?: number;
|
|
64
|
+
/** 翻页按钮右侧内边距(默认:72) */
|
|
65
|
+
paddingRight?: number;
|
|
66
|
+
/** 是否向前翻页(默认:false) */
|
|
67
|
+
backwards?: boolean;
|
|
68
|
+
/** 最大尝试次数(默认:屏幕宽度 / 标签页图标宽度) */
|
|
69
|
+
maxAttempts?: number;
|
|
70
|
+
/** 每次尝试间隔时间(毫秒,默认:1000) */
|
|
71
|
+
retryInterval?: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* 导航到指定标签页
|
|
75
|
+
* @param condition 导航成功的条件
|
|
76
|
+
* @param options 翻页配置
|
|
77
|
+
* @returns - true 条件满足
|
|
78
|
+
* - false 达到最大重试次数
|
|
79
|
+
*/
|
|
80
|
+
export declare const navigateToTab: (condition: () => boolean, options?: TabNavigationOptions) => Promise<boolean>;
|
|
56
81
|
export {};
|
package/dist/game.js
CHANGED
|
@@ -10,13 +10,13 @@ import { findTextInDirection, findTextWithinBounds, findTextWithinListView } fro
|
|
|
10
10
|
* @param action 执行动作
|
|
11
11
|
*/
|
|
12
12
|
export const withGameMetrics = async (w, h, dpi, action) => {
|
|
13
|
-
const
|
|
13
|
+
const [_w, _h, _dpi] = globalThis["getGameMetrics"] ? getGameMetrics() : [];
|
|
14
14
|
try {
|
|
15
15
|
setGameMetrics(w, h, dpi);
|
|
16
16
|
return await action();
|
|
17
17
|
}
|
|
18
18
|
finally {
|
|
19
|
-
setGameMetrics(
|
|
19
|
+
globalThis["getGameMetrics"] && setGameMetrics(_w, _h, _dpi);
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
@@ -39,13 +39,13 @@ export const openMenu = async (name, stepBackwards = false, options = {}) => {
|
|
|
39
39
|
await openPaimonMenu();
|
|
40
40
|
// 2.步进搜索菜单按钮
|
|
41
41
|
const { navWidth = 95, steps: step = 30 } = options;
|
|
42
|
-
const findTooltip = () => findTextWithinBounds(name, navWidth, 0, 20 + name.length * 20,
|
|
42
|
+
const findTooltip = () => findTextWithinBounds(name, navWidth, 0, 20 + name.length * 20, 1080);
|
|
43
43
|
const tooltip = await withGameMetrics(1920, 1080, 1.5, async () => {
|
|
44
44
|
let result = undefined;
|
|
45
|
-
const steps = Math.ceil(
|
|
45
|
+
const steps = Math.ceil(1080 / step);
|
|
46
46
|
for (let i = 0; i < steps; i++) {
|
|
47
47
|
const o = i * step;
|
|
48
|
-
const y = stepBackwards ?
|
|
48
|
+
const y = stepBackwards ? 1080 - o : o;
|
|
49
49
|
moveMouseTo(Math.round(navWidth / 2), y);
|
|
50
50
|
await sleep(30); // 等待提示文字出现
|
|
51
51
|
if ((result = findTooltip()) !== undefined)
|
|
@@ -92,13 +92,14 @@ export const setTimeTo = async (hour, minute, options) => {
|
|
|
92
92
|
// 1.打开时间页面
|
|
93
93
|
await openMenu("时间", true);
|
|
94
94
|
// 2.计算调整时钟的路径点
|
|
95
|
-
const { centerX = 1440, centerY = 502, offsetHours = 6, radius =
|
|
95
|
+
const { centerX = 1440, centerY = 502, offsetHours = 6, radius = 150, smooth = 4 } = options || {};
|
|
96
96
|
const radian = ((((hour + offsetHours) * 60 + minute) % 1440) / 1440) * Math.PI * 2;
|
|
97
|
+
const trackRadius = radius + 150; // 增加拨动半径,提高准确度
|
|
97
98
|
const waypoints = [{ x: centerX, y: centerY }].concat(Array.from({ length: Math.max(smooth, 3) })
|
|
98
99
|
// 计算弧度
|
|
99
100
|
.map((_, i) => radian + (1 + i / (Math.max(smooth, 3) - 1)) * Math.PI)
|
|
100
101
|
// 计算相对圆点坐标
|
|
101
|
-
.map(rad => ({ x:
|
|
102
|
+
.map(rad => ({ x: trackRadius * Math.cos(rad), y: trackRadius * Math.sin(rad) }))
|
|
102
103
|
// 计算绝对坐标
|
|
103
104
|
.map(p => ({ x: Math.round(p.x + centerX), y: Math.round(p.y + centerY) })));
|
|
104
105
|
// 3.调整时间
|
|
@@ -130,3 +131,23 @@ export const setTime = async (period, options) => {
|
|
|
130
131
|
return setTimeTo(18, 5, options);
|
|
131
132
|
}
|
|
132
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* 导航到指定标签页
|
|
136
|
+
* @param condition 导航成功的条件
|
|
137
|
+
* @param options 翻页配置
|
|
138
|
+
* @returns - true 条件满足
|
|
139
|
+
* - false 达到最大重试次数
|
|
140
|
+
*/
|
|
141
|
+
export const navigateToTab = async (condition, options) => {
|
|
142
|
+
const { tabIconWidth = 96, verticalOffset = 540, paddingLeft = 72, paddingRight = 72, backwards = false, retryInterval = 1000 } = options || {};
|
|
143
|
+
const attempts = Math.floor(options?.maxAttempts ?? 1920 / tabIconWidth);
|
|
144
|
+
for (let i = 0; i < attempts; i++) {
|
|
145
|
+
if (i === 0 && condition())
|
|
146
|
+
return true; // fast path
|
|
147
|
+
click(backwards ? paddingLeft : 1920 - paddingRight, verticalOffset); // prev or next
|
|
148
|
+
await sleep(retryInterval);
|
|
149
|
+
if (condition())
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
};
|
package/dist/ocr.js
CHANGED
|
@@ -13,7 +13,13 @@ const directionToBounds = (direction) => {
|
|
|
13
13
|
const y = direction.includes("south") ? genshin.height / 2 : 0;
|
|
14
14
|
const w = direction === "north" || direction === "south" ? genshin.width : genshin.width / 2;
|
|
15
15
|
const h = direction === "west" || direction === "east" ? genshin.height : genshin.height / 2;
|
|
16
|
-
|
|
16
|
+
const scale = genshin.width / 1920;
|
|
17
|
+
if (scale <= 1) {
|
|
18
|
+
return { x, y, w, h };
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return { x: x / scale, y: y / scale, w: w / scale, h: h / scale };
|
|
22
|
+
}
|
|
17
23
|
};
|
|
18
24
|
/**
|
|
19
25
|
* 在整个画面内搜索图片
|
|
@@ -21,13 +27,16 @@ const directionToBounds = (direction) => {
|
|
|
21
27
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
22
28
|
*/
|
|
23
29
|
export const findImage = (path) => {
|
|
30
|
+
const ir = captureGameRegion();
|
|
24
31
|
try {
|
|
25
|
-
const ir = captureGameRegion();
|
|
26
32
|
const ro = RecognitionObject.templateMatch(file.readImageMatSync(path));
|
|
27
33
|
return findFirst(ir, ro, region => region.isExist());
|
|
28
34
|
}
|
|
29
35
|
catch (err) {
|
|
30
|
-
|
|
36
|
+
log.warn(`${err.message || err}`);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
ir.dispose();
|
|
31
40
|
}
|
|
32
41
|
};
|
|
33
42
|
/**
|
|
@@ -40,13 +49,16 @@ export const findImage = (path) => {
|
|
|
40
49
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
41
50
|
*/
|
|
42
51
|
export const findImageWithinBounds = (path, x, y, w, h) => {
|
|
52
|
+
const ir = captureGameRegion();
|
|
43
53
|
try {
|
|
44
|
-
const ir = captureGameRegion();
|
|
45
54
|
const ro = RecognitionObject.templateMatch(file.readImageMatSync(path), x, y, w, h);
|
|
46
55
|
return findFirst(ir, ro, region => region.isExist());
|
|
47
56
|
}
|
|
48
57
|
catch (err) {
|
|
49
|
-
|
|
58
|
+
log.warn(`${err.message || err}`);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
ir.dispose();
|
|
50
62
|
}
|
|
51
63
|
};
|
|
52
64
|
/**
|
|
@@ -69,12 +81,20 @@ export const findText = (text, options) => {
|
|
|
69
81
|
const { ignoreCase = true, contains = false } = options || {};
|
|
70
82
|
const searchText = ignoreCase ? text.toLowerCase() : text;
|
|
71
83
|
const ir = captureGameRegion();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
try {
|
|
85
|
+
const ro = RecognitionObject.ocrThis;
|
|
86
|
+
return findFirst(ir, ro, region => {
|
|
87
|
+
const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
|
|
88
|
+
const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
|
|
89
|
+
return isMatch && region.isExist();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
log.warn(`${err.message || err}`);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
ir.dispose();
|
|
97
|
+
}
|
|
78
98
|
};
|
|
79
99
|
/**
|
|
80
100
|
* 在指定区域内搜索文本
|
|
@@ -90,12 +110,20 @@ export const findTextWithinBounds = (text, x, y, w, h, options) => {
|
|
|
90
110
|
const { ignoreCase = true, contains = false } = options || {};
|
|
91
111
|
const searchText = ignoreCase ? text.toLowerCase() : text;
|
|
92
112
|
const ir = captureGameRegion();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
try {
|
|
114
|
+
const ro = RecognitionObject.ocr(x, y, w, h);
|
|
115
|
+
return findFirst(ir, ro, region => {
|
|
116
|
+
const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
|
|
117
|
+
const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
|
|
118
|
+
return isMatch && region.isExist();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
log.warn(`${err.message || err}`);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
ir.dispose();
|
|
126
|
+
}
|
|
99
127
|
};
|
|
100
128
|
/**
|
|
101
129
|
* 在指定方向上搜索文本
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettergi/utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "开发 BetterGI 脚本常用工具集",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Bread Grocery<https://github.com/breadgrocery>",
|
|
@@ -30,10 +30,11 @@
|
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
32
|
"dev": "tsc --watch",
|
|
33
|
-
"build": "tsc"
|
|
33
|
+
"build": "rimraf dist && tsc"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@bettergi/types": "^0.1.
|
|
36
|
+
"@bettergi/types": "^0.1.3",
|
|
37
|
+
"rimraf": "^6.0.1",
|
|
37
38
|
"typescript": "^5.9.3"
|
|
38
39
|
}
|
|
39
40
|
}
|