Skip to content

Commit dac0bba

Browse files
authored
feat: 新增人物移动 Custom Action 与喷泉打卡交互 (#91)
* feat: 新增人物移动 Custom Action(转向/前进/后退) * feat: 实现喷泉打卡,打卡交互-虔诚许愿 * feat: 增加了在已打卡情况下的执行流程 * fix: 修复打卡后卡在剧情跳过框的bug * chore: 在interface中删除测试用的TestMovement * fix: 处理 Movement action 中的余数丢失与脏输入 - mouse_move: dx/dy 用浮点累加 + 末尾余数补偿,避免 // 丢步导致总位移不足 - mouse_move: dx/dy 显式 int 转换;align 段前后补 stopping 检查 - character_move: duration 转换包 try/except,脏值时降级为 no-op * fix: 喷泉打卡打卡成功后优先考虑 Esc 处理退出弹窗 * fix: 已打卡情况下 Esc 退出弹窗
1 parent 045fd1d commit dac0bba

13 files changed

Lines changed: 492 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .mouse_move import *
2+
from .character_move import *
3+
4+
__all__ = ["MouseMoveAction", "CharacterMoveAction"]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import json
2+
import time
3+
4+
from maa.agent.agent_server import AgentServer
5+
from maa.custom_action import CustomAction
6+
from maa.context import Context
7+
8+
_KEY_MAP = {"W": 87, "A": 65, "S": 83, "D": 68}
9+
10+
11+
@AgentServer.custom_action("character_move")
12+
class CharacterMoveAction(CustomAction):
13+
def run(self, context: Context, argv: CustomAction.RunArg) -> CustomAction.RunResult:
14+
controller = context.tasker.controller
15+
16+
params = {}
17+
if argv.custom_action_param:
18+
try:
19+
params = json.loads(argv.custom_action_param)
20+
except Exception:
21+
pass
22+
23+
key = params.get("key", None)
24+
try:
25+
duration = float(params.get("duration", 0.0))
26+
except (TypeError, ValueError):
27+
duration = 0.0
28+
29+
vk = _KEY_MAP.get(str(key).upper()) if key else None
30+
if vk and duration > 0:
31+
controller.post_key_down(vk).wait()
32+
deadline = time.time() + duration
33+
while time.time() < deadline:
34+
if context.tasker.stopping:
35+
controller.post_key_up(vk).wait()
36+
return CustomAction.RunResult(success=False)
37+
time.sleep(0.05)
38+
controller.post_key_up(vk).wait()
39+
40+
return CustomAction.RunResult(success=True)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import json
2+
import time
3+
4+
from maa.agent.agent_server import AgentServer
5+
from maa.custom_action import CustomAction
6+
from maa.context import Context
7+
8+
_KEY_W = 87
9+
_ALIGN_DURATION = 0.1
10+
11+
12+
@AgentServer.custom_action("mouse_relative_move")
13+
class MouseMoveAction(CustomAction):
14+
def run(self, context: Context, argv: CustomAction.RunArg) -> CustomAction.RunResult:
15+
controller = context.tasker.controller
16+
17+
params = {}
18+
if argv.custom_action_param:
19+
try:
20+
params = json.loads(argv.custom_action_param)
21+
except Exception:
22+
pass
23+
24+
dx = int(params.get("dx", 0))
25+
dy = int(params.get("dy", 0))
26+
steps = params.get("steps", 30)
27+
step_delay = params.get("step_delay", 0.03)
28+
align = params.get("align", True)
29+
30+
if steps < 1:
31+
steps = 1
32+
33+
step_x = dx / steps
34+
step_y = dy / steps
35+
36+
moved_x = 0
37+
moved_y = 0
38+
for i in range(steps):
39+
if context.tasker.stopping:
40+
return CustomAction.RunResult(success=False)
41+
target_x = int(round(step_x * (i + 1)))
42+
target_y = int(round(step_y * (i + 1)))
43+
delta_x = target_x - moved_x
44+
delta_y = target_y - moved_y
45+
if delta_x != 0 or delta_y != 0:
46+
controller.post_relative_move(delta_x, delta_y).wait()
47+
moved_x = target_x
48+
moved_y = target_y
49+
time.sleep(step_delay)
50+
51+
remainder_x = dx - moved_x
52+
remainder_y = dy - moved_y
53+
if remainder_x != 0 or remainder_y != 0:
54+
controller.post_relative_move(remainder_x, remainder_y).wait()
55+
56+
if align and (dx != 0 or dy != 0):
57+
if context.tasker.stopping:
58+
return CustomAction.RunResult(success=False)
59+
controller.post_key_down(_KEY_W).wait()
60+
time.sleep(_ALIGN_DURATION)
61+
controller.post_key_up(_KEY_W).wait()
62+
if context.tasker.stopping:
63+
return CustomAction.RunResult(success=False)
64+
65+
return CustomAction.RunResult(success=True)

agent/custom/action/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from .AutoFish.auto_fish_withoutCV import *
1212
from .SoundTrigger.SoundDodgeAction import *
1313
from .auto_f_scroll import *
14+
from .Movement.mouse_move import *
15+
from .Movement.character_move import *
1416

1517
__all__ = [
1618
"AutoFishNew",

assets/interface.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"resource/tasks/RealTime.json",
6060
"resource/tasks/MakeCoffee.json",
6161
"resource/tasks/Rhythm.json",
62+
"resource/tasks/FountainCheckin.json",
6263
"resource/tasks/SoundDodge.json",
6364
"resource/tasks/AutoFScroll.json"
6465
],
7.58 KB
Loading
6.22 KB
Loading
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
{
2+
"FountainCheckinEntrance": {
3+
"desc": "喷泉打卡入口",
4+
"next": [
5+
"FountainFindButton",
6+
"FountainCheckinExit"
7+
]
8+
},
9+
"FountainFindButton": {
10+
"desc": "识别喷泉打卡按钮",
11+
"recognition": {
12+
"type": "TemplateMatch",
13+
"param": {
14+
"roi": [
15+
765,
16+
373,
17+
210,
18+
40
19+
],
20+
"template": [
21+
"auto_fountain_checkin/find_fountain_button.png"
22+
],
23+
"threshold": 0.7
24+
}
25+
},
26+
"action": "ClickKey",
27+
"key": 70,
28+
"post_delay": 500,
29+
"next": [
30+
"FountainWaitAnimation"
31+
]
32+
},
33+
"FountainWaitAnimation": {
34+
"desc": "等待动画结束,轮询许愿选项",
35+
"next": [
36+
"FountainMakeWish",
37+
"FountainSkipStory",
38+
"FountainSkipStoryDialog",
39+
"FountainCheckinIfExit"
40+
],
41+
"timeout": 5000
42+
},
43+
"FountainMakeWish": {
44+
"desc": "识别许愿选项并按F",
45+
"recognition": {
46+
"type": "TemplateMatch",
47+
"param": {
48+
"roi": [
49+
871,
50+
415,
51+
239,
52+
49
53+
],
54+
"template": [
55+
"auto_fountain_checkin/fountain_make_wish.png"
56+
],
57+
"threshold": 0.7
58+
}
59+
},
60+
"action": "Click",
61+
"post_delay": 2000,
62+
"next": [
63+
"FountainCheckinAnyExit",
64+
"FountainSkipStoryDialog",
65+
"FountainSkipStoryDialogConfirm",
66+
"FountainCheckinIfExit"
67+
]
68+
},
69+
"FountainSkipStory": {
70+
"desc": "长时间未识别到许愿选项,尝试跳过剧情",
71+
"recognition": {
72+
"type": "TemplateMatch",
73+
"param": {
74+
"roi": [
75+
1218,
76+
24,
77+
39,
78+
37
79+
],
80+
"template": [
81+
"realtime_assist/skip_story_btn.png"
82+
],
83+
"threshold": 0.7
84+
}
85+
},
86+
"action": {
87+
"type": "Click"
88+
},
89+
"post_delay": 0,
90+
"next": [
91+
"FountainCheckinAnyExit",
92+
"FountainSkipStoryDialog",
93+
"FountainWaitAnimation"
94+
]
95+
},
96+
"FountainSkipStoryDialog": {
97+
"desc": "跳过剧情对话框",
98+
"recognition": {
99+
"type": "TemplateMatch",
100+
"param": {
101+
"template": [
102+
"realtime_assist/skip_story_dialog.png"
103+
],
104+
"roi": [
105+
420,
106+
275,
107+
450,
108+
50
109+
],
110+
"threshold": 0.7
111+
}
112+
},
113+
"action": "DoNothing",
114+
"post_delay": 0,
115+
"next": [
116+
"FountainSkipStoryDialogConfirm",
117+
"FountainWaitAnimation"
118+
]
119+
},
120+
"FountainSkipStoryDialogConfirm": {
121+
"desc": "确认跳过剧情对话框确认按钮",
122+
"recognition": {
123+
"type": "TemplateMatch",
124+
"param": {
125+
"template": "realtime_assist/skip_story_dialog_confirm.png",
126+
"roi": [
127+
700,
128+
410,
129+
250,
130+
70
131+
],
132+
"threshold": 0.7
133+
}
134+
},
135+
"action": {
136+
"type": "Click"
137+
},
138+
"post_delay": 300,
139+
"next": [
140+
"FountainCheckinIfExit",
141+
"FountainWaitAnimation"
142+
]
143+
},
144+
"FountainCheckinAnyExit": {
145+
"desc": "Esc退出",
146+
"pre_delay": 0,
147+
"action": {
148+
"type": "ClickKey",
149+
"param": {
150+
"key": 27 // ESC
151+
}
152+
},
153+
"post_wait_freezes": {
154+
"target": [
155+
0,
156+
0,
157+
100,
158+
100
159+
],
160+
"time": 600
161+
},
162+
"post_delay": 0,
163+
"next": [
164+
"FountainCheckinIfExit"
165+
]
166+
},
167+
"FountainCheckinIfExit": {
168+
"desc": "识别喷泉打卡按钮,判断是否已退出",
169+
"recognition": {
170+
"type": "TemplateMatch",
171+
"param": {
172+
"roi": [
173+
765,
174+
373,
175+
210,
176+
40
177+
],
178+
"template": [
179+
"auto_fountain_checkin/find_fountain_button.png"
180+
],
181+
"threshold": 0.7
182+
}
183+
},
184+
"next": [
185+
"FountainCheckinExit"
186+
]
187+
},
188+
"FountainCheckinExit": {
189+
"desc": "喷泉打卡任务出口"
190+
}
191+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"MoveForward": {
3+
"desc": "前进:按住 W 键移动指定时长",
4+
"action": "Custom",
5+
"custom_action": "character_move",
6+
"custom_action_param": {
7+
"key": "W",
8+
"duration": 1.0
9+
}
10+
},
11+
"MoveBackward": {
12+
"desc": "后退:按住 S 键移动指定时长",
13+
"action": "Custom",
14+
"custom_action": "character_move",
15+
"custom_action_param": {
16+
"key": "S",
17+
"duration": 1.0
18+
}
19+
},
20+
"Turn": {
21+
"desc": "转向:鼠标相对位移,使人物朝向与视角对齐",
22+
"action": "Custom",
23+
"custom_action": "mouse_relative_move",
24+
"custom_action_param": {
25+
"dx": 0,
26+
"dy": 0,
27+
"steps": 30,
28+
"step_delay": 0.03,
29+
"align": true
30+
}
31+
}
32+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"MouseRelativeMove": {
3+
"desc": "鼠标相对位移转向(仅前台 Seize 控制器)",
4+
"action": "Custom",
5+
"custom_action": "mouse_relative_move",
6+
"custom_action_param": {
7+
"dx": 0,
8+
"dy": 0,
9+
"steps": 30,
10+
"step_delay": 0.03
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)