Finish rewrite task list

This commit is contained in:
2026-05-10 22:35:25 +02:00
parent 5a186fb606
commit 3a52db0071
10 changed files with 575 additions and 175 deletions

View File

@@ -0,0 +1,20 @@
namespace ReactorMaintenance.Simulation;
public enum EEditorTool
{
Cursor,
Floor,
Wall,
Underground,
Flow,
Consumer,
Junction,
Door,
AllSeeingEyeTerminal,
RemedySupply,
ReactorControl,
Leak,
SurfaceHazard,
Heat,
Robot
}

View File

@@ -0,0 +1,8 @@
namespace ReactorMaintenance.Simulation;
public sealed record EditorToolCommand
{
public EEditorTool Tool { get; init; }
public ECarrierType Carrier { get; init; }
public ERemedyType RemedyType { get; init; }
}

View File

@@ -1,91 +1,92 @@
namespace ReactorMaintenance.Simulation;
public enum EEditorTool
{
Cursor,
Floor,
Wall,
FuelUnderground,
CoolantUnderground,
ElectricityUnderground,
FuelFlow,
CoolantFlow,
ElectricityFlow,
FuelConsumer,
CoolantConsumer,
ElectricityConsumer,
Junction,
Door,
AllSeeingEyeTerminal,
FuelRemedySupply,
CoolantRemedySupply,
ElectricityRemedySupply,
HeatRemedySupply,
ReactorControl,
FuelLeak,
CoolantLeak,
ElectricityLeak,
FuelHazard,
CoolantHazard,
ElectricityHazard,
Heat,
Robot
}
public static class LevelEditor
{
public static LevelState Apply(LevelState level, GridPosition position, EEditorTool tool)
public static LevelState Apply(LevelState level, GridPosition position, EditorToolCommand command)
{
if (!level.InBounds(position))
return level;
return tool switch {
return command.Tool switch {
EEditorTool.Cursor => level,
EEditorTool.Floor => level.SetTerrain(position, ECellTerrain.Floor),
EEditorTool.Wall => level.SetTerrain(position, ECellTerrain.Wall),
EEditorTool.FuelUnderground => SetUnderground(level, position, ECarrierType.Fuel),
EEditorTool.CoolantUnderground => SetUnderground(level, position, ECarrierType.Coolant),
EEditorTool.ElectricityUnderground => SetUnderground(level, position, ECarrierType.Electricity),
EEditorTool.FuelFlow => SetCarrierProp(level, position, EPropType.Flow, ECarrierType.Fuel),
EEditorTool.CoolantFlow => SetCarrierProp(level, position, EPropType.Flow, ECarrierType.Coolant),
EEditorTool.ElectricityFlow => SetCarrierProp(level, position, EPropType.Flow, ECarrierType.Electricity),
EEditorTool.FuelConsumer => SetCarrierProp(level, position, EPropType.Consumer, ECarrierType.Fuel),
EEditorTool.CoolantConsumer => SetCarrierProp(level, position, EPropType.Consumer, ECarrierType.Coolant),
EEditorTool.ElectricityConsumer => SetCarrierProp(level, position, EPropType.Consumer, ECarrierType.Electricity),
EEditorTool.Underground => SetUnderground(level, position, command.Carrier),
EEditorTool.Flow => SetCarrierProp(level, position, EPropType.Flow, command.Carrier),
EEditorTool.Consumer => SetCarrierProp(level, position, EPropType.Consumer, command.Carrier),
EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }),
EEditorTool.Door => SetDoor(level, position),
EEditorTool.Door => SetFloorProp(level, position, new() { Type = EPropType.Door }),
EEditorTool.AllSeeingEyeTerminal => SetFloorProp(level, position, new() { Type = EPropType.AllSeeingEyeTerminal }),
EEditorTool.FuelRemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = ERemedyType.FuelNeutralizer }),
EEditorTool.CoolantRemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = ERemedyType.CoolantNeutralizer }),
EEditorTool.ElectricityRemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = ERemedyType.ElectricityNeutralizer }),
EEditorTool.HeatRemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = ERemedyType.HeatShield }),
EEditorTool.RemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = command.RemedyType }),
EEditorTool.ReactorControl => SetReactorControl(level, position),
EEditorTool.FuelLeak => SetLeak(level, position, ECarrierType.Fuel),
EEditorTool.CoolantLeak => SetLeak(level, position, ECarrierType.Coolant),
EEditorTool.ElectricityLeak => SetLeak(level, position, ECarrierType.Electricity),
EEditorTool.FuelHazard => level.SetSurface(position, level.GetSurface(position) with { Fuel = level.GetSurface(position).Fuel + 1 }),
EEditorTool.CoolantHazard => level.SetSurface(position, level.GetSurface(position) with { Coolant = level.GetSurface(position).Coolant + 1 }),
EEditorTool.ElectricityHazard => level.SetSurface(position, level.GetSurface(position) with { Electricity = level.GetSurface(position).Electricity + 1 }),
EEditorTool.Leak => SetLeak(level, position, command.Carrier),
EEditorTool.SurfaceHazard => AddSurfaceHazard(level, position, command.Carrier),
EEditorTool.Heat => level.SetSurface(position, level.GetSurface(position) with { Heat = level.GetSurface(position).Heat + 1 }),
EEditorTool.Robot => level.IsFloor(position) ? level with { Robot = level.Robot with { Position = position } } : level,
_ => level
};
}
public static LevelState BindFirstReactorToConsumers(LevelState level, GridPosition fuelConsumer, GridPosition coolantConsumer, GridPosition electricityConsumer)
public static LevelState SetDoorEdge(LevelState level, GridPosition a, GridPosition b)
{
if (level.Reactors.Count == 0)
if (!level.IsFloor(a) || !level.IsFloor(b) || a.ManhattanDistance(b) != 1)
return level;
var reactors = level.Reactors.ToArray();
reactors[0] = reactors[0] with {
FuelConsumerPosition = fuelConsumer,
CoolantConsumerPosition = coolantConsumer,
ElectricityConsumerPosition = electricityConsumer
return level.SetProp(a, new() { Type = EPropType.Door }) with {
Doors = [
.. level.Doors.Where(door => !SameDoorEdge(door, a, b)),
new() { A = a, B = b, State = EDoorState.Closed }
]
};
}
public static LevelState SetLeak(LevelState level, GridPosition undergroundPosition, GridPosition accessPosition, ECarrierType carrier)
{
if (!level.InBounds(undergroundPosition) || !level.IsFloor(accessPosition))
return level;
if (carrier is ECarrierType.Fuel or ECarrierType.Coolant && undergroundPosition != accessPosition)
return level;
if (carrier == ECarrierType.Electricity && undergroundPosition.ManhattanDistance(accessPosition) != 1)
return level;
var next = level.SetUnderground(undergroundPosition, carrier, new() { State = EUndergroundState.Leaking });
return next with {
Leaks = [
.. next.Leaks.Where(leak => leak.UndergroundPosition != undergroundPosition || leak.Carrier != carrier),
new() {
Carrier = carrier,
UndergroundPosition = undergroundPosition,
AccessPosition = accessPosition
}
]
};
}
public static LevelState BindReactorConsumer(LevelState level, int reactorId, ECarrierType carrier, GridPosition consumerPosition)
{
if (!level.InBounds(consumerPosition) || level.GetProp(consumerPosition) is not { Type: EPropType.Consumer } prop || prop.Carrier != carrier)
return level;
var reactors = level.Reactors.Select(reactor => reactor.ReactorId == reactorId ? BindConsumer(reactor, carrier, consumerPosition) : reactor).ToArray();
return level with { Reactors = reactors };
}
public static LevelState AddRuleEvent(LevelState level, RuleEventState ruleEvent)
{
var id = string.IsNullOrWhiteSpace(ruleEvent.Id) ? NextRuleEventId(level) : ruleEvent.Id;
var authoredEvent = ruleEvent with { Id = id };
return level with {
RuleEvents = [.. level.RuleEvents.Where(existing => existing.Id != id), authoredEvent]
};
}
public static LevelState RemoveRuleEvent(LevelState level, string id)
{
return level with { RuleEvents = level.RuleEvents.Where(ruleEvent => ruleEvent.Id != id).ToArray() };
}
private static LevelState SetUnderground(LevelState level, GridPosition position, ECarrierType carrier)
{
return level.SetUnderground(position, carrier, new() { State = EUndergroundState.Intact });
@@ -96,25 +97,22 @@ public static class LevelEditor
return SetFloorProp(level, position, new() { Type = type, Carrier = carrier, SwitchState = EPropSwitchState.Enabled });
}
private static LevelState AddSurfaceHazard(LevelState level, GridPosition position, ECarrierType carrier)
{
var surface = level.GetSurface(position);
return carrier switch {
ECarrierType.Fuel => level.SetSurface(position, surface with { Fuel = surface.Fuel + 1 }),
ECarrierType.Coolant => level.SetSurface(position, surface with { Coolant = surface.Coolant + 1 }),
ECarrierType.Electricity => level.SetSurface(position, surface with { Electricity = surface.Electricity + 1 }),
_ => level
};
}
private static LevelState SetFloorProp(LevelState level, GridPosition position, PropState prop)
{
return level.IsFloor(position) ? level.SetProp(position, prop) : level;
}
private static LevelState SetDoor(LevelState level, GridPosition position)
{
if (!level.IsFloor(position))
return level;
var neighbor = position.Neighbors().FirstOrDefault(level.IsFloor);
if (neighbor is null)
return SetFloorProp(level, position, new() { Type = EPropType.Door });
return SetFloorProp(level, position, new() { Type = EPropType.Door }) with {
Doors = [.. level.Doors, new() { A = position, B = neighbor }]
};
}
private static LevelState SetReactorControl(LevelState level, GridPosition position)
{
if (!level.IsFloor(position))
@@ -141,23 +139,30 @@ public static class LevelEditor
if (!level.InBounds(position))
return level;
var accessPosition = carrier == ECarrierType.Electricity && level.GetTerrain(position) == ECellTerrain.Wall
? position.Neighbors().FirstOrDefault(level.IsFloor)
: position;
return SetLeak(level, position, position, carrier);
}
if (accessPosition is null || !level.IsFloor(accessPosition))
return level;
private static bool SameDoorEdge(DoorState door, GridPosition a, GridPosition b)
{
return door.A == a && door.B == b || door.A == b && door.B == a;
}
var next = level.SetUnderground(position, carrier, new() { State = EUndergroundState.Leaking });
return next with {
Leaks = [
.. next.Leaks,
new() {
Carrier = carrier,
UndergroundPosition = position,
AccessPosition = accessPosition
}
]
private static ReactorBinding BindConsumer(ReactorBinding reactor, ECarrierType carrier, GridPosition consumerPosition)
{
return carrier switch {
ECarrierType.Fuel => reactor with { FuelConsumerPosition = consumerPosition },
ECarrierType.Coolant => reactor with { CoolantConsumerPosition = consumerPosition },
ECarrierType.Electricity => reactor with { ElectricityConsumerPosition = consumerPosition },
_ => reactor
};
}
private static string NextRuleEventId(LevelState level)
{
var next = level.RuleEvents.Count + 1;
while (level.RuleEvents.Any(ruleEvent => ruleEvent.Id == $"rule-{next}"))
next++;
return $"rule-{next}";
}
}

View File

@@ -52,7 +52,7 @@
<TextBlock Text="Left click selects or paints. Right click clears surface prop and hazards."
Foreground="#9EA7AE" TextWrapping="Wrap" />
<TextBlock
Text="Door chooses the first adjacent floor edge. Reactor controls auto-bind to the first available consumers."
Text="Door and wall electricity leaks use two clicks: choose the source cell, then the adjacent floor face."
Foreground="#9EA7AE"
TextWrapping="Wrap" />
</StackPanel>
@@ -96,6 +96,34 @@
<TextBlock Text="Selected Cell" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<TextBlock x:Name="CellText" Foreground="#CCD4DA" TextWrapping="Wrap" />
<TextBlock Text="Editor Workflow" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<TextBlock x:Name="WorkflowText" Foreground="#CCD4DA" TextWrapping="Wrap" />
<TextBlock Text="Reactor Binding" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<TextBlock x:Name="ReactorBindingText" Foreground="#CCD4DA" TextWrapping="Wrap" />
<Grid ColumnSpacing="8" RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="Fuel" Click="BindFuel_Click" HorizontalAlignment="Stretch" />
<Button Grid.Column="1" Content="Coolant" Click="BindCoolant_Click" HorizontalAlignment="Stretch" />
<Button Grid.Column="2" Content="Electric" Click="BindElectricity_Click" HorizontalAlignment="Stretch" />
</Grid>
<TextBlock Text="Rule Events" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<TextBlock x:Name="RuleEventText" Foreground="#CCD4DA" TextWrapping="Wrap" />
<Grid ColumnSpacing="8" RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Content="Warn Next Turn" Click="AddWarningRule_Click" HorizontalAlignment="Stretch" />
<Button Grid.Column="1" Content="Leak Next Turn" Click="AddLeakRule_Click" HorizontalAlignment="Stretch" />
</Grid>
<Button Content="Remove Last Rule" Click="RemoveLastRule_Click" HorizontalAlignment="Stretch" />
<TextBlock Text="Forecasts" FontSize="16" FontWeight="SemiBold" Foreground="#F4F1E8" />
<ItemsControl x:Name="ForecastList">
<ItemsControl.ItemTemplate>
@@ -111,4 +139,4 @@
</ScrollViewer>
</Grid>
</Grid>
</Window>
</Window>

View File

@@ -28,9 +28,9 @@ public sealed partial class MainWindow
private sealed record ForecastViewModel(string Message);
private sealed class EditorToolViewModel(EEditorTool tool, string label)
private sealed class EditorToolViewModel(EditorToolCommand command, string label)
{
public EEditorTool Tool { get; } = tool;
public EditorToolCommand Command { get; } = command;
public string Label { get; } = label;
public bool IsSelected { get; set; }
}
@@ -40,7 +40,7 @@ public sealed partial class MainWindow
InitializeComponent();
m_Level = BuildStarterLevel();
m_EditorTools = Enum.GetValues<EEditorTool>().Select(tool => new EditorToolViewModel(tool, ToolLabel(tool)) { IsSelected = tool == m_SelectedTool }).ToArray();
m_EditorTools = BuildEditorTools();
ToolPicker.ItemsSource = m_EditorTools;
RefreshInspector();
}
@@ -69,9 +69,12 @@ public sealed partial class MainWindow
if ((sender as FrameworkElement)?.DataContext is not EditorToolViewModel tool)
return;
m_SelectedTool = tool.Tool;
m_SelectedTool = tool.Command;
ClearPendingEditorOperation();
foreach (var editorTool in m_EditorTools)
editorTool.IsSelected = editorTool == tool;
RefreshInspector();
}
private void New_Click(object sender, RoutedEventArgs e)
@@ -79,6 +82,7 @@ public sealed partial class MainWindow
m_Level = BuildStarterLevel();
m_CurrentFile = null;
m_SelectedCell = null;
ClearPendingEditorOperation();
RefreshInspector();
LevelCanvas.Invalidate();
}
@@ -100,6 +104,8 @@ public sealed partial class MainWindow
m_Level = loaded with { Forecasts = m_Simulation.Forecast(loaded) };
m_CurrentFile = file;
m_SelectedCell = null;
m_SelectedReactorId = m_Level.Reactors.FirstOrDefault()?.ReactorId;
ClearPendingEditorOperation();
RefreshInspector();
LevelCanvas.Invalidate();
}
@@ -247,11 +253,13 @@ public sealed partial class MainWindow
return;
m_SelectedCell = position;
if (m_SelectedTool != EEditorTool.Cursor)
if (m_SelectedTool.Tool != EEditorTool.Cursor)
{
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
m_Level = AutoBindReactors(m_Level);
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
ApplySelectedTool(position);
}
else
{
SelectReactorFromCell(position);
}
RefreshInspector();
@@ -264,6 +272,7 @@ public sealed partial class MainWindow
return;
m_SelectedCell = position;
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(),
@@ -478,6 +487,131 @@ public sealed partial class MainWindow
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();
WorkflowText.Text = WorkflowInspectionText();
ReactorBindingText.Text = ReactorBindingInspectionText();
RuleEventText.Text = RuleEventInspectionText();
}
private void ApplySelectedTool(GridPosition position)
{
switch (m_SelectedTool.Tool)
{
case EEditorTool.Door:
ApplyDoorTool(position);
break;
case EEditorTool.Leak when m_SelectedTool.Carrier == ECarrierType.Electricity:
ApplyElectricityLeakTool(position);
break;
default:
ClearPendingEditorOperation();
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
SelectReactorFromCell(position);
RefreshForecasts();
break;
}
}
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;
RefreshForecasts();
}
private void ApplyElectricityLeakTool(GridPosition position)
{
if (m_Level.GetTerrain(position) == ECellTerrain.Wall)
{
m_PendingElectricityLeakCell = position;
return;
}
if (m_PendingElectricityLeakCell is { } undergroundPosition)
{
m_Level = LevelEditor.SetLeak(m_Level, undergroundPosition, position, ECarrierType.Electricity);
m_PendingElectricityLeakCell = null;
RefreshForecasts();
return;
}
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
RefreshForecasts();
}
private void BindFuel_Click(object sender, RoutedEventArgs e)
{
BindSelectedConsumer(ECarrierType.Fuel);
}
private void BindCoolant_Click(object sender, RoutedEventArgs e)
{
BindSelectedConsumer(ECarrierType.Coolant);
}
private void BindElectricity_Click(object sender, RoutedEventArgs e)
{
BindSelectedConsumer(ECarrierType.Electricity);
}
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();
}
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();
}
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();
}
private void BindSelectedConsumer(ECarrierType carrier)
{
if (m_SelectedCell is not { } position || m_SelectedReactorId is not { } reactorId)
return;
m_Level = LevelEditor.BindReactorConsumer(m_Level, reactorId, carrier, position);
RefreshForecasts();
RefreshInspector();
}
private string CellInspectionText(GridPosition position)
@@ -497,6 +631,43 @@ public sealed partial class MainWindow
+ $"Blocks F/C/E: {surface.FuelBlockTurns}/{surface.CoolantBlockTurns}/{surface.ElectricityBlockTurns}";
}
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}.";
return "No pending editor operation.";
}
private string ReactorBindingInspectionText()
{
var reactor = m_SelectedReactorId is { } reactorId ? m_Level.Reactors.FirstOrDefault(candidate => candidate.ReactorId == reactorId) : null;
if (reactor is null)
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)}";
}
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}).";
}
private static string PositionText(GridPosition position)
{
return $"{position.X},{position.Y}";
}
private static string UndergroundText(UndergroundCell cell)
{
return $"{cell.State} amount {Format(cell.Amount)} intensity {Format(cell.Intensity)}";
@@ -555,28 +726,6 @@ public sealed partial class MainWindow
return level;
}
private static LevelState AutoBindReactors(LevelState level)
{
if (level.Reactors.Count == 0)
return level;
var fuel = FirstConsumer(level, ECarrierType.Fuel);
var coolant = FirstConsumer(level, ECarrierType.Coolant);
var electricity = FirstConsumer(level, ECarrierType.Electricity);
var reactors = level.Reactors.Select(reactor => reactor with {
FuelConsumerPosition = fuel ?? reactor.FuelConsumerPosition,
CoolantConsumerPosition = coolant ?? reactor.CoolantConsumerPosition,
ElectricityConsumerPosition = electricity ?? reactor.ElectricityConsumerPosition
})
.ToArray();
return level with { Reactors = reactors };
}
private static GridPosition? FirstConsumer(LevelState level, ECarrierType carrier)
{
return AllPositions(level).FirstOrDefault(position => level.GetProp(position) is { Type: EPropType.Consumer, Carrier: var propCarrier } && propCarrier == carrier);
}
private IEnumerable<GridPosition> AllPositions()
{
return AllPositions(m_Level);
@@ -667,29 +816,92 @@ public sealed partial class MainWindow
};
}
private static string ToolLabel(EEditorTool tool)
private static IReadOnlyList<EditorToolViewModel> BuildEditorTools()
{
return tool switch {
EEditorTool.FuelUnderground => "Fuel Net",
EEditorTool.CoolantUnderground => "Coolant Net",
EEditorTool.ElectricityUnderground => "Electric Net",
EEditorTool.FuelFlow => "Fuel Source",
EEditorTool.CoolantFlow => "Coolant Source",
EEditorTool.ElectricityFlow => "Electric Source",
EEditorTool.FuelConsumer => "Fuel Consumer",
EEditorTool.CoolantConsumer => "Coolant Consumer",
EEditorTool.ElectricityConsumer => "Electric Consumer",
EEditorTool.AllSeeingEyeTerminal => "Eye Terminal",
EEditorTool.FuelRemedySupply => "Fuel Remedy",
EEditorTool.CoolantRemedySupply => "Coolant Remedy",
EEditorTool.ElectricityRemedySupply => "Electric Remedy",
EEditorTool.HeatRemedySupply => "Heat Shield",
EEditorTool.ReactorControl => "Reactor",
EEditorTool.FuelHazard => "Fuel Hazard",
EEditorTool.CoolantHazard => "Coolant Hazard",
EEditorTool.ElectricityHazard => "Electric Hazard",
_ => tool.ToString()
};
EditorToolViewModel Tool(EEditorTool tool, string label)
{
return new(new() { Tool = tool }, label) { IsSelected = tool == EEditorTool.Cursor };
}
EditorToolViewModel CarrierTool(EEditorTool tool, ECarrierType carrier, string label)
{
return new(new() { Tool = tool, Carrier = carrier }, label);
}
EditorToolViewModel RemedyTool(ERemedyType remedy, string label)
{
return new(new() { Tool = EEditorTool.RemedySupply, RemedyType = remedy }, label);
}
return [
Tool(EEditorTool.Cursor, "Cursor"),
Tool(EEditorTool.Floor, "Floor"),
Tool(EEditorTool.Wall, "Wall"),
CarrierTool(EEditorTool.Underground, ECarrierType.Fuel, "Fuel Net"),
CarrierTool(EEditorTool.Underground, ECarrierType.Coolant, "Coolant Net"),
CarrierTool(EEditorTool.Underground, ECarrierType.Electricity, "Electric Net"),
CarrierTool(EEditorTool.Flow, ECarrierType.Fuel, "Fuel Source"),
CarrierTool(EEditorTool.Flow, ECarrierType.Coolant, "Coolant Source"),
CarrierTool(EEditorTool.Flow, ECarrierType.Electricity, "Electric Source"),
CarrierTool(EEditorTool.Consumer, ECarrierType.Fuel, "Fuel Consumer"),
CarrierTool(EEditorTool.Consumer, ECarrierType.Coolant, "Coolant Consumer"),
CarrierTool(EEditorTool.Consumer, ECarrierType.Electricity, "Electric Consumer"),
Tool(EEditorTool.Junction, "Junction"),
Tool(EEditorTool.Door, "Door"),
Tool(EEditorTool.AllSeeingEyeTerminal, "Eye Terminal"),
RemedyTool(ERemedyType.FuelNeutralizer, "Fuel Remedy"),
RemedyTool(ERemedyType.CoolantNeutralizer, "Coolant Remedy"),
RemedyTool(ERemedyType.ElectricityNeutralizer, "Electric Remedy"),
RemedyTool(ERemedyType.HeatShield, "Heat Shield"),
Tool(EEditorTool.ReactorControl, "Reactor"),
CarrierTool(EEditorTool.Leak, ECarrierType.Fuel, "Fuel Leak"),
CarrierTool(EEditorTool.Leak, ECarrierType.Coolant, "Coolant Leak"),
CarrierTool(EEditorTool.Leak, ECarrierType.Electricity, "Electric Leak"),
CarrierTool(EEditorTool.SurfaceHazard, ECarrierType.Fuel, "Fuel Hazard"),
CarrierTool(EEditorTool.SurfaceHazard, ECarrierType.Coolant, "Coolant Hazard"),
CarrierTool(EEditorTool.SurfaceHazard, ECarrierType.Electricity, "Electric Hazard"),
Tool(EEditorTool.Heat, "Heat"),
Tool(EEditorTool.Robot, "Robot")
];
}
private void SelectReactorFromCell(GridPosition position)
{
var prop = m_Level.GetProp(position);
if (prop is { Type: EPropType.ReactorControl, ReactorId: > 0 })
m_SelectedReactorId = prop.ReactorId;
}
private bool TryGetAuthoredLeakCarrier(GridPosition position, out ECarrierType carrier)
{
if (!m_Level.IsFloor(position))
{
carrier = default;
return false;
}
foreach (var candidate in Enum.GetValues<ECarrierType>())
{
if (m_Level.GetUnderground(position, candidate).IsPresent)
{
carrier = candidate;
return true;
}
}
carrier = default;
return false;
}
private void RefreshForecasts()
{
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
}
private void ClearPendingEditorOperation()
{
m_PendingDoorCell = null;
m_PendingElectricityLeakCell = null;
}
private const double c_MinZoom = 0.5;
@@ -714,7 +926,10 @@ public sealed partial class MainWindow
private double m_PanY;
private CanvasBitmap? m_RobotSprite;
private GridPosition? m_SelectedCell;
private EEditorTool m_SelectedTool = EEditorTool.Cursor;
private int? m_SelectedReactorId = 1;
private GridPosition? m_PendingDoorCell;
private GridPosition? m_PendingElectricityLeakCell;
private EditorToolCommand m_SelectedTool = new() { Tool = EEditorTool.Cursor };
private CanvasBitmap? m_TerrainTilemap;
private double m_Zoom = 1;
}