Fix wrong enemies spawning all over
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -136,7 +136,7 @@ abstract class MapObject {
|
|||||||
|
|
||||||
// Normalize patrolling enemies back to the standing block, THEN get the
|
// Normalize patrolling enemies back to the standing block, THEN get the
|
||||||
// 4-way angle
|
// 4-way angle
|
||||||
int directionIndex = ((id - type.mapBaseId) % 18) % 4;
|
int directionIndex = ((id - type.patrolId) % 18) % 4;
|
||||||
return CardinalDirection.fromEnemyIndex(directionIndex).radians;
|
return CardinalDirection.fromEnemyIndex(directionIndex).radians;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +146,8 @@ abstract class MapObject {
|
|||||||
// If it's not a standard enemy (it's a decoration, boss, or player), spawn it
|
// If it's not a standard enemy (it's a decoration, boss, or player), spawn it
|
||||||
if (type == null) return true;
|
if (type == null) return true;
|
||||||
|
|
||||||
int offset = id - type.mapBaseId;
|
bool isStaticId = id >= (type.staticId - 2) && id <= type.staticId;
|
||||||
|
int offset = isStaticId ? (id - type.staticId) : (id - type.patrolId);
|
||||||
int normalizedOffset = offset >= 18 ? offset - 18 : offset;
|
int normalizedOffset = offset >= 18 ? offset - 18 : offset;
|
||||||
|
|
||||||
return switch (normalizedOffset) {
|
return switch (normalizedOffset) {
|
||||||
|
|||||||
@@ -11,24 +11,55 @@ import 'package:wolf_3d_entities/src/entity.dart';
|
|||||||
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }
|
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }
|
||||||
|
|
||||||
enum EnemyType {
|
enum EnemyType {
|
||||||
guard(mapBaseId: 108, spriteBaseIdx: 50),
|
guard(
|
||||||
dog(mapBaseId: 216, spriteBaseIdx: 99),
|
staticId: 108,
|
||||||
ss(mapBaseId: 180, spriteBaseIdx: 138),
|
patrolId: 124,
|
||||||
mutant(mapBaseId: 252, spriteBaseIdx: 187),
|
spriteBaseIdx: 50,
|
||||||
officer(mapBaseId: 144, spriteBaseIdx: 238);
|
), // Update spriteBaseIdx to your actual value
|
||||||
|
dog(
|
||||||
|
staticId: 144,
|
||||||
|
patrolId: 160,
|
||||||
|
spriteBaseIdx: 99,
|
||||||
|
), // Update spriteBaseIdx to your actual value
|
||||||
|
ss(
|
||||||
|
staticId: 176,
|
||||||
|
patrolId: 192,
|
||||||
|
spriteBaseIdx: 138,
|
||||||
|
), // Retained from your snippet
|
||||||
|
mutant(
|
||||||
|
staticId: 238,
|
||||||
|
patrolId: 254,
|
||||||
|
spriteBaseIdx: 185,
|
||||||
|
), // Update spriteBaseIdx to your actual value
|
||||||
|
officer(
|
||||||
|
staticId: 270,
|
||||||
|
patrolId: 286,
|
||||||
|
spriteBaseIdx: 226,
|
||||||
|
); // Update spriteBaseIdx to your actual value
|
||||||
|
|
||||||
final int mapBaseId;
|
final int staticId;
|
||||||
|
final int patrolId;
|
||||||
final int spriteBaseIdx;
|
final int spriteBaseIdx;
|
||||||
|
|
||||||
const EnemyType({required this.mapBaseId, required this.spriteBaseIdx});
|
const EnemyType({
|
||||||
|
required this.staticId,
|
||||||
|
required this.patrolId,
|
||||||
|
required this.spriteBaseIdx,
|
||||||
|
});
|
||||||
|
|
||||||
/// Helper to check if a specific TED5 Map ID belongs to this enemy
|
/// Wolfenstein 3D allocates blocks of 16 IDs per enemy type for standing and patrolling
|
||||||
bool claimsMapId(int id) => id >= mapBaseId && id <= mapBaseId + 35;
|
/// (4 directions x 4 difficulty levels = 16 IDs)
|
||||||
|
bool claimsMapId(int id) {
|
||||||
|
bool isStatic = id >= staticId && id < staticId + 16;
|
||||||
|
bool isPatrol = id >= patrolId && id < patrolId + 16;
|
||||||
|
return isStatic || isPatrol;
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper to find which EnemyType a given Map ID belongs to
|
|
||||||
static EnemyType? fromMapId(int id) {
|
static EnemyType? fromMapId(int id) {
|
||||||
for (final type in EnemyType.values) {
|
for (final type in EnemyType.values) {
|
||||||
if (type.claimsMapId(id)) return type;
|
if (type.claimsMapId(id)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -92,7 +123,7 @@ enum EnemyType {
|
|||||||
required int elapsedMs,
|
required int elapsedMs,
|
||||||
required int lastActionTime,
|
required int lastActionTime,
|
||||||
double angleDiff = 0,
|
double angleDiff = 0,
|
||||||
int? walkFrameOverride, // Optional for custom timing
|
int? walkFrameOverride,
|
||||||
}) {
|
}) {
|
||||||
// 1. Calculate Octant for directional sprites (Idle/Walk)
|
// 1. Calculate Octant for directional sprites (Idle/Walk)
|
||||||
int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
||||||
@@ -110,28 +141,46 @@ enum EnemyType {
|
|||||||
EnemyAnimation.attacking => () {
|
EnemyAnimation.attacking => () {
|
||||||
int time = elapsedMs - lastActionTime;
|
int time = elapsedMs - lastActionTime;
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
EnemyType.guard || EnemyType.ss || EnemyType.dog =>
|
// Offset 40-42 is the Shooting sequence (Aim, Fire, Recoil)
|
||||||
|
EnemyType.guard || EnemyType.ss =>
|
||||||
spriteBaseIdx +
|
spriteBaseIdx +
|
||||||
(time < 150
|
(time < 150
|
||||||
? 40
|
? 40
|
||||||
: time < 300
|
: time < 300
|
||||||
? 41
|
? 41
|
||||||
: 40),
|
: 42),
|
||||||
EnemyType.officer ||
|
EnemyType.officer ||
|
||||||
EnemyType.mutant => spriteBaseIdx + (time < 200 ? 40 : 41),
|
EnemyType.mutant => spriteBaseIdx + (time < 200 ? 40 : 41),
|
||||||
|
EnemyType.dog =>
|
||||||
|
spriteBaseIdx + (time < 150 ? 32 : 33), // Dog Leap/Bite
|
||||||
};
|
};
|
||||||
}(),
|
}(),
|
||||||
|
|
||||||
EnemyAnimation.pain => spriteBaseIdx + (this == EnemyType.dog ? 32 : 42),
|
EnemyAnimation.pain =>
|
||||||
|
spriteBaseIdx +
|
||||||
|
switch (this) {
|
||||||
|
EnemyType.dog => 32,
|
||||||
|
EnemyType.officer || EnemyType.mutant => 42,
|
||||||
|
_ => 43, // guard, ss
|
||||||
|
},
|
||||||
|
|
||||||
EnemyAnimation.dying => () {
|
EnemyAnimation.dying => () {
|
||||||
int frame = (elapsedMs - lastActionTime) ~/ 150;
|
int frame = (elapsedMs - lastActionTime) ~/ 150;
|
||||||
int maxFrames = this == EnemyType.dog ? 2 : 3;
|
int dyingStart = switch (this) {
|
||||||
int offset = this == EnemyType.dog ? 35 : 43;
|
EnemyType.dog => 35,
|
||||||
return spriteBaseIdx + offset + (frame.clamp(0, maxFrames));
|
EnemyType.officer || EnemyType.mutant => 43,
|
||||||
|
_ => 44, // guard, ss
|
||||||
|
};
|
||||||
|
return spriteBaseIdx + dyingStart + (frame.clamp(0, 2));
|
||||||
}(),
|
}(),
|
||||||
|
|
||||||
EnemyAnimation.dead => spriteBaseIdx + (this == EnemyType.dog ? 37 : 45),
|
EnemyAnimation.dead =>
|
||||||
|
spriteBaseIdx +
|
||||||
|
switch (this) {
|
||||||
|
EnemyType.dog => 38,
|
||||||
|
EnemyType.officer || EnemyType.mutant => 46,
|
||||||
|
_ => 48, // guard, ss
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +347,6 @@ abstract class Enemy extends Entity {
|
|||||||
return intendedMovement;
|
return intendedMovement;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updated Signature
|
|
||||||
({Coordinate2D movement, double newAngle}) update({
|
({Coordinate2D movement, double newAngle}) update({
|
||||||
required int elapsedMs,
|
required int elapsedMs,
|
||||||
required Coordinate2D playerPosition,
|
required Coordinate2D playerPosition,
|
||||||
@@ -328,7 +376,7 @@ abstract class Enemy extends Entity {
|
|||||||
final type = EnemyType.fromMapId(objId);
|
final type = EnemyType.fromMapId(objId);
|
||||||
if (type == null) return null;
|
if (type == null) return null;
|
||||||
|
|
||||||
bool isPatrolling = objId >= type.mapBaseId + 18;
|
bool isPatrolling = objId >= type.patrolId;
|
||||||
double spawnAngle = MapObject.getAngle(objId);
|
double spawnAngle = MapObject.getAngle(objId);
|
||||||
|
|
||||||
// 2. Return the specific instance
|
// 2. Return the specific instance
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Guard extends Enemy {
|
|||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get sprite based on current state
|
// Use the centralized animation logic to avoid manual offset errors
|
||||||
EnemyAnimation currentAnim = switch (state) {
|
EnemyAnimation currentAnim = switch (state) {
|
||||||
EntityState.patrolling => EnemyAnimation.walking,
|
EntityState.patrolling => EnemyAnimation.walking,
|
||||||
EntityState.attacking => EnemyAnimation.attacking,
|
EntityState.attacking => EnemyAnimation.attacking,
|
||||||
@@ -66,18 +66,43 @@ class Guard extends Enemy {
|
|||||||
angleDiff: diff,
|
angleDiff: diff,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Logic triggers (Damage, State transitions)
|
if (state == EntityState.patrolling) {
|
||||||
|
if (distance > 0.8) {
|
||||||
|
double moveX = math.cos(angleToPlayer) * speed;
|
||||||
|
double moveY = math.sin(angleToPlayer) * speed;
|
||||||
|
movement = getValidMovement(
|
||||||
|
Coordinate2D(moveX, moveY),
|
||||||
|
isWalkable,
|
||||||
|
tryOpenDoor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||||
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
|
state = EntityState.attacking;
|
||||||
|
lastActionTime = elapsedMs;
|
||||||
|
_hasFiredThisCycle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state == EntityState.attacking) {
|
if (state == EntityState.attacking) {
|
||||||
int time = elapsedMs - lastActionTime;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (time >= 150 && time < 300 && !_hasFiredThisCycle) {
|
// SS-Specific firing logic
|
||||||
onDamagePlayer(10);
|
if (timeShooting >= 100 && timeShooting < 200 && !_hasFiredThisCycle) {
|
||||||
|
onDamagePlayer(damage);
|
||||||
_hasFiredThisCycle = true;
|
_hasFiredThisCycle = true;
|
||||||
} else if (time >= 450) {
|
} else if (timeShooting >= 300) {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state == EntityState.pain && elapsedMs - lastActionTime > 250) {
|
||||||
|
state = EntityState.patrolling;
|
||||||
|
lastActionTime = elapsedMs;
|
||||||
|
}
|
||||||
|
|
||||||
return (movement: movement, newAngle: newAngle);
|
return (movement: movement, newAngle: newAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class Mutant extends Enemy {
|
|||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the centralized animation logic to avoid manual offset errors
|
||||||
EnemyAnimation currentAnim = switch (state) {
|
EnemyAnimation currentAnim = switch (state) {
|
||||||
EntityState.patrolling => EnemyAnimation.walking,
|
EntityState.patrolling => EnemyAnimation.walking,
|
||||||
EntityState.attacking => EnemyAnimation.attacking,
|
EntityState.attacking => EnemyAnimation.attacking,
|
||||||
@@ -68,17 +69,43 @@ class Mutant extends Enemy {
|
|||||||
angleDiff: diff,
|
angleDiff: diff,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (state == EntityState.patrolling) {
|
||||||
|
if (distance > 0.8) {
|
||||||
|
double moveX = math.cos(angleToPlayer) * speed;
|
||||||
|
double moveY = math.sin(angleToPlayer) * speed;
|
||||||
|
movement = getValidMovement(
|
||||||
|
Coordinate2D(moveX, moveY),
|
||||||
|
isWalkable,
|
||||||
|
tryOpenDoor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||||
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
|
state = EntityState.attacking;
|
||||||
|
lastActionTime = elapsedMs;
|
||||||
|
_hasFiredThisCycle = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state == EntityState.attacking) {
|
if (state == EntityState.attacking) {
|
||||||
int time = elapsedMs - lastActionTime;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (time >= 150 && !_hasFiredThisCycle) {
|
// SS-Specific firing logic
|
||||||
|
if (timeShooting >= 100 && timeShooting < 200 && !_hasFiredThisCycle) {
|
||||||
onDamagePlayer(damage);
|
onDamagePlayer(damage);
|
||||||
_hasFiredThisCycle = true;
|
_hasFiredThisCycle = true;
|
||||||
} else if (time >= 300) {
|
} else if (timeShooting >= 300) {
|
||||||
state = EntityState.patrolling;
|
state = EntityState.patrolling;
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state == EntityState.pain && elapsedMs - lastActionTime > 250) {
|
||||||
|
state = EntityState.patrolling;
|
||||||
|
lastActionTime = elapsedMs;
|
||||||
|
}
|
||||||
|
|
||||||
return (movement: movement, newAngle: newAngle);
|
return (movement: movement, newAngle: newAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,80 +53,55 @@ class Officer extends Enemy {
|
|||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
// Use centralized animation logic
|
||||||
if (octant < 0) octant += 8;
|
EnemyAnimation currentAnim = switch (state) {
|
||||||
|
EntityState.patrolling => EnemyAnimation.walking,
|
||||||
|
EntityState.attacking => EnemyAnimation.attacking,
|
||||||
|
EntityState.pain => EnemyAnimation.pain,
|
||||||
|
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||||
|
_ => EnemyAnimation.idle,
|
||||||
|
};
|
||||||
|
|
||||||
switch (state) {
|
spriteIndex = EnemyType.officer.getSpriteFromAnimation(
|
||||||
case EntityState.idle:
|
animation: currentAnim,
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + octant;
|
elapsedMs: elapsedMs,
|
||||||
break;
|
lastActionTime: lastActionTime,
|
||||||
|
angleDiff: diff,
|
||||||
|
);
|
||||||
|
|
||||||
case EntityState.patrolling:
|
if (state == EntityState.patrolling) {
|
||||||
if (distance > 0.8) {
|
if (distance > 0.8) {
|
||||||
double moveX = math.cos(angleToPlayer) * speed;
|
double moveX = math.cos(angleToPlayer) * speed;
|
||||||
double moveY = math.sin(angleToPlayer) * speed;
|
double moveY = math.sin(angleToPlayer) * speed;
|
||||||
movement = getValidMovement(
|
movement = getValidMovement(
|
||||||
Coordinate2D(moveX, moveY),
|
Coordinate2D(moveX, moveY),
|
||||||
isWalkable,
|
isWalkable,
|
||||||
tryOpenDoor,
|
tryOpenDoor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
||||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
spriteIndex =
|
state = EntityState.attacking;
|
||||||
(EnemyType.officer.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
|
||||||
|
|
||||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
|
|
||||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
|
||||||
state = EntityState.attacking;
|
|
||||||
lastActionTime = elapsedMs;
|
|
||||||
_hasFiredThisCycle = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EntityState.attacking:
|
|
||||||
int timeShooting = elapsedMs - lastActionTime;
|
|
||||||
if (timeShooting < 150) {
|
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + 40; // Aiming
|
|
||||||
} else if (timeShooting < 300) {
|
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + 41; // Firing
|
|
||||||
if (!_hasFiredThisCycle) {
|
|
||||||
onDamagePlayer(damage);
|
|
||||||
_hasFiredThisCycle = true;
|
|
||||||
}
|
|
||||||
} else if (timeShooting < 450) {
|
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + 40; // Recoil
|
|
||||||
} else {
|
|
||||||
state = EntityState.patrolling;
|
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
|
_hasFiredThisCycle = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case EntityState.pain:
|
if (state == EntityState.attacking) {
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + 42;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (elapsedMs - lastActionTime > 250) {
|
if (timeShooting >= 150 && timeShooting < 300 && !_hasFiredThisCycle) {
|
||||||
state = EntityState.patrolling;
|
onDamagePlayer(damage);
|
||||||
lastActionTime = elapsedMs;
|
_hasFiredThisCycle = true;
|
||||||
}
|
} else if (timeShooting >= 450) {
|
||||||
break;
|
state = EntityState.patrolling;
|
||||||
|
lastActionTime = elapsedMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case EntityState.dead:
|
if (state == EntityState.pain && elapsedMs - lastActionTime > 250) {
|
||||||
if (isDying) {
|
state = EntityState.patrolling;
|
||||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
lastActionTime = elapsedMs;
|
||||||
if (deathFrame < 3) {
|
|
||||||
spriteIndex = (EnemyType.officer.spriteBaseIdx + 43) + deathFrame;
|
|
||||||
} else {
|
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + 45;
|
|
||||||
isDying = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
spriteIndex = EnemyType.officer.spriteBaseIdx + 45;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (movement: movement, newAngle: newAngle);
|
return (movement: movement, newAngle: newAngle);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class SS extends Enemy {
|
|||||||
static const double speed = 0.04;
|
static const double speed = 0.04;
|
||||||
bool _hasFiredThisCycle = false;
|
bool _hasFiredThisCycle = false;
|
||||||
|
|
||||||
|
static EnemyType get type => EnemyType.ss;
|
||||||
|
|
||||||
SS({
|
SS({
|
||||||
required super.x,
|
required super.x,
|
||||||
required super.y,
|
required super.y,
|
||||||
@@ -42,6 +44,7 @@ class SS extends Enemy {
|
|||||||
newAngle = angleToPlayer;
|
newAngle = angleToPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate angle diff for the octant logic
|
||||||
double diff = angleToPlayer - newAngle;
|
double diff = angleToPlayer - newAngle;
|
||||||
while (diff <= -math.pi) {
|
while (diff <= -math.pi) {
|
||||||
diff += 2 * math.pi;
|
diff += 2 * math.pi;
|
||||||
@@ -50,81 +53,57 @@ class SS extends Enemy {
|
|||||||
diff -= 2 * math.pi;
|
diff -= 2 * math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
int octant = ((diff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
|
// Use the centralized animation logic to avoid manual offset errors
|
||||||
if (octant < 0) octant += 8;
|
EnemyAnimation currentAnim = switch (state) {
|
||||||
|
EntityState.patrolling => EnemyAnimation.walking,
|
||||||
|
EntityState.attacking => EnemyAnimation.attacking,
|
||||||
|
EntityState.pain => EnemyAnimation.pain,
|
||||||
|
EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
|
||||||
|
_ => EnemyAnimation.idle,
|
||||||
|
};
|
||||||
|
|
||||||
switch (state) {
|
spriteIndex = type.getSpriteFromAnimation(
|
||||||
case EntityState.idle:
|
animation: currentAnim,
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + octant;
|
elapsedMs: elapsedMs,
|
||||||
break;
|
lastActionTime: lastActionTime,
|
||||||
|
angleDiff: diff,
|
||||||
|
);
|
||||||
|
|
||||||
case EntityState.patrolling:
|
if (state == EntityState.patrolling) {
|
||||||
if (distance > 0.8) {
|
if (distance > 0.8) {
|
||||||
double moveX = math.cos(angleToPlayer) * speed;
|
double moveX = math.cos(angleToPlayer) * speed;
|
||||||
double moveY = math.sin(angleToPlayer) * speed;
|
double moveY = math.sin(angleToPlayer) * speed;
|
||||||
movement = getValidMovement(
|
movement = getValidMovement(
|
||||||
Coordinate2D(moveX, moveY),
|
Coordinate2D(moveX, moveY),
|
||||||
isWalkable,
|
isWalkable,
|
||||||
tryOpenDoor,
|
tryOpenDoor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int walkFrame = (elapsedMs ~/ 150) % 4;
|
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||||
spriteIndex =
|
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||||
(EnemyType.ss.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
|
state = EntityState.attacking;
|
||||||
|
|
||||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
|
||||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
|
||||||
state = EntityState.attacking;
|
|
||||||
lastActionTime = elapsedMs;
|
|
||||||
_hasFiredThisCycle = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EntityState.attacking:
|
|
||||||
// SS machine gun fires much faster than a standard pistol!
|
|
||||||
int timeShooting = elapsedMs - lastActionTime;
|
|
||||||
if (timeShooting < 100) {
|
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + 46; // Aiming
|
|
||||||
} else if (timeShooting < 200) {
|
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + 47; // Firing
|
|
||||||
if (!_hasFiredThisCycle) {
|
|
||||||
onDamagePlayer(damage);
|
|
||||||
_hasFiredThisCycle = true;
|
|
||||||
}
|
|
||||||
} else if (timeShooting < 300) {
|
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + 48; // Recoil
|
|
||||||
} else {
|
|
||||||
state = EntityState.patrolling;
|
|
||||||
lastActionTime = elapsedMs;
|
lastActionTime = elapsedMs;
|
||||||
|
_hasFiredThisCycle = false;
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case EntityState.pain:
|
if (state == EntityState.attacking) {
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + 44;
|
int timeShooting = elapsedMs - lastActionTime;
|
||||||
if (elapsedMs - lastActionTime > 250) {
|
// SS-Specific firing logic
|
||||||
state = EntityState.patrolling;
|
if (timeShooting >= 100 && timeShooting < 200 && !_hasFiredThisCycle) {
|
||||||
lastActionTime = elapsedMs;
|
onDamagePlayer(damage);
|
||||||
}
|
_hasFiredThisCycle = true;
|
||||||
break;
|
} else if (timeShooting >= 300) {
|
||||||
|
state = EntityState.patrolling;
|
||||||
|
lastActionTime = elapsedMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case EntityState.dead:
|
if (state == EntityState.pain && elapsedMs - lastActionTime > 250) {
|
||||||
if (isDying) {
|
state = EntityState.patrolling;
|
||||||
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
|
lastActionTime = elapsedMs;
|
||||||
if (deathFrame < 4) {
|
|
||||||
spriteIndex = (EnemyType.ss.spriteBaseIdx + 40) + deathFrame;
|
|
||||||
} else {
|
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + 45;
|
|
||||||
isDying = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
spriteIndex = EnemyType.ss.spriteBaseIdx + 45;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (movement: movement, newAngle: newAngle);
|
return (movement: movement, newAngle: newAngle);
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ typedef EntitySpawner =
|
|||||||
|
|
||||||
abstract class EntityRegistry {
|
abstract class EntityRegistry {
|
||||||
static final List<EntitySpawner> _spawners = [
|
static final List<EntitySpawner> _spawners = [
|
||||||
// Enemies need to try to spawn first
|
|
||||||
Enemy.spawn,
|
|
||||||
|
|
||||||
// Bosses
|
// Bosses
|
||||||
HansGrosse.trySpawn,
|
HansGrosse.trySpawn,
|
||||||
|
|
||||||
|
// Enemies need to try to spawn first
|
||||||
|
Enemy.spawn,
|
||||||
|
|
||||||
// Everything else
|
// Everything else
|
||||||
Collectible.trySpawn,
|
Collectible.trySpawn,
|
||||||
Decorative.trySpawn,
|
Decorative.trySpawn,
|
||||||
@@ -44,7 +44,13 @@ abstract class EntityRegistry {
|
|||||||
if (objId == 0) return null;
|
if (objId == 0) return null;
|
||||||
|
|
||||||
for (final spawner in _spawners) {
|
for (final spawner in _spawners) {
|
||||||
Entity? entity = spawner(objId, x, y, difficulty);
|
Entity? entity = spawner(
|
||||||
|
objId,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
difficulty,
|
||||||
|
isSharewareMode: isSharewareMode,
|
||||||
|
);
|
||||||
|
|
||||||
final EnemyType? type = EnemyType.fromMapId(objId);
|
final EnemyType? type = EnemyType.fromMapId(objId);
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user