diff --git a/src/ReactorMaintenance.Win2D/Images/Failures/failure-ignition.png b/src/ReactorMaintenance.Win2D/Images/Failures/failure-ignition.png new file mode 100644 index 0000000..19d1103 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Failures/failure-ignition.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Failures/failure-meltdown.png b/src/ReactorMaintenance.Win2D/Images/Failures/failure-meltdown.png new file mode 100644 index 0000000..849c085 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Failures/failure-meltdown.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Failures/failure-pipe-burst.png b/src/ReactorMaintenance.Win2D/Images/Failures/failure-pipe-burst.png new file mode 100644 index 0000000..8456e65 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Failures/failure-pipe-burst.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Failures/failure-reactor-ready.png b/src/ReactorMaintenance.Win2D/Images/Failures/failure-reactor-ready.png new file mode 100644 index 0000000..02c2e32 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Failures/failure-reactor-ready.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Failures/failure-stability-collapse.png b/src/ReactorMaintenance.Win2D/Images/Failures/failure-stability-collapse.png new file mode 100644 index 0000000..413a6dd Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Failures/failure-stability-collapse.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-coolant-tilemap.png b/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-coolant-tilemap.png new file mode 100644 index 0000000..a1f8cfc Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-coolant-tilemap.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-fuel-tilemap.png b/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-fuel-tilemap.png new file mode 100644 index 0000000..b326323 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-fuel-tilemap.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-pressure-tilemap.png b/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-pressure-tilemap.png new file mode 100644 index 0000000..b008d94 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Pipes/pipe-pressure-tilemap.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/control-terminal.png b/src/ReactorMaintenance.Win2D/Images/Props/control-terminal.png new file mode 100644 index 0000000..15a6fd9 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/control-terminal.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/cooling-pump.png b/src/ReactorMaintenance.Win2D/Images/Props/cooling-pump.png new file mode 100644 index 0000000..3379b30 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/cooling-pump.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/diagnostic-terminal.png b/src/ReactorMaintenance.Win2D/Images/Props/diagnostic-terminal.png new file mode 100644 index 0000000..27ded22 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/diagnostic-terminal.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/fire.png b/src/ReactorMaintenance.Win2D/Images/Props/fire.png new file mode 100644 index 0000000..a1450ab Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/fire.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/generator.png b/src/ReactorMaintenance.Win2D/Images/Props/generator.png new file mode 100644 index 0000000..6bfea83 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/generator.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/heat.png b/src/ReactorMaintenance.Win2D/Images/Props/heat.png new file mode 100644 index 0000000..f9e0417 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/heat.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/leak.png b/src/ReactorMaintenance.Win2D/Images/Props/leak.png new file mode 100644 index 0000000..8c34c61 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/leak.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/pressure-regulator.png b/src/ReactorMaintenance.Win2D/Images/Props/pressure-regulator.png new file mode 100644 index 0000000..73cc9b2 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/pressure-regulator.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/reactor.png b/src/ReactorMaintenance.Win2D/Images/Props/reactor.png new file mode 100644 index 0000000..1a324c5 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/reactor.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/repair.png b/src/ReactorMaintenance.Win2D/Images/Props/repair.png new file mode 100644 index 0000000..78cccf6 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/repair.png differ diff --git a/src/ReactorMaintenance.Win2D/Images/Props/robot.png b/src/ReactorMaintenance.Win2D/Images/Props/robot.png new file mode 100644 index 0000000..dc3f2a4 Binary files /dev/null and b/src/ReactorMaintenance.Win2D/Images/Props/robot.png differ diff --git a/src/ReactorMaintenance.Win2D/MainWindow.xaml b/src/ReactorMaintenance.Win2D/MainWindow.xaml index f92d0b7..904fe3a 100644 --- a/src/ReactorMaintenance.Win2D/MainWindow.xaml +++ b/src/ReactorMaintenance.Win2D/MainWindow.xaml @@ -29,7 +29,20 @@ - + + + + + + + + + + + + + + @@ -78,7 +91,15 @@ - + + + + + + + + diff --git a/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs b/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs index 186ca9e..fc0728e 100644 --- a/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs +++ b/src/ReactorMaintenance.Win2D/MainWindow.xaml.cs @@ -1,18 +1,17 @@ -using System.Numerics; -using Windows.Foundation; -using Windows.Storage; -using Windows.Storage.Pickers; -using Windows.UI; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Text; +using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.UI; using Microsoft.Graphics.Canvas.UI.Xaml; using Microsoft.UI; 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.UI; using Windows.UI.Popups; using WinRT.Interop; @@ -24,40 +23,65 @@ public sealed partial class MainWindow { public Rect CellRect(int x, int y) { - return new(OriginX + x * CellSize, OriginY + y * CellSize, CellSize, CellSize); + 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); + return new(OriginX + ((x - 0.5) * CellSize), OriginY + ((y - 0.5) * CellSize), CellSize, CellSize); } } + private sealed record ForecastViewModel(BitmapImage Icon, string Message); + + private sealed record EditorToolViewModel(EEditorTool Tool, BitmapImage? Icon, string Label); + public MainWindow() { InitializeComponent(); m_Level = BuildStarterLevel(); - ToolPicker.ItemsSource = Enum.GetValues(); - ToolPicker.SelectedItem = m_SelectedTool; + m_EditorTools = Enum.GetValues().Select(tool => new EditorToolViewModel(tool, EditorToolIcon(tool), tool.ToString())).ToArray(); + ToolPicker.ItemsSource = m_EditorTools; + ToolPicker.SelectedItem = m_EditorTools.First(tool => tool.Tool == m_SelectedTool); RefreshInspector(); } private void LevelCanvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) { - args.TrackAsyncAction(LoadTilemapAsync(sender).AsAsyncAction()); + args.TrackAsyncAction(LoadImagesAsync(sender).AsAsyncAction()); } - private async Task LoadTilemapAsync(CanvasControl sender) + private async Task LoadImagesAsync(CanvasControl sender) { - var tilemapPath = Path.Combine(AppContext.BaseDirectory, "Images", "tilemap.png"); - m_TerrainTilemap = await CanvasBitmap.LoadAsync(sender, tilemapPath); + 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 LoadCanvasBitmapAsync(CanvasControl sender, params string[] pathParts) + { + var path = Path.Combine([AppContext.BaseDirectory, .. pathParts]); + return await CanvasBitmap.LoadAsync(sender, path); } private void ToolPicker_SelectionChanged(object sender, SelectionChangedEventArgs e) { - if (ToolPicker.SelectedItem is EEditorTool tool) - m_SelectedTool = tool; + if (ToolPicker.SelectedItem is EditorToolViewModel tool) + m_SelectedTool = tool.Tool; } private void New_Click(object sender, RoutedEventArgs e) @@ -183,45 +207,116 @@ public sealed partial class MainWindow 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)); + { + 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); - - if (cell.HasPipe) + for (var x = 0; x < m_Level.Width; x++) { - var center = new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2)); - var pipeColor = cell.Pipe switch { - EPipeMedium.Coolant => Colors.DeepSkyBlue, - EPipeMedium.Fuel => Colors.Goldenrod, - EPipeMedium.Pressure => Colors.LightSteelBlue, - _ => Colors.Transparent - }; - drawing.DrawLine(center with { X = (float)rect.X + 6 }, center with { X = (float)(rect.X + rect.Width - 6) }, pipeColor, Math.Max(3, (float)rect.Width / 7)); - drawing.DrawLine(center with { Y = (float)rect.Y + 6 }, center with { Y = (float)(rect.Y + rect.Height - 6) }, pipeColor, Math.Max(3, (float)rect.Width / 7)); + 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); } - - if (cell.LeakRate > 0) - drawing.DrawCircle(new((float)(rect.X + rect.Width - 10), (float)(rect.Y + 10)), 5, Colors.OrangeRed, 2); - - if (cell.Hazards.Fire) - drawing.FillCircle(new((float)(rect.X + rect.Width * 0.5), (float)(rect.Y + rect.Height * 0.5)), (float)rect.Width * 0.24f, Colors.OrangeRed); - - 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) @@ -235,21 +330,21 @@ public sealed partial class MainWindow private static Rect TilemapSourceRect(int wallMask) { var tilePosition = wallMask switch { - c_BottomLeftCorner => new GridPosition(0, 0), - c_TopRightCorner | c_BottomRightCorner => new GridPosition(1, 0), - c_TopLeftCorner | c_BottomLeftCorner | c_BottomRightCorner => new GridPosition(2, 0), - c_BottomLeftCorner | c_BottomRightCorner => new GridPosition(3, 0), - c_TopLeftCorner | c_BottomRightCorner => new GridPosition(0, 1), - c_BottomLeftCorner | c_TopRightCorner | c_BottomRightCorner => new GridPosition(1, 1), - c_AllCorners => new GridPosition(2, 1), - c_TopLeftCorner | c_BottomLeftCorner | c_TopRightCorner => new GridPosition(3, 1), - c_TopRightCorner => new GridPosition(0, 2), - c_TopLeftCorner | c_TopRightCorner => new GridPosition(1, 2), - c_TopLeftCorner | c_TopRightCorner | c_BottomRightCorner => new GridPosition(2, 2), - c_BottomLeftCorner | c_TopLeftCorner => new GridPosition(3, 2), - 0 => new GridPosition(0, 3), - c_BottomRightCorner => new GridPosition(1, 3), - c_BottomLeftCorner | c_TopRightCorner => new GridPosition(2, 3), + 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.") }; @@ -287,52 +382,29 @@ public sealed partial class MainWindow private void DrawCellProp(CanvasDrawingSession drawing, CellState cell, Rect rect) { - var text = cell.Prop switch { - ECellProp.Reactor => "R", - ECellProp.CoolingPump => "C", - ECellProp.Generator => "G", - ECellProp.PressureRegulator => "P", - ECellProp.DiagnosticTerminal => "D", - ECellProp.ControlTerminal => "T", - _ => string.Empty - }; - - if (string.IsNullOrEmpty(text)) - return; - - var propRect = new Rect(rect.X + rect.Width * 0.18, rect.Y + rect.Height * 0.18, rect.Width * 0.64, rect.Height * 0.64); - drawing.FillRoundedRectangle(propRect, 4, 4, PropColor(cell.Prop)); - drawing.DrawRoundedRectangle(propRect, 4, 4, ColorHelper.FromArgb(210, 12, 14, 16), 2); - - using var format = new CanvasTextFormat(); - format.FontSize = Math.Max(14, (float)rect.Width * 0.34f); - format.HorizontalAlignment = CanvasHorizontalAlignment.Center; - format.VerticalAlignment = CanvasVerticalAlignment.Center; - - drawing.DrawText(text, propRect, Colors.White, format); + 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); + 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); + 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); - var center = new Vector2((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2)); - drawing.FillCircle(center, (float)rect.Width * 0.28f, Colors.White); - drawing.DrawCircle(center, (float)rect.Width * 0.28f, Colors.Black, 2); + DrawImage(drawing, m_RobotSprite, rect); } private bool TryGetGridPosition(Point point, out GridPosition position) @@ -350,24 +422,11 @@ public sealed partial class MainWindow var availableHeight = Math.Max(1, LevelCanvas.ActualHeight); var size = Math.Floor(Math.Min(availableWidth / m_Level.Width, availableHeight / m_Level.Height)); size = Math.Max(20, size); - var originX = Math.Max(0, (availableWidth - size * m_Level.Width) / 2); - var originY = Math.Max(0, (availableHeight - size * m_Level.Height) / 2); + var originX = Math.Max(0, (availableWidth - (size * m_Level.Width)) / 2); + var originY = Math.Max(0, (availableHeight - (size * m_Level.Height)) / 2); return new(size, originX, originY); } - private static Color PropColor(ECellProp prop) - { - return prop switch { - ECellProp.Reactor => ColorHelper.FromArgb(255, 61, 76, 82), - ECellProp.CoolingPump => ColorHelper.FromArgb(255, 25, 79, 96), - ECellProp.Generator => ColorHelper.FromArgb(255, 86, 75, 35), - ECellProp.PressureRegulator => ColorHelper.FromArgb(255, 70, 78, 98), - ECellProp.DiagnosticTerminal => ColorHelper.FromArgb(255, 39, 84, 62), - ECellProp.ControlTerminal => ColorHelper.FromArgb(255, 80, 61, 91), - _ => Colors.Transparent - }; - } - private void RefreshInspector() { LevelNameText.Text = m_Level.Name; @@ -383,7 +442,61 @@ public sealed partial class MainWindow else CellText.Text = "No cell selected."; - ForecastList.ItemsSource = m_Level.Forecasts; + 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.Floor or EEditorTool.Wall => null, + 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() @@ -438,17 +551,31 @@ public sealed partial class MainWindow return level with { Forecasts = new SimulationEngine().Forecast(level) }; } - private readonly SimulationEngine m_Simulation = new(); 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 CanvasBitmap? m_TerrainTilemap; + 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 readonly SimulationEngine m_Simulation = new(); + private readonly Dictionary m_PropSprites = []; + private readonly Dictionary m_PipeTilemaps = []; private StorageFile? m_CurrentFile; private LevelState m_Level; + private IReadOnlyList m_EditorTools = []; private bool m_Painting; private GridPosition? m_SelectedCell; private EEditorTool m_SelectedTool = EEditorTool.Floor; -} \ No newline at end of file + private CanvasBitmap? m_TerrainTilemap; + private CanvasBitmap? m_RobotSprite; + private CanvasBitmap? m_LeakSprite; + private CanvasBitmap? m_HeatSprite; + private CanvasBitmap? m_FireSprite; +} diff --git a/src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj b/src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj index 5ae97e4..727591e 100644 --- a/src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj +++ b/src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj @@ -26,7 +26,7 @@ - + PreserveNewest