|
|
|
|
@@ -61,6 +61,21 @@ class ColoredChar {
|
|
|
|
|
|
|
|
|
|
// Outputs standard AARRGGBB for Flutter's Color(int) constructor
|
|
|
|
|
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
|
|
|
|
|
|
|
|
|
|
// Same AABBGGRR → AARRGGBB conversion for the background channel.
|
|
|
|
|
int? get backgroundArgb {
|
|
|
|
|
final bg = rawBackgroundColor;
|
|
|
|
|
if (bg == null) return null;
|
|
|
|
|
final int bgR = bg & 0xFF;
|
|
|
|
|
final int bgG = (bg >> 8) & 0xFF;
|
|
|
|
|
final int bgB = (bg >> 16) & 0xFF;
|
|
|
|
|
return 0xFF000000 | (bgR << 16) | (bgG << 8) | bgB;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum AsciiRasterizerMode {
|
|
|
|
|
terminalAnsi,
|
|
|
|
|
terminalGrid,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
@@ -71,21 +86,23 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
static const int _simpleHudMinWidth = 84;
|
|
|
|
|
static const int _simpleHudMinRows = 7;
|
|
|
|
|
|
|
|
|
|
static const int _menuSelectedTextPaletteIndex = 19;
|
|
|
|
|
static const int _menuUnselectedTextPaletteIndex = 23;
|
|
|
|
|
static const int _menuHintKeyPaletteIndex = 12;
|
|
|
|
|
static const int _menuHintLabelPaletteIndex = 4;
|
|
|
|
|
static const int _menuHintBackgroundPaletteIndex = 0;
|
|
|
|
|
|
|
|
|
|
AsciiRasterizer({
|
|
|
|
|
this.activeTheme = AsciiThemes.blocks,
|
|
|
|
|
this.isTerminal = false,
|
|
|
|
|
this.mode = AsciiRasterizerMode.terminalGrid,
|
|
|
|
|
this.aspectMultiplier = 1.0,
|
|
|
|
|
this.verticalStretch = 1.0,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
AsciiTheme activeTheme = AsciiThemes.blocks;
|
|
|
|
|
final bool isTerminal;
|
|
|
|
|
final AsciiRasterizerMode mode;
|
|
|
|
|
|
|
|
|
|
bool get _usesTerminalLayout => true;
|
|
|
|
|
|
|
|
|
|
bool get _emitAnsi => mode == AsciiRasterizerMode.terminalAnsi;
|
|
|
|
|
|
|
|
|
|
late List<List<ColoredChar>> _screen;
|
|
|
|
|
late List<List<int>> _scenePixels;
|
|
|
|
|
@@ -97,7 +114,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
final double verticalStretch;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
int get projectionWidth => isTerminal
|
|
|
|
|
int get projectionWidth => _usesTerminalLayout
|
|
|
|
|
? math.max(
|
|
|
|
|
1,
|
|
|
|
|
math.min(width, (_terminalPixelHeight * _targetAspectRatio).floor()),
|
|
|
|
|
@@ -105,14 +122,16 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
: width;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
int get projectionOffsetX => isTerminal ? (width - projectionWidth) ~/ 2 : 0;
|
|
|
|
|
int get projectionOffsetX =>
|
|
|
|
|
_usesTerminalLayout ? (width - projectionWidth) ~/ 2 : 0;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
int get projectionViewHeight => isTerminal ? viewHeight * 2 : viewHeight;
|
|
|
|
|
int get projectionViewHeight =>
|
|
|
|
|
_usesTerminalLayout ? viewHeight * 2 : viewHeight;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
bool isTerminalSizeSupported(int columns, int rows) {
|
|
|
|
|
if (!isTerminal) {
|
|
|
|
|
if (!_usesTerminalLayout) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return columns >= _minimumTerminalColumns && rows >= _minimumTerminalRows;
|
|
|
|
|
@@ -123,7 +142,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
'ASCII renderer requires a minimum resolution of '
|
|
|
|
|
'${_minimumTerminalColumns}x$_minimumTerminalRows.';
|
|
|
|
|
|
|
|
|
|
int get _terminalPixelHeight => isTerminal ? height * 2 : height;
|
|
|
|
|
int get _terminalPixelHeight => _usesTerminalLayout ? height * 2 : height;
|
|
|
|
|
|
|
|
|
|
int get _viewportRightX => projectionOffsetX + projectionWidth;
|
|
|
|
|
|
|
|
|
|
@@ -149,7 +168,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
// Just grab the raw ints!
|
|
|
|
|
final int ceilingColor = ColorPalette.vga32Bit[25];
|
|
|
|
|
final int floorColor = ColorPalette.vga32Bit[29];
|
|
|
|
|
final int backdropColor = isTerminal
|
|
|
|
|
final int backdropColor = _usesTerminalLayout
|
|
|
|
|
? _terminalBackdropColor
|
|
|
|
|
: ColorPalette.vga32Bit[0];
|
|
|
|
|
|
|
|
|
|
@@ -167,7 +186,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isTerminal) {
|
|
|
|
|
if (!_usesTerminalLayout) {
|
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
|
|
|
for (int x = 0; x < width; x++) {
|
|
|
|
|
if (y < viewHeight / 2) {
|
|
|
|
|
@@ -206,7 +225,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
pixelColor = shadeColor(pixelColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_scenePixels[y][x] = _scaleColor(pixelColor, brightness);
|
|
|
|
|
} else {
|
|
|
|
|
final String wallChar = activeTheme.getByBrightness(brightness);
|
|
|
|
|
@@ -250,7 +269,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
|
|
|
|
|
int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r;
|
|
|
|
|
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_scenePixels[y][stripeX] = shadedColor;
|
|
|
|
|
} else {
|
|
|
|
|
_screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
|
|
|
|
|
@@ -274,7 +293,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
int startY =
|
|
|
|
|
projectionViewHeight -
|
|
|
|
|
weaponHeight +
|
|
|
|
|
(engine.player.weaponAnimOffset * (isTerminal ? 2 : 1) ~/ 4);
|
|
|
|
|
(engine.player.weaponAnimOffset * (_usesTerminalLayout ? 2 : 1) ~/ 4);
|
|
|
|
|
|
|
|
|
|
for (int dy = 0; dy < weaponHeight; dy++) {
|
|
|
|
|
for (int dx = 0; dx < weaponWidth; dx++) {
|
|
|
|
|
@@ -288,9 +307,9 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
if (sceneX >= projectionOffsetX &&
|
|
|
|
|
sceneX < _viewportRightX &&
|
|
|
|
|
drawY >= 0) {
|
|
|
|
|
if (isTerminal && drawY < projectionViewHeight) {
|
|
|
|
|
if (_usesTerminalLayout && drawY < projectionViewHeight) {
|
|
|
|
|
_scenePixels[drawY][sceneX] = ColorPalette.vga32Bit[colorByte];
|
|
|
|
|
} else if (!isTerminal && drawY < viewHeight) {
|
|
|
|
|
} else if (!_usesTerminalLayout && drawY < viewHeight) {
|
|
|
|
|
_screen[drawY][sceneX] = ColoredChar(
|
|
|
|
|
activeTheme.solid,
|
|
|
|
|
ColorPalette.vga32Bit[colorByte],
|
|
|
|
|
@@ -324,7 +343,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
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.
|
|
|
|
|
int hudWidth = isTerminal ? projectionWidth : width;
|
|
|
|
|
int hudWidth = _usesTerminalLayout ? projectionWidth : width;
|
|
|
|
|
if (hudWidth >= 160 && height >= 50) {
|
|
|
|
|
_drawFullVgaHud(engine);
|
|
|
|
|
} else {
|
|
|
|
|
@@ -338,14 +357,11 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
engine.menuManager.selectedDifficultyIndex;
|
|
|
|
|
final int bgColor = _rgbToPaletteColor(engine.menuBackgroundRgb);
|
|
|
|
|
final int panelColor = _rgbToPaletteColor(engine.menuPanelRgb);
|
|
|
|
|
// Exact title tint requested for menu heading text (#fff700).
|
|
|
|
|
final int headingColor = _rgbToRawColor(0xFFF700);
|
|
|
|
|
final int selectedTextColor =
|
|
|
|
|
ColorPalette.vga32Bit[_menuSelectedTextPaletteIndex];
|
|
|
|
|
final int unselectedTextColor =
|
|
|
|
|
ColorPalette.vga32Bit[_menuUnselectedTextPaletteIndex];
|
|
|
|
|
final int headingColor = WolfMenuPalette.headerTextColor;
|
|
|
|
|
final int selectedTextColor = WolfMenuPalette.selectedTextColor;
|
|
|
|
|
final int unselectedTextColor = WolfMenuPalette.unselectedTextColor;
|
|
|
|
|
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_fillTerminalRect(0, 0, width, _terminalPixelHeight, bgColor);
|
|
|
|
|
} else {
|
|
|
|
|
_fillRect(0, 0, width, height, activeTheme.solid, bgColor);
|
|
|
|
|
@@ -464,7 +480,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int get _fullMenuHeadingScale {
|
|
|
|
|
if (!isTerminal) {
|
|
|
|
|
if (!_usesTerminalLayout) {
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
return projectionWidth < 140 ? 1 : 2;
|
|
|
|
|
@@ -473,7 +489,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
bool get _useMinimalMenuText => _menuGlyphHeightInRows(scale: 1) <= 4;
|
|
|
|
|
|
|
|
|
|
int _menuGlyphHeightInRows({required int scale}) {
|
|
|
|
|
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
final double scaleY =
|
|
|
|
|
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
// Use pre-compose pixel scale so terminal mode can graduate back to
|
|
|
|
|
// bitmap menu text at practical window sizes.
|
|
|
|
|
final double rasterHeight = 7.0 * scale * scaleY;
|
|
|
|
|
@@ -481,15 +498,17 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _menuY200ToRow(int y200) {
|
|
|
|
|
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
final double scaleY =
|
|
|
|
|
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
final int pixelY = (y200 * scaleY).toInt();
|
|
|
|
|
final int row = isTerminal ? (pixelY / 2).round() : pixelY;
|
|
|
|
|
final int row = _usesTerminalLayout ? (pixelY / 2).round() : pixelY;
|
|
|
|
|
return row.clamp(0, height - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _menuX320ToColumn(int x320) {
|
|
|
|
|
final double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
|
|
|
|
|
final int offsetX = isTerminal ? projectionOffsetX : 0;
|
|
|
|
|
final double scaleX =
|
|
|
|
|
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
|
|
|
|
|
final int offsetX = _usesTerminalLayout ? projectionOffsetX : 0;
|
|
|
|
|
final int pixelX = offsetX + (x320 * scaleX).toInt();
|
|
|
|
|
return pixelX.clamp(0, width - 1);
|
|
|
|
|
}
|
|
|
|
|
@@ -517,10 +536,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
const int rowStep = 15;
|
|
|
|
|
for (int i = 0; i < Difficulty.values.length; i++) {
|
|
|
|
|
final bool isSelected = i == selectedDifficultyIndex;
|
|
|
|
|
final int baseRowY = _menuY200ToRow(rowYStart + (i * rowStep));
|
|
|
|
|
final int rowY = i == Difficulty.values.length - 1
|
|
|
|
|
? (baseRowY + 1).clamp(0, height - 1)
|
|
|
|
|
: baseRowY;
|
|
|
|
|
final int rowY = _menuY200ToRow(rowYStart + (i * rowStep));
|
|
|
|
|
|
|
|
|
|
final String rowText = Difficulty.values[i].title;
|
|
|
|
|
_writeLeftClipped(
|
|
|
|
|
rowY,
|
|
|
|
|
@@ -534,6 +551,11 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _drawCenteredMenuFooter() {
|
|
|
|
|
if (_usesTerminalLayout && !_emitAnsi) {
|
|
|
|
|
_drawFlutterGridMenuFooter();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final int hintKeyColor = ColorPalette.vga32Bit[_menuHintKeyPaletteIndex];
|
|
|
|
|
final int hintLabelColor =
|
|
|
|
|
ColorPalette.vga32Bit[_menuHintLabelPaletteIndex];
|
|
|
|
|
@@ -558,7 +580,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
final int boxX = ((width - boxWidth) ~/ 2).clamp(0, width - boxWidth);
|
|
|
|
|
final int boxY = math.max(0, height - boxHeight - 1);
|
|
|
|
|
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_fillTerminalRect(
|
|
|
|
|
boxX,
|
|
|
|
|
boxY * 2,
|
|
|
|
|
@@ -587,6 +609,41 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _drawFlutterGridMenuFooter() {
|
|
|
|
|
final int hintKeyColor = ColorPalette.vga32Bit[_menuHintKeyPaletteIndex];
|
|
|
|
|
final int hintLabelColor =
|
|
|
|
|
ColorPalette.vga32Bit[_menuHintLabelPaletteIndex];
|
|
|
|
|
final int hintBackground =
|
|
|
|
|
ColorPalette.vga32Bit[_menuHintBackgroundPaletteIndex];
|
|
|
|
|
|
|
|
|
|
final List<(String text, int color)> segments = <(String, int)>[
|
|
|
|
|
('UP/DN', hintKeyColor),
|
|
|
|
|
(' MOVE ', hintLabelColor),
|
|
|
|
|
('RET', hintKeyColor),
|
|
|
|
|
(' SELECT ', hintLabelColor),
|
|
|
|
|
('ESC', hintKeyColor),
|
|
|
|
|
(' BACK', hintLabelColor),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
int textWidth = 0;
|
|
|
|
|
for (final (text, _) in segments) {
|
|
|
|
|
textWidth += WolfMenuFont.measureTextWidth(text, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final int panelWidth = (textWidth + 12).clamp(1, 320);
|
|
|
|
|
final int panelHeight = 12;
|
|
|
|
|
final int panelX = ((320 - panelWidth) ~/ 2).clamp(0, 319);
|
|
|
|
|
const int panelY = 184;
|
|
|
|
|
_fillRect320(panelX, panelY, panelWidth, panelHeight, hintBackground);
|
|
|
|
|
|
|
|
|
|
int cursorX = panelX + 6;
|
|
|
|
|
const int textY = panelY + 2;
|
|
|
|
|
for (final (text, color) in segments) {
|
|
|
|
|
_drawMenuText(text, cursorX, textY, color, scale: 1);
|
|
|
|
|
cursorX += WolfMenuFont.measureTextWidth(text, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _writeLeftClipped(
|
|
|
|
|
int y,
|
|
|
|
|
String text,
|
|
|
|
|
@@ -613,15 +670,17 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _plotMenuPixel320(int x320, int y200, int color) {
|
|
|
|
|
final double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
|
|
|
|
|
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
final double scaleX =
|
|
|
|
|
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
|
|
|
|
|
final double scaleY =
|
|
|
|
|
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
|
|
|
|
|
final int offsetX = isTerminal ? projectionOffsetX : 0;
|
|
|
|
|
final int offsetX = _usesTerminalLayout ? projectionOffsetX : 0;
|
|
|
|
|
final int startX = offsetX + (x320 * scaleX).toInt();
|
|
|
|
|
final int startY = (y200 * scaleY).toInt();
|
|
|
|
|
final int pixelW = math.max(1, scaleX.ceil());
|
|
|
|
|
final int pixelH = math.max(1, scaleY.ceil());
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
for (int dy = 0; dy < pixelH; dy++) {
|
|
|
|
|
final int y = startY + dy;
|
|
|
|
|
if (y < 0 || y >= _terminalPixelHeight) {
|
|
|
|
|
@@ -654,7 +713,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _drawSimpleHud(WolfEngine engine) {
|
|
|
|
|
final int hudWidth = isTerminal ? projectionWidth : width;
|
|
|
|
|
final int hudWidth = _usesTerminalLayout ? projectionWidth : width;
|
|
|
|
|
final int hudRows = height - viewHeight;
|
|
|
|
|
if (hudWidth < _simpleHudMinWidth || hudRows < _simpleHudMinRows) {
|
|
|
|
|
_drawMinimalHud(engine);
|
|
|
|
|
@@ -693,7 +752,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
final int baseY = viewHeight + 1;
|
|
|
|
|
|
|
|
|
|
// 3. Clear HUD Base
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_fillTerminalRect(
|
|
|
|
|
projectionOffsetX,
|
|
|
|
|
viewHeight * 2,
|
|
|
|
|
@@ -722,7 +781,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
|
|
|
|
|
// 4. Panel Drawing Helper
|
|
|
|
|
void drawBorderedPanel(int startX, int startY, int w, int h) {
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_fillTerminalRect(startX, startY * 2, w, h * 2, vgaPanelDark);
|
|
|
|
|
_fillTerminalRect(startX, startY * 2, w, 1, white);
|
|
|
|
|
_fillTerminalRect(startX, (startY + h) * 2 - 1, w, 1, white);
|
|
|
|
|
@@ -821,7 +880,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
final int red = ColorPalette.vga32Bit[4];
|
|
|
|
|
|
|
|
|
|
final int hudRows = height - viewHeight;
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_fillTerminalRect(
|
|
|
|
|
projectionOffsetX,
|
|
|
|
|
viewHeight * 2,
|
|
|
|
|
@@ -853,8 +912,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
final int lineY = viewHeight + 1;
|
|
|
|
|
if (lineY >= height) return;
|
|
|
|
|
|
|
|
|
|
final int drawStartX = isTerminal ? projectionOffsetX : 0;
|
|
|
|
|
final int drawWidth = isTerminal ? projectionWidth : width;
|
|
|
|
|
final int drawStartX = _usesTerminalLayout ? projectionOffsetX : 0;
|
|
|
|
|
final int drawWidth = _usesTerminalLayout ? projectionWidth : width;
|
|
|
|
|
final int maxTextLen = math.max(0, drawWidth - 2);
|
|
|
|
|
String clipped = hudText;
|
|
|
|
|
if (clipped.length > maxTextLen) {
|
|
|
|
|
@@ -965,15 +1024,15 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
@override
|
|
|
|
|
dynamic finalizeFrame() {
|
|
|
|
|
if (_engine.difficulty != null && _engine.player.damageFlash > 0.0) {
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_applyDamageFlashToScene();
|
|
|
|
|
} else {
|
|
|
|
|
_applyDamageFlash();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_composeTerminalScene();
|
|
|
|
|
return toAnsiString();
|
|
|
|
|
return _emitAnsi ? toAnsiString() : _screen;
|
|
|
|
|
}
|
|
|
|
|
return _screen;
|
|
|
|
|
}
|
|
|
|
|
@@ -983,14 +1042,16 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) {
|
|
|
|
|
int planeWidth = image.width ~/ 4;
|
|
|
|
|
int planeSize = planeWidth * image.height;
|
|
|
|
|
int maxDrawHeight = isTerminal ? _terminalPixelHeight : height;
|
|
|
|
|
int maxDrawWidth = isTerminal ? _viewportRightX : width;
|
|
|
|
|
int maxDrawHeight = _usesTerminalLayout ? _terminalPixelHeight : height;
|
|
|
|
|
int maxDrawWidth = _usesTerminalLayout ? _viewportRightX : width;
|
|
|
|
|
|
|
|
|
|
double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
|
|
|
|
|
double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
double scaleX = (_usesTerminalLayout ? projectionWidth : width) / 320.0;
|
|
|
|
|
double scaleY =
|
|
|
|
|
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
|
|
|
|
|
int destStartX =
|
|
|
|
|
(isTerminal ? projectionOffsetX : 0) + (startX_320 * scaleX).toInt();
|
|
|
|
|
(_usesTerminalLayout ? projectionOffsetX : 0) +
|
|
|
|
|
(startX_320 * scaleX).toInt();
|
|
|
|
|
int destStartY = (startY_200 * scaleY).toInt();
|
|
|
|
|
int destWidth = (image.width * scaleX).toInt();
|
|
|
|
|
int destHeight = (image.height * scaleY).toInt();
|
|
|
|
|
@@ -1013,7 +1074,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
|
|
|
|
|
int colorByte = image.pixels[index];
|
|
|
|
|
if (colorByte != 255) {
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_scenePixels[drawY][drawX] = ColorPalette.vga32Bit[colorByte];
|
|
|
|
|
} else {
|
|
|
|
|
_screen[drawY][drawX] = ColoredChar(
|
|
|
|
|
@@ -1046,16 +1107,19 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
int h200,
|
|
|
|
|
int color,
|
|
|
|
|
) {
|
|
|
|
|
final double scaleX = (isTerminal ? projectionWidth : width) / 320.0;
|
|
|
|
|
final double scaleY = (isTerminal ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
final double scaleX =
|
|
|
|
|
(_usesTerminalLayout ? projectionWidth : width) / 320.0;
|
|
|
|
|
final double scaleY =
|
|
|
|
|
(_usesTerminalLayout ? _terminalPixelHeight : height) / 200.0;
|
|
|
|
|
|
|
|
|
|
final int startX =
|
|
|
|
|
(isTerminal ? projectionOffsetX : 0) + (startX320 * scaleX).toInt();
|
|
|
|
|
(_usesTerminalLayout ? projectionOffsetX : 0) +
|
|
|
|
|
(startX320 * scaleX).toInt();
|
|
|
|
|
final int startY = (startY200 * scaleY).toInt();
|
|
|
|
|
final int w = math.max(1, (w320 * scaleX).toInt());
|
|
|
|
|
final int h = math.max(1, (h200 * scaleY).toInt());
|
|
|
|
|
|
|
|
|
|
if (isTerminal) {
|
|
|
|
|
if (_usesTerminalLayout) {
|
|
|
|
|
_fillTerminalRect(startX, startY, w, h, color);
|
|
|
|
|
} else {
|
|
|
|
|
_fillRect(startX, startY, w, h, activeTheme.solid, color);
|
|
|
|
|
@@ -1119,7 +1183,11 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
|
|
|
|
|
ColoredChar overlay = _screen[y][x];
|
|
|
|
|
if (overlay.char != ' ') {
|
|
|
|
|
if (overlay.rawBackgroundColor == null) {
|
|
|
|
|
// In ANSI terminal mode, inject the bottom scene pixel as background
|
|
|
|
|
// so half-block rows pack two pixel rows into one cell. In Flutter
|
|
|
|
|
// grid mode any injected background renders as a colored rectangle
|
|
|
|
|
// behind the character, so we leave the cell as-is.
|
|
|
|
|
if (_emitAnsi && overlay.rawBackgroundColor == null) {
|
|
|
|
|
_screen[y][x] = ColoredChar(
|
|
|
|
|
overlay.char,
|
|
|
|
|
overlay.rawColor,
|
|
|
|
|
@@ -1129,6 +1197,9 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pack two scene rows into one cell for both terminal and Flutter grid
|
|
|
|
|
// modes. Overlay characters above keep a null background in Flutter
|
|
|
|
|
// mode, so this does not introduce text background artifacts.
|
|
|
|
|
_screen[y][x] = topColor == bottomColor
|
|
|
|
|
? ColoredChar('█', topColor)
|
|
|
|
|
: ColoredChar('▀', topColor, bottomColor);
|
|
|
|
|
@@ -1185,13 +1256,6 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|
|
|
|
return ColorPalette.vga32Bit[_rgbToPaletteIndex(rgb)];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _rgbToRawColor(int rgb) {
|
|
|
|
|
final int r = (rgb >> 16) & 0xFF;
|
|
|
|
|
final int g = (rgb >> 8) & 0xFF;
|
|
|
|
|
final int b = rgb & 0xFF;
|
|
|
|
|
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int _rgbToPaletteIndex(int rgb) {
|
|
|
|
|
final int targetR = (rgb >> 16) & 0xFF;
|
|
|
|
|
final int targetG = (rgb >> 8) & 0xFF;
|
|
|
|
|
|