diff --git a/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart b/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart index d3748ab..2be75f3 100644 --- a/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart +++ b/packages/wolf_3d_dart/lib/src/engine/wolf_3d_engine_base.dart @@ -75,6 +75,9 @@ class WolfEngine { bool isInitialized = false; + /// Tracks the last time a sound wave was emitted to prevent BFS spam. + int _lastAcousticAlertTime = 0; + /// Initializes the engine, sets the starting episode, and loads the first level. void init() { _currentEpisodeIndex = startingEpisode; @@ -222,8 +225,15 @@ class WolfEngine { if (input.requestedWeapon != null) { player.requestWeaponSwitch(input.requestedWeapon!); } + if (input.isFiring) { player.fire(_timeAliveMs); + + // Throttle the acoustic flood-fill to emit a "wave" every 400ms while firing + if (_timeAliveMs - _lastAcousticAlertTime > 400) { + _propagateGunfire(); + _lastAcousticAlertTime = _timeAliveMs; + } } else { player.releaseTrigger(); } @@ -363,6 +373,70 @@ class WolfEngine { if (itemsToAdd.isNotEmpty) entities.addAll(itemsToAdd); } + /// Propagates weapon noise through corridors and open doors using a Breadth-First Search. + void _propagateGunfire() { + int maxAcousticRange = 20; // How many tiles the sound wave travels + int startX = player.x.toInt(); + int startY = player.y.toInt(); + + // Track visited tiles using a 1D index to prevent infinite loops + Set visited = {startY * 64 + startX}; + + List<({int x, int y})> queue = [(x: startX, y: startY)]; + + int distance = 0; + + while (queue.isNotEmpty && distance < maxAcousticRange) { + List<({int x, int y})> nextQueue = []; + + for (var tile in queue) { + // 1. Alert any enemies standing on this specific tile + for (var entity in entities) { + if (entity is Enemy && + !entity.isAlerted && + entity.state != EntityState.dead) { + if (entity.position.x.toInt() == tile.x && + entity.position.y.toInt() == tile.y) { + entity.isAlerted = true; + + // Wake them up! + if (entity.state == EntityState.idle || + entity.state == EntityState.ambush) { + entity.state = EntityState.patrolling; + entity.lastActionTime = _timeAliveMs; + } + } + } + } + + // 2. Expand the sound wave outward to North, East, South, West + final neighbors = <({int x, int y})>[ + (x: tile.x + 1, y: tile.y), + (x: tile.x - 1, y: tile.y), + (x: tile.x, y: tile.y + 1), + (x: tile.x, y: tile.y - 1), + ]; + + for (var n in neighbors) { + // Keep it within the 64x64 grid limits + if (n.x >= 0 && n.x < 64 && n.y >= 0 && n.y < 64) { + int idx = n.y * 64 + n.x; + if (!visited.contains(idx)) { + visited.add(idx); + + // Sound only travels through walkable tiles (air and OPEN doors). + if (isWalkable(n.x, n.y)) { + nextQueue.add(n); + } + } + } + } + } + queue = nextQueue; + distance++; + } + } + /// Returns true if a tile is empty or contains a door that is sufficiently open. bool isWalkable(int x, int y) { if (currentLevel[y][x] == 0) return true;