import 'dart:math' as math; import 'package:wolf_dart/classes/coordinate_2d.dart'; import 'package:wolf_dart/features/difficulty/difficulty.dart'; import 'package:wolf_dart/features/entities/enemies/enemy.dart'; import 'package:wolf_dart/features/entities/entity.dart'; import 'package:wolf_dart/features/entities/map_objects.dart'; // NEW class Dog extends Enemy { static const double speed = 0.05; bool _hasBittenThisCycle = false; Dog({ required super.x, required super.y, required super.angle, required super.mapId, }) : super( spriteIndex: 99, state: EntityState.idle, ); static Dog? trySpawn(int objId, double x, double y, Difficulty _) { // Dogs span 216 to 251. if (objId >= MapObject.dogStart && objId <= MapObject.dogStart + 35) { bool isPatrolling = objId >= MapObject.dogStart + 18; return Dog( x: x, y: y, angle: MapObject.getAngle(objId), mapId: objId, )..state = isPatrolling ? EntityState.patrolling : EntityState.idle; } return null; } @override ({Coordinate2D movement, double newAngle}) update({ required int elapsedMs, required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, required void Function(int x, int y) tryOpenDoor, required void Function(int damage) onDamagePlayer, }) { Coordinate2D movement = const Coordinate2D(0, 0); double newAngle = angle; checkWakeUp( elapsedMs: elapsedMs, playerPosition: playerPosition, isWalkable: isWalkable, baseReactionMs: 100, reactionVarianceMs: 200, ); double distance = position.distanceTo(playerPosition); double angleToPlayer = position.angleTo(playerPosition); if (state != EntityState.idle && state != EntityState.dead) { newAngle = angleToPlayer; } double diff = angleToPlayer - newAngle; while (diff <= -math.pi) { diff += 2 * math.pi; } while (diff > math.pi) { diff -= 2 * math.pi; } int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8; if (octant < 0) octant += 8; // 3. Clean State Machine switch (state) { case EntityState.idle: spriteIndex = 99 + octant; break; case EntityState.patrolling: if (distance > 0.8) { // UPGRADED: Smooth vector movement instead of grid-snapping double moveX = math.cos(angleToPlayer) * speed; double moveY = math.sin(angleToPlayer) * speed; Coordinate2D intendedMovement = Coordinate2D(moveX, moveY); movement = getValidMovement( intendedMovement, isWalkable, tryOpenDoor, ); } int walkFrame = (elapsedMs ~/ 100) % 4; spriteIndex = 107 + (walkFrame * 8) + octant; if (distance < 1.0 && elapsedMs - lastActionTime > 1000) { state = EntityState.attacking; lastActionTime = elapsedMs; _hasBittenThisCycle = false; } break; case EntityState.attacking: int timeAttacking = elapsedMs - lastActionTime; if (timeAttacking < 200) { spriteIndex = 139; if (!_hasBittenThisCycle) { onDamagePlayer(5); _hasBittenThisCycle = true; } } else { state = EntityState.patrolling; lastActionTime = elapsedMs; } break; // Make sure dogs have a death state so they don't stay standing! case EntityState.dead: if (isDying) { int deathFrame = (elapsedMs - lastActionTime) ~/ 100; if (deathFrame < 4) { spriteIndex = 140 + deathFrame; // Dog death frames usually start here } else { spriteIndex = 143; // Dead dog on floor isDying = false; } } else { spriteIndex = 143; } break; default: break; } return (movement: movement, newAngle: newAngle); } }