Working on fixing enemy identification

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-14 21:06:31 +01:00
parent 12e2e7e3a8
commit 1ec891d9a0
18 changed files with 293 additions and 292 deletions

View File

@@ -16,14 +16,13 @@ class Dog extends Enemy {
required super.angle,
required super.mapId,
}) : super(
spriteIndex: 99,
spriteIndex: EnemyType.dog.spriteBaseIdx,
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;
if (EnemyType.dog.claimsMapId(objId)) {
bool isPatrolling = objId >= EnemyType.dog.mapBaseId + 18;
return Dog(
x: x,

View File

@@ -3,6 +3,49 @@ import 'dart:math' as math;
import 'package:wolf_dart/classes/coordinate_2d.dart';
import 'package:wolf_dart/features/entities/entity.dart';
enum EnemyType {
guard(mapBaseId: 108, spriteBaseIdx: 50),
dog(mapBaseId: 216, spriteBaseIdx: 99),
ss(mapBaseId: 180, spriteBaseIdx: 138),
mutant(mapBaseId: 252, spriteBaseIdx: 187),
officer(mapBaseId: 144, spriteBaseIdx: 238),
;
final int mapBaseId;
final int spriteBaseIdx;
const EnemyType({
required this.mapBaseId,
required this.spriteBaseIdx,
});
/// Helper to check if a specific TED5 Map ID belongs to this enemy
bool claimsMapId(int id) => id >= mapBaseId && id <= mapBaseId + 35;
/// Helper to find which EnemyType a given Map ID belongs to
static EnemyType? fromMapId(int id) {
for (final type in EnemyType.values) {
if (type.claimsMapId(id)) return type;
}
return null;
}
bool claimsSpriteIndex(int index) {
return switch (this) {
// Walk, Action, & Death: 50-98
EnemyType.guard => index >= 50 && index <= 98,
// Walk, Action, & Death: 99-137
EnemyType.dog => index >= 99 && index <= 137,
// Walk, Action, & Death: 138-186
EnemyType.ss => index >= 138 && index <= 186,
// Walk, Action, & Death: 187-237
EnemyType.mutant => index >= 187 && index <= 237,
// Walk, Action, & Death: 238-287
EnemyType.officer => index >= 238 && index <= 287,
};
}
}
abstract class Enemy extends Entity {
Enemy({
required super.x,

View File

@@ -16,18 +16,13 @@ class Guard extends Enemy {
required super.angle,
required super.mapId,
}) : super(
spriteIndex: 50,
spriteIndex: EnemyType.guard.spriteBaseIdx,
state: EntityState.idle,
);
static Guard? trySpawn(int objId, double x, double y, Difficulty difficulty) {
// Guards span 108 to 143. (124 and 125 are decorative dead bodies).
if (objId >= MapObject.guardStart &&
objId <= MapObject.guardStart + 35 &&
objId != 124 &&
objId != 125) {
// If the ID is in the second half of the block, it's a Patrolling guard
bool isPatrolling = objId >= MapObject.guardStart + 18;
static Guard? trySpawn(int objId, double x, double y, Difficulty _) {
if (EnemyType.guard.claimsMapId(objId) && objId != 124 && objId != 125) {
bool isPatrolling = objId >= EnemyType.guard.mapBaseId + 18;
return Guard(
x: x,
@@ -110,15 +105,15 @@ class Guard extends Enemy {
case EntityState.attacking:
int timeShooting = elapsedMs - lastActionTime;
if (timeShooting < 150) {
spriteIndex = 96; // Aiming
spriteIndex = 90; // Aiming
} else if (timeShooting < 300) {
spriteIndex = 97; // Firing
spriteIndex = 91; // Firing
if (!_hasFiredThisCycle) {
onDamagePlayer(10);
_hasFiredThisCycle = true;
}
} else if (timeShooting < 450) {
spriteIndex = 98; // Recoil
spriteIndex = 90; // Recoil (back to aim pose)
} else {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -126,8 +121,7 @@ class Guard extends Enemy {
break;
case EntityState.pain:
spriteIndex = 94; // Ouch frame
// Stay in pain for a brief moment, then resume attacking
spriteIndex = 92; // Ouch frame
if (elapsedMs - lastActionTime > 250) {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -137,15 +131,14 @@ class Guard extends Enemy {
case EntityState.dead:
if (isDying) {
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
if (deathFrame < 4) {
// FIX: Removed the buggy "- 1"
spriteIndex = 90 + deathFrame;
if (deathFrame < 3) {
spriteIndex = 93 + deathFrame; // Cycles 93, 94, 95
} else {
spriteIndex = 95; // Final dead frame
isDying = false;
}
} else {
spriteIndex = 95;
spriteIndex = 95; // Final dead frame
}
break;

View File

@@ -8,7 +8,6 @@ import 'package:wolf_dart/features/entities/map_objects.dart';
class Mutant extends Enemy {
static const double speed = 0.045;
static const int _baseSprite = 187;
bool _hasFiredThisCycle = false;
Mutant({
@@ -17,22 +16,16 @@ class Mutant extends Enemy {
required super.angle,
required super.mapId,
}) : super(
spriteIndex: _baseSprite,
spriteIndex: EnemyType.mutant.spriteBaseIdx,
state: EntityState.idle,
) {
health = 45;
damage = 10;
}
static Mutant? trySpawn(
int objId,
double x,
double y,
Difficulty difficulty,
) {
// Mutants span 252 to 287
if (objId >= MapObject.mutantStart && objId <= MapObject.mutantStart + 35) {
bool isPatrolling = objId >= MapObject.mutantStart + 18;
static Mutant? trySpawn(int objId, double x, double y, Difficulty _) {
if (EnemyType.mutant.claimsMapId(objId)) {
bool isPatrolling = objId >= EnemyType.mutant.mapBaseId + 18;
return Mutant(
x: x,
@@ -81,7 +74,7 @@ class Mutant extends Enemy {
switch (state) {
case EntityState.idle:
spriteIndex = _baseSprite + octant;
spriteIndex = EnemyType.mutant.spriteBaseIdx + octant;
break;
case EntityState.patrolling:
@@ -96,7 +89,8 @@ class Mutant extends Enemy {
}
int walkFrame = (elapsedMs ~/ 150) % 4;
spriteIndex = (_baseSprite + 8) + (walkFrame * 8) + octant;
spriteIndex =
(EnemyType.mutant.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
if (hasLineOfSight(playerPosition, isWalkable)) {
@@ -110,15 +104,15 @@ class Mutant extends Enemy {
case EntityState.attacking:
int timeShooting = elapsedMs - lastActionTime;
if (timeShooting < 150) {
spriteIndex = _baseSprite + 46; // Aiming
spriteIndex = EnemyType.mutant.spriteBaseIdx + 46; // Aiming
} else if (timeShooting < 300) {
spriteIndex = _baseSprite + 47; // Firing
spriteIndex = EnemyType.mutant.spriteBaseIdx + 47; // Firing
if (!_hasFiredThisCycle) {
onDamagePlayer(damage);
_hasFiredThisCycle = true;
}
} else if (timeShooting < 450) {
spriteIndex = _baseSprite + 48; // Recoil
spriteIndex = EnemyType.mutant.spriteBaseIdx + 48; // Recoil
} else {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -126,7 +120,7 @@ class Mutant extends Enemy {
break;
case EntityState.pain:
spriteIndex = _baseSprite + 44;
spriteIndex = EnemyType.mutant.spriteBaseIdx + 44;
if (elapsedMs - lastActionTime > 250) {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -137,13 +131,13 @@ class Mutant extends Enemy {
if (isDying) {
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
if (deathFrame < 4) {
spriteIndex = (_baseSprite + 40) + deathFrame;
spriteIndex = (EnemyType.mutant.spriteBaseIdx + 40) + deathFrame;
} else {
spriteIndex = _baseSprite + 45;
spriteIndex = EnemyType.mutant.spriteBaseIdx + 45;
isDying = false;
}
} else {
spriteIndex = _baseSprite + 45;
spriteIndex = EnemyType.mutant.spriteBaseIdx + 45;
}
break;

View File

@@ -8,7 +8,6 @@ import 'package:wolf_dart/features/entities/map_objects.dart';
class Officer extends Enemy {
static const double speed = 0.055;
static const int _baseSprite = 50;
bool _hasFiredThisCycle = false;
Officer({
@@ -17,22 +16,16 @@ class Officer extends Enemy {
required super.angle,
required super.mapId,
}) : super(
spriteIndex: _baseSprite,
spriteIndex: EnemyType.officer.spriteBaseIdx,
state: EntityState.idle,
) {
health = 50;
damage = 15;
}
static Officer? trySpawn(
int objId,
double x,
double y,
Difficulty difficulty,
) {
if (objId >= MapObject.officerStart &&
objId <= MapObject.officerStart + 35) {
bool isPatrolling = objId >= MapObject.officerStart + 18;
static Officer? trySpawn(int objId, double x, double y, Difficulty _) {
if (EnemyType.officer.claimsMapId(objId)) {
bool isPatrolling = objId >= EnemyType.officer.mapBaseId + 18;
return Officer(
x: x,
@@ -81,7 +74,7 @@ class Officer extends Enemy {
switch (state) {
case EntityState.idle:
spriteIndex = _baseSprite + octant;
spriteIndex = EnemyType.officer.spriteBaseIdx + octant;
break;
case EntityState.patrolling:
@@ -96,7 +89,8 @@ class Officer extends Enemy {
}
int walkFrame = (elapsedMs ~/ 150) % 4;
spriteIndex = (_baseSprite + 8) + (walkFrame * 8) + octant;
spriteIndex =
(EnemyType.officer.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
if (distance < 6.0 && elapsedMs - lastActionTime > 1000) {
if (hasLineOfSight(playerPosition, isWalkable)) {
@@ -110,15 +104,15 @@ class Officer extends Enemy {
case EntityState.attacking:
int timeShooting = elapsedMs - lastActionTime;
if (timeShooting < 150) {
spriteIndex = _baseSprite + 46; // Aiming
spriteIndex = EnemyType.officer.spriteBaseIdx + 40; // Aiming
} else if (timeShooting < 300) {
spriteIndex = _baseSprite + 47; // Firing
spriteIndex = EnemyType.officer.spriteBaseIdx + 41; // Firing
if (!_hasFiredThisCycle) {
onDamagePlayer(damage);
_hasFiredThisCycle = true;
}
} else if (timeShooting < 450) {
spriteIndex = _baseSprite + 48; // Recoil
spriteIndex = EnemyType.officer.spriteBaseIdx + 40; // Recoil
} else {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -126,7 +120,7 @@ class Officer extends Enemy {
break;
case EntityState.pain:
spriteIndex = _baseSprite + 44;
spriteIndex = EnemyType.officer.spriteBaseIdx + 42;
if (elapsedMs - lastActionTime > 250) {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -136,14 +130,14 @@ class Officer extends Enemy {
case EntityState.dead:
if (isDying) {
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
if (deathFrame < 4) {
spriteIndex = (_baseSprite + 40) + deathFrame;
if (deathFrame < 3) {
spriteIndex = (EnemyType.officer.spriteBaseIdx + 43) + deathFrame;
} else {
spriteIndex = _baseSprite + 45;
spriteIndex = EnemyType.officer.spriteBaseIdx + 45;
isDying = false;
}
} else {
spriteIndex = _baseSprite + 45;
spriteIndex = EnemyType.officer.spriteBaseIdx + 45;
}
break;

View File

@@ -8,7 +8,6 @@ import 'package:wolf_dart/features/entities/map_objects.dart';
class SS extends Enemy {
static const double speed = 0.04;
static const int _baseSprite = 138;
bool _hasFiredThisCycle = false;
SS({
@@ -17,16 +16,16 @@ class SS extends Enemy {
required super.angle,
required super.mapId,
}) : super(
spriteIndex: _baseSprite,
spriteIndex: EnemyType.ss.spriteBaseIdx,
state: EntityState.idle,
) {
health = 100;
damage = 20;
}
static SS? trySpawn(int objId, double x, double y, Difficulty difficulty) {
if (objId >= MapObject.ssStart && objId <= MapObject.ssStart + 35) {
bool isPatrolling = objId >= MapObject.ssStart + 18;
static SS? trySpawn(int objId, double x, double y, Difficulty _) {
if (EnemyType.ss.claimsMapId(objId)) {
bool isPatrolling = objId >= EnemyType.ss.mapBaseId + 18;
return SS(
x: x,
@@ -75,7 +74,7 @@ class SS extends Enemy {
switch (state) {
case EntityState.idle:
spriteIndex = _baseSprite + octant;
spriteIndex = EnemyType.ss.spriteBaseIdx + octant;
break;
case EntityState.patrolling:
@@ -90,7 +89,8 @@ class SS extends Enemy {
}
int walkFrame = (elapsedMs ~/ 150) % 4;
spriteIndex = (_baseSprite + 8) + (walkFrame * 8) + octant;
spriteIndex =
(EnemyType.ss.spriteBaseIdx + 8) + (walkFrame * 8) + octant;
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
if (hasLineOfSight(playerPosition, isWalkable)) {
@@ -105,15 +105,15 @@ class SS extends Enemy {
// SS machine gun fires much faster than a standard pistol!
int timeShooting = elapsedMs - lastActionTime;
if (timeShooting < 100) {
spriteIndex = _baseSprite + 46; // Aiming
spriteIndex = EnemyType.ss.spriteBaseIdx + 46; // Aiming
} else if (timeShooting < 200) {
spriteIndex = _baseSprite + 47; // Firing
spriteIndex = EnemyType.ss.spriteBaseIdx + 47; // Firing
if (!_hasFiredThisCycle) {
onDamagePlayer(damage);
_hasFiredThisCycle = true;
}
} else if (timeShooting < 300) {
spriteIndex = _baseSprite + 48; // Recoil
spriteIndex = EnemyType.ss.spriteBaseIdx + 48; // Recoil
} else {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -121,7 +121,7 @@ class SS extends Enemy {
break;
case EntityState.pain:
spriteIndex = _baseSprite + 44;
spriteIndex = EnemyType.ss.spriteBaseIdx + 44;
if (elapsedMs - lastActionTime > 250) {
state = EntityState.patrolling;
lastActionTime = elapsedMs;
@@ -132,13 +132,13 @@ class SS extends Enemy {
if (isDying) {
int deathFrame = (elapsedMs - lastActionTime) ~/ 150;
if (deathFrame < 4) {
spriteIndex = (_baseSprite + 40) + deathFrame;
spriteIndex = (EnemyType.ss.spriteBaseIdx + 40) + deathFrame;
} else {
spriteIndex = _baseSprite + 45;
spriteIndex = EnemyType.ss.spriteBaseIdx + 45;
isDying = false;
}
} else {
spriteIndex = _baseSprite + 45;
spriteIndex = EnemyType.ss.spriteBaseIdx + 45;
}
break;

View File

@@ -3,6 +3,7 @@ import 'package:wolf_dart/features/entities/collectible.dart';
import 'package:wolf_dart/features/entities/decorative.dart';
import 'package:wolf_dart/features/entities/enemies/bosses/hans_grosse.dart';
import 'package:wolf_dart/features/entities/enemies/dog.dart';
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
import 'package:wolf_dart/features/entities/enemies/guard.dart';
import 'package:wolf_dart/features/entities/enemies/mutant.dart';
import 'package:wolf_dart/features/entities/enemies/officer.dart';
@@ -50,6 +51,11 @@ abstract class EntityRegistry {
for (final spawner in _spawners) {
Entity? entity = spawner(objId, x, y, difficulty);
final EnemyType? type = EnemyType.fromMapId(objId);
if (type != null) {
print("Spawning ${type.name} enemy");
}
if (entity != null) {
// Safety bounds check for the VSWAP array
if (entity.spriteIndex >= 0 && entity.spriteIndex < maxSprites) {

View File

@@ -1,5 +1,6 @@
import 'package:wolf_dart/classes/cardinal_direction.dart';
import 'package:wolf_dart/features/difficulty/difficulty.dart';
import 'package:wolf_dart/features/entities/enemies/enemy.dart';
abstract class MapObject {
// --- Player Spawns ---
@@ -81,10 +82,10 @@ abstract class MapObject {
// --- Enemy Range Constants ---
static const int guardStart = 108; // 108-143
static const int officerStart = 144; // 144-179 (WL6)
static const int ssStart = 180; // 180-215 (WL6)
static const int officerStart = 144; // 144-179
static const int ssStart = 180; // 180-215
static const int dogStart = 216; // 216-251
static const int mutantStart = 252; // 252-287 (WL6)
static const int mutantStart = 252; // 252-287
// --- Missing Decorative Bodies ---
static const int deadGuard = 124; // Decorative only in WL1
@@ -112,6 +113,7 @@ abstract class MapObject {
}
static double getAngle(int id) {
// Player spawn
switch (id) {
case playerNorth:
return CardinalDirection.north.radians;
@@ -123,52 +125,34 @@ abstract class MapObject {
return CardinalDirection.west.radians;
}
// FIX: Expand the boundary to include ALL enemies (Dogs and Mutants)
if (id < guardStart || id > (mutantStart + 35)) return 0.0;
// Boss check
if (id == bossHansGrosse) return 0.0;
int baseId;
if (id >= mutantStart) {
baseId = mutantStart;
} else if (id >= dogStart) {
baseId = dogStart;
} else if (id >= ssStart) {
baseId = ssStart;
} else if (id >= officerStart) {
baseId = officerStart;
} else {
baseId = guardStart;
}
final EnemyType? type = EnemyType.fromMapId(id);
if (type == null) return 0.0; // Not a standard directional enemy
// FIX: Normalize patrolling enemies back to the standing block, THEN get the 4-way angle
int directionIndex = ((id - baseId) % 18) % 4;
// Normalize patrolling enemies back to the standing block, THEN get the
// 4-way angle
int directionIndex = ((id - type.mapBaseId) % 18) % 4;
return CardinalDirection.fromEnemyIndex(directionIndex).radians;
}
static bool shouldSpawn(int id, Difficulty selectedDifficulty) {
// FIX: Expand the boundary so Dogs and Mutants aren't bypassing difficulty checks
if (id < guardStart || id > (mutantStart + 35)) return true;
EnemyType? type = EnemyType.fromMapId(id);
int baseId;
if (id >= mutantStart) {
baseId = mutantStart;
} else if (id >= dogStart) {
baseId = dogStart;
} else if (id >= ssStart) {
baseId = ssStart;
} else if (id >= officerStart) {
baseId = officerStart;
} else {
baseId = guardStart;
}
// If it's not a standard enemy (it's a decoration, boss, or player), spawn it
if (type == null) return true;
int relativeId = (id - baseId) % 18;
int offset = id - type.mapBaseId;
int normalizedOffset = offset >= 18 ? offset - 18 : offset;
return switch (relativeId) {
< 4 => true,
< 8 => selectedDifficulty.level >= Difficulty.dontHurtMe.level,
< 12 => selectedDifficulty.level >= Difficulty.bringEmOn.level,
< 16 => selectedDifficulty.level >= Difficulty.iAmDeathIncarnate.level,
_ => true,
return switch (normalizedOffset) {
< 4 => true, // Spawns on all difficulties
< 8 => selectedDifficulty.level >= Difficulty.bringEmOn.level, // Normal
< 16 =>
selectedDifficulty.level >=
Difficulty.iAmDeathIncarnate.level, // Hard & Ambush
_ => true, // Dead bodies (decorations)
};
}
}