feat: Implement chaingun pickup face animation and update HUD rendering logic
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -26,6 +26,16 @@ class Player {
|
||||
double bonusFlash = 0.0; // 0.0 is none, 1.0 is maximum white
|
||||
final double bonusFlashFadeSpeed = 0.05; // How fast it fades per tick
|
||||
|
||||
// Chaingun pickup face (classic GOTGATLINGPIC)
|
||||
int _chaingunPickupFaceMsRemaining = 0;
|
||||
static const int _chaingunPickupFaceDurationMs = 900;
|
||||
|
||||
// Classic face animation (UpdateFace/FACETICS random glance frames)
|
||||
math.Random _faceRng = math.Random(0);
|
||||
int _faceFrame = 0;
|
||||
double _faceCountTics = 0.0;
|
||||
int _nextFaceChangeThreshold = 0;
|
||||
|
||||
// Inventory
|
||||
bool hasGoldKey = false;
|
||||
bool hasSilverKey = false;
|
||||
@@ -52,11 +62,22 @@ class Player {
|
||||
|
||||
Player({required this.x, required this.y, required this.angle}) {
|
||||
currentWeapon = weapons[WeaponType.pistol]!;
|
||||
setHudFaceAnimationSeed(0);
|
||||
}
|
||||
|
||||
// Helper getter to interface with the RaycasterPainter
|
||||
Coordinate2D get position => Coordinate2D(x, y);
|
||||
|
||||
bool get isChaingunPickupFaceActive => _chaingunPickupFaceMsRemaining > 0;
|
||||
int get hudFaceFrame => _faceFrame;
|
||||
|
||||
void setHudFaceAnimationSeed(int seed) {
|
||||
_faceRng = math.Random(seed);
|
||||
_faceFrame = 0;
|
||||
_faceCountTics = 0.0;
|
||||
_nextFaceChangeThreshold = _faceRng.nextInt(256);
|
||||
}
|
||||
|
||||
// --- General Update ---
|
||||
|
||||
void tick(Duration elapsed) {
|
||||
@@ -70,6 +91,24 @@ class Player {
|
||||
bonusFlash = math.max(0.0, bonusFlash - bonusFlashFadeSpeed);
|
||||
}
|
||||
|
||||
if (_chaingunPickupFaceMsRemaining > 0) {
|
||||
_chaingunPickupFaceMsRemaining = math.max(
|
||||
0,
|
||||
_chaingunPickupFaceMsRemaining - elapsed.inMilliseconds,
|
||||
);
|
||||
} else {
|
||||
_faceCountTics += (elapsed.inMilliseconds * 70) / 1000.0;
|
||||
if (_faceCountTics > _nextFaceChangeThreshold) {
|
||||
int nextFrame = _faceRng.nextInt(4);
|
||||
if (nextFrame == 3) {
|
||||
nextFrame = 1;
|
||||
}
|
||||
_faceFrame = nextFrame;
|
||||
_faceCountTics = 0.0;
|
||||
_nextFaceChangeThreshold = _faceRng.nextInt(256);
|
||||
}
|
||||
}
|
||||
|
||||
updateWeaponSwitch();
|
||||
}
|
||||
|
||||
@@ -123,6 +162,7 @@ class Player {
|
||||
// Spike the damage flash based on how much damage was taken
|
||||
// A 10 damage hit gives a 0.5 flash, a 20 damage hit maxes it out at 1.0
|
||||
damageFlash = math.min(1.0, damageFlash + (damage * 0.05));
|
||||
_chaingunPickupFaceMsRemaining = 0;
|
||||
|
||||
if (health <= 0) {
|
||||
log("[PLAYER] Died! Final Score: $score");
|
||||
@@ -137,12 +177,19 @@ class Player {
|
||||
log("[PLAYER] Healed for $amount ($health -> $newHealth)");
|
||||
}
|
||||
health = newHealth;
|
||||
_chaingunPickupFaceMsRemaining = 0;
|
||||
}
|
||||
|
||||
void triggerBonusFlash() {
|
||||
bonusFlash = 1.0;
|
||||
}
|
||||
|
||||
void triggerChaingunPickupFace({int? durationMs}) {
|
||||
_chaingunPickupFaceMsRemaining = durationMs == null
|
||||
? _chaingunPickupFaceDurationMs
|
||||
: math.max(0, durationMs);
|
||||
}
|
||||
|
||||
void addAmmo(int amount) {
|
||||
final int newAmmo = math.min(99, ammo + amount);
|
||||
if (ammo < 99) {
|
||||
@@ -214,7 +261,10 @@ class Player {
|
||||
}
|
||||
|
||||
if (weaponType == WeaponType.machineGun) hasMachineGun = true;
|
||||
if (weaponType == WeaponType.chainGun) hasChainGun = true;
|
||||
if (weaponType == WeaponType.chainGun) {
|
||||
hasChainGun = true;
|
||||
triggerChaingunPickupFace();
|
||||
}
|
||||
|
||||
log("[PLAYER] Collected ${weaponType.name}.");
|
||||
}
|
||||
|
||||
@@ -822,6 +822,14 @@ class WolfEngine {
|
||||
player = Player(x: 1.5, y: 1.5, angle: 0.0);
|
||||
}
|
||||
|
||||
if (!preservePlayerState) {
|
||||
final int faceSeed =
|
||||
((_currentEpisodeIndex + 1) * 1000) +
|
||||
((_currentLevelIndex + 1) * 10) +
|
||||
(_currentGameIndex + 1);
|
||||
player.setHudFaceAnimationSeed(faceSeed);
|
||||
}
|
||||
|
||||
// Sanitize the level grid to ensure only valid walls/doors remain
|
||||
for (int y = 0; y < 64; y++) {
|
||||
for (int x = 0; x < 64; x++) {
|
||||
@@ -1095,6 +1103,11 @@ class WolfEngine {
|
||||
if (player.position.distanceTo(entity.position) < 0.5) {
|
||||
final pickupSoundEffect = player.tryPickup(entity);
|
||||
if (pickupSoundEffect != null) {
|
||||
if (pickupSoundEffect == SoundEffect.getChainGun) {
|
||||
player.triggerChaingunPickupFace(
|
||||
durationMs: _soundEffectDurationMs(pickupSoundEffect),
|
||||
);
|
||||
}
|
||||
audio.playSoundEffect(pickupSoundEffect);
|
||||
itemsToRemove.add(entity);
|
||||
}
|
||||
@@ -1154,6 +1167,23 @@ class WolfEngine {
|
||||
return area >= 0 && area < _areasByPlayer.length && _areasByPlayer[area];
|
||||
}
|
||||
|
||||
int _soundEffectDurationMs(SoundEffect effect) {
|
||||
final int slotIndex =
|
||||
data.registry.sfx.resolve(effect)?.slotIndex ??
|
||||
effect.idFor(data.version);
|
||||
if (slotIndex < 0 || slotIndex >= data.sounds.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int sampleCount = data.sounds[slotIndex].bytes.length;
|
||||
if (sampleCount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Digitized Wolf3D effects are played as 8-bit mono PCM at 7000Hz.
|
||||
return ((sampleCount * 1000) / 7000).round();
|
||||
}
|
||||
|
||||
void _buildFallbackAreasIfNeeded() {
|
||||
int maxArea = -1;
|
||||
for (int y = 0; y < 64; y++) {
|
||||
|
||||
@@ -44,6 +44,7 @@ class RetailHudModule extends HudModule {
|
||||
HudKey.faceDying: 121,
|
||||
HudKey.faceNearDeath: 124,
|
||||
HudKey.faceDead: 127,
|
||||
HudKey.faceGotGatling: 128,
|
||||
// Weapon icons.
|
||||
HudKey.pistolIcon: 89,
|
||||
HudKey.machineGunIcon: 90,
|
||||
|
||||
@@ -44,6 +44,7 @@ class SharewareHudModule extends HudModule {
|
||||
HudKey.faceDying: 121,
|
||||
HudKey.faceNearDeath: 124,
|
||||
HudKey.faceDead: 127,
|
||||
HudKey.faceGotGatling: 128,
|
||||
HudKey.pistolIcon: 89,
|
||||
HudKey.machineGunIcon: 90,
|
||||
HudKey.chainGunIcon: 91,
|
||||
|
||||
@@ -37,6 +37,7 @@ class SpearDemoHudModule extends HudModule {
|
||||
HudKey.faceDying: 109,
|
||||
HudKey.faceNearDeath: 112,
|
||||
HudKey.faceDead: 122, // BJOUCHPIC
|
||||
HudKey.faceGotGatling: 116, // GOTGATLINGPIC
|
||||
HudKey.pistolIcon: 77, // GUNPIC
|
||||
HudKey.machineGunIcon: 78, // MACHINEGUNPIC
|
||||
HudKey.chainGunIcon: 79, // GATLINGGUNPIC
|
||||
|
||||
@@ -24,6 +24,7 @@ enum HudKey {
|
||||
faceDying('faceDying'), // health 5-20
|
||||
faceNearDeath('faceNearDeath'), // health 1-4
|
||||
faceDead('faceDead'), // health <= 0
|
||||
faceGotGatling('faceGotGatling'),
|
||||
|
||||
// --- Weapon icons ---
|
||||
pistolIcon('pistolIcon'),
|
||||
|
||||
@@ -222,10 +222,25 @@ abstract class RendererBackend<T>
|
||||
}
|
||||
|
||||
void _drawHudFace(WolfEngine engine, List<VgaImage> vgaImages) {
|
||||
final faceRef = engine.data.registry.hud.faceForHealth(
|
||||
engine.player.health,
|
||||
);
|
||||
final int faceIndex = faceRef?.vgaIndex ?? -1;
|
||||
int faceIndex = -1;
|
||||
|
||||
if (engine.player.isChaingunPickupFaceActive) {
|
||||
faceIndex =
|
||||
engine.data.registry.hud.resolve(HudKey.faceGotGatling)?.vgaIndex ??
|
||||
-1;
|
||||
} else {
|
||||
final HudKey faceKey = engine.data.registry.hud.faceKeyForHealth(
|
||||
engine.player.health,
|
||||
);
|
||||
final int baseIndex =
|
||||
engine.data.registry.hud.resolve(faceKey)?.vgaIndex ?? -1;
|
||||
if (baseIndex >= 0) {
|
||||
faceIndex = faceKey == HudKey.faceDead
|
||||
? baseIndex
|
||||
: baseIndex + engine.player.hudFaceFrame;
|
||||
}
|
||||
}
|
||||
|
||||
if (faceIndex >= 0 && faceIndex < vgaImages.length) {
|
||||
blitHudVgaImage(vgaImages[faceIndex], 136, 164);
|
||||
}
|
||||
|
||||
@@ -190,6 +190,10 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
? normalExitColor
|
||||
: objTile == MapObject.secretExitTrigger
|
||||
? secretExitColor
|
||||
: objTile == MapObject.goldKey
|
||||
? goldKeyColor
|
||||
: objTile == MapObject.silverKey
|
||||
? silverKeyColor
|
||||
: (wallTile == 0
|
||||
? floorColor
|
||||
: (wallTile >= 90 ? doorColor : wallColor));
|
||||
|
||||
Reference in New Issue
Block a user