Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions agent/go-service/accountswitch/action_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
"unsafe"

maa "github.com/MaaXYZ/maa-framework-go/v4"
Expand All @@ -17,10 +16,13 @@ import (
const (
swRestore = 9

wmClose = 0x0010

mouseEventFLeftDown = 0x0002
mouseEventFLeftUp = 0x0004
wmClose = 0x0010
wmActivate = 0x0006
waActive = 1
wmMouseMove = 0x0200
wmLButtonDown = 0x0201
wmLButtonUp = 0x0202
mkLButton = 0x0001

awarenessContextPerMonitorAwareV2 = ^uintptr(3) // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
)
Expand All @@ -33,12 +35,12 @@ var (
procShowWindow = user32.NewProc("ShowWindow")
procBringWindowToTop = user32.NewProc("BringWindowToTop")
procSetForegroundWindow = user32.NewProc("SetForegroundWindow")
procSetCursorPos = user32.NewProc("SetCursorPos")
procMouseEvent = user32.NewProc("mouse_event")
procSendMessageW = user32.NewProc("SendMessageW")
procPostMessageW = user32.NewProc("PostMessageW")
procGetClientRect = user32.NewProc("GetClientRect")
procGetWindowRect = user32.NewProc("GetWindowRect")
procClientToScreen = user32.NewProc("ClientToScreen")
procScreenToClient = user32.NewProc("ScreenToClient")
procSetThreadDpiCtx = user32.NewProc("SetThreadDpiAwarenessContext")
)

Expand Down Expand Up @@ -242,7 +244,7 @@ func clickWindow(ctx *maa.Context, arg *maa.CustomActionArg, targetHwnd uintptr,
Msg("mapped click point is outside target window rect")
}

if !cursorClick(screenX, screenY) {
if !postClick(targetHwnd, screenX, screenY) {
return false
}

Expand Down Expand Up @@ -400,29 +402,54 @@ func scaleCoord(value int, sourceSize int, targetSize int32) int32 {
return int32((int64(value)*int64(targetSize) + int64(sourceSize)/2) / int64(sourceSize))
}

func cursorClick(screenX, screenY int32) bool {
if !setCursorPos(screenX, screenY) {
// PostMessage-style click (no physical cursor): activate + move/down/up on target hwnd.
func postClick(hwnd uintptr, screenX, screenY int32) bool {
pt := winPoint{X: screenX, Y: screenY}
if !screenToClient(hwnd, &pt) {
log.Error().
Str("component", componentName).
Uint64("hwnd", uint64(hwnd)).
Int32("screen_x", screenX).
Int32("screen_y", screenY).
Msg("failed to move cursor for account switch click")
Msg("ScreenToClient failed for account switch click")
return false
}

lParam := makeMouseLParam(pt.X, pt.Y)
sendMessage(hwnd, wmActivate, waActive, 0)
if !postMessage(hwnd, wmMouseMove, 0, lParam) {
return false
}
if !postMessage(hwnd, wmLButtonDown, mkLButton, lParam) {
return false
}
if !postMessage(hwnd, wmLButtonUp, 0, lParam) {
return false
}
time.Sleep(30 * time.Millisecond)
mouseEvent(mouseEventFLeftDown, 0, 0, 0, 0)
time.Sleep(50 * time.Millisecond)
mouseEvent(mouseEventFLeftUp, 0, 0, 0, 0)
return true
}

func setCursorPos(x, y int32) bool {
ret, _, _ := procSetCursorPos.Call(uintptr(x), uintptr(y))
func makeMouseLParam(x, y int32) uintptr {
return uintptr(uint32(uint16(int16(x))) | uint32(uint16(int16(y)))<<16)
}

func screenToClient(hwnd uintptr, point *winPoint) bool {
ret, _, _ := procScreenToClient.Call(hwnd, uintptr(unsafe.Pointer(point)))
return ret != 0
}

func mouseEvent(flags, dx, dy, data, extraInfo uintptr) {
procMouseEvent.Call(flags, dx, dy, data, extraInfo)
func postMessage(hwnd, msg, wparam, lparam uintptr) bool {
ret, _, err := procPostMessageW.Call(hwnd, msg, wparam, lparam)
if ret != 0 {
return true
}
log.Error().
Err(err).
Str("component", componentName).
Uint64("hwnd", uint64(hwnd)).
Uint64("msg", uint64(msg)).
Msg("PostMessageW failed for account switch click")
return false
}

func describeWindowSelector(param windowActionParam) string {
Expand Down
1 change: 1 addition & 0 deletions agent/go-service/scenemanager/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ func Register() {
maa.AgentServerRegisterCustomAction("SceneManagerMenuListClickItemAction", &SceneManagerMenuListClickItemAction{})
maa.AgentServerRegisterCustomRecognition("ImageCheckNotPassedRecognition", &ImageCheckNotPassedRecognition{})
maa.AgentServerRegisterCustomAction("ImageCheckSetResultAction", &ImageCheckSetResultAction{})
maa.AgentServerRegisterCustomRecognition("ScreenshotStableRecognition", &ScreenshotStableRecognition{})
}
108 changes: 108 additions & 0 deletions agent/go-service/scenemanager/screenshotstable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package scenemanager

import (
"image"

"github.com/MaaXYZ/MaaEnd/agent/go-service/pkg/minicv"
"github.com/MaaXYZ/MaaEnd/agent/go-service/pkg/pienv"
"github.com/MaaXYZ/MaaEnd/agent/go-service/pretask/gamesetting"
maa "github.com/MaaXYZ/maa-framework-go/v4"
"github.com/rs/zerolog/log"
)

var screenshotStableDone bool
var screenshotStableCount int // 0=未采;1=已有 baseline;2=第 2 帧已匹配,等待第 3 帧
var screenshotStableBaseline *image.RGBA

var _ maa.CustomRecognitionRunner = (*ScreenshotStableRecognition)(nil)

// ScreenshotStableRecognition 仅在 Win32-Front + 游戏全屏时,于 Agent 进程生命周期内判定一次
// 画面是否连续三帧稳定。每次调用只用 arg.Img:第 1 次存 baseline;第 2 帧不匹配则结束周期;
// 匹配后再采第 3 帧确认。仅第 3 帧仍相似时命中。
type ScreenshotStableRecognition struct{}

func (r *ScreenshotStableRecognition) Run(ctx *maa.Context, arg *maa.CustomRecognitionArg) (*maa.CustomRecognitionResult, bool) {
_ = ctx
if arg == nil || arg.Img == nil {
log.Error().Str("component", "ScreenshotStableRecognition").Msg("nil arg or image")
return nil, false
}
if screenshotStableDone {
return nil, false
}
if !shouldRunScreenshotStable() {
return nil, false
}

cur := minicv.ImageConvertRGBA(arg.Img)
if cur == nil || cur.Bounds().Empty() {
log.Error().Str("component", "ScreenshotStableRecognition").Msg("empty image")
return nil, false
}

// 第 1 帧:只存 baseline
if screenshotStableCount == 0 {
screenshotStableBaseline = minicv.ImageCopy(cur)
screenshotStableCount = 1
return nil, false
}

// 第 2 帧:与 baseline 比;不匹配则结束,不再需要第 3 帧
if screenshotStableCount == 1 {
if !imagesSimilar(cur, screenshotStableBaseline, 0.99) {
finishScreenshotStable()
return nil, false
}
screenshotStableCount = 2
return nil, false
}

// 第 3 帧:仅在第 2 帧已匹配时才会走到这里
matched := imagesSimilar(cur, screenshotStableBaseline, 0.99)
finishScreenshotStable()
if !matched {
return nil, false
}

b := cur.Bounds()
return &maa.CustomRecognitionResult{
Box: maa.Rect{b.Min.X, b.Min.Y, b.Dx(), b.Dy()},
Detail: `{"stable":true}`,
}, true
}

func shouldRunScreenshotStable() bool {
if pienv.ControllerName() != "Win32-Front" {
return false
}
fullScreen, err := gamesetting.GetVideoFullScreen()
if err != nil {
log.Debug().
Err(err).
Str("component", "ScreenshotStableRecognition").
Msg("failed to read fullscreen setting, skip")
return false
}
return fullScreen == 1
}

func finishScreenshotStable() {
screenshotStableDone = true
screenshotStableBaseline = nil
screenshotStableCount = 0
}

func imagesSimilar(cur, baseline *image.RGBA, threshold float64) bool {
if cur == nil || baseline == nil {
return false
}
if cur.Bounds().Dx() != baseline.Bounds().Dx() || cur.Bounds().Dy() != baseline.Bounds().Dy() {
return false
}
stats := minicv.GetImageStats(baseline)
if stats.Std < 1e-6 {
return false
}
score := minicv.ComputeNCC(cur, minicv.GetIntegralArray(cur), baseline, stats, 0, 0)
return score >= threshold
}
4 changes: 3 additions & 1 deletion assets/locales/interface/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
"option.QuickTeleport.description": "When selecting a teleport point on the world map (or from a list), automatically click Teleport.",
"option.AutoPuzzleSolving.label": "Solve Puzzle",
"option.AutoPuzzleSolving.description": "Automatically clears the puzzle minigame for you (equivalent to enabling the ⌈🧩 Solve Puzzle⌋ task in Loop mode).",
"option.AutoAeroSalvage.label": "Aerial Salvage",
"option.AutoAeroSalvage.description": "Automatically clears the Aerial Salvage minigame for you (equivalent to enabling the ⌈🎈 Aerial Salvage⌋ task in Loop mode).",
"option.AutoTaskInterval.label": "Real-time Assist Interval",
"option.AutoTaskInterval.description": "Too fast a check interval may cause lag. Adjust according to your situation.",
"option.AutoTaskInterval.cases.AutoTaskIntervalFast.label": "Fast",
Expand Down Expand Up @@ -1004,6 +1006,7 @@
"task.SceneManager.focus.mod_check_failed_prefix": "<span style=\"color: red; font-weight: bold;\">UID display is incomplete. Enabling UID-hiding mods removes text in the upper-left corner of the screen, which affects recognition.</span>",
"task.SceneManager.focus.osd_check_failed": "<span style=\"color: red; font-weight: bold;\">Screen overlay detected. Close FPS/hardware monitoring overlays (e.g. FPS, CPU, GPU, Afterburner) and try again</span>",
"task.SceneManager.focus.language_not_zh_cn": "<span style=\"color: red; font-weight: bold;\">The game language is not Simplified Chinese. Compatibility is limited, and tasks may fail.</span>",
"task.SceneManager.focus.screenshot_stable_abnormal": "<span style=\"color: red; font-weight: bold;\">Environment recognition is abnormal. Please launch the game with D3D11, or switch to windowed mode and retry.</span>",
"description.file": "locales/interface/DESCRIPTION/DESCRIPTION.en_us.md",
"contact.file": "https://end.maafw.com/CONTACT/CONTACT.en_us.md",
"operator.Perlica": "Perlica",
Expand Down Expand Up @@ -1317,4 +1320,3 @@
"task.ProtocolSpace.focus.supply_plan.override_started.WEAPON_EXP": "Weapon EXP: Protocol Space target switched; start farming",
"task.ProtocolSpace.focus.supply_plan.reward_item_met.WEAPON_EXP": "Weapon EXP target met; end current Protocol Space run"
}

4 changes: 3 additions & 1 deletion assets/locales/interface/ja_jp.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
"option.QuickTeleport.description": "ワールドマップ(または一覧)で転送地点を選択した時に、転送を自動でクリックします。",
"option.AutoPuzzleSolving.label": "パズル解決",
"option.AutoPuzzleSolving.description": "パズルミニゲームを自動でクリアします(⌈🧩 パズル解決⌋タスクの繰り返し実行を有効にするのと同等)。",
"option.AutoAeroSalvage.label": "浮遊回収",
"option.AutoAeroSalvage.description": "浮遊回収ミニゲームを自動でクリアします(⌈🎈 浮遊回収⌋タスクの繰り返し実行を有効にするのと同等)。",
"option.AutoTaskInterval.label": "リアルタイム支援間隔",
"option.AutoTaskInterval.description": "間隔が速すぎるとカクつく可能性があります。状況に応じて調整してください。",
"option.AutoTaskInterval.cases.AutoTaskIntervalFast.label": "速い",
Expand Down Expand Up @@ -1004,6 +1006,7 @@
"task.SceneManager.focus.mod_check_failed_prefix": "<span style=\"color: red; font-weight: bold;\">UIDの表示が不完全です。UID非表示MODを有効にすると、画面左上の文字が欠落し、認識に影響します。</span>",
"task.SceneManager.focus.osd_check_failed": "<span style=\"color: red; font-weight: bold;\">画面にオーバーレイが検出されました。フレームレート/ハードウェア監視オーバーレイ(FPS、CPU、GPU、Afterburner など)を閉じてから再試行してください</span>",
"task.SceneManager.focus.language_not_zh_cn": "<span style=\"color: red; font-weight: bold;\">ゲーム言語が簡体字中国語ではありません。互換性が低く、タスクが失敗する可能性があります。</span>",
"task.SceneManager.focus.screenshot_stable_abnormal": "<span style=\"color: red; font-weight: bold;\">現在の環境認識に異常があります。D3D11 でゲームを起動するか、ウィンドウモードに切り替えて再試行してください。</span>",
"description.file": "locales/interface/DESCRIPTION/DESCRIPTION.ja_jp.md",
"contact.file": "https://end.maafw.com/CONTACT/CONTACT.ja_jp.md",
"operator.Perlica": "ペリカ",
Expand Down Expand Up @@ -1317,4 +1320,3 @@
"task.ProtocolSpace.focus.supply_plan.override_started.WEAPON_EXP": "武器経験 のプロトコル空間目標へ切り替え、周回を開始します",
"task.ProtocolSpace.focus.supply_plan.reward_item_met.WEAPON_EXP": "武器経験 が目標達成のため、現在のプロトコル空間周回を終了します"
}

4 changes: 3 additions & 1 deletion assets/locales/interface/ko_kr.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
"option.QuickTeleport.description": "월드맵(또는 목록)에서 텔레포트 지점을 선택할 때 텔레포트를 자동으로 클릭합니다.",
"option.AutoPuzzleSolving.label": "퍼즐 해결",
"option.AutoPuzzleSolving.description": "퍼즐 미니게임을 자동으로 클리어합니다(⌈🧩 퍼즐 해결⌋ 작업을 반복 실행으로 켜는 것과 동일).",
"option.AutoAeroSalvage.label": "부유 회수",
"option.AutoAeroSalvage.description": "부유 회수 미니게임을 자동으로 클리어합니다(⌈🎈 부유 회수⌋ 작업을 반복 실행으로 켜는 것과 동일).",
"option.AutoTaskInterval.label": "실시간 지원 간격",
"option.AutoTaskInterval.description": "간격이 너무 빠르면 끊김이 발생할 수 있습니다. 상황에 맞게 조정해 주세요.",
"option.AutoTaskInterval.cases.AutoTaskIntervalFast.label": "빠름",
Expand Down Expand Up @@ -1004,6 +1006,7 @@
"task.SceneManager.focus.mod_check_failed_prefix": "<span style=\"color: red; font-weight: bold;\">UID 표시가 불완전합니다. UID 숨김 MOD를 켜면 화면 왼쪽 상단의 문자가 누락되어 인식에 영향을 줍니다.</span>",
"task.SceneManager.focus.osd_check_failed": "<span style=\"color: red; font-weight: bold;\">화면에 오버레이가 감지되었습니다. 프레임레이트/하드웨어 모니터링 오버레이(FPS, CPU, GPU, Afterburner 등)를 끈 후 다시 시도하세요</span>",
"task.SceneManager.focus.language_not_zh_cn": "<span style=\"color: red; font-weight: bold;\">게임 언어가 간체 중국어가 아닙니다. 호환성이 낮아 작업이 실패할 수 있습니다.</span>",
"task.SceneManager.focus.screenshot_stable_abnormal": "<span style=\"color: red; font-weight: bold;\">현재 환경 인식이 비정상입니다. D3D11로 게임을 실행하거나 창 모드로 전환한 후 다시 시도하세요.</span>",
"description.file": "locales/interface/DESCRIPTION/DESCRIPTION.ko_kr.md",
"contact.file": "https://end.maafw.com/CONTACT/CONTACT.ko_kr.md",
"operator.Perlica": "펠리카",
Expand Down Expand Up @@ -1317,4 +1320,3 @@
"task.ProtocolSpace.focus.supply_plan.override_started.WEAPON_EXP": "무기 경험: 프로토콜 공간 목표 전환, 파밍 시작",
"task.ProtocolSpace.focus.supply_plan.reward_item_met.WEAPON_EXP": "무기 경험 목표 달성, 현재 프로토콜 공간 종료"
}

4 changes: 3 additions & 1 deletion assets/locales/interface/zh_cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
"option.QuickTeleport.description": "在大地图上点击传送点(或列表中有传送点)时,自动点击传送",
"option.AutoPuzzleSolving.label": "解拼图",
"option.AutoPuzzleSolving.description": "自动帮你通关拼图小游戏(等价于开启⌈🧩解拼图⌋任务的循环执行)",
"option.AutoAeroSalvage.label": "浮空回收",
"option.AutoAeroSalvage.description": "自动帮你完成浮空回收小游戏(等价于开启⌈🎈浮空回收⌋任务的循环执行)",
"option.AutoTaskInterval.label": "实时辅助间隔",
"option.AutoTaskInterval.description": "过快的检测间隔可能会导致卡顿,请根据实际情况调整",
"option.AutoTaskInterval.cases.AutoTaskIntervalFast.label": "快",
Expand Down Expand Up @@ -1004,6 +1006,7 @@
"task.SceneManager.focus.mod_check_failed_prefix": "<span style=\"color: red; font-weight: bold;\">UID显示不完整,开启UID隐藏MOD会导致画面左上方文字缺失,影响识别</span>",
"task.SceneManager.focus.osd_check_failed": "<span style=\"color: red; font-weight: bold;\">屏幕存在遮挡,请关闭帧率/硬件监控悬浮窗(如 FPS、CPU、GPU、Afterburner 等)后重试</span>",
"task.SceneManager.focus.language_not_zh_cn": "<span style=\"color: red; font-weight: bold;\">当前游戏语言不是简体中文,兼容性较差,任务可能会失败</span>",
"task.SceneManager.focus.screenshot_stable_abnormal": "<span style=\"color: red; font-weight: bold;\">当前环境识别异常,请使用 D3D11 启动游戏,或切换到窗口化重试</span>",
"description.file": "locales/interface/DESCRIPTION/DESCRIPTION.zh_cn.md",
"contact.file": "https://end.maafw.com/CONTACT/CONTACT.zh_cn.md",
"operator.Perlica": "佩丽卡",
Expand Down Expand Up @@ -1317,4 +1320,3 @@
"task.ProtocolSpace.focus.supply_plan.override_started.WEAPON_EXP": "武器经验 协议空间目标已切换,开始刷取",
"task.ProtocolSpace.focus.supply_plan.reward_item_met.WEAPON_EXP": "武器经验 目标已达标,结束当前协议空间刷取"
}

4 changes: 3 additions & 1 deletion assets/locales/interface/zh_tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
"option.QuickTeleport.description": "在大地圖上點擊傳送點(或列表中有傳送點)時,自動點擊傳送",
"option.AutoPuzzleSolving.label": "解拼圖",
"option.AutoPuzzleSolving.description": "自動幫你通關拼圖小遊戲(等價於開啟⌈🧩解拼圖⌋任務的重複執行)",
"option.AutoAeroSalvage.label": "浮空回收",
"option.AutoAeroSalvage.description": "自動幫你完成浮空回收小遊戲(等價於開啟⌈🎈浮空回收⌋任務的重複執行)",
"option.AutoTaskInterval.label": "即時輔助間隔",
"option.AutoTaskInterval.description": "過快的檢測間隔可能會導致卡頓,請根據實際情況調整",
"option.AutoTaskInterval.cases.AutoTaskIntervalFast.label": "快",
Expand Down Expand Up @@ -1004,6 +1006,7 @@
"task.SceneManager.focus.mod_check_failed_prefix": "<span style=\"color: red; font-weight: bold;\">UID顯示不完整,開啟UID隱藏MOD會導致畫面左上方文字缺失,影響識別</span>",
"task.SceneManager.focus.osd_check_failed": "<span style=\"color: red; font-weight: bold;\">螢幕存在遮擋,請關閉幀率/硬體監控懸浮窗(如 FPS、CPU、GPU、Afterburner 等)後重試</span>",
"task.SceneManager.focus.language_not_zh_cn": "<span style=\"color: red; font-weight: bold;\">當前遊戲語言不是簡體中文,相容性較差,任務可能會失敗</span>",
"task.SceneManager.focus.screenshot_stable_abnormal": "<span style=\"color: red; font-weight: bold;\">當前環境識別異常,請使用 D3D11 啟動遊戲,或切換到視窗化重試</span>",
"description.file": "locales/interface/DESCRIPTION/DESCRIPTION.zh_tw.md",
"contact.file": "https://end.maafw.com/CONTACT/CONTACT.zh_tw.md",
"operator.Perlica": "佩麗卡",
Expand Down Expand Up @@ -1317,4 +1320,3 @@
"task.ProtocolSpace.focus.supply_plan.override_started.WEAPON_EXP": "武器經驗 協議空間目標已切換,開始刷取",
"task.ProtocolSpace.focus.supply_plan.reward_item_met.WEAPON_EXP": "武器經驗 目標已達標,結束當前協議空間刷取"
}

Binary file added assets/resource/image/AccountSwitch/LoginClose.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading