Re-added the sprite screen. Made some adjustments to enemy AI.
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||
|
||||
class DeadAardwolf extends Decorative {
|
||||
static const int sprite = 96;
|
||||
|
||||
DeadAardwolf({required super.x, required super.y})
|
||||
: super(spriteIndex: sprite, state: EntityState.staticObj, mapId: 125);
|
||||
|
||||
/// This is the self-spawning logic we discussed.
|
||||
/// It only claims the ID 124.
|
||||
static DeadAardwolf? trySpawn(
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
if (objId == 125) {
|
||||
return DeadAardwolf(x: x, y: y);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
|
||||
|
||||
class DeadGuard extends Decorative {
|
||||
/// The sprite index in VSWAP for the final "dead" frame of a Guard.
|
||||
static const int deadGuardSprite = 95;
|
||||
|
||||
DeadGuard({required super.x, required super.y})
|
||||
: super(
|
||||
spriteIndex: deadGuardSprite,
|
||||
state: EntityState.staticObj,
|
||||
// We set mapId to 124 so we can identify it if needed
|
||||
mapId: 124,
|
||||
);
|
||||
|
||||
/// This is the self-spawning logic we discussed.
|
||||
/// It only claims the ID 124.
|
||||
static DeadGuard? trySpawn(
|
||||
int objId,
|
||||
double x,
|
||||
double y,
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
if (objId == 124) {
|
||||
return DeadGuard(x: x, y: y);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@ class Decorative extends Entity {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// 2. Standard props (Table, Lamp, etc) use the tiered check
|
||||
if (!MapObject.isDifficultyAllowed(objId, difficulty)) return null;
|
||||
|
||||
if (isDecoration(objId)) {
|
||||
return Decorative(
|
||||
x: x,
|
||||
|
||||
@@ -11,31 +11,11 @@ import 'package:wolf_3d_entities/src/entity.dart';
|
||||
enum EnemyAnimation { idle, walking, attacking, pain, dying, dead }
|
||||
|
||||
enum EnemyType {
|
||||
guard(
|
||||
staticId: 108,
|
||||
patrolId: 124,
|
||||
spriteBaseIdx: 50,
|
||||
), // 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
|
||||
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;
|
||||
@@ -47,19 +27,18 @@ enum EnemyType {
|
||||
required this.spriteBaseIdx,
|
||||
});
|
||||
|
||||
/// Wolfenstein 3D allocates blocks of 16 IDs per enemy type for standing and patrolling
|
||||
/// (4 directions x 4 difficulty levels = 16 IDs)
|
||||
/// Checks if the ID belongs to this enemy type range
|
||||
bool claimsMapId(int id) {
|
||||
bool isStatic = id >= staticId && id < staticId + 16;
|
||||
bool isPatrol = id >= patrolId && id < patrolId + 16;
|
||||
// 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;
|
||||
}
|
||||
if (type.claimsMapId(id)) return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -200,6 +179,7 @@ abstract class Enemy extends Entity {
|
||||
int damage = 10;
|
||||
bool isDying = false;
|
||||
bool hasDroppedItem = false;
|
||||
bool isAlerted = false;
|
||||
|
||||
// Replaces ob->temp2 for reaction delays
|
||||
int reactionTimeMs = 0;
|
||||
@@ -210,6 +190,8 @@ abstract class Enemy extends Entity {
|
||||
health -= amount;
|
||||
lastActionTime = currentTime;
|
||||
|
||||
isAlerted = true;
|
||||
|
||||
if (health <= 0) {
|
||||
state = EntityState.dead;
|
||||
isDying = true;
|
||||
@@ -227,15 +209,19 @@ abstract class Enemy extends Entity {
|
||||
int baseReactionMs = 200,
|
||||
int reactionVarianceMs = 600,
|
||||
}) {
|
||||
if (state == EntityState.idle &&
|
||||
hasLineOfSight(playerPosition, isWalkable)) {
|
||||
if (!isAlerted && hasLineOfSight(playerPosition, isWalkable)) {
|
||||
if (reactionTimeMs == 0) {
|
||||
reactionTimeMs =
|
||||
elapsedMs +
|
||||
baseReactionMs +
|
||||
math.Random().nextInt(reactionVarianceMs);
|
||||
} else if (elapsedMs >= reactionTimeMs) {
|
||||
state = EntityState.patrolling;
|
||||
isAlerted = true;
|
||||
|
||||
if (state == EntityState.idle) {
|
||||
state = EntityState.patrolling;
|
||||
}
|
||||
|
||||
lastActionTime = elapsedMs;
|
||||
reactionTimeMs = 0;
|
||||
}
|
||||
@@ -363,16 +349,20 @@ abstract class Enemy extends Entity {
|
||||
Difficulty difficulty, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// 1. Check Difficulty & Compatibility
|
||||
if (!MapObject.shouldSpawn(objId, difficulty)) return null;
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Explicitly ignore "Dead Guard" Map ID so Decorative.trySpawn can handle it
|
||||
// In Wolf3D, 124 is the dead guard.
|
||||
if (objId == 124) return null;
|
||||
if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the checkbox is checked, block non-Shareware enemies
|
||||
if (isSharewareMode && !MapObject.isSharewareCompatible(objId)) 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;
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@ class Guard extends Enemy {
|
||||
double distance = position.distanceTo(playerPosition);
|
||||
double angleToPlayer = position.angleTo(playerPosition);
|
||||
|
||||
if (isAlerted && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
|
||||
if (state != EntityState.idle && state != EntityState.dead) {
|
||||
newAngle = angleToPlayer;
|
||||
}
|
||||
@@ -67,9 +71,11 @@ class Guard extends Enemy {
|
||||
);
|
||||
|
||||
if (state == EntityState.patrolling) {
|
||||
if (distance > 0.8) {
|
||||
double moveX = math.cos(angleToPlayer) * speed;
|
||||
double moveY = math.sin(angleToPlayer) * speed;
|
||||
if (!isAlerted || distance > 0.8) {
|
||||
double currentMoveAngle = isAlerted ? angleToPlayer : angle;
|
||||
double moveX = math.cos(currentMoveAngle) * speed;
|
||||
double moveY = math.sin(currentMoveAngle) * speed;
|
||||
|
||||
movement = getValidMovement(
|
||||
Coordinate2D(moveX, moveY),
|
||||
isWalkable,
|
||||
@@ -77,7 +83,7 @@ class Guard extends Enemy {
|
||||
);
|
||||
}
|
||||
|
||||
if (distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||
if (isAlerted && distance < 6.0 && elapsedMs - lastActionTime > 1500) {
|
||||
if (hasLineOfSight(playerPosition, isWalkable)) {
|
||||
state = EntityState.attacking;
|
||||
lastActionTime = elapsedMs;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/collectible.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/decorations/dead_aardwolf.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/decorations/dead_guard.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/decorative.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/enemies/bosses/hans_grosse.dart';
|
||||
import 'package:wolf_3d_entities/src/entities/enemies/enemy.dart';
|
||||
@@ -16,15 +18,21 @@ typedef EntitySpawner =
|
||||
|
||||
abstract class EntityRegistry {
|
||||
static final List<EntitySpawner> _spawners = [
|
||||
// Special
|
||||
DeadGuard.trySpawn,
|
||||
DeadAardwolf.trySpawn,
|
||||
|
||||
// Bosses
|
||||
HansGrosse.trySpawn,
|
||||
|
||||
// Decorations
|
||||
Decorative.trySpawn,
|
||||
|
||||
// Enemies need to try to spawn first
|
||||
Enemy.spawn,
|
||||
|
||||
// Everything else
|
||||
// Collectables
|
||||
Collectible.trySpawn,
|
||||
Decorative.trySpawn,
|
||||
];
|
||||
|
||||
static Entity? spawn(
|
||||
@@ -35,12 +43,6 @@ abstract class EntityRegistry {
|
||||
int maxSprites, {
|
||||
bool isSharewareMode = false,
|
||||
}) {
|
||||
// 1. Difficulty check before even looking for a spawner
|
||||
if (!MapObject.shouldSpawn(objId, difficulty)) return null;
|
||||
|
||||
// If the checkbox is checked, block non-Shareware enemies
|
||||
if (isSharewareMode && !MapObject.isSharewareCompatible(objId)) return null;
|
||||
|
||||
if (objId == 0) return null;
|
||||
|
||||
for (final spawner in _spawners) {
|
||||
@@ -51,23 +53,8 @@ abstract class EntityRegistry {
|
||||
difficulty,
|
||||
isSharewareMode: isSharewareMode,
|
||||
);
|
||||
|
||||
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) {
|
||||
print("Spawned entity with objId $objId");
|
||||
return entity;
|
||||
}
|
||||
print("VSWAP doesn't have this sprite! objId $objId");
|
||||
return null; // VSWAP doesn't have this sprite!
|
||||
}
|
||||
if (entity != null) return entity;
|
||||
}
|
||||
print("No class claimed this Map ID > objId $objId");
|
||||
return null; // No class claimed this Map ID
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user