Double "resolution" in the CLI
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -41,7 +41,7 @@ void main() async {
|
|||||||
final input = CliInput();
|
final input = CliInput();
|
||||||
final cliAudio = CliSilentAudio();
|
final cliAudio = CliSilentAudio();
|
||||||
|
|
||||||
final rasterizer = AsciiRasterizer();
|
final rasterizer = AsciiRasterizer(isTerminal: true);
|
||||||
|
|
||||||
FrameBuffer buffer = FrameBuffer(
|
FrameBuffer buffer = FrameBuffer(
|
||||||
stdout.terminalColumns,
|
stdout.terminalColumns,
|
||||||
@@ -69,7 +69,6 @@ void main() async {
|
|||||||
|
|
||||||
if (bytes.contains(9)) {
|
if (bytes.contains(9)) {
|
||||||
rasterizer.activeTheme = AsciiThemes.nextOf(rasterizer.activeTheme);
|
rasterizer.activeTheme = AsciiThemes.nextOf(rasterizer.activeTheme);
|
||||||
print("Switched to ${rasterizer.activeTheme.name} theme!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.handleKey(bytes);
|
input.handleKey(bytes);
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ abstract class AsciiThemes {
|
|||||||
class ColoredChar {
|
class ColoredChar {
|
||||||
final String char;
|
final String char;
|
||||||
final int rawColor; // Stores the AABBGGRR integer from the palette
|
final int rawColor; // Stores the AABBGGRR integer from the palette
|
||||||
|
final int? rawBackgroundColor;
|
||||||
|
|
||||||
ColoredChar(this.char, this.rawColor);
|
ColoredChar(this.char, this.rawColor, [this.rawBackgroundColor]);
|
||||||
|
|
||||||
// Safely extract the exact RGB channels regardless of framework
|
// Safely extract the exact RGB channels regardless of framework
|
||||||
int get r => rawColor & 0xFF;
|
int get r => rawColor & 0xFF;
|
||||||
@@ -61,13 +62,16 @@ class ColoredChar {
|
|||||||
class AsciiRasterizer extends Rasterizer {
|
class AsciiRasterizer extends Rasterizer {
|
||||||
AsciiRasterizer({
|
AsciiRasterizer({
|
||||||
this.activeTheme = AsciiThemes.blocks,
|
this.activeTheme = AsciiThemes.blocks,
|
||||||
|
this.isTerminal = false,
|
||||||
this.aspectMultiplier = 1.0,
|
this.aspectMultiplier = 1.0,
|
||||||
this.verticalStretch = 1.0,
|
this.verticalStretch = 1.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
AsciiTheme activeTheme = AsciiThemes.blocks;
|
AsciiTheme activeTheme = AsciiThemes.blocks;
|
||||||
|
final bool isTerminal;
|
||||||
|
|
||||||
late List<List<ColoredChar>> _screen;
|
late List<List<ColoredChar>> _screen;
|
||||||
|
late List<List<int>> _scenePixels;
|
||||||
late WolfEngine _engine;
|
late WolfEngine _engine;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -75,6 +79,11 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
@override
|
@override
|
||||||
final double verticalStretch;
|
final double verticalStretch;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get projectionViewHeight => isTerminal ? viewHeight * 2 : viewHeight;
|
||||||
|
|
||||||
|
int get _terminalPixelHeight => isTerminal ? height * 2 : height;
|
||||||
|
|
||||||
// Intercept the base render call to initialize our text grid
|
// Intercept the base render call to initialize our text grid
|
||||||
@override
|
@override
|
||||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
||||||
@@ -92,7 +101,23 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
// Just grab the raw ints!
|
// Just grab the raw ints!
|
||||||
final int ceilingColor = ColorPalette.vga32Bit[25];
|
final int ceilingColor = ColorPalette.vga32Bit[25];
|
||||||
final int floorColor = ColorPalette.vga32Bit[29];
|
final int floorColor = ColorPalette.vga32Bit[29];
|
||||||
|
final int black = ColorPalette.vga32Bit[0];
|
||||||
|
|
||||||
|
_scenePixels = List.generate(
|
||||||
|
_terminalPixelHeight,
|
||||||
|
(_) => List.filled(width, black),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int y = 0; y < projectionViewHeight; y++) {
|
||||||
|
final int color = y < projectionViewHeight / 2
|
||||||
|
? ceilingColor
|
||||||
|
: floorColor;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
_scenePixels[y][x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTerminal) {
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
if (y < viewHeight / 2) {
|
if (y < viewHeight / 2) {
|
||||||
@@ -103,6 +128,7 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void drawWallColumn(
|
void drawWallColumn(
|
||||||
@@ -116,11 +142,10 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
int side,
|
int side,
|
||||||
) {
|
) {
|
||||||
double brightness = calculateDepthBrightness(perpWallDist);
|
double brightness = calculateDepthBrightness(perpWallDist);
|
||||||
String wallChar = activeTheme.getByBrightness(brightness);
|
|
||||||
|
|
||||||
for (int y = drawStart; y < drawEnd; y++) {
|
for (int y = drawStart; y < drawEnd; y++) {
|
||||||
double relativeY =
|
double relativeY =
|
||||||
(y - (-columnHeight ~/ 2 + viewHeight ~/ 2)) / columnHeight;
|
(y - (-columnHeight ~/ 2 + projectionViewHeight ~/ 2)) / columnHeight;
|
||||||
int texY = (relativeY * 64).toInt().clamp(0, 63);
|
int texY = (relativeY * 64).toInt().clamp(0, 63);
|
||||||
|
|
||||||
int colorByte = texture.pixels[texX * 64 + texY];
|
int colorByte = texture.pixels[texX * 64 + texY];
|
||||||
@@ -131,9 +156,14 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
pixelColor = shadeColor(pixelColor);
|
pixelColor = shadeColor(pixelColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTerminal) {
|
||||||
|
_scenePixels[y][x] = _scaleColor(pixelColor, brightness);
|
||||||
|
} else {
|
||||||
|
String wallChar = activeTheme.getByBrightness(brightness);
|
||||||
_screen[y][x] = ColoredChar(wallChar, pixelColor);
|
_screen[y][x] = ColoredChar(wallChar, pixelColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void drawSpriteStripe(
|
void drawSpriteStripe(
|
||||||
@@ -149,7 +179,7 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
for (
|
for (
|
||||||
int y = math.max(0, drawStartY);
|
int y = math.max(0, drawStartY);
|
||||||
y < math.min(viewHeight, drawEndY);
|
y < math.min(projectionViewHeight, drawEndY);
|
||||||
y++
|
y++
|
||||||
) {
|
) {
|
||||||
double relativeY = (y - drawStartY) / spriteHeight;
|
double relativeY = (y - drawStartY) / spriteHeight;
|
||||||
@@ -170,11 +200,15 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r;
|
int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||||
|
|
||||||
|
if (isTerminal) {
|
||||||
|
_scenePixels[y][stripeX] = shadedColor;
|
||||||
|
} else {
|
||||||
// Force sprites to be SOLID so they don't vanish into the terminal background
|
// Force sprites to be SOLID so they don't vanish into the terminal background
|
||||||
_screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
|
_screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void drawWeapon(WolfEngine engine) {
|
void drawWeapon(WolfEngine engine) {
|
||||||
@@ -184,11 +218,13 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
Sprite weaponSprite = engine.data.sprites[spriteIndex];
|
Sprite weaponSprite = engine.data.sprites[spriteIndex];
|
||||||
|
|
||||||
int weaponWidth = (width * 0.5).toInt();
|
int weaponWidth = (width * 0.5).toInt();
|
||||||
int weaponHeight = (viewHeight * 0.8).toInt();
|
int weaponHeight = ((projectionViewHeight * 0.8)).toInt();
|
||||||
|
|
||||||
int startX = (width ~/ 2) - (weaponWidth ~/ 2);
|
int startX = (width ~/ 2) - (weaponWidth ~/ 2);
|
||||||
int startY =
|
int startY =
|
||||||
viewHeight - weaponHeight + (engine.player.weaponAnimOffset ~/ 4);
|
projectionViewHeight -
|
||||||
|
weaponHeight +
|
||||||
|
(engine.player.weaponAnimOffset * (isTerminal ? 2 : 1) ~/ 4);
|
||||||
|
|
||||||
for (int dy = 0; dy < weaponHeight; dy++) {
|
for (int dy = 0; dy < weaponHeight; dy++) {
|
||||||
for (int dx = 0; dx < weaponWidth; dx++) {
|
for (int dx = 0; dx < weaponWidth; dx++) {
|
||||||
@@ -197,10 +233,13 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
int colorByte = weaponSprite.pixels[texX * 64 + texY];
|
int colorByte = weaponSprite.pixels[texX * 64 + texY];
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
int drawX = startX + dx;
|
int sceneX = startX + dx;
|
||||||
int drawY = startY + dy;
|
int drawY = startY + dy;
|
||||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) {
|
if (sceneX >= 0 && sceneX < width && drawY >= 0) {
|
||||||
_screen[drawY][drawX] = ColoredChar(
|
if (isTerminal && drawY < projectionViewHeight) {
|
||||||
|
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
|
||||||
|
} else if (!isTerminal && drawY < viewHeight) {
|
||||||
|
_screen[drawY][sceneX] = ColoredChar(
|
||||||
activeTheme.solid,
|
activeTheme.solid,
|
||||||
ColorPalette.vga32Bit[colorByte],
|
ColorPalette.vga32Bit[colorByte],
|
||||||
);
|
);
|
||||||
@@ -209,15 +248,22 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- PRIVATE HUD DRAWING HELPER ---
|
// --- PRIVATE HUD DRAWING HELPER ---
|
||||||
|
|
||||||
/// Injects a pure text string directly into the rasterizer grid
|
/// Injects a pure text string directly into the rasterizer grid
|
||||||
void _writeString(int startX, int y, String text, int color) {
|
void _writeString(
|
||||||
|
int startX,
|
||||||
|
int y,
|
||||||
|
String text,
|
||||||
|
int color, [
|
||||||
|
int? backgroundColor,
|
||||||
|
]) {
|
||||||
for (int i = 0; i < text.length; i++) {
|
for (int i = 0; i < text.length; i++) {
|
||||||
int x = startX + i;
|
int x = startX + i;
|
||||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
if (x >= 0 && x < width && y >= 0 && y < height) {
|
||||||
_screen[y][x] = ColoredChar(text[i], color);
|
_screen[y][x] = ColoredChar(text[i], color, backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,11 +293,36 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width);
|
final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width);
|
||||||
|
|
||||||
// 3. Clear HUD Base
|
// 3. Clear HUD Base
|
||||||
_fillRect(0, viewHeight, width, height - viewHeight, ' ', vgaStatusBarBlue);
|
if (isTerminal) {
|
||||||
|
_fillTerminalRect(
|
||||||
|
0,
|
||||||
|
viewHeight * 2,
|
||||||
|
width,
|
||||||
|
(height - viewHeight) * 2,
|
||||||
|
vgaStatusBarBlue,
|
||||||
|
);
|
||||||
|
_fillTerminalRect(0, viewHeight * 2, width, 1, white);
|
||||||
|
} else {
|
||||||
|
_fillRect(
|
||||||
|
0,
|
||||||
|
viewHeight,
|
||||||
|
width,
|
||||||
|
height - viewHeight,
|
||||||
|
' ',
|
||||||
|
vgaStatusBarBlue,
|
||||||
|
);
|
||||||
_writeString(0, viewHeight, "═" * width, white);
|
_writeString(0, viewHeight, "═" * width, white);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Panel Drawing Helper
|
// 4. Panel Drawing Helper
|
||||||
void drawBorderedPanel(int startX, int startY, int w, int h) {
|
void drawBorderedPanel(int startX, int startY, int w, int h) {
|
||||||
|
if (isTerminal) {
|
||||||
|
_fillTerminalRect(startX, startY * 2, w, h * 2, vgaPanelDark);
|
||||||
|
_fillTerminalRect(startX, startY * 2, w, 1, white);
|
||||||
|
_fillTerminalRect(startX, (startY + h) * 2 - 1, w, 1, white);
|
||||||
|
_fillTerminalRect(startX, startY * 2, 1, h * 2, white);
|
||||||
|
_fillTerminalRect(startX + w - 1, startY * 2, 1, h * 2, white);
|
||||||
|
} else {
|
||||||
_fillRect(startX, startY, w, h, ' ', vgaPanelDark);
|
_fillRect(startX, startY, w, h, ' ', vgaPanelDark);
|
||||||
// Horizontal lines
|
// Horizontal lines
|
||||||
_writeString(startX, startY, "┌${"─" * (w - 2)}┐", white);
|
_writeString(startX, startY, "┌${"─" * (w - 2)}┐", white);
|
||||||
@@ -262,32 +333,35 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
_writeString(startX + w - 1, startY + i, "│", white);
|
_writeString(startX + w - 1, startY + i, "│", white);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Draw the Panels
|
// 5. Draw the Panels
|
||||||
// FLOOR
|
// FLOOR
|
||||||
drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5);
|
drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5);
|
||||||
_writeString(offsetX + 7, viewHeight + 3, "FLOOR", white);
|
_writeString(offsetX + 7, viewHeight + 3, "FLOOR", white, vgaPanelDark);
|
||||||
_writeString(
|
_writeString(
|
||||||
offsetX + 9,
|
offsetX + 9,
|
||||||
viewHeight + 5,
|
viewHeight + 5,
|
||||||
engine.activeLevel.name.split(' ').last,
|
engine.activeLevel.name.split(' ').last,
|
||||||
white,
|
white,
|
||||||
|
vgaPanelDark,
|
||||||
);
|
);
|
||||||
|
|
||||||
// SCORE
|
// SCORE
|
||||||
drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5);
|
drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5);
|
||||||
_writeString(offsetX + 27, viewHeight + 3, "SCORE", white);
|
_writeString(offsetX + 27, viewHeight + 3, "SCORE", white, vgaPanelDark);
|
||||||
_writeString(
|
_writeString(
|
||||||
offsetX + 27,
|
offsetX + 27,
|
||||||
viewHeight + 5,
|
viewHeight + 5,
|
||||||
engine.player.score.toString().padLeft(6, '0'),
|
engine.player.score.toString().padLeft(6, '0'),
|
||||||
white,
|
white,
|
||||||
|
vgaPanelDark,
|
||||||
);
|
);
|
||||||
|
|
||||||
// LIVES
|
// LIVES
|
||||||
drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5);
|
drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5);
|
||||||
_writeString(offsetX + 47, viewHeight + 3, "LIVES", white);
|
_writeString(offsetX + 47, viewHeight + 3, "LIVES", white, vgaPanelDark);
|
||||||
_writeString(offsetX + 49, viewHeight + 5, "3", white);
|
_writeString(offsetX + 49, viewHeight + 5, "3", white, vgaPanelDark);
|
||||||
|
|
||||||
// FACE (With Reactive BJ Logic)
|
// FACE (With Reactive BJ Logic)
|
||||||
drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7);
|
drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7);
|
||||||
@@ -301,30 +375,37 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
} else if (engine.player.health <= 60) {
|
} else if (engine.player.health <= 60) {
|
||||||
face = "ಠ~ಠ";
|
face = "ಠ~ಠ";
|
||||||
}
|
}
|
||||||
_writeString(offsetX + 63, viewHeight + 4, face, yellow);
|
_writeString(offsetX + 63, viewHeight + 4, face, yellow, vgaPanelDark);
|
||||||
|
|
||||||
// HEALTH
|
// HEALTH
|
||||||
int healthColor = engine.player.health > 25 ? white : red;
|
int healthColor = engine.player.health > 25 ? white : red;
|
||||||
drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5);
|
drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5);
|
||||||
_writeString(offsetX + 78, viewHeight + 3, "HEALTH", white);
|
_writeString(offsetX + 78, viewHeight + 3, "HEALTH", white, vgaPanelDark);
|
||||||
_writeString(
|
_writeString(
|
||||||
offsetX + 79,
|
offsetX + 79,
|
||||||
viewHeight + 5,
|
viewHeight + 5,
|
||||||
"${engine.player.health}%",
|
"${engine.player.health}%",
|
||||||
healthColor,
|
healthColor,
|
||||||
|
vgaPanelDark,
|
||||||
);
|
);
|
||||||
|
|
||||||
// AMMO
|
// AMMO
|
||||||
drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5);
|
drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5);
|
||||||
_writeString(offsetX + 95, viewHeight + 3, "AMMO", white);
|
_writeString(offsetX + 95, viewHeight + 3, "AMMO", white, vgaPanelDark);
|
||||||
_writeString(offsetX + 97, viewHeight + 5, "${engine.player.ammo}", white);
|
_writeString(
|
||||||
|
offsetX + 97,
|
||||||
|
viewHeight + 5,
|
||||||
|
"${engine.player.ammo}",
|
||||||
|
white,
|
||||||
|
vgaPanelDark,
|
||||||
|
);
|
||||||
|
|
||||||
// WEAPON
|
// WEAPON
|
||||||
drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5);
|
drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5);
|
||||||
String weapon = engine.player.currentWeapon.type.name.spacePascalCase!
|
String weapon = engine.player.currentWeapon.type.name.spacePascalCase!
|
||||||
.toUpperCase();
|
.toUpperCase();
|
||||||
if (weapon.length > 12) weapon = weapon.substring(0, 12);
|
if (weapon.length > 12) weapon = weapon.substring(0, 12);
|
||||||
_writeString(offsetX + 107, viewHeight + 4, weapon, white);
|
_writeString(offsetX + 107, viewHeight + 4, weapon, white, vgaPanelDark);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawFullVgaHud(WolfEngine engine) {
|
void _drawFullVgaHud(WolfEngine engine) {
|
||||||
@@ -427,8 +508,15 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
@override
|
@override
|
||||||
dynamic finalizeFrame() {
|
dynamic finalizeFrame() {
|
||||||
if (_engine.player.damageFlash > 0.0) {
|
if (_engine.player.damageFlash > 0.0) {
|
||||||
|
if (isTerminal) {
|
||||||
|
_applyDamageFlashToScene();
|
||||||
|
} else {
|
||||||
_applyDamageFlash();
|
_applyDamageFlash();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (isTerminal) {
|
||||||
|
_composeTerminalScene();
|
||||||
|
}
|
||||||
return _screen;
|
return _screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,9 +525,10 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) {
|
void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) {
|
||||||
int planeWidth = image.width ~/ 4;
|
int planeWidth = image.width ~/ 4;
|
||||||
int planeSize = planeWidth * image.height;
|
int planeSize = planeWidth * image.height;
|
||||||
|
int maxDrawHeight = isTerminal ? _terminalPixelHeight : height;
|
||||||
|
|
||||||
double scaleX = width / 320.0;
|
double scaleX = width / 320.0;
|
||||||
double scaleY = height / 200.0;
|
double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
||||||
|
|
||||||
int destStartX = (startX_320 * scaleX).toInt();
|
int destStartX = (startX_320 * scaleX).toInt();
|
||||||
int destStartY = (startY_200 * scaleY).toInt();
|
int destStartY = (startY_200 * scaleY).toInt();
|
||||||
@@ -451,7 +540,10 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
int drawX = destStartX + dx;
|
int drawX = destStartX + dx;
|
||||||
int drawY = destStartY + dy;
|
int drawY = destStartY + dy;
|
||||||
|
|
||||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) {
|
if (drawX >= 0 &&
|
||||||
|
drawX < width &&
|
||||||
|
drawY >= 0 &&
|
||||||
|
drawY < maxDrawHeight) {
|
||||||
int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1);
|
int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1);
|
||||||
int srcY = (dy / scaleY).toInt().clamp(0, image.height - 1);
|
int srcY = (dy / scaleY).toInt().clamp(0, image.height - 1);
|
||||||
|
|
||||||
@@ -461,6 +553,9 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
|
|
||||||
int colorByte = image.pixels[index];
|
int colorByte = image.pixels[index];
|
||||||
if (colorByte != 255) {
|
if (colorByte != 255) {
|
||||||
|
if (isTerminal) {
|
||||||
|
_scenePixels[drawY][drawX] = ColorPalette.vga32Bit[colorByte];
|
||||||
|
} else {
|
||||||
_screen[drawY][drawX] = ColoredChar(
|
_screen[drawY][drawX] = ColoredChar(
|
||||||
activeTheme.solid,
|
activeTheme.solid,
|
||||||
ColorPalette.vga32Bit[colorByte],
|
ColorPalette.vga32Bit[colorByte],
|
||||||
@@ -470,30 +565,90 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fillTerminalRect(int startX, int startY, int w, int h, 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 < _terminalPixelHeight) {
|
||||||
|
_scenePixels[y][x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- DAMAGE FLASH ---
|
// --- DAMAGE FLASH ---
|
||||||
void _applyDamageFlash() {
|
void _applyDamageFlash() {
|
||||||
|
for (int y = 0; y < viewHeight; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
ColoredChar cell = _screen[y][x];
|
||||||
|
_screen[y][x] = ColoredChar(
|
||||||
|
cell.char,
|
||||||
|
_applyDamageFlashToColor(cell.rawColor),
|
||||||
|
cell.rawBackgroundColor == null
|
||||||
|
? null
|
||||||
|
: _applyDamageFlashToColor(cell.rawBackgroundColor!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applyDamageFlashToScene() {
|
||||||
|
for (int y = 0; y < _terminalPixelHeight; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
_scenePixels[y][x] = _applyDamageFlashToColor(_scenePixels[y][x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _applyDamageFlashToColor(int color) {
|
||||||
double intensity = _engine.player.damageFlash;
|
double intensity = _engine.player.damageFlash;
|
||||||
int redBoost = (150 * intensity).toInt();
|
int redBoost = (150 * intensity).toInt();
|
||||||
double colorDrop = 1.0 - (0.5 * intensity);
|
double colorDrop = 1.0 - (0.5 * intensity);
|
||||||
|
|
||||||
for (int y = 0; y < viewHeight; y++) {
|
int r = color & 0xFF;
|
||||||
for (int x = 0; x < width; x++) {
|
int g = (color >> 8) & 0xFF;
|
||||||
ColoredChar cell = _screen[y][x];
|
int b = (color >> 16) & 0xFF;
|
||||||
|
|
||||||
// Use our safe getters!
|
|
||||||
int r = cell.r;
|
|
||||||
int g = cell.g;
|
|
||||||
int b = cell.b;
|
|
||||||
|
|
||||||
r = (r + redBoost).clamp(0, 255);
|
r = (r + redBoost).clamp(0, 255);
|
||||||
g = (g * colorDrop).toInt().clamp(0, 255);
|
g = (g * colorDrop).toInt().clamp(0, 255);
|
||||||
b = (b * colorDrop).toInt().clamp(0, 255);
|
b = (b * colorDrop).toInt().clamp(0, 255);
|
||||||
|
|
||||||
// Pack back into the native AABBGGRR format that ColoredChar expects
|
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||||
int newRawColor = (0xFF000000) | (b << 16) | (g << 8) | r;
|
}
|
||||||
|
|
||||||
_screen[y][x] = ColoredChar(cell.char, newRawColor);
|
int _scaleColor(int color, double brightness) {
|
||||||
|
int r = ((color & 0xFF) * brightness).toInt().clamp(0, 255);
|
||||||
|
int g = (((color >> 8) & 0xFF) * brightness).toInt().clamp(0, 255);
|
||||||
|
int b = (((color >> 16) & 0xFF) * brightness).toInt().clamp(0, 255);
|
||||||
|
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _composeTerminalScene() {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
int topY = y * 2;
|
||||||
|
int bottomY = math.min(topY + 1, _terminalPixelHeight - 1);
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int topColor = _scenePixels[topY][x];
|
||||||
|
int bottomColor = _scenePixels[bottomY][x];
|
||||||
|
|
||||||
|
ColoredChar overlay = _screen[y][x];
|
||||||
|
if (overlay.char != ' ') {
|
||||||
|
if (overlay.rawBackgroundColor == null) {
|
||||||
|
_screen[y][x] = ColoredChar(
|
||||||
|
overlay.char,
|
||||||
|
overlay.rawColor,
|
||||||
|
bottomColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_screen[y][x] = topColor == bottomColor
|
||||||
|
? ColoredChar('█', topColor)
|
||||||
|
: ColoredChar('▀', topColor, bottomColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,18 +657,30 @@ class AsciiRasterizer extends Rasterizer {
|
|||||||
String toAnsiString() {
|
String toAnsiString() {
|
||||||
StringBuffer buffer = StringBuffer();
|
StringBuffer buffer = StringBuffer();
|
||||||
|
|
||||||
int lastR = -1;
|
int? lastForeground;
|
||||||
int lastG = -1;
|
int? lastBackground;
|
||||||
int lastB = -1;
|
|
||||||
|
|
||||||
for (int y = 0; y < _screen.length; y++) {
|
for (int y = 0; y < _screen.length; y++) {
|
||||||
List<ColoredChar> row = _screen[y];
|
List<ColoredChar> row = _screen[y];
|
||||||
for (ColoredChar cell in row) {
|
for (ColoredChar cell in row) {
|
||||||
if (cell.r != lastR || cell.g != lastG || cell.b != lastB) {
|
if (cell.rawColor != lastForeground) {
|
||||||
buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m');
|
buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m');
|
||||||
lastR = cell.r;
|
lastForeground = cell.rawColor;
|
||||||
lastG = cell.g;
|
}
|
||||||
lastB = cell.b;
|
if (cell.rawBackgroundColor != lastBackground) {
|
||||||
|
if (cell.rawBackgroundColor == null) {
|
||||||
|
buffer.write('\x1b[49m');
|
||||||
|
} else {
|
||||||
|
int background = cell.rawBackgroundColor!;
|
||||||
|
int bgR = background & 0xFF;
|
||||||
|
int bgG = (background >> 8) & 0xFF;
|
||||||
|
int bgB = (background >> 16) & 0xFF;
|
||||||
|
buffer.write(
|
||||||
|
'\x1b[48;2;$bgR;$bgG;$bgB'
|
||||||
|
'm',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lastBackground = cell.rawBackgroundColor;
|
||||||
}
|
}
|
||||||
buffer.write(cell.char);
|
buffer.write(cell.char);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ abstract class Rasterizer {
|
|||||||
/// Defaults to 1.0 (no squish) for standard pixel rendering.
|
/// Defaults to 1.0 (no squish) for standard pixel rendering.
|
||||||
double get verticalStretch => 1.0;
|
double get verticalStretch => 1.0;
|
||||||
|
|
||||||
|
/// The logical height of the 3D projection before a renderer maps rows to output pixels.
|
||||||
|
/// Most renderers use the visible view height. Terminal ASCII can override this to render
|
||||||
|
/// more vertical detail and collapse it into half-block glyphs.
|
||||||
|
int get projectionViewHeight => viewHeight;
|
||||||
|
|
||||||
/// The main entry point called by the game loop.
|
/// The main entry point called by the game loop.
|
||||||
/// Orchestrates the mathematical rendering pipeline.
|
/// Orchestrates the mathematical rendering pipeline.
|
||||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
||||||
@@ -103,6 +108,7 @@ abstract class Rasterizer {
|
|||||||
final Player player = engine.player;
|
final Player player = engine.player;
|
||||||
final SpriteMap map = engine.currentLevel;
|
final SpriteMap map = engine.currentLevel;
|
||||||
final List<Sprite> wallTextures = engine.data.walls;
|
final List<Sprite> wallTextures = engine.data.walls;
|
||||||
|
final int sceneHeight = projectionViewHeight;
|
||||||
|
|
||||||
final Map<String, double> doorOffsets = engine.doorManager
|
final Map<String, double> doorOffsets = engine.doorManager
|
||||||
.getOffsetsForRenderer();
|
.getOffsetsForRenderer();
|
||||||
@@ -283,13 +289,16 @@ abstract class Rasterizer {
|
|||||||
if (side == 1 && math.sin(player.angle) < 0) texX = 63 - texX;
|
if (side == 1 && math.sin(player.angle) < 0) texX = 63 - texX;
|
||||||
|
|
||||||
// Calculate drawing dimensions
|
// Calculate drawing dimensions
|
||||||
int columnHeight = ((viewHeight / perpWallDist) * verticalStretch)
|
int columnHeight = ((sceneHeight / perpWallDist) * verticalStretch)
|
||||||
.toInt();
|
.toInt();
|
||||||
int drawStart = (-columnHeight ~/ 2 + viewHeight ~/ 2).clamp(
|
int drawStart = (-columnHeight ~/ 2 + sceneHeight ~/ 2).clamp(
|
||||||
0,
|
0,
|
||||||
viewHeight,
|
sceneHeight,
|
||||||
|
);
|
||||||
|
int drawEnd = (columnHeight ~/ 2 + sceneHeight ~/ 2).clamp(
|
||||||
|
0,
|
||||||
|
sceneHeight,
|
||||||
);
|
);
|
||||||
int drawEnd = (columnHeight ~/ 2 + viewHeight ~/ 2).clamp(0, viewHeight);
|
|
||||||
|
|
||||||
// Tell the implementation to draw this column
|
// Tell the implementation to draw this column
|
||||||
drawWallColumn(
|
drawWallColumn(
|
||||||
@@ -308,6 +317,7 @@ abstract class Rasterizer {
|
|||||||
void _castSprites(WolfEngine engine) {
|
void _castSprites(WolfEngine engine) {
|
||||||
final Player player = engine.player;
|
final Player player = engine.player;
|
||||||
final List<Entity> activeSprites = List.from(engine.entities);
|
final List<Entity> activeSprites = List.from(engine.entities);
|
||||||
|
final int sceneHeight = projectionViewHeight;
|
||||||
|
|
||||||
// Sort from furthest to closest (Painter's Algorithm)
|
// Sort from furthest to closest (Painter's Algorithm)
|
||||||
activeSprites.sort((a, b) {
|
activeSprites.sort((a, b) {
|
||||||
@@ -335,20 +345,23 @@ abstract class Rasterizer {
|
|||||||
if (transformY > 0) {
|
if (transformY > 0) {
|
||||||
int spriteScreenX = ((width / 2) * (1 + transformX / transformY))
|
int spriteScreenX = ((width / 2) * (1 + transformX / transformY))
|
||||||
.toInt();
|
.toInt();
|
||||||
int spriteHeight = ((viewHeight / transformY).abs() * verticalStretch)
|
int spriteHeight = ((sceneHeight / transformY).abs() * verticalStretch)
|
||||||
.toInt();
|
.toInt();
|
||||||
|
int displayedSpriteHeight =
|
||||||
|
((viewHeight / transformY).abs() * verticalStretch).toInt();
|
||||||
|
|
||||||
// Scale width based on the aspectMultiplier (useful for ASCII)
|
// Scale width based on the aspectMultiplier (useful for ASCII)
|
||||||
int spriteWidth = (spriteHeight * aspectMultiplier / verticalStretch)
|
int spriteWidth =
|
||||||
|
(displayedSpriteHeight * aspectMultiplier / verticalStretch)
|
||||||
.toInt();
|
.toInt();
|
||||||
|
|
||||||
int drawStartY = -spriteHeight ~/ 2 + viewHeight ~/ 2;
|
int drawStartY = -spriteHeight ~/ 2 + sceneHeight ~/ 2;
|
||||||
int drawEndY = spriteHeight ~/ 2 + viewHeight ~/ 2;
|
int drawEndY = spriteHeight ~/ 2 + sceneHeight ~/ 2;
|
||||||
int drawStartX = -spriteWidth ~/ 2 + spriteScreenX;
|
int drawStartX = -spriteWidth ~/ 2 + spriteScreenX;
|
||||||
int drawEndX = spriteWidth ~/ 2 + spriteScreenX;
|
int drawEndX = spriteWidth ~/ 2 + spriteScreenX;
|
||||||
|
|
||||||
int clipStartX = math.max(0, drawStartX);
|
int clipStartX = math.max(0, drawStartX);
|
||||||
int clipEndX = math.min(width - 1, drawEndX);
|
int clipEndX = math.min(width, drawEndX);
|
||||||
|
|
||||||
int safeIndex = entity.spriteIndex.clamp(
|
int safeIndex = entity.spriteIndex.clamp(
|
||||||
0,
|
0,
|
||||||
|
|||||||
Reference in New Issue
Block a user