Files
zfxaction26_2/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs
2026-05-09 02:41:05 +02:00

739 lines
28 KiB
C#

using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.UI;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Imaging;
using ReactorMaintenance.Simulation;
using System.Globalization;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.System;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Popups;
using WinRT.Interop;
namespace ReactorMaintenance.Win2D;
public sealed partial class MainWindow
{
private sealed record CanvasLayout(double CellSize, double OriginX, double OriginY)
{
public Rect CellRect(int x, int y)
{
return new(OriginX + (x * CellSize), OriginY + (y * CellSize), CellSize, CellSize);
}
public Rect DualTileRect(int x, int y)
{
return new(OriginX + ((x - 0.5) * CellSize), OriginY + ((y - 0.5) * CellSize), CellSize, CellSize);
}
}
private sealed record ForecastViewModel(BitmapImage Icon, string Message);
private sealed class EditorToolViewModel(EEditorTool tool, BitmapImage? icon, string label)
{
public EEditorTool Tool { get; } = tool;
public BitmapImage? Icon { get; } = icon;
public string Label { get; } = label;
public bool IsSelected { get; set; }
}
public MainWindow()
{
InitializeComponent();
m_Level = BuildStarterLevel();
m_EditorTools = Enum.GetValues<EEditorTool>().Select(tool => new EditorToolViewModel(tool, EditorToolIcon(tool), tool.ToString()) { IsSelected = tool == m_SelectedTool }).ToArray();
ToolPicker.ItemsSource = m_EditorTools;
RefreshInspector();
}
private void LevelCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(LoadImagesAsync(sender).AsAsyncAction());
}
private async Task LoadImagesAsync(CanvasControl sender)
{
m_TerrainTilemap = await LoadCanvasBitmapAsync(sender, "Images", "tilemap.png");
m_RobotSprite = await LoadCanvasBitmapAsync(sender, "Images", "Props", "robot.png");
m_LeakSprite = await LoadCanvasBitmapAsync(sender, "Images", "Props", "leak.png");
m_HeatSprite = await LoadCanvasBitmapAsync(sender, "Images", "Props", "heat.png");
m_FireSprite = await LoadCanvasBitmapAsync(sender, "Images", "Props", "fire.png");
m_PropSprites[ECellProp.Reactor] = await LoadCanvasBitmapAsync(sender, "Images", "Props", "reactor.png");
m_PropSprites[ECellProp.CoolingPump] = await LoadCanvasBitmapAsync(sender, "Images", "Props", "cooling-pump.png");
m_PropSprites[ECellProp.Generator] = await LoadCanvasBitmapAsync(sender, "Images", "Props", "generator.png");
m_PropSprites[ECellProp.PressureRegulator] = await LoadCanvasBitmapAsync(sender, "Images", "Props", "pressure-regulator.png");
m_PropSprites[ECellProp.DiagnosticTerminal] = await LoadCanvasBitmapAsync(sender, "Images", "Props", "diagnostic-terminal.png");
m_PropSprites[ECellProp.ControlTerminal] = await LoadCanvasBitmapAsync(sender, "Images", "Props", "control-terminal.png");
m_PipeTilemaps[EPipeMedium.Pressure] = await LoadCanvasBitmapAsync(sender, "Images", "Pipes", "pipe-pressure-tilemap.png");
m_PipeTilemaps[EPipeMedium.Coolant] = await LoadCanvasBitmapAsync(sender, "Images", "Pipes", "pipe-coolant-tilemap.png");
m_PipeTilemaps[EPipeMedium.Fuel] = await LoadCanvasBitmapAsync(sender, "Images", "Pipes", "pipe-fuel-tilemap.png");
}
private static async Task<CanvasBitmap> LoadCanvasBitmapAsync(CanvasControl sender, params string[] pathParts)
{
var path = Path.Combine([AppContext.BaseDirectory, .. pathParts]);
return await CanvasBitmap.LoadAsync(sender, path);
}
private void ToolRadio_Checked(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.DataContext is EditorToolViewModel tool)
{
m_SelectedTool = tool.Tool;
foreach (var editorTool in m_EditorTools)
editorTool.IsSelected = editorTool == tool;
}
}
private void New_Click(object sender, RoutedEventArgs e)
{
m_Level = BuildStarterLevel();
m_CurrentFile = null;
m_SelectedCell = null;
RefreshInspector();
LevelCanvas.Invalidate();
}
private async void Open_Click(object sender, RoutedEventArgs args)
{
try
{
var picker = new FileOpenPicker();
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
picker.FileTypeFilter.Add(".json");
var file = await picker.PickSingleFileAsync();
if (file is null)
return;
var json = await FileIO.ReadTextAsync(file);
m_Level = LevelSerializer.Deserialize(json);
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
m_CurrentFile = file;
m_SelectedCell = null;
RefreshInspector();
LevelCanvas.Invalidate();
}
catch (Exception e)
{
var messageDialog = new MessageDialog(e.Message);
_ = await messageDialog.ShowAsync();
}
}
private async void Save_Click(object sender, RoutedEventArgs args)
{
try
{
var file = m_CurrentFile;
if (file is null)
{
var picker = new FileSavePicker();
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
picker.SuggestedFileName = m_Level.Name.Replace(' ', '-').ToLowerInvariant();
picker.FileTypeChoices.Add("Reactor level", [".json"]);
file = await picker.PickSaveFileAsync();
}
if (file is null)
return;
await FileIO.WriteTextAsync(file, LevelSerializer.Serialize(m_Level));
m_CurrentFile = file;
}
catch (Exception e)
{
var messageDialog = new MessageDialog(e.Message);
_ = await messageDialog.ShowAsync();
}
}
private void Simulate_Click(object sender, RoutedEventArgs e)
{
m_Level = m_Simulation.AdvanceTurn(m_Level);
RefreshInspector();
LevelCanvas.Invalidate();
}
private void Activate_Click(object sender, RoutedEventArgs e)
{
m_Level = m_Simulation.ActivateReactor(m_Level);
RefreshInspector();
LevelCanvas.Invalidate();
}
private void LevelCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(LevelCanvas);
if (point.Properties.IsLeftButtonPressed && IsShiftDown())
{
m_Panning = true;
m_LastPanPoint = point.Position;
_ = LevelCanvas.CapturePointer(e.Pointer);
e.Handled = true;
return;
}
if (point.Properties.IsRightButtonPressed)
{
RemovePropAt(point.Position);
e.Handled = true;
return;
}
if (point.Properties.IsLeftButtonPressed)
{
_ = LevelCanvas.CapturePointer(e.Pointer);
SelectOrPaintAt(point.Position);
m_Painting = m_SelectedTool != EEditorTool.Cursor;
e.Handled = true;
}
}
private void LevelCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(LevelCanvas);
if (m_Panning)
{
var deltaX = point.Position.X - m_LastPanPoint.X;
var deltaY = point.Position.Y - m_LastPanPoint.Y;
m_LastPanPoint = point.Position;
m_PanX += deltaX;
m_PanY += deltaY;
ClampPan();
LevelCanvas.Invalidate();
e.Handled = true;
return;
}
if (m_Painting)
{
PaintAt(point.Position);
e.Handled = true;
}
}
private void LevelCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)
{
m_Painting = false;
m_Panning = false;
LevelCanvas.ReleasePointerCapture(e.Pointer);
e.Handled = true;
}
private void LevelCanvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var point = e.GetCurrentPoint(LevelCanvas);
var wheelDelta = point.Properties.MouseWheelDelta;
if (wheelDelta == 0)
return;
ZoomAt(point.Position, wheelDelta > 0 ? c_ZoomStep : 1 / c_ZoomStep);
e.Handled = true;
}
private static bool IsShiftDown()
{
return InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(CoreVirtualKeyStates.Down);
}
private void ZoomAt(Point point, double zoomFactor)
{
var oldLayout = GetLayout();
var cellX = (point.X - oldLayout.OriginX) / oldLayout.CellSize;
var cellY = (point.Y - oldLayout.OriginY) / oldLayout.CellSize;
m_Zoom = Math.Clamp(m_Zoom * zoomFactor, c_MinZoom, c_MaxZoom);
var newCellSize = GetBaseCellSize() * m_Zoom;
var originWithoutPan = GetCenteredOrigin(newCellSize);
m_PanX = point.X - originWithoutPan.X - (cellX * newCellSize);
m_PanY = point.Y - originWithoutPan.Y - (cellY * newCellSize);
ClampPan();
LevelCanvas.Invalidate();
}
private void SelectOrPaintAt(Point point)
{
if (m_SelectedTool == EEditorTool.Cursor)
SelectAt(point);
else
PaintAt(point);
}
private void SelectAt(Point point)
{
if (!TryGetGridPosition(point, out var position))
return;
m_SelectedCell = position;
RefreshInspector();
LevelCanvas.Invalidate();
}
private void RemovePropAt(Point point)
{
if (!TryGetGridPosition(point, out var position))
return;
var cell = m_Level.GetCell(position);
m_SelectedCell = position;
m_Level = m_Level.SetCell(position, cell with { Prop = ECellProp.None });
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
RefreshInspector();
LevelCanvas.Invalidate();
}
private void PaintAt(Point point)
{
if (!TryGetGridPosition(point, out var position))
return;
m_SelectedCell = position;
m_Level = LevelEditor.Apply(m_Level, position, m_SelectedTool);
m_Level = m_Level with { Forecasts = m_Simulation.Forecast(m_Level) };
RefreshInspector();
LevelCanvas.Invalidate();
}
private void LevelCanvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
var drawing = args.DrawingSession;
var layout = GetLayout();
drawing.Clear(ColorHelper.FromArgb(255, 16, 18, 21));
DrawTerrain(drawing, layout);
DrawCellOverlays(drawing, layout);
DrawGrid(drawing, layout);
DrawRobot(drawing, layout);
}
private void DrawTerrain(CanvasDrawingSession drawing, CanvasLayout layout)
{
for (var y = 0; y <= m_Level.Height; y++)
{
for (var x = 0; x <= m_Level.Width; x++)
{
DrawDualTerrainTile(drawing, layout.DualTileRect(x, y), GetDualTileMask(x, y));
}
}
}
private void DrawCellOverlays(CanvasDrawingSession drawing, CanvasLayout layout)
{
for (var y = 0; y < m_Level.Height; y++)
{
for (var x = 0; x < m_Level.Width; x++)
{
var position = new GridPosition(x, y);
var cell = m_Level.GetCell(position);
var rect = layout.CellRect(x, y);
DrawPipe(drawing, position, cell, rect);
if (cell.LeakRate > 0)
DrawImage(drawing, m_LeakSprite, Inset(rect, 0.12));
if (cell.Hazards.Heat > 0)
DrawImage(drawing, m_HeatSprite, Inset(rect, 0.08), Math.Clamp(cell.Hazards.Heat / 10.0f, 0.35f, 0.9f));
if (cell.Hazards.Fire)
DrawImage(drawing, m_FireSprite, Inset(rect, 0.08));
if (m_SelectedCell == position)
drawing.DrawRectangle(rect, Colors.White, 3);
DrawCellProp(drawing, cell, rect);
}
}
}
private void DrawPipe(CanvasDrawingSession drawing, GridPosition position, CellState cell, Rect rect)
{
if (!cell.HasPipe || !m_PipeTilemaps.TryGetValue(cell.Pipe, out var tilemap))
return;
var sourceRect = PipeTileSourceRect(GetPipeConnectionMask(position, cell.Pipe));
drawing.DrawImage(tilemap, rect, sourceRect);
}
private int GetPipeConnectionMask(GridPosition position, EPipeMedium medium)
{
var mask = 0;
if (HasMatchingPipe(position with { Y = position.Y - 1 }, medium))
mask |= c_NorthConnection;
if (HasMatchingPipe(position with { X = position.X + 1 }, medium))
mask |= c_EastConnection;
if (HasMatchingPipe(position with { Y = position.Y + 1 }, medium))
mask |= c_SouthConnection;
if (HasMatchingPipe(position with { X = position.X - 1 }, medium))
mask |= c_WestConnection;
return mask;
}
private bool HasMatchingPipe(GridPosition position, EPipeMedium medium)
{
return m_Level.InBounds(position) && m_Level.GetCell(position).Pipe == medium;
}
private static Rect PipeTileSourceRect(int connectionMask)
{
var tileIndex = connectionMask switch {
0 => 0,
c_NorthConnection => 1,
c_EastConnection => 2,
c_SouthConnection => 3,
c_WestConnection => 4,
c_NorthConnection | c_EastConnection => 5,
c_EastConnection | c_SouthConnection => 6,
c_SouthConnection | c_WestConnection => 7,
c_WestConnection | c_NorthConnection => 8,
c_NorthConnection | c_SouthConnection => 9,
c_EastConnection | c_WestConnection => 10,
c_NorthConnection | c_EastConnection | c_SouthConnection => 11,
c_EastConnection | c_SouthConnection | c_WestConnection => 12,
c_SouthConnection | c_WestConnection | c_NorthConnection => 13,
c_WestConnection | c_NorthConnection | c_EastConnection => 14,
c_NorthConnection | c_EastConnection | c_SouthConnection | c_WestConnection => 15,
_ => throw new ArgumentOutOfRangeException(nameof(connectionMask), connectionMask, "Unsupported pipe connection mask.")
};
return new(
tileIndex % c_PipeTilemapColumns * c_PipeTilemapTileSize,
tileIndex / c_PipeTilemapColumns * c_PipeTilemapTileSize,
c_PipeTilemapTileSize,
c_PipeTilemapTileSize);
}
private static void DrawImage(CanvasDrawingSession drawing, CanvasBitmap? image, Rect rect, float opacity = 1)
{
if (image is not null)
drawing.DrawImage(image, rect, image.Bounds, opacity);
}
private static Rect Inset(Rect rect, double fraction)
{
var inset = rect.Width * fraction;
return new(rect.X + inset, rect.Y + inset, rect.Width - inset * 2, rect.Height - inset * 2);
}
private void DrawDualTerrainTile(CanvasDrawingSession drawing, Rect rect, int floorMask)
{
if (m_TerrainTilemap is null)
return;
var wallMask = c_AllCorners ^ floorMask;
var sourceRect = TilemapSourceRect(wallMask);
drawing.DrawImage(m_TerrainTilemap, rect, sourceRect);
}
private static Rect TilemapSourceRect(int wallMask)
{
var tilePosition = wallMask switch {
c_BottomLeftCorner => new(0, 0),
c_TopRightCorner | c_BottomRightCorner => new(1, 0),
c_TopLeftCorner | c_BottomLeftCorner | c_BottomRightCorner => new(2, 0),
c_BottomLeftCorner | c_BottomRightCorner => new(3, 0),
c_TopLeftCorner | c_BottomRightCorner => new(0, 1),
c_BottomLeftCorner | c_TopRightCorner | c_BottomRightCorner => new(1, 1),
c_AllCorners => new(2, 1),
c_TopLeftCorner | c_BottomLeftCorner | c_TopRightCorner => new(3, 1),
c_TopRightCorner => new(0, 2),
c_TopLeftCorner | c_TopRightCorner => new(1, 2),
c_TopLeftCorner | c_TopRightCorner | c_BottomRightCorner => new(2, 2),
c_BottomLeftCorner | c_TopLeftCorner => new(3, 2),
0 => new(0, 3),
c_BottomRightCorner => new(1, 3),
c_BottomLeftCorner | c_TopRightCorner => new(2, 3),
c_TopLeftCorner => new GridPosition(3, 3),
_ => throw new ArgumentOutOfRangeException(nameof(wallMask), wallMask, "Unsupported tile mask.")
};
return new(
tilePosition.X * c_TilemapTileSize,
tilePosition.Y * c_TilemapTileSize,
c_TilemapTileSize,
c_TilemapTileSize);
}
private int GetDualTileMask(int x, int y)
{
var mask = 0;
if (GetTerrainOrWall(x - 1, y - 1) == ECellTerrain.Floor)
mask |= c_TopLeftCorner;
if (GetTerrainOrWall(x, y - 1) == ECellTerrain.Floor)
mask |= c_TopRightCorner;
if (GetTerrainOrWall(x - 1, y) == ECellTerrain.Floor)
mask |= c_BottomLeftCorner;
if (GetTerrainOrWall(x, y) == ECellTerrain.Floor)
mask |= c_BottomRightCorner;
return mask;
}
private ECellTerrain GetTerrainOrWall(int x, int y)
{
var position = new GridPosition(x, y);
return m_Level.InBounds(position) ? m_Level.GetCell(position).Terrain : ECellTerrain.Wall;
}
private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect)
{
if (m_PropSprites.TryGetValue(cell.Prop, out var sprite))
drawing.DrawImage(sprite, rect, sprite.Bounds);
}
private void DrawGrid(CanvasDrawingSession drawing, CanvasLayout layout)
{
for (var x = 0; x <= m_Level.Width; x++)
{
var xPos = (float)(layout.OriginX + (x * layout.CellSize));
drawing.DrawLine(xPos, (float)layout.OriginY, xPos, (float)(layout.OriginY + (m_Level.Height * layout.CellSize)), ColorHelper.FromArgb(120, 91, 104, 115), 1);
}
for (var y = 0; y <= m_Level.Height; y++)
{
var yPos = (float)(layout.OriginY + (y * layout.CellSize));
drawing.DrawLine((float)layout.OriginX, yPos, (float)(layout.OriginX + (m_Level.Width * layout.CellSize)), yPos, ColorHelper.FromArgb(120, 91, 104, 115), 1);
}
}
private void DrawRobot(CanvasDrawingSession drawing, CanvasLayout layout)
{
var rect = layout.CellRect(m_Level.Robot.X, m_Level.Robot.Y);
DrawImage(drawing, m_RobotSprite, rect);
}
private bool TryGetGridPosition(Point point, out GridPosition position)
{
var layout = GetLayout();
var x = (int)((point.X - layout.OriginX) / layout.CellSize);
var y = (int)((point.Y - layout.OriginY) / layout.CellSize);
position = new(x, y);
return m_Level.InBounds(position);
}
private CanvasLayout GetLayout()
{
ClampPan();
var cellSize = GetBaseCellSize() * m_Zoom;
var centeredOrigin = GetCenteredOrigin(cellSize);
return new(cellSize, centeredOrigin.X + m_PanX, centeredOrigin.Y + m_PanY);
}
private double GetBaseCellSize()
{
var availableWidth = Math.Max(1, LevelCanvas.ActualWidth);
var availableHeight = Math.Max(1, LevelCanvas.ActualHeight);
var size = Math.Floor(Math.Min(availableWidth / m_Level.Width, availableHeight / m_Level.Height));
return Math.Max(20, size);
}
private Point GetCenteredOrigin(double cellSize)
{
var availableWidth = Math.Max(1, LevelCanvas.ActualWidth);
var availableHeight = Math.Max(1, LevelCanvas.ActualHeight);
return new((availableWidth - (cellSize * m_Level.Width)) / 2, (availableHeight - (cellSize * m_Level.Height)) / 2);
}
private void ClampPan()
{
var cellSize = GetBaseCellSize() * m_Zoom;
var contentWidth = cellSize * m_Level.Width;
var contentHeight = cellSize * m_Level.Height;
var availableWidth = Math.Max(1, LevelCanvas.ActualWidth);
var availableHeight = Math.Max(1, LevelCanvas.ActualHeight);
m_PanX = ClampAxisPan(m_PanX, contentWidth, availableWidth);
m_PanY = ClampAxisPan(m_PanY, contentHeight, availableHeight);
}
private static double ClampAxisPan(double pan, double contentSize, double availableSize)
{
if (contentSize <= availableSize)
return 0;
var maxPan = (contentSize - availableSize) / 2;
return Math.Clamp(pan, -maxPan, maxPan);
}
private void RefreshInspector()
{
LevelNameText.Text = m_Level.Name;
TurnText.Text = m_Level.Global.Turn.ToString(CultureInfo.InvariantCulture);
StatusText.Text = m_Level.Global.Status;
GlobalText.Text = $"Power: {m_Level.Global.Power}/10\n" + $"Cooling: {m_Level.Global.Cooling}/10\n" + $"Core Heat: {m_Level.Global.CoreHeat}/10\n" + $"Facility Stability: {m_Level.Global.FacilityStability}/10";
if (m_SelectedCell is { } position && m_Level.InBounds(position))
{
var cell = m_Level.GetCell(position);
CellText.Text = $"Position: {position.X},{position.Y}\n" + $"Terrain: {cell.Terrain}\n" + $"Prop: {cell.Prop}\n" + $"Pipe: {cell.Pipe}\n" + $"Flow: {cell.Flow}, Pressure: {cell.Pressure}\n" + $"Integrity: {cell.Integrity}, Leak: {cell.LeakRate}\n" + $"Heat: {cell.Hazards.Heat}, Smoke: {cell.Hazards.Smoke}\n" + $"Fuel Vapor: {cell.Hazards.FuelVapor}, Fuel: {cell.Hazards.LiquidFuel}\n" + $"Coolant: {cell.Hazards.CoolantPooling}, Charge: {cell.Hazards.ElectricalCharge}";
}
else
CellText.Text = "No cell selected.";
ForecastList.ItemsSource = m_Level.Forecasts.Select(forecast => new ForecastViewModel(FailureIcon(forecast.Kind), forecast.Message)).ToArray();
}
private static BitmapImage FailureIcon(EFailureKind kind)
{
return ImageFromOutputPath("Images", "Failures", FailureIconFileName(kind));
}
private static string FailureIconFileName(EFailureKind kind)
{
return kind switch {
EFailureKind.PipeBurst => "failure-pipe-burst.png",
EFailureKind.Ignition => "failure-ignition.png",
EFailureKind.Meltdown => "failure-meltdown.png",
EFailureKind.StabilityCollapse => "failure-stability-collapse.png",
EFailureKind.ReactorReady => "failure-reactor-ready.png",
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unsupported failure kind.")
};
}
private static BitmapImage? EditorToolIcon(EEditorTool tool)
{
return tool switch {
EEditorTool.Cursor => PropImage("cursor.png"),
EEditorTool.Floor => PropImage("floor.png"),
EEditorTool.Wall => PropImage("wall.png"),
EEditorTool.Reactor => PropImage("reactor.png"),
EEditorTool.CoolingPump => PropImage("cooling-pump.png"),
EEditorTool.Generator => PropImage("generator.png"),
EEditorTool.PressureRegulator => PropImage("pressure-regulator.png"),
EEditorTool.DiagnosticTerminal => PropImage("diagnostic-terminal.png"),
EEditorTool.ControlTerminal => PropImage("control-terminal.png"),
EEditorTool.CoolantPipe => PipeImage("pipe-coolant-tilemap.png"),
EEditorTool.FuelPipe => PipeImage("pipe-fuel-tilemap.png"),
EEditorTool.PressurePipe => PipeImage("pipe-pressure-tilemap.png"),
EEditorTool.Leak => PropImage("leak.png"),
EEditorTool.Repair => PropImage("repair.png"),
EEditorTool.Heat => PropImage("heat.png"),
EEditorTool.Fire => PropImage("fire.png"),
EEditorTool.Robot => PropImage("robot.png"),
_ => throw new ArgumentOutOfRangeException(nameof(tool), tool, "Unsupported editor tool.")
};
}
private static BitmapImage PropImage(string fileName)
{
return ImageFromOutputPath("Images", "Props", fileName);
}
private static BitmapImage PipeImage(string fileName)
{
return ImageFromOutputPath("Images", "Pipes", fileName);
}
private static BitmapImage ImageFromOutputPath(params string[] pathParts)
{
return new(new(Path.Combine([AppContext.BaseDirectory, .. pathParts])));
}
private static LevelState BuildStarterLevel()
{
var level = LevelState.Create("Cooling Sector B", 16, 12);
level = level.SetCell(new(3, 5), new() {
Prop = ECellProp.CoolingPump,
Pipe = EPipeMedium.Coolant,
Flow = 5,
Pressure = 5,
Powered = true
});
level = level.SetCell(new(4, 5), new() {
Pipe = EPipeMedium.Coolant,
Flow = 5,
Pressure = 7
});
level = level.SetCell(new(5, 5), new() {
Pipe = EPipeMedium.Coolant,
Flow = 3,
Pressure = 8,
LeakRate = 2,
Integrity = 4
});
level = level.SetCell(new(6, 5), new() {
Pipe = EPipeMedium.Coolant,
Flow = 3,
Pressure = 7
});
level = level.SetCell(new(8, 5), new() {
Prop = ECellProp.Reactor,
Hazards = new() {
Heat = 6,
Stability = 8
}
});
level = level.SetCell(new(2, 8), new() {
Prop = ECellProp.Generator,
Pipe = EPipeMedium.Fuel,
Flow = 4,
Pressure = 6,
Powered = true
});
level = level.SetCell(new(11, 4), new() {
Prop = ECellProp.DiagnosticTerminal,
Powered = true
});
level = level.SetCell(new(12, 8), new() {
Prop = ECellProp.ControlTerminal,
Powered = true
});
return level with { Forecasts = new SimulationEngine().Forecast(level) };
}
private const int c_TilemapTileSize = 512;
private const int c_PipeTilemapTileSize = 256;
private const int c_PipeTilemapColumns = 4;
private const int c_TopLeftCorner = 1;
private const int c_TopRightCorner = 2;
private const int c_BottomLeftCorner = 4;
private const int c_BottomRightCorner = 8;
private const int c_AllCorners = c_TopLeftCorner | c_TopRightCorner | c_BottomLeftCorner | c_BottomRightCorner;
private const int c_NorthConnection = 1;
private const int c_EastConnection = 2;
private const int c_SouthConnection = 4;
private const int c_WestConnection = 8;
private const double c_MinZoom = 0.5;
private const double c_MaxZoom = 4;
private const double c_ZoomStep = 1.15;
private readonly SimulationEngine m_Simulation = new();
private readonly Dictionary<ECellProp, CanvasBitmap> m_PropSprites = [];
private readonly Dictionary<EPipeMedium, CanvasBitmap> m_PipeTilemaps = [];
private StorageFile? m_CurrentFile;
private LevelState m_Level;
private IReadOnlyList<EditorToolViewModel> m_EditorTools = [];
private bool m_Painting;
private bool m_Panning;
private Point m_LastPanPoint;
private GridPosition? m_SelectedCell;
private EEditorTool m_SelectedTool = EEditorTool.Cursor;
private double m_Zoom = 1;
private double m_PanX;
private double m_PanY;
private CanvasBitmap? m_TerrainTilemap;
private CanvasBitmap? m_RobotSprite;
private CanvasBitmap? m_LeakSprite;
private CanvasBitmap? m_HeatSprite;
private CanvasBitmap? m_FireSprite;
}