diff --git a/lib/features/entities/enemies/brown_guard.dart b/lib/features/entities/enemies/brown_guard.dart index 77a4e86..b1afb51 100644 --- a/lib/features/entities/enemies/brown_guard.dart +++ b/lib/features/entities/enemies/brown_guard.dart @@ -53,13 +53,15 @@ class BrownGuard extends Enemy { } @override - @override - void update({ + ({Coordinate2D movement, double newAngle}) update({ required int elapsedMs, required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, required void Function(int damage) onDamagePlayer, }) { + Coordinate2D movement = const Coordinate2D(0, 0); + double newAngle = angle; + // 1. Wake up logic if (state == EntityState.idle && hasLineOfSight(playerPosition, isWalkable)) { @@ -67,25 +69,22 @@ class BrownGuard extends Enemy { lastActionTime = elapsedMs; } - // 2. Pre-calculate angles (needed for almost all states) - double dx = playerPosition.x - x; - double dy = playerPosition.y - y; - double distance = math.sqrt(dx * dx + dy * dy); - double angleToPlayer = math.atan2(dy, dx); + // 2. Pre-calculate spatial relations + double distance = position.distanceTo(playerPosition); + double angleToPlayer = position.angleTo(playerPosition); - // Face the player if active if (state != EntityState.idle && state != EntityState.dead) { - angle = angleToPlayer; + newAngle = angleToPlayer; } - double diff = angle - angleToPlayer; + // Calculate Octant for sprite direction + double diff = newAngle - angleToPlayer; 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; @@ -96,17 +95,15 @@ class BrownGuard extends Enemy { break; case EntityState.patrolling: - // Movement if (distance > 0.8) { - double moveX = x + math.cos(angle) * speed; - double moveY = y + math.sin(angle) * speed; - if (isWalkable(moveX.toInt(), y.toInt())) x = moveX; - if (isWalkable(x.toInt(), moveY.toInt())) y = moveY; + // Calculate movement intent + movement = + Coordinate2D(math.cos(newAngle), math.sin(newAngle)) * speed; } - // Animation + int walkFrame = (elapsedMs ~/ 150) % 4; spriteIndex = 58 + (walkFrame * 8) + octant; - // Shooting transition + if (distance < 5.0 && elapsedMs - lastActionTime > 2000) { if (hasLineOfSight(playerPosition, isWalkable)) { state = EntityState.shooting; @@ -123,7 +120,7 @@ class BrownGuard extends Enemy { } else if (timeShooting < 300) { spriteIndex = 97; if (!_hasFiredThisCycle) { - onDamagePlayer(10); + onDamagePlayer(10); // DAMAGING PLAYER _hasFiredThisCycle = true; } } else if (timeShooting < 450) { @@ -135,32 +132,30 @@ class BrownGuard extends Enemy { break; case EntityState.pain: - spriteIndex = 90; + spriteIndex = 94; if (elapsedMs - lastActionTime > 250) { - // Slight delay state = EntityState.patrolling; - lastActionTime = elapsedMs; // Reset so they don't immediately shoot + lastActionTime = elapsedMs; } break; case EntityState.dead: if (isDying) { - // Use max(0, ...) to prevent negative numbers if currentTime is wonky - int timeSinceDeath = math.max(0, elapsedMs - lastActionTime); - int deathFrame = timeSinceDeath ~/ 150; - + int deathFrame = (elapsedMs - lastActionTime) ~/ 150; if (deathFrame < 4) { - spriteIndex = 91 + deathFrame; + spriteIndex = 90 + deathFrame - 1; } else { - spriteIndex = 95; // The final corpse - isDying = false; // Stop animating + spriteIndex = 95; + isDying = false; } } else { - spriteIndex = 95; // Keep showing the corpse + spriteIndex = 95; } break; default: break; } + + return (movement: movement, newAngle: newAngle); } } diff --git a/lib/features/entities/enemies/dog.dart b/lib/features/entities/enemies/dog.dart index 7aa4b1a..6008442 100644 --- a/lib/features/entities/enemies/dog.dart +++ b/lib/features/entities/enemies/dog.dart @@ -47,79 +47,67 @@ class Dog extends Enemy { } @override - void update({ + ({Coordinate2D movement, double newAngle}) update({ required int elapsedMs, required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, required void Function(int damage) onDamagePlayer, }) { + Coordinate2D movement = const Coordinate2D(0, 0); + double newAngle = angle; + + if (state == EntityState.idle && + hasLineOfSight(playerPosition, isWalkable)) { + state = EntityState.patrolling; + lastActionTime = elapsedMs; + } + + double distance = position.distanceTo(playerPosition); + double angleToPlayer = position.angleTo(playerPosition); + + if (state == EntityState.patrolling || state == EntityState.shooting) { + newAngle = angleToPlayer; + } + + double diff = newAngle - angleToPlayer; + 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; + if (state == EntityState.idle) { - if (hasLineOfSight(playerPosition, isWalkable)) { + spriteIndex = 99 + octant; + } else if (state == EntityState.patrolling) { + if (distance > 0.8) { + movement = Coordinate2D(math.cos(newAngle), math.sin(newAngle)) * speed; + } + + int walkFrame = (elapsedMs ~/ 100) % 4; + spriteIndex = 107 + (walkFrame * 8) + octant; + + if (distance < 1.0 && elapsedMs - lastActionTime > 1000) { + state = EntityState.shooting; + lastActionTime = elapsedMs; + _hasBittenThisCycle = false; + } + } else if (state == EntityState.shooting) { + int timeAttacking = elapsedMs - lastActionTime; + if (timeAttacking < 200) { + spriteIndex = 139; + if (!_hasBittenThisCycle) { + onDamagePlayer(5); // DOG BITE + _hasBittenThisCycle = true; + } + } else { state = EntityState.patrolling; lastActionTime = elapsedMs; } } - if (state == EntityState.idle || - state == EntityState.patrolling || - state == EntityState.shooting) { - // "Shooting" here means biting - - double dx = playerPosition.x - x; - double dy = playerPosition.y - y; - double distance = math.sqrt(dx * dx + dy * dy); - double angleToPlayer = math.atan2(dy, dx); - - if (state == EntityState.patrolling || state == EntityState.shooting) { - angle = angleToPlayer; - } - - double diff = angle - angleToPlayer; - 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; - - if (state == EntityState.idle) { - spriteIndex = 99 + octant; // Base dog standing sprite - } else if (state == EntityState.patrolling) { - if (distance > 0.8) { - double moveX = x + math.cos(angle) * speed; - double moveY = y + math.sin(angle) * speed; - - if (isWalkable(moveX.toInt(), y.toInt())) x = moveX; - if (isWalkable(x.toInt(), moveY.toInt())) y = moveY; - } - - // Dogs only have 4 walking angles, alternating legs - int walkFrame = (elapsedMs ~/ 100) % 4; - spriteIndex = 107 + (walkFrame * 8) + octant; - - // Dog Bite Attack (Must be practically touching the player) - if (distance < 1.0 && elapsedMs - lastActionTime > 1000) { - state = EntityState.shooting; - lastActionTime = elapsedMs; - _hasBittenThisCycle = false; - } - } else if (state == EntityState.shooting) { - int timeAttacking = elapsedMs - lastActionTime; - - if (timeAttacking < 200) { - spriteIndex = 139; // Jumping/Biting frame - if (!_hasBittenThisCycle) { - onDamagePlayer(5); // Dogs do less damage than guards - _hasBittenThisCycle = true; - } - } else { - state = EntityState.patrolling; - lastActionTime = elapsedMs; - } - } - } + return (movement: movement, newAngle: newAngle); } } diff --git a/lib/features/entities/enemies/enemy.dart b/lib/features/entities/enemies/enemy.dart index e3ea510..3c11218 100644 --- a/lib/features/entities/enemies/enemy.dart +++ b/lib/features/entities/enemies/enemy.dart @@ -16,6 +16,7 @@ abstract class Enemy extends Entity { // Standard guard health int health = 25; + int damage = 10; bool isDying = false; void takeDamage(int amount, int currentTime) { @@ -106,7 +107,7 @@ abstract class Enemy extends Entity { return true; } - void update({ + ({Coordinate2D movement, double newAngle}) update({ required int elapsedMs, required Coordinate2D playerPosition, required bool Function(int x, int y) isWalkable, diff --git a/lib/features/entities/entity.dart b/lib/features/entities/entity.dart index c601c38..16a8afd 100644 --- a/lib/features/entities/entity.dart +++ b/lib/features/entities/entity.dart @@ -21,5 +21,10 @@ abstract class Entity { this.lastActionTime = 0, }); + set position(Coordinate2D pos) { + x = pos.x; + y = pos.y; + } + Coordinate2D get position => Coordinate2D(x, y); } diff --git a/lib/features/renderer/renderer.dart b/lib/features/renderer/renderer.dart index d62a42e..f2a2af8 100644 --- a/lib/features/renderer/renderer.dart +++ b/lib/features/renderer/renderer.dart @@ -190,7 +190,7 @@ class _WolfRendererState extends State if (player.angle < 0) player.angle += 2 * math.pi; if (player.angle >= 2 * math.pi) player.angle -= 2 * math.pi; - Coordinate2D validatedPos = _calculateValidatedPosition( + final Coordinate2D validatedPos = _calculateValidatedPosition( player.position, inputResult.movement, ); @@ -223,8 +223,8 @@ class _WolfRendererState extends State ({Coordinate2D movement, double dAngle}) _processInputs(Duration elapsed) { inputManager.update(); - const double moveSpeed = 0.16; - const double turnSpeed = 0.12; + const double moveSpeed = 0.14; + const double turnSpeed = 0.10; Coordinate2D movement = const Coordinate2D(0, 0); double dAngle = 0.0; @@ -301,18 +301,37 @@ class _WolfRendererState extends State return Coordinate2D(newX, newY); } + // renderer.dart void _updateEntities(Duration elapsed) { List itemsToRemove = []; for (Entity entity in entities) { if (entity is Enemy) { - entity.update( + // 1. Get Intent + final intent = entity.update( elapsedMs: elapsed.inMilliseconds, playerPosition: player.position, isWalkable: _isWalkable, onDamagePlayer: _takeDamage, ); + + // 2. Update Angle + entity.angle = intent.newAngle; + + // 3. Resolve Movement & Collision + // We reuse the same logic we used for the player! + Coordinate2D validatedPos = _calculateValidatedPosition( + entity.position, + intent.movement, + ); + + entity.position = validatedPos; + + // 4. Handle Attacking (if the enemy logic decides to) + // You can move 'onDamagePlayer' calls into the enemy's + // internal state check here if preferred. } else if (entity is Collectible) { + // Collectible pickup logic remains the same if (player.position.distanceTo(entity.position) < 0.5) { if (player.tryPickup(entity)) { itemsToRemove.add(entity);