Rework simulation rules

This commit is contained in:
2026-05-11 22:18:43 +02:00
parent 3d406179bf
commit e1ac56d201
30 changed files with 554 additions and 848 deletions

View File

@@ -280,7 +280,6 @@ public sealed partial class MainWindow
ClearPendingEditorOperation();
m_Level = m_Level.SetProp(position, new()).SetSurface(position, new()) with {
Leaks = m_Level.Leaks.Where(leak => leak.AccessPosition != position && leak.UndergroundPosition != position).ToArray(),
Doors = m_Level.Doors.Where(door => door.A != position && door.B != position).ToArray(),
Reactors = m_Level.Reactors.Where(reactor => reactor.ControlPosition != position).ToArray()
};
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
@@ -384,6 +383,11 @@ public sealed partial class MainWindow
return m_Level.InBounds(position) ? m_Level.GetTerrain(position) : ECellTerrain.Wall;
}
private bool IsWall(GridPosition position)
{
return m_Level.InBounds(position) && m_Level.GetTerrain(position) == ECellTerrain.Wall;
}
private void DrawUnderground(CanvasDrawingSession drawing, CanvasLayout layout)
{
foreach (var position in AllPositions())
@@ -431,11 +435,19 @@ public sealed partial class MainWindow
private void DrawDoors(CanvasDrawingSession drawing, CanvasLayout layout)
{
foreach (var door in m_Level.Doors)
foreach (var position in AllPositions())
{
var centerA = Center(layout.CellRect(door.A));
var centerB = Center(layout.CellRect(door.B));
drawing.DrawLine((float)centerA.X, (float)centerA.Y, (float)centerB.X, (float)centerB.Y, door.State == EDoorState.Open ? Colors.LightGreen : Colors.OrangeRed, 5);
var prop = m_Level.GetProp(position);
if (prop.Type != EPropType.Door)
continue;
var rect = layout.CellRect(position);
var center = Center(rect);
var color = prop.DoorState == EDoorState.Open ? Colors.LightGreen : Colors.OrangeRed;
if (IsWall(new(position.X, position.Y - 1)) && IsWall(new(position.X, position.Y + 1)))
drawing.DrawLine((float)rect.Left, (float)center.Y, (float)rect.Right, (float)center.Y, color, 5);
else
drawing.DrawLine((float)center.X, (float)rect.Top, (float)center.X, (float)rect.Bottom, color, 5);
}
}
@@ -554,11 +566,11 @@ public sealed partial class MainWindow
LevelNameText.Text = m_Level.Name;
TurnText.Text = m_Level.Global.Turn.ToString(CultureInfo.InvariantCulture);
StatusText.Text = $"{m_Level.Global.LevelState}: {m_Level.Global.Status}";
GlobalText.Text = $"Actions: {m_Level.Global.ActionsRemaining}/{Balancing.Current.ActionsPerTurn}\n"
+ $"All-seeing-eye: {(m_Level.Global.AllSeeingEyeUnlocked ? "unlocked" : "locked")}\n"
+ $"Inventory: F {m_Level.Robot.FuelNeutralizers}, C {m_Level.Robot.CoolantNeutralizers}, E {m_Level.Robot.ElectricityNeutralizers}, H {m_Level.Robot.HeatShields}\n"
var doorCount = m_Level.Props.Count(prop => prop.Type == EPropType.Door);
GlobalText.Text = $"Inventory: F {m_Level.Robot.FuelNeutralizers}, C {m_Level.Robot.CoolantNeutralizers}, E {m_Level.Robot.ElectricityNeutralizers}, H {m_Level.Robot.HeatShields}\n"
+ $"Heat immunity steps: {m_Level.Robot.HeatImmunitySteps}\n"
+ $"Reactors: {m_Level.Reactors.Count}, Leaks: {m_Level.Leaks.Count}, Doors: {m_Level.Doors.Count}";
+ $"Required consumers F/C/E: {m_Level.RequiredFuelConsumers}/{m_Level.RequiredCoolantConsumers}/{m_Level.RequiredElectricityConsumers}\n"
+ $"Reactors: {m_Level.Reactors.Count}, Leaks: {m_Level.Leaks.Count}, Doors: {doorCount}";
CellText.Text = m_SelectedCell is { } position && m_Level.InBounds(position) ? CellInspectionText(position) : "No cell selected.";
ForecastList.ItemsSource = m_Level.Forecasts.Select(forecast => new ForecastViewModel($"{forecast.Turns}: {forecast.Message}")).ToArray();
@@ -588,19 +600,8 @@ public sealed partial class MainWindow
private void ApplyDoorTool(GridPosition position)
{
if (!m_Level.IsFloor(position))
return;
if (m_PendingDoorCell is not { } pending)
{
m_PendingDoorCell = position;
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
RefreshForecasts();
return;
}
m_Level = LevelEditor.SetDoorEdge(m_Level, pending, position);
m_PendingDoorCell = null;
ClearPendingEditorOperation();
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
RefreshForecasts();
}
@@ -641,42 +642,17 @@ public sealed partial class MainWindow
private void AddWarningRule_Click(object sender, RoutedEventArgs e)
{
var turn = m_Level.Global.Turn + 1;
m_Level = LevelEditor.AddRuleEvent(m_Level, new() {
Phase = ERuleEventPhase.EndOfTurn,
ForecastText = $"Warning on turn {turn}",
Predicates = [new() { Kind = ERulePredicateKind.TurnAtLeast, Turn = turn }],
Effects = [new() { Kind = ERuleEffectKind.EmitWarning, Message = $"Authored warning on turn {turn}" }]
});
RefreshForecasts();
RefreshInspector();
StatusText.Text = "Rule events were removed from level authoring.";
}
private void AddLeakRule_Click(object sender, RoutedEventArgs e)
{
if (m_SelectedCell is not { } position || !TryGetAuthoredLeakCarrier(position, out var carrier))
return;
var turn = m_Level.Global.Turn + 1;
m_Level = LevelEditor.AddRuleEvent(m_Level, new() {
Phase = ERuleEventPhase.EndOfTurn,
ForecastText = $"{carrier} leak starts on turn {turn}",
Predicates = [new() { Kind = ERulePredicateKind.TurnAtLeast, Turn = turn }],
Effects = [new() { Kind = ERuleEffectKind.StartLeak, Position = position, AccessPosition = position, Carrier = carrier }]
});
RefreshForecasts();
RefreshInspector();
StatusText.Text = "Rule events were removed from level authoring.";
}
private void RemoveLastRule_Click(object sender, RoutedEventArgs e)
{
var ruleEvent = m_Level.RuleEvents.LastOrDefault();
if (ruleEvent is null)
return;
m_Level = LevelEditor.RemoveRuleEvent(m_Level, ruleEvent.Id);
RefreshForecasts();
RefreshInspector();
StatusText.Text = "Rule events were removed from level authoring.";
}
private void BindSelectedConsumer(ECarrierType carrier)
@@ -684,9 +660,7 @@ public sealed partial class MainWindow
if (m_SelectedCell is not { } position || m_SelectedReactorId is not { } reactorId)
return;
m_Level = LevelEditor.BindReactorConsumer(m_Level, reactorId, carrier, position);
RefreshForecasts();
RefreshInspector();
StatusText.Text = "Reactors now use required consumer counts instead of bindings.";
}
private string CellInspectionText(GridPosition position)
@@ -698,7 +672,7 @@ public sealed partial class MainWindow
var electricity = m_Level.GetUnderground(position, ECarrierType.Electricity);
return $"Position: {position.X},{position.Y}\n"
+ $"Terrain: {m_Level.GetTerrain(position)}\n"
+ $"Prop: {prop.Type} {prop.Carrier} {prop.SwitchState} {prop.ServiceState}\n"
+ $"Prop: {prop.Type} {prop.SwitchState} {prop.ServiceState}\n"
+ $"Fuel: {UndergroundText(fuel)}\n"
+ $"Coolant: {UndergroundText(coolant)}\n"
+ $"Electricity: {UndergroundText(electricity)}\n"
@@ -708,9 +682,6 @@ public sealed partial class MainWindow
private string WorkflowInspectionText()
{
if (m_PendingDoorCell is { } door)
return $"Door edge: select an adjacent floor for {door.X},{door.Y}.";
if (m_PendingElectricityLeakCell is { } leak)
return $"Electricity leak face: select adjacent floor access for {leak.X},{leak.Y}.";
@@ -724,18 +695,14 @@ public sealed partial class MainWindow
return "Select or place a reactor control.";
return $"Reactor {reactor.ReactorId}\n"
+ $"Fuel: {PositionText(reactor.FuelConsumerPosition)}\n"
+ $"Coolant: {PositionText(reactor.CoolantConsumerPosition)}\n"
+ $"Electric: {PositionText(reactor.ElectricityConsumerPosition)}";
+ $"Control: {PositionText(reactor.ControlPosition)}\n"
+ $"Ready: {reactor.Ready}\n"
+ $"Required F/C/E: {m_Level.RequiredFuelConsumers}/{m_Level.RequiredCoolantConsumers}/{m_Level.RequiredElectricityConsumers}";
}
private string RuleEventInspectionText()
{
if (m_Level.RuleEvents.Count == 0)
return "No authored rule events.";
var last = m_Level.RuleEvents[^1];
return $"{m_Level.RuleEvents.Count} authored. Last: {last.Id} ({last.Phase}).";
return "Rule events were removed from level authoring.";
}
private static string PositionText(GridPosition position)
@@ -745,7 +712,7 @@ public sealed partial class MainWindow
private static string UndergroundText(UndergroundCell cell)
{
return $"{cell.State} amount {Format(cell.Amount)} intensity {Format(cell.Intensity)}";
return $"{cell.State} amount {Format(cell.Amount)} intensity {Format(cell.Intensity)} integrity {cell.StructuralIntegrity}";
}
private static string Format(float value)
@@ -762,26 +729,25 @@ public sealed partial class MainWindow
level = level.SetProp(new(2, 3), new() { Type = EPropType.Flow, Carrier = ECarrierType.Fuel });
level = level.SetProp(new(2, 5), new() { Type = EPropType.Flow, Carrier = ECarrierType.Coolant });
level = level.SetProp(new(2, 7), new() { Type = EPropType.Flow, Carrier = ECarrierType.Electricity });
level = level.SetProp(new(5, 3), new() { Type = EPropType.Consumer, Carrier = ECarrierType.Fuel });
level = level.SetProp(new(5, 5), new() { Type = EPropType.Consumer, Carrier = ECarrierType.Coolant });
level = level.SetProp(new(5, 7), new() { Type = EPropType.Consumer, Carrier = ECarrierType.Electricity });
level = level.SetProp(new(5, 3), new() { Type = EPropType.Consumer });
level = level.SetProp(new(5, 5), new() { Type = EPropType.Consumer });
level = level.SetProp(new(5, 7), new() { Type = EPropType.Consumer });
level = level.SetProp(new(10, 5), new() { Type = EPropType.ReactorControl, ReactorId = 1 });
level = level.SetProp(new(11, 4), new() { Type = EPropType.AllSeeingEyeTerminal });
level = level.SetProp(new(11, 6), new() { Type = EPropType.RemedySupply, RemedyType = ERemedyType.HeatShield });
level = level.SetUnderground(new(4, 5), ECarrierType.Coolant, new() { State = EUndergroundState.Leaking }) with {
Leaks = [new() { Carrier = ECarrierType.Coolant, UndergroundPosition = new(4, 5), AccessPosition = new(4, 5) }],
Doors = [new() { A = new(8, 5), B = new(9, 5), State = EDoorState.Closed }],
Robot = new() { Position = new(10, 5) },
Reactors = [
new() {
ReactorId = 1,
ControlPosition = new(10, 5),
FuelConsumerPosition = new(5, 3),
CoolantConsumerPosition = new(5, 5),
ElectricityConsumerPosition = new(5, 7)
ControlPosition = new(10, 5)
}
]
};
level = level.SetTerrain(new(8, 4), ECellTerrain.Wall);
level = level.SetTerrain(new(8, 6), ECellTerrain.Wall);
level = level.SetProp(new(8, 5), new() { Type = EPropType.Door });
return level with { Forecasts = new SimulationEngine().Forecast(level) };
}
@@ -860,7 +826,7 @@ public sealed partial class MainWindow
{
return prop.Type switch {
EPropType.Flow => $"{CarrierShort(prop.Carrier)} SRC",
EPropType.Consumer => $"{CarrierShort(prop.Carrier)} CON",
EPropType.Consumer => "CON",
EPropType.Junction => $"J {prop.JunctionMode}",
EPropType.Door => "DOOR",
EPropType.AllSeeingEyeTerminal => "EYE",
@@ -975,7 +941,6 @@ public sealed partial class MainWindow
private void ClearPendingEditorOperation()
{
m_PendingDoorCell = null;
m_PendingElectricityLeakCell = null;
}
@@ -1005,7 +970,6 @@ public sealed partial class MainWindow
private LevelState m_Level;
private double m_PanX;
private double m_PanY;
private GridPosition? m_PendingDoorCell;
private GridPosition? m_PendingElectricityLeakCell;
private CanvasBitmap? m_RobotSprite;
private GridPosition? m_SelectedCell;