Fix some enemy stuff

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-14 01:18:52 +01:00
parent 1bd0814e8c
commit 6bf479dcf7
5 changed files with 106 additions and 98 deletions

View File

@@ -53,13 +53,15 @@ class BrownGuard extends Enemy {
} }
@override @override
@override ({Coordinate2D movement, double newAngle}) update({
void update({
required int elapsedMs, required int elapsedMs,
required Coordinate2D playerPosition, required Coordinate2D playerPosition,
required bool Function(int x, int y) isWalkable, required bool Function(int x, int y) isWalkable,
required void Function(int damage) onDamagePlayer, required void Function(int damage) onDamagePlayer,
}) { }) {
Coordinate2D movement = const Coordinate2D(0, 0);
double newAngle = angle;
// 1. Wake up logic // 1. Wake up logic
if (state == EntityState.idle && if (state == EntityState.idle &&
hasLineOfSight(playerPosition, isWalkable)) { hasLineOfSight(playerPosition, isWalkable)) {
@@ -67,25 +69,22 @@ class BrownGuard extends Enemy {
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
} }
// 2. Pre-calculate angles (needed for almost all states) // 2. Pre-calculate spatial relations
double dx = playerPosition.x - x; double distance = position.distanceTo(playerPosition);
double dy = playerPosition.y - y; double angleToPlayer = position.angleTo(playerPosition);
double distance = math.sqrt(dx * dx + dy * dy);
double angleToPlayer = math.atan2(dy, dx);
// Face the player if active
if (state != EntityState.idle && state != EntityState.dead) { 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) { while (diff <= -math.pi) {
diff += 2 * math.pi; diff += 2 * math.pi;
} }
while (diff > math.pi) { while (diff > math.pi) {
diff -= 2 * math.pi; diff -= 2 * math.pi;
} }
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8; int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
if (octant < 0) octant += 8; if (octant < 0) octant += 8;
@@ -96,17 +95,15 @@ class BrownGuard extends Enemy {
break; break;
case EntityState.patrolling: case EntityState.patrolling:
// Movement
if (distance > 0.8) { if (distance > 0.8) {
double moveX = x + math.cos(angle) * speed; // Calculate movement intent
double moveY = y + math.sin(angle) * speed; movement =
if (isWalkable(moveX.toInt(), y.toInt())) x = moveX; Coordinate2D(math.cos(newAngle), math.sin(newAngle)) * speed;
if (isWalkable(x.toInt(), moveY.toInt())) y = moveY;
} }
// Animation
int walkFrame = (elapsedMs ~/ 150) % 4; int walkFrame = (elapsedMs ~/ 150) % 4;
spriteIndex = 58 + (walkFrame * 8) + octant; spriteIndex = 58 + (walkFrame * 8) + octant;
// Shooting transition
if (distance < 5.0 && elapsedMs - lastActionTime > 2000) { if (distance < 5.0 && elapsedMs - lastActionTime > 2000) {
if (hasLineOfSight(playerPosition, isWalkable)) { if (hasLineOfSight(playerPosition, isWalkable)) {
state = EntityState.shooting; state = EntityState.shooting;
@@ -123,7 +120,7 @@ class BrownGuard extends Enemy {
} else if (timeShooting < 300) { } else if (timeShooting < 300) {
spriteIndex = 97; spriteIndex = 97;
if (!_hasFiredThisCycle) { if (!_hasFiredThisCycle) {
onDamagePlayer(10); onDamagePlayer(10); // DAMAGING PLAYER
_hasFiredThisCycle = true; _hasFiredThisCycle = true;
} }
} else if (timeShooting < 450) { } else if (timeShooting < 450) {
@@ -135,32 +132,30 @@ class BrownGuard extends Enemy {
break; break;
case EntityState.pain: case EntityState.pain:
spriteIndex = 90; spriteIndex = 94;
if (elapsedMs - lastActionTime > 250) { if (elapsedMs - lastActionTime > 250) {
// Slight delay
state = EntityState.patrolling; state = EntityState.patrolling;
lastActionTime = elapsedMs; // Reset so they don't immediately shoot lastActionTime = elapsedMs;
} }
break; break;
case EntityState.dead: case EntityState.dead:
if (isDying) { if (isDying) {
// Use max(0, ...) to prevent negative numbers if currentTime is wonky int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
int timeSinceDeath = math.max(0, elapsedMs - lastActionTime);
int deathFrame = timeSinceDeath ~/ 150;
if (deathFrame < 4) { if (deathFrame < 4) {
spriteIndex = 91 + deathFrame; spriteIndex = 90 + deathFrame - 1;
} else { } else {
spriteIndex = 95; // The final corpse spriteIndex = 95;
isDying = false; // Stop animating isDying = false;
} }
} else { } else {
spriteIndex = 95; // Keep showing the corpse spriteIndex = 95;
} }
break; break;
default: default:
break; break;
} }
return (movement: movement, newAngle: newAngle);
} }
} }

View File

@@ -47,60 +47,48 @@ class Dog extends Enemy {
} }
@override @override
void update({ ({Coordinate2D movement, double newAngle}) update({
required int elapsedMs, required int elapsedMs,
required Coordinate2D playerPosition, required Coordinate2D playerPosition,
required bool Function(int x, int y) isWalkable, required bool Function(int x, int y) isWalkable,
required void Function(int damage) onDamagePlayer, required void Function(int damage) onDamagePlayer,
}) { }) {
if (state == EntityState.idle) { Coordinate2D movement = const Coordinate2D(0, 0);
if (hasLineOfSight(playerPosition, isWalkable)) { double newAngle = angle;
if (state == EntityState.idle &&
hasLineOfSight(playerPosition, isWalkable)) {
state = EntityState.patrolling; state = EntityState.patrolling;
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
} }
}
if (state == EntityState.idle || double distance = position.distanceTo(playerPosition);
state == EntityState.patrolling || double angleToPlayer = position.angleTo(playerPosition);
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) { if (state == EntityState.patrolling || state == EntityState.shooting) {
angle = angleToPlayer; newAngle = angleToPlayer;
} }
double diff = angle - angleToPlayer; double diff = newAngle - angleToPlayer;
while (diff <= -math.pi) { while (diff <= -math.pi) {
diff += 2 * math.pi; diff += 2 * math.pi;
} }
while (diff > math.pi) { while (diff > math.pi) {
diff -= 2 * math.pi; diff -= 2 * math.pi;
} }
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8; int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
if (octant < 0) octant += 8; if (octant < 0) octant += 8;
if (state == EntityState.idle) { if (state == EntityState.idle) {
spriteIndex = 99 + octant; // Base dog standing sprite spriteIndex = 99 + octant;
} else if (state == EntityState.patrolling) { } else if (state == EntityState.patrolling) {
if (distance > 0.8) { if (distance > 0.8) {
double moveX = x + math.cos(angle) * speed; movement = Coordinate2D(math.cos(newAngle), math.sin(newAngle)) * 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; int walkFrame = (elapsedMs ~/ 100) % 4;
spriteIndex = 107 + (walkFrame * 8) + octant; spriteIndex = 107 + (walkFrame * 8) + octant;
// Dog Bite Attack (Must be practically touching the player)
if (distance < 1.0 && elapsedMs - lastActionTime > 1000) { if (distance < 1.0 && elapsedMs - lastActionTime > 1000) {
state = EntityState.shooting; state = EntityState.shooting;
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
@@ -108,11 +96,10 @@ class Dog extends Enemy {
} }
} else if (state == EntityState.shooting) { } else if (state == EntityState.shooting) {
int timeAttacking = elapsedMs - lastActionTime; int timeAttacking = elapsedMs - lastActionTime;
if (timeAttacking < 200) { if (timeAttacking < 200) {
spriteIndex = 139; // Jumping/Biting frame spriteIndex = 139;
if (!_hasBittenThisCycle) { if (!_hasBittenThisCycle) {
onDamagePlayer(5); // Dogs do less damage than guards onDamagePlayer(5); // DOG BITE
_hasBittenThisCycle = true; _hasBittenThisCycle = true;
} }
} else { } else {
@@ -120,6 +107,7 @@ class Dog extends Enemy {
lastActionTime = elapsedMs; lastActionTime = elapsedMs;
} }
} }
}
return (movement: movement, newAngle: newAngle);
} }
} }

View File

@@ -16,6 +16,7 @@ abstract class Enemy extends Entity {
// Standard guard health // Standard guard health
int health = 25; int health = 25;
int damage = 10;
bool isDying = false; bool isDying = false;
void takeDamage(int amount, int currentTime) { void takeDamage(int amount, int currentTime) {
@@ -106,7 +107,7 @@ abstract class Enemy extends Entity {
return true; return true;
} }
void update({ ({Coordinate2D movement, double newAngle}) update({
required int elapsedMs, required int elapsedMs,
required Coordinate2D playerPosition, required Coordinate2D playerPosition,
required bool Function(int x, int y) isWalkable, required bool Function(int x, int y) isWalkable,

View File

@@ -21,5 +21,10 @@ abstract class Entity<T> {
this.lastActionTime = 0, this.lastActionTime = 0,
}); });
set position(Coordinate2D pos) {
x = pos.x;
y = pos.y;
}
Coordinate2D get position => Coordinate2D(x, y); Coordinate2D get position => Coordinate2D(x, y);
} }

View File

@@ -190,7 +190,7 @@ class _WolfRendererState extends State<WolfRenderer>
if (player.angle < 0) player.angle += 2 * math.pi; if (player.angle < 0) player.angle += 2 * math.pi;
if (player.angle >= 2 * math.pi) 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, player.position,
inputResult.movement, inputResult.movement,
); );
@@ -223,8 +223,8 @@ class _WolfRendererState extends State<WolfRenderer>
({Coordinate2D movement, double dAngle}) _processInputs(Duration elapsed) { ({Coordinate2D movement, double dAngle}) _processInputs(Duration elapsed) {
inputManager.update(); inputManager.update();
const double moveSpeed = 0.16; const double moveSpeed = 0.14;
const double turnSpeed = 0.12; const double turnSpeed = 0.10;
Coordinate2D movement = const Coordinate2D(0, 0); Coordinate2D movement = const Coordinate2D(0, 0);
double dAngle = 0.0; double dAngle = 0.0;
@@ -301,18 +301,37 @@ class _WolfRendererState extends State<WolfRenderer>
return Coordinate2D(newX, newY); return Coordinate2D(newX, newY);
} }
// renderer.dart
void _updateEntities(Duration elapsed) { void _updateEntities(Duration elapsed) {
List<Entity> itemsToRemove = []; List<Entity> itemsToRemove = [];
for (Entity entity in entities) { for (Entity entity in entities) {
if (entity is Enemy) { if (entity is Enemy) {
entity.update( // 1. Get Intent
final intent = entity.update(
elapsedMs: elapsed.inMilliseconds, elapsedMs: elapsed.inMilliseconds,
playerPosition: player.position, playerPosition: player.position,
isWalkable: _isWalkable, isWalkable: _isWalkable,
onDamagePlayer: _takeDamage, 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) { } else if (entity is Collectible) {
// Collectible pickup logic remains the same
if (player.position.distanceTo(entity.position) < 0.5) { if (player.position.distanceTo(entity.position) < 0.5) {
if (player.tryPickup(entity)) { if (player.tryPickup(entity)) {
itemsToRemove.add(entity); itemsToRemove.add(entity);