feat: Implement difficulty scaling for enemy damage and enhance enemy behaviors

- Added `scaleIncomingEnemyDamage` method to `Difficulty` enum for handling damage scaling based on difficulty level.
- Introduced `lives` attribute to `Player` class with methods to manage lives.
- Updated enemy classes (`Dog`, `Guard`, `Mutant`, `Officer`, `SS`, `HansGrosse`) to utilize difficulty settings for health and damage calculations.
- Modified enemy drop logic to reflect new collectible types and behaviors.
- Created tests for verifying enemy drop parity, collectible values, and difficulty damage scaling.
- Adjusted enemy spawn logic to ensure standing enemies remain idle until alerted.
- Enhanced scoring system to reflect updated score values for different enemy types.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-19 16:02:36 +01:00
parent 8bf0dbd57c
commit 225840f3ee
17 changed files with 644 additions and 63 deletions

View File

@@ -15,6 +15,7 @@ class Player {
int health = 100;
int ammo = 8;
int score = 0;
int lives = 3;
// Damage flash
double damageFlash = 0.0; // 0.0 is none, 1.0 is maximum red
@@ -135,6 +136,10 @@ class Player {
ammo = newAmmo;
}
void addLives(int amount) {
lives = math.min(9, lives + amount);
}
/// Attempts to collect [item] and returns the SFX to play.
///
/// Returns `null` when the item was not collected (for example: full health).
@@ -164,6 +169,10 @@ class Player {
score += effect.scoreToAdd;
}
if (effect.extraLivesToAdd > 0) {
addLives(effect.extraLivesToAdd);
}
if (effect.grantGoldKey) {
hasGoldKey = true;
}

View File

@@ -125,6 +125,8 @@ class WolfEngine {
List<Entity> entities = [];
int _currentEpisodeIndex = 0;
bool _isPlayerMovingFast = false;
int _currentLevelIndex = 0;
/// Stores the previous level index when entering a secret floor,
@@ -213,6 +215,10 @@ class WolfEngine {
inputResult.movement,
);
// RUN key does not exist yet in current input model. Keep canonical hit
// chance in "walking" mode until true run-state input is implemented.
_isPlayerMovingFast = false;
player.x = validatedPos.x;
player.y = validatedPos.y;
@@ -533,9 +539,14 @@ class WolfEngine {
elapsedMs: _timeAliveMs,
elapsedDeltaMs: elapsed.inMilliseconds,
playerPosition: player.position,
playerAngle: player.angle,
isPlayerRunning: _isPlayerMovingFast,
isWalkable: isWalkable,
tryOpenDoor: doorManager.tryOpenDoor,
onDamagePlayer: (int damage) => player.takeDamage(damage),
onDamagePlayer: (int damage) {
final difficultyMode = difficulty ?? Difficulty.medium;
player.takeDamage(difficultyMode.scaleIncomingEnemyDamage(damage));
},
onPlaySound: audio.playSoundEffect,
);
@@ -561,14 +572,25 @@ class WolfEngine {
entity.isDying &&
!entity.hasDroppedItem) {
entity.hasDroppedItem = true;
Entity? droppedAmmo = EntityRegistry.spawn(
MapObject.ammoClip,
entity.x,
entity.y,
difficulty!,
data.sprites.length,
);
if (droppedAmmo != null) itemsToAdd.add(droppedAmmo);
Entity? droppedItem;
if (entity is Dog) {
droppedItem = null;
} else if (entity is SS) {
droppedItem = !player.hasMachineGun
? WeaponCollectible(
x: entity.x,
y: entity.y,
mapId: MapObject.machineGun,
)
: SmallAmmoCollectible(x: entity.x, y: entity.y);
} else if (entity is Guard || entity is Officer || entity is Mutant) {
droppedItem = SmallAmmoCollectible(x: entity.x, y: entity.y);
}
if (droppedItem != null) {
itemsToAdd.add(droppedItem);
}
}
} else if (entity is Collectible) {
if (player.position.distanceTo(entity.position) < 0.5) {