WIP pushing menu to game engine - improving menu text clarity in ASCII mode
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -37,13 +37,12 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
return PopScope<Object?>(
|
||||||
onWillPop: () async {
|
canPop: _engine.difficulty != null,
|
||||||
if (_engine.isDifficultySelectionPending) {
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
|
if (!didPop && _engine.difficulty == null) {
|
||||||
widget.wolf3d.input.queueBackAction();
|
widget.wolf3d.input.queueBackAction();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: LayoutBuilder(
|
body: LayoutBuilder(
|
||||||
@@ -55,7 +54,7 @@ class _GameScreenState extends State<GameScreen> {
|
|||||||
return Listener(
|
return Listener(
|
||||||
onPointerDown: (event) {
|
onPointerDown: (event) {
|
||||||
widget.wolf3d.input.onPointerDown(event);
|
widget.wolf3d.input.onPointerDown(event);
|
||||||
if (_engine.isDifficultySelectionPending &&
|
if (_engine.difficulty == null &&
|
||||||
viewportRect.width > 0 &&
|
viewportRect.width > 0 &&
|
||||||
viewportRect.height > 0 &&
|
viewportRect.height > 0 &&
|
||||||
viewportRect.contains(event.localPosition)) {
|
viewportRect.contains(event.localPosition)) {
|
||||||
|
|||||||
@@ -1,412 +0,0 @@
|
|||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:wolf_3d_dart/src/rasterizer/rasterizer.dart';
|
|
||||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
|
||||||
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
|
|
||||||
import 'package:wolf_3d_dart/wolf_3d_menu.dart';
|
|
||||||
|
|
||||||
class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
|
|
||||||
static const Map<String, List<String>> _menuFont = {
|
|
||||||
'A': ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],
|
|
||||||
'B': ['11110', '10001', '10001', '11110', '10001', '10001', '11110'],
|
|
||||||
'C': ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],
|
|
||||||
'D': ['11110', '10001', '10001', '10001', '10001', '10001', '11110'],
|
|
||||||
'E': ['11111', '10000', '10000', '11110', '10000', '10000', '11111'],
|
|
||||||
'F': ['11111', '10000', '10000', '11110', '10000', '10000', '10000'],
|
|
||||||
'G': ['01110', '10001', '10000', '10111', '10001', '10001', '01111'],
|
|
||||||
'H': ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],
|
|
||||||
'I': ['11111', '00100', '00100', '00100', '00100', '00100', '11111'],
|
|
||||||
'K': ['10001', '10010', '10100', '11000', '10100', '10010', '10001'],
|
|
||||||
'L': ['10000', '10000', '10000', '10000', '10000', '10000', '11111'],
|
|
||||||
'M': ['10001', '11011', '10101', '10101', '10001', '10001', '10001'],
|
|
||||||
'N': ['10001', '10001', '11001', '10101', '10011', '10001', '10001'],
|
|
||||||
'O': ['01110', '10001', '10001', '10001', '10001', '10001', '01110'],
|
|
||||||
'P': ['11110', '10001', '10001', '11110', '10000', '10000', '10000'],
|
|
||||||
'R': ['11110', '10001', '10001', '11110', '10100', '10010', '10001'],
|
|
||||||
'S': ['01111', '10000', '10000', '01110', '00001', '00001', '11110'],
|
|
||||||
'T': ['11111', '00100', '00100', '00100', '00100', '00100', '00100'],
|
|
||||||
'U': ['10001', '10001', '10001', '10001', '10001', '10001', '01110'],
|
|
||||||
'W': ['10001', '10001', '10001', '10101', '10101', '11011', '10001'],
|
|
||||||
'Y': ['10001', '10001', '01010', '00100', '00100', '00100', '00100'],
|
|
||||||
'?': ['01110', '10001', '00001', '00010', '00100', '00000', '00100'],
|
|
||||||
'!': ['00100', '00100', '00100', '00100', '00100', '00000', '00100'],
|
|
||||||
',': ['00000', '00000', '00000', '00000', '00110', '00100', '01000'],
|
|
||||||
'.': ['00000', '00000', '00000', '00000', '00000', '00110', '00110'],
|
|
||||||
"'": ['00100', '00100', '00100', '00000', '00000', '00000', '00000'],
|
|
||||||
' ': ['00000', '00000', '00000', '00000', '00000', '00000', '00000'],
|
|
||||||
};
|
|
||||||
|
|
||||||
late FrameBuffer _buffer;
|
|
||||||
late WolfEngine _engine;
|
|
||||||
|
|
||||||
// Intercept the base render call to store our references
|
|
||||||
@override
|
|
||||||
FrameBuffer render(WolfEngine engine) {
|
|
||||||
_engine = engine;
|
|
||||||
_buffer = engine.frameBuffer;
|
|
||||||
return super.render(engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void prepareFrame(WolfEngine engine) {
|
|
||||||
// Top half is ceiling color (25), bottom half is floor color (29)
|
|
||||||
int ceilingColor = ColorPalette.vga32Bit[25];
|
|
||||||
int floorColor = ColorPalette.vga32Bit[29];
|
|
||||||
|
|
||||||
for (int y = 0; y < viewHeight; y++) {
|
|
||||||
int color = (y < viewHeight / 2) ? ceilingColor : floorColor;
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
_buffer.pixels[y * width + x] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void drawWallColumn(
|
|
||||||
int x,
|
|
||||||
int drawStart,
|
|
||||||
int drawEnd,
|
|
||||||
int columnHeight,
|
|
||||||
Sprite texture,
|
|
||||||
int texX,
|
|
||||||
double perpWallDist,
|
|
||||||
int side,
|
|
||||||
) {
|
|
||||||
for (int y = drawStart; y < drawEnd; y++) {
|
|
||||||
// Calculate which Y pixel of the texture to sample
|
|
||||||
double relativeY =
|
|
||||||
(y - (-columnHeight ~/ 2 + viewHeight ~/ 2)) / columnHeight;
|
|
||||||
int texY = (relativeY * 64).toInt().clamp(0, 63);
|
|
||||||
|
|
||||||
int colorByte = texture.pixels[texX * 64 + texY];
|
|
||||||
int pixelColor = ColorPalette.vga32Bit[colorByte];
|
|
||||||
|
|
||||||
// Darken Y-side walls for faux directional lighting
|
|
||||||
if (side == 1) {
|
|
||||||
pixelColor = shadeColor(pixelColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffer.pixels[y * width + x] = pixelColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void drawSpriteStripe(
|
|
||||||
int stripeX,
|
|
||||||
int drawStartY,
|
|
||||||
int drawEndY,
|
|
||||||
int spriteHeight,
|
|
||||||
Sprite texture,
|
|
||||||
int texX,
|
|
||||||
double transformY,
|
|
||||||
) {
|
|
||||||
for (
|
|
||||||
int y = math.max(0, drawStartY);
|
|
||||||
y < math.min(viewHeight, drawEndY);
|
|
||||||
y++
|
|
||||||
) {
|
|
||||||
double relativeY = (y - drawStartY) / spriteHeight;
|
|
||||||
int texY = (relativeY * 64).toInt().clamp(0, 63);
|
|
||||||
|
|
||||||
int colorByte = texture.pixels[texX * 64 + texY];
|
|
||||||
|
|
||||||
// 255 is the "transparent" color index in VGA Wolfenstein
|
|
||||||
if (colorByte != 255) {
|
|
||||||
_buffer.pixels[y * width + stripeX] = ColorPalette.vga32Bit[colorByte];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void drawWeapon(WolfEngine engine) {
|
|
||||||
int spriteIndex = engine.player.currentWeapon.getCurrentSpriteIndex(
|
|
||||||
engine.data.sprites.length,
|
|
||||||
);
|
|
||||||
Sprite weaponSprite = engine.data.sprites[spriteIndex];
|
|
||||||
|
|
||||||
int weaponWidth = (width * 0.5).toInt();
|
|
||||||
int weaponHeight = (viewHeight * 0.8).toInt();
|
|
||||||
|
|
||||||
int startX = (width ~/ 2) - (weaponWidth ~/ 2);
|
|
||||||
int startY =
|
|
||||||
viewHeight - weaponHeight + (engine.player.weaponAnimOffset ~/ 4);
|
|
||||||
|
|
||||||
for (int dy = 0; dy < weaponHeight; dy++) {
|
|
||||||
for (int dx = 0; dx < weaponWidth; dx++) {
|
|
||||||
int texX = (dx * 64 ~/ weaponWidth).clamp(0, 63);
|
|
||||||
int texY = (dy * 64 ~/ weaponHeight).clamp(0, 63);
|
|
||||||
|
|
||||||
int colorByte = weaponSprite.pixels[texX * 64 + texY];
|
|
||||||
if (colorByte != 255) {
|
|
||||||
int drawX = startX + dx;
|
|
||||||
int drawY = startY + dy;
|
|
||||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) {
|
|
||||||
_buffer.pixels[drawY * width + drawX] =
|
|
||||||
ColorPalette.vga32Bit[colorByte];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void drawHud(WolfEngine engine) {
|
|
||||||
int statusBarIndex = engine.data.vgaImages.indexWhere(
|
|
||||||
(img) => img.width == 320 && img.height == 40,
|
|
||||||
);
|
|
||||||
if (statusBarIndex == -1) return;
|
|
||||||
|
|
||||||
// 1. Draw Background
|
|
||||||
_blitVgaImage(engine.data.vgaImages[statusBarIndex], 0, 160);
|
|
||||||
|
|
||||||
// 2. Draw Stats (100% mathematically accurate right-aligned coordinates)
|
|
||||||
_drawNumber(1, 32, 176, engine.data.vgaImages); // Floor
|
|
||||||
_drawNumber(engine.player.score, 96, 176, engine.data.vgaImages); // Score
|
|
||||||
_drawNumber(3, 120, 176, engine.data.vgaImages); // Lives
|
|
||||||
_drawNumber(
|
|
||||||
engine.player.health,
|
|
||||||
192,
|
|
||||||
176,
|
|
||||||
engine.data.vgaImages,
|
|
||||||
); // Health
|
|
||||||
_drawNumber(engine.player.ammo, 232, 176, engine.data.vgaImages); // Ammo
|
|
||||||
|
|
||||||
// 3. Draw BJ's Face & Current Weapon
|
|
||||||
_drawFace(engine);
|
|
||||||
_drawWeaponIcon(engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void drawMenu(WolfEngine engine) {
|
|
||||||
final int bgColor = ColorPalette.vga32Bit[153];
|
|
||||||
final int panelColor = ColorPalette.vga32Bit[157];
|
|
||||||
final int headingColor = ColorPalette.vga32Bit[119];
|
|
||||||
final int selectedTextColor = ColorPalette.vga32Bit[19];
|
|
||||||
final int unselectedTextColor = ColorPalette.vga32Bit[23];
|
|
||||||
|
|
||||||
for (int i = 0; i < _buffer.pixels.length; i++) {
|
|
||||||
_buffer.pixels[i] = bgColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const panelX = 28;
|
|
||||||
const panelY = 70;
|
|
||||||
const panelW = 264;
|
|
||||||
const panelH = 82;
|
|
||||||
|
|
||||||
for (int y = panelY; y < panelY + panelH; y++) {
|
|
||||||
if (y < 0 || y >= height) continue;
|
|
||||||
final rowStart = y * width;
|
|
||||||
for (int x = panelX; x < panelX + panelW; x++) {
|
|
||||||
if (x >= 0 && x < width) {
|
|
||||||
_buffer.pixels[rowStart + x] = panelColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final art = WolfClassicMenuArt(engine.data);
|
|
||||||
_drawMenuTextCentered('HOW TOUGH ARE YOU?', 48, headingColor, scale: 2);
|
|
||||||
|
|
||||||
final bottom = art.pic(15);
|
|
||||||
if (bottom != null) {
|
|
||||||
final x = (width - bottom.width) ~/ 2;
|
|
||||||
final y = height - bottom.height - 8;
|
|
||||||
_blitVgaImage(bottom, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
final face = art.difficultyOption(
|
|
||||||
Difficulty.values[engine.menuSelectedDifficultyIndex],
|
|
||||||
);
|
|
||||||
if (face != null) {
|
|
||||||
_blitVgaImage(face, panelX + panelW - face.width - 10, panelY + 22);
|
|
||||||
}
|
|
||||||
|
|
||||||
final cursor = art.pic(engine.isMenuCursorAltFrame ? 9 : 8);
|
|
||||||
const rowYStart = panelY + 16;
|
|
||||||
const rowStep = 15;
|
|
||||||
const textX = panelX + 42;
|
|
||||||
const labels = [
|
|
||||||
'CAN I PLAY, DADDY?',
|
|
||||||
"DON'T HURT ME.",
|
|
||||||
"BRING 'EM ON!",
|
|
||||||
'I AM DEATH INCARNATE!',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (int i = 0; i < Difficulty.values.length; i++) {
|
|
||||||
final y = rowYStart + (i * rowStep);
|
|
||||||
final isSelected = i == engine.menuSelectedDifficultyIndex;
|
|
||||||
|
|
||||||
if (isSelected && cursor != null) {
|
|
||||||
_blitVgaImage(cursor, panelX + 10, y - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
_drawMenuText(
|
|
||||||
labels[i],
|
|
||||||
textX,
|
|
||||||
y,
|
|
||||||
isSelected ? selectedTextColor : unselectedTextColor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _drawMenuText(
|
|
||||||
String text,
|
|
||||||
int startX,
|
|
||||||
int startY,
|
|
||||||
int color, {
|
|
||||||
int scale = 1,
|
|
||||||
}) {
|
|
||||||
int x = startX;
|
|
||||||
for (final rune in text.runes) {
|
|
||||||
final char = String.fromCharCode(rune).toUpperCase();
|
|
||||||
final pattern = _menuFont[char] ?? _menuFont[' ']!;
|
|
||||||
|
|
||||||
for (int row = 0; row < pattern.length; row++) {
|
|
||||||
final bits = pattern[row];
|
|
||||||
for (int col = 0; col < bits.length; col++) {
|
|
||||||
if (bits[col] != '1') continue;
|
|
||||||
for (int sy = 0; sy < scale; sy++) {
|
|
||||||
for (int sx = 0; sx < scale; sx++) {
|
|
||||||
final drawX = x + (col * scale) + sx;
|
|
||||||
final drawY = startY + (row * scale) + sy;
|
|
||||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) {
|
|
||||||
_buffer.pixels[drawY * width + drawX] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x += (6 * scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _drawMenuTextCentered(
|
|
||||||
String text,
|
|
||||||
int y,
|
|
||||||
int color, {
|
|
||||||
int scale = 1,
|
|
||||||
}) {
|
|
||||||
final textWidth = text.length * 6 * scale;
|
|
||||||
final x = ((width - textWidth) ~/ 2).clamp(0, width - 1);
|
|
||||||
_drawMenuText(text, x, y, color, scale: scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FrameBuffer finalizeFrame() {
|
|
||||||
// If the player took damage, overlay a red tint across the 3D view
|
|
||||||
if (!_engine.isDifficultySelectionPending &&
|
|
||||||
_engine.player.damageFlash > 0) {
|
|
||||||
_applyDamageFlash();
|
|
||||||
}
|
|
||||||
return _buffer; // Return the fully painted pixel array
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===========================================================================
|
|
||||||
// PRIVATE HELPER METHODS
|
|
||||||
// ===========================================================================
|
|
||||||
|
|
||||||
/// Maps the planar VGA image data directly to 32-bit pixels.
|
|
||||||
/// (Assuming a 1:1 scale, which is standard for the 320x200 software renderer).
|
|
||||||
void _blitVgaImage(VgaImage image, int startX, int startY) {
|
|
||||||
int planeWidth = image.width ~/ 4;
|
|
||||||
int planeSize = planeWidth * image.height;
|
|
||||||
|
|
||||||
for (int dy = 0; dy < image.height; dy++) {
|
|
||||||
for (int dx = 0; dx < image.width; dx++) {
|
|
||||||
int drawX = startX + dx;
|
|
||||||
int drawY = startY + dy;
|
|
||||||
|
|
||||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) {
|
|
||||||
int srcX = dx.clamp(0, image.width - 1);
|
|
||||||
int srcY = dy.clamp(0, image.height - 1);
|
|
||||||
|
|
||||||
int plane = srcX % 4;
|
|
||||||
int sx = srcX ~/ 4;
|
|
||||||
int index = (plane * planeSize) + (srcY * planeWidth) + sx;
|
|
||||||
|
|
||||||
int colorByte = image.pixels[index];
|
|
||||||
if (colorByte != 255) {
|
|
||||||
_buffer.pixels[drawY * width + drawX] =
|
|
||||||
ColorPalette.vga32Bit[colorByte];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _drawNumber(
|
|
||||||
int value,
|
|
||||||
int rightAlignX,
|
|
||||||
int startY,
|
|
||||||
List<VgaImage> vgaImages,
|
|
||||||
) {
|
|
||||||
const int zeroIndex = 96;
|
|
||||||
String numStr = value.toString();
|
|
||||||
int currentX = rightAlignX - (numStr.length * 8);
|
|
||||||
|
|
||||||
for (int i = 0; i < numStr.length; i++) {
|
|
||||||
int digit = int.parse(numStr[i]);
|
|
||||||
if (zeroIndex + digit < vgaImages.length) {
|
|
||||||
_blitVgaImage(vgaImages[zeroIndex + digit], currentX, startY);
|
|
||||||
}
|
|
||||||
currentX += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _drawFace(WolfEngine engine) {
|
|
||||||
int health = engine.player.health;
|
|
||||||
int faceIndex;
|
|
||||||
|
|
||||||
if (health <= 0) {
|
|
||||||
faceIndex = 127; // Dead face
|
|
||||||
} else {
|
|
||||||
int healthTier = ((100 - health) ~/ 16).clamp(0, 6);
|
|
||||||
faceIndex = 106 + (healthTier * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (faceIndex < engine.data.vgaImages.length) {
|
|
||||||
_blitVgaImage(engine.data.vgaImages[faceIndex], 136, 164);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _drawWeaponIcon(WolfEngine engine) {
|
|
||||||
int weaponIndex = 89; // Default to Pistol
|
|
||||||
|
|
||||||
if (engine.player.hasChainGun) {
|
|
||||||
weaponIndex = 91;
|
|
||||||
} else if (engine.player.hasMachineGun) {
|
|
||||||
weaponIndex = 90;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (weaponIndex < engine.data.vgaImages.length) {
|
|
||||||
_blitVgaImage(engine.data.vgaImages[weaponIndex], 256, 164);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tints the top 80% of the screen red based on player.damageFlash intensity
|
|
||||||
void _applyDamageFlash() {
|
|
||||||
// Grab the intensity (0.0 to 1.0)
|
|
||||||
double intensity = _engine.player.damageFlash;
|
|
||||||
|
|
||||||
// Calculate how much to boost red and drop green/blue
|
|
||||||
int redBoost = (150 * intensity).toInt();
|
|
||||||
double colorDrop = 1.0 - (0.5 * intensity);
|
|
||||||
|
|
||||||
for (int y = 0; y < viewHeight; y++) {
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int index = y * width + x;
|
|
||||||
int color = _buffer.pixels[index];
|
|
||||||
|
|
||||||
int r = color & 0xFF;
|
|
||||||
int g = (color >> 8) & 0xFF;
|
|
||||||
int b = (color >> 16) & 0xFF;
|
|
||||||
|
|
||||||
r = (r + redBoost).clamp(0, 255);
|
|
||||||
g = (g * colorDrop).toInt();
|
|
||||||
b = (b * colorDrop).toInt();
|
|
||||||
|
|
||||||
_buffer.pixels[index] = (0xFF000000) | (b << 16) | (g << 8) | r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,17 +45,11 @@ class WolfEngine {
|
|||||||
/// The active difficulty level, affecting enemy spawning and behavior.
|
/// The active difficulty level, affecting enemy spawning and behavior.
|
||||||
Difficulty? difficulty;
|
Difficulty? difficulty;
|
||||||
|
|
||||||
/// Whether the engine is waiting on player difficulty selection.
|
|
||||||
bool get isDifficultySelectionPending => difficulty == null;
|
|
||||||
|
|
||||||
/// Menu state owner for difficulty-selection navigation and edge detection.
|
/// Menu state owner for difficulty-selection navigation and edge detection.
|
||||||
final MenuManager menuManager = MenuManager();
|
final MenuManager menuManager = MenuManager();
|
||||||
|
|
||||||
/// Cursor index used by renderer-side difficulty menus.
|
/// Elapsed engine lifetime in milliseconds.
|
||||||
int get menuSelectedDifficultyIndex => menuManager.selectedDifficultyIndex;
|
int get timeAliveMs => _timeAliveMs;
|
||||||
|
|
||||||
/// Cursor blink phase used by renderer-side difficulty menus.
|
|
||||||
bool get isMenuCursorAltFrame => menuManager.isCursorAltFrame(_timeAliveMs);
|
|
||||||
|
|
||||||
/// The episode index where the game session begins.
|
/// The episode index where the game session begins.
|
||||||
final int startingEpisode;
|
final int startingEpisode;
|
||||||
@@ -119,7 +113,7 @@ class WolfEngine {
|
|||||||
|
|
||||||
menuManager.beginDifficultySelection(initialDifficulty: difficulty);
|
menuManager.beginDifficultySelection(initialDifficulty: difficulty);
|
||||||
|
|
||||||
if (!isDifficultySelectionPending) {
|
if (difficulty != null) {
|
||||||
_loadLevel();
|
_loadLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +145,7 @@ class WolfEngine {
|
|||||||
input.update();
|
input.update();
|
||||||
final currentInput = input.currentInput;
|
final currentInput = input.currentInput;
|
||||||
|
|
||||||
if (isDifficultySelectionPending) {
|
if (difficulty == null) {
|
||||||
_tickDifficultyMenu(currentInput);
|
_tickDifficultyMenu(currentInput);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,55 @@ class ColoredChar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AsciiRasterizer extends CliRasterizer<dynamic> {
|
class AsciiRasterizer extends CliRasterizer<dynamic> {
|
||||||
|
static const int _unsetMenuSubPixel = -1;
|
||||||
|
static const Map<int, String> _quadrantGlyphByMask = {
|
||||||
|
0x0: ' ',
|
||||||
|
0x1: '▘',
|
||||||
|
0x2: '▝',
|
||||||
|
0x3: '▀',
|
||||||
|
0x4: '▖',
|
||||||
|
0x5: '▌',
|
||||||
|
0x6: '▞',
|
||||||
|
0x7: '▛',
|
||||||
|
0x8: '▗',
|
||||||
|
0x9: '▚',
|
||||||
|
0xA: '▐',
|
||||||
|
0xB: '▜',
|
||||||
|
0xC: '▄',
|
||||||
|
0xD: '▙',
|
||||||
|
0xE: '▟',
|
||||||
|
0xF: '█',
|
||||||
|
};
|
||||||
|
static const Map<String, List<String>> _menuFont = {
|
||||||
|
'A': ['01110', '10001', '10001', '11111', '10001', '10001', '10001'],
|
||||||
|
'B': ['11110', '10001', '10001', '11110', '10001', '10001', '11110'],
|
||||||
|
'C': ['01110', '10001', '10000', '10000', '10000', '10001', '01110'],
|
||||||
|
'D': ['11110', '10001', '10001', '10001', '10001', '10001', '11110'],
|
||||||
|
'E': ['11111', '10000', '10000', '11110', '10000', '10000', '11111'],
|
||||||
|
'F': ['11111', '10000', '10000', '11110', '10000', '10000', '10000'],
|
||||||
|
'G': ['01110', '10001', '10000', '10111', '10001', '10001', '01111'],
|
||||||
|
'H': ['10001', '10001', '10001', '11111', '10001', '10001', '10001'],
|
||||||
|
'I': ['11111', '00100', '00100', '00100', '00100', '00100', '11111'],
|
||||||
|
'K': ['10001', '10010', '10100', '11000', '10100', '10010', '10001'],
|
||||||
|
'L': ['10000', '10000', '10000', '10000', '10000', '10000', '11111'],
|
||||||
|
'M': ['10001', '11011', '10101', '10101', '10001', '10001', '10001'],
|
||||||
|
'N': ['10001', '10001', '11001', '10101', '10011', '10001', '10001'],
|
||||||
|
'O': ['01110', '10001', '10001', '10001', '10001', '10001', '01110'],
|
||||||
|
'P': ['11110', '10001', '10001', '11110', '10000', '10000', '10000'],
|
||||||
|
'R': ['11110', '10001', '10001', '11110', '10100', '10010', '10001'],
|
||||||
|
'S': ['01111', '10000', '10000', '01110', '00001', '00001', '11110'],
|
||||||
|
'T': ['11111', '00100', '00100', '00100', '00100', '00100', '00100'],
|
||||||
|
'U': ['10001', '10001', '10001', '10001', '10001', '10001', '01110'],
|
||||||
|
'W': ['10001', '10001', '10001', '10101', '10101', '11011', '10001'],
|
||||||
|
'Y': ['10001', '10001', '01010', '00100', '00100', '00100', '00100'],
|
||||||
|
'?': ['01110', '10001', '00001', '00010', '00100', '00000', '00100'],
|
||||||
|
'!': ['00100', '00100', '00100', '00100', '00100', '00000', '00100'],
|
||||||
|
',': ['00000', '00000', '00000', '00000', '00110', '00100', '01000'],
|
||||||
|
'.': ['00000', '00000', '00000', '00000', '00000', '00110', '00110'],
|
||||||
|
"'": ['00100', '00100', '00100', '00000', '00000', '00000', '00000'],
|
||||||
|
' ': ['00000', '00000', '00000', '00000', '00000', '00000', '00000'],
|
||||||
|
};
|
||||||
|
|
||||||
static const double _targetAspectRatio = 4 / 3;
|
static const double _targetAspectRatio = 4 / 3;
|
||||||
static const int _terminalBackdropArgb = 0xFF009688;
|
static const int _terminalBackdropArgb = 0xFF009688;
|
||||||
static const int _minimumTerminalColumns = 80;
|
static const int _minimumTerminalColumns = 80;
|
||||||
@@ -83,6 +132,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|||||||
late List<List<ColoredChar>> _screen;
|
late List<List<ColoredChar>> _screen;
|
||||||
late List<List<int>> _scenePixels;
|
late List<List<int>> _scenePixels;
|
||||||
late WolfEngine _engine;
|
late WolfEngine _engine;
|
||||||
|
List<List<int>>? _menuTextSubPixels;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final double aspectMultiplier;
|
final double aspectMultiplier;
|
||||||
@@ -327,6 +377,8 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void drawMenu(WolfEngine engine) {
|
void drawMenu(WolfEngine engine) {
|
||||||
|
final int selectedDifficultyIndex =
|
||||||
|
engine.menuManager.selectedDifficultyIndex;
|
||||||
final int bgColor = _rgbToRawColor(engine.menuBackgroundRgb);
|
final int bgColor = _rgbToRawColor(engine.menuBackgroundRgb);
|
||||||
final int panelColor = _rgbToRawColor(engine.menuPanelRgb);
|
final int panelColor = _rgbToRawColor(engine.menuPanelRgb);
|
||||||
final int headingColor = ColorPalette.vga32Bit[119];
|
final int headingColor = ColorPalette.vga32Bit[119];
|
||||||
@@ -337,53 +389,51 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|||||||
_fillTerminalRect(0, 0, width, _terminalPixelHeight, bgColor);
|
_fillTerminalRect(0, 0, width, _terminalPixelHeight, bgColor);
|
||||||
} else {
|
} else {
|
||||||
_fillRect(0, 0, width, height, activeTheme.solid, bgColor);
|
_fillRect(0, 0, width, height, activeTheme.solid, bgColor);
|
||||||
|
_menuTextSubPixels = List.generate(
|
||||||
|
height * 2,
|
||||||
|
(_) => List<int>.filled(width * 2, _unsetMenuSubPixel),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fillRect320(28, 70, 264, 82, panelColor);
|
_fillRect320(28, 70, 264, 82, panelColor);
|
||||||
|
|
||||||
const heading = 'HOW TOUGH ARE YOU?';
|
_drawMenuTextCentered('HOW TOUGH ARE YOU?', 48, headingColor, scale: 2);
|
||||||
final headingY = ((48 / 200) * height).toInt().clamp(0, height - 1);
|
|
||||||
final headingX = ((width - heading.length) ~/ 2).clamp(0, width - 1);
|
|
||||||
_writeString(headingX, headingY, heading, headingColor, bgColor);
|
|
||||||
|
|
||||||
final art = WolfClassicMenuArt(engine.data);
|
final art = WolfClassicMenuArt(engine.data);
|
||||||
|
|
||||||
final face = art.difficultyOption(
|
final face = art.difficultyOption(
|
||||||
Difficulty.values[engine.menuSelectedDifficultyIndex],
|
Difficulty.values[selectedDifficultyIndex],
|
||||||
);
|
);
|
||||||
if (face != null) {
|
if (face != null) {
|
||||||
_blitVgaImageAscii(face, 28 + 264 - face.width - 10, 92);
|
_blitVgaImageAscii(face, 28 + 264 - face.width - 10, 92);
|
||||||
}
|
}
|
||||||
|
|
||||||
final cursor = art.pic(engine.isMenuCursorAltFrame ? 9 : 8);
|
final cursor = art.pic(
|
||||||
|
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
||||||
|
);
|
||||||
const rowYStart = 86;
|
const rowYStart = 86;
|
||||||
const rowStep = 15;
|
const rowStep = 15;
|
||||||
const labels = [
|
|
||||||
'CAN I PLAY, DADDY?',
|
|
||||||
"DON'T HURT ME.",
|
|
||||||
"BRING 'EM ON!",
|
|
||||||
'I AM DEATH INCARNATE!',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (int i = 0; i < Difficulty.values.length; i++) {
|
for (int i = 0; i < Difficulty.values.length; i++) {
|
||||||
final y = rowYStart + (i * rowStep);
|
final y = rowYStart + (i * rowStep);
|
||||||
final isSelected = i == engine.menuSelectedDifficultyIndex;
|
final isSelected = i == selectedDifficultyIndex;
|
||||||
|
|
||||||
if (isSelected && cursor != null) {
|
if (isSelected && cursor != null) {
|
||||||
_blitVgaImageAscii(cursor, 38, y - 2);
|
_blitVgaImageAscii(cursor, 38, y - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
final textY = ((y / 200) * height).toInt().clamp(0, height - 1);
|
_drawMenuText(
|
||||||
final textX = ((70 / 320) * width).toInt().clamp(0, width - 1);
|
_difficultyLabel(Difficulty.values[i]),
|
||||||
_writeString(
|
70,
|
||||||
textX,
|
y,
|
||||||
textY,
|
|
||||||
labels[i],
|
|
||||||
isSelected ? selectedTextColor : unselectedTextColor,
|
isSelected ? selectedTextColor : unselectedTextColor,
|
||||||
panelColor,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isTerminal) {
|
||||||
|
_composeMenuTextSubPixels();
|
||||||
|
_menuTextSubPixels = null;
|
||||||
|
}
|
||||||
|
|
||||||
final int hintKeyColor = _rgbToRawColor(0xFF5555);
|
final int hintKeyColor = _rgbToRawColor(0xFF5555);
|
||||||
final int hintLabelColor = _rgbToRawColor(0x900303);
|
final int hintLabelColor = _rgbToRawColor(0x900303);
|
||||||
final int hintBackground = _rgbToRawColor(0x000000);
|
final int hintBackground = _rgbToRawColor(0x000000);
|
||||||
@@ -442,6 +492,190 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _difficultyLabel(Difficulty difficulty) {
|
||||||
|
switch (difficulty) {
|
||||||
|
case Difficulty.baby:
|
||||||
|
return 'CAN I PLAY, DADDY?';
|
||||||
|
case Difficulty.easy:
|
||||||
|
return "DON'T HURT ME.";
|
||||||
|
case Difficulty.medium:
|
||||||
|
return "BRING 'EM ON!";
|
||||||
|
case Difficulty.hard:
|
||||||
|
return 'I AM DEATH INCARNATE!';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawMenuText(
|
||||||
|
String text,
|
||||||
|
int startX320,
|
||||||
|
int startY200,
|
||||||
|
int color, {
|
||||||
|
int scale = 1,
|
||||||
|
}) {
|
||||||
|
int x320 = startX320;
|
||||||
|
for (final rune in text.runes) {
|
||||||
|
final String char = String.fromCharCode(rune).toUpperCase();
|
||||||
|
final List<String> pattern = _menuFont[char] ?? _menuFont[' ']!;
|
||||||
|
|
||||||
|
for (int row = 0; row < pattern.length; row++) {
|
||||||
|
final String bits = pattern[row];
|
||||||
|
for (int col = 0; col < bits.length; col++) {
|
||||||
|
if (bits[col] != '1') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int sy = 0; sy < scale; sy++) {
|
||||||
|
for (int sx = 0; sx < scale; sx++) {
|
||||||
|
_plotMenuPixel320(
|
||||||
|
x320 + (col * scale) + sx,
|
||||||
|
startY200 + (row * scale) + sy,
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x320 += _menuGlyphAdvance(char, scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawMenuTextCentered(
|
||||||
|
String text,
|
||||||
|
int y200,
|
||||||
|
int color, {
|
||||||
|
int scale = 1,
|
||||||
|
}) {
|
||||||
|
final int textWidth = _measureMenuTextWidth(text, scale);
|
||||||
|
final int x320 = ((320 - textWidth) ~/ 2).clamp(0, 319);
|
||||||
|
_drawMenuText(text, x320, y200, color, scale: scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _measureMenuTextWidth(String text, int scale) {
|
||||||
|
int width320 = 0;
|
||||||
|
for (final rune in text.runes) {
|
||||||
|
final char = String.fromCharCode(rune).toUpperCase();
|
||||||
|
width320 += _menuGlyphAdvance(char, scale);
|
||||||
|
}
|
||||||
|
return width320;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _menuGlyphAdvance(String char, int scale) {
|
||||||
|
switch (char) {
|
||||||
|
case 'I':
|
||||||
|
case '!':
|
||||||
|
case '.':
|
||||||
|
case ',':
|
||||||
|
case "'":
|
||||||
|
return 4 * scale;
|
||||||
|
case ' ':
|
||||||
|
return 5 * scale;
|
||||||
|
default:
|
||||||
|
return 6 * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 int offsetX = isTerminal ? projectionOffsetX : 0;
|
||||||
|
final int startX = offsetX + (x320 * scaleX).toInt();
|
||||||
|
final int pixelW = math.max(1, scaleX.ceil());
|
||||||
|
if (isTerminal) {
|
||||||
|
final int startY = (y200 * scaleY).toInt();
|
||||||
|
final int pixelH = math.max(1, scaleY.ceil());
|
||||||
|
|
||||||
|
for (int dy = 0; dy < pixelH; dy++) {
|
||||||
|
final int y = startY + dy;
|
||||||
|
if (y < 0 || y >= _terminalPixelHeight) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int dx = 0; dx < pixelW; dx++) {
|
||||||
|
final int x = startX + dx;
|
||||||
|
if (x < 0 || x >= width) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_scenePixels[y][x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final overlay = _menuTextSubPixels;
|
||||||
|
if (overlay == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double subScaleX = (width * 2) / 320.0;
|
||||||
|
final double subScaleY = (height * 2) / 200.0;
|
||||||
|
final int startXSub = (x320 * subScaleX).toInt();
|
||||||
|
final int startYSub = (y200 * subScaleY).toInt();
|
||||||
|
final int pixelWSub = math.max(1, subScaleX.ceil());
|
||||||
|
final int pixelHSub = math.max(1, subScaleY.ceil());
|
||||||
|
|
||||||
|
for (int dy = 0; dy < pixelHSub; dy++) {
|
||||||
|
final int y = startYSub + dy;
|
||||||
|
if (y < 0 || y >= overlay.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int dx = 0; dx < pixelWSub; dx++) {
|
||||||
|
final int x = startXSub + dx;
|
||||||
|
if (x < 0 || x >= (width * 2)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
overlay[y][x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _composeMenuTextSubPixels() {
|
||||||
|
final overlay = _menuTextSubPixels;
|
||||||
|
if (overlay == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
final int topY = y * 2;
|
||||||
|
final int bottomY = math.min(topY + 1, overlay.length - 1);
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
final int leftX = x * 2;
|
||||||
|
final int rightX = leftX + 1;
|
||||||
|
|
||||||
|
final int tl = overlay[topY][leftX];
|
||||||
|
final int tr = overlay[topY][rightX];
|
||||||
|
final int bl = overlay[bottomY][leftX];
|
||||||
|
final int br = overlay[bottomY][rightX];
|
||||||
|
|
||||||
|
int mask = 0;
|
||||||
|
if (tl != _unsetMenuSubPixel) mask |= 0x1;
|
||||||
|
if (tr != _unsetMenuSubPixel) mask |= 0x2;
|
||||||
|
if (bl != _unsetMenuSubPixel) mask |= 0x4;
|
||||||
|
if (br != _unsetMenuSubPixel) mask |= 0x8;
|
||||||
|
|
||||||
|
if (mask == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int baseColor = _screen[y][x].rawColor;
|
||||||
|
final int fgColor = tl != _unsetMenuSubPixel
|
||||||
|
? tl
|
||||||
|
: tr != _unsetMenuSubPixel
|
||||||
|
? tr
|
||||||
|
: bl != _unsetMenuSubPixel
|
||||||
|
? bl
|
||||||
|
: br;
|
||||||
|
final String glyph = _quadrantGlyphByMask[mask] ?? '█';
|
||||||
|
|
||||||
|
if (mask == 0xF) {
|
||||||
|
_screen[y][x] = ColoredChar('█', fgColor);
|
||||||
|
} else {
|
||||||
|
_screen[y][x] = ColoredChar(glyph, fgColor, baseColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _drawSimpleHud(WolfEngine engine) {
|
void _drawSimpleHud(WolfEngine engine) {
|
||||||
final int hudWidth = isTerminal ? projectionWidth : width;
|
final int hudWidth = isTerminal ? projectionWidth : width;
|
||||||
final int hudRows = height - viewHeight;
|
final int hudRows = height - viewHeight;
|
||||||
@@ -753,8 +987,7 @@ class AsciiRasterizer extends CliRasterizer<dynamic> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
dynamic finalizeFrame() {
|
dynamic finalizeFrame() {
|
||||||
if (!_engine.isDifficultySelectionPending &&
|
if (_engine.difficulty != null && _engine.player.damageFlash > 0.0) {
|
||||||
_engine.player.damageFlash > 0.0) {
|
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
_applyDamageFlashToScene();
|
_applyDamageFlashToScene();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ abstract class Rasterizer<T> {
|
|||||||
// 1. Setup the frame (clear screen, draw floor/ceiling)
|
// 1. Setup the frame (clear screen, draw floor/ceiling)
|
||||||
prepareFrame(engine);
|
prepareFrame(engine);
|
||||||
|
|
||||||
if (engine.isDifficultySelectionPending) {
|
if (engine.difficulty == null) {
|
||||||
drawMenu(engine);
|
drawMenu(engine);
|
||||||
return finalizeFrame();
|
return finalizeFrame();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,6 +359,8 @@ class SixelRasterizer extends CliRasterizer<String> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void drawMenu(WolfEngine engine) {
|
void drawMenu(WolfEngine engine) {
|
||||||
|
final int selectedDifficultyIndex =
|
||||||
|
engine.menuManager.selectedDifficultyIndex;
|
||||||
final int bgColor = _rgbToPaletteIndex(engine.menuBackgroundRgb);
|
final int bgColor = _rgbToPaletteIndex(engine.menuBackgroundRgb);
|
||||||
final int panelColor = _rgbToPaletteIndex(engine.menuPanelRgb);
|
final int panelColor = _rgbToPaletteIndex(engine.menuPanelRgb);
|
||||||
const int headingIndex = 119;
|
const int headingIndex = 119;
|
||||||
@@ -380,13 +382,15 @@ class SixelRasterizer extends CliRasterizer<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final face = art.difficultyOption(
|
final face = art.difficultyOption(
|
||||||
Difficulty.values[engine.menuSelectedDifficultyIndex],
|
Difficulty.values[selectedDifficultyIndex],
|
||||||
);
|
);
|
||||||
if (face != null) {
|
if (face != null) {
|
||||||
_blitVgaImage(face, 28 + 264 - face.width - 10, 92);
|
_blitVgaImage(face, 28 + 264 - face.width - 10, 92);
|
||||||
}
|
}
|
||||||
|
|
||||||
final cursor = art.pic(engine.isMenuCursorAltFrame ? 9 : 8);
|
final cursor = art.pic(
|
||||||
|
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
||||||
|
);
|
||||||
const rowYStart = 86;
|
const rowYStart = 86;
|
||||||
const rowStep = 15;
|
const rowStep = 15;
|
||||||
const textX = 70;
|
const textX = 70;
|
||||||
@@ -398,7 +402,7 @@ class SixelRasterizer extends CliRasterizer<String> {
|
|||||||
];
|
];
|
||||||
for (int i = 0; i < Difficulty.values.length; i++) {
|
for (int i = 0; i < Difficulty.values.length; i++) {
|
||||||
final y = rowYStart + (i * rowStep);
|
final y = rowYStart + (i * rowStep);
|
||||||
final isSelected = i == engine.menuSelectedDifficultyIndex;
|
final isSelected = i == selectedDifficultyIndex;
|
||||||
|
|
||||||
if (isSelected && cursor != null) {
|
if (isSelected && cursor != null) {
|
||||||
_blitVgaImage(cursor, 38, y - 2);
|
_blitVgaImage(cursor, 38, y - 2);
|
||||||
@@ -532,7 +536,7 @@ class SixelRasterizer extends CliRasterizer<String> {
|
|||||||
StringBuffer sb = StringBuffer();
|
StringBuffer sb = StringBuffer();
|
||||||
sb.write('\x1bPq');
|
sb.write('\x1bPq');
|
||||||
|
|
||||||
double damageIntensity = _engine.isDifficultySelectionPending
|
double damageIntensity = _engine.difficulty == null
|
||||||
? 0.0
|
? 0.0
|
||||||
: _engine.player.damageFlash;
|
: _engine.player.damageFlash;
|
||||||
int redBoost = (150 * damageIntensity).toInt();
|
int redBoost = (150 * damageIntensity).toInt();
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void drawMenu(WolfEngine engine) {
|
void drawMenu(WolfEngine engine) {
|
||||||
|
final int selectedDifficultyIndex =
|
||||||
|
engine.menuManager.selectedDifficultyIndex;
|
||||||
final int bgColor = _rgbToFrameColor(engine.menuBackgroundRgb);
|
final int bgColor = _rgbToFrameColor(engine.menuBackgroundRgb);
|
||||||
final int panelColor = _rgbToFrameColor(engine.menuPanelRgb);
|
final int panelColor = _rgbToFrameColor(engine.menuPanelRgb);
|
||||||
final int headingColor = ColorPalette.vga32Bit[119];
|
final int headingColor = ColorPalette.vga32Bit[119];
|
||||||
@@ -214,13 +216,15 @@ class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final face = art.difficultyOption(
|
final face = art.difficultyOption(
|
||||||
Difficulty.values[engine.menuSelectedDifficultyIndex],
|
Difficulty.values[selectedDifficultyIndex],
|
||||||
);
|
);
|
||||||
if (face != null) {
|
if (face != null) {
|
||||||
_blitVgaImage(face, panelX + panelW - face.width - 10, panelY + 22);
|
_blitVgaImage(face, panelX + panelW - face.width - 10, panelY + 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
final cursor = art.pic(engine.isMenuCursorAltFrame ? 9 : 8);
|
final cursor = art.pic(
|
||||||
|
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
||||||
|
);
|
||||||
const rowYStart = panelY + 16;
|
const rowYStart = panelY + 16;
|
||||||
const rowStep = 15;
|
const rowStep = 15;
|
||||||
const textX = panelX + 42;
|
const textX = panelX + 42;
|
||||||
@@ -233,7 +237,7 @@ class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
|
|||||||
|
|
||||||
for (int i = 0; i < Difficulty.values.length; i++) {
|
for (int i = 0; i < Difficulty.values.length; i++) {
|
||||||
final y = rowYStart + (i * rowStep);
|
final y = rowYStart + (i * rowStep);
|
||||||
final isSelected = i == engine.menuSelectedDifficultyIndex;
|
final isSelected = i == selectedDifficultyIndex;
|
||||||
|
|
||||||
if (isSelected && cursor != null) {
|
if (isSelected && cursor != null) {
|
||||||
_blitVgaImage(cursor, panelX + 10, y - 2);
|
_blitVgaImage(cursor, panelX + 10, y - 2);
|
||||||
@@ -303,8 +307,7 @@ class SoftwareRasterizer extends Rasterizer<FrameBuffer> {
|
|||||||
@override
|
@override
|
||||||
FrameBuffer finalizeFrame() {
|
FrameBuffer finalizeFrame() {
|
||||||
// If the player took damage, overlay a red tint across the 3D view
|
// If the player took damage, overlay a red tint across the 3D view
|
||||||
if (!_engine.isDifficultySelectionPending &&
|
if (_engine.difficulty != null && _engine.player.damageFlash > 0) {
|
||||||
_engine.player.damageFlash > 0) {
|
|
||||||
_applyDamageFlash();
|
_applyDamageFlash();
|
||||||
}
|
}
|
||||||
return _buffer; // Return the fully painted pixel array
|
return _buffer; // Return the fully painted pixel array
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class _WolfAsciiRendererState extends BaseWolfRendererState<WolfAsciiRenderer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get scaffoldColor => widget.engine.isDifficultySelectionPending
|
Color get scaffoldColor => widget.engine.difficulty == null
|
||||||
? _colorFromRgb(widget.engine.menuBackgroundRgb)
|
? _colorFromRgb(widget.engine.menuBackgroundRgb)
|
||||||
: const Color.fromARGB(255, 4, 64, 64);
|
: const Color.fromARGB(255, 4, 64, 64);
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class _WolfFlutterRendererState
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Color get scaffoldColor => widget.engine.isDifficultySelectionPending
|
Color get scaffoldColor => widget.engine.difficulty == null
|
||||||
? _colorFromRgb(widget.engine.menuBackgroundRgb)
|
? _colorFromRgb(widget.engine.menuBackgroundRgb)
|
||||||
: const Color.fromARGB(255, 4, 64, 64);
|
: const Color.fromARGB(255, 4, 64, 64);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user