Fixed ASCII and cli rendering
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -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<int> 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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user