Mapping enemy ids to difficulties

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-15 19:21:12 +01:00
parent 6c55136d5b
commit 0eebf8e4fa
15 changed files with 274 additions and 196 deletions

View File

@@ -36,6 +36,16 @@ class SpriteGallery extends StatelessWidget {
if (animation != null) { if (animation != null) {
label += "\n${animation.name}"; label += "\n${animation.name}";
} }
// Append the Map IDs for level editing reference
int staticBase = enemy.mapData.baseStaticId;
int patrolBase = enemy.mapData.basePatrolId;
label +=
"\nStat: $staticBase (E), ${staticBase + 1} (M), ${staticBase + 2} (H)";
label += "\nPat: $patrolBase-${patrolBase + 3}(E)";
label += "\nPat: ${patrolBase + 4}-${patrolBase + 7}(M)";
label += "\nPat: ${patrolBase + 8}-${patrolBase + 11}(H)";
break; break;
} }
} }

View File

@@ -0,0 +1,32 @@
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
class EnemyMapData {
final int baseStaticId;
final int basePatrolId;
const EnemyMapData({required this.baseStaticId, required this.basePatrolId});
/// True if this ID belongs to this enemy type at all
bool claimsId(int id) {
bool isStatic = id >= baseStaticId && id <= baseStaticId + 2;
// Patrol blocks contain 12 directional IDs and up to 6 ambush/special IDs
bool isPatrolOrAmbush = id >= basePatrolId && id < basePatrolId + 18;
return isStatic || isPatrolOrAmbush;
}
/// Exact check: Is this a static enemy on this specific difficulty?
bool isStaticForDifficulty(int id, Difficulty difficulty) {
return id == baseStaticId + difficulty.level;
}
/// Exact check: Is this a patrolling enemy on this specific difficulty?
bool isPatrolForDifficulty(int id, Difficulty difficulty) {
int startId = basePatrolId + (difficulty.level * 4);
return id >= startId && id < startId + 4; // Spans 4 directions
}
/// Exact check: Is this an ambush enemy on this specific difficulty?
bool isAmbushForDifficulty(int id, Difficulty difficulty) {
return id == basePatrolId + 12 + difficulty.level;
}
}

View File

@@ -111,11 +111,12 @@ abstract class MapObject {
if (id == bossHansGrosse) return 0.0; if (id == bossHansGrosse) return 0.0;
final EnemyType? type = EnemyType.fromMapId(id); final EnemyType? type = EnemyType.fromMapId(id);
if (type == null) return 0.0; // Not a standard directional enemy if (type == null) return 0.0;
// Normalize patrolling enemies back to the standing block, THEN get the // Because the map data strictly groups patrol directions in blocks of 4
// 4-way angle // (East=0, North=1, West=2, South=3), a simple modulo 4 against the base ID
int directionIndex = ((id - type.patrolId) % 18) % 4; // gives us the exact cardinal direction!
int directionIndex = (id - type.mapData.basePatrolId) % 4;
return CardinalDirection.fromEnemyIndex(directionIndex).radians; return CardinalDirection.fromEnemyIndex(directionIndex).radians;
} }

View File

@@ -0,0 +1,10 @@
/// Defines the exact start and end sprite indices for an animation state.
class SpriteFrameRange {
final int start;
final int end;
const SpriteFrameRange(this.start, this.end);
int get length => end - start + 1;
bool contains(int index) => index >= start && index <= end;
}

View File

@@ -6,6 +6,7 @@ library;
export 'src/cardinal_direction.dart' show CardinalDirection; export 'src/cardinal_direction.dart' show CardinalDirection;
export 'src/coordinate_2d.dart' show Coordinate2D; export 'src/coordinate_2d.dart' show Coordinate2D;
export 'src/difficulty.dart' show Difficulty; export 'src/difficulty.dart' show Difficulty;
export 'src/enemy_map_data.dart' show EnemyMapData;
export 'src/episode.dart' show Episode; export 'src/episode.dart' show Episode;
export 'src/game_file.dart' show GameFile; export 'src/game_file.dart' show GameFile;
export 'src/game_version.dart' show GameVersion; export 'src/game_version.dart' show GameVersion;
@@ -14,5 +15,6 @@ export 'src/map_objects.dart' show MapObject;
export 'src/sound.dart' export 'src/sound.dart'
show PcmSound, AdLibSound, ImfMusic, ImfInstruction, WolfMusicMap; show PcmSound, AdLibSound, ImfMusic, ImfInstruction, WolfMusicMap;
export 'src/sprite.dart' hide Matrix; export 'src/sprite.dart' hide Matrix;
export 'src/sprite_frame_range.dart' show SpriteFrameRange;
export 'src/wolf_level.dart' show WolfLevel; export 'src/wolf_level.dart' show WolfLevel;
export 'src/wolfenstein_data.dart' show WolfensteinData; export 'src/wolfenstein_data.dart' show WolfensteinData;

