Finish Godot campaign polish
This commit is contained in:
8
TASKS.md
8
TASKS.md
@@ -180,10 +180,10 @@ This backlog tracks what must change so the implementation matches `docs/design.
|
||||
|
||||
## P2 Polish And Release Tasks
|
||||
|
||||
- [ ] Add concise pulse-result feedback for major outcomes: isolated leak, restored pressure, downstream starvation, reactor ready, wet-electric risk, and terminal heat danger.
|
||||
- [ ] Add campaign completion flow for the final Group 6 level.
|
||||
- [ ] Add loading and malformed-level error states for campaign level loading.
|
||||
- [ ] Revisit art labels/icons for `Water`, `Unsafe`, powered props, and sprinkler controls after mechanics are implemented.
|
||||
- [x] Add concise pulse-result feedback for major outcomes: isolated leak, restored pressure, downstream starvation, reactor ready, wet-electric risk, and terminal heat danger.
|
||||
- [x] Add campaign completion flow for the final Group 6 level.
|
||||
- [x] Add loading and malformed-level error states for campaign level loading.
|
||||
- [x] Revisit art labels/icons for `Water`, `Unsafe`, powered props, and sprinkler controls after mechanics are implemented.
|
||||
|
||||
## Verification Rules
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ public sealed class GameSession
|
||||
var trace = m_Engine.InteractPropWithPulseTrace(LevelState);
|
||||
LevelState = trace.FinalState;
|
||||
LastPulseSteps = trace.Steps;
|
||||
LastPulseFeedback = CreatePulseFeedback(LevelState);
|
||||
if (LevelState.Global.Pulse == pulse)
|
||||
{
|
||||
LevelStateChanged?.Invoke(this);
|
||||
@@ -60,6 +61,7 @@ public sealed class GameSession
|
||||
var trace = m_Engine.InteractLeakWithPulseTrace(LevelState, carrier, useRemedy);
|
||||
LevelState = trace.FinalState;
|
||||
LastPulseSteps = trace.Steps;
|
||||
LastPulseFeedback = CreatePulseFeedback(LevelState);
|
||||
if (LevelState.Global.Pulse == pulse)
|
||||
{
|
||||
LevelStateChanged?.Invoke(this);
|
||||
@@ -79,6 +81,7 @@ public sealed class GameSession
|
||||
var trace = m_Engine.ApplyHeatShieldWithPulseTrace(LevelState);
|
||||
LevelState = trace.FinalState;
|
||||
LastPulseSteps = trace.Steps;
|
||||
LastPulseFeedback = CreatePulseFeedback(LevelState);
|
||||
if (LevelState.Global.Pulse == pulse)
|
||||
{
|
||||
LevelStateChanged?.Invoke(this);
|
||||
@@ -108,6 +111,7 @@ public sealed class GameSession
|
||||
{
|
||||
LevelState = m_StartSnapshot;
|
||||
LastPulseSteps = Array.Empty<LevelState>();
|
||||
LastPulseFeedback = Array.Empty<string>();
|
||||
LevelStateChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
@@ -138,6 +142,30 @@ public sealed class GameSession
|
||||
PulseAdvanced?.Invoke(this);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> CreatePulseFeedback(LevelState level)
|
||||
{
|
||||
var feedback = new List<string>();
|
||||
if (level.Global.LevelState == ELevelState.Ready)
|
||||
feedback.Add("Reactor ready");
|
||||
|
||||
if (level.Props.Any(prop => prop.Type == EPropType.Consumer && prop.ServiceStateFor(ECarrierType.Fuel) == EConsumerServiceState.Starved))
|
||||
feedback.Add("Fuel consumer starved");
|
||||
|
||||
if (level.Props.Any(prop => prop.Type == EPropType.Consumer && prop.ServiceStateFor(ECarrierType.Water) == EConsumerServiceState.Starved))
|
||||
feedback.Add("Water consumer starved");
|
||||
|
||||
if (level.Props.Any(prop => prop.Type == EPropType.Consumer && prop.ServiceStateFor(ECarrierType.Electricity) == EConsumerServiceState.Starved))
|
||||
feedback.Add("Electricity consumer starved");
|
||||
|
||||
if (level.Surface.Any(surface => surface.IsWetElectricUnsafe()))
|
||||
feedback.Add("Wet-electric risk");
|
||||
|
||||
if (level.Surface.Any(surface => surface.Heat >= Balancing.Current.RobotHeatSafetyThreshold))
|
||||
feedback.Add("Heat danger");
|
||||
|
||||
return feedback.Count > 0 ? feedback : [level.Global.Status];
|
||||
}
|
||||
|
||||
private void CheckOutcome()
|
||||
{
|
||||
if (LevelState.Global.LevelState == ELevelState.Won)
|
||||
@@ -150,6 +178,7 @@ public sealed class GameSession
|
||||
public GridPosition RobotPosition => LevelState.Robot.Position;
|
||||
public GlobalState GlobalState => LevelState.Global;
|
||||
public IReadOnlyList<Forecast> Forecasts => LevelState.Forecasts;
|
||||
public IReadOnlyList<string> LastPulseFeedback { get; private set; } = Array.Empty<string>();
|
||||
public IReadOnlyList<LevelState> LastPulseSteps { get; private set; } = Array.Empty<LevelState>();
|
||||
public IReadOnlyList<LeakState> Leaks => LevelState.Leaks;
|
||||
public string LevelPath { get; private set; } = string.Empty;
|
||||
|
||||
@@ -16,4 +16,16 @@ public partial class GameOverScreen : ScreenBase
|
||||
actions.AddChild(CreateButton("Retry Current Level", app.RetryCurrentLevel));
|
||||
actions.AddChild(CreateButton("Main Menu", app.ShowMainMenu));
|
||||
}
|
||||
|
||||
public void ConfigureLoadError(AppController app, CampaignLevel level, string message)
|
||||
{
|
||||
var body = CreatePage("Level Load Error");
|
||||
body.Alignment = BoxContainer.AlignmentMode.Center;
|
||||
body.AddChild(CreateBodyText($"{level.Name}: {message}"));
|
||||
|
||||
var actions = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ShrinkCenter };
|
||||
body.AddChild(actions);
|
||||
actions.AddChild(CreateButton("Retry", app.RetryCurrentLevel));
|
||||
actions.AddChild(CreateButton("Main Menu", app.ShowMainMenu));
|
||||
}
|
||||
}
|
||||
@@ -431,13 +431,22 @@ public partial class LevelScreen : ScreenBase
|
||||
timer.Stop();
|
||||
UpdateUI();
|
||||
m_InputLocked = false;
|
||||
SetEditorStatus(m_EditMode ? CurrentValidationText() : $"Pulse {m_Session?.LevelState.Global.Pulse ?? 0}");
|
||||
SetEditorStatus(m_EditMode ? CurrentValidationText() : CurrentPulseFeedback());
|
||||
timer.QueueFree();
|
||||
};
|
||||
AddChild(timer);
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private string CurrentPulseFeedback()
|
||||
{
|
||||
if (m_Session is null)
|
||||
return "Play mode";
|
||||
|
||||
var feedback = m_Session.LastPulseFeedback.Count > 0 ? string.Join(", ", m_Session.LastPulseFeedback) : m_Session.LevelState.Global.Status;
|
||||
return $"Pulse {m_Session.LevelState.Global.Pulse}: {feedback}";
|
||||
}
|
||||
|
||||
private void ShowPlaybackSnapshot(LevelState snapshot)
|
||||
{
|
||||
m_GridViewport?.SetLevelState(snapshot);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Godot;
|
||||
using ReactorMaintenance.Godot.Data;
|
||||
using ReactorMaintenance.Godot.Screens;
|
||||
using ReactorMaintenance.Simulation;
|
||||
|
||||
namespace ReactorMaintenance.Godot;
|
||||
|
||||
@@ -49,7 +50,17 @@ public partial class AppController : Control
|
||||
m_Session.MarkCurrentLevelLoaded();
|
||||
var level = m_Session.CurrentLevel;
|
||||
var gameSession = new GameSession();
|
||||
var levelState = LevelStateLoader.Load(level.LevelPath);
|
||||
LevelState levelState;
|
||||
try
|
||||
{
|
||||
levelState = LevelStateLoader.Load(level.LevelPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowScreen<GameOverScreen>("res://Scenes/GameOverScreen.tscn", screen => screen.ConfigureLoadError(this, level, ex.Message));
|
||||
return;
|
||||
}
|
||||
|
||||
gameSession.Initialize(levelState, level.LevelPath);
|
||||
ShowScreen<LevelScreen>("res://Scenes/LevelScreen.tscn", screen => screen.Configure(this, gameSession, level, m_Session.LevelNumber, m_Session.LevelCount));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user