Windows poll the mouse cursor object (0xcdbadc, x +4) for hover; it is shifted inside a moved window's render/event scope like the events. Tooltips are separate windows placed by the hovered window in its 1024 layout, so cUI_Tooltip renders with the offset of the moved window under the cursor. Adds Move-To to the test input helpers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
88 lines
3.9 KiB
PowerShell
88 lines
3.9 KiB
PowerShell
# Drive the game for automated tests: start it, click, press keys, take
|
|
# screenshots. Dot-source it: . .\tools\game-input.ps1
|
|
#
|
|
# Start-Sacred # launcher, returns the sacred.exe process
|
|
# Click-At 1720 779 # screen pixels (left click)
|
|
# Press-Key 0x1B # virtual key (ESC)
|
|
# Save-Screen screenshots\x.png # primary monitor area of the game
|
|
# Stop-Sacred $game # only the given process
|
|
#
|
|
# Input goes to the foreground window: don't touch mouse/keyboard meanwhile.
|
|
|
|
Add-Type -AssemblyName System.Drawing
|
|
Add-Type -Namespace SacredRe -Name Input -MemberDefinition @'
|
|
[DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y);
|
|
[DllImport("user32.dll")] public static extern void mouse_event(uint flags, uint dx, uint dy, uint data, System.UIntPtr extra);
|
|
[DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte scan, uint flags, System.UIntPtr extra);
|
|
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(System.IntPtr window);
|
|
'@
|
|
|
|
$script:Root = Split-Path $PSScriptRoot
|
|
|
|
# The game pauses its start-up until its window is active, and Windows does
|
|
# not hand the foreground to a window started from a background process; a
|
|
# synthetic Alt press lifts that restriction for SetForegroundWindow.
|
|
function Focus-Sacred($Game) {
|
|
$Game.Refresh()
|
|
if ($Game.MainWindowHandle -eq [IntPtr]::Zero) { return $false }
|
|
[SacredRe.Input]::keybd_event(0x12, 0, 0, [UIntPtr]::Zero)
|
|
[SacredRe.Input]::keybd_event(0x12, 0, 2, [UIntPtr]::Zero)
|
|
[SacredRe.Input]::SetForegroundWindow($Game.MainWindowHandle)
|
|
}
|
|
|
|
function Start-Sacred([string]$Arguments = "log", [int]$WaitSeconds = 18) {
|
|
if (Get-Process sacred -ErrorAction SilentlyContinue) { throw "sacred.exe is already running" }
|
|
$null = Start-Process (Join-Path $script:Root "build\x86-debug\sacred_re.exe") -ArgumentList $Arguments
|
|
$game = $null
|
|
for ($i = 0; $i -lt $WaitSeconds * 4; $i++) {
|
|
Start-Sleep -Milliseconds 250
|
|
if (-not $game) { $game = Get-Process sacred -ErrorAction SilentlyContinue | Select-Object -First 1 }
|
|
if ($game) { Focus-Sacred $game | Out-Null }
|
|
}
|
|
if (-not $game -or $game.HasExited) { throw "sacred.exe did not start (or crashed)" }
|
|
$game
|
|
}
|
|
|
|
function Stop-Sacred($Game) {
|
|
if ($Game -and -not $Game.HasExited) {
|
|
Stop-Process -Id $Game.Id -Force
|
|
$Game.WaitForExit(10000) | Out-Null
|
|
}
|
|
}
|
|
|
|
function Click-At([int]$X, [int]$Y, [int]$AfterMs = 800) {
|
|
[SacredRe.Input]::SetCursorPos($X, $Y) | Out-Null
|
|
Start-Sleep -Milliseconds 150
|
|
[SacredRe.Input]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero) # left down
|
|
Start-Sleep -Milliseconds 80
|
|
[SacredRe.Input]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero) # left up
|
|
Start-Sleep -Milliseconds $AfterMs
|
|
}
|
|
|
|
function Press-Key([byte]$VirtualKey, [int]$AfterMs = 800) {
|
|
[SacredRe.Input]::keybd_event($VirtualKey, 0, 0, [UIntPtr]::Zero)
|
|
Start-Sleep -Milliseconds 80
|
|
[SacredRe.Input]::keybd_event($VirtualKey, 0, 2, [UIntPtr]::Zero) # key up
|
|
Start-Sleep -Milliseconds $AfterMs
|
|
}
|
|
|
|
function Save-Screen([string]$Path, [int]$Width = 3440, [int]$Height = 1440) {
|
|
$bitmap = New-Object System.Drawing.Bitmap $Width, $Height
|
|
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
$graphics.CopyFromScreen(0, 0, 0, 0, $bitmap.Size)
|
|
$full = if ([IO.Path]::IsPathRooted($Path)) { $Path } else { Join-Path $script:Root $Path }
|
|
$bitmap.Save($full)
|
|
$graphics.Dispose(); $bitmap.Dispose()
|
|
$full
|
|
}
|
|
|
|
# Moves the cursor in small steps (the game only sees real mouse movement).
|
|
function Move-To([int]$X, [int]$Y, [int]$AfterMs = 800) {
|
|
[SacredRe.Input]::SetCursorPos($X - 20, $Y - 20) | Out-Null
|
|
for ($i = 1; $i -le 10; $i++) {
|
|
Start-Sleep -Milliseconds 20
|
|
[SacredRe.Input]::SetCursorPos($X - 20 + 2 * $i, $Y - 20 + 2 * $i) | Out-Null
|
|
}
|
|
Start-Sleep -Milliseconds $AfterMs
|
|
}
|