View File

@@ -2,6 +2,8 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart'; import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_entities/src/entity.dart'; import 'package:wolf_3d_entities/src/entity.dart';
class Dog extends Enemy { class Dog extends Enemy {
@@ -15,7 +17,7 @@ class Dog extends Enemy {
required super.y, required super.y,
required super.angle, required super.angle,
required super.mapId, required super.mapId,
}) : super(spriteIndex: type.spriteBaseIdx, state: EntityState.idle) { }) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
health = 1; health = 1;
damage = 5; damage = 5;
} }
@@ -40,7 +42,7 @@ class Dog extends Enemy {
double distance = position.distanceTo(playerPosition); double distance = position.distanceTo(playerPosition);
double angleToPlayer = position.angleTo(playerPosition); double angleToPlayer = position.angleTo(playerPosition);
if (state != EntityState.idle && state != EntityState.dead) { if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer; newAngle = angleToPlayer;
} }
@@ -66,10 +68,6 @@ class Dog extends Enemy {
angleDiff: diff, angleDiff: diff,
); );
if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer;
}
// Dogs attack based on distance, so wrap the movement and attack in alert checks // Dogs attack based on distance, so wrap the movement and attack in alert checks
if (state == EntityState.patrolling) { if (state == EntityState.patrolling) {
if (!isAlerted || distance > 1.0) { if (!isAlerted || distance > 1.0) {

View File

@@ -2,159 +2,13 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/dog.dart'; import 'package:wolf_3d_entities/src/entities/enemies/dog.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_entities/src/entities/enemies/guard.dart'; import 'package:wolf_3d_entities/src/entities/enemies/guard.dart';
import 'package:wolf_3d_entities/src/entities/enemies/mutant.dart'; import 'package:wolf_3d_entities/src/entities/enemies/mutant.dart';
import 'package:wolf_3d_entities/src/entities/enemies/officer.dart'; import 'package:wolf_3d_entities/src/entities/enemies/officer.dart';
import 'package:wolf_3d_entities/src/entities/enemies/ss.dart'; import 'package:wolf_3d_entities/src/entities/enemies/ss.dart';
import 'package:wolf_3d_entities/src/entity.dart'; import 'package:wolf_3d_entities/src/entity.dart';
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }
enum EnemyType {
guard(staticId: 23, patrolId: 108, spriteBaseIdx: 50),
officer(staticId: 26, patrolId: 126, spriteBaseIdx: 238),
ss(staticId: 29, patrolId: 144, spriteBaseIdx: 138),
dog(staticId: 32, patrolId: 162, spriteBaseIdx: 99),
mutant(staticId: 35, patrolId: 180, spriteBaseIdx: 187);
final int staticId;
final int patrolId;
final int spriteBaseIdx;
const EnemyType({
required this.staticId,
required this.patrolId,
required this.spriteBaseIdx,
});
/// Checks if the ID belongs to this enemy type range
bool claimsMapId(int id) {
// Static enemies span 3 IDs (Easy, Medium, Hard)
bool isStatic = id >= staticId && id < staticId + 3;
// Patrolling enemies span 18 IDs per type
bool isPatrol = id >= patrolId && id < patrolId + 18;
return isStatic || isPatrol;
}
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,
};
}
EnemyAnimation? getAnimationFromSprite(int spriteIndex) {
if (!claimsSpriteIndex(spriteIndex)) return null;
int offset = spriteIndex - spriteBaseIdx;
if (offset >= 0 && offset <= 7) return EnemyAnimation.idle;
return switch (this) {
EnemyType.guard || EnemyType.ss => switch (offset) {
>= 8 && <= 39 => EnemyAnimation.walking,
>= 40 && <= 42 => EnemyAnimation.dying,
43 => EnemyAnimation.pain,
>= 44 && <= 46 => EnemyAnimation.attacking,
_ => EnemyAnimation.dead,
},
EnemyType.officer || EnemyType.mutant => switch (offset) {
>= 8 && <= 39 => EnemyAnimation.walking,
// All humanoids share 3 dying frames
>= 40 && <= 42 => EnemyAnimation.dying,
43 => EnemyAnimation.pain,
// Officers/Mutants only have 2 attack frames
>= 44 && <= 45 => EnemyAnimation.attacking,
_ => EnemyAnimation.dead,
},
EnemyType.dog => switch (offset) {
>= 8 && <= 31 => EnemyAnimation.walking,
>= 32 && <= 34 => EnemyAnimation.attacking,
>= 35 && <= 37 => EnemyAnimation.dying,
_ => EnemyAnimation.dead,
},
};
}
int getSpriteFromAnimation({
required EnemyAnimation animation,
required int elapsedMs,
required int lastActionTime,
double angleDiff = 0,
int? walkFrameOverride,
}) {
int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
if (octant < 0) octant += 8;
return switch (animation) {
EnemyAnimation.idle => spriteBaseIdx + octant,
EnemyAnimation.walking => () {
int frameCount = this == EnemyType.dog ? 3 : 4;
int frame = walkFrameOverride ?? (elapsedMs ~/ 150) % frameCount;
return (spriteBaseIdx + 8) + (frame * 8) + octant;
}(),
EnemyAnimation.attacking => () {
int time = elapsedMs - lastActionTime;
return switch (this) {
EnemyType.guard || EnemyType.ss =>
spriteBaseIdx +
(time < 150
? 44
: time < 300
? 45
: 46),
EnemyType.officer ||
EnemyType.mutant => spriteBaseIdx + (time < 200 ? 44 : 45),
EnemyType.dog => spriteBaseIdx + (time < 150 ? 32 : 33),
};
}(),
EnemyAnimation.pain =>
spriteBaseIdx +
switch (this) {
EnemyType.dog => 32,
_ => 43,
},
EnemyAnimation.dying => () {
int frame = (elapsedMs - lastActionTime) ~/ 150;
int dyingStart = switch (this) {
EnemyType.dog => 35,
_ => 40,
};
return spriteBaseIdx + dyingStart + (frame.clamp(0, 2));
}(),
EnemyAnimation.dead =>
spriteBaseIdx +
switch (this) {
EnemyType.dog => 38,
EnemyType.officer || EnemyType.mutant => 46,
_ => 48,
},
};
}
}
abstract class Enemy extends Entity { abstract class Enemy extends Entity {
Enemy({ Enemy({
required super.x, required super.x,
@@ -340,33 +194,41 @@ abstract class Enemy extends Entity {
Difficulty difficulty, { Difficulty difficulty, {
bool isSharewareMode = false, bool isSharewareMode = false,
}) { }) {
// ID 124 (dead guard) and 125 (dead aardwolf) fall inside Guard's patrol
// range (108125) but are decorative bodies, not live actors.
if (objId == MapObject.deadGuard || objId == MapObject.deadAardwolf) { if (objId == MapObject.deadGuard || objId == MapObject.deadAardwolf) {
return null; return null;
} }
if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) { if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) {
return null; return null;
} }
// 2. I use the utility to check if this enemy is allowed on this difficulty
if (!MapObject.isDifficultyAllowed(objId, difficulty)) return null;
// 3. I check if I even know what this enemy is
final type = EnemyType.fromMapId(objId); final type = EnemyType.fromMapId(objId);
if (type == null) return null; if (type == null) return null;
bool isPatrolling = objId >= type.patrolId; final mapData = type.mapData;
double spawnAngle = MapObject.getAngle(objId);
// 2. Return the specific instance // 1. Validate Difficulty and Determine State
EntityState spawnState;
double spawnAngle = 0.0;
if (mapData.isPatrolForDifficulty(objId, difficulty)) {
spawnState = EntityState.patrolling;
spawnAngle = CardinalDirection.fromEnemyIndex(objId).radians;
} else if (mapData.isStaticForDifficulty(objId, difficulty)) {
spawnState = EntityState.idle; // Faces the player dynamically later
} else if (mapData.isAmbushForDifficulty(objId, difficulty)) {
spawnState = EntityState.ambush; // Stays perfectly still until alerted
} else {
// If the ID belongs to this enemy but NOT this difficulty, skip spawning!
return null;
}
// 2. Return the instance
return switch (type) { return switch (type) {
EnemyType.guard => Guard(x: x, y: y, angle: spawnAngle, mapId: objId), EnemyType.guard => Guard(x: x, y: y, angle: spawnAngle, mapId: objId),
EnemyType.dog => Dog(x: x, y: y, angle: spawnAngle, mapId: objId), EnemyType.dog => Dog(x: x, y: y, angle: spawnAngle, mapId: objId),
EnemyType.ss => SS(x: x, y: y, angle: spawnAngle, mapId: objId), EnemyType.ss => SS(x: x, y: y, angle: spawnAngle, mapId: objId),
EnemyType.mutant => Mutant(x: x, y: y, angle: spawnAngle, mapId: objId), EnemyType.mutant => Mutant(x: x, y: y, angle: spawnAngle, mapId: objId),
EnemyType.officer => Officer(x: x, y: y, angle: spawnAngle, mapId: objId), EnemyType.officer => Officer(x: x, y: y, angle: spawnAngle, mapId: objId),
}..state = isPatrolling ? EntityState.patrolling : EntityState.idle; }..state = spawnState;
} }
} }

View File

@@ -0,0 +1 @@
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }

View File

@@ -0,0 +1,169 @@
import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
/// Maps all possible animation states to their specific frame ranges.
class EnemyAnimationMap {
final SpriteFrameRange idle;
final SpriteFrameRange walking;
final SpriteFrameRange attacking;
final SpriteFrameRange pain;
final SpriteFrameRange dying;
final SpriteFrameRange dead;
const EnemyAnimationMap({
required this.idle,
required this.walking,
required this.attacking,
required this.pain,
required this.dying,
required this.dead,
});
EnemyAnimation? getAnimation(int spriteIndex) {
if (idle.contains(spriteIndex)) return EnemyAnimation.idle;
if (walking.contains(spriteIndex)) return EnemyAnimation.walking;
if (attacking.contains(spriteIndex)) return EnemyAnimation.attacking;
if (pain.contains(spriteIndex)) return EnemyAnimation.pain;
if (dying.contains(spriteIndex)) return EnemyAnimation.dying;
if (dead.contains(spriteIndex)) return EnemyAnimation.dead;
return null;
}
SpriteFrameRange getRange(EnemyAnimation animation) {
return switch (animation) {
EnemyAnimation.idle => idle,
EnemyAnimation.walking => walking,
EnemyAnimation.attacking => attacking,
EnemyAnimation.pain => pain,
EnemyAnimation.dying => dying,
EnemyAnimation.dead => dead,
};
}
}
enum EnemyType {
guard(
mapData: EnemyMapData(baseStaticId: 23, basePatrolId: 108),
animations: EnemyAnimationMap(
idle: SpriteFrameRange(50, 57),
walking: SpriteFrameRange(58, 89),
dying: SpriteFrameRange(90, 93),
pain: SpriteFrameRange(94, 94),
dead: SpriteFrameRange(95, 95),
attacking: SpriteFrameRange(96, 98),
),
),
dog(
mapData: EnemyMapData(baseStaticId: 26, basePatrolId: 126),
// Translated from your old offset logic so the game doesn't break
animations: EnemyAnimationMap(
idle: SpriteFrameRange(99, 106),
walking: SpriteFrameRange(107, 130),
attacking: SpriteFrameRange(131, 133),
pain: SpriteFrameRange(
131,
131,
), // Dog shared pain/attack frame in old code
dying: SpriteFrameRange(134, 136),
dead: SpriteFrameRange(137, 137),
),
),
ss(
mapData: EnemyMapData(baseStaticId: 29, basePatrolId: 144),
animations: EnemyAnimationMap(
idle: SpriteFrameRange(138, 145),
walking: SpriteFrameRange(146, 177),
attacking: SpriteFrameRange(178, 179),
pain: SpriteFrameRange(180, 180),
dying: SpriteFrameRange(181, 182),
dead: SpriteFrameRange(183, 183),
),
),
mutant(
mapData: EnemyMapData(baseStaticId: 32, basePatrolId: 162),
animations: EnemyAnimationMap(
idle: SpriteFrameRange(187, 194),
walking: SpriteFrameRange(195, 226),
attacking: SpriteFrameRange(227, 228),
pain: SpriteFrameRange(229, 229),
dying: SpriteFrameRange(230, 231),
dead: SpriteFrameRange(232, 232),
),
),
officer(
mapData: EnemyMapData(baseStaticId: 35, basePatrolId: 180),
animations: EnemyAnimationMap(
idle: SpriteFrameRange(238, 245),
walking: SpriteFrameRange(246, 277),
attacking: SpriteFrameRange(278, 279),
pain: SpriteFrameRange(280, 280),
dying: SpriteFrameRange(281, 282),
dead: SpriteFrameRange(283, 283),
),
);
final EnemyMapData mapData;
final EnemyAnimationMap animations;
const EnemyType({required this.mapData, required this.animations});
static EnemyType? fromMapId(int id) {
for (final type in EnemyType.values) {
if (type.mapData.claimsId(id)) return type;
}
return null;
}
bool claimsSpriteIndex(int index) => animations.getAnimation(index) != null;
EnemyAnimation? getAnimationFromSprite(int spriteIndex) {
return animations.getAnimation(spriteIndex);
}
int getSpriteFromAnimation({
required EnemyAnimation animation,
required int elapsedMs,
required int lastActionTime,
double angleDiff = 0,
int? walkFrameOverride,
}) {
final range = animations.getRange(animation);
// Calculates which of the 8 directions the enemy is facing relative to player
int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
if (octant < 0) octant += 8;
return switch (animation) {
EnemyAnimation.idle => range.start + octant,
EnemyAnimation.walking => () {
// Automatically calculates frames per angle (e.g. 32 frames / 8 angles = 4 frames)
int framesPerAngle = range.length ~/ 8;
if (framesPerAngle < 1) framesPerAngle = 1; // Failsafe
int frame = walkFrameOverride ?? (elapsedMs ~/ 150) % framesPerAngle;
return range.start + (frame * 8) + octant;
}(),
EnemyAnimation.attacking => () {
int time = elapsedMs - lastActionTime;
// Progresses through attack frames based on time, clamping at the last frame
int mappedFrame = (time ~/ 150).clamp(0, range.length - 1);
return range.start + mappedFrame;
}(),
EnemyAnimation.pain => range.start,
EnemyAnimation.dying => () {
int time = elapsedMs - lastActionTime;
// Progresses through death frames, staying on the final frame
int mappedFrame = (time ~/ 150).clamp(0, range.length - 1);
return range.start + mappedFrame;
}(),
EnemyAnimation.dead => range.start,
};
}
}

View File

@@ -2,6 +2,8 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart'; import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_entities/src/entity.dart'; import 'package:wolf_3d_entities/src/entity.dart';
class Guard extends Enemy { class Guard extends Enemy {
@@ -15,7 +17,7 @@ class Guard extends Enemy {
required super.y, required super.y,
required super.angle, required super.angle,
required super.mapId, required super.mapId,
}) : super(spriteIndex: type.spriteBaseIdx, state: EntityState.idle); }) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle);
@override @override
({Coordinate2D movement, double newAngle}) update({ ({Coordinate2D movement, double newAngle}) update({
@@ -41,10 +43,6 @@ class Guard extends Enemy {
newAngle = angleToPlayer; newAngle = angleToPlayer;
} }
if (state != EntityState.idle && state != EntityState.dead) {
newAngle = angleToPlayer;
}
// Calculate angle diff for the octant logic // Calculate angle diff for the octant logic
double diff = angleToPlayer - newAngle; double diff = angleToPlayer - newAngle;
while (diff <= -math.pi) { while (diff <= -math.pi) {

View File

@@ -2,6 +2,8 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart'; import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_entities/src/entity.dart'; import 'package:wolf_3d_entities/src/entity.dart';
class Mutant extends Enemy { class Mutant extends Enemy {
@@ -15,7 +17,7 @@ class Mutant extends Enemy {
required super.y, required super.y,
required super.angle, required super.angle,
required super.mapId, required super.mapId,
}) : super(spriteIndex: type.spriteBaseIdx, state: EntityState.idle) { }) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
health = 45; health = 45;
damage = 10; damage = 10;
} }
@@ -40,7 +42,7 @@ class Mutant extends Enemy {
double distance = position.distanceTo(playerPosition); double distance = position.distanceTo(playerPosition);
double angleToPlayer = position.angleTo(playerPosition); double angleToPlayer = position.angleTo(playerPosition);
if (state != EntityState.idle && state != EntityState.dead) { if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer; newAngle = angleToPlayer;
} }
@@ -69,10 +71,6 @@ class Mutant extends Enemy {
angleDiff: diff, angleDiff: diff,
); );
if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer;
}
if (state == EntityState.patrolling) { if (state == EntityState.patrolling) {
// FIX 2: Move along patrol angle if unalerted, chase if alerted // FIX 2: Move along patrol angle if unalerted, chase if alerted
if (!isAlerted || distance > 0.8) { if (!isAlerted || distance > 0.8) {

View File

@@ -2,21 +2,22 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart'; import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_entities/src/entity.dart'; import 'package:wolf_3d_entities/src/entity.dart';
class Officer extends Enemy { class Officer extends Enemy {
static const double speed = 0.055; static const double speed = 0.055;
bool _hasFiredThisCycle = false; bool _hasFiredThisCycle = false;
static EnemyType get type => EnemyType.officer;
Officer({ Officer({
required super.x, required super.x,
required super.y, required super.y,
required super.angle, required super.angle,
required super.mapId, required super.mapId,
}) : super( }) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
spriteIndex: EnemyType.officer.spriteBaseIdx,
state: EntityState.idle,
) {
health = 50; health = 50;
damage = 15; damage = 15;
} }
@@ -41,7 +42,7 @@ class Officer extends Enemy {
double distance = position.distanceTo(playerPosition); double distance = position.distanceTo(playerPosition);
double angleToPlayer = position.angleTo(playerPosition); double angleToPlayer = position.angleTo(playerPosition);
if (state != EntityState.idle && state != EntityState.dead) { if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer; newAngle = angleToPlayer;
} }
@@ -62,17 +63,13 @@ class Officer extends Enemy {
_ => EnemyAnimation.idle, _ => EnemyAnimation.idle,
}; };
spriteIndex = EnemyType.officer.getSpriteFromAnimation( spriteIndex = type.getSpriteFromAnimation(
animation: currentAnim, animation: currentAnim,
elapsedMs: elapsedMs, elapsedMs: elapsedMs,
lastActionTime: lastActionTime, lastActionTime: lastActionTime,
angleDiff: diff, angleDiff: diff,
); );
if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer;
}
if (state == EntityState.patrolling) { if (state == EntityState.patrolling) {
// FIX 2: Move along patrol angle if unalerted, chase if alerted // FIX 2: Move along patrol angle if unalerted, chase if alerted
if (!isAlerted || distance > 0.8) { if (!isAlerted || distance > 0.8) {

View File

@@ -2,6 +2,8 @@ import 'dart:math' as math;
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart'; import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_animation.dart';
import 'package:wolf_3d_entities/src/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_entities/src/entity.dart'; import 'package:wolf_3d_entities/src/entity.dart';
class SS extends Enemy { class SS extends Enemy {
@@ -15,7 +17,7 @@ class SS extends Enemy {
required super.y, required super.y,
required super.angle, required super.angle,
required super.mapId, required super.mapId,
}) : super(spriteIndex: EnemyType.ss.spriteBaseIdx, state: EntityState.idle) { }) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
health = 100; health = 100;
damage = 20; damage = 20;
} }
@@ -40,7 +42,7 @@ class SS extends Enemy {
double distance = position.distanceTo(playerPosition); double distance = position.distanceTo(playerPosition);
double angleToPlayer = position.angleTo(playerPosition); double angleToPlayer = position.angleTo(playerPosition);
if (state != EntityState.idle && state != EntityState.dead) { if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer; newAngle = angleToPlayer;
} }
@@ -69,10 +71,6 @@ class SS extends Enemy {
angleDiff: diff, angleDiff: diff,
); );
if (isAlerted && state != EntityState.dead) {
newAngle = angleToPlayer;
}
if (state == EntityState.patrolling) { if (state == EntityState.patrolling) {
// FIX 2: Move along patrol angle if unalerted, chase if alerted // FIX 2: Move along patrol angle if unalerted, chase if alerted
if (!isAlerted || distance > 0.8) { if (!isAlerted || distance > 0.8) {

View File

@@ -1,6 +1,6 @@
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart'; import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
enum EntityState { staticObj, idle, patrolling, attacking, pain, dead } enum EntityState { staticObj, ambush, idle, patrolling, attacking, pain, dead }
abstract class Entity<T> { abstract class Entity<T> {
double x; double x;

View File

@@ -9,6 +9,8 @@ export 'src/entities/door.dart';
export 'src/entities/enemies/bosses/hans_grosse.dart'; export 'src/entities/enemies/bosses/hans_grosse.dart';
export 'src/entities/enemies/dog.dart'; export 'src/entities/enemies/dog.dart';
export 'src/entities/enemies/enemy.dart'; export 'src/entities/enemies/enemy.dart';
export 'src/entities/enemies/enemy_animation.dart';
export 'src/entities/enemies/enemy_type.dart';
export 'src/entities/enemies/guard.dart'; export 'src/entities/enemies/guard.dart';
export 'src/entities/enemies/mutant.dart'; export 'src/entities/enemies/mutant.dart';
export 'src/entities/enemies/officer.dart'; export 'src/entities/enemies/officer.dart';