Skip to content

Commit aaf8466

Browse files
committed
Release v0.6.4
1 parent 050763e commit aaf8466

15 files changed

Lines changed: 742 additions & 5 deletions
0 Bytes
Binary file not shown.
7 KB
Binary file not shown.
0 Bytes
Binary file not shown.

1.6/Assemblies/RimBridgeServer.dll

8 KB
Binary file not shown.

About/About.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<li>1.6</li>
77
</supportedVersions>
88
<packageId>brrainz.rimbridgeserver</packageId>
9-
<modVersion>0.6.3</modVersion>
9+
<modVersion>0.6.4</modVersion>
1010
<url>https://github.com/pardeike/RimBridgeServer</url>
1111
<description>RimBridgeServer spins up an MCP server inside RimWorld to enable automated, remote control of a running game. It is designed so AI agents or external tools can rapidly test and validate the mods they are developing by issuing commands and querying state through a stable protocol.
1212

About/Manifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
22
<Manifest>
33
<identifier>net.pardeike.rimworld.mod.rimbridgeserver</identifier>
4-
<version>0.6.3</version>
4+
<version>0.6.4</version>
55
<targetVersions>
66
<li>1.6.0</li>
77
</targetVersions>

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<ModName>RimBridgeServer</ModName>
44
<ModFileName>RimBridgeServer</ModFileName>
55
<Repository>https://github.com/pardeike/RimBridgeServer</Repository>
6-
<ModVersion>0.6.3</ModVersion>
6+
<ModVersion>0.6.4</ModVersion>
77
<ProjectGuid>{6A41B185-4CC6-43FD-A4D3-5D2819F4DC96}</ProjectGuid>
88
</PropertyGroup>
99
</Project>

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ Lua authoring note: `rimbridge/run_lua` is intentionally a lowered Lua subset, n
161161

162162
- `rimworld/pause_game` - Pause or unpause the game
163163
- `rimworld/set_time_speed` - Set RimWorld's current time speed directly
164+
- `rimworld/play_for` - Unpause the current game at a requested time speed for a bounded real-time duration, then pause it again, ending early if the game is paused, returns to the main menu, or the session changes
164165
- `rimworld/list_debug_action_roots` - List top-level RimWorld debug action roots using stable internal debug-action paths
165166
- `rimworld/list_debug_action_children` - List direct children of a RimWorld debug action path
166167
- `rimworld/search_debug_actions` - Search the full RimWorld debug-action tree globally by path, label, category, and source metadata so callers do not need to walk one subtree at a time

Source/BuiltInCapabilityModuleProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ private static CapabilityExecutionKind ResolveExecutionKind(string toolName)
303303
"rimworld/start_debug_game" => CapabilityExecutionKind.LongEventBound,
304304
"rimworld/load_game" => CapabilityExecutionKind.LongEventBound,
305305
"rimworld/switch_language" => CapabilityExecutionKind.LongEventBound,
306+
"rimworld/play_for" => CapabilityExecutionKind.BackgroundObserved,
306307
"rimworld/take_screenshot" => CapabilityExecutionKind.BackgroundObserved,
307308
"rimworld/frame_cell_rect" => CapabilityExecutionKind.BackgroundObserved,
308309
"rimworld/screenshot_cell_rect" => CapabilityExecutionKind.BackgroundObserved,

Source/LifecycleCapabilityModule.cs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Linq;
5+
using RimBridgeServer.Core;
46
using RimWorld;
57
using UnityEngine;
68
using Verse;
@@ -63,6 +65,94 @@ public object SetTimeSpeed(string speed = "Normal")
6365
};
6466
}
6567

