forked from kevinpbuckley/VibeUE
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathBuildAndLaunchGame.ps1
More file actions
354 lines (320 loc) · 15.8 KB
/
Copy pathBuildAndLaunchGame.ps1
File metadata and controls
354 lines (320 loc) · 15.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# Build and Launch an Unreal Engine project in Development Mode without Debugger
# Can live anywhere inside the project tree (e.g. Plugins/VibeUE).
# Walks UP from its own directory until it finds a .uproject file.
param(
[string]$Mode = "Development",
[switch]$Clean,
[switch]$SkipBuild,
# Force a full recompile of the VibeUE plugin under strict settings.
# Strict compilation (warnings-as-errors / deprecation-as-error) is enforced
# module-wide via VibeUE.Build.cs (bWarningsAsErrors = true), so every build is
# strict. Incremental builds, however, only recompile changed files — so a stale
# object file can hide a freshly-deprecated engine API. -StrictRebuild wipes the
# plugin's Binaries/Intermediate so ALL plugin files are recompiled and rechecked.
[switch]$StrictRebuild,
# Block until the launched editor writes its readiness signal
# (Saved/VibeUE/Signals/editor-<pid>-true.json, written once VibeUE's toolsets are
# registered), so agents can chain the next MCP call without watching the file
# themselves. Exit codes: 0 ready, 2 timed out, 3 editor exited before ready.
[switch]$WaitForReady,
[int]$ReadyTimeoutSec = 120,
# Map to open on launch (e.g. /Game/Maps/TrainingPool). Without this the editor opens the
# project's default map, which after a mid-task relaunch is usually the WRONG level — world
# edits then land on the default map (issue #554). The loaded map is also published in the
# readiness signal JSON as "currentMap" so agents can verify before editing.
[string]$Map = ""
)
# ============================================================================
# Walk up the directory tree to find the .uproject
# ============================================================================
$searchDir = $PSScriptRoot
$uprojectFile = $null
while ($searchDir) {
$found = Get-ChildItem -Path $searchDir -Filter "*.uproject" -File -ErrorAction SilentlyContinue
if ($found) {
if ($found.Count -gt 1) {
Write-Host "WARNING: Multiple .uproject files found in $searchDir, using first: $($found[0].Name)" -ForegroundColor Yellow
}
$uprojectFile = $found[0]
break
}
$parent = Split-Path $searchDir -Parent
if ($parent -eq $searchDir) { break } # reached filesystem root
$searchDir = $parent
}
if (-not $uprojectFile) {
Write-Host "ERROR: No .uproject file found walking up from $PSScriptRoot" -ForegroundColor Red
exit 1
}
$projectPath = $uprojectFile.FullName
$projectName = $uprojectFile.BaseName
$projectRoot = $uprojectFile.DirectoryName
# ============================================================================
# Auto-discover Unreal Engine install from EngineAssociation in .uproject
# ============================================================================
$uprojectJson = Get-Content $projectPath -Raw | ConvertFrom-Json
$engineAssociation = $uprojectJson.EngineAssociation
# First try: look up custom/source builds from registry (HKCU)
$enginePath = $null
try {
$customBuilds = Get-ItemProperty "HKCU:\SOFTWARE\Epic Games\Unreal Engine\Builds" -ErrorAction SilentlyContinue
if ($customBuilds -and $customBuilds.$engineAssociation) {
$enginePath = $customBuilds.$engineAssociation
}
} catch {}
# Second try: standard launcher installs (HKLM)
if (-not $enginePath) {
try {
$regPath = "HKLM:\SOFTWARE\EpicGames\Unreal Engine\$engineAssociation"
$regEntry = Get-ItemProperty $regPath -ErrorAction SilentlyContinue
if ($regEntry -and $regEntry.InstalledDirectory) {
$enginePath = $regEntry.InstalledDirectory
}
} catch {}
}
# Third try: well-known path pattern (e.g. UE_5.7)
if (-not $enginePath) {
$candidates = @(
"E:\Program Files\Epic Games\UE_$engineAssociation",
"C:\Program Files\Epic Games\UE_$engineAssociation",
"D:\Program Files\Epic Games\UE_$engineAssociation"
)
foreach ($c in $candidates) {
if (Test-Path $c) { $enginePath = $c; break }
}
}
# Fourth try: ask for a manual path rather than just giving up (the Epic launcher
# doesn't always register a drive, e.g. an engine installed on a non-default drive).
if (-not $enginePath) {
Write-Host "Could not auto-detect Unreal Engine '$engineAssociation' from the registry or common install paths." -ForegroundColor Yellow
# Non-interactive session (CI, agent-run builds): fail fast instead of prompting.
if ([Console]::IsInputRedirected -or ([Environment]::GetCommandLineArgs() -match '^-NonI')) {
Write-Host "ERROR: Non-interactive session - cannot prompt for a path. Set the engine path manually or register the install." -ForegroundColor Red
exit 1
}
while (-not $enginePath) {
$manualPath = Read-Host "Enter the full path to your Unreal Engine install (e.g. D:\Program Files\Epic Games\UE_$engineAssociation), or leave blank to abort"
if ([string]::IsNullOrWhiteSpace($manualPath)) {
Write-Host "ERROR: No Unreal Engine path provided. Aborting." -ForegroundColor Red
exit 1
}
$manualPath = $manualPath.Trim().Trim('"')
if (Test-Path (Join-Path $manualPath "Engine\Build\BatchFiles\Build.bat")) {
$enginePath = $manualPath
} else {
Write-Host "That path doesn't look like an Unreal Engine install (expected Engine\Build\BatchFiles\Build.bat under it)." -ForegroundColor Red
}
}
}
$buildBat = Join-Path $enginePath "Engine\Build\BatchFiles\Build.bat"
$editorExe = Join-Path $enginePath "Engine\Binaries\Win64\UnrealEditor.exe"
$buildManifestPath = Join-Path $projectRoot "Saved\VibeUE\last-build.json"
function Write-BuildManifest([string]$Status, [Nullable[int]]$ExitCode, [string]$Diagnostic = "") {
$manifestDir = Split-Path $buildManifestPath -Parent
New-Item -ItemType Directory -Path $manifestDir -Force | Out-Null
$payload = [ordered]@{
schema = "vibeue.build.v1"
status = $Status
projectFile = [IO.Path]::GetFullPath($projectPath)
engineRoot = [IO.Path]::GetFullPath($enginePath)
target = "${projectName}Editor"
platform = "Win64"
configuration = $Mode
command = "Build.bat ${projectName}Editor Win64 $Mode `"$projectPath`" -waitmutex"
completedAtIso = if ($Status -in @("succeeded", "failed", "skipped")) { [DateTime]::UtcNow.ToString("o") } else { $null }
exitCode = $ExitCode
verdict = $Status
logPath = Join-Path $env:LOCALAPPDATA "UnrealBuildTool\Log.txt"
diagnostic = $Diagnostic
}
$temp = "$buildManifestPath.tmp"
$payload | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $temp -Encoding UTF8
Move-Item -LiteralPath $temp -Destination $buildManifestPath -Force
}
Write-Host "=== $projectName Build and Launch Script ===" -ForegroundColor Cyan
Write-Host "Script : $PSScriptRoot" -ForegroundColor Gray
Write-Host "Project : $projectPath" -ForegroundColor Yellow
Write-Host "Engine : $enginePath" -ForegroundColor Yellow
Write-Host "Mode : $Mode" -ForegroundColor Yellow
# Stop any running Unreal Engine processes gracefully
$unrealProcesses = Get-Process | Where-Object {
$_.ProcessName -like "UnrealEditor*" -or
$_.ProcessName -like "CrashReportClient*" -or
$_.ProcessName -eq "UnrealTraceServer" -or
($_.ProcessName -eq "crashpad_handler" -and $_.MainModule.FileName -like "*Epic Games*")
}
if ($unrealProcesses) {
Write-Host "Requesting Unreal Engine processes to close gracefully..." -ForegroundColor Yellow
$unrealProcesses | ForEach-Object {
if ($_.MainWindowHandle -ne 0) {
$_.CloseMainWindow() | Out-Null
}
}
# Wait up to 15 seconds for graceful shutdown
$timeout = 15
$elapsed = 0
$remaining = Get-Process | Where-Object {
$_.ProcessName -like "UnrealEditor*" -or
$_.ProcessName -like "CrashReportClient*" -or
$_.ProcessName -eq "UnrealTraceServer" -or
($_.ProcessName -eq "crashpad_handler" -and $_.MainModule.FileName -like "*Epic Games*")
}
while ($remaining -and ($elapsed -lt $timeout)) {
Start-Sleep 1
$elapsed++
Write-Host " Waiting for processes to close... ($elapsed/$timeout seconds)" -ForegroundColor Gray
$remaining = Get-Process | Where-Object {
$_.ProcessName -like "UnrealEditor*" -or
$_.ProcessName -like "CrashReportClient*" -or
$_.ProcessName -eq "UnrealTraceServer" -or
($_.ProcessName -eq "crashpad_handler" -and $_.MainModule.FileName -like "*Epic Games*")
}
}
# Force kill if still running
$remaining = Get-Process | Where-Object {
$_.ProcessName -like "UnrealEditor*" -or
$_.ProcessName -like "CrashReportClient*" -or
$_.ProcessName -eq "UnrealTraceServer" -or
($_.ProcessName -eq "crashpad_handler" -and $_.MainModule.FileName -like "*Epic Games*")
}
if ($remaining) {
Write-Host "Processes didn't close gracefully, forcing shutdown..." -ForegroundColor Yellow
$remaining | ForEach-Object {
Write-Host " Killing $($_.ProcessName) (PID: $($_.Id)) and child processes..." -ForegroundColor Gray
taskkill /F /PID $_.Id /T 2>$null
}
Start-Sleep 3
$stillRunning = Get-Process | Where-Object { $_.ProcessName -like "UnrealEditor*" }
if ($stillRunning) {
Write-Host " Some processes still running, using taskkill by name..." -ForegroundColor Yellow
taskkill /F /IM UnrealEditor.exe /T 2>$null
Start-Sleep 2
}
Write-Host "All Unreal Engine processes terminated!" -ForegroundColor Green
} else {
Write-Host "All processes closed gracefully!" -ForegroundColor Green
}
} else {
Write-Host "No running Unreal Engine processes found." -ForegroundColor Gray
}
# Clean build if requested (relative to project root)
if ($Clean) {
Write-Host "Cleaning intermediate and binary files..." -ForegroundColor Yellow
$cleanPaths = @(
(Join-Path $projectRoot "Intermediate"),
(Join-Path $projectRoot "Binaries")
)
foreach ($p in $cleanPaths) {
if (Test-Path $p) { Remove-Item $p -Recurse -Force }
}
# Clean plugin binaries too
Get-ChildItem (Join-Path $projectRoot "Plugins") -Directory -ErrorAction SilentlyContinue | ForEach-Object {
foreach ($sub in @("Binaries","Intermediate")) {
$subPath = Join-Path $_.FullName $sub
if (Test-Path $subPath) { Remove-Item $subPath -Recurse -Force }
}
}
}
# Strict rebuild: wipe the VibeUE plugin's build artifacts so every plugin file is
# recompiled under the module's warnings-as-errors setting (catches newly-deprecated APIs).
if ($StrictRebuild -and -not $Clean) {
Write-Host "Strict rebuild: cleaning VibeUE plugin Binaries/Intermediate..." -ForegroundColor Yellow
foreach ($sub in @("Binaries","Intermediate")) {
$subPath = Join-Path $PSScriptRoot $sub
if (Test-Path $subPath) { Remove-Item $subPath -Recurse -Force }
}
}
# Build the project
if (-not $SkipBuild) {
Write-Host "Building $projectName in $Mode mode (strict: warnings-as-errors via VibeUE.Build.cs)..." -ForegroundColor Yellow
Write-BuildManifest "running" $null
& $buildBat "${projectName}Editor" Win64 $Mode $projectPath -waitmutex
if ($LASTEXITCODE -ne 0) {
Write-BuildManifest "failed" $LASTEXITCODE "UnrealBuildTool returned a non-zero exit code."
Write-Host "Build failed! Exit code: $LASTEXITCODE" -ForegroundColor Red
exit 1
}
Write-BuildManifest "succeeded" 0
Write-Host "Build completed successfully!" -ForegroundColor Green
} else {
Write-BuildManifest "skipped" $null "Build was skipped by caller; this is not compile verification."
Write-Host "Skipping build..." -ForegroundColor Yellow
}
# Clear logs folder (relative to project root)
Write-Host "Clearing all files in logs folder..." -ForegroundColor Yellow
$logsPath = Join-Path $projectRoot "Saved\Logs"
if (Test-Path $logsPath) {
$logsCleared = (Get-ChildItem $logsPath -File -ErrorAction SilentlyContinue).Count
Get-ChildItem $logsPath -File -ErrorAction SilentlyContinue | ForEach-Object {
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
}
if ($logsCleared -gt 0) {
Write-Host "Cleared $logsCleared file(s) from logs folder!" -ForegroundColor Green
} else {
Write-Host "No files found in logs folder." -ForegroundColor Gray
}
} else {
Write-Host "Logs folder not found." -ForegroundColor Gray
}
# Clear agent conversation files (relative to project root)
Write-Host "Clearing agent conversations..." -ForegroundColor Yellow
$agentConversationsPath = Join-Path $projectRoot "Saved\Logs\AgentConversations"
if (Test-Path $agentConversationsPath) {
$conversationsCleared = (Get-ChildItem $agentConversationsPath -File -ErrorAction SilentlyContinue).Count
Get-ChildItem $agentConversationsPath -File -ErrorAction SilentlyContinue | ForEach-Object {
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
}
if ($conversationsCleared -gt 0) {
Write-Host "Cleared $conversationsCleared agent conversation file(s)!" -ForegroundColor Green
} else {
Write-Host "No agent conversation files found." -ForegroundColor Gray
}
} else {
Write-Host "AgentConversations folder not found." -ForegroundColor Gray
}
# Launch Unreal Editor
Write-Host "Launching Unreal Editor..." -ForegroundColor Yellow
# The project path MUST be quoted: -ArgumentList passes the string to the child process
# verbatim, so a path with a space (e.g. Documents\Unreal Projects, Unreal's default
# location) arrives as two invalid arguments and the editor silently opens the last
# project or the Project Browser instead (issue #532).
$editorArgs = "`"$projectPath`""
if ($Map) {
$editorArgs += " `"$Map`""
Write-Host "Opening map: $Map" -ForegroundColor Yellow
}
$editorProcess = Start-Process -FilePath $editorExe -ArgumentList $editorArgs -PassThru
# Windows recycles process IDs, so an Editor that crashed without running OnPreExit can leave a signal
# file whose name matches the PID we just got. VibeUE also clears it in RegisterToolsets(), but that runs
# late in startup - an agent watching from now would see the stale file first and call MCP too early.
# The Editor takes tens of seconds to reach RegisterToolsets(), so deleting here cannot race its write.
$signalsDir = Join-Path $projectRoot "Saved\VibeUE\Signals"
if (Test-Path $signalsDir) {
Get-ChildItem -Path $signalsDir -Filter "editor-$($editorProcess.Id)-*.json*" -File -ErrorAction SilentlyContinue |
ForEach-Object {
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
Write-Host "Cleared stale readiness signal: $($_.Name)" -ForegroundColor Gray
}
}
Write-Output "Editor-PID=$($editorProcess.Id)"
if ($WaitForReady) {
$readySignal = Join-Path $signalsDir "editor-$($editorProcess.Id)-true.json"
Write-Host "Waiting for editor readiness signal (timeout: ${ReadyTimeoutSec}s)..." -ForegroundColor Yellow
$waited = 0
while (-not (Test-Path $readySignal)) {
if ($editorProcess.HasExited) {
Write-Host "Editor process exited (code $($editorProcess.ExitCode)) before signaling ready." -ForegroundColor Red
exit 3
}
if ($waited -ge $ReadyTimeoutSec) {
Write-Host "Editor did not signal ready within ${ReadyTimeoutSec}s (it may still be loading)." -ForegroundColor Red
exit 2
}
Start-Sleep 1
$waited++
}
Write-Host "Editor is ready (signaled after ${waited}s)." -ForegroundColor Green
}
Write-Host "=== Launch Complete ===" -ForegroundColor Green
Write-Host "Unreal Editor is starting with $projectName" -ForegroundColor Green
Write-Host "No debugger attached - MCP tools should work without breakpoints!" -ForegroundColor Green