From 0c3ca1c090c33913d72c7ceb4e3c8849afc0a3c1 Mon Sep 17 00:00:00 2001 From: Hans Kokx Date: Fri, 13 Mar 2026 17:05:15 +0100 Subject: [PATCH] Fixed color palette and player starting location Signed-off-by: Hans Kokx --- lib/features/map/wolf_level.dart | 2 + lib/features/map/wolf_map_parser.dart | 34 +++-- lib/features/renderer/color_palette.dart | 164 ++++++++++----------- lib/features/renderer/raycast_painter.dart | 10 +- lib/features/renderer/renderer.dart | 116 ++++++++++++--- 5 files changed, 207 insertions(+), 119 deletions(-) diff --git a/lib/features/map/wolf_level.dart b/lib/features/map/wolf_level.dart index 254a6d8..fc05612 100644 --- a/lib/features/map/wolf_level.dart +++ b/lib/features/map/wolf_level.dart @@ -5,11 +5,13 @@ class WolfLevel { final int width; // Always 64 in standard Wolf3D final int height; // Always 64 final Matrix wallGrid; + final Matrix objectGrid; WolfLevel({ required this.name, required this.width, required this.height, required this.wallGrid, + required this.objectGrid, }); } diff --git a/lib/features/map/wolf_map_parser.dart b/lib/features/map/wolf_map_parser.dart index 5973874..720435c 100644 --- a/lib/features/map/wolf_map_parser.dart +++ b/lib/features/map/wolf_map_parser.dart @@ -48,27 +48,36 @@ abstract class WolfMapParser { } String name = ascii.decode(nameBytes); - // 3. EXTRACT AND DECOMPRESS THE WALL DATA + // 3. EXTRACT AND DECOMPRESS BOTH PLANES + + // --- PLANE 0: WALLS --- final compressedWallData = gameMaps.buffer.asUint8List( plane0Offset, plane0Length, ); + Uint16List carmackExpandedWalls = _expandCarmack(compressedWallData); + List flatWallGrid = _expandRlew(carmackExpandedWalls, rlewTag); - // 1st Pass: Un-Carmack - Uint16List carmackExpanded = _expandCarmack(compressedWallData); - // 2nd Pass: Un-RLEW - List flatGrid = _expandRlew(carmackExpanded, rlewTag); + // --- PLANE 1: OBJECTS (NEW) --- + final compressedObjectData = gameMaps.buffer.asUint8List( + plane1Offset, + plane1Length, + ); + Uint16List carmackExpandedObjects = _expandCarmack(compressedObjectData); + List flatObjectGrid = _expandRlew(carmackExpandedObjects, rlewTag); - // Convert the flat List (4096 items) into a Matrix (64x64 grid) Matrix wallGrid = []; + Matrix objectGrid = []; // NEW + for (int y = 0; y < height; y++) { - List row = []; + List wallRow = []; + List objectRow = []; // NEW for (int x = 0; x < width; x++) { - // Note: In original Wolf3D, empty space is usually ID 90 or 106, - // but we can map them down to 0 for your raycaster logic later. - row.add(flatGrid[y * width + x]); + wallRow.add(flatWallGrid[y * width + x]); + objectRow.add(flatObjectGrid[y * width + x]); // NEW } - wallGrid.add(row); + wallGrid.add(wallRow); + objectGrid.add(objectRow); // NEW } levels.add( @@ -76,7 +85,8 @@ abstract class WolfMapParser { name: name, width: width, height: height, - wallGrid: wallGrid, // Pass the fully decompressed matrix! + wallGrid: wallGrid, + objectGrid: objectGrid, ), ); } diff --git a/lib/features/renderer/color_palette.dart b/lib/features/renderer/color_palette.dart index 1e17732..b504ceb 100644 --- a/lib/features/renderer/color_palette.dart +++ b/lib/features/renderer/color_palette.dart @@ -146,53 +146,53 @@ abstract class ColorPalette { Color(0xFF044C00), Color(0xFF044000), Color(0xFFDAFFFF), - Color(0xBAFFFFFF), - Color(0x9DFFFFFF), - Color(0x7DFFFAFF), + Color(0xFFBAFFFF), + Color(0xFF9DFFFF), + Color(0xFF7DFFFA), Color(0xFF5DFFFF), - Color(0x40FFFFFF), - Color(0x20FFFFFF), - Color(0x00FFFFFF), - Color(0x00E6E6FF), - Color(0x00CECEFF), - Color(0x00B6B6FF), - Color(0x009D9DFF), - Color(0x008585FF), - Color(0x007171FF), - Color(0x005959FF), - Color(0x004040FF), + Color(0xFF40FFFF), + Color(0xFF20FFFF), + Color(0xFF00FFFF), + Color(0xFF00E6E6), + Color(0xFF00CECE), + Color(0xFF00B6B6), + Color(0xFF009D9D), + Color(0xFF008585), + Color(0xFF007171), + Color(0xFF005959), + Color(0xFF004040), Color(0xFF5DBEFF), - Color(0x40B2FFFF), - Color(0x20AAFFFF), - Color(0x009DFFFF), - Color(0x008DE6FF), - Color(0x007DCEFF), - Color(0x006DB6FF), - Color(0x005D9DFF), - Color(0xDADADAFF), - Color(0xBABEFFFF), - Color(0x9D9DFFFF), - Color(0x7D81FFFF), - Color(0x5d61ffff), - Color(0x4040FFFF), - Color(0x2024FFFF), - Color(0x0004FFFF), + Color(0xFF40B2FF), + Color(0xFF20AAFF), + Color(0xFF009DFF), + Color(0xFF008DE6), + Color(0xFF007DCE), + Color(0xFF006DB6), + Color(0xFF005D9D), + Color(0xFFDADADA), + Color(0xFFBABEFF), + Color(0xFF9D9DFF), + Color(0xFF7D81FF), + Color(0xFF5D61FF), + Color(0xFF4040FF), + Color(0xFF2024FF), + Color(0xFF0004FF), Color(0xFF0000FF), - Color(0x0000EEFF), - Color(0x0000E2FF), - Color(0x0000D6FF), + Color(0xFF0000EE), + Color(0xFF0000E2), + Color(0xFF0000D6), Color(0xFF0000CA), - Color(0x0000BEFF), - Color(0x0000B2FF), - Color(0x0000A5FF), + Color(0xFF0000BE), + Color(0xFF0000B2), + Color(0xFF0000A5), Color(0xFF000099), - Color(0x000089FF), - Color(0x00007DFF), - Color(0x000071FF), + Color(0xFF000089), + Color(0xFF00007D), + Color(0xFF000071), Color(0xFF000065), - Color(0x000059FF), - Color(0x00004CFF), - Color(0x000040FF), + Color(0xFF000059), + Color(0xFF00004C), + Color(0xFF000040), Color(0xFF282828), Color(0xFFFFE234), Color(0xFFFFD624), @@ -200,15 +200,15 @@ abstract class ColorPalette { Color(0xFFFFC208), Color(0xFFFFB600), Color(0xFFB620FF), - Color(0xAA00FFFF), + Color(0xFFAA00FF), Color(0xFF9900E6), - Color(0x8100CEFF), - Color(0x7500B6FF), - Color(0x61009DFF), + Color(0xFF8100CE), + Color(0xFF7500B6), + Color(0xFF61009D), Color(0xFF500085), - Color(0x440071FF), - Color(0x340059FF), - Color(0x280040FF), + Color(0xFF440071), + Color(0xFF340059), + Color(0xFF280040), Color(0xFFFFDAFF), Color(0xFFFFBAFF), Color(0xFFFF9DFF), @@ -218,13 +218,13 @@ abstract class ColorPalette { Color(0xFFFF20FF), Color(0xFFFF00FF), Color(0xFFE200E6), - Color(0xCA00CEFF), - Color(0xB600B6FF), - Color(0x9D009DFF), + Color(0xFFCA00CE), + Color(0xFFB600B6), + Color(0xFF9D009D), Color(0xFF850085), - Color(0x6D0071FF), - Color(0x590059FF), - Color(0x400040FF), + Color(0xFF6D0071), + Color(0xFF590059), + Color(0xFF400040), Color(0xFFFFEADE), Color(0xFFFFE2D2), Color(0xFFFFDAC6), @@ -254,40 +254,40 @@ abstract class ColorPalette { Color(0xFF5D4020), Color(0xFF553C1C), Color(0xFF483818), - Color(0x403018FF), - Color(0x382C14FF), - Color(0x28200CFF), + Color(0xFF403018), + Color(0xFF382C14), + Color(0xFF28200C), Color(0xFF610065), - Color(0x006565FF), - Color(0x006161FF), - Color(0x00001CFF), + Color(0xFF006565), + Color(0xFF006161), + Color(0xFF00001C), Color(0xFF00002C), - Color(0x302410FF), - Color(0x480048FF), - Color(0x500050FF), + Color(0xFF302410), + Color(0xFF480048), + Color(0xFF500050), Color(0xFF000034), - Color(0x1C1C1CFF), - Color(0x4C4C4CFF), - Color(0x5D5D5DFF), + Color(0xFF1C1C1C), + Color(0xFF4C4C4C), + Color(0xFF5D5D5D), Color(0xFF404040), - Color(0x303030FF), - Color(0x343434FF), - Color(0xDAF6F6FF), - Color(0xBAEAEAFF), - Color(0x00009dde), - Color(0x00075cac), - Color(0x48C2C2FF), + Color(0xFF303030), + Color(0xFF343434), + Color(0xFFDAF6F6), + Color(0xFFBAEAEA), + Color(0xFF9DDEDE), + Color(0xFF75CACA), + Color(0xFF48C2C2), Color(0xFF20B6B6), - Color(0x20B2B2FF), - Color(0x00A5A5FF), - Color(0x009999FF), + Color(0xFF20B2B2), + Color(0xFF00A5A5), + Color(0xFF009999), Color(0xFF008D8D), - Color(0x008585FF), - Color(0x007D7DFF), - Color(0x007979FF), + Color(0xFF008585), + Color(0xFF007D7D), + Color(0xFF007979), Color(0xFF007575), - Color(0x007171FF), - Color(0x006D6DFF), - Color(0x990089FF), + Color(0xFF007171), + Color(0xFF006D6D), + Color(0xFF990089), ]; } diff --git a/lib/features/renderer/raycast_painter.dart b/lib/features/renderer/raycast_painter.dart index f4f0675..c947a80 100644 --- a/lib/features/renderer/raycast_painter.dart +++ b/lib/features/renderer/raycast_painter.dart @@ -165,7 +165,15 @@ class RaycasterPainter extends CustomPainter { // 1. PERFECT TEXTURE MAPPING // Wolf3D stores textures in pairs. Even = N/S (Light), Odd = E/W (Dark). int texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2); - if (side == 1) texNum += 1; // Instantly use the native dark texture! + + if (hitWallId >= 90) { + texNum = 98; // 98 is the canonical index for the Wood Door in VSWAP + // Optional: Doors don't usually have a dark E/W variant, so we don't add +1 for side + } else { + // Standard wall texture pairing + texNum = ((hitWallId - 1) * 2).clamp(0, textures.length - 2); + if (side == 1) texNum += 1; + } int texX = (wallX * 64).toInt().clamp(0, 63); diff --git a/lib/features/renderer/renderer.dart b/lib/features/renderer/renderer.dart index 85d9019..d2079b3 100644 --- a/lib/features/renderer/renderer.dart +++ b/lib/features/renderer/renderer.dart @@ -22,12 +22,14 @@ class _WolfRendererState extends State late WolfMap gameMap; late Matrix currentLevel; - bool _isLoading = true; - - LinearCoordinates player = (x: 2.5, y: 2.5); - double playerAngle = 0.0; final double fov = math.pi / 3; + late LinearCoordinates player; + late double playerAngle; + + bool _isLoading = true; + bool _spaceWasPressed = false; + @override void initState() { super.initState(); @@ -41,16 +43,41 @@ class _WolfRendererState extends State // 2. Extract Level 1 (E1M1) currentLevel = gameMap.levels[0].wallGrid; - // 3. (Optional) Remap the Wolf3D floor IDs so they work with your raycaster. - // In Wolf3D, 90 through 106 are usually empty floor. Your raycaster currently - // expects 0 to be empty space. Let's force them to 0 for now. + final Matrix objectLevel = gameMap.levels[0].objectGrid; + + // 1. SCAN FOR PLAYER SPAWN for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { - // In Wolf3D, wall values are 1 through ~63. - // Values 90+ represent empty floor spaces and doors. - // Let's zero out anything 90 or above, and LEAVE the walls alone. - if (currentLevel[y][x] >= 90) { - currentLevel[y][x] = 0; // Empty space + int objId = objectLevel[y][x]; + + // In Wolf3D, IDs 19-22 represent the player's spawn point and facing direction. + if (objId >= 19 && objId <= 22) { + // Place the player perfectly in the center of the block + player = (x: x + 0.5, y: y + 0.5); + + // Map the ID to standard radians + switch (objId) { + case 19: + playerAngle = 3 * math.pi / 2; // North (Facing up the Y-axis) + case 20: + playerAngle = 0.0; // East (Facing right) + case 21: + playerAngle = math.pi / 2; // South (Facing down) + case 22: + playerAngle = math.pi; // West (Facing left) + } + } + } + } + + // 2. CLEAN UP WALLS / PRESERVE DOORS + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 64; x++) { + int id = currentLevel[y][x]; + if ((id >= 1 && id <= 63) || (id >= 90 && id <= 101)) { + // Leave walls and doors solid + } else { + currentLevel[y][x] = 0; } } } @@ -105,38 +132,79 @@ class _WolfRendererState extends State } void _tick(Duration elapsed) { - const double moveSpeed = 0.05; - const double turnSpeed = 0.04; + const double moveSpeed = 0.12; + const double turnSpeed = 0.08; - double newX = player.x; - double newY = player.y; + double moveStepX = 0; + double moveStepY = 0; final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed; + // 1. Calculate intended movement amounts if (pressedKeys.contains(LogicalKeyboardKey.keyW)) { - newX += math.cos(playerAngle) * moveSpeed; - newY += math.sin(playerAngle) * moveSpeed; + moveStepX += math.cos(playerAngle) * moveSpeed; + moveStepY += math.sin(playerAngle) * moveSpeed; } if (pressedKeys.contains(LogicalKeyboardKey.keyS)) { - newX -= math.cos(playerAngle) * moveSpeed; - newY -= math.sin(playerAngle) * moveSpeed; + moveStepX -= math.cos(playerAngle) * moveSpeed; + moveStepY -= math.sin(playerAngle) * moveSpeed; } + // 2. Handle Turning if (pressedKeys.contains(LogicalKeyboardKey.keyA)) { playerAngle -= turnSpeed; } if (pressedKeys.contains(LogicalKeyboardKey.keyD)) { playerAngle += turnSpeed; } - - // Keep the angle mapped cleanly between 0 and 2*PI (optional, but good practice) if (playerAngle < 0) playerAngle += 2 * math.pi; if (playerAngle > 2 * math.pi) playerAngle -= 2 * math.pi; - if (currentLevel[newY.toInt()][newX.toInt()] == 0) { - player = (x: newX, y: newY); + // 3. Wall Sliding Collision (with Hitbox Margin!) + // A 0.3 margin keeps the camera plane safely out of the walls + const double margin = 0.3; + + double newX = player.x + moveStepX; + // Check the edge of our hitbox, not just the center point + int checkX = (moveStepX > 0) + ? (newX + margin).toInt() + : (newX - margin).toInt(); + + // Try to move along the X axis + if (currentLevel[player.y.toInt()][checkX] == 0) { + player = (x: newX, y: player.y); } + double newY = player.y + moveStepY; + int checkY = (moveStepY > 0) + ? (newY + margin).toInt() + : (newY - margin).toInt(); + + // Try to move along the Y axis + if (currentLevel[checkY][player.x.toInt()] == 0) { + player = (x: player.x, y: newY); + } + + // 4. DOOR INTERACTION LOGIC + bool isSpacePressed = pressedKeys.contains(LogicalKeyboardKey.space); + + if (isSpacePressed && !_spaceWasPressed) { + int targetX = (player.x + math.cos(playerAngle)).toInt(); + int targetY = (player.y + math.sin(playerAngle)).toInt(); + + if (targetY > 0 && + targetY < currentLevel.length && + targetX > 0 && + targetX < currentLevel[0].length) { + int targetBlock = currentLevel[targetY][targetX]; + + if (targetBlock >= 90) { + currentLevel[targetY][targetX] = 0; + } + } + } + _spaceWasPressed = isSpacePressed; + setState(() {}); }