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 c464693..3423af8 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 @@ -416,7 +416,7 @@ class WolfEngine { // Wake them up! if (entity.state == EntityState.idle || - entity.state == EntityState.ambush) { + entity.state == EntityState.chasing) { entity.state = EntityState.patrolling; entity.lastActionTime = _timeAliveMs; } diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart index 7685704..eb2f85f 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/bosses/hans_grosse.dart @@ -88,7 +88,7 @@ class HansGrosse extends Enemy { switch (state) { case EntityState.idle: - case EntityState.ambush: + case EntityState.chasing: spriteIndex = _baseSprite; break; diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart index 0fc4b69..1dbcbb8 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/dog.dart @@ -11,6 +11,10 @@ class Dog extends Enemy { /// 1024 / 65536 = ~0.0156 tiles per tic. static const double speedPerTic = 0.0156; + // Used to simulate SelectDodgeDir's zigzag behavior + double _dodgeAngleOffset = 0.0; + int _dodgeTicTimer = 0; + @override EnemyType get type => EnemyType.dog; @@ -20,10 +24,10 @@ class Dog extends Enemy { required super.angle, required super.mapId, }) : super( - spriteIndex: EnemyType.dog.animations.idle.start, - state: EntityState.idle, + spriteIndex: EnemyType.dog.animations.walking.start, + state: EntityState.patrolling, ) { - health = 1; // Dogs always have 1 HP in the original engine + health = 1; } @override @@ -37,59 +41,76 @@ class Dog extends Enemy { }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; - double distance = position.distanceTo(playerPosition); // 1. Perception - checkWakeUp( - elapsedMs: elapsedMs, - playerPosition: playerPosition, - isWalkable: isWalkable, - ); + if (state != EntityState.dead && !isDying) { + checkWakeUp( + elapsedMs: elapsedMs, + playerPosition: playerPosition, + isWalkable: isWalkable, + ); + } - // 2. Discrete AI Decision Rhythm bool ticReady = processTics(elapsedDeltaMs, moveSpeed: 0); + // 2. State-Based Logic Gate if (state == EntityState.attacking) { - // --- AUTHENTIC T_Bite / Jump Sequence --- - // Original Jump sequence (s_dogjump1 to s_dogjump5) is 5 frames, 10 tics each if (ticReady) { currentFrame++; - + // Phase 2: The actual bite if (currentFrame == 1) { - // Phase 2: The actual bite (s_dogjump2) - // Original hit chance is US_RndT() < 180 (~70% chance) - if (distance <= 1.2 && math.Random().nextDouble() < (180 / 256)) { - // Original damage: US_RndT() >> 4 (0 to 15 damage) - int actualDamage = math.Random().nextInt(16); - onDamagePlayer(actualDamage); + final bool attackSuccessful = + math.Random().nextDouble() < (180 / 256); + + double dx = (position.x - playerPosition.x).abs() - speedPerTic; + double dy = (position.y - playerPosition.y).abs() - speedPerTic; + bool inBiteRange = dx <= 1.0 && dy <= 1.0; + + if (inBiteRange && attackSuccessful) { + onDamagePlayer(math.Random().nextInt(16)); } - setTics(10); - } else if (currentFrame < 5) { - setTics(10); // Phases 3-5: Mid-air and Landing - } else { - // Sequence complete, return to chase - state = EntityState.patrolling; - currentFrame = 0; - setTics(10); } + + if (currentFrame >= 5) { + state = EntityState.chasing; + currentFrame = 0; + } + setTics(10); } - } else if (state != EntityState.dead) { - // 3. Continuous Movement (T_DogChase / T_Path) + _updateAnimation(elapsedMs, newAngle, playerPosition); + return (movement: const Coordinate2D(0, 0), newAngle: newAngle); + } else if (state != EntityState.dead && !isDying) { + // 3. Chase / Movement Logic double ticsThisFrame = elapsedDeltaMs / 14.28; double currentMoveSpeed = speedPerTic * ticsThisFrame; - if (isAlerted) { - newAngle = position.angleTo(playerPosition); + if (isAlerted || state == EntityState.chasing) { + state = EntityState.chasing; - // Trigger Jump: Original uses MINACTORDIST (approx 0.8 tiles) - if (distance <= 0.8) { + double dx = (position.x - playerPosition.x).abs() - currentMoveSpeed; + double dy = (position.y - playerPosition.y).abs() - currentMoveSpeed; + + if (dx <= 1.0 && dy <= 1.0) { state = EntityState.attacking; currentFrame = 0; lastActionTime = elapsedMs; setTics(10); - return (movement: const Coordinate2D(0, 0), newAngle: newAngle); + return ( + movement: const Coordinate2D(0, 0), + newAngle: position.angleTo(playerPosition), + ); } + if (_dodgeTicTimer <= 0) { + _dodgeAngleOffset = + (math.Random().nextBool() ? 1 : -1) * (math.pi / 4); + _dodgeTicTimer = 15; + } else if (ticReady) { + _dodgeTicTimer--; + } + + newAngle = position.angleTo(playerPosition) + _dodgeAngleOffset; + movement = getValidMovement( intendedMovement: Coordinate2D( math.cos(newAngle) * currentMoveSpeed, @@ -97,8 +118,13 @@ class Dog extends Enemy { ), playerPosition: playerPosition, isWalkable: isWalkable, - tryOpenDoor: tryOpenDoor, + // FIX: Alerted dogs cannot open doors! Pass an empty closure to treat doors as walls. + tryOpenDoor: (x, y) {}, ); + + if (movement.x == 0 && movement.y == 0) { + _dodgeTicTimer = 0; + } } else if (state == EntityState.patrolling) { movement = getValidMovement( intendedMovement: Coordinate2D( @@ -107,13 +133,14 @@ class Dog extends Enemy { ), playerPosition: playerPosition, isWalkable: isWalkable, + // Patrolling dogs using T_Path CAN open doors in the original logic. tryOpenDoor: tryOpenDoor, ); } if (ticReady) { currentFrame = (currentFrame + 1) % 4; - setTics(10); // Chase rhythm (s_dogchase1-4) + setTics(10); } } @@ -126,7 +153,7 @@ class Dog extends Enemy { double newAngle, Coordinate2D playerPosition, ) { - double diff = position.angleTo(playerPosition) - newAngle; + double diff = newAngle - position.angleTo(playerPosition); while (diff <= -math.pi) { diff += 2 * math.pi; } @@ -135,8 +162,9 @@ class Dog extends Enemy { } EnemyAnimation currentAnim = switch (state) { - EntityState.patrolling => EnemyAnimation.walking, + EntityState.patrolling || EntityState.chasing => EnemyAnimation.walking, EntityState.attacking => EnemyAnimation.attacking, + EntityState.pain => EnemyAnimation.pain, EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead, _ => EnemyAnimation.idle, }; @@ -146,7 +174,10 @@ class Dog extends Enemy { elapsedMs: elapsedMs, lastActionTime: lastActionTime, angleDiff: diff, - walkFrameOverride: (state == EntityState.patrolling || isAlerted) + walkFrameOverride: + (state == EntityState.patrolling || + state == EntityState.chasing || + isAlerted) ? currentFrame : null, ); diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart index 638719a..be8cf24 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy.dart @@ -165,7 +165,7 @@ abstract class Enemy extends Entity { // Reaction delay has passed isAlerted = true; - if (state == EntityState.idle || state == EntityState.ambush) { + if (state == EntityState.idle || state == EntityState.chasing) { state = EntityState.patrolling; setTics(10); } @@ -356,7 +356,7 @@ abstract class Enemy extends Entity { } else if (mapData.isStaticForDifficulty(objId, difficulty)) { spawnState = EntityState.idle; } else if (mapData.isAmbushForDifficulty(objId, difficulty)) { - spawnState = EntityState.ambush; + spawnState = EntityState.chasing; } else { // The ID belongs to this enemy type, but not for this specific difficulty level return null; diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart index 01ef698..9c94179 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/enemy_type.dart @@ -25,11 +25,17 @@ enum EnemyType { dog( mapData: EnemyMapData(MapObject.dogStart), animations: EnemyAnimationMap( + // Dogs don't have true idle sprites, so map idle to the first walk frame safely idle: SpriteFrameRange(99, 106), - walking: SpriteFrameRange(107, 130), + // Dogs have 4 walk frames * 8 octants = 32 sprites + walking: SpriteFrameRange(99, 130), + // Jump / Attack is 3 global sprites attacking: SpriteFrameRange(135, 137), + // Dogs don't have pain frames in Wolf3D pain: SpriteFrameRange(0, 0), + // Die is 3 global sprites dying: SpriteFrameRange(131, 133), + // Dead is 1 global sprite dead: SpriteFrameRange(134, 134), ), ), diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart index 98c1db2..32551df 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/guard.dart @@ -132,7 +132,7 @@ class Guard extends Enemy { } EnemyAnimation currentAnim = switch (state) { - EntityState.patrolling => EnemyAnimation.walking, + EntityState.patrolling || EntityState.chasing => EnemyAnimation.walking, EntityState.attacking => EnemyAnimation.attacking, EntityState.pain => EnemyAnimation.pain, EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead, diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart index 3ee76d2..616b957 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/mutant.dart @@ -57,7 +57,7 @@ class Mutant extends Enemy { } EnemyAnimation currentAnim = switch (state) { - EntityState.patrolling => EnemyAnimation.walking, + EntityState.patrolling || EntityState.chasing => EnemyAnimation.walking, EntityState.attacking => EnemyAnimation.attacking, EntityState.pain => EnemyAnimation.pain, EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead, diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart index 0e05698..42f65b9 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/officer.dart @@ -57,7 +57,7 @@ class Officer extends Enemy { } EnemyAnimation currentAnim = switch (state) { - EntityState.patrolling => EnemyAnimation.walking, + EntityState.patrolling || EntityState.chasing => EnemyAnimation.walking, EntityState.attacking => EnemyAnimation.attacking, EntityState.pain => EnemyAnimation.pain, EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead, diff --git a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart index 80dc060..e36a836 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entities/enemies/ss.dart @@ -56,7 +56,7 @@ class SS extends Enemy { } EnemyAnimation currentAnim = switch (state) { - EntityState.patrolling => EnemyAnimation.walking, + EntityState.patrolling || EntityState.chasing => EnemyAnimation.walking, EntityState.attacking => EnemyAnimation.attacking, EntityState.pain => EnemyAnimation.pain, EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead, diff --git a/packages/wolf_3d_dart/lib/src/entities/entity.dart b/packages/wolf_3d_dart/lib/src/entities/entity.dart index 7f5bf87..4952407 100644 --- a/packages/wolf_3d_dart/lib/src/entities/entity.dart +++ b/packages/wolf_3d_dart/lib/src/entities/entity.dart @@ -6,7 +6,7 @@ enum EntityState { staticObj, /// Enemies waiting for the player to enter their field of view. - ambush, + chasing, /// Enemies standing still but capable of hearing noise. idle,