@@ -1,32 +1,25 @@
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
|
||||
class EnemyMapData {
|
||||
final int baseStaticId;
|
||||
final int basePatrolId;
|
||||
final int baseId;
|
||||
|
||||
const EnemyMapData({required this.baseStaticId, required this.basePatrolId});
|
||||
const EnemyMapData(this.baseId);
|
||||
|
||||
/// 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;
|
||||
}
|
||||
/// True if the ID falls anywhere within this enemy's 36-ID block
|
||||
bool claimsId(int id) => id >= baseId && id < baseId + 36;
|
||||
|
||||
/// Exact check: Is this a static enemy on this specific difficulty?
|
||||
bool isStaticForDifficulty(int id, Difficulty difficulty) {
|
||||
return id == baseStaticId + difficulty.level;
|
||||
int start = baseId + (difficulty.level * 4);
|
||||
return id >= start && id < start + 4;
|
||||
}
|
||||
|
||||
/// 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
|
||||
int start = baseId + 12 + (difficulty.level * 4);
|
||||
return id >= start && id < start + 4;
|
||||
}
|
||||
|
||||
/// Exact check: Is this an ambush enemy on this specific difficulty?
|
||||
bool isAmbushForDifficulty(int id, Difficulty difficulty) {
|
||||
return id == basePatrolId + 12 + difficulty.level;
|
||||
int start = baseId + 24 + (difficulty.level * 4);
|
||||
return id >= start && id < start + 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ abstract class MapObject {
|
||||
static const int deadAardwolf = 125; // Decorative only in WL1
|
||||
|
||||
static double getAngle(int id) {
|
||||
// Player spawn
|
||||
switch (id) {
|
||||
case playerNorth:
|
||||
return CardinalDirection.north.radians;
|
||||
@@ -107,47 +106,29 @@ abstract class MapObject {
|
||||
return CardinalDirection.west.radians;
|
||||
}
|
||||
|
||||
// Boss check
|
||||
if (id == bossHansGrosse) return 0.0;
|
||||
|
||||
final EnemyType? type = EnemyType.fromMapId(id);
|
||||
if (type == null) return 0.0;
|
||||
|
||||
// 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;
|
||||
// Because all enemies are in blocks of 4, modulo 4 gets the exact angle
|
||||
return CardinalDirection.fromEnemyIndex(id % 4).radians;
|
||||
}
|
||||
|
||||
/// Only handles the "Is this ID allowed on this difficulty?" math.
|
||||
/// Does NOT decide IF an object is an enemy or decoration.
|
||||
/// Determines if an object should be spawned on the current difficulty.
|
||||
static bool isDifficultyAllowed(int objId, Difficulty difficulty) {
|
||||
if (objId == 124) return true;
|
||||
// 1. Dead bodies always spawn
|
||||
if (objId == deadGuard || objId == deadAardwolf) return true;
|
||||
|
||||
int? requiredTier;
|
||||
|
||||
// Static Tier Math (IDs 23-54)
|
||||
if (objId >= 23 && objId <= 54) {
|
||||
requiredTier = (objId - 23) % 3;
|
||||
}
|
||||
// Patrol Tier Math (IDs 108-197)
|
||||
else if (objId >= 108 && objId <= 197) {
|
||||
int offsetInType = (objId - 108) % 18;
|
||||
requiredTier = offsetInType ~/ 4;
|
||||
}
|
||||
|
||||
if (requiredTier == null) {
|
||||
// Default to allowed if no tier logic exists
|
||||
// 2. If it's an enemy, we return true to let it pass through to the
|
||||
// Enemy.spawn factory. The factory will safely return null if the
|
||||
// enemy does not belong on this difficulty.
|
||||
if (EnemyType.fromMapId(objId) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int currentTier = switch (difficulty) {
|
||||
Difficulty.canIPlayDaddy || Difficulty.dontHurtMe => 0,
|
||||
Difficulty.bringEmOn => 1,
|
||||
Difficulty.iAmDeathIncarnate => 2,
|
||||
};
|
||||
|
||||
return requiredTier == currentTier;
|
||||
// 3. All non-enemy map objects (keys, ammo, puddles, plants)
|
||||
// are NOT difficulty-tiered. They always spawn.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,6 @@ abstract class Enemy extends Entity {
|
||||
required void Function(int damage) onDamagePlayer,
|
||||
});
|
||||
|
||||
/// Centralized factory to handle all enemy spawning logic
|
||||
static Enemy? spawn(
|
||||
int objId,
|
||||
double x,
|
||||
@@ -194,6 +193,7 @@ abstract class Enemy extends Entity {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// 124 (Dead Guard) famously overwrote a patrol ID in the original engine!
|
||||
if (objId == MapObject.deadGuard || objId == MapObject.deadAardwolf) {
|
||||
return null;
|
||||
}
|
||||
@@ -206,23 +206,20 @@ abstract class Enemy extends Entity {
|
||||
|
||||
final mapData = type.mapData;
|
||||
|
||||
// 1. Validate Difficulty and Determine State
|
||||
// ALL enemies have explicit directional angles!
|
||||
double spawnAngle = CardinalDirection.fromEnemyIndex(objId).radians;
|
||||
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
|
||||
spawnState = EntityState.idle;
|
||||
} else if (mapData.isAmbushForDifficulty(objId, difficulty)) {
|
||||
spawnState = EntityState.ambush; // Stays perfectly still until alerted
|
||||
spawnState = EntityState.ambush;
|
||||
} else {
|
||||
// If the ID belongs to this enemy but NOT this difficulty, skip spawning!
|
||||
return null;
|
||||
return null; // ID belongs to this enemy, but not on this difficulty
|
||||
}
|
||||
|
||||
// 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),
|
||||
|
||||
@@ -45,7 +45,7 @@ class EnemyAnimationMap {
|
||||
|
||||
enum EnemyType {
|
||||
guard(
|
||||
mapData: EnemyMapData(baseStaticId: 23, basePatrolId: 108),
|
||||
mapData: EnemyMapData(108),
|
||||
animations: EnemyAnimationMap(
|
||||
idle: SpriteFrameRange(50, 57),
|
||||
walking: SpriteFrameRange(58, 89),
|
||||
@@ -56,22 +56,18 @@ enum EnemyType {
|
||||
),
|
||||
),
|
||||
dog(
|
||||
mapData: EnemyMapData(baseStaticId: 26, basePatrolId: 126),
|
||||
// Translated from your old offset logic so the game doesn't break
|
||||
mapData: EnemyMapData(216),
|
||||
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
|
||||
pain: SpriteFrameRange(131, 131),
|
||||
dying: SpriteFrameRange(134, 136),
|
||||
dead: SpriteFrameRange(137, 137),
|
||||
),
|
||||
),
|
||||
ss(
|
||||
mapData: EnemyMapData(baseStaticId: 29, basePatrolId: 144),
|
||||
mapData: EnemyMapData(180),
|
||||
animations: EnemyAnimationMap(
|
||||
idle: SpriteFrameRange(138, 145),
|
||||
walking: SpriteFrameRange(146, 177),
|
||||
@@ -82,7 +78,7 @@ enum EnemyType {
|
||||
),
|
||||
),
|
||||
mutant(
|
||||
mapData: EnemyMapData(baseStaticId: 32, basePatrolId: 162),
|
||||
mapData: EnemyMapData(252),
|
||||
animations: EnemyAnimationMap(
|
||||
idle: SpriteFrameRange(187, 194),
|
||||
walking: SpriteFrameRange(195, 226),
|
||||
@@ -93,7 +89,7 @@ enum EnemyType {
|
||||
),
|
||||
),
|
||||
officer(
|
||||
mapData: EnemyMapData(baseStaticId: 35, basePatrolId: 180),
|
||||
mapData: EnemyMapData(144),
|
||||
animations: EnemyAnimationMap(
|
||||
idle: SpriteFrameRange(238, 245),
|
||||
walking: SpriteFrameRange(246, 277),
|
||||
|
||||
Reference in New Issue
Block a user