@bettergi/utils 0.0.11 → 0.1.1

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 CHANGED
@@ -21,8 +21,6 @@ pnpm install @bettergi/utils
21
21
  > 常见游戏内操作封装,省去手动实现的繁琐。
22
22
 
23
23
  ```ts
24
- import { openMenu, openMenuPage, openPaimonMenu, setTime } from "@bettergi/utils";
25
-
26
24
  // 打开派蒙菜单
27
25
  await openPaimonMenu();
28
26
 
@@ -32,7 +30,10 @@ await openMenu("邮件");
32
30
  // 打开菜单页面
33
31
  await openMenuPage("问卷");
34
32
 
35
- // 调整游戏时间
33
+ // 调整游戏到指定时间
34
+ await setTimeTo(12, 35);
35
+
36
+ // 调整游戏时间段
36
37
  await setTime("evening");
37
38
  ```
38
39
 
@@ -41,16 +42,6 @@ await setTime("evening");
41
42
  > 对 RecognitionObject 代码的封装,对于简单的找图、找字操作,不再需要编写复杂的代码。
42
43
 
43
44
  ```ts
44
- import {
45
- findImage,
46
- findImageInDirection,
47
- findImageWithinBounds,
48
- findText,
49
- findTextInDirection,
50
- findTextWithinBounds
51
- } from "@bettergi/utils";
52
-
53
- // 在整个画面内搜索图片,找不到返回 undefined
54
45
  const i1 = findImage("assets/关闭.png");
55
46
 
56
47
  // 在指定方向上搜索图片,找不到返回 undefined
@@ -60,50 +51,42 @@ const i2 = findImageInDirection("assets/关闭.png", "north-east");
60
51
  const i3 = findImageWithinBounds("assets/关闭.png", 960, 0, 960, 1080);
61
52
 
62
53
  // 在整个画面内搜索文本(不包含、忽略大小写),找不到返回 undefined
63
- const t1 = findText("购买", false, true);
54
+ const t1 = findText("购买");
64
55
 
65
56
  // 在指定方向上搜索文本(包含、忽略大小写),找不到返回 undefined
66
- const t2 = findTextInDirection("师傅", true, true, "east");
57
+ const t2 = findTextInDirection("师傅", "east", { contains: true, ignoreCase: true });
67
58
 
68
59
  // 在指定区域内搜索文本(不包含、忽略大小写),找不到返回 undefined
69
- const t3 = findTextWithinBounds("确认", false, true, 960, 540, 960, 540);
60
+ const t3 = findTextWithinBounds("确认", 960, 540, 960, 540);
70
61
  ```
71
62
 
72
63
  ### 行为流程
73
64
 
74
- > 对脚本开发过程中常见工作流的抽象,例如:等待 XXX 完成/出现/消失。
65
+ > 对脚本开发过程中常见工作流的抽象,例如: 等待/断言 操作/元素/区域 完成/出现/消失。
75
66
 
