feat: Implement patrol path markers and enhance enemy movement logic
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -122,6 +122,9 @@ class WolfEngine {
|
||||
/// The static level data source used for reloading or reference.
|
||||
late WolfLevel activeLevel;
|
||||
|
||||
/// The static object layer (spawns, markers, triggers) for the active level.
|
||||
late SpriteMap _objectLevel;
|
||||
|
||||
/// All dynamic entities currently in the level (Enemies, Pickups).
|
||||
List<Entity> entities = [];
|
||||
|
||||
@@ -134,6 +137,9 @@ class WolfEngine {
|
||||
/// Total number of known area indices in the active level.
|
||||
int _areaCount = 0;
|
||||
|
||||
/// Last tile processed for patrol marker routing, keyed by enemy debug id.
|
||||
final Map<int, ({int x, int y})> _lastPatrolTileByEnemy = {};
|
||||
|
||||
int _currentEpisodeIndex = 0;
|
||||
|
||||
bool _isPlayerMovingFast = false;
|
||||
@@ -336,6 +342,7 @@ class WolfEngine {
|
||||
/// Wipes the current world state and builds a new floor from map data.
|
||||
void _loadLevel() {
|
||||
entities.clear();
|
||||
_lastPatrolTileByEnemy.clear();
|
||||
|
||||
final episode = data.episodes[_currentEpisodeIndex];
|
||||
activeLevel = episode.levels[_currentLevelIndex];
|
||||
@@ -343,16 +350,16 @@ class WolfEngine {
|
||||
// Create a mutable copy of the wall grid so pushwalls can modify it
|
||||
currentLevel = List.generate(64, (y) => List.from(activeLevel.wallGrid[y]));
|
||||
_areaGrid = List.generate(64, (y) => List.from(activeLevel.areaGrid[y]));
|
||||
final SpriteMap objectLevel = activeLevel.objectGrid;
|
||||
_objectLevel = activeLevel.objectGrid;
|
||||
|
||||
doorManager.initDoors(currentLevel);
|
||||
pushwallManager.initPushwalls(currentLevel, objectLevel);
|
||||
pushwallManager.initPushwalls(currentLevel, _objectLevel);
|
||||
audio.playLevelMusic(activeLevel);
|
||||
|
||||
// Spawn Player and Entities from the Object Grid
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
int objId = objectLevel[y][x];
|
||||
int objId = _objectLevel[y][x];
|
||||
|
||||
// Map IDs 19-22 are Reserved for Player Starts
|
||||
if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) {
|
||||
@@ -552,6 +559,10 @@ class WolfEngine {
|
||||
}
|
||||
|
||||
// Standard AI Update cycle
|
||||
if (!entity.isAlerted && entity.state == EntityState.patrolling) {
|
||||
_applyPatrolPathMarker(entity);
|
||||
}
|
||||
|
||||
final intent = entity.update(
|
||||
elapsedMs: _timeAliveMs,
|
||||
elapsedDeltaMs: elapsed.inMilliseconds,
|
||||
@@ -805,6 +816,40 @@ class WolfEngine {
|
||||
}
|
||||
}
|
||||
|
||||
void _applyPatrolPathMarker(Enemy enemy) {
|
||||
final tileX = enemy.x.toInt();
|
||||
final tileY = enemy.y.toInt();
|
||||
if (tileX < 0 || tileX >= 64 || tileY < 0 || tileY >= 64) return;
|
||||
|
||||
final previousTile = _lastPatrolTileByEnemy[enemy.debugId];
|
||||
if (previousTile != null &&
|
||||
previousTile.x == tileX &&
|
||||
previousTile.y == tileY) {
|
||||
return;
|
||||
}
|
||||
_lastPatrolTileByEnemy[enemy.debugId] = (x: tileX, y: tileY);
|
||||
|
||||
final markerId = _objectLevel[tileY][tileX];
|
||||
final markerAngle = MapObject.patrolAngleForMarker(markerId);
|
||||
if (enemy is Dog) {
|
||||
log(
|
||||
'[DEBUG] Dog #${enemy.debugId} patrol tile entry '
|
||||
'($tileX, $tileY) marker=$markerId',
|
||||
);
|
||||
}
|
||||
if (markerAngle == null) return;
|
||||
|
||||
final normalizedDiff =
|
||||
((enemy.angle - markerAngle + math.pi) % (2 * math.pi)) - math.pi;
|
||||
if (normalizedDiff.abs() > 0.001) {
|
||||
enemy.angle = markerAngle;
|
||||
log(
|
||||
'[DEBUG] Enemy #${enemy.debugId} (${enemy.type.name}) '
|
||||
'patrol marker $markerId applied at ($tileX, $tileY)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if a tile is empty or contains a door that is sufficiently open.
|
||||
bool isWalkable(int x, int y) {
|
||||
// 1. Boundary Guard: Prevent range errors by checking if coordinates are on the map
|
||||
|
||||
Reference in New Issue
Block a user