Mapping enemy ids to difficulties
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -36,6 +36,16 @@ class SpriteGallery extends StatelessWidget {
|
||||
if (animation != null) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
32
packages/wolf_3d_data_types/lib/src/enemy_map_data.dart
Normal file
32
packages/wolf_3d_data_types/lib/src/enemy_map_data.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -111,11 +111,12 @@ abstract class MapObject {
|
||||
if (id == bossHansGrosse) return 0.0;
|
||||
|
||||
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
|
||||
// 4-way angle
|
||||
int directionIndex = ((id - type.patrolId) % 18) % 4;
|
||||
// Because the map data strictly groups patrol directions in blocks of 4
|
||||
// (East=0, North=1, West=2, South=3), a simple modulo 4 against the base ID
|
||||
// gives us the exact cardinal direction!
|
||||
int directionIndex = (id - type.mapData.basePatrolId) % 4;
|
||||
return CardinalDirection.fromEnemyIndex(directionIndex).radians;
|
||||
}
|
||||
|
||||
|
||||
10
packages/wolf_3d_data_types/lib/src/sprite_frame_range.dart
Normal file
10
packages/wolf_3d_data_types/lib/src/sprite_frame_range.dart
Normal 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;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ library;
|
||||
export 'src/cardinal_direction.dart' show CardinalDirection;
|
||||
export 'src/coordinate_2d.dart' show Coordinate2D;
|
||||
export 'src/difficulty.dart' show Difficulty;
|
||||
export 'src/enemy_map_data.dart' show EnemyMapData;
|
||||
export 'src/episode.dart' show Episode;
|
||||
export 'src/game_file.dart' show GameFile;
|
||||
export 'src/game_version.dart' show GameVersion;
|
||||
@@ -14,5 +15,6 @@ export 'src/map_objects.dart' show MapObject;
|
||||
export 'src/sound.dart'
|
||||
show PcmSound, AdLibSound, ImfMusic, ImfInstruction, WolfMusicMap;
|
||||
export 'src/sprite.dart' hide Matrix;
|
||||
export 'src/sprite_frame_range.dart' show SpriteFrameRange;
|
||||
export 'src/wolf_level.dart' show WolfLevel;
|
||||
export 'src/wolfenstein_data.dart' show WolfensteinData;
|
||||
|
||||
@@ -2,6 +2,8 @@ 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.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';
|
||||
|
||||
class Dog extends Enemy {
|
||||
@@ -15,7 +17,7 @@ class Dog extends Enemy {
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(spriteIndex: type.spriteBaseIdx, state: EntityState.idle) {
|
||||
}) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
|
||||
health = 1;
|
||||
damage = 5;
|
||||
}
|
||||
@@ -40,7 +42,7 @@ class Dog extends Enemy {
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
|
||||
if (state != EntityState.idle && state != EntityState.dead) {
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
@@ -66,10 +68,6 @@ class Dog extends Enemy {
|
||||
angleDiff: diff,
|
||||
);
|
||||
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
// Dogs attack based on distance, so wrap the movement and attack in alert checks
|
||||
if (state == EntityState.patrolling) {
|
||||
if (!isAlerted || distance > 1.0) {
|
||||
|
||||
@@ -2,159 +2,13 @@ import 'dart:math' as math;
|
||||
|
||||
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/enemy_type.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/officer.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/enemies/ss.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 {
|
||||
Enemy({
|
||||
required super.x,
|
||||
@@ -340,33 +194,41 @@ abstract class Enemy extends Entity {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// ID 124 (dead guard) and 125 (dead aardwolf) fall inside Guard's patrol
|
||||
// range (108–125) but are decorative bodies, not live actors.
|
||||
if (objId == MapObject.deadGuard || objId == MapObject.deadAardwolf) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) {
|
||||
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);
|
||||
if (type == null) return null;
|
||||
|
||||
bool isPatrolling = objId >= type.patrolId;
|
||||
double spawnAngle = MapObject.getAngle(objId);
|
||||
final mapData = type.mapData;
|
||||
|
||||
// 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) {
|
||||
EnemyType.guard => Guard(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.mutant => Mutant(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ 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.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';
|
||||
|
||||
class Guard extends Enemy {
|
||||
@@ -15,7 +17,7 @@ class Guard extends Enemy {
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(spriteIndex: type.spriteBaseIdx, state: EntityState.idle);
|
||||
}) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle);
|
||||
|
||||
@override
|
||||
({Coordinate2D movement, double newAngle}) update({
|
||||
@@ -41,10 +43,6 @@ class Guard extends Enemy {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
if (state != EntityState.idle && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
// Calculate angle diff for the octant logic
|
||||
double diff = angleToPlayer - newAngle;
|
||||
while (diff <= -math.pi) {
|
||||
|
||||
@@ -2,6 +2,8 @@ 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.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';
|
||||
|
||||
class Mutant extends Enemy {
|
||||
@@ -15,7 +17,7 @@ class Mutant extends Enemy {
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(spriteIndex: type.spriteBaseIdx, state: EntityState.idle) {
|
||||
}) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
|
||||
health = 45;
|
||||
damage = 10;
|
||||
}
|
||||
@@ -40,7 +42,7 @@ class Mutant extends Enemy {
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
|
||||
if (state != EntityState.idle && state != EntityState.dead) {
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
@@ -69,10 +71,6 @@ class Mutant extends Enemy {
|
||||
angleDiff: diff,
|
||||
);
|
||||
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
if (state == EntityState.patrolling) {
|
||||
// FIX 2: Move along patrol angle if unalerted, chase if alerted
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
|
||||
@@ -2,21 +2,22 @@ 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.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';
|
||||
|
||||
class Officer extends Enemy {
|
||||
static const double speed = 0.055;
|
||||
bool _hasFiredThisCycle = false;
|
||||
|
||||
static EnemyType get type => EnemyType.officer;
|
||||
|
||||
Officer({
|
||||
required super.x,
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(
|
||||
spriteIndex: EnemyType.officer.spriteBaseIdx,
|
||||
state: EntityState.idle,
|
||||
) {
|
||||
}) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
|
||||
health = 50;
|
||||
damage = 15;
|
||||
}
|
||||
@@ -41,7 +42,7 @@ class Officer extends Enemy {
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
|
||||
if (state != EntityState.idle && state != EntityState.dead) {
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
@@ -62,17 +63,13 @@ class Officer extends Enemy {
|
||||
_ => EnemyAnimation.idle,
|
||||
};
|
||||
|
||||
spriteIndex = EnemyType.officer.getSpriteFromAnimation(
|
||||
spriteIndex = type.getSpriteFromAnimation(
|
||||
animation: currentAnim,
|
||||
elapsedMs: elapsedMs,
|
||||
lastActionTime: lastActionTime,
|
||||
angleDiff: diff,
|
||||
);
|
||||
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
if (state == EntityState.patrolling) {
|
||||
// FIX 2: Move along patrol angle if unalerted, chase if alerted
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
|
||||
@@ -2,6 +2,8 @@ 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.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';
|
||||
|
||||
class SS extends Enemy {
|
||||
@@ -15,7 +17,7 @@ class SS extends Enemy {
|
||||
required super.y,
|
||||
required super.angle,
|
||||
required super.mapId,
|
||||
}) : super(spriteIndex: EnemyType.ss.spriteBaseIdx, state: EntityState.idle) {
|
||||
}) : super(spriteIndex: type.animations.idle.start, state: EntityState.idle) {
|
||||
health = 100;
|
||||
damage = 20;
|
||||
}
|
||||
@@ -40,7 +42,7 @@ class SS extends Enemy {
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
|
||||
if (state != EntityState.idle && state != EntityState.dead) {
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
@@ -69,10 +71,6 @@ class SS extends Enemy {
|
||||
angleDiff: diff,
|
||||
);
|
||||
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
if (state == EntityState.patrolling) {
|
||||
// FIX 2: Move along patrol angle if unalerted, chase if alerted
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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> {
|
||||
double x;
|
||||
|
||||
@@ -9,6 +9,8 @@ export 'src/entities/door.dart';
|
||||
export 'src/entities/enemies/bosses/hans_grosse.dart';
|
||||
export 'src/entities/enemies/dog.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/mutant.dart';
|
||||
export 'src/entities/enemies/officer.dart';
|
||||
|
||||
Reference in New Issue
Block a user