Fixed some enemy movement logic
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -96,12 +96,13 @@ class HansGrosse extends Enemy {
|
||||
if (!isAlerted || distance > 1.5) {
|
||||
double currentMoveAngle = isAlerted ? newAngle : angle;
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(currentMoveAngle) * speed,
|
||||
math.sin(currentMoveAngle) * speed,
|
||||
),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import 'package:wolf_3d_dart/src/entities/entity.dart';
|
||||
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
|
||||
|
||||
class Dog extends Enemy {
|
||||
static const double speed = 0.05;
|
||||
/// Original SPDDOG is 1024. 1 Tile is 65536 units.
|
||||
/// 1024 / 65536 = ~0.0156 tiles per tic.
|
||||
static const double speedPerTic = 0.0156;
|
||||
|
||||
@override
|
||||
EnemyType get type => EnemyType.dog;
|
||||
@@ -21,8 +23,7 @@ class Dog extends Enemy {
|
||||
spriteIndex: EnemyType.dog.animations.idle.start,
|
||||
state: EntityState.idle,
|
||||
) {
|
||||
health = 1;
|
||||
damage = 2;
|
||||
health = 1; // Dogs always have 1 HP in the original engine
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -36,19 +37,96 @@ 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,
|
||||
);
|
||||
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
// 2. Discrete AI Decision Rhythm
|
||||
bool ticReady = processTics(elapsedDeltaMs, moveSpeed: 0);
|
||||
|
||||
if (isAlerted && state != EntityState.dead) newAngle = angleToPlayer;
|
||||
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++;
|
||||
|
||||
double diff = angleToPlayer - newAngle;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else if (state != EntityState.dead) {
|
||||
// 3. Continuous Movement (T_DogChase / T_Path)
|
||||
double ticsThisFrame = elapsedDeltaMs / 14.28;
|
||||
double currentMoveSpeed = speedPerTic * ticsThisFrame;
|
||||
|
||||
if (isAlerted) {
|
||||
newAngle = position.angleTo(playerPosition);
|
||||
|
||||
// Trigger Jump: Original uses MINACTORDIST (approx 0.8 tiles)
|
||||
if (distance <= 0.8) {
|
||||
state = EntityState.attacking;
|
||||
currentFrame = 0;
|
||||
lastActionTime = elapsedMs;
|
||||
setTics(10);
|
||||
return (movement: const Coordinate2D(0, 0), newAngle: newAngle);
|
||||
}
|
||||
|
||||
movement = getValidMovement(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(newAngle) * currentMoveSpeed,
|
||||
math.sin(newAngle) * currentMoveSpeed,
|
||||
),
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
} else if (state == EntityState.patrolling) {
|
||||
movement = getValidMovement(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(angle) * currentMoveSpeed,
|
||||
math.sin(angle) * currentMoveSpeed,
|
||||
),
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
if (ticReady) {
|
||||
currentFrame = (currentFrame + 1) % 4;
|
||||
setTics(10); // Chase rhythm (s_dogchase1-4)
|
||||
}
|
||||
}
|
||||
|
||||
_updateAnimation(elapsedMs, newAngle, playerPosition);
|
||||
return (movement: movement, newAngle: newAngle);
|
||||
}
|
||||
|
||||
void _updateAnimation(
|
||||
int elapsedMs,
|
||||
double newAngle,
|
||||
Coordinate2D playerPosition,
|
||||
) {
|
||||
double diff = position.angleTo(playerPosition) - newAngle;
|
||||
while (diff <= -math.pi) {
|
||||
diff += 2 * math.pi;
|
||||
}
|
||||
@@ -68,51 +146,9 @@ class Dog extends Enemy {
|
||||
elapsedMs: elapsedMs,
|
||||
lastActionTime: lastActionTime,
|
||||
angleDiff: diff,
|
||||
walkFrameOverride: state == EntityState.patrolling ? currentFrame : null,
|
||||
walkFrameOverride: (state == EntityState.patrolling || isAlerted)
|
||||
? currentFrame
|
||||
: null,
|
||||
);
|
||||
|
||||
if (state == EntityState.patrolling) {
|
||||
if (!isAlerted || distance > 1.0) {
|
||||
double currentMoveAngle = isAlerted ? angleToPlayer : angle;
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(
|
||||
math.cos(currentMoveAngle) * speed,
|
||||
math.sin(currentMoveAngle) * speed,
|
||||
),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
if (processTics(elapsedDeltaMs, moveSpeed: speed)) {
|
||||
currentFrame = (currentFrame + 1) % 4;
|
||||
setTics(5);
|
||||
|
||||
if (isAlerted && distance < 1.0) {
|
||||
state = EntityState.attacking;
|
||||
currentFrame = 0;
|
||||
lastActionTime = elapsedMs;
|
||||
setTics(5); // Leap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state == EntityState.attacking) {
|
||||
if (processTics(elapsedDeltaMs, moveSpeed: 0)) {
|
||||
currentFrame++;
|
||||
if (currentFrame == 1) {
|
||||
onDamagePlayer(damage); // Bite
|
||||
setTics(5);
|
||||
} else if (currentFrame == 2) {
|
||||
setTics(5); // Land
|
||||
} else {
|
||||
state = EntityState.patrolling;
|
||||
currentFrame = 0;
|
||||
setTics(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (movement: movement, newAngle: newAngle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,11 +240,23 @@ abstract class Enemy extends Entity {
|
||||
/// The logic performs separate X and Y collision checks to allow "sliding" along
|
||||
/// walls. If a movement is blocked, it calls [tryOpenDoor] to simulate the
|
||||
/// enemy's ability to navigate through the level.
|
||||
Coordinate2D getValidMovement(
|
||||
Coordinate2D intendedMovement,
|
||||
bool Function(int x, int y) isWalkable,
|
||||
void Function(int x, int y) tryOpenDoor,
|
||||
) {
|
||||
Coordinate2D getValidMovement({
|
||||
required Coordinate2D intendedMovement,
|
||||
required Coordinate2D playerPosition,
|
||||
required bool Function(int x, int y) isWalkable,
|
||||
required void Function(int x, int y) tryOpenDoor,
|
||||
}) {
|
||||
final double distToPlayer = position.distanceTo(playerPosition);
|
||||
const double minDistance = 0.9;
|
||||
|
||||
if (distToPlayer < minDistance) {
|
||||
// If already too close, only allow movement if it increases distance
|
||||
Coordinate2D nextPos = position + intendedMovement;
|
||||
if (nextPos.distanceTo(playerPosition) < distToPlayer) {
|
||||
return const Coordinate2D(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
double newX = position.x + intendedMovement.x;
|
||||
double newY = position.y + intendedMovement.y;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ enum EnemyType {
|
||||
idle: SpriteFrameRange(99, 106),
|
||||
walking: SpriteFrameRange(107, 130),
|
||||
attacking: SpriteFrameRange(135, 137),
|
||||
pain: SpriteFrameRange(137, 137),
|
||||
pain: SpriteFrameRange(0, 0),
|
||||
dying: SpriteFrameRange(131, 133),
|
||||
dead: SpriteFrameRange(134, 134),
|
||||
),
|
||||
|
||||
@@ -86,19 +86,25 @@ class Guard extends Enemy {
|
||||
}
|
||||
|
||||
// Pursuit movement
|
||||
movement = _calculateMovement(
|
||||
newAngle,
|
||||
currentMoveSpeed,
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
movement = getValidMovement(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(newAngle) * currentMoveSpeed,
|
||||
math.sin(newAngle) * currentMoveSpeed,
|
||||
),
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
} else if (state == EntityState.patrolling) {
|
||||
// Normal patrol movement
|
||||
movement = _calculateMovement(
|
||||
angle,
|
||||
currentMoveSpeed,
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
movement = getValidMovement(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(angle) * currentMoveSpeed,
|
||||
math.sin(angle) * currentMoveSpeed,
|
||||
),
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,22 +118,6 @@ class Guard extends Enemy {
|
||||
return (movement: movement, newAngle: newAngle);
|
||||
}
|
||||
|
||||
Coordinate2D _calculateMovement(
|
||||
double moveAngle,
|
||||
double moveSpeed,
|
||||
bool Function(int x, int y) isWalkable,
|
||||
void Function(int x, int y) tryOpenDoor,
|
||||
) {
|
||||
return getValidMovement(
|
||||
Coordinate2D(
|
||||
math.cos(moveAngle) * moveSpeed,
|
||||
math.sin(moveAngle) * moveSpeed,
|
||||
),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
void _updateAnimation(
|
||||
int elapsedMs,
|
||||
double newAngle,
|
||||
|
||||
@@ -76,12 +76,13 @@ class Mutant extends Enemy {
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
double currentMoveAngle = isAlerted ? angleToPlayer : angle;
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(currentMoveAngle) * speed,
|
||||
math.sin(currentMoveAngle) * speed,
|
||||
),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,12 +76,13 @@ class Officer extends Enemy {
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
double currentMoveAngle = isAlerted ? angleToPlayer : angle;
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(currentMoveAngle) * speed,
|
||||
math.sin(currentMoveAngle) * speed,
|
||||
),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,13 @@ class SS extends Enemy {
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
double currentMoveAngle = isAlerted ? angleToPlayer : angle;
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(
|
||||
intendedMovement: Coordinate2D(
|
||||
math.cos(currentMoveAngle) * speed,
|
||||
math.sin(currentMoveAngle) * speed,
|
||||
),
|
||||
isWalkable,
|
||||
tryOpenDoor,
|
||||
playerPosition: playerPosition,
|
||||
isWalkable: isWalkable,
|
||||
tryOpenDoor: tryOpenDoor,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user