76
67
  ```ts
77
- import {
78
- assertExists,
79
- assertNotExists,
80
- findImageInDirection,
81
- findTextInDirection,
82
- findTextWithinBounds,
83
- waitUntil
84
- } from "@bettergi/utils";
85
-
86
68
  // 等待直到找不到[关闭按钮] 或 5秒后超时,每隔1秒检查一次,期间按 Esc 键
87
- const done = await waitUntil(
69
+ const done = await waitForAction(
88
70
  () => findImageInDirection("assets/关闭.png", "north-east") === undefined,
89
- 5000,
90
- 1000,
91
- () => keyPress("ESCAPE")
71
+ () => keyPress("ESCAPE"),
72
+ { maxAttempts: 5, retryInterval: 1000 }
92
73
  );
93
74
  if (!done) throw new Error("关闭页面超时");
94
75
 
95
- // 断言 "世界等级" 区域存在 或 5秒后超时抛出异常,每隔1秒检查一次,期间按 Esc 键
96
- await assertExists(
97
- () => findTextInDirection("世界等级", false, true, "north-west"),
76
+ // 断言 "世界等级" 区域即将出现 或 5秒后超时抛出异常,每隔1秒检查一次,期间按 Esc 键
77
+ await assertRegionAppearing(
78
+ () => findTextInDirection("世界等级", "north-west"),
98
79
  "打开派蒙菜单超时",
99
- 5000,
100
- 1000,
101
- () => keyPress("ESCAPE")
80
+ () => keyPress("ESCAPE"),
81
+ { maxAttempts: 5, retryInterval: 1000 }
102
82
  );
103
83
 
104
84
  // 断言 "购买" 区域不存在 或 5秒后超时抛出异常,每隔1秒检查一次,期间如果存在 "购买" 按钮则点击
105
- const findButton = () => findTextWithinBounds("购买", true, true, 500, 740, 900, 110);
106
- await assertNotExists(findButton, "点击购买按钮超时", 5000, 1000, () => findButton()?.click());
85
+ const findButton = () => findTextWithinBounds("购买", 500, 740, 900, 110);
86
+ await assertRegionDisappearing(findButton, "点击购买按钮超时", () => findButton()?.click(), {
87
+ maxAttempts: 5,
88
+ retryInterval: 1000
89
+ });
107
90
  ```
108
91
 
109
92
  ### 鼠标操作
@@ -111,15 +94,11 @@ await assertNotExists(findButton, "点击购买按钮超时", 5000, 1000, () =>
111
94
  > 对常见鼠标操作的封装,如鼠标滚动、拖拽等。
112
95
 
113
96
  ```ts
114
- import {
115
- mouseScrollDown,
116
- mouseScrollDownLines,
117
- mouseScrollUp,
118
- mouseScrollUpLines,
119
- mouseSlide,
120
- mouseSlideX,
121
- mouseSlideY
122
- } from "@bettergi/utils";
97
+ // 鼠标从 (745, 610) 平滑自然地移动 (1920, 1080)
98
+ await naturalMouseMove(0, 0, 1920, 1080);
99
+
100
+ // 鼠标从 (745, 610) 拖拽到 (1280, 610)
101
+ await mouseDrag(745, 610, 1280, 610);
123
102
 
124
103
  // 鼠标滚轮向上滚动 175 像素
125
104
  await mouseScrollUp(175);
@@ -127,20 +106,11 @@ await mouseScrollUp(175);
127
106
  // 鼠标滚轮向下滚动 175 像素
128
107
  await mouseScrollDown(175);
129
108
 
130
- // 鼠标滚轮向上滚动 99 行,行高 175(默认:背包物品行高)
109
+ // 鼠标滚轮向上滚动 99 行,行高 175(默认: 背包物品行高)
131
110
  await mouseScrollUpLines(99);
132
111
 
133
- // 鼠标滚轮向下滚动 1 行,行高 115(自定义:商店物品行高)
112
+ // 鼠标滚轮向下滚动 1 行,行高 115(自定义: 商店物品行高)
134
113
  await mouseScrollDownLines(1, 115);
135
-
136
- // 鼠标从 (745, 610) 拖拽到 (1280, 610)
137
- await mouseSlide(745, 610, 1280, 610);
138
-
139
- // 鼠标从 (745, 610) 向右拖拽 435 像素
140
- await mouseSlideX(745, 610, 435);
141
-
142
- // 鼠标从 (1290, 140) 向下拖拽 175 像素
143
- await mouseSlideY(1290, 140, 175);
144
114
  ```
145
115
 
146
116
  ### 数据存储
@@ -153,7 +123,7 @@ import { useStore } from "@bettergi/utils";
153
123
  // 创建/读取存储对象,保存到存储文件 store/state.json 中
154
124
  const state = useStore<{ lastUsedTime?: number; count: number }>("state");
155
125
  if (state?.lastUsedTime) {
156
- log.info(`欢迎回来!上次使用时间:${state.lastUsedTime},计数器已累计至:${state.count}`);
126
+ log.info(`欢迎回来!上次使用时间: ${state.lastUsedTime},计数器已累计至: ${state.count}`);
157
127
  }
158
128
  try {
159
129
  // 模拟脚本运行期间状态的变化
@@ -173,9 +143,9 @@ try {
173
143
  import { getForBody, postForBody } from "@bettergi/utils";
174
144
 
175
145
  // 发送 GET 请求获取响应体内容
176
- // 提示:需要在 `manifest.json` 文件中配置 `http_allowed_urls`,并在 调度器 -> 修改通用配置 中启用
177
- const body1 = await getForBody("https://example.com/", null, { "User-Agent": "BetterGI" });
178
- const body2 = await postForBody("https://example.com/", null, { "User-Agent": "BetterGI" });
146
+ // 提示: 需要在 `manifest.json` 文件中配置 `http_allowed_urls`,并在 调度器 -> 修改通用配置 中启用
147
+ const body1 = await getForBody("https://example.com/", undefined, { "User-Agent": "BetterGI" });
148
+ const body2 = await postForBody("https://example.com/", undefined, { "User-Agent": "BetterGI" });
179
149
  log.info(`GET 请求响应体内容${body1}`);
180
150
  log.info(`POST 请求响应体内容${body2}`);
181
151
  ```
@@ -0,0 +1,57 @@
1
+ import { type Action, type RetryOptions } from "./workflow";
2
+ /**
3
+ * 断言某个区域当前存在,否则抛出异常
4
+ * @param regionProvider 返回区域的函数
5
+ * @param message 错误信息
6
+ */
7
+ export declare const assertRegionPresent: (regionProvider: () => Region | null | undefined, message: string) => Promise<void>;
8
+ /**
9
+ * 断言某个区域当前不存在,否则抛出异常
10
+ * @param regionProvider 返回区域的函数
11
+ * @param message 错误信息
12
+ */
13
+ export declare const assertRegionAbsent: (regionProvider: () => Region | null | undefined, message: string) => Promise<void>;
14
+ /**
15
+ * 断言整个画面上某个元素当前存在,否则抛出异常
16
+ * @param recognitionObject 识别对象
17
+ * @param message 错误信息
18
+ */
19
+ export declare const assertElementPresent: (recognitionObject: RecognitionObject, message: string) => Promise<void>;
20
+ /**
21
+ * 断言整个画面上某个元素当前不存在,否则抛出异常
22
+ * @param recognitionObject 识别对象
23
+ * @param message 错误信息
24
+ */
25
+ export declare const assertElementAbsent: (recognitionObject: RecognitionObject, message: string) => Promise<void>;
26
+ /**
27
+ * 断言某个区域即将出现,否则抛出异常
28
+ * @param regionProvider 返回区域的函数
29
+ * @param message 错误信息
30
+ * @param retryAction 每次重试时执行的操作(可选)
31
+ * @param options 配置选项
32
+ */
33
+ export declare const assertRegionAppearing: (regionProvider: () => Region | null | undefined, message: string, retryAction?: Action, options?: RetryOptions) => Promise<void>;
34
+ /**
35
+ * 断言某个区域即将消失,否则抛出异常
36
+ * @param regionProvider 返回区域的函数
37
+ * @param message 错误信息
38
+ * @param retryAction 每次重试时执行的操作(可选)
39
+ * @param options 配置选项
40
+ */
41
+ export declare const assertRegionDisappearing: (regionProvider: () => Region | null | undefined, message: string, retryAction?: Action, options?: RetryOptions) => Promise<void>;
42
+ /**
43
+ * 断言整个画面上某个元素即将出现,否则抛出异常
44
+ * @param recognitionObject 识别对象
45
+ * @param message 错误信息
46
+ * @param retryAction 每次重试时执行的操作(可选)
47
+ * @param options 配置选项
48
+ */
49
+ export declare const assertElementAppearing: (recognitionObject: RecognitionObject, message: string, retryAction?: Action, options?: RetryOptions) => Promise<void>;
50
+ /**
51
+ * 断言整个画面上某个元素即将消失,否则抛出异常
52
+ * @param recognitionObject 识别对象
53
+ * @param message 错误信息
54
+ * @param retryAction 每次重试时执行的操作(可选)
55
+ * @param options 配置选项
56
+ */
57
+ export declare const assertElementDisappearing: (recognitionObject: RecognitionObject, message: string, retryAction?: Action, options?: RetryOptions) => Promise<void>;
@@ -0,0 +1,91 @@
1
+ import { waitForElementAppear, waitForElementDisappear, waitForRegionAppear, waitForRegionDisappear } from "./workflow";
2
+ /**
3
+ * 断言某个区域当前存在,否则抛出异常
4
+ * @param regionProvider 返回区域的函数
5
+ * @param message 错误信息
6
+ */
7
+ export const assertRegionPresent = async (regionProvider, message) => {
8
+ const region = regionProvider();
9
+ if (region == null || !region.isExist()) {
10
+ throw new Error(message);
11
+ }
12
+ };
13
+ /**
14
+ * 断言某个区域当前不存在,否则抛出异常
15
+ * @param regionProvider 返回区域的函数
16
+ * @param message 错误信息
17
+ */
18
+ export const assertRegionAbsent = async (regionProvider, message) => {
19
+ const region = regionProvider();
20
+ if (region != null && region.isExist()) {
21
+ throw new Error(message);
22
+ }
23
+ };
24
+ /**
25
+ * 断言整个画面上某个元素当前存在,否则抛出异常
26
+ * @param recognitionObject 识别对象
27
+ * @param message 错误信息
28
+ */
29
+ export const assertElementPresent = async (recognitionObject, message) => {
30
+ return assertRegionPresent(() => captureGameRegion().find(recognitionObject), message);
31
+ };
32
+ /**
33
+ * 断言整个画面上某个元素当前不存在,否则抛出异常
34
+ * @param recognitionObject 识别对象
35
+ * @param message 错误信息
36
+ */
37
+ export const assertElementAbsent = async (recognitionObject, message) => {
38
+ return assertRegionAbsent(() => captureGameRegion().find(recognitionObject), message);
39
+ };
40
+ /**
41
+ * 断言某个区域即将出现,否则抛出异常
42
+ * @param regionProvider 返回区域的函数
43
+ * @param message 错误信息
44
+ * @param retryAction 每次重试时执行的操作(可选)
45
+ * @param options 配置选项
46
+ */
47
+ export const assertRegionAppearing = async (regionProvider, message, retryAction, options) => {
48
+ const isAppeared = await waitForRegionAppear(regionProvider, retryAction, options);
49
+ if (!isAppeared) {
50
+ throw new Error(message);
51
+ }
52
+ };
53
+ /**
54
+ * 断言某个区域即将消失,否则抛出异常
55
+ * @param regionProvider 返回区域的函数
56
+ * @param message 错误信息
57
+ * @param retryAction 每次重试时执行的操作(可选)
58
+ * @param options 配置选项
59
+ */
60
+ export const assertRegionDisappearing = async (regionProvider, message, retryAction, options) => {
61
+ const isDisappeared = await waitForRegionDisappear(regionProvider, retryAction, options);
62
+ if (!isDisappeared) {
63
+ throw new Error(message);
64
+ }
65
+ };
66
+ /**
67
+ * 断言整个画面上某个元素即将出现,否则抛出异常
68
+ * @param recognitionObject 识别对象
69
+ * @param message 错误信息
70
+ * @param retryAction 每次重试时执行的操作(可选)
71
+ * @param options 配置选项
72
+ */
73
+ export const assertElementAppearing = async (recognitionObject, message, retryAction, options) => {
74
+ const isAppeared = await waitForElementAppear(recognitionObject, retryAction, options);
75
+ if (!isAppeared) {
76
+ throw new Error(message);
77
+ }
78
+ };
79
+ /**
80
+ * 断言整个画面上某个元素即将消失,否则抛出异常
81
+ * @param recognitionObject 识别对象
82
+ * @param message 错误信息
83
+ * @param retryAction 每次重试时执行的操作(可选)
84
+ * @param options 配置选项
85
+ */
86
+ export const assertElementDisappearing = async (recognitionObject, message, retryAction, options) => {
87
+ const isDisappeared = await waitForElementDisappear(recognitionObject, retryAction, options);
88
+ if (!isDisappeared) {
89
+ throw new Error(message);
90
+ }
91
+ };
package/dist/game.d.ts CHANGED
@@ -1,3 +1,13 @@
1
+ import { ListView } from "./ocr";
2
+ /**
3
+ * 临时设置游戏分辨率和DPI缩放比例,执行指定动作后恢复
4
+ * 适用于使用了鼠标移动的操作,保证在不同的分辨率和DPI下都能正确地复现鼠标操作
5
+ * @param w 游戏宽度
6
+ * @param h 游戏高度
7
+ * @param dpi 系统屏幕的DPI缩放比例
8
+ * @param action 执行动作
9
+ */
10
+ export declare const withGameMetrics: <T>(w: number, h: number, dpi: number, action: () => Promise<T> | T) => Promise<T>;
1
11
  /**
2
12
  * 打开派蒙菜单
3
13
  */
@@ -5,34 +15,42 @@ export declare const openPaimonMenu: () => Promise<void>;
5
15
  /**
6
16
  * 打开游戏菜单(左侧按钮)
7
17
  * @param name 菜单名称
8
- * @param reverse 是否反向搜索(可选)
9
- * @param config 搜索参数(可选)
18
+ * @param stepBackwards 是否反向步进(可选)
19
+ * @param options 搜索参数(可选)
10
20
  */
11
- export declare const openMenu: (name: "\u62CD\u7167" | "\u516C\u544A" | "\u90AE\u4EF6" | "\u65F6\u95F4" | "\u8BBE\u7F6E" | (string & {}), reverse?: boolean, config?: {
12
- x?: number;
13
- step?: number;
14
- timeout?: number;
21
+ export declare const openMenu: (name: "\u62CD\u7167" | "\u516C\u544A" | "\u90AE\u4EF6" | "\u65F6\u95F4" | "\u8BBE\u7F6E" | (string & {}), stepBackwards?: boolean, options?: {
22
+ navWidth?: number;
23
+ steps?: number;
15
24
  }) => Promise<void>;
16
25
  /**
17
26
  * 打开游戏菜单页面(菜单按钮列表)
18
27
  * @param name 菜单页面名称
19
- * @param config 菜单页面视图参数
20
- */
21
- export declare const openMenuPage: (name: string, config?: {
22
- x?: number;
23
- y?: number;
24
- w?: number;
25
- h?: number;
26
- lineHeight?: number;
27
- }) => Promise<void>;
28
- /**
29
- * 调整游戏时间
30
- * @param period 时间段
31
- * @param config 时钟参数
28
+ * @param listView 菜单页面视图参数
32
29
  */
33
- export declare const setTime: (period: "night" | "morning" | "noon" | "evening", config?: {
30
+ export declare const openMenuPage: (name: "\u5546\u57CE" | "\u961F\u4F0D\u914D\u7F6E" | "\u597D\u53CB" | "\u6210\u5C31" | "\u56FE\u9274" | "\u89D2\u8272\u56FE\u9274" | "\u89D2\u8272" | "\u63D0\u5347\u6307\u5357" | "\u80CC\u5305" | "\u4EFB\u52A1" | "\u5730\u56FE" | "\u6D3B\u52A8" | "\u5192\u9669\u4E4B\u8BC1" | "\u7948\u613F" | "\u7EAA\u884C" | "\u591A\u4EBA\u6E38\u620F" | "\u5343\u661F\u5546\u57CE" | "\u4EBA\u6C14\u5947\u57DF" | "\u5947\u57DF\u6536\u85CF" | "\u6211\u7684\u5947\u57DF" | "\u5927\u5385" | "\u88C5\u626E\u642D\u914D" | "\u9882\u613F" | "\u7EAA\u6E38" | (string & {}), listView?: ListView) => Promise<void>;
31
+ type ClockOptions = {
32
+ /** 时钟中心X坐标 */
34
33
  centerX?: number;
34
+ /** 时钟中心Y坐标 */
35
35
  centerY?: number;
36
+ /** 偏移小时数(默认: 6) */
37
+ offsetHours?: number;
38
+ /** 时钟半径(默认: 154) */
36
39
  radius?: number;
37
- offset?: number;
38
- }) => Promise<void>;
40
+ /** 平滑度(默认: 3) */
41
+ smooth?: number;
42
+ };
43
+ /**
44
+ * 调整游戏时间到指定时分
45
+ * @param hour 小时
46
+ * @param minute 分钟
47
+ * @param options 时钟参数
48
+ */
49
+ export declare const setTimeTo: (hour: number, minute: number, options?: ClockOptions) => Promise<void>;
50
+ /**
51
+ * 调整游戏时间到指定时间段
52
+ * @param period 时间段
53
+ * @param options 时钟参数
54
+ */
55
+ export declare const setTime: (period: "night" | "morning" | "noon" | "evening", options?: ClockOptions) => Promise<void>;
56
+ export {};
package/dist/game.js CHANGED
@@ -1,6 +1,24 @@
1
- import { assertExists, assertNotExists } from "./flow";
2
- import { mouseSlide } from "./mouse";
1
+ import { assertRegionAppearing, assertRegionDisappearing } from "./asserts";
2
+ import { mouseMoveAlongWaypoints } from "./mouse";
3
3
  import { findTextInDirection, findTextWithinBounds, findTextWithinListView } from "./ocr";
4
+ /**
5
+ * 临时设置游戏分辨率和DPI缩放比例,执行指定动作后恢复
6
+ * 适用于使用了鼠标移动的操作,保证在不同的分辨率和DPI下都能正确地复现鼠标操作
7
+ * @param w 游戏宽度
8
+ * @param h 游戏高度
9
+ * @param dpi 系统屏幕的DPI缩放比例
10
+ * @param action 执行动作
11
+ */
12
+ export const withGameMetrics = async (w, h, dpi, action) => {
13
+ const { width, height, screenDpiScale } = genshin;
14
+ try {
15
+ setGameMetrics(w, h, dpi);
16
+ return await action();
17
+ }
18
+ finally {
19
+ setGameMetrics(width, height, screenDpiScale);
20
+ }
21
+ };
4
22
  /**
5
23
  * 打开派蒙菜单
6
24
  */
@@ -8,34 +26,36 @@ export const openPaimonMenu = async () => {
8
26
  // 1.返回主界面
9
27
  await genshin.returnMainUi();
10
28
  // 2.打开派蒙菜单
11
- await assertExists(() => findTextInDirection("世界等级", false, true, "north-west"), "打开派蒙菜单超时", 5000, 1000, () => keyPress("ESCAPE"));
29
+ await assertRegionAppearing(() => findTextWithinBounds("生日", 300, 230, 440, 100), "打开派蒙菜单超时", () => keyPress("ESCAPE"));
12
30
  };
13
31
  /**
14
32
  * 打开游戏菜单(左侧按钮)
15
33
  * @param name 菜单名称
16
- * @param reverse 是否反向搜索(可选)
17
- * @param config 搜索参数(可选)
34
+ * @param stepBackwards 是否反向步进(可选)
35
+ * @param options 搜索参数(可选)
18
36
  */
19
- export const openMenu = async (name, reverse = false, config = {}) => {
37
+ export const openMenu = async (name, stepBackwards = false, options = {}) => {
20
38
  // 1.打开派蒙菜单
21
39
  await openPaimonMenu();
22
- // 2.搜索菜单按钮
23
- const { x = 50, step = 30, timeout = 3000 } = config;
24
- const findTooltip = () => findTextWithinBounds(name, false, true, 0, 0, x + 150, genshin.height);
25
- let result = undefined;
26
- const steps = Math.ceil(genshin.height / step);
27
- for (let i = 0; i < steps; i++) {
28
- const o = i * step;
29
- const y = reverse ? genshin.height - o : o;
30
- moveMouseTo(x, y);
31
- await sleep(30); // 等待提示文字出现
32
- if ((result = findTooltip()) !== undefined)
33
- break;
34
- }
40
+ // 2.步进搜索菜单按钮
41
+ const { navWidth = 95, steps: step = 30 } = options;
42
+ const findTooltip = () => findTextWithinBounds(name, navWidth, 0, 20 + name.length * 20, genshin.height);
43
+ const tooltip = await withGameMetrics(1920, 1080, 1.5, async () => {
44
+ let result = undefined;
45
+ const steps = Math.ceil(genshin.height / step);
46
+ for (let i = 0; i < steps; i++) {
47
+ const o = i * step;
48
+ const y = stepBackwards ? genshin.height - o : o;
49
+ moveMouseTo(Math.round(navWidth / 2), y);
50
+ await sleep(30); // 等待提示文字出现
51
+ if ((result = findTooltip()) !== undefined)
52
+ return result;
53
+ }
54
+ });
35
55
  // 3.点击菜单按钮
36
- if (result != undefined) {
37
- await assertNotExists(findTooltip, `打开菜单 ${name} 超时`, timeout, 1000, () => {
38
- click(x, result.y);
56
+ if (tooltip != undefined) {
57
+ await assertRegionDisappearing(findTooltip, `打开菜单 ${name} 超时`, () => {
58
+ click(Math.round(navWidth / 2), tooltip.y);
39
59
  });
40
60
  }
41
61
  else {
@@ -45,50 +65,67 @@ export const openMenu = async (name, reverse = false, config = {}) => {
45
65
  /**
46
66
  * 打开游戏菜单页面(菜单按钮列表)
47
67
  * @param name 菜单页面名称
48
- * @param config 菜单页面视图参数
68
+ * @param listView 菜单页面视图参数
49
69
  */
50
- export const openMenuPage = async (name, config = {}) => {
70
+ export const openMenuPage = async (name, listView) => {
51
71
  // 1.打开派蒙菜单
52
72
  await openPaimonMenu();
53
- // 2.搜索菜单页面
54
- const { x = 100, y = 330, w = 670, h = 730, lineHeight = 142 } = config;
55
- const pageButton = await findTextWithinListView(name, false, true, {
56
- x,
57
- y,
58
- w,
59
- h,
60
- maxListItems: 5,
61
- lineHeight
73
+ // 2.搜索菜单页面按钮
74
+ const button = await withGameMetrics(1920, 1080, 1.5, async () => {
75
+ const { x = 95, y = 330, w = 670, h = 730, lineHeight = 142 } = listView || {};
76
+ return await findTextWithinListView(name, { x, y, w, h, lineHeight, scrollLines: 2 });
62
77
  });
63
- if (!pageButton)
78
+ if (!button)
64
79
  throw new Error(`搜索菜单页面 ${name} 失败`);
65
- // 3.点击菜单页面
66
- pageButton.click();
80
+ // 3.点击打开菜单页面
81
+ await assertRegionDisappearing(() => findTextWithinBounds(name, button.x, button.y, button.width, button.height), `打开菜单页面 ${name} 超时`, () => {
82
+ button.click();
83
+ });
67
84
  };
68
85
  /**
69
- * 调整游戏时间
70
- * @param period 时间段
71
- * @param config 时钟参数
86
+ * 调整游戏时间到指定时分
87
+ * @param hour 小时
88
+ * @param minute 分钟
89
+ * @param options 时钟参数
72
90
  */
73
- export const setTime = async (period, config = {}) => {
91
+ export const setTimeTo = async (hour, minute, options) => {
74
92
  // 1.打开时间页面
75
93
  await openMenu("时间", true);
76
- // 2.拨动指针
77
- const { centerX = 1440, centerY = 502, radius = 400, offset = 5 } = config;
78
- const index = ["night", "morning", "noon", "evening"].indexOf(period);
79
- const periodsDirections = [
80
- () => mouseSlide(centerX, centerY, centerX - offset, centerY + radius),
81
- () => mouseSlide(centerX, centerY, centerX - radius, centerY - offset),
82
- () => mouseSlide(centerX, centerY, centerX + offset, centerY - radius),
83
- () => mouseSlide(centerX, centerY, centerX + radius, centerY + offset)
84
- ];
85
- const jobs = Array.from({ length: 4 }, (_, i) => periodsDirections[(index + i + 1) % periodsDirections.length]);
86
- for (const job of jobs)
87
- await job();
88
- // 3.点击确认按钮,等待调整结束
89
- await assertExists(() => findTextInDirection("时间少于", true, true, "south-east"), "调整时间超时", 20 * 1000, 1000, () => {
90
- findTextInDirection("确认", false, true, "south-east")?.click();
94
+ // 2.计算调整时钟的路径点
95
+ const { centerX = 1440, centerY = 502, offsetHours = 6, radius = 154, smooth = 3 } = options || {};
96
+ const radian = ((((hour + offsetHours) * 60 + minute) % 1440) / 1440) * Math.PI * 2;
97
+ const waypoints = [{ x: centerX, y: centerY }].concat(Array.from({ length: Math.max(smooth, 3) })
98
+ // 计算弧度
99
+ .map((_, i) => radian + (1 + i / (Math.max(smooth, 3) - 1)) * Math.PI)
100
+ // 计算相对圆点坐标
101
+ .map(rad => ({ x: radius * Math.cos(rad), y: radius * Math.sin(rad) }))
102
+ // 计算绝对坐标
103
+ .map(p => ({ x: Math.round(p.x + centerX), y: Math.round(p.y + centerY) })));
104
+ // 3.拨动指针
105
+ await withGameMetrics(1920, 1080, 1.5, async () => {
106
+ await mouseMoveAlongWaypoints(waypoints, { shouldDrag: true });
107
+ // 3.点击确认按钮,等待调整结束
108
+ await assertRegionAppearing(() => findTextInDirection("时间少于", "south-east", { contains: true }), "调整时间超时", () => {
109
+ findTextInDirection("确认", "south-east")?.click();
110
+ }, { maxAttempts: 20, retryInterval: 1000 });
91
111
  });
92
112
  // 4.返回主界面
93
113
  await genshin.returnMainUi();
94
114
  };
115
+ /**
116
+ * 调整游戏时间到指定时间段
117
+ * @param period 时间段
118
+ * @param options 时钟参数
119
+ */
120
+ export const setTime = async (period, options) => {
121
+ switch (period) {
122
+ case "night":
123
+ return setTimeTo(0, 0, options);
124
+ case "morning":
125
+ return setTimeTo(6, 0, options);
126
+ case "noon":
127
+ return setTimeTo(12, 0, options);
128
+ case "evening":
129
+ return setTimeTo(18, 0, options);
130
+ }
131
+ };
package/dist/http.js CHANGED
@@ -6,8 +6,8 @@
6
6
  * @param headers 请求头
7
7
  * @returns 响应体内容
8
8
  */
9
- export const requestForBody = async (method, url, body = "null", headers) => {
10
- const resp = await http.request(method, url, body, headers ? JSON.stringify(headers) : "null");
9
+ export const requestForBody = async (method, url, body, headers) => {
10
+ const resp = await http.request(method, url, body ?? "null", headers ? JSON.stringify(headers) : "null");
11
11
  if (resp.status_code >= 200 && resp.status_code < 400) {
12
12
  return resp.body;
13
13
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from "./flow";
1
+ export * from "./asserts";
2
+ export * from "./workflow";
2
3
  export * from "./game";
3
4
  export * from "./http";
4
5
  export * from "./mouse";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export * from "./flow";
1
+ export * from "./asserts";
2
+ export * from "./workflow";
2
3
  export * from "./game";
3
4
  export * from "./http";
4
5
  export * from "./mouse";