Finish rewrite task list
This commit is contained in:
20
src/ReactorMaintenance.Simulation/EEditorTool.cs
Normal file
20
src/ReactorMaintenance.Simulation/EEditorTool.cs
Normal 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
|
||||
}
|
||||
8
src/ReactorMaintenance.Simulation/EditorToolCommand.cs
Normal file
8
src/ReactorMaintenance.Simulation/EditorToolCommand.cs
Normal 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; }
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user