From fdfe5d336fd53c8304adf2249b2cd36894197797 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Mon, 16 Mar 2026 15:26:10 +0100 Subject: [PATCH] Fixed ASCII and cli rendering Signed-off-by: Hans Kokx --- apps/wolf_3d_cli/bin/main.dart | 18 +++- .../lib/src/ascii_rasterizer.dart | 93 +++++++++++++++++-- .../lib/src/wolf_3d_engine_base.dart | 6 +- packages/wolf_3d_input/lib/src/cli_input.dart | 5 +- 4 files changed, 109 insertions(+), 13 deletions(-) diff --git a/apps/wolf_3d_cli/bin/main.dart b/apps/wolf_3d_cli/bin/main.dart index 1031836..657fee5 100644 --- a/apps/wolf_3d_cli/bin/main.dart +++ b/apps/wolf_3d_cli/bin/main.dart @@ -41,6 +41,13 @@ void main() async { final input = CliInput(); final cliAudio = CliSilentAudio(); + final rasterizer = AsciiRasterizer(); + + FrameBuffer buffer = FrameBuffer( + stdout.terminalColumns, + stdout.terminalLines, + ); + final engine = WolfEngine( data: data, difficulty: Difficulty.bringEmOn, @@ -52,9 +59,6 @@ void main() async { }, ); - final rasterizer = AsciiRasterizer(); - final buffer = FrameBuffer(120, 40); - engine.init(); stdin.listen((List bytes) { @@ -70,7 +74,9 @@ void main() async { Timer.periodic(const Duration(milliseconds: 33), (timer) { // 1. Terminal Size Safety Check if (stdout.hasTerminal) { - if (stdout.terminalColumns < 120 || stdout.terminalLines < 40) { + int cols = stdout.terminalColumns; + int rows = stdout.terminalLines; + if (cols < 80 || rows < 24) { // Clear the screen and print the warning at the top left stdout.write('\x1b[2J\x1b[H'); stdout.write('\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n'); @@ -86,6 +92,10 @@ void main() async { lastTick = stopwatch.elapsed; return; } + + if (buffer.width != cols || buffer.height != rows) { + buffer = FrameBuffer(cols, rows); + } } // 2. Normal Game Loop diff --git a/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart b/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart index 76199f4..56a4f77 100644 --- a/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart +++ b/packages/wolf_3d_engine/lib/src/ascii_rasterizer.dart @@ -127,9 +127,7 @@ class AsciiRasterizer extends Rasterizer { int texX, double transformY, ) { - // Ask the base class for the depth brightness double brightness = calculateDepthBrightness(transformY); - String spriteChar = activeTheme.getByBrightness(brightness); for ( int y = math.max(0, drawStartY); @@ -141,10 +139,21 @@ class AsciiRasterizer extends Rasterizer { int colorByte = texture.pixels[texX * 64 + texY]; if (colorByte != 255) { - _screen[y][stripeX] = ColoredChar( - spriteChar, - ColorPalette.vga32Bit[colorByte], - ); + int rawColor = ColorPalette.vga32Bit[colorByte]; + + // Shade the sprite's actual RGB color based on distance + int r = (rawColor & 0xFF); + int g = ((rawColor >> 8) & 0xFF); + int b = ((rawColor >> 16) & 0xFF); + + r = (r * brightness).toInt(); + g = (g * brightness).toInt(); + b = (b * brightness).toInt(); + + int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r; + + // Force sprites to be SOLID so they don't vanish into the terminal background + _screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor); } } } @@ -183,8 +192,80 @@ class AsciiRasterizer extends Rasterizer { } } + // --- PRIVATE HUD DRAWING HELPER --- + + /// Injects a pure text string directly into the rasterizer grid + void _writeString(int startX, int y, String text, int color) { + for (int i = 0; i < text.length; i++) { + int x = startX + i; + if (x >= 0 && x < width && y >= 0 && y < height) { + _screen[y][x] = ColoredChar(text[i], color); + } + } + } + @override void drawHud(WolfEngine engine) { + // If the terminal is at least 160 columns wide and 50 rows tall, + // there are enough "pixels" to downscale the VGA image clearly. + if (width >= 160 && height >= 50) { + _drawFullVgaHud(engine); + } else { + _drawSimpleHud(engine); + } + } + + 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); + } + } + + // 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); + } + + // 3. Define some clean terminal colors + int white = 0xFFFFFFFF; + int yellow = 0xFFFFFF55; + int red = 0xFFFF5555; + + // Turn health text red if dying + int healthColor = engine.player.health > 25 ? white : red; + + // 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 = " :-) "; + if (engine.player.health <= 0) { + face = " X-x "; + } else if (engine.player.health <= 25) { + face = " :-( "; + } else if (engine.player.health <= 60) { + face = " :-| "; + } + + _writeString(62, textY, "[$face]", yellow); + + _writeString(78, textY, health, healthColor); + _writeString(100, textY, ammo, white); + } + + void _drawFullVgaHud(WolfEngine engine) { int statusBarIndex = engine.data.vgaImages.indexWhere( (img) => img.width == 320 && img.height == 40, ); diff --git a/packages/wolf_3d_engine/lib/src/wolf_3d_engine_base.dart b/packages/wolf_3d_engine/lib/src/wolf_3d_engine_base.dart index 6c9e1b7..974c9ee 100644 --- a/packages/wolf_3d_engine/lib/src/wolf_3d_engine_base.dart +++ b/packages/wolf_3d_engine/lib/src/wolf_3d_engine_base.dart @@ -15,6 +15,8 @@ class WolfEngine { onPlaySound: (sfxId) => audio.playSoundEffect(sfxId), ); + int _timeAliveMs = 0; + final WolfensteinData data; final Difficulty difficulty; final int startingEpisode; @@ -52,6 +54,8 @@ class WolfEngine { void tick(Duration elapsed, EngineInput input) { if (!isInitialized) return; + _timeAliveMs += elapsed.inMilliseconds; + final inputResult = _processInputs(elapsed, input); doorManager.update(elapsed); @@ -74,7 +78,7 @@ class WolfEngine { _updateEntities(elapsed); player.updateWeapon( - currentTime: elapsed.inMilliseconds, + currentTime: _timeAliveMs, entities: entities, isWalkable: isWalkable, ); diff --git a/packages/wolf_3d_input/lib/src/cli_input.dart b/packages/wolf_3d_input/lib/src/cli_input.dart index afbf997..3864562 100644 --- a/packages/wolf_3d_input/lib/src/cli_input.dart +++ b/packages/wolf_3d_input/lib/src/cli_input.dart @@ -20,8 +20,9 @@ class CliInput extends Wolf3dInput { if (char == 'a') _pLeft = true; if (char == 'd') _pRight = true; - if (char == 'f' || char == ' ') _pFire = true; - if (char == 'e') _pInteract = true; + // --- NEW MAPPINGS --- + if (char == 'j') _pFire = true; + if (char == ' ') _pInteract = true; if (char == '1') _pWeapon = WeaponType.knife; if (char == '2') _pWeapon = WeaponType.pistol;