diff --git a/apps/wolf_3d_cli/bin/main.dart b/apps/wolf_3d_cli/bin/main.dart index b9f568f..4d8be46 100644 --- a/apps/wolf_3d_cli/bin/main.dart +++ b/apps/wolf_3d_cli/bin/main.dart @@ -41,10 +41,7 @@ void main() async { final input = CliInput(); final cliAudio = CliSilentAudio(); - final rasterizer = AsciiRasterizer( - aspectMultiplier: 1.0, - verticalStretch: 2.0, - ); + final rasterizer = AsciiRasterizer(); FrameBuffer buffer = FrameBuffer( stdout.terminalColumns, diff --git a/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart b/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart index a29d6bc..d28cf67 100644 --- a/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart +++ b/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'package:arcane_helper_utils/arcane_helper_utils.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_engine/wolf_3d_engine.dart'; @@ -222,53 +223,97 @@ class AsciiRasterizer extends Rasterizer { } void _drawSimpleHud(WolfEngine engine) { - // 1. Clear the HUD area so stray wall characters don't bleed in - for (int y = viewHeight; y < height; y++) { - for (int x = 0; x < width; x++) { - _screen[y][x] = ColoredChar(' ', 0xFF000000); + // 1. Pull Retro Colors + final int vgaStatusBarBlue = ColorPalette.vga32Bit[153]; + final int vgaPanelDark = ColorPalette.vga32Bit[0]; + final int white = ColorPalette.vga32Bit[15]; + final int yellow = ColorPalette.vga32Bit[11]; + final int red = ColorPalette.vga32Bit[4]; + + // 2. Setup Centered Layout + // The total width of our standard HUD elements is roughly 120 chars + const int hudContentWidth = 120; + final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width); + + // 3. Clear HUD Base + _fillRect(0, viewHeight, width, height - viewHeight, ' ', vgaStatusBarBlue); + _writeString(0, viewHeight, "═" * width, white); + + // 4. Panel Drawing Helper + void drawBorderedPanel(int startX, int startY, int w, int h) { + _fillRect(startX, startY, w, h, ' ', vgaPanelDark); + // Horizontal lines + _writeString(startX, startY, "┌${"─" * (w - 2)}┐", white); + _writeString(startX, startY + h - 1, "└${"─" * (w - 2)}┘", white); + // Vertical sides + for (int i = 1; i < h - 1; i++) { + _writeString(startX, startY + i, "│", white); + _writeString(startX + w - 1, startY + i, "│", white); } } - // 2. Draw a decorative border to separate the 3D view from the HUD - int borderColor = 0xFF555555; // Dark Gray - for (int x = 0; x < width; x++) { - _screen[viewHeight][x] = ColoredChar('=', borderColor); - } + // 5. Draw the Panels + // FLOOR + drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5); + _writeString(offsetX + 7, viewHeight + 3, "FLOOR", white); + _writeString( + offsetX + 9, + viewHeight + 5, + engine.activeLevel.name.split(' ').last, + white, + ); - // 3. Define some clean terminal colors - int white = 0xFFFFFFFF; - int yellow = 0xFFFFFF55; - int red = 0xFFFF5555; + // SCORE + drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5); + _writeString(offsetX + 27, viewHeight + 3, "SCORE", white); + _writeString( + offsetX + 27, + viewHeight + 5, + engine.player.score.toString().padLeft(6, '0'), + white, + ); - // Turn health text red if dying - int healthColor = engine.player.health > 25 ? white : red; + // LIVES + drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5); + _writeString(offsetX + 47, viewHeight + 3, "LIVES", white); + _writeString(offsetX + 49, viewHeight + 5, "3", white); - // Format the strings nicely - String score = "SCORE: ${engine.player.score.toString().padLeft(6, '0')}"; - String health = "HEALTH: ${engine.player.health}%"; - String ammo = "AMMO: ${engine.player.ammo}"; - - int textY = viewHeight + 4; // Center it vertically in the HUD area - - // 4. Print the stats evenly spaced across the bottom - _writeString(6, textY, "FLOOR 1", white); - _writeString(22, textY, score, white); - _writeString(44, textY, "LIVES: 3", white); - - // 5. Reactive ASCII Face - String face = " :-) "; + // FACE (With Reactive BJ Logic) + drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7); + String face = " :-)"; if (engine.player.health <= 0) { - face = " X-x "; + face = " X-x"; + } else if (engine.player.damageFlash > 0.1) { + face = " :-O"; // Mouth open in pain! } else if (engine.player.health <= 25) { - face = " :-( "; + face = " :-("; } else if (engine.player.health <= 60) { - face = " :-| "; + face = " :-|"; } + _writeString(offsetX + 63, viewHeight + 4, face, yellow); - _writeString(62, textY, "[$face]", yellow); + // HEALTH + int healthColor = engine.player.health > 25 ? white : red; + drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5); + _writeString(offsetX + 78, viewHeight + 3, "HEALTH", white); + _writeString( + offsetX + 79, + viewHeight + 5, + "${engine.player.health}%", + healthColor, + ); - _writeString(78, textY, health, healthColor); - _writeString(100, textY, ammo, white); + // AMMO + drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5); + _writeString(offsetX + 95, viewHeight + 3, "AMMO", white); + _writeString(offsetX + 97, viewHeight + 5, "${engine.player.ammo}", white); + + // WEAPON + drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5); + String weapon = engine.player.currentWeapon.type.name.spacePascalCase! + .toUpperCase(); + if (weapon.length > 12) weapon = weapon.substring(0, 12); + _writeString(offsetX + 107, viewHeight + 4, weapon, white); } void _drawFullVgaHud(WolfEngine engine) { @@ -355,6 +400,19 @@ class AsciiRasterizer extends Rasterizer { } } + /// Helper to fill a rectangular area with a specific char and background color + void _fillRect(int startX, int startY, int w, int h, String char, int color) { + for (int dy = 0; dy < h; dy++) { + for (int dx = 0; dx < w; dx++) { + int x = startX + dx; + int y = startY + dy; + if (x >= 0 && x < width && y >= 0 && y < height) { + _screen[y][x] = ColoredChar(char, color); + } + } + } + } + @override dynamic finalizeFrame() { if (_engine.player.damageFlash > 0.0) { diff --git a/packages/wolf_3d_engine/pubspec.yaml b/packages/wolf_3d_engine/pubspec.yaml index d3a2d64..55889ba 100644 --- a/packages/wolf_3d_engine/pubspec.yaml +++ b/packages/wolf_3d_engine/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: wolf_3d_entities: any wolf_3d_data: any wolf_3d_input: any + arcane_helper_utils: ^1.4.7 dev_dependencies: lints: ^6.0.0