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 input = CliInput();
|
||||||
final cliAudio = CliSilentAudio();
|
final cliAudio = CliSilentAudio();
|
||||||
|
|
||||||
|
final rasterizer = AsciiRasterizer();
|
||||||
|
|
||||||
|
FrameBuffer buffer = FrameBuffer(
|
||||||
|
stdout.terminalColumns,
|
||||||
|
stdout.terminalLines,
|
||||||
|
);
|
||||||
|
|
||||||
final engine = WolfEngine(
|
final engine = WolfEngine(
|
||||||
data: data,
|
data: data,
|
||||||
difficulty: Difficulty.bringEmOn,
|
difficulty: Difficulty.bringEmOn,
|
||||||
@@ -52,9 +59,6 @@ void main() async {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final rasterizer = AsciiRasterizer();
|
|
||||||
final buffer = FrameBuffer(120, 40);
|
|
||||||
|
|
||||||
engine.init();
|
engine.init();
|
||||||
|
|
||||||
stdin.listen((List<int> bytes) {
|
stdin.listen((List<int> bytes) {
|
||||||
@@ -70,7 +74,9 @@ void main() async {
|
|||||||
Timer.periodic(const Duration(milliseconds: 33), (timer) {
|
Timer.periodic(const Duration(milliseconds: 33), (timer) {
|
||||||
// 1. Terminal Size Safety Check
|
// 1. Terminal Size Safety Check
|
||||||
if (stdout.hasTerminal) {
|
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
|
// Clear the screen and print the warning at the top left
|
||||||
stdout.write('\x1b[2J\x1b[H');
|
stdout.write('\x1b[2J\x1b[H');
|
||||||
stdout.write('\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n');
|
stdout.write('\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n');
|
||||||
@@ -86,6 +92,10 @@ void main() async {
|
|||||||
lastTick = stopwatch.elapsed;
|
lastTick = stopwatch.elapsed;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buffer.width != cols || buffer.height != rows) {
|
||||||
|
buffer = FrameBuffer(cols, rows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Normal Game Loop
|
// 2. Normal Game Loop
|
||||||
|
|||||||
@@ -127,9 +127,7 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
int texX,
|
int texX,
|
||||||
double transformY,
|
double transformY,
|
||||||
) {
|
) {
|
||||||
// Ask the base class for the depth brightness
|
|
||||||
double brightness = calculateDepthBrightness(transformY);
|
double brightness = calculateDepthBrightness(transformY);
|
||||||
String spriteChar = activeTheme.getByBrightness(brightness);
|
|
||||||
|
|
||||||
for (
|
for (
|
||||||
int y = math.max(0, drawStartY);
|
int y = math.max(0, drawStartY);
|
||||||
@@ -141,10 +139,21 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
int colorByte = texture.pixels[texX * 64 + texY];
|
int colorByte = texture.pixels[texX * 64 + texY];
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
_screen[y][stripeX] = ColoredChar(
|
int rawColor = ColorPalette.vga32Bit[colorByte];
|
||||||
spriteChar,
|
|
||||||
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
|
@override
|
||||||
void drawHud(WolfEngine engine) {
|
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(
|
int statusBarIndex = engine.data.vgaImages.indexWhere(
|
||||||
(img) => img.width == 320 && img.height == 40,
|
(img) => img.width == 320 && img.height == 40,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class WolfEngine {
|
|||||||
onPlaySound: (sfxId) => audio.playSoundEffect(sfxId),
|
onPlaySound: (sfxId) => audio.playSoundEffect(sfxId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
int _timeAliveMs = 0;
|
||||||
|
|
||||||
final WolfensteinData data;
|
final WolfensteinData data;
|
||||||
final Difficulty difficulty;
|
final Difficulty difficulty;
|
||||||
final int startingEpisode;
|
final int startingEpisode;
|
||||||
@@ -52,6 +54,8 @@ class WolfEngine {
|
|||||||
void tick(Duration elapsed, EngineInput input) {
|
void tick(Duration elapsed, EngineInput input) {
|
||||||
if (!isInitialized) return;
|
if (!isInitialized) return;
|
||||||
|
|
||||||
|
_timeAliveMs += elapsed.inMilliseconds;
|
||||||
|
|
||||||
final inputResult = _processInputs(elapsed, input);
|
final inputResult = _processInputs(elapsed, input);
|
||||||
|
|
||||||
doorManager.update(elapsed);
|
doorManager.update(elapsed);
|
||||||
@@ -74,7 +78,7 @@ class WolfEngine {
|
|||||||
_updateEntities(elapsed);
|
_updateEntities(elapsed);
|
||||||
|
|
||||||
player.updateWeapon(
|
player.updateWeapon(
|
||||||
currentTime: elapsed.inMilliseconds,
|
currentTime: _timeAliveMs,
|
||||||
entities: entities,
|
entities: entities,
|
||||||
isWalkable: isWalkable,
|
isWalkable: isWalkable,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ class CliInput extends Wolf3dInput {
|
|||||||
if (char == 'a') _pLeft = true;
|
if (char == 'a') _pLeft = true;
|
||||||
if (char == 'd') _pRight = true;
|
if (char == 'd') _pRight = true;
|
||||||
|
|
||||||
if (char == 'f' || char == ' ') _pFire = true;
|
// --- NEW MAPPINGS ---
|
||||||
if (char == 'e') _pInteract = true;
|
if (char == 'j') _pFire = true;
|
||||||
|
if (char == ' ') _pInteract = true;
|
||||||
|
|
||||||
if (char == '1') _pWeapon = WeaponType.knife;
|
if (char == '1') _pWeapon = WeaponType.knife;
|
||||||
if (char == '2') _pWeapon = WeaponType.pistol;
|
if (char == '2') _pWeapon = WeaponType.pistol;
|
||||||
|
|||||||
Reference in New Issue
Block a user