Aliens that actually shoot
BTTask_AttackBat has been a stub for about three weeks. Military aliens would spot the bat, run their alert behavior, line up in firing posture, kick off the attack task, and then nothing would happen. The bat would just chill. It was the world’s politest combat encounter and I let it sit in the backlog longer than I should have because the rest of the loop felt more urgent. Got to it.
AEchoProjectile is the new actor: sphere collision, ProjectileMovementComponent, a UNiagaraComponent for the trail, and an IAbilitySystemInterface hookup so it can actually deal damage. The attack task is properly latent now: wind-up, fire (with optional target leading, because a stationary bat is the easiest target in the world), recovery, finish. It respects the rediscovery grace window, so no cheap shots while the alien is still squinting into the rafters trying to decide if it actually saw something.
The damage value used to be hardcoded at -25, which always made me feel a little ill. Now it rides in via SetByCaller on the Data.Damage tag, so each projectile carries its own number.
And then the health bar didn’t move.
It would sit at 100% through three or four hits and then drop straight to 0 on the kill shot. Took most of an afternoon to untangle, and it turned out to be two GAS bugs stacked on top of each other, both of which I deserve.
First one: PostGameplayEffectExecute was reading GetBaseValue() after the effect had already applied. So old-value and new-value were the same number, the broadcast guard suppressed every update, and the only time the delegate fired was on overkill, because clamping at zero made the values disagree again. Read the docs three times before I saw it. The fourth time I was annoyed enough to actually retain it.
Second one is worse: even after I moved the capture into PreAttributeChange, GAS was calling that function twice per Instant GE. Once for the modify, once for the post-write aggregator recompute. The second call overwrote my captured old-value with the new value, putting me right back where I started. I yelled. Guard fix: only capture when !FMath::IsNearlyEqual(CurrentHealth, NewValue).
After both, the bar moves on every hit and the death handler fires. While I was already deep in HUD code I also wired things up the way I should have weeks ago. HUDWidgetClass exposed on the controller, BeginPlay creating and registering the widget, SetHUDWidget pushing initial Health/MaxHealth so the bar doesn’t start blank for a frame. Small thing. Bugged me every playtest.
Two things I’m leaving for next week.
There’s a phantom Niagara trail sitting slightly to the right of the actual projectile, like the projectile has a ghost. Almost certainly a duplicate component in the BP that I’ll spot the second I open it. Didn’t want to context-switch into BP after that GAS afternoon.
Military aliens stop moving after their first attack. Nothing in this PR touches CharacterMovement and the projectile is QueryOnly, so it’s not me. Probably a BT flow issue where the attack branch isn’t returning control cleanly. The BT debugger and a Wait task swap will pin it. Future-me’s problem.
The thing that surprised me is how much sharper the night reads now that taking a hit is real. The bat used to fly around like it was on vacation. It doesn’t anymore.