@@ -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<String, Door> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Pushwall> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user