Simulation bridge
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Platform and documentation
|
# Platform and documentation
|
||||||
|
|
||||||
|
Find out which platform you're running on.
|
||||||
If this is a linux environment, read `AGENTS.linux.md`.
|
If this is a linux environment, read `AGENTS.linux.md`.
|
||||||
If this is a windows environment, read `AGENTS.windows.md`.
|
If this is a windows environment, read `AGENTS.windows.md`.
|
||||||
Follow the guidelines laid out in `CODESTYLE.md`.
|
Follow the guidelines laid out in `CODESTYLE.md`.
|
||||||
|
|||||||
93
TASKS.md
93
TASKS.md
@@ -1,87 +1,4 @@
|
|||||||
# Reactor Maintenance Rewrite Tasks
|
# Reactor Maintenance Tasks
|
||||||
|
|
||||||
## Current State
|
|
||||||
- Approved design iteration targets the simulation model, rule removal, action economy, reactor requirements, and editor layer workflow.
|
|
||||||
- Work is proceeding on branch `design-iteration-structural-editor` in methodical commits.
|
|
||||||
- Completed commits:
|
|
||||||
- `787f1e5` Document approved design iteration.
|
|
||||||
- `3d40617` Restore complete design system documentation.
|
|
||||||
- `e1ac56d` Rework simulation rules.
|
|
||||||
- Design documentation must preserve every existing system-level rule unless a change explicitly supersedes it. Superseded sections must document the replacement behavior with equal detail.
|
|
||||||
- The next implementation iteration is the Win2D editor overhaul.
|
|
||||||
|
|
||||||
## Completed Work
|
|
||||||
- Created the approved implementation plan for:
|
|
||||||
- single multi-service consumers,
|
|
||||||
- count-based reactor requirements,
|
|
||||||
- cell-derived doors,
|
|
||||||
- 0-10 structural integrity,
|
|
||||||
- fixed automatic rule systems,
|
|
||||||
- quick/lengthy action economy,
|
|
||||||
- all-seeing-eye viewing without persistent unlocking,
|
|
||||||
- layer-aware editor visualization and tools.
|
|
||||||
- Repaired the design documentation after the hazard-interaction regression:
|
|
||||||
- restored the complete surface hazard interaction matrix,
|
|
||||||
- documented that leaked coolant plus leaked fuel directly holds unless mediated by heat/electricity,
|
|
||||||
- expanded structural integrity, consumer, reactor, all-seeing-eye, and action-economy details.
|
|
||||||
- Reworked simulation state and systems:
|
|
||||||
- removed data-driven rule predicates/effects/events from runtime state, validation, forecasts, serialization, and tests,
|
|
||||||
- replaced explicit reactor consumer bindings with unbound reactor controls plus required fuel/coolant/electricity consumer counts,
|
|
||||||
- made consumer props carrier-agnostic with per-carrier service state derived from networks beneath the cell,
|
|
||||||
- moved doors from explicit edge state to door props on floor cells with orientation inferred from opposing wall cells,
|
|
||||||
- added underground structural integrity, high-pressure degradation, automatic leak creation, structural forecasts, and repair-to-max behavior,
|
|
||||||
- removed action budgets and made movement quick while mutating interactions resolve one simulation step,
|
|
||||||
- removed persistent all-seeing-eye unlocking from simulation state,
|
|
||||||
- bumped serialized level schema to version 3.
|
|
||||||
- Updated simulation tests for the replacement systems:
|
|
||||||
- consumer derivation and disabled consumer service state,
|
|
||||||
- count-based reactor readiness and reactor-under-network positive-flow requirement,
|
|
||||||
- quick movement versus lengthy door interaction,
|
|
||||||
- inferred door blocking,
|
|
||||||
- structural degradation, automatic leaks, repair integrity reset,
|
|
||||||
- schema version 3 round-tripping and old-schema rejection.
|
|
||||||
- Kept the Win2D project compiling after the simulation model changes with narrow compatibility edits. Full editor workflow/rendering work remains outstanding.
|
|
||||||
- Verified after the simulation rework:
|
|
||||||
- `dotnet test tests\ReactorMaintenance.Simulation.Tests\ReactorMaintenance.Simulation.Tests.csproj` passed with 23 tests,
|
|
||||||
- `dotnet build ReactorMaintenance.slnx` passed with 0 warnings.
|
|
||||||
- Reworked the Win2D editor workflow:
|
|
||||||
- added the Surface/Electricity/Fuel/Coolant layer combobox,
|
|
||||||
- filtered tools by active layer and fixed exclusive tool selection,
|
|
||||||
- rendered underground networks as carrier-colored centerline networks with source dots and layer opacity rules,
|
|
||||||
- removed Rule Events, Reactor Binding, and pending workflow panels from the editor UI,
|
|
||||||
- replaced two-click electricity leak authoring with electric-layer leak access cycling,
|
|
||||||
- made Shift+left drag pan in all tools and Cursor drag move the robot or props.
|
|
||||||
- Added editor-helper tests for electricity leak access cycling and cursor drag movement behavior.
|
|
||||||
- Verified after the editor overhaul:
|
|
||||||
- `dotnet test tests\ReactorMaintenance.Simulation.Tests\ReactorMaintenance.Simulation.Tests.csproj` passed with 26 tests,
|
|
||||||
- `dotnet build ReactorMaintenance.slnx` passed with 0 warnings.
|
|
||||||
|
|
||||||
## Current Work
|
|
||||||
- Editor overhaul implementation is complete; commit is pending.
|
|
||||||
|
|
||||||
## Editor Overhaul Requirements
|
|
||||||
- Add a layer combobox with Surface, Electricity, Fuel, and Coolant.
|
|
||||||
- When Surface is active, draw the surface layer at full opacity and all underground layers at 25% opacity.
|
|
||||||
- When an underground layer is active, draw the surface layer at 50% opacity, other underground layers at 25% opacity, and the active underground layer at full opacity.
|
|
||||||
- Render coolant blue, fuel red, and electricity yellow.
|
|
||||||
- Render networks as thick lines connecting adjacent cell centers; render sources as large centered dots.
|
|
||||||
- Make tools layer-aware:
|
|
||||||
- Cursor is always available.
|
|
||||||
- Heat, Floor, Walls, Props, Consumers, Hazards, and Doors are only available for Surface.
|
|
||||||
- Network painting and Sources are only available on their respective underground layers.
|
|
||||||
- Selecting a tool must deselect all other tools. The current two-way binding can leave multiple tools selected.
|
|
||||||
- Shift+LMB should pan the view in all tools, including Cursor mode.
|
|
||||||
- Cursor LMB drag should move any prop or robot from one cell to another.
|
|
||||||
- Remove Rule Events UI.
|
|
||||||
- Remove Reactor Binding UI.
|
|
||||||
- Remove editor workflow and pending actions.
|
|
||||||
- Door cells are redesigned as single prop cells.
|
|
||||||
- Electricity leak neighbour should be toggled by using the electric leak tool on an existing electric leak cell.
|
|
||||||
|
|
||||||
## Future Work
|
|
||||||
- Add authored sample levels once the new schema stabilizes.
|
|
||||||
- Tune structural integrity balancing after playtesting.
|
|
||||||
- Extend UI affordances for inspecting per-carrier consumer service state.
|
|
||||||
|
|
||||||
## Godot Frontend Integration
|
## Godot Frontend Integration
|
||||||
|
|
||||||
@@ -91,7 +8,7 @@ The Godot frontend (src/ReactorMaintenance.Godot) has a complete UX scaffold (sp
|
|||||||
|
|
||||||
**Goal:** Connect LevelScreen to SimulationEngine so gameplay state flows between simulation and UI.
|
**Goal:** Connect LevelScreen to SimulationEngine so gameplay state flows between simulation and UI.
|
||||||
|
|
||||||
- [ ] **Task 1.1: Create GameSession class**
|
- [x] **Task 1.1: Create GameSession class**
|
||||||
- Location: src/ReactorMaintenance.Godot/Data/GameSession.cs
|
- Location: src/ReactorMaintenance.Godot/Data/GameSession.cs
|
||||||
- Wraps SimulationEngine and holds the current LevelState
|
- Wraps SimulationEngine and holds the current LevelState
|
||||||
- Loads LevelState from JSON via LevelSerializer.Deserialize() using Godot FileAccess
|
- Loads LevelState from JSON via LevelSerializer.Deserialize() using Godot FileAccess
|
||||||
@@ -101,7 +18,7 @@ The Godot frontend (src/ReactorMaintenance.Godot) has a complete UX scaffold (sp
|
|||||||
- Validates actions before committing (rejects invalid moves)
|
- Validates actions before committing (rejects invalid moves)
|
||||||
- Handles level start snapshot for retry
|
- Handles level start snapshot for retry
|
||||||
|
|
||||||
- [ ] **Task 1.2: Create LevelStateLoader helper**
|
- [x] **Task 1.2: Create LevelStateLoader helper**
|
||||||
- Location: src/ReactorMaintenance.Godot/Data/LevelStateLoader.cs
|
- Location: src/ReactorMaintenance.Godot/Data/LevelStateLoader.cs
|
||||||
- Static helper that takes a res://Data/Levels/... path
|
- Static helper that takes a res://Data/Levels/... path
|
||||||
- Uses Godot FileAccess to read JSON string
|
- Uses Godot FileAccess to read JSON string
|
||||||
@@ -109,7 +26,7 @@ The Godot frontend (src/ReactorMaintenance.Godot) has a complete UX scaffold (sp
|
|||||||
- Throws descriptive exceptions for missing files or schema errors
|
- Throws descriptive exceptions for missing files or schema errors
|
||||||
- Supports both res:// and user:// paths
|
- Supports both res:// and user:// paths
|
||||||
|
|
||||||
- [ ] **Task 1.3: Wire LevelScreen to GameSession**
|
- [x] **Task 1.3: Wire LevelScreen to GameSession**
|
||||||
- Update LevelScreen.Configure() to accept GameSession instead of raw CampaignLevel
|
- Update LevelScreen.Configure() to accept GameSession instead of raw CampaignLevel
|
||||||
- Replace placeholder grid with real viewport
|
- Replace placeholder grid with real viewport
|
||||||
- Wire CellInspector.SetCellInfo() to display live selected cell data
|
- Wire CellInspector.SetCellInfo() to display live selected cell data
|
||||||
@@ -118,7 +35,7 @@ The Godot frontend (src/ReactorMaintenance.Godot) has a complete UX scaffold (sp
|
|||||||
- Update LevelHeader with live global state badge (Stable/Caution/Critical/Ready)
|
- Update LevelHeader with live global state badge (Stable/Caution/Critical/Ready)
|
||||||
- Update InventoryStrip with live remedy/heat shield counts
|
- Update InventoryStrip with live remedy/heat shield counts
|
||||||
|
|
||||||
- [ ] **Task 1.4: Create res://Data/Levels/ directory and level files**
|
- [x] **Task 1.4: Create res://Data/Levels/ directory and level files**
|
||||||
- Create placeholder level files for the 3 campaign levels
|
- Create placeholder level files for the 3 campaign levels
|
||||||
- Use Win2D editor to export v3 JSON schema for each level
|
- Use Win2D editor to export v3 JSON schema for each level
|
||||||
- Files: coolant_restart.json, fuel_bleed.json, black_start.json
|
- Files: coolant_restart.json, fuel_bleed.json, black_start.json
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Godot.Controls;
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
@@ -18,9 +20,31 @@ public partial class CellInspector : PanelContainer
|
|||||||
body.AddChild(header);
|
body.AddChild(header);
|
||||||
|
|
||||||
body.AddChild(m_Text);
|
body.AddChild(m_Text);
|
||||||
m_Text.Text = "Selected Cell: 0,0\nTerrain: service floor\nProp: none\nHazards: none\nUnderground: hidden";
|
|
||||||
m_Text.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
m_Text.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCellInfo(GridPosition? position, ECellTerrain terrain, EPropType prop, EConsumerServiceState serviceState, float fuelHazard, float coolantHazard, float electricityHazard, float heatHazard)
|
||||||
|
{
|
||||||
|
var pos = position ?? new(-1, -1);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"Selected Cell: {pos.X},{pos.Y}");
|
||||||
|
sb.AppendLine($"Terrain: {terrain}");
|
||||||
|
sb.AppendLine($"Prop: {prop}");
|
||||||
|
if (prop != EPropType.None)
|
||||||
|
sb.AppendLine($"Service: {serviceState}");
|
||||||
|
sb.AppendLine($"Hazards: {FormatHazards(fuelHazard, coolantHazard, electricityHazard, heatHazard)}");
|
||||||
|
m_Text.Text = sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatHazards(float fuel, float coolant, float electricity, float heat)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
if (fuel > 0) parts.Add($"fuel {fuel:F1}");
|
||||||
|
if (coolant > 0) parts.Add($"coolant {coolant:F1}");
|
||||||
|
if (electricity > 0) parts.Add($"electricity {electricity:F1}");
|
||||||
|
if (heat > 0) parts.Add($"heat {heat:F1}");
|
||||||
|
return parts.Count > 0 ? string.Join(", ", parts) : "none";
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Label m_Text = new();
|
private readonly Label m_Text = new();
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Godot.Controls;
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
@@ -7,17 +8,44 @@ public partial class ForecastList : PanelContainer
|
|||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
AddChild(m_Items);
|
AddChild(m_Items);
|
||||||
SetForecasts(["Turn +1: Pressure stable", "Turn +2: No forecast warnings"]);
|
SetForecasts(Array.Empty<Forecast>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetForecasts(IReadOnlyList<string> forecasts)
|
public void SetForecasts(IReadOnlyList<Forecast> forecasts)
|
||||||
{
|
{
|
||||||
foreach (var child in m_Items.GetChildren())
|
foreach (var child in m_Items.GetChildren())
|
||||||
child.QueueFree();
|
child.QueueFree();
|
||||||
|
|
||||||
m_Items.AddChild(new Label { Text = "Forecasts" });
|
m_Items.AddChild(new Label { Text = "Forecasts" });
|
||||||
foreach (var forecast in forecasts)
|
foreach (var forecast in forecasts)
|
||||||
m_Items.AddChild(new Label { Text = forecast, AutowrapMode = TextServer.AutowrapMode.WordSmart });
|
{
|
||||||
|
var label = new Label {
|
||||||
|
Text = FormatForecast(forecast),
|
||||||
|
AutowrapMode = TextServer.AutowrapMode.WordSmart
|
||||||
|
};
|
||||||
|
ApplyForecastColor(label, forecast.Kind);
|
||||||
|
m_Items.AddChild(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatForecast(Forecast forecast)
|
||||||
|
{
|
||||||
|
var pos = forecast.Position;
|
||||||
|
var posStr = pos != null ? $" [{pos.X},{pos.Y}]" : "";
|
||||||
|
return $"Turn +{forecast.Turns}: {forecast.Message}{posStr}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyForecastColor(Label label, EForecastKind kind)
|
||||||
|
{
|
||||||
|
var color = kind switch {
|
||||||
|
EForecastKind.TerminalLoss => new(1.0f, 0.36f, 0.32f),
|
||||||
|
EForecastKind.ConsumerStarved => new(1.0f, 0.6f, 0.2f),
|
||||||
|
EForecastKind.HazardGrowth => new(1.0f, 0.8f, 0.2f),
|
||||||
|
EForecastKind.StructuralIntegrity => new(0.6f, 0.8f, 1.0f),
|
||||||
|
EForecastKind.ReactorReady => new(0.45f, 1.0f, 0.58f),
|
||||||
|
_ => Colors.White
|
||||||
|
};
|
||||||
|
label.AddThemeColorOverride("font_color", color);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly VBoxContainer m_Items = new();
|
private readonly VBoxContainer m_Items = new();
|
||||||
|
|||||||
@@ -4,20 +4,6 @@ namespace ReactorMaintenance.Godot.Controls;
|
|||||||
|
|
||||||
internal static class FrontendAssets
|
internal static class FrontendAssets
|
||||||
{
|
{
|
||||||
public const string CoolantIcon = "res://Assets/Ui/coolant_icon.png";
|
|
||||||
public const string ElectricIcon = "res://Assets/Ui/electric_icon.png";
|
|
||||||
public const string FuelIcon = "res://Assets/Ui/fuel_icon.png";
|
|
||||||
public const string HeatShieldIcon = "res://Assets/Ui/heat_shield_icon.png";
|
|
||||||
public const string MaintenanceRobot = "res://Assets/Characters/maintenance_robot.png";
|
|
||||||
public const string PrimaryButtonAccent = "res://Assets/Ui/primary_button_accent.png";
|
|
||||||
public const string ScannerEyeIcon = "res://Assets/Ui/scanner_eye_icon.png";
|
|
||||||
public const string StateBadgeFrame = "res://Assets/Ui/state_badge_frame.png";
|
|
||||||
|
|
||||||
public static Texture2D? LoadTexture(string path)
|
|
||||||
{
|
|
||||||
return ResourceLoader.Exists(path) ? ResourceLoader.Load<Texture2D>(path) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TextureRect CreateIcon(string path, Vector2 size)
|
public static TextureRect CreateIcon(string path, Vector2 size)
|
||||||
{
|
{
|
||||||
return new() {
|
return new() {
|
||||||
@@ -28,4 +14,18 @@ internal static class FrontendAssets
|
|||||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Texture2D? LoadTexture(string path)
|
||||||
|
{
|
||||||
|
return ResourceLoader.Exists(path) ? ResourceLoader.Load<Texture2D>(path) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public const string CoolantIcon = "res://Assets/Ui/coolant_icon.png";
|
||||||
|
public const string ElectricIcon = "res://Assets/Ui/electric_icon.png";
|
||||||
|
public const string FuelIcon = "res://Assets/Ui/fuel_icon.png";
|
||||||
|
public const string HeatShieldIcon = "res://Assets/Ui/heat_shield_icon.png";
|
||||||
|
public const string MaintenanceRobot = "res://Assets/Characters/maintenance_robot.png";
|
||||||
|
public const string PrimaryButtonAccent = "res://Assets/Ui/primary_button_accent.png";
|
||||||
|
public const string ScannerEyeIcon = "res://Assets/Ui/scanner_eye_icon.png";
|
||||||
|
public const string StateBadgeFrame = "res://Assets/Ui/state_badge_frame.png";
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Godot.Controls;
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
@@ -6,23 +6,43 @@ public partial class InventoryStrip : HBoxContainer
|
|||||||
{
|
{
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
AddChild(CreateItem("Fuel Neutralizer", 2, FrontendAssets.FuelIcon));
|
AddChild(CreateItem("Fuel Neutralizer", 2, FrontendAssets.FuelIcon, out var fuelLabel));
|
||||||
AddChild(CreateItem("Coolant Neutralizer", 2, FrontendAssets.CoolantIcon));
|
AddChild(CreateItem("Coolant Neutralizer", 2, FrontendAssets.CoolantIcon, out var coolantLabel));
|
||||||
AddChild(CreateItem("Electric Neutralizer", 1, FrontendAssets.ElectricIcon));
|
AddChild(CreateItem("Electric Neutralizer", 1, FrontendAssets.ElectricIcon, out var electricLabel));
|
||||||
AddChild(CreateItem("Heat Shield", 1, FrontendAssets.HeatShieldIcon));
|
AddChild(CreateItem("Heat Shield", 1, FrontendAssets.HeatShieldIcon, out var heatLabel));
|
||||||
|
m_FuelLabel = fuelLabel;
|
||||||
|
m_CoolantLabel = coolantLabel;
|
||||||
|
m_ElectricLabel = electricLabel;
|
||||||
|
m_HeatLabel = heatLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HBoxContainer CreateItem(string name, int count, string iconPath)
|
public void SetInventory(int fuelNeutralizers, int coolantNeutralizers, int electricNeutralizers, int heatShields)
|
||||||
|
{
|
||||||
|
m_FuelLabel.Text = $"{fuelNeutralizers}";
|
||||||
|
m_CoolantLabel.Text = $"{coolantNeutralizers}";
|
||||||
|
m_ElectricLabel.Text = $"{electricNeutralizers}";
|
||||||
|
m_HeatLabel.Text = $"{heatShields}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HBoxContainer CreateItem(string name, int count, string iconPath, out Label countLabel)
|
||||||
{
|
{
|
||||||
var item = new HBoxContainer {
|
var item = new HBoxContainer {
|
||||||
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||||
};
|
};
|
||||||
item.AddChild(FrontendAssets.CreateIcon(iconPath, new(30, 30)));
|
item.AddChild(FrontendAssets.CreateIcon(iconPath, new(30, 30)));
|
||||||
item.AddChild(new Label {
|
countLabel = new() {
|
||||||
Text = $"{name}: {count}",
|
Text = $"{count}",
|
||||||
|
Name = name,
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||||
});
|
};
|
||||||
|
item.AddChild(countLabel);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Label m_CoolantLabel = null!;
|
||||||
|
private Label m_ElectricLabel = null!;
|
||||||
|
|
||||||
|
private Label m_FuelLabel = null!;
|
||||||
|
private Label m_HeatLabel = null!;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using ReactorMaintenance.Godot.Data;
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Godot.Controls;
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
@@ -18,12 +18,25 @@ public partial class LevelHeader : HBoxContainer
|
|||||||
m_Summary.HorizontalAlignment = HorizontalAlignment.Right;
|
m_Summary.HorizontalAlignment = HorizontalAlignment.Right;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLevel(CampaignLevel level, int levelNumber, int levelCount, string state)
|
public void SetLevel(string title, int levelNumber, int levelCount, ELevelState state)
|
||||||
{
|
{
|
||||||
m_Title.Text = level.Name;
|
m_Title.Text = title;
|
||||||
m_Progress.Text = $"Level {levelNumber} / {levelCount}";
|
m_Progress.Text = $"Level {levelNumber} / {levelCount}";
|
||||||
m_Badge.SetState(state);
|
m_Badge.SetState(StateToString(state));
|
||||||
m_Summary.Text = "Heat nominal | Reactor offline";
|
m_Summary.Text = "Heat: nominal | Reactor: offline";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string StateToString(ELevelState state)
|
||||||
|
{
|
||||||
|
return state switch {
|
||||||
|
ELevelState.Stable => "Stable",
|
||||||
|
ELevelState.Caution => "Caution",
|
||||||
|
ELevelState.Critical => "Critical",
|
||||||
|
ELevelState.Ready => "Ready",
|
||||||
|
ELevelState.Lost => "Lost",
|
||||||
|
ELevelState.Won => "Won",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly StateBadge m_Badge = new();
|
private readonly StateBadge m_Badge = new();
|
||||||
|
|||||||
121
src/ReactorMaintenance.Godot/Data/GameSession.cs
Normal file
121
src/ReactorMaintenance.Godot/Data/GameSession.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
public sealed class GameSession
|
||||||
|
{
|
||||||
|
public event StateChangedHandler? LevelStateChanged;
|
||||||
|
public event StateChangedHandler? RobotMoved;
|
||||||
|
public event StateChangedHandler? TurnAdvanced;
|
||||||
|
public event StateChangedHandler? LevelWon;
|
||||||
|
public event StateChangedHandler? LevelLost;
|
||||||
|
|
||||||
|
public void Initialize(LevelState levelState)
|
||||||
|
{
|
||||||
|
LevelState = levelState;
|
||||||
|
m_StartSnapshot = LevelSerializer.Deserialize(LevelSerializer.Serialize(levelState));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MoveRobot(GridPosition destination)
|
||||||
|
{
|
||||||
|
if (LevelState.Terrain[(destination.Y * LevelState.Width) + destination.X] == ECellTerrain.Wall)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (destination.X < 0 || destination.X >= LevelState.Width ||
|
||||||
|
destination.Y < 0 || destination.Y >= LevelState.Height)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LevelState = m_Engine.MoveRobot(LevelState, destination);
|
||||||
|
RobotMoved?.Invoke(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelState EndTurn()
|
||||||
|
{
|
||||||
|
LevelState = m_Engine.EndTurn(LevelState);
|
||||||
|
OnTurnAdvanced();
|
||||||
|
return LevelState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InteractProp()
|
||||||
|
{
|
||||||
|
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LevelState = m_Engine.InteractProp(LevelState);
|
||||||
|
OnTurnAdvanced();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool InteractLeak(ECarrierType carrier, bool useRemedy)
|
||||||
|
{
|
||||||
|
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LevelState = m_Engine.InteractLeak(LevelState, carrier, useRemedy);
|
||||||
|
OnTurnAdvanced();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApplyHeatShield()
|
||||||
|
{
|
||||||
|
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LevelState = m_Engine.ApplyHeatShield(LevelState);
|
||||||
|
OnTurnAdvanced();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ActivateReactor()
|
||||||
|
{
|
||||||
|
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LevelState = m_Engine.ActivateReactor(LevelState);
|
||||||
|
CheckOutcome();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<Forecast> GetForecasts()
|
||||||
|
{
|
||||||
|
return m_Engine.Forecast(LevelState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Retry()
|
||||||
|
{
|
||||||
|
LevelState = m_StartSnapshot;
|
||||||
|
LevelStateChanged?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTurnAdvanced()
|
||||||
|
{
|
||||||
|
CheckOutcome();
|
||||||
|
TurnAdvanced?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckOutcome()
|
||||||
|
{
|
||||||
|
if (LevelState.Global.LevelState == ELevelState.Won)
|
||||||
|
LevelWon?.Invoke(this);
|
||||||
|
else if (LevelState.Global.LevelState == ELevelState.Lost)
|
||||||
|
LevelLost?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelState LevelState { get; private set; } = null!;
|
||||||
|
public GridPosition RobotPosition => LevelState.Robot.Position;
|
||||||
|
public GlobalState GlobalState => LevelState.Global;
|
||||||
|
public IReadOnlyList<Forecast> Forecasts => LevelState.Forecasts;
|
||||||
|
public IReadOnlyList<LeakState> Leaks => LevelState.Leaks;
|
||||||
|
public IReadOnlyList<ReactorState> Reactors => LevelState.Reactors;
|
||||||
|
public IReadOnlyList<PropState> Props => LevelState.Props;
|
||||||
|
public ECellTerrain[] Terrain => LevelState.Terrain;
|
||||||
|
public SurfaceState[] Surface => LevelState.Surface;
|
||||||
|
public UndergroundCell[] UndergroundFuel => LevelState.Fuel;
|
||||||
|
public UndergroundCell[] UndergroundCoolant => LevelState.Coolant;
|
||||||
|
public UndergroundCell[] UndergroundElectricity => LevelState.Electricity;
|
||||||
|
public delegate void StateChangedHandler(GameSession sender);
|
||||||
|
|
||||||
|
private readonly SimulationEngine m_Engine = new();
|
||||||
|
private LevelState m_StartSnapshot = null!;
|
||||||
|
}
|
||||||
34
src/ReactorMaintenance.Godot/Data/LevelStateLoader.cs
Normal file
34
src/ReactorMaintenance.Godot/Data/LevelStateLoader.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
using FileAccess = Godot.FileAccess;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
public static class LevelStateLoader
|
||||||
|
{
|
||||||
|
public static LevelState Load(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
throw new ArgumentException("Level path must not be null or empty.", nameof(path));
|
||||||
|
|
||||||
|
if (!FileAccess.FileExists(path))
|
||||||
|
throw new FileNotFoundException($"Level file not found: {path}");
|
||||||
|
|
||||||
|
var json = FileAccess.GetFileAsString(path);
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
throw new InvalidOperationException($"Level file is empty: {path}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return LevelSerializer.Deserialize(json);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Failed to deserialize level from {path}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3974
src/ReactorMaintenance.Godot/Data/Levels/black_start.json
Normal file
3974
src/ReactorMaintenance.Godot/Data/Levels/black_start.json
Normal file
File diff suppressed because it is too large
Load Diff
2798
src/ReactorMaintenance.Godot/Data/Levels/coolant_restart.json
Normal file
2798
src/ReactorMaintenance.Godot/Data/Levels/coolant_restart.json
Normal file
File diff suppressed because it is too large
Load Diff
2811
src/ReactorMaintenance.Godot/Data/Levels/fuel_bleed.json
Normal file
2811
src/ReactorMaintenance.Godot/Data/Levels/fuel_bleed.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,23 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using ReactorMaintenance.Godot.Controls;
|
using ReactorMaintenance.Godot.Controls;
|
||||||
using ReactorMaintenance.Godot.Data;
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
using ReactorMaintenance.Simulation;
|
||||||
|
|
||||||
namespace ReactorMaintenance.Godot.Screens;
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
public partial class LevelScreen : ScreenBase
|
public partial class LevelScreen : ScreenBase
|
||||||
{
|
{
|
||||||
public void Configure(AppController app, CampaignLevel level, int levelNumber, int levelCount)
|
public void Configure(AppController app, GameSession session, CampaignLevel level, int levelNumber, int levelCount)
|
||||||
{
|
{
|
||||||
m_App = app;
|
m_App = app;
|
||||||
|
m_Session = session;
|
||||||
m_Level = level;
|
m_Level = level;
|
||||||
|
m_OutcomeVisible = false;
|
||||||
|
|
||||||
var body = CreatePage(string.Empty);
|
var body = CreatePage(string.Empty);
|
||||||
var header = new LevelHeader();
|
var header = new LevelHeader();
|
||||||
body.AddChild(header);
|
body.AddChild(header);
|
||||||
header.SetLevel(level, levelNumber, levelCount, "Stable");
|
header.SetLevel(level.Name, levelNumber, levelCount, session.LevelState.Global.LevelState);
|
||||||
|
|
||||||
var flavor = CreateBodyText(level.FlavorText);
|
var flavor = CreateBodyText(level.FlavorText);
|
||||||
flavor.HorizontalAlignment = HorizontalAlignment.Left;
|
flavor.HorizontalAlignment = HorizontalAlignment.Left;
|
||||||
@@ -23,109 +26,163 @@ public partial class LevelScreen : ScreenBase
|
|||||||
var split = new HSplitContainer { SizeFlagsVertical = SizeFlags.ExpandFill };
|
var split = new HSplitContainer { SizeFlagsVertical = SizeFlags.ExpandFill };
|
||||||
body.AddChild(split);
|
body.AddChild(split);
|
||||||
|
|
||||||
split.AddChild(CreateGridPlaceholder());
|
m_GridPanel = CreateGridContainer();
|
||||||
split.AddChild(CreateSidePanel(level));
|
split.AddChild(m_GridPanel);
|
||||||
|
|
||||||
body.AddChild(new InventoryStrip());
|
m_Inspector = new();
|
||||||
|
m_ForecastList = new();
|
||||||
|
var sidePanel = new VBoxContainer { CustomMinimumSize = new(300, 0) };
|
||||||
|
sidePanel.AddChild(m_Inspector);
|
||||||
|
sidePanel.AddChild(m_ForecastList);
|
||||||
|
split.AddChild(sidePanel);
|
||||||
|
|
||||||
|
m_InventoryStrip = new();
|
||||||
|
body.AddChild(m_InventoryStrip);
|
||||||
body.AddChild(CreateActionBar());
|
body.AddChild(CreateActionBar());
|
||||||
|
|
||||||
m_OverlayLayer = new();
|
m_OverlayLayer = new();
|
||||||
AddChild(m_OverlayLayer);
|
AddChild(m_OverlayLayer);
|
||||||
|
|
||||||
|
UpdateUI();
|
||||||
|
m_Session.LevelStateChanged += OnLevelStateChanged;
|
||||||
|
m_Session.TurnAdvanced += OnTurnAdvanced;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PanelContainer CreateGridPlaceholder()
|
private PanelContainer CreateGridContainer()
|
||||||
{
|
{
|
||||||
var panel = new PanelContainer { CustomMinimumSize = new(560, 360), SizeFlagsHorizontal = SizeFlags.ExpandFill, SizeFlagsVertical = SizeFlags.ExpandFill };
|
return new() {
|
||||||
var viewport = new Control {
|
CustomMinimumSize = new(560, 360),
|
||||||
SizeFlagsHorizontal = SizeFlags.ExpandFill,
|
SizeFlagsHorizontal = SizeFlags.ExpandFill,
|
||||||
SizeFlagsVertical = SizeFlags.ExpandFill
|
SizeFlagsVertical = SizeFlags.ExpandFill
|
||||||
};
|
};
|
||||||
panel.AddChild(viewport);
|
|
||||||
|
|
||||||
var label = new Label {
|
|
||||||
Text = "Level Grid Placeholder\nRobot marker, terrain, props, hazards, and underground overlays will render here.",
|
|
||||||
AnchorRight = 1,
|
|
||||||
AnchorBottom = 1,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
AutowrapMode = TextServer.AutowrapMode.WordSmart
|
|
||||||
};
|
|
||||||
viewport.AddChild(label);
|
|
||||||
|
|
||||||
var robot = new TextureRect {
|
|
||||||
Texture = FrontendAssets.LoadTexture(FrontendAssets.MaintenanceRobot),
|
|
||||||
AnchorLeft = 0.5f,
|
|
||||||
AnchorTop = 0.5f,
|
|
||||||
AnchorRight = 0.5f,
|
|
||||||
AnchorBottom = 0.5f,
|
|
||||||
OffsetLeft = -48,
|
|
||||||
OffsetTop = -32,
|
|
||||||
OffsetRight = 48,
|
|
||||||
OffsetBottom = 64,
|
|
||||||
ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional,
|
|
||||||
StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered,
|
|
||||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
|
||||||
};
|
|
||||||
viewport.AddChild(robot);
|
|
||||||
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static VBoxContainer CreateSidePanel(CampaignLevel level)
|
|
||||||
{
|
|
||||||
var sidePanel = new VBoxContainer { CustomMinimumSize = new(300, 0) };
|
|
||||||
sidePanel.AddChild(new CellInspector());
|
|
||||||
sidePanel.AddChild(new ForecastList());
|
|
||||||
sidePanel.AddChild(new Label { Text = $"Level JSON: {level.LevelPath}", AutowrapMode = TextServer.AutowrapMode.WordSmart });
|
|
||||||
return sidePanel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private HBoxContainer CreateActionBar()
|
private HBoxContainer CreateActionBar()
|
||||||
{
|
{
|
||||||
var actions = new HBoxContainer();
|
var actions = new HBoxContainer();
|
||||||
actions.AddChild(CreateButton("Move", () => { }, "Quick action placeholder"));
|
actions.AddChild(CreateButton("Move", OnMoveAction, "Move robot to adjacent floor cell"));
|
||||||
actions.AddChild(CreateButton("Interact", () => { }, "Lengthy action placeholder"));
|
actions.AddChild(CreateButton("Interact", OnInteractAction, "Interact with prop at robot position"));
|
||||||
actions.AddChild(CreateButton("Repair", () => { }, "Lengthy action placeholder"));
|
actions.AddChild(CreateButton("Repair", OnRepairAction, "Repair leak at robot position"));
|
||||||
actions.AddChild(CreateButton("Trigger Win", ShowWinOverlay));
|
actions.AddChild(CreateButton("End Turn", OnEndTurnAction));
|
||||||
actions.AddChild(CreateButton("Trigger Lose", ShowLoseOverlay));
|
|
||||||
actions.AddChild(CreateButton("Main Menu", () => m_App?.ShowMainMenu()));
|
actions.AddChild(CreateButton("Main Menu", () => m_App?.ShowMainMenu()));
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowLoseOverlay()
|
private void OnMoveAction()
|
||||||
|
{
|
||||||
|
if (m_Session is null) return;
|
||||||
|
|
||||||
|
var current = m_Session.RobotPosition;
|
||||||
|
var next = current with { X = current.X + 1 };
|
||||||
|
if (!m_Session.MoveRobot(next))
|
||||||
|
{
|
||||||
|
next = current with { Y = current.Y + 1 };
|
||||||
|
m_Session.MoveRobot(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInteractAction()
|
||||||
|
{
|
||||||
|
m_Session?.InteractProp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRepairAction()
|
||||||
|
{
|
||||||
|
if (m_Session is null) return;
|
||||||
|
|
||||||
|
foreach (var leak in m_Session.Leaks)
|
||||||
|
{
|
||||||
|
if (leak.AccessPosition == m_Session.RobotPosition && !leak.Repaired)
|
||||||
|
{
|
||||||
|
m_Session.InteractLeak(leak.Carrier, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEndTurnAction()
|
||||||
|
{
|
||||||
|
m_Session?.EndTurn();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUI()
|
||||||
|
{
|
||||||
|
if (m_Session is null || m_Inspector is null || m_ForecastList is null || m_InventoryStrip is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ls = m_Session.LevelState;
|
||||||
|
var robotPos = m_Session.RobotPosition;
|
||||||
|
var index = (robotPos.Y * ls.Width) + robotPos.X;
|
||||||
|
var surface = ls.Surface[index];
|
||||||
|
var prop = index < ls.Props.Length ? ls.Props[index] : new();
|
||||||
|
|
||||||
|
m_Inspector.SetCellInfo(
|
||||||
|
robotPos,
|
||||||
|
ls.Terrain[index],
|
||||||
|
prop.Type,
|
||||||
|
prop.ServiceState,
|
||||||
|
surface.Fuel,
|
||||||
|
surface.Coolant,
|
||||||
|
surface.Electricity,
|
||||||
|
surface.Heat);
|
||||||
|
|
||||||
|
m_ForecastList.SetForecasts(ls.Forecasts);
|
||||||
|
|
||||||
|
m_InventoryStrip.SetInventory(
|
||||||
|
m_Session.LevelState.Robot.FuelNeutralizers,
|
||||||
|
m_Session.LevelState.Robot.CoolantNeutralizers,
|
||||||
|
m_Session.LevelState.Robot.ElectricityNeutralizers,
|
||||||
|
m_Session.LevelState.Robot.HeatShields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLevelStateChanged(GameSession sender)
|
||||||
|
{
|
||||||
|
UpdateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTurnAdvanced(GameSession sender)
|
||||||
|
{
|
||||||
|
UpdateUI();
|
||||||
|
if (sender.LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||||
|
{
|
||||||
|
ShowOutcomeOverlay(sender.LevelState.Global.LevelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowOutcomeOverlay(ELevelState state)
|
||||||
{
|
{
|
||||||
if (m_OutcomeVisible || m_App is null || m_Level is null || m_OverlayLayer is null)
|
if (m_OutcomeVisible || m_App is null || m_Level is null || m_OverlayLayer is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_OutcomeVisible = true;
|
m_OutcomeVisible = true;
|
||||||
var overlay = new LoseOverlay();
|
Control overlay;
|
||||||
overlay.Configure(m_Level, m_App.RetryCurrentLevel, m_App.ShowGameOver, m_App.ShowMainMenu);
|
|
||||||
AddCenteredOverlay(overlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowWinOverlay()
|
if (state == ELevelState.Won)
|
||||||
{
|
{
|
||||||
if (m_OutcomeVisible || m_App is null || m_Level is null || m_OverlayLayer is null)
|
overlay = new WinOverlay();
|
||||||
return;
|
((WinOverlay)overlay).Configure(m_Level, m_App.CompleteCurrentLevel, m_App.ShowMainMenu);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
overlay = new LoseOverlay();
|
||||||
|
((LoseOverlay)overlay).Configure(m_Level, m_App.RetryCurrentLevel, m_App.ShowGameOver, m_App.ShowMainMenu);
|
||||||
|
}
|
||||||
|
|
||||||
m_OutcomeVisible = true;
|
|
||||||
var overlay = new WinOverlay();
|
|
||||||
overlay.Configure(m_Level, m_App.CompleteCurrentLevel, m_App.ShowMainMenu);
|
|
||||||
AddCenteredOverlay(overlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddCenteredOverlay(Control overlay)
|
|
||||||
{
|
|
||||||
var center = new CenterContainer {
|
var center = new CenterContainer {
|
||||||
AnchorRight = 1,
|
AnchorRight = 1,
|
||||||
AnchorBottom = 1
|
AnchorBottom = 1
|
||||||
};
|
};
|
||||||
center.AddChild(overlay);
|
center.AddChild(overlay);
|
||||||
m_OverlayLayer?.AddChild(center);
|
m_OverlayLayer.AddChild(center);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppController? m_App;
|
private AppController? m_App;
|
||||||
|
private ForecastList? m_ForecastList;
|
||||||
|
private PanelContainer? m_GridPanel;
|
||||||
|
private CellInspector? m_Inspector;
|
||||||
|
private InventoryStrip? m_InventoryStrip;
|
||||||
private CampaignLevel? m_Level;
|
private CampaignLevel? m_Level;
|
||||||
private bool m_OutcomeVisible;
|
private bool m_OutcomeVisible;
|
||||||
private CanvasLayer? m_OverlayLayer;
|
private CanvasLayer? m_OverlayLayer;
|
||||||
|
private GameSession? m_Session;
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,11 @@ public partial class AppController : Control
|
|||||||
public void LoadCurrentLevel()
|
public void LoadCurrentLevel()
|
||||||
{
|
{
|
||||||
m_Session.MarkCurrentLevelLoaded();
|
m_Session.MarkCurrentLevelLoaded();
|
||||||
ShowScreen<LevelScreen>("res://Scenes/LevelScreen.tscn", screen => screen.Configure(this, m_Session.CurrentLevel, m_Session.LevelNumber, m_Session.LevelCount));
|
var level = m_Session.CurrentLevel;
|
||||||
|
var gameSession = new GameSession();
|
||||||
|
var levelState = LevelStateLoader.Load(level.LevelPath);
|
||||||
|
gameSession.Initialize(levelState);
|
||||||
|
ShowScreen<LevelScreen>("res://Scenes/LevelScreen.tscn", screen => screen.Configure(this, gameSession, level, m_Session.LevelNumber, m_Session.LevelCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RetryCurrentLevel()
|
public void RetryCurrentLevel()
|
||||||
|
|||||||
Reference in New Issue
Block a user