diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-20 15:14:23 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-20 15:14:23 +0800 |
| commit | 9724663602b4b1eb755bf334c475dc2c80e4b386 (patch) | |
| tree | 681bfcc9f7bb6422ed80142702cfff1498f064b1 /src/LivingVillage.Desktop/Game.fs | |
| parent | 7e197babfbb68d7f7d1866d6a0ca47cf17d3af23 (diff) | |
| download | living-village-9724663602b4b1eb755bf334c475dc2c80e4b386.tar.gz | |
feat(m6a): add save speed and day-night controls
Diffstat (limited to 'src/LivingVillage.Desktop/Game.fs')
| -rw-r--r-- | src/LivingVillage.Desktop/Game.fs | 77 |
1 files changed, 67 insertions, 10 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs index 09d3364..232930d 100644 --- a/src/LivingVillage.Desktop/Game.fs +++ b/src/LivingVillage.Desktop/Game.fs @@ -139,13 +139,19 @@ type LivingVillageGame() as this = let autoplay = Environment.GetEnvironmentVariable("LV_AUTOPLAY") = "1" let titleSuffix = if autoplay then " [AUTOPLAY]" else "" let modeName = if autoplay then "autoplay" else "keyboard" - let stepsPerFrame = + let configuredStepsPerFrame = match Environment.GetEnvironmentVariable("LV_STEPS_PER_FRAME") with | null | "" -> 1 | s -> match Int32.TryParse s with | true, n when n > 0 -> n | _ -> 1 + let initialSimulationControl, initialLegacyStepsPerFrame = + SimulationControl.initialForConfiguredSteps configuredStepsPerFrame + let savePath = + match Environment.GetEnvironmentVariable("LV_SAVE_PATH") with + | null | "" -> "living-village.save" + | path -> path let graphics = new GraphicsDeviceManager(this) let mutable spriteBatch = Unchecked.defaultof<SpriteBatch> let mutable atlas = Unchecked.defaultof<Texture2D> @@ -159,6 +165,8 @@ type LivingVillageGame() as this = let mutable relationView = false let mutable relationSnapshot: float32[,] option = None let mutable m5View = M5Interaction.initial + let mutable simulationControl = initialSimulationControl + let mutable legacyStepsPerFrame = initialLegacyStepsPerFrame do graphics.PreferredBackBufferWidth <- 1280 @@ -166,9 +174,9 @@ type LivingVillageGame() as this = graphics.SynchronizeWithVerticalRetrace <- true this.IsFixedTimeStep <- true this.TargetElapsedTime <- TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60L) - this.Window.Title <- if autoplay then "Living Village M5 [AUTOPLAY]" else "Living Village M5" + this.Window.Title <- if autoplay then "Living Village M6 [AUTOPLAY]" else "Living Village M6" printfn $"mode={modeName} seed=42" - printfn "controls=WASD move E interact 1-6 choose Q observe Tab needs C chronicle L relations Esc close/exit" + printfn "controls=WASD move E interact 1-6 choose Q observe Tab needs C chronicle L relations P pause F1/F2/F3 speed F6 save F7 load Esc close/exit" member private this.CenterCamera() = let viewport = this.GraphicsDevice.Viewport let vw = float32 viewport.Width @@ -196,6 +204,37 @@ type LivingVillageGame() as this = let pressedAny (keys: Keys list) = keys |> List.exists pressed let lPressed = pressed Keys.L let escapePressed = pressed Keys.Escape + if pressed Keys.P then + simulationControl <- SimulationControl.togglePause simulationControl + printfn $"simulation={M6Presentation.clockLabel simulationControl}" + if pressed Keys.F1 then + simulationControl <- SimulationControl.setSpeed OneX simulationControl + legacyStepsPerFrame <- None + printfn $"simulation={M6Presentation.clockLabel simulationControl}" + if pressed Keys.F2 then + simulationControl <- SimulationControl.setSpeed TwoX simulationControl + legacyStepsPerFrame <- None + printfn $"simulation={M6Presentation.clockLabel simulationControl}" + if pressed Keys.F3 then + simulationControl <- SimulationControl.setSpeed FiveX simulationControl + legacyStepsPerFrame <- None + printfn $"simulation={M6Presentation.clockLabel simulationControl}" + if pressed Keys.F6 then + try + WorldSave.saveToFile savePath world + printfn $"save=ok path={savePath} tick={world.Tick}" + with + | ex -> printfn $"save=error path={savePath} message={ex.Message}" + if pressed Keys.F7 then + match WorldSave.loadFromFile savePath with + | Ok nextWorld -> + world <- nextWorld + relationView <- false + relationSnapshot <- None + m5View <- M5Interaction.initial + this.CenterCamera() + printfn $"load=ok path={savePath} tick={world.Tick}" + | Error failure -> printfn $"load=error path={savePath} message={failure}" if escapePressed then if m5View.Panel <> WorldPanel then let nextWorld, nextView = M5Interaction.apply ClosePanel world m5View @@ -245,8 +284,10 @@ type LivingVillageGame() as this = elif kb.IsKeyDown(Keys.W) then -1.0f else 0.0f) } let ts = { Input = input } - for _ in 1 .. stepsPerFrame do - world <- Sim.step ts world + let stepCount = SimulationControl.stepsPerFrameWithLegacy legacyStepsPerFrame simulationControl + if stepCount > 0 then + for _ in 1 .. stepCount do + world <- Sim.step ts world this.CenterCamera() fpsFrames <- fpsFrames + 1 fpsSeconds <- fpsSeconds + gameTime.ElapsedGameTime.TotalSeconds @@ -258,9 +299,14 @@ type LivingVillageGame() as this = if world.Npcs.Length > 0 then NpcView.actionName world.Npcs.[0].Mind.Action else "none" + let profile = M6Presentation.profileAtTick world.Tick + let dayNight = + match profile.Mode with + | Day -> "DAY" + | Night -> "NIGHT" let viewTag = if relationView then " | [RELATIONS]" else "" this.Window.Title <- - $"Living Village M5{titleSuffix} | fps {fps:F1} | tick {world.Tick} | npc {npcAction}{viewTag} | panel {M5Interaction.panelName m5View} | {M5Interaction.titleText m5View}" + $"Living Village M6{titleSuffix} | fps {fps:F1} | tick {world.Tick} | {dayNight} | {M6Presentation.clockLabel simulationControl} | npc {npcAction}{viewTag} | panel {M5Interaction.panelName m5View} | {M5Interaction.titleText m5View}" printfn $"fps={fps:F1} tick={world.Tick} pos=({world.Avatar.Pos.X:F0},{world.Avatar.Pos.Y:F0})" prevKb <- kb @@ -272,7 +318,8 @@ type LivingVillageGame() as this = spriteBatch.Draw(pixel, a, System.Nullable(), color, angle, Vector2.Zero, Vector2(len, thickness), SpriteEffects.None, 0.0f) member private this.DrawRelationView() = - this.GraphicsDevice.Clear(Color(16, 20, 26)) + let profile = M6Presentation.profileAtTick world.Tick + this.GraphicsDevice.Clear(profile.Background) let viewport = this.GraphicsDevice.Viewport let vw = float32 viewport.Width let vh = float32 viewport.Height @@ -310,7 +357,8 @@ type LivingVillageGame() as this = this.DrawWorldView() member private this.DrawWorldView() = - this.GraphicsDevice.Clear(Color.CornflowerBlue) + let profile = M6Presentation.profileAtTick world.Tick + this.GraphicsDevice.Clear(profile.Background) let viewport = this.GraphicsDevice.Viewport let camX = int camera.X let camY = int camera.Y @@ -324,7 +372,7 @@ type LivingVillageGame() as this = let src = Rectangle(int map.[x, y] * Sim.tilePixels, 0, Sim.tilePixels, Sim.tilePixels) let dst = Rectangle(x * Sim.tilePixels - camX, y * Sim.tilePixels - camY, Sim.tilePixels, Sim.tilePixels) - spriteBatch.Draw(atlas, dst, src, Color.White) + spriteBatch.Draw(atlas, dst, src, profile.WorldTint) let avatarDst = Rectangle(int world.Avatar.Pos.X - camX, int world.Avatar.Pos.Y - camY, Sim.tilePixels, Sim.tilePixels) spriteBatch.Draw(pixel, avatarDst, Color.Red) @@ -351,7 +399,16 @@ type LivingVillageGame() as this = let lineHeight = 18 let maxChars = max 1 (innerWidth / (6 * textScale)) let maxLines = max 1 ((panelHeight - 32) / lineHeight) - let lines = M5Interaction.panelLines world m5View |> List.truncate maxLines + let profile = M6Presentation.profileAtTick world.Tick + let dayNight = + match profile.Mode with + | Day -> "DAY" + | Night -> "NIGHT" + let m6Lines = + [ sprintf "TIME %s %s" dayNight (M6Presentation.clockLabel simulationControl) + "P PAUSE F1 F2 F3 SPEED" + "F6 SAVE F7 LOAD" ] + let lines = (m6Lines @ M5Interaction.panelLines world m5View) |> List.truncate maxLines spriteBatch.Begin() spriteBatch.Draw(pixel, panel, Color(15, 19, 25, 220)) spriteBatch.Draw(pixel, Rectangle(panelX, panelY, panelWidth, 4), Color(92, 180, 190)) |