68+
public object PlayFor(int durationMs, string speed = "Normal", int pollIntervalMs = 25)
69+
{
70+
if (durationMs <= 0)
71+
{
72+
return new
73+
{
74+
success = false,
75+
message = "durationMs must be greater than 0.",
76+
state = RimWorldState.ToolStateSnapshot()
77+
};
78+
}
79+
80+
if (pollIntervalMs < 0)
81+
{
82+
return new
83+
{
84+
success = false,
85+
message = "pollIntervalMs cannot be negative.",
86+
state = RimWorldState.ToolStateSnapshot()
87+
};
88+
}
89+
90+
if (Enum.TryParse<TimeSpeed>(speed, ignoreCase: true, out var parsedSpeed) == false)
91+
{
92+
return new
93+
{
94+
success = false,
95+
message = $"Unknown time speed '{speed}'. Supported values: Normal, Fast, Superfast, Ultrafast.",
96+
state = RimWorldState.ToolStateSnapshot()
97+
};
98+
}
99+
100+
if (parsedSpeed == TimeSpeed.Paused)
101+
{
102+
return new
103+
{
104+
success = false,
105+
message = "play_for requires an active play speed. Use Normal, Fast, Superfast, or Ultrafast.",
106+
state = RimWorldState.ToolStateSnapshot()
107+
};
108+
}
109+
110+
try
111+
{
112+
Stopwatch stopwatch = null;
113+
var controller = new TimedPlaybackController();
114+
var result = controller.PlayFor(
115+
durationMs,
116+
pollIntervalMs,
117+
getElapsedMs: () => stopwatch?.ElapsedMilliseconds ?? 0L,
118+
readState: () => RimBridgeMainThread.Invoke(CapturePlaybackState, timeoutMs: 5000),
119+
startPlayback: () => RimBridgeMainThread.Invoke(() =>
120+
{
121+
stopwatch = Stopwatch.StartNew();
122+
EnsurePlaybackRunning(parsedSpeed);
123+
}, timeoutMs: 5000),
124+
pausePlayback: () => RimBridgeMainThread.Invoke(PauseIfNeeded, timeoutMs: 5000));
125+
126+
return new
127+
{
128+
success = result.Success,
129+
requestedDurationMs = durationMs,
130+
elapsedMs = result.ElapsedMs,
131+
pollIntervalMs,
132+
timeSpeed = parsedSpeed.ToString(),
133+
paused = result.PausedAtEnd,
134+
initiallyPaused = result.InitiallyPaused,
135+
startTick = result.StartTick,
136+
endTick = result.EndTick,
137+
advancedTicks = result.AdvancedTicks,
138+
attempts = result.Attempts,
139+
probeFailureCount = result.ProbeFailureCount,
140+
lastProbeError = string.IsNullOrWhiteSpace(result.LastProbeError) ? null : result.LastProbeError,
141+
message = result.Message,
142+
state = result.Snapshot
143+
};
144+
}
145+
catch (Exception ex)
146+
{
147+
return new
148+
{
149+
success = false,
150+
message = $"Failed to play for {durationMs}ms: {ex.Message}",
151+
state = RimWorldState.ToolStateSnapshot()
152+
};
153+
}
154+
}
155+
66156
public object StartDebugGame()
67157
{
68158
if (LongEventHandler.AnyEventNowOrWaiting)
@@ -247,4 +337,46 @@ public object LoadGame(string saveName)
247337
state = RimWorldState.ToolStateSnapshot()
248338
};
249339
}
340+
341+
private static TimedPlaybackState CapturePlaybackState()
342+
{
343+
var hasPlayableGame = Current.ProgramState == ProgramState.Playing
344+
&& Current.Game != null
345+
&& Find.TickManager != null;
346+
var hasLongEvent = LongEventHandler.AnyEventNowOrWaiting;
347+
var atMainMenu = GenScene.InEntryScene || Current.ProgramState == ProgramState.Entry;
348+
349+
return new TimedPlaybackState
350+
{
351+
Available = hasPlayableGame && !hasLongEvent,
352+
Paused = hasPlayableGame && Find.TickManager.Paused,
353+
TickCount = hasPlayableGame ? Find.TickManager.TicksGame : 0,
354+
SessionToken = Current.Game,
355+
Snapshot = RimWorldState.ToolStateSnapshot(),
356+
Message = hasPlayableGame
357+
? (hasLongEvent ? "RimWorld is busy with a long event." : string.Empty)
358+
: (atMainMenu ? "RimWorld returned to the main menu." : "No playable game is currently loaded.")
359+
};
360+
}
361+
362+
private static void EnsurePlaybackRunning(TimeSpeed speed)
363+
{
364+
if (Current.ProgramState != ProgramState.Playing || Current.Game == null || Find.TickManager == null)
365+
throw new InvalidOperationException("No playable game is currently loaded.");
366+
if (LongEventHandler.AnyEventNowOrWaiting)
367+
throw new InvalidOperationException("RimWorld is busy with a long event.");
368+
369+
Find.TickManager.CurTimeSpeed = speed;
370+
if (Find.TickManager.Paused)
371+
Find.TickManager.TogglePaused();
372+
}
373+
374+
private static void PauseIfNeeded()
375+
{
376+
if (Current.Game == null || Find.TickManager == null)
377+
return;
378+
379+
if (!Find.TickManager.Paused)
380+
Find.TickManager.TogglePaused();
381+
}
250382
}

0 commit comments

Comments
 (0)