Fixed ASCII rasterizer, abstracted out input and audio, and created CLI client (untested)
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
96
bin/cli_main.dart
Normal file
96
bin/cli_main.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:wolf_3d_data/wolf_3d_data.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_input/wolf_3d_input.dart';
|
||||
import 'package:wolf_3d_synth/wolf_3d_synth.dart';
|
||||
|
||||
// Helper to gracefully exit and restore the terminal
|
||||
void exitCleanly(int code) {
|
||||
stdout.write('\x1b[0m'); // Reset color
|
||||
stdout.write('\x1b[2J\x1b[H'); // Clear screen
|
||||
stdout.write('\x1b[?25h'); // SHOW the cursor again
|
||||
exit(code);
|
||||
}
|
||||
|
||||
void main() async {
|
||||
stdin.echoMode = false;
|
||||
stdin.lineMode = false;
|
||||
|
||||
// HIDE the blinking cursor and clear the screen to prep the canvas
|
||||
stdout.write('\x1b[?25l\x1b[2J');
|
||||
|
||||
// ... (Keep your game discovery and instantiation exactly the same) ...
|
||||
final availableGames = await WolfensteinLoader.discover(
|
||||
directoryPath: 'assets/shareware',
|
||||
recursive: true,
|
||||
);
|
||||
final data = availableGames.values.first;
|
||||
|
||||
final input = CliInput();
|
||||
final cliAudio = CliSilentAudio();
|
||||
|
||||
final engine = WolfEngine(
|
||||
data: data,
|
||||
difficulty: Difficulty.bringEmOn,
|
||||
startingEpisode: 0,
|
||||
audio: cliAudio,
|
||||
onGameWon: () {
|
||||
exitCleanly(0);
|
||||
print("YOU WON!");
|
||||
},
|
||||
);
|
||||
|
||||
final rasterizer = AsciiRasterizer();
|
||||
final buffer = FrameBuffer(120, 40);
|
||||
|
||||
engine.init();
|
||||
|
||||
stdin.listen((List<int> bytes) {
|
||||
if (bytes.contains(113) || bytes.contains(27)) {
|
||||
exitCleanly(0);
|
||||
}
|
||||
input.handleKey(bytes);
|
||||
});
|
||||
|
||||
Stopwatch stopwatch = Stopwatch()..start();
|
||||
Duration lastTick = Duration.zero;
|
||||
|
||||
Timer.periodic(const Duration(milliseconds: 33), (timer) {
|
||||
// 1. Terminal Size Safety Check
|
||||
if (stdout.hasTerminal) {
|
||||
if (stdout.terminalColumns < 120 || stdout.terminalLines < 40) {
|
||||
// Clear the screen and print the warning at the top left
|
||||
stdout.write('\x1b[2J\x1b[H');
|
||||
stdout.write('\x1b[31m[ ERROR ] TERMINAL TOO SMALL\x1b[0m\n\n');
|
||||
stdout.write(
|
||||
'Wolfenstein 3D requires a minimum resolution of 120x40.\n',
|
||||
);
|
||||
stdout.write(
|
||||
'Current size: \x1b[33m${stdout.terminalColumns}x${stdout.terminalLines}\x1b[0m\n\n',
|
||||
);
|
||||
stdout.write('Please resize your window to resume the game...');
|
||||
|
||||
// Prevent the engine from simulating a massive time jump when resumed
|
||||
lastTick = stopwatch.elapsed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Normal Game Loop
|
||||
Duration currentTick = stopwatch.elapsed;
|
||||
Duration elapsed = currentTick - lastTick;
|
||||
lastTick = currentTick;
|
||||
|
||||
// Move cursor to top-left (0,0) before drawing the frame
|
||||
stdout.write('\x1b[H');
|
||||
|
||||
engine.tick(elapsed, input.currentInput);
|
||||
rasterizer.render(engine, buffer);
|
||||
rasterizer.finalizeFrame();
|
||||
|
||||
stdout.write(rasterizer.toAnsiString());
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d.dart';
|
||||
import 'package:wolf_3d_renderer/wolf_3d_renderer.dart';
|
||||
import 'package:wolf_3d_renderer/wolf_3d_ascii_renderer.dart';
|
||||
|
||||
class DifficultyScreen extends StatefulWidget {
|
||||
const DifficultyScreen({
|
||||
@@ -26,7 +26,7 @@ class _DifficultyScreenState extends State<DifficultyScreen> {
|
||||
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => WolfRenderer(
|
||||
builder: (_) => WolfAsciiRenderer(
|
||||
Wolf3d.I.activeGame,
|
||||
difficulty: difficulty,
|
||||
startingEpisode: Wolf3d.I.activeEpisode,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
|
||||
@@ -34,8 +33,17 @@ abstract class AsciiThemes {
|
||||
|
||||
class ColoredChar {
|
||||
final String char;
|
||||
final Color color;
|
||||
ColoredChar(this.char, this.color);
|
||||
final int rawColor; // Stores the AABBGGRR integer from the palette
|
||||
|
||||
ColoredChar(this.char, this.rawColor);
|
||||
|
||||
// Safely extract the exact RGB channels regardless of framework
|
||||
int get r => rawColor & 0xFF;
|
||||
int get g => (rawColor >> 8) & 0xFF;
|
||||
int get b => (rawColor >> 16) & 0xFF;
|
||||
|
||||
// Outputs standard AARRGGBB for Flutter's Color(int) constructor
|
||||
int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
class AsciiRasterizer extends Rasterizer {
|
||||
@@ -49,38 +57,29 @@ class AsciiRasterizer extends Rasterizer {
|
||||
@override
|
||||
double get aspectMultiplier => 0.6;
|
||||
|
||||
// --- HELPER: Color Conversion ---
|
||||
Color _vgaToColor(int vgaColor) {
|
||||
int r = vgaColor & 0xFF;
|
||||
int g = (vgaColor >> 8) & 0xFF;
|
||||
int b = (vgaColor >> 16) & 0xFF;
|
||||
|
||||
return Color.fromARGB(255, r, g, b);
|
||||
}
|
||||
|
||||
// Intercept the base render call to initialize our text grid
|
||||
@override
|
||||
dynamic render(WolfEngine engine, FrameBuffer buffer) {
|
||||
_engine = engine;
|
||||
_screen = List.generate(
|
||||
buffer.height,
|
||||
(_) => List.filled(buffer.width, ColoredChar(' ', Colors.black)),
|
||||
(_) =>
|
||||
List.filled(buffer.width, ColoredChar(' ', ColorPalette.vga32Bit[0])),
|
||||
);
|
||||
return super.render(engine, buffer);
|
||||
}
|
||||
|
||||
@override
|
||||
void prepareFrame(WolfEngine engine) {
|
||||
final Color ceilingColor = _vgaToColor(ColorPalette.vga32Bit[25]);
|
||||
final Color floorColor = _vgaToColor(ColorPalette.vga32Bit[29]);
|
||||
// Just grab the raw ints!
|
||||
final int ceilingColor = ColorPalette.vga32Bit[25];
|
||||
final int floorColor = ColorPalette.vga32Bit[29];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (y < viewHeight / 2) {
|
||||
// Fetch the solid character from the theme
|
||||
_screen[y][x] = ColoredChar(activeTheme.solid, ceilingColor);
|
||||
} else if (y < viewHeight) {
|
||||
// Fetch the solid character from the theme
|
||||
_screen[y][x] = ColoredChar(activeTheme.solid, floorColor);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +97,7 @@ class AsciiRasterizer extends Rasterizer {
|
||||
double perpWallDist,
|
||||
int side,
|
||||
) {
|
||||
double brightness = (4.0 / (perpWallDist + 1.0));
|
||||
double brightness = calculateDepthBrightness(perpWallDist);
|
||||
String wallChar = activeTheme.getByBrightness(brightness);
|
||||
|
||||
for (int y = drawStart; y < drawEnd; y++) {
|
||||
@@ -107,16 +106,11 @@ class AsciiRasterizer extends Rasterizer {
|
||||
int texY = (relativeY * 64).toInt().clamp(0, 63);
|
||||
|
||||
int colorByte = texture.pixels[texX * 64 + texY];
|
||||
Color pixelColor = _vgaToColor(ColorPalette.vga32Bit[colorByte]);
|
||||
int pixelColor = ColorPalette.vga32Bit[colorByte]; // Raw int
|
||||
|
||||
// Faux directional lighting
|
||||
// Faux directional lighting using your new base class method
|
||||
if (side == 1) {
|
||||
pixelColor = Color.fromARGB(
|
||||
255,
|
||||
(pixelColor.r * 0.9).toInt().clamp(0, 255),
|
||||
(pixelColor.g * 0.9).toInt().clamp(0, 255),
|
||||
(pixelColor.b * 0.9).toInt().clamp(0, 255),
|
||||
);
|
||||
pixelColor = shadeColor(pixelColor);
|
||||
}
|
||||
|
||||
_screen[y][x] = ColoredChar(wallChar, pixelColor);
|
||||
@@ -133,7 +127,8 @@ class AsciiRasterizer extends Rasterizer {
|
||||
int texX,
|
||||
double transformY,
|
||||
) {
|
||||
double brightness = (4.0 / (transformY + 1.0)).clamp(0.0, 1.0);
|
||||
// Ask the base class for the depth brightness
|
||||
double brightness = calculateDepthBrightness(transformY);
|
||||
String spriteChar = activeTheme.getByBrightness(brightness);
|
||||
|
||||
for (
|
||||
@@ -148,7 +143,7 @@ class AsciiRasterizer extends Rasterizer {
|
||||
if (colorByte != 255) {
|
||||
_screen[y][stripeX] = ColoredChar(
|
||||
spriteChar,
|
||||
_vgaToColor(ColorPalette.vga32Bit[colorByte]),
|
||||
ColorPalette.vga32Bit[colorByte],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -180,7 +175,7 @@ class AsciiRasterizer extends Rasterizer {
|
||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) {
|
||||
_screen[drawY][drawX] = ColoredChar(
|
||||
activeTheme.solid,
|
||||
_vgaToColor(ColorPalette.vga32Bit[colorByte]),
|
||||
ColorPalette.vga32Bit[colorByte],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -310,10 +305,9 @@ class AsciiRasterizer extends Rasterizer {
|
||||
|
||||
int colorByte = image.pixels[index];
|
||||
if (colorByte != 255) {
|
||||
// Using '█' for UI to make it look solid
|
||||
_screen[drawY][drawX] = ColoredChar(
|
||||
activeTheme.solid,
|
||||
_vgaToColor(ColorPalette.vga32Bit[colorByte]),
|
||||
ColorPalette.vga32Bit[colorByte],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -322,7 +316,6 @@ class AsciiRasterizer extends Rasterizer {
|
||||
}
|
||||
|
||||
// --- DAMAGE FLASH ---
|
||||
|
||||
void _applyDamageFlash() {
|
||||
double intensity = _engine.player.damageFlash;
|
||||
int redBoost = (150 * intensity).toInt();
|
||||
@@ -330,24 +323,55 @@ class AsciiRasterizer extends Rasterizer {
|
||||
|
||||
for (int y = 0; y < viewHeight; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
Color c = _screen[y][x].color;
|
||||
ColoredChar cell = _screen[y][x];
|
||||
|
||||
int r = ((c.r * 255).round().clamp(0, 255) + redBoost).clamp(0, 255);
|
||||
int g = ((c.g * 255).round().clamp(0, 255) * colorDrop).toInt().clamp(
|
||||
0,
|
||||
255,
|
||||
);
|
||||
int b = ((c.b * 255).round().clamp(0, 255) * colorDrop).toInt().clamp(
|
||||
0,
|
||||
255,
|
||||
);
|
||||
// Use our safe getters!
|
||||
int r = cell.r;
|
||||
int g = cell.g;
|
||||
int b = cell.b;
|
||||
|
||||
// Replace the existing character with a red-tinted version
|
||||
_screen[y][x] = ColoredChar(
|
||||
_screen[y][x].char,
|
||||
Color.fromARGB(255, r, g, b),
|
||||
);
|
||||
r = (r + redBoost).clamp(0, 255);
|
||||
g = (g * colorDrop).toInt().clamp(0, 255);
|
||||
b = (b * colorDrop).toInt().clamp(0, 255);
|
||||
|
||||
// Pack back into the native AABBGGRR format that ColoredChar expects
|
||||
int newRawColor = (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||
|
||||
_screen[y][x] = ColoredChar(cell.char, newRawColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the current frame to a single printable ANSI string
|
||||
String toAnsiString() {
|
||||
StringBuffer buffer = StringBuffer();
|
||||
|
||||
int lastR = -1;
|
||||
int lastG = -1;
|
||||
int lastB = -1;
|
||||
|
||||
for (int y = 0; y < _screen.length; y++) {
|
||||
List<ColoredChar> row = _screen[y];
|
||||
for (ColoredChar cell in row) {
|
||||
if (cell.r != lastR || cell.g != lastG || cell.b != lastB) {
|
||||
buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m');
|
||||
lastR = cell.r;
|
||||
lastG = cell.g;
|
||||
lastB = cell.b;
|
||||
}
|
||||
buffer.write(cell.char);
|
||||
}
|
||||
|
||||
// Only print a newline if we are NOT on the very last row.
|
||||
// This stops the terminal from scrolling down!
|
||||
if (y < _screen.length - 1) {
|
||||
buffer.write('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the terminal color at the very end
|
||||
buffer.write('\x1b[0m');
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,17 @@ abstract class Rasterizer {
|
||||
/// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
|
||||
dynamic finalizeFrame();
|
||||
|
||||
// ===========================================================================
|
||||
// SHARED LIGHTING MATH
|
||||
// ===========================================================================
|
||||
|
||||
/// Calculates depth-based lighting falloff (0.0 to 1.0).
|
||||
/// While the original Wolf3D didn't use depth fog, this provides a great
|
||||
/// atmospheric effect for custom renderers (like ASCII dithering).
|
||||
double calculateDepthBrightness(double distance) {
|
||||
return (10.0 / (distance + 2.0)).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// CORE ENGINE MATH (Shared across all renderers)
|
||||
// ===========================================================================
|
||||
@@ -359,4 +370,12 @@ abstract class Rasterizer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Darkens a 32-bit 0xAABBGGRR color by roughly 30% without touching Alpha
|
||||
int shadeColor(int color) {
|
||||
int r = (color & 0xFF) * 7 ~/ 10;
|
||||
int g = ((color >> 8) & 0xFF) * 7 ~/ 10;
|
||||
int b = ((color >> 16) & 0xFF) * 7 ~/ 10;
|
||||
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class SoftwareRasterizer extends Rasterizer {
|
||||
|
||||
// Darken Y-side walls for faux directional lighting
|
||||
if (side == 1) {
|
||||
pixelColor = _shadeColor(pixelColor);
|
||||
pixelColor = shadeColor(pixelColor);
|
||||
}
|
||||
|
||||
_buffer.pixels[y * width + x] = pixelColor;
|
||||
@@ -235,14 +235,6 @@ class SoftwareRasterizer extends Rasterizer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Darkens a 32-bit 0xAABBGGRR color by roughly 30% without touching Alpha
|
||||
int _shadeColor(int color) {
|
||||
int r = (color & 0xFF) * 7 ~/ 10;
|
||||
int g = ((color >> 8) & 0xFF) * 7 ~/ 10;
|
||||
int b = ((color >> 16) & 0xFF) * 7 ~/ 10;
|
||||
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
|
||||
/// Tints the top 80% of the screen red based on player.damageFlash intensity
|
||||
void _applyDamageFlash() {
|
||||
// Grab the intensity (0.0 to 1.0)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
/// More dartdocs go here.
|
||||
library;
|
||||
|
||||
export 'src/ascii_rasterizer.dart';
|
||||
export 'src/engine_audio.dart';
|
||||
export 'src/engine_input.dart';
|
||||
export 'src/managers/door_manager.dart';
|
||||
|
||||
38
packages/wolf_3d_flutter/lib/wolf_3d_input_flutter.dart
Normal file
38
packages/wolf_3d_flutter/lib/wolf_3d_input_flutter.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||
import 'package:wolf_3d_input/wolf_3d_input.dart';
|
||||
|
||||
class Wolf3dFlutterInput extends Wolf3dInput {
|
||||
Set<LogicalKeyboardKey> _previousKeys = {};
|
||||
|
||||
@override
|
||||
void update() {
|
||||
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
|
||||
final newlyPressedKeys = pressedKeys.difference(_previousKeys);
|
||||
|
||||
isMovingForward = pressedKeys.contains(LogicalKeyboardKey.keyW);
|
||||
isMovingBackward = pressedKeys.contains(LogicalKeyboardKey.keyS);
|
||||
isTurningLeft = pressedKeys.contains(LogicalKeyboardKey.keyA);
|
||||
isTurningRight = pressedKeys.contains(LogicalKeyboardKey.keyD);
|
||||
|
||||
isInteracting = newlyPressedKeys.contains(LogicalKeyboardKey.space);
|
||||
|
||||
isFiring =
|
||||
pressedKeys.contains(LogicalKeyboardKey.controlLeft) &&
|
||||
!pressedKeys.contains(LogicalKeyboardKey.space);
|
||||
|
||||
requestedWeapon = null;
|
||||
for (final LogicalKeyboardKey key in newlyPressedKeys) {
|
||||
if (key == LogicalKeyboardKey.digit1) requestedWeapon = WeaponType.knife;
|
||||
if (key == LogicalKeyboardKey.digit2) requestedWeapon = WeaponType.pistol;
|
||||
if (key == LogicalKeyboardKey.digit3) {
|
||||
requestedWeapon = WeaponType.machineGun;
|
||||
}
|
||||
if (key == LogicalKeyboardKey.digit4) {
|
||||
requestedWeapon = WeaponType.chainGun;
|
||||
}
|
||||
}
|
||||
|
||||
_previousKeys = Set.from(pressedKeys);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ dependencies:
|
||||
wolf_3d_data_types: any
|
||||
wolf_3d_synth: any
|
||||
wolf_3d_engine: any
|
||||
wolf_3d_entities: any
|
||||
wolf_3d_input: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
47
packages/wolf_3d_input/lib/src/cli_input.dart
Normal file
47
packages/wolf_3d_input/lib/src/cli_input.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||
import 'package:wolf_3d_input/src/wolf_3d_input.dart';
|
||||
|
||||
class CliInput extends Wolf3dInput {
|
||||
// Pending buffer for asynchronous stdin events
|
||||
bool _pForward = false;
|
||||
bool _pBackward = false;
|
||||
bool _pLeft = false;
|
||||
bool _pRight = false;
|
||||
bool _pFire = false;
|
||||
bool _pInteract = false;
|
||||
WeaponType? _pWeapon;
|
||||
|
||||
/// Call this directly from the stdin listener to queue inputs for the next frame
|
||||
void handleKey(List<int> bytes) {
|
||||
String char = String.fromCharCodes(bytes).toLowerCase();
|
||||
|
||||
if (char == 'w') _pForward = true;
|
||||
if (char == 's') _pBackward = true;
|
||||
if (char == 'a') _pLeft = true;
|
||||
if (char == 'd') _pRight = true;
|
||||
|
||||
if (char == 'f' || char == ' ') _pFire = true;
|
||||
if (char == 'e') _pInteract = true;
|
||||
|
||||
if (char == '1') _pWeapon = WeaponType.knife;
|
||||
if (char == '2') _pWeapon = WeaponType.pistol;
|
||||
if (char == '3') _pWeapon = WeaponType.machineGun;
|
||||
if (char == '4') _pWeapon = WeaponType.chainGun;
|
||||
}
|
||||
|
||||
@override
|
||||
void update() {
|
||||
// 1. Move pending inputs to the active state
|
||||
isMovingForward = _pForward;
|
||||
isMovingBackward = _pBackward;
|
||||
isTurningLeft = _pLeft;
|
||||
isTurningRight = _pRight;
|
||||
isFiring = _pFire;
|
||||
isInteracting = _pInteract;
|
||||
requestedWeapon = _pWeapon;
|
||||
|
||||
// 2. Wipe the pending slate clean for the next frame
|
||||
_pForward = _pBackward = _pLeft = _pRight = _pFire = _pInteract = false;
|
||||
_pWeapon = null;
|
||||
}
|
||||
}
|
||||
27
packages/wolf_3d_input/lib/src/wolf_3d_input.dart
Normal file
27
packages/wolf_3d_input/lib/src/wolf_3d_input.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||
|
||||
abstract class Wolf3dInput {
|
||||
bool isMovingForward = false;
|
||||
bool isMovingBackward = false;
|
||||
bool isTurningLeft = false;
|
||||
bool isTurningRight = false;
|
||||
bool isInteracting = false;
|
||||
bool isFiring = false;
|
||||
WeaponType? requestedWeapon;
|
||||
|
||||
/// Called once per frame by the game loop to refresh the state.
|
||||
void update();
|
||||
|
||||
/// Exports the current state as a clean DTO for the engine.
|
||||
/// Subclasses do not need to override this.
|
||||
EngineInput get currentInput => EngineInput(
|
||||
isMovingForward: isMovingForward,
|
||||
isMovingBackward: isMovingBackward,
|
||||
isTurningLeft: isTurningLeft,
|
||||
isTurningRight: isTurningRight,
|
||||
isFiring: isFiring,
|
||||
isInteracting: isInteracting,
|
||||
requestedWeapon: requestedWeapon,
|
||||
);
|
||||
}
|
||||
@@ -1,58 +1,4 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||
library;
|
||||
|
||||
class WolfInput {
|
||||
Set<LogicalKeyboardKey> _previousKeys = {};
|
||||
|
||||
bool isMovingForward = false;
|
||||
bool isMovingBackward = false;
|
||||
bool isTurningLeft = false;
|
||||
bool isTurningRight = false;
|
||||
bool isInteracting = false;
|
||||
bool isFiring = false;
|
||||
WeaponType? requestedWeapon;
|
||||
|
||||
void update() {
|
||||
final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
|
||||
final newlyPressedKeys = pressedKeys.difference(_previousKeys);
|
||||
|
||||
isMovingForward = pressedKeys.contains(LogicalKeyboardKey.keyW);
|
||||
isMovingBackward = pressedKeys.contains(LogicalKeyboardKey.keyS);
|
||||
isTurningLeft = pressedKeys.contains(LogicalKeyboardKey.keyA);
|
||||
isTurningRight = pressedKeys.contains(LogicalKeyboardKey.keyD);
|
||||
|
||||
isInteracting = newlyPressedKeys.contains(LogicalKeyboardKey.space);
|
||||
|
||||
isFiring =
|
||||
pressedKeys.contains(LogicalKeyboardKey.controlLeft) &&
|
||||
!pressedKeys.contains(LogicalKeyboardKey.space);
|
||||
|
||||
requestedWeapon = null;
|
||||
for (final LogicalKeyboardKey key in newlyPressedKeys) {
|
||||
switch (key) {
|
||||
case LogicalKeyboardKey.digit1:
|
||||
requestedWeapon = WeaponType.knife;
|
||||
case LogicalKeyboardKey.digit2:
|
||||
requestedWeapon = WeaponType.pistol;
|
||||
case LogicalKeyboardKey.digit3:
|
||||
requestedWeapon = WeaponType.machineGun;
|
||||
case LogicalKeyboardKey.digit4:
|
||||
requestedWeapon = WeaponType.chainGun;
|
||||
}
|
||||
}
|
||||
|
||||
_previousKeys = Set.from(pressedKeys);
|
||||
}
|
||||
|
||||
/// Exports the current state as a clean DTO for the engine
|
||||
EngineInput get currentInput => EngineInput(
|
||||
isMovingForward: isMovingForward,
|
||||
isMovingBackward: isMovingBackward,
|
||||
isTurningLeft: isTurningLeft,
|
||||
isTurningRight: isTurningRight,
|
||||
isFiring: isFiring,
|
||||
isInteracting: isInteracting,
|
||||
requestedWeapon: requestedWeapon,
|
||||
);
|
||||
}
|
||||
export 'src/cli_input.dart';
|
||||
export 'src/wolf_3d_input.dart';
|
||||
|
||||
@@ -5,54 +5,9 @@ homepage:
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.1
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
resolution: workspace
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
wolf_3d_entities: any
|
||||
wolf_3d_engine: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# To add custom fonts to your package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/to/font-from-package
|
||||
|
||||
@@ -2,3 +2,6 @@ include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
formatter:
|
||||
trailing_commas: preserve
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
|
||||
import 'package:wolf_3d_input/wolf_3d_input.dart';
|
||||
import 'package:wolf_3d_renderer/ascii_rasterizer.dart';
|
||||
|
||||
class WolfAsciiRenderer extends StatefulWidget {
|
||||
const WolfAsciiRenderer(
|
||||
@@ -25,7 +25,7 @@ class WolfAsciiRenderer extends StatefulWidget {
|
||||
|
||||
class _WolfAsciiRendererState extends State<WolfAsciiRenderer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final WolfInput inputManager = WolfInput();
|
||||
final Wolf3dInput inputManager = Wolf3dFlutterInput();
|
||||
late final WolfEngine engine;
|
||||
late Ticker _gameLoop;
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
@@ -116,11 +116,11 @@ class AsciiFrameWidget extends StatelessWidget {
|
||||
children: frameData.map((row) {
|
||||
List<TextSpan> optimizedSpans = [];
|
||||
if (row.isNotEmpty) {
|
||||
Color currentColor = row[0].color;
|
||||
Color currentColor = Color(row[0].argb);
|
||||
StringBuffer currentSegment = StringBuffer(row[0].char);
|
||||
|
||||
for (int i = 1; i < row.length; i++) {
|
||||
if (row[i].color == currentColor) {
|
||||
if (Color(row[i].argb) == currentColor) {
|
||||
currentSegment.write(row[i].char);
|
||||
} else {
|
||||
optimizedSpans.add(
|
||||
@@ -129,7 +129,7 @@ class AsciiFrameWidget extends StatelessWidget {
|
||||
style: TextStyle(color: currentColor),
|
||||
),
|
||||
);
|
||||
currentColor = row[i].color;
|
||||
currentColor = Color(row[i].argb);
|
||||
currentSegment = StringBuffer(row[i].char);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_input_flutter.dart';
|
||||
import 'package:wolf_3d_input/wolf_3d_input.dart';
|
||||
|
||||
class WolfRenderer extends StatefulWidget {
|
||||
@@ -26,7 +27,7 @@ class WolfRenderer extends StatefulWidget {
|
||||
|
||||
class _WolfRendererState extends State<WolfRenderer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final WolfInput inputManager = WolfInput();
|
||||
final Wolf3dInput inputManager = Wolf3dFlutterInput();
|
||||
late final WolfEngine engine;
|
||||
late Ticker _gameLoop;
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
@@ -16,6 +16,7 @@ dependencies:
|
||||
wolf_3d_engine: any
|
||||
wolf_3d_entities: any
|
||||
wolf_3d_input: any
|
||||
wolf_3d_flutter: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
34
packages/wolf_3d_synth/lib/src/silent_renderer.dart
Normal file
34
packages/wolf_3d_synth/lib/src/silent_renderer.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
|
||||
|
||||
class CliSilentAudio implements EngineAudio {
|
||||
@override
|
||||
WolfensteinData? activeGame;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
// No-op for CLI
|
||||
}
|
||||
|
||||
@override
|
||||
void playMenuMusic() {}
|
||||
|
||||
@override
|
||||
void playLevelMusic(WolfLevel level) {
|
||||
// Optional: Print a log so you know it's working!
|
||||
// print("🎵 Playing music for: ${level.name} 🎵");
|
||||
}
|
||||
|
||||
@override
|
||||
void stopMusic() {}
|
||||
|
||||
@override
|
||||
void playSoundEffect(int sfxId) {
|
||||
// Optional: You could use the terminal 'bell' character here
|
||||
// to actually make a system beep when a sound plays!
|
||||
// stdout.write('\x07');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
@@ -3,4 +3,5 @@
|
||||
/// More dartdocs go here.
|
||||
library;
|
||||
|
||||
export 'src/silent_renderer.dart' show CliSilentAudio;
|
||||
export 'src/wolf_3d_audio.dart' show WolfAudio;
|
||||
|
||||
Reference in New Issue
Block a user