Implement Godot UX scene scaffold
This commit is contained in:
10
README.md
10
README.md
@@ -1,11 +1,11 @@
|
|||||||
# Reactor Maintenance
|
# Reactor Maintenance
|
||||||
|
|
||||||
C# simulation with WinUI 3 + Win2D editor and an empty Godot frontend shell for the deterministic grid simulation described in `docs/design.md`.
|
C# simulation with WinUI 3 + Win2D editor and a Godot frontend shell for the deterministic grid simulation described in `docs/design.md`.
|
||||||
|
|
||||||
## Projects
|
## Projects
|
||||||
|
|
||||||
- `src/ReactorMaintenance.Simulation`: UI-independent level model, editor operations, validation, forecasts, simulation turns, versioned JSON serialization, and deterministic balancing defaults.
|
- `src/ReactorMaintenance.Simulation`: UI-independent level model, editor operations, validation, forecasts, simulation turns, versioned JSON serialization, and deterministic balancing defaults.
|
||||||
- `src/ReactorMaintenance.Godot`: empty Godot 4.5 .NET frontend project shell referencing the simulation core.
|
- `src/ReactorMaintenance.Godot`: Godot 4.5 .NET frontend project with scene routing, UX blueprint screens, reusable UI controls, and a mock campaign manifest referencing future level JSON files.
|
||||||
- `src/ReactorMaintenance.Win2D`: Win2D editor app for authoring terrain, underground fuel/coolant/electricity networks, props, explicit leak access faces, door edges, reactor consumer bindings, rule events, surface hazards, robot start, loading/saving levels, ending turns, interacting with props, and activating a ready reactor.
|
- `src/ReactorMaintenance.Win2D`: Win2D editor app for authoring terrain, underground fuel/coolant/electricity networks, props, explicit leak access faces, door edges, reactor consumer bindings, rule events, surface hazards, robot start, loading/saving levels, ending turns, interacting with props, and activating a ready reactor.
|
||||||
- `tests/ReactorMaintenance.Simulation.Tests`: unit tests for deterministic simulation behavior, validation, serialization, and editor operations.
|
- `tests/ReactorMaintenance.Simulation.Tests`: unit tests for deterministic simulation behavior, validation, serialization, and editor operations.
|
||||||
|
|
||||||
@@ -29,3 +29,9 @@ dotnet run --project src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.cspro
|
|||||||
|
|
||||||
The WinUI/XAML compiler is Windows-specific. On Linux, the simulation tests run normally, but the Win2D app build must be verified in a Windows-capable environment.
|
The WinUI/XAML compiler is Windows-specific. On Linux, the simulation tests run normally, but the Win2D app build must be verified in a Windows-capable environment.
|
||||||
|
|
||||||
|
## Godot Frontend
|
||||||
|
|
||||||
|
The current Godot frontend is a navigable UX scaffold. It starts at a splash screen, routes through the main menu, campaign intro, random generation placeholder, level screen, win/loss overlays, options, tutorial, game over, and campaign complete screens.
|
||||||
|
|
||||||
|
The mock campaign manifest lives at `src\ReactorMaintenance.Godot\Data\default_campaign_manifest.json`. Each entry includes the future serialized simulation level path; those JSON files are intentionally placeholders for authored levels that will use the simulation `LevelSerializer` format.
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ SplashScreen
|
|||||||
MainMenu
|
MainMenu
|
||||||
-> New Campaign -> CampaignIntro or LevelScreen
|
-> New Campaign -> CampaignIntro or LevelScreen
|
||||||
-> Continue Campaign -> LevelScreen
|
-> Continue Campaign -> LevelScreen
|
||||||
-> Play Random Level -> LevelScreen
|
-> Play Random Level -> GenerationScreen -> LevelScreen
|
||||||
-> OptionsScreen
|
-> OptionsScreen
|
||||||
-> TutorialScreen
|
-> TutorialScreen
|
||||||
|
|
||||||
|
|||||||
15
src/ReactorMaintenance.Godot/Controls/CellInspector.cs
Normal file
15
src/ReactorMaintenance.Godot/Controls/CellInspector.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class CellInspector : PanelContainer
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Label m_Text = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d0d3kglv6s32m
|
||||||
33
src/ReactorMaintenance.Godot/Controls/ConfirmDialog.cs
Normal file
33
src/ReactorMaintenance.Godot/Controls/ConfirmDialog.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class ConfirmDialog : PanelContainer
|
||||||
|
{
|
||||||
|
public event Action? Confirmed;
|
||||||
|
public event Action? Canceled;
|
||||||
|
|
||||||
|
public void Configure(string title, string message, string confirmText = "Confirm")
|
||||||
|
{
|
||||||
|
foreach (var child in GetChildren())
|
||||||
|
child.QueueFree();
|
||||||
|
|
||||||
|
var body = new VBoxContainer();
|
||||||
|
AddChild(body);
|
||||||
|
body.AddChild(new Label { Text = title, HorizontalAlignment = HorizontalAlignment.Center });
|
||||||
|
body.AddChild(new Label { Text = message, AutowrapMode = TextServer.AutowrapMode.WordSmart });
|
||||||
|
|
||||||
|
var actions = new HBoxContainer();
|
||||||
|
body.AddChild(actions);
|
||||||
|
|
||||||
|
var confirm = new PrimaryButton();
|
||||||
|
confirm.Configure(confirmText);
|
||||||
|
confirm.Pressed += () => Confirmed?.Invoke();
|
||||||
|
actions.AddChild(confirm);
|
||||||
|
|
||||||
|
var cancel = new PrimaryButton();
|
||||||
|
cancel.Configure("Cancel");
|
||||||
|
cancel.Pressed += () => Canceled?.Invoke();
|
||||||
|
actions.AddChild(cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cp5qe2n4nwb32
|
||||||
24
src/ReactorMaintenance.Godot/Controls/ForecastList.cs
Normal file
24
src/ReactorMaintenance.Godot/Controls/ForecastList.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class ForecastList : PanelContainer
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
AddChild(m_Items);
|
||||||
|
SetForecasts(["Turn +1: Pressure stable", "Turn +2: No forecast warnings"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetForecasts(IReadOnlyList<string> forecasts)
|
||||||
|
{
|
||||||
|
foreach (var child in m_Items.GetChildren())
|
||||||
|
child.QueueFree();
|
||||||
|
|
||||||
|
m_Items.AddChild(new Label { Text = "Forecasts" });
|
||||||
|
foreach (var forecast in forecasts)
|
||||||
|
m_Items.AddChild(new Label { Text = forecast, AutowrapMode = TextServer.AutowrapMode.WordSmart });
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly VBoxContainer m_Items = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bswy75n15jxl5
|
||||||
23
src/ReactorMaintenance.Godot/Controls/InventoryStrip.cs
Normal file
23
src/ReactorMaintenance.Godot/Controls/InventoryStrip.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class InventoryStrip : HBoxContainer
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
AddChild(CreateItem("Fuel Neutralizer", 2));
|
||||||
|
AddChild(CreateItem("Coolant Neutralizer", 2));
|
||||||
|
AddChild(CreateItem("Electric Neutralizer", 1));
|
||||||
|
AddChild(CreateItem("Heat Shield", 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label CreateItem(string name, int count)
|
||||||
|
{
|
||||||
|
return new() {
|
||||||
|
Text = $"{name}: {count}",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d1pdwv570am3v
|
||||||
33
src/ReactorMaintenance.Godot/Controls/LevelHeader.cs
Normal file
33
src/ReactorMaintenance.Godot/Controls/LevelHeader.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class LevelHeader : HBoxContainer
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
AddChild(m_Title);
|
||||||
|
AddChild(m_Progress);
|
||||||
|
AddChild(m_Badge);
|
||||||
|
AddChild(m_Summary);
|
||||||
|
|
||||||
|
m_Title.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||||
|
m_Title.AddThemeFontSizeOverride("font_size", 24);
|
||||||
|
m_Progress.HorizontalAlignment = HorizontalAlignment.Center;
|
||||||
|
m_Summary.HorizontalAlignment = HorizontalAlignment.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLevel(CampaignLevel level, int levelNumber, int levelCount, string state)
|
||||||
|
{
|
||||||
|
m_Title.Text = level.Name;
|
||||||
|
m_Progress.Text = $"Level {levelNumber} / {levelCount}";
|
||||||
|
m_Badge.SetState(state);
|
||||||
|
m_Summary.Text = "Heat nominal | Reactor offline";
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly StateBadge m_Badge = new();
|
||||||
|
private readonly Label m_Progress = new();
|
||||||
|
private readonly Label m_Summary = new();
|
||||||
|
private readonly Label m_Title = new();
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Controls/LevelHeader.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Controls/LevelHeader.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cp50vj0rystlk
|
||||||
41
src/ReactorMaintenance.Godot/Controls/OutcomeOverlay.cs
Normal file
41
src/ReactorMaintenance.Godot/Controls/OutcomeOverlay.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class OutcomeOverlay : PanelContainer
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
CustomMinimumSize = new(420, 220);
|
||||||
|
AddChild(m_Body);
|
||||||
|
m_Body.AddChild(m_Title);
|
||||||
|
m_Body.AddChild(m_Message);
|
||||||
|
m_Body.AddChild(m_Actions);
|
||||||
|
m_Title.HorizontalAlignment = HorizontalAlignment.Center;
|
||||||
|
m_Title.AddThemeFontSizeOverride("font_size", 26);
|
||||||
|
m_Message.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||||
|
m_Message.HorizontalAlignment = HorizontalAlignment.Center;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Configure(string title, string message, IReadOnlyList<(string Text, Action Pressed)> actions)
|
||||||
|
{
|
||||||
|
m_Title.Text = title;
|
||||||
|
m_Message.Text = message;
|
||||||
|
|
||||||
|
foreach (var child in m_Actions.GetChildren())
|
||||||
|
child.QueueFree();
|
||||||
|
|
||||||
|
foreach (var action in actions)
|
||||||
|
{
|
||||||
|
var button = new PrimaryButton();
|
||||||
|
button.Configure(action.Text);
|
||||||
|
button.Pressed += action.Pressed;
|
||||||
|
m_Actions.AddChild(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HBoxContainer m_Actions = new();
|
||||||
|
private readonly VBoxContainer m_Body = new();
|
||||||
|
private readonly Label m_Message = new();
|
||||||
|
private readonly Label m_Title = new();
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://65iow3r0egvo
|
||||||
20
src/ReactorMaintenance.Godot/Controls/PrimaryButton.cs
Normal file
20
src/ReactorMaintenance.Godot/Controls/PrimaryButton.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class PrimaryButton : Button
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
FocusMode = FocusModeEnum.All;
|
||||||
|
CustomMinimumSize = new(220, 44);
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(string text, string tooltip = "", bool disabled = false)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
TooltipText = tooltip;
|
||||||
|
Disabled = disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bxk3mw5bybqxr
|
||||||
30
src/ReactorMaintenance.Godot/Controls/StateBadge.cs
Normal file
30
src/ReactorMaintenance.Godot/Controls/StateBadge.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
public partial class StateBadge : Label
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center;
|
||||||
|
VerticalAlignment = VerticalAlignment.Center;
|
||||||
|
CustomMinimumSize = new(96, 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetState(string state)
|
||||||
|
{
|
||||||
|
Text = state;
|
||||||
|
AddThemeColorOverride("font_color", GetColor(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetColor(string state)
|
||||||
|
{
|
||||||
|
return state.ToLowerInvariant() switch {
|
||||||
|
"stable" => new(0.72f, 0.86f, 0.76f),
|
||||||
|
"caution" => new(1.0f, 0.76f, 0.24f),
|
||||||
|
"critical" or "lost" => new(1.0f, 0.36f, 0.32f),
|
||||||
|
"ready" or "won" => new(0.45f, 1.0f, 0.58f),
|
||||||
|
_ => Colors.White
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Controls/StateBadge.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Controls/StateBadge.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://y7qn5eixkr7j
|
||||||
9
src/ReactorMaintenance.Godot/Data/CampaignLevel.cs
Normal file
9
src/ReactorMaintenance.Godot/Data/CampaignLevel.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
public sealed record CampaignLevel
|
||||||
|
{
|
||||||
|
public string Id { get; init; } = string.Empty;
|
||||||
|
public string Name { get; init; } = string.Empty;
|
||||||
|
public string FlavorText { get; init; } = string.Empty;
|
||||||
|
public string LevelPath { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Data/CampaignLevel.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Data/CampaignLevel.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://uis6i7yfkrty
|
||||||
6
src/ReactorMaintenance.Godot/Data/CampaignManifest.cs
Normal file
6
src/ReactorMaintenance.Godot/Data/CampaignManifest.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
public sealed record CampaignManifest
|
||||||
|
{
|
||||||
|
public IReadOnlyList<CampaignLevel> Levels { get; init; } = Array.Empty<CampaignLevel>();
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bnyc3qlyksmua
|
||||||
39
src/ReactorMaintenance.Godot/Data/CampaignRepository.cs
Normal file
39
src/ReactorMaintenance.Godot/Data/CampaignRepository.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using FileAccess = Godot.FileAccess;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
public static class CampaignRepository
|
||||||
|
{
|
||||||
|
public static CampaignManifest LoadDefault()
|
||||||
|
{
|
||||||
|
var json = FileAccess.GetFileAsString(c_DefaultManifestPath);
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
return CreateFallback();
|
||||||
|
|
||||||
|
var manifest = JsonSerializer.Deserialize<CampaignManifest>(json, s_Options);
|
||||||
|
return manifest is { Levels.Count: > 0 } ? manifest : CreateFallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CampaignManifest CreateFallback()
|
||||||
|
{
|
||||||
|
return new() {
|
||||||
|
Levels = [
|
||||||
|
new() {
|
||||||
|
Id = "fallback",
|
||||||
|
Name = "Fallback Reactor",
|
||||||
|
FlavorText = "A placeholder level loaded because the campaign manifest was unavailable.",
|
||||||
|
LevelPath = "res://Data/Levels/fallback.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string c_DefaultManifestPath = "res://Data/default_campaign_manifest.json";
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions s_Options = new() {
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
Converters = { new JsonStringEnumConverter() }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bufo8npdbreb1
|
||||||
62
src/ReactorMaintenance.Godot/Data/FrontendSession.cs
Normal file
62
src/ReactorMaintenance.Godot/Data/FrontendSession.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
namespace ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
public sealed class FrontendSession
|
||||||
|
{
|
||||||
|
public FrontendSession(CampaignManifest campaign)
|
||||||
|
{
|
||||||
|
m_Campaign = campaign;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartNewCampaign()
|
||||||
|
{
|
||||||
|
IsRandomLevel = false;
|
||||||
|
CampaignIndex = 0;
|
||||||
|
HasContinue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContinueCampaign()
|
||||||
|
{
|
||||||
|
IsRandomLevel = false;
|
||||||
|
HasContinue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartRandomLevel()
|
||||||
|
{
|
||||||
|
IsRandomLevel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkCurrentLevelLoaded()
|
||||||
|
{
|
||||||
|
if (!IsRandomLevel)
|
||||||
|
HasContinue = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AdvanceToNextLevel()
|
||||||
|
{
|
||||||
|
if (HasNextLevel)
|
||||||
|
CampaignIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CompleteCampaign()
|
||||||
|
{
|
||||||
|
IsRandomLevel = false;
|
||||||
|
CampaignIndex = 0;
|
||||||
|
HasContinue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasContinue { get; private set; }
|
||||||
|
public bool IsRandomLevel { get; private set; }
|
||||||
|
public int CampaignIndex { get; private set; }
|
||||||
|
public CampaignLevel CurrentLevel => IsRandomLevel ? m_RandomLevel : m_Campaign.Levels[CampaignIndex];
|
||||||
|
public int LevelNumber => IsRandomLevel ? 1 : CampaignIndex + 1;
|
||||||
|
public int LevelCount => IsRandomLevel ? 1 : m_Campaign.Levels.Count;
|
||||||
|
public bool HasNextLevel => !IsRandomLevel && CampaignIndex + 1 < m_Campaign.Levels.Count;
|
||||||
|
private readonly CampaignManifest m_Campaign;
|
||||||
|
|
||||||
|
private readonly CampaignLevel m_RandomLevel = new() {
|
||||||
|
Id = "random-maintenance",
|
||||||
|
Name = "Random Maintenance Shift",
|
||||||
|
FlavorText = "A generated shift will use authored JSON level data until generation is implemented.",
|
||||||
|
LevelPath = "res://Data/Levels/random_placeholder.json"
|
||||||
|
};
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Data/FrontendSession.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Data/FrontendSession.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bsbukw3qpo0by
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"levels": [
|
||||||
|
{
|
||||||
|
"id": "coolant-restart",
|
||||||
|
"name": "Coolant Restart",
|
||||||
|
"flavorText": "The lower coolant loop is starving the reactor core. Restore enough service to keep the first startup window alive.",
|
||||||
|
"levelPath": "res://Data/Levels/coolant_restart.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fuel-bleed",
|
||||||
|
"name": "Fuel Bleed",
|
||||||
|
"flavorText": "A fuel manifold is venting through the maintenance deck. Isolate the leak before pressure cascades into the ignition zone.",
|
||||||
|
"levelPath": "res://Data/Levels/fuel_bleed.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "black-start",
|
||||||
|
"name": "Black Start",
|
||||||
|
"flavorText": "The final reactor needs fuel, coolant, and electricity in balance before the station can carry load again.",
|
||||||
|
"levelPath": "res://Data/Levels/black_start.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/CampaignIntro.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/CampaignIntro.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/CampaignIntro.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="CampaignIntro" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/CellInspector.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="CellInspector" type="PanelContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/ConfirmDialog.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="ConfirmDialog" type="PanelContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/ForecastList.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="ForecastList" type="PanelContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/InventoryStrip.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="InventoryStrip" type="HBoxContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/LevelHeader.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="LevelHeader" type="HBoxContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/OutcomeOverlay.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="OutcomeOverlay" type="PanelContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/PrimaryButton.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="PrimaryButton" type="Button"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Controls/StateBadge.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="StateBadge" type="Label"]
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/GameOverScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/GameOverScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/GameOverScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="GameOverScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/GameWonScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/GameWonScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/GameWonScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="GameWonScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/GenerationScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/GenerationScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/GenerationScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="GenerationScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/LevelScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/LevelScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/LevelScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="LevelScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
6
src/ReactorMaintenance.Godot/Scenes/LoseOverlay.tscn
Normal file
6
src/ReactorMaintenance.Godot/Scenes/LoseOverlay.tscn
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/LoseOverlay.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="LoseOverlay" type="PanelContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/MainMenu.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/MainMenu.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/MainMenu.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="MainMenu" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/OptionsScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/OptionsScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/OptionsScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="OptionsScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/SplashScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/SplashScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/SplashScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="SplashScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
12
src/ReactorMaintenance.Godot/Scenes/TutorialScreen.tscn
Normal file
12
src/ReactorMaintenance.Godot/Scenes/TutorialScreen.tscn
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/TutorialScreen.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="TutorialScreen" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
6
src/ReactorMaintenance.Godot/Scenes/WinOverlay.tscn
Normal file
6
src/ReactorMaintenance.Godot/Scenes/WinOverlay.tscn
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://Screens/WinOverlay.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="WinOverlay" type="PanelContainer"]
|
||||||
|
script = ExtResource("1")
|
||||||
20
src/ReactorMaintenance.Godot/Screens/CampaignIntro.cs
Normal file
20
src/ReactorMaintenance.Godot/Screens/CampaignIntro.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class CampaignIntro : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app, CampaignLevel level, int levelNumber, int levelCount)
|
||||||
|
{
|
||||||
|
var body = CreatePage(level.Name);
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
body.AddChild(CreateBodyText($"Campaign level {levelNumber} / {levelCount}"));
|
||||||
|
body.AddChild(CreateBodyText(level.FlavorText));
|
||||||
|
|
||||||
|
var actions = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ShrinkCenter };
|
||||||
|
body.AddChild(actions);
|
||||||
|
actions.AddChild(CreateButton("Begin", app.LoadCurrentLevel));
|
||||||
|
actions.AddChild(CreateButton("Back", app.ShowMainMenu));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cd0ffydc3pdc2
|
||||||
19
src/ReactorMaintenance.Godot/Screens/GameOverScreen.cs
Normal file
19
src/ReactorMaintenance.Godot/Screens/GameOverScreen.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class GameOverScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app, CampaignLevel level)
|
||||||
|
{
|
||||||
|
var body = CreatePage("Game Over");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
body.AddChild(CreateBodyText($"Failed level: {level.Name}"));
|
||||||
|
|
||||||
|
var actions = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ShrinkCenter };
|
||||||
|
body.AddChild(actions);
|
||||||
|
actions.AddChild(CreateButton("Retry Current Level", app.RetryCurrentLevel));
|
||||||
|
actions.AddChild(CreateButton("Main Menu", app.ShowMainMenu));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d1ncb4ysjstdd
|
||||||
15
src/ReactorMaintenance.Godot/Screens/GameWonScreen.cs
Normal file
15
src/ReactorMaintenance.Godot/Screens/GameWonScreen.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class GameWonScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app, int completedLevels)
|
||||||
|
{
|
||||||
|
var body = CreatePage("Campaign Complete");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
body.AddChild(CreateBodyText($"All {completedLevels} handcrafted reactor incidents are resolved."));
|
||||||
|
body.AddChild(CreateBodyText("The maintenance crew has restored enough capacity to bring the station back online."));
|
||||||
|
body.AddChild(CreateButton("Main Menu", app.ShowMainMenu));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cfijlvnlio424
|
||||||
20
src/ReactorMaintenance.Godot/Screens/GenerationScreen.cs
Normal file
20
src/ReactorMaintenance.Godot/Screens/GenerationScreen.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class GenerationScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app, CampaignLevel level)
|
||||||
|
{
|
||||||
|
var body = CreatePage("Random Level");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
body.AddChild(CreateBodyText(level.FlavorText));
|
||||||
|
body.AddChild(CreateBodyText($"Future level JSON: {level.LevelPath}"));
|
||||||
|
|
||||||
|
var actions = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ShrinkCenter };
|
||||||
|
body.AddChild(actions);
|
||||||
|
actions.AddChild(CreateButton("Begin", app.LoadCurrentLevel));
|
||||||
|
actions.AddChild(CreateButton("Back", app.ShowMainMenu));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cs63x3a7h8pad
|
||||||
107
src/ReactorMaintenance.Godot/Screens/LevelScreen.cs
Normal file
107
src/ReactorMaintenance.Godot/Screens/LevelScreen.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Controls;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class LevelScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app, CampaignLevel level, int levelNumber, int levelCount)
|
||||||
|
{
|
||||||
|
m_App = app;
|
||||||
|
m_Level = level;
|
||||||
|
|
||||||
|
var body = CreatePage(string.Empty);
|
||||||
|
var header = new LevelHeader();
|
||||||
|
body.AddChild(header);
|
||||||
|
header.SetLevel(level, levelNumber, levelCount, "Stable");
|
||||||
|
|
||||||
|
var flavor = CreateBodyText(level.FlavorText);
|
||||||
|
flavor.HorizontalAlignment = HorizontalAlignment.Left;
|
||||||
|
body.AddChild(flavor);
|
||||||
|
|
||||||
|
var split = new HSplitContainer { SizeFlagsVertical = SizeFlags.ExpandFill };
|
||||||
|
body.AddChild(split);
|
||||||
|
|
||||||
|
split.AddChild(CreateGridPlaceholder());
|
||||||
|
split.AddChild(CreateSidePanel(level));
|
||||||
|
|
||||||
|
body.AddChild(new InventoryStrip());
|
||||||
|
body.AddChild(CreateActionBar());
|
||||||
|
|
||||||
|
m_OverlayLayer = new();
|
||||||
|
AddChild(m_OverlayLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PanelContainer CreateGridPlaceholder()
|
||||||
|
{
|
||||||
|
var panel = new PanelContainer { CustomMinimumSize = new(560, 360), SizeFlagsHorizontal = SizeFlags.ExpandFill, SizeFlagsVertical = SizeFlags.ExpandFill };
|
||||||
|
var label = new Label {
|
||||||
|
Text = "Level Grid Placeholder\nRobot marker, terrain, props, hazards, and underground overlays will render here.",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
AutowrapMode = TextServer.AutowrapMode.WordSmart
|
||||||
|
};
|
||||||
|
panel.AddChild(label);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VBoxContainer CreateSidePanel(CampaignLevel level)
|
||||||
|
{
|
||||||
|
var sidePanel = new VBoxContainer { CustomMinimumSize = new(300, 0) };
|
||||||
|
sidePanel.AddChild(new Label { Text = "Inspector" });
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
var actions = new HBoxContainer();
|
||||||
|
actions.AddChild(CreateButton("Move", () => { }, "Quick action placeholder"));
|
||||||
|
actions.AddChild(CreateButton("Interact", () => { }, "Lengthy action placeholder"));
|
||||||
|
actions.AddChild(CreateButton("Repair", () => { }, "Lengthy action placeholder"));
|
||||||
|
actions.AddChild(CreateButton("Trigger Win", ShowWinOverlay));
|
||||||
|
actions.AddChild(CreateButton("Trigger Lose", ShowLoseOverlay));
|
||||||
|
actions.AddChild(CreateButton("Main Menu", () => m_App?.ShowMainMenu()));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowLoseOverlay()
|
||||||
|
{
|
||||||
|
if (m_OutcomeVisible || m_App is null || m_Level is null || m_OverlayLayer is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_OutcomeVisible = true;
|
||||||
|
var overlay = new LoseOverlay();
|
||||||
|
overlay.Configure(m_Level, m_App.RetryCurrentLevel, m_App.ShowGameOver, m_App.ShowMainMenu);
|
||||||
|
AddCenteredOverlay(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowWinOverlay()
|
||||||
|
{
|
||||||
|
if (m_OutcomeVisible || m_App is null || m_Level is null || m_OverlayLayer is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
AnchorRight = 1,
|
||||||
|
AnchorBottom = 1
|
||||||
|
};
|
||||||
|
center.AddChild(overlay);
|
||||||
|
m_OverlayLayer?.AddChild(center);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppController? m_App;
|
||||||
|
private CampaignLevel? m_Level;
|
||||||
|
private bool m_OutcomeVisible;
|
||||||
|
private CanvasLayer? m_OverlayLayer;
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Screens/LevelScreen.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Screens/LevelScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dix1dibqgxr3y
|
||||||
19
src/ReactorMaintenance.Godot/Screens/LoseOverlay.cs
Normal file
19
src/ReactorMaintenance.Godot/Screens/LoseOverlay.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using ReactorMaintenance.Godot.Controls;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class LoseOverlay : OutcomeOverlay
|
||||||
|
{
|
||||||
|
public void Configure(CampaignLevel level, Action retryLevel, Action openGameOver, Action mainMenu)
|
||||||
|
{
|
||||||
|
Configure(
|
||||||
|
"Level Lost",
|
||||||
|
$"{level.Name} has failed. Retry reloads the level from its starting snapshot.",
|
||||||
|
[
|
||||||
|
("Retry Level", retryLevel),
|
||||||
|
("Game Over", openGameOver),
|
||||||
|
("Main Menu", mainMenu)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Screens/LoseOverlay.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Screens/LoseOverlay.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bvonbqula7dxq
|
||||||
26
src/ReactorMaintenance.Godot/Screens/MainMenu.cs
Normal file
26
src/ReactorMaintenance.Godot/Screens/MainMenu.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class MainMenu : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app, bool hasContinue)
|
||||||
|
{
|
||||||
|
var body = CreatePage("Reactor Maintenance");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
|
||||||
|
var commands = new VBoxContainer {
|
||||||
|
CustomMinimumSize = new(320, 0),
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||||
|
};
|
||||||
|
body.AddChild(commands);
|
||||||
|
|
||||||
|
commands.AddChild(CreateButton("New Campaign", app.StartNewCampaign));
|
||||||
|
commands.AddChild(CreateButton("Continue Campaign", app.ContinueCampaign, "No campaign progress exists yet.", !hasContinue));
|
||||||
|
commands.AddChild(CreateButton("Play Random Level", app.StartRandomLevel));
|
||||||
|
commands.AddChild(CreateButton("Options", app.ShowOptions));
|
||||||
|
commands.AddChild(CreateButton("Tutorial", app.ShowTutorial));
|
||||||
|
|
||||||
|
body.AddChild(CreateBodyText("Godot frontend prototype"));
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Screens/MainMenu.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Screens/MainMenu.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dj4he4y5anmdy
|
||||||
26
src/ReactorMaintenance.Godot/Screens/OptionsScreen.cs
Normal file
26
src/ReactorMaintenance.Godot/Screens/OptionsScreen.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class OptionsScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app)
|
||||||
|
{
|
||||||
|
var body = CreatePage("Options");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
|
||||||
|
body.AddChild(CreateSlider("Master Volume"));
|
||||||
|
body.AddChild(CreateSlider("Music Volume"));
|
||||||
|
body.AddChild(new CheckBox { Text = "Fullscreen" });
|
||||||
|
body.AddChild(new OptionButton { Text = "UI Scale" });
|
||||||
|
body.AddChild(CreateButton("Back", app.ShowMainMenu));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HBoxContainer CreateSlider(string label)
|
||||||
|
{
|
||||||
|
var row = new HBoxContainer { CustomMinimumSize = new(360, 40), SizeFlagsHorizontal = SizeFlags.ShrinkCenter };
|
||||||
|
row.AddChild(new Label { Text = label, CustomMinimumSize = new(140, 0) });
|
||||||
|
row.AddChild(new HSlider { MinValue = 0, MaxValue = 100, Value = 75, SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bkf0ctd7yk656
|
||||||
58
src/ReactorMaintenance.Godot/Screens/ScreenBase.cs
Normal file
58
src/ReactorMaintenance.Godot/Screens/ScreenBase.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Controls;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public abstract partial class ScreenBase : Control
|
||||||
|
{
|
||||||
|
protected VBoxContainer CreatePage(string title)
|
||||||
|
{
|
||||||
|
foreach (var child in GetChildren())
|
||||||
|
child.QueueFree();
|
||||||
|
|
||||||
|
SetAnchorsPreset(LayoutPreset.FullRect);
|
||||||
|
|
||||||
|
var margin = new MarginContainer {
|
||||||
|
AnchorRight = 1,
|
||||||
|
AnchorBottom = 1
|
||||||
|
};
|
||||||
|
margin.AddThemeConstantOverride("margin_left", 48);
|
||||||
|
margin.AddThemeConstantOverride("margin_top", 36);
|
||||||
|
margin.AddThemeConstantOverride("margin_right", 48);
|
||||||
|
margin.AddThemeConstantOverride("margin_bottom", 36);
|
||||||
|
AddChild(margin);
|
||||||
|
|
||||||
|
var body = new VBoxContainer {
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ExpandFill,
|
||||||
|
SizeFlagsVertical = SizeFlags.ExpandFill
|
||||||
|
};
|
||||||
|
margin.AddChild(body);
|
||||||
|
|
||||||
|
var titleLabel = new Label {
|
||||||
|
Text = title,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
titleLabel.AddThemeFontSizeOverride("font_size", 32);
|
||||||
|
body.AddChild(titleLabel);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static PrimaryButton CreateButton(string text, Action pressed, string tooltip = "", bool disabled = false)
|
||||||
|
{
|
||||||
|
var button = new PrimaryButton();
|
||||||
|
button.Configure(text, tooltip, disabled);
|
||||||
|
button.Pressed += pressed;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Label CreateBodyText(string text)
|
||||||
|
{
|
||||||
|
return new() {
|
||||||
|
Text = text,
|
||||||
|
AutowrapMode = TextServer.AutowrapMode.WordSmart,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Screens/ScreenBase.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Screens/ScreenBase.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dcwn6wqeikkpn
|
||||||
33
src/ReactorMaintenance.Godot/Screens/SplashScreen.cs
Normal file
33
src/ReactorMaintenance.Godot/Screens/SplashScreen.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class SplashScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public event Action? Started;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
var body = CreatePage("Reactor Maintenance");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
body.AddChild(CreateBodyText("Systems booting..."));
|
||||||
|
GetTree().CreateTimer(1.2).Timeout += Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _UnhandledInput(InputEvent @event)
|
||||||
|
{
|
||||||
|
if (@event.IsPressed())
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (m_Transitioned)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_Transitioned = true;
|
||||||
|
Started?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool m_Transitioned;
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Screens/SplashScreen.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Screens/SplashScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://yiggx6pgqntg
|
||||||
18
src/ReactorMaintenance.Godot/Screens/TutorialScreen.cs
Normal file
18
src/ReactorMaintenance.Godot/Screens/TutorialScreen.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class TutorialScreen : ScreenBase
|
||||||
|
{
|
||||||
|
public void Configure(AppController app)
|
||||||
|
{
|
||||||
|
var body = CreatePage("Tutorial");
|
||||||
|
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||||
|
body.AddChild(CreateBodyText("Topics: action economy, hazards, forecasts, remedies, heat shields, and reactor activation."));
|
||||||
|
|
||||||
|
var actions = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ShrinkCenter };
|
||||||
|
body.AddChild(actions);
|
||||||
|
actions.AddChild(CreateButton("Start Tutorial Level", app.StartRandomLevel));
|
||||||
|
actions.AddChild(CreateButton("Back", app.ShowMainMenu));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://xfq61pdayub7
|
||||||
18
src/ReactorMaintenance.Godot/Screens/WinOverlay.cs
Normal file
18
src/ReactorMaintenance.Godot/Screens/WinOverlay.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using ReactorMaintenance.Godot.Controls;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
public partial class WinOverlay : OutcomeOverlay
|
||||||
|
{
|
||||||
|
public void Configure(CampaignLevel level, Action nextLevel, Action mainMenu)
|
||||||
|
{
|
||||||
|
Configure(
|
||||||
|
"Reactor Online",
|
||||||
|
$"{level.Name} is stabilized. Continue loads the next campaign level or campaign completion screen.",
|
||||||
|
[
|
||||||
|
("Continue", nextLevel),
|
||||||
|
("Main Menu", mainMenu)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/ReactorMaintenance.Godot/Screens/WinOverlay.cs.uid
Normal file
1
src/ReactorMaintenance.Godot/Screens/WinOverlay.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://cqiqwgr0usmki
|
||||||
110
src/ReactorMaintenance.Godot/Scripts/AppController.cs
Normal file
110
src/ReactorMaintenance.Godot/Scripts/AppController.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using Godot;
|
||||||
|
using ReactorMaintenance.Godot.Data;
|
||||||
|
using ReactorMaintenance.Godot.Screens;
|
||||||
|
|
||||||
|
namespace ReactorMaintenance.Godot;
|
||||||
|
|
||||||
|
public partial class AppController : Control
|
||||||
|
{
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
ShowSplash();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowSplash()
|
||||||
|
{
|
||||||
|
ShowScreen<SplashScreen>("res://Scenes/SplashScreen.tscn", screen => screen.Started += ShowMainMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowMainMenu()
|
||||||
|
{
|
||||||
|
ShowScreen<MainMenu>("res://Scenes/MainMenu.tscn", screen => screen.Configure(this, m_Session.HasContinue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartNewCampaign()
|
||||||
|
{
|
||||||
|
m_Session.StartNewCampaign();
|
||||||
|
ShowCampaignIntro();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContinueCampaign()
|
||||||
|
{
|
||||||
|
m_Session.ContinueCampaign();
|
||||||
|
LoadCurrentLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartRandomLevel()
|
||||||
|
{
|
||||||
|
m_Session.StartRandomLevel();
|
||||||
|
ShowScreen<GenerationScreen>("res://Scenes/GenerationScreen.tscn", screen => screen.Configure(this, m_Session.CurrentLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowCampaignIntro()
|
||||||
|
{
|
||||||
|
ShowScreen<CampaignIntro>("res://Scenes/CampaignIntro.tscn", screen => screen.Configure(this, m_Session.CurrentLevel, m_Session.LevelNumber, m_Session.LevelCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCurrentLevel()
|
||||||
|
{
|
||||||
|
m_Session.MarkCurrentLevelLoaded();
|
||||||
|
ShowScreen<LevelScreen>("res://Scenes/LevelScreen.tscn", screen => screen.Configure(this, m_Session.CurrentLevel, m_Session.LevelNumber, m_Session.LevelCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RetryCurrentLevel()
|
||||||
|
{
|
||||||
|
LoadCurrentLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CompleteCurrentLevel()
|
||||||
|
{
|
||||||
|
if (m_Session.IsRandomLevel)
|
||||||
|
{
|
||||||
|
StartRandomLevel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Session.HasNextLevel)
|
||||||
|
{
|
||||||
|
m_Session.AdvanceToNextLevel();
|
||||||
|
ShowCampaignIntro();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Session.CompleteCampaign();
|
||||||
|
ShowGameWon();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowOptions()
|
||||||
|
{
|
||||||
|
ShowScreen<OptionsScreen>("res://Scenes/OptionsScreen.tscn", screen => screen.Configure(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowTutorial()
|
||||||
|
{
|
||||||
|
ShowScreen<TutorialScreen>("res://Scenes/TutorialScreen.tscn", screen => screen.Configure(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowGameOver()
|
||||||
|
{
|
||||||
|
ShowScreen<GameOverScreen>("res://Scenes/GameOverScreen.tscn", screen => screen.Configure(this, m_Session.CurrentLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowGameWon()
|
||||||
|
{
|
||||||
|
ShowScreen<GameWonScreen>("res://Scenes/GameWonScreen.tscn", screen => screen.Configure(this, m_Session.LevelCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowScreen<TScreen>(string scenePath, Action<TScreen> configure) where TScreen : Control
|
||||||
|
{
|
||||||
|
m_CurrentScreen?.QueueFree();
|
||||||
|
|
||||||
|
var packedScene = ResourceLoader.Load<PackedScene>(scenePath);
|
||||||
|
var screen = packedScene.Instantiate<TScreen>();
|
||||||
|
m_CurrentScreen = screen;
|
||||||
|
AddChild(screen);
|
||||||
|
configure(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly FrontendSession m_Session = new(CampaignRepository.LoadDefault());
|
||||||
|
private Control? m_CurrentScreen;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://exf77g2sqb6d
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
[gd_scene format=3 uid="uid://bhhppnvto57ht"]
|
[gd_scene load_steps=2 format=3 uid="uid://bhhppnvto57ht"]
|
||||||
|
|
||||||
[node name="Node2D" type="Node2D"]
|
[ext_resource type="Script" path="res://Scripts/AppController.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="Main" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
|
|||||||
@@ -8,12 +8,10 @@
|
|||||||
|
|
||||||
config_version=5
|
config_version=5
|
||||||
|
|
||||||
"config_version"=5
|
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Reactor Maintenance"
|
config/name="Reactor Maintenance"
|
||||||
run/main_scene="uid://bhhppnvto57ht"
|
run/main_scene="res://main.tscn"
|
||||||
config/features=PackedStringArray("4.5", "C#", "Forward Plus")
|
config/features=PackedStringArray("4.5", "C#", "Forward Plus")
|
||||||
|
|
||||||
[dotnet]
|
[dotnet]
|
||||||
|
|||||||
Reference in New Issue
Block a user