diff --git a/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart b/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart index f51104c..d2bdf48 100644 --- a/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart +++ b/packages/wolf_3d_dart/lib/src/engine/managers/door_manager.dart @@ -3,15 +3,22 @@ import 'dart:math' as math; import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_entities.dart'; +/// Orchestrates the state and interaction of all doors within a level. +/// +/// This manager maintains a map of [Door] objects and ensures that when a +/// door's state changes (e.g., starts closing), the appropriate global +/// game events (like sound effects) are triggered. class DoorManager { - // Key is '$x,$y' + /// A lookup table for doors, keyed by their grid coordinates: "$x,$y". final Map doors = {}; - // Callback to play sounds without tightly coupling to the audio engine + /// Callback used to trigger sound effects without tight coupling + /// to a specific audio engine implementation. final void Function(int sfxId) onPlaySound; DoorManager({required this.onPlaySound}); + /// Scans the [wallGrid] for tile IDs >= 90 and initializes [Door] instances. void initDoors(SpriteMap wallGrid) { doors.clear(); for (int y = 0; y < wallGrid.length; y++) { @@ -24,17 +31,19 @@ class DoorManager { } } + /// Updates all managed doors and plays the "close" sound if a door + /// naturally begins its closing cycle. void update(Duration elapsed) { for (final door in doors.values) { final newState = door.update(elapsed.inMilliseconds); - // The Manager decides: "If a door just started closing, play the close sound." if (newState == DoorState.closing) { onPlaySound(WolfSound.closeDoor); } } } + /// Handles player-initiated interaction with doors based on facing direction. void handleInteraction(double playerX, double playerY, double playerAngle) { int targetX = (playerX + math.cos(playerAngle)).toInt(); int targetY = (playerY + math.sin(playerAngle)).toInt(); @@ -42,14 +51,15 @@ class DoorManager { String key = '$targetX,$targetY'; if (doors.containsKey(key)) { if (doors[key]!.interact()) { - // The Manager decides: "Player successfully opened a door, play the sound." onPlaySound(WolfSound.openDoor); } } } + /// Attempted by AI entities to open a door blocking their path. void tryOpenDoor(int x, int y) { String key = '$x,$y'; + // AI only interacts if the door is currently fully closed (offset == 0). if (doors.containsKey(key) && doors[key]!.offset == 0.0) { if (doors[key]!.interact()) { onPlaySound(WolfSound.openDoor); @@ -68,12 +78,14 @@ class DoorManager { return offsets; } + /// Returns true if the door at [x], [y] is sufficiently open for + /// an entity (player or enemy) to walk through. bool isDoorOpenEnough(int x, int y) { String key = '$x,$y'; if (doors.containsKey(key)) { - // 0.7 offset means 70% open, similar to the original engine's check + // 0.7 (70% open) is the standard collision threshold. return doors[key]!.offset > 0.7; } - return false; // Not a door we manage + return false; } } diff --git a/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart b/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart index e8c6679..53d44b8 100644 --- a/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart +++ b/packages/wolf_3d_dart/lib/src/engine/managers/pushwall_manager.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; +/// Represents a secret wall that slides when triggered by the player. class Pushwall { int x; int y; @@ -14,16 +15,21 @@ class Pushwall { Pushwall(this.x, this.y, this.mapId); } +/// Manages the detection and real-time movement of secret pushwalls. +/// +/// Only one pushwall can be active (moving) at any given time. class PushwallManager { final Map pushwalls = {}; Pushwall? activePushwall; + /// Populates the lookup table using the level's object grid. void initPushwalls(SpriteMap wallGrid, SpriteMap objectGrid) { pushwalls.clear(); activePushwall = null; for (int y = 0; y < objectGrid.length; y++) { for (int x = 0; x < objectGrid[y].length; x++) { + // Pushwalls are identified by a specific trigger ID in the object layer. if (objectGrid[y][x] == MapObject.pushwallTrigger) { pushwalls['$x,$y'] = Pushwall(x, y, wallGrid[y][x]); } @@ -31,16 +37,16 @@ class PushwallManager { } } + /// Logic for sliding the wall and updating the physical [wallGrid] for collisions. void update(Duration elapsed, SpriteMap wallGrid) { if (activePushwall == null) return; final pw = activePushwall!; - // Original logic: 1/128 tile per tick. - // At 70 ticks/sec, that is roughly 0.54 tiles per second. + // Movement speed matches the original DOS executable (roughly 0.54 tiles/sec). const double originalSpeed = 0.546875; pw.offset += (elapsed.inMilliseconds / 1000.0) * originalSpeed; - // Once it crosses a full tile boundary, we update the collision grid! + // Handle the transition between grid tiles. if (pw.offset >= 1.0) { pw.offset -= 1.0; pw.tilesMoved++; @@ -48,17 +54,19 @@ class PushwallManager { int nextX = pw.x + pw.dirX; int nextY = pw.y + pw.dirY; - // Move the solid block in the physical grid + // Update structural grid: the wall is now "solid" in the new tile. wallGrid[nextY][nextX] = pw.mapId; - wallGrid[pw.y][pw.x] = 0; // Clear the old space so the player can walk in - // Update the dictionary key + // The previous tile is now walkable. + wallGrid[pw.y][pw.x] = 0; + + // Update lookup keys pushwalls.remove('${pw.x},${pw.y}'); pw.x = nextX; pw.y = nextY; pushwalls['${pw.x},${pw.y}'] = pw; - // Check if we should keep sliding + // Determine if movement is blocked by the world boundary or another solid tile. bool blocked = false; int checkX = pw.x + pw.dirX; int checkY = pw.y + pw.dirY; @@ -69,10 +77,10 @@ class PushwallManager { checkY >= wallGrid.length) { blocked = true; } else if (wallGrid[checkY][checkX] != 0) { - blocked = true; // Blocked by another wall or a door + blocked = true; } - // Standard Wolf3D pushwalls move exactly 2 tiles (or 1 if blocked) + // Secret walls stop after moving 2 tiles or hitting an obstruction. if (pw.tilesMoved >= 2 || blocked) { activePushwall = null; pw.offset = 0.0; @@ -80,13 +88,13 @@ class PushwallManager { } } + /// Triggers a pushwall based on player proximity and facing direction. void handleInteraction( double playerX, double playerY, double playerAngle, SpriteMap wallGrid, ) { - // Only one pushwall can move at a time in the original engine! if (activePushwall != null) return; int targetX = (playerX + math.cos(playerAngle)).toInt(); @@ -96,7 +104,7 @@ class PushwallManager { if (pushwalls.containsKey(key)) { final pw = pushwalls[key]!; - // Determine the push direction based on the player's relative position + // Determine push direction (X or Y) based on which axis the player is closer to. double dx = (targetX + 0.5) - playerX; double dy = (targetY + 0.5) - playerY; @@ -108,10 +116,9 @@ class PushwallManager { pw.dirY = dy > 0 ? 1 : -1; } - // Make sure the tile behind the wall is empty before starting the push + // Only start the push if the space immediately behind the wall is empty. int checkX = targetX + pw.dirX; int checkY = targetY + pw.dirY; - if (wallGrid[checkY][checkX] == 0) { activePushwall = pw; }