feat: Implement map overlay toggle functionality and rendering across input and rendering systems
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -406,6 +406,93 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
_writeString(1, 0, ' ${fpsLabel(engine)} ', textColor, bgColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawGameplayOverlay(WolfEngine engine) {
|
||||
if (!engine.isMapOverlayVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int mapBgColor = ColorPalette.vga32Bit[0];
|
||||
final int floorColor = ColorPalette.vga32Bit[8];
|
||||
final int wallColor = ColorPalette.vga32Bit[7];
|
||||
final int doorColor = ColorPalette.vga32Bit[14];
|
||||
final int playerColor = ColorPalette.vga32Bit[10];
|
||||
final int facingColor = ColorPalette.vga32Bit[12];
|
||||
|
||||
if (_usesTerminalLayout) {
|
||||
_fillTerminalRect(
|
||||
projectionOffsetX,
|
||||
0,
|
||||
projectionWidth,
|
||||
_terminalPixelHeight,
|
||||
mapBgColor,
|
||||
);
|
||||
} else {
|
||||
_fillRect(0, 0, width, height, activeTheme.solid, mapBgColor);
|
||||
}
|
||||
|
||||
final int viewportX = _usesTerminalLayout ? projectionOffsetX : 0;
|
||||
final int viewportWidth = _usesTerminalLayout ? projectionWidth : width;
|
||||
final int viewportHeight = _usesTerminalLayout
|
||||
? _terminalPixelHeight
|
||||
: height;
|
||||
final int viewportPadding = math.max(
|
||||
3,
|
||||
math.min(viewportWidth, viewportHeight) ~/ 24,
|
||||
);
|
||||
final int availableWidth = math.max(
|
||||
1,
|
||||
viewportWidth - (viewportPadding * 2),
|
||||
);
|
||||
final int availableHeight = math.max(
|
||||
1,
|
||||
viewportHeight - (viewportPadding * 2),
|
||||
);
|
||||
final int tileSize = math.max(
|
||||
1,
|
||||
math.min(availableWidth, availableHeight) ~/ 64,
|
||||
);
|
||||
final int mapPixelWidth = tileSize * 64;
|
||||
final int mapPixelHeight = tileSize * 64;
|
||||
final int mapStartX = viewportX + ((viewportWidth - mapPixelWidth) ~/ 2);
|
||||
final int mapStartY = (viewportHeight - mapPixelHeight) ~/ 2;
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
final int tileId = engine.currentLevel[y][x];
|
||||
final int color = tileId == 0
|
||||
? floorColor
|
||||
: (tileId >= 90 ? doorColor : wallColor);
|
||||
_fillMapRect(
|
||||
mapStartX + (x * tileSize),
|
||||
mapStartY + (y * tileSize),
|
||||
tileSize,
|
||||
tileSize,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final int playerX = (mapStartX + (engine.player.x * tileSize)).floor();
|
||||
final int playerY = (mapStartY + (engine.player.y * tileSize)).floor();
|
||||
final int markerRadius = math.max(1, tileSize ~/ 2);
|
||||
final int markerSize = (markerRadius * 2) + 1;
|
||||
_fillMapRect(
|
||||
playerX - markerRadius,
|
||||
playerY - markerRadius,
|
||||
markerSize,
|
||||
markerSize,
|
||||
playerColor,
|
||||
);
|
||||
|
||||
final int facingLength = math.max(4, (tileSize * 3) ~/ 2);
|
||||
final int facingEndX =
|
||||
(playerX + (math.cos(engine.player.angle) * facingLength)).round();
|
||||
final int facingEndY =
|
||||
(playerY + (math.sin(engine.player.angle) * facingLength)).round();
|
||||
_drawMapLine(playerX, playerY, facingEndX, facingEndY, facingColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawMenu(WolfEngine engine) {
|
||||
final int bgColor = _rgbToPaletteColor(
|
||||
@@ -2053,6 +2140,48 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
|
||||
}
|
||||
}
|
||||
|
||||
void _fillMapRect(int startX, int startY, int w, int h, int color) {
|
||||
for (int y = startY; y < startY + h; y++) {
|
||||
for (int x = startX; x < startX + w; x++) {
|
||||
_setMapPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _drawMapLine(int x0, int y0, int x1, int y1, int color) {
|
||||
final int dx = x1 - x0;
|
||||
final int dy = y1 - y0;
|
||||
final int steps = math.max(dx.abs(), dy.abs());
|
||||
if (steps == 0) {
|
||||
_setMapPixel(x0, y0, color);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
final double t = i / steps;
|
||||
final int x = (x0 + (dx * t)).round();
|
||||
final int y = (y0 + (dy * t)).round();
|
||||
_setMapPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
void _setMapPixel(int x, int y, int color) {
|
||||
if (_usesTerminalLayout) {
|
||||
if (x < projectionOffsetX || x >= _viewportRightX) {
|
||||
return;
|
||||
}
|
||||
if (y < 0 || y >= _terminalPixelHeight) {
|
||||
return;
|
||||
}
|
||||
_scenePixels[y][x] = color;
|
||||
return;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return;
|
||||
}
|
||||
_screen[y][x] = ColoredChar(activeTheme.solid, color);
|
||||
}
|
||||
|
||||
// --- DAMAGE FLASH ---
|
||||
void _applyDamageFlash() {
|
||||
for (int y = 0; y < viewHeight; y++) {
|
||||
|
||||
@@ -91,6 +91,7 @@ abstract class RendererBackend<T>
|
||||
// 3. Draw 2D overlays.
|
||||
drawWeapon(engine);
|
||||
drawHud(engine);
|
||||
drawGameplayOverlay(engine);
|
||||
if (engine.showFpsCounter) {
|
||||
drawFpsOverlay(engine);
|
||||
}
|
||||
@@ -140,6 +141,11 @@ abstract class RendererBackend<T>
|
||||
/// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
|
||||
T finalizeFrame();
|
||||
|
||||
/// Draws gameplay overlays that should appear above world/HUD content.
|
||||
///
|
||||
/// Default implementation is a no-op.
|
||||
void drawGameplayOverlay(WolfEngine engine) {}
|
||||
|
||||
/// Draws a non-world menu frame when the engine is awaiting configuration.
|
||||
///
|
||||
/// Default implementation is a no-op for backends that don't support menus.
|
||||
|
||||
@@ -360,6 +360,71 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
_drawMenuText(label, 4, 3, textColor, scale: 1);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawGameplayOverlay(WolfEngine engine) {
|
||||
if (!engine.isMapOverlayVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int mapBgColor = 0;
|
||||
const int floorColor = 8;
|
||||
const int wallColor = 7;
|
||||
const int doorColor = 14;
|
||||
const int playerColor = 10;
|
||||
const int facingColor = 12;
|
||||
|
||||
for (int i = 0; i < _screen.length; i++) {
|
||||
_screen[i] = mapBgColor;
|
||||
}
|
||||
|
||||
final int viewportPadding = math.max(3, math.min(width, height) ~/ 24);
|
||||
final int availableWidth = math.max(1, width - (viewportPadding * 2));
|
||||
final int availableHeight = math.max(1, height - (viewportPadding * 2));
|
||||
final int tileSize = math.max(
|
||||
1,
|
||||
math.min(availableWidth, availableHeight) ~/ 64,
|
||||
);
|
||||
final int mapPixelWidth = tileSize * 64;
|
||||
final int mapPixelHeight = tileSize * 64;
|
||||
final int mapStartX = (width - mapPixelWidth) ~/ 2;
|
||||
final int mapStartY = (height - mapPixelHeight) ~/ 2;
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
final int tileId = engine.currentLevel[y][x];
|
||||
final int color = tileId == 0
|
||||
? floorColor
|
||||
: (tileId >= 90 ? doorColor : wallColor);
|
||||
_fillMapRect(
|
||||
mapStartX + (x * tileSize),
|
||||
mapStartY + (y * tileSize),
|
||||
tileSize,
|
||||
tileSize,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final int playerX = (mapStartX + (engine.player.x * tileSize)).floor();
|
||||
final int playerY = (mapStartY + (engine.player.y * tileSize)).floor();
|
||||
final int markerRadius = math.max(1, tileSize ~/ 2);
|
||||
final int markerSize = (markerRadius * 2) + 1;
|
||||
_fillMapRect(
|
||||
playerX - markerRadius,
|
||||
playerY - markerRadius,
|
||||
markerSize,
|
||||
markerSize,
|
||||
playerColor,
|
||||
);
|
||||
|
||||
final int facingLength = math.max(4, (tileSize * 3) ~/ 2);
|
||||
final int facingEndX =
|
||||
(playerX + (math.cos(engine.player.angle) * facingLength)).round();
|
||||
final int facingEndY =
|
||||
(playerY + (math.sin(engine.player.angle) * facingLength)).round();
|
||||
_drawMapLine(playerX, playerY, facingEndX, facingEndY, facingColor);
|
||||
}
|
||||
|
||||
@override
|
||||
/// Blits a VGA image into the Sixel index buffer HUD space (320x200).
|
||||
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
||||
@@ -1392,6 +1457,37 @@ class SixelRenderer extends CliRendererBackend<String> {
|
||||
}
|
||||
}
|
||||
|
||||
void _fillMapRect(int startX, int startY, int w, int h, int colorIndex) {
|
||||
for (int y = startY; y < startY + h; y++) {
|
||||
for (int x = startX; x < startX + w; x++) {
|
||||
_setMapPixel(x, y, colorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _drawMapLine(int x0, int y0, int x1, int y1, int colorIndex) {
|
||||
final int dx = x1 - x0;
|
||||
final int dy = y1 - y0;
|
||||
final int steps = math.max(dx.abs(), dy.abs());
|
||||
if (steps == 0) {
|
||||
_setMapPixel(x0, y0, colorIndex);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
final double t = i / steps;
|
||||
final int x = (x0 + (dx * t)).round();
|
||||
final int y = (y0 + (dy * t)).round();
|
||||
_setMapPixel(x, y, colorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void _setMapPixel(int x, int y, int colorIndex) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return;
|
||||
}
|
||||
_screen[(y * width) + x] = colorIndex;
|
||||
}
|
||||
|
||||
/// Maps an RGB color to the nearest VGA palette index.
|
||||
int _rgbToPaletteIndex(int rgb) {
|
||||
return ColorPalette.findClosestPaletteIndex(rgb);
|
||||
|
||||
@@ -140,6 +140,69 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
_drawMenuText(fpsLabel(engine), panelX + 3, panelY + 1, textColor);
|
||||
}
|
||||
|
||||
@override
|
||||
void drawGameplayOverlay(WolfEngine engine) {
|
||||
if (!engine.isMapOverlayVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int mapBgColor = ColorPalette.vga32Bit[0];
|
||||
final int floorColor = ColorPalette.vga32Bit[8];
|
||||
final int wallColor = ColorPalette.vga32Bit[7];
|
||||
final int doorColor = ColorPalette.vga32Bit[14];
|
||||
final int playerColor = ColorPalette.vga32Bit[10];
|
||||
final int facingColor = ColorPalette.vga32Bit[12];
|
||||
|
||||
_fillMenuPanel(0, 0, width, height, mapBgColor);
|
||||
|
||||
final int viewportPadding = math.max(6, math.min(width, height) ~/ 24);
|
||||
final int availableWidth = math.max(1, width - (viewportPadding * 2));
|
||||
final int availableHeight = math.max(1, height - (viewportPadding * 2));
|
||||
final int tileSize = math.max(
|
||||
1,
|
||||
math.min(availableWidth, availableHeight) ~/ 64,
|
||||
);
|
||||
final int mapPixelWidth = tileSize * 64;
|
||||
final int mapPixelHeight = tileSize * 64;
|
||||
final int mapStartX = (width - mapPixelWidth) ~/ 2;
|
||||
final int mapStartY = (height - mapPixelHeight) ~/ 2;
|
||||
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
final int tileId = engine.currentLevel[y][x];
|
||||
final int color = tileId == 0
|
||||
? floorColor
|
||||
: (tileId >= 90 ? doorColor : wallColor);
|
||||
_fillMenuPanel(
|
||||
mapStartX + (x * tileSize),
|
||||
mapStartY + (y * tileSize),
|
||||
tileSize,
|
||||
tileSize,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final int playerX = (mapStartX + (engine.player.x * tileSize)).floor();
|
||||
final int playerY = (mapStartY + (engine.player.y * tileSize)).floor();
|
||||
final int markerRadius = math.max(1, tileSize ~/ 2);
|
||||
final int markerSize = (markerRadius * 2) + 1;
|
||||
_fillMenuPanel(
|
||||
playerX - markerRadius,
|
||||
playerY - markerRadius,
|
||||
markerSize,
|
||||
markerSize,
|
||||
playerColor,
|
||||
);
|
||||
|
||||
final int facingLength = math.max(4, (tileSize * 3) ~/ 2);
|
||||
final int facingEndX =
|
||||
(playerX + (math.cos(engine.player.angle) * facingLength)).round();
|
||||
final int facingEndY =
|
||||
(playerY + (math.sin(engine.player.angle) * facingLength)).round();
|
||||
_drawMapLine(playerX, playerY, facingEndX, facingEndY, facingColor);
|
||||
}
|
||||
|
||||
@override
|
||||
/// Blits a VGA image into the software framebuffer HUD space (320x200).
|
||||
void blitHudVgaImage(VgaImage image, int startX320, int startY200) {
|
||||
@@ -925,6 +988,29 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
void _drawMapLine(int x0, int y0, int x1, int y1, int color) {
|
||||
final int dx = x1 - x0;
|
||||
final int dy = y1 - y0;
|
||||
final int steps = math.max(dx.abs(), dy.abs());
|
||||
if (steps == 0) {
|
||||
_setMapPixel(x0, y0, color);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
final double t = i / steps;
|
||||
final int x = (x0 + (dx * t)).round();
|
||||
final int y = (y0 + (dy * t)).round();
|
||||
_setMapPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
void _setMapPixel(int x, int y, int color) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return;
|
||||
}
|
||||
_buffer.pixels[(y * width) + x] = color;
|
||||
}
|
||||
|
||||
String _gameTitle(GameVersion version) {
|
||||
switch (version) {
|
||||
case GameVersion.shareware:
|
||||
|
||||
Reference in New Issue
Block a user