Add MVP campfire audio hooks

This commit is contained in:
2026-05-19 12:08:16 -07:00
parent 47cd7a5479
commit 280fa76af2
5 changed files with 140 additions and 1 deletions
+1 -1
View File
@@ -836,7 +836,7 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
- [x] Add ambient biome audio. Extended the placed `AAgrarianWeatherAudioController` so its ambient component explicitly owns a Ground Zero coastal-scrub biome loop slot with separate day/night volume targets, keeping the current MVP silent until placeholder or final audio assets are assigned while giving the map a real ambient audio attachment point.
- [x] Add footstep placeholders. Added native player-character footstep hooks with assignable walk, sprint, crouch, and prone sound slots plus movement-state cadence, keeping the MVP silent until placeholder or final surface-aware audio assets are assigned.
- [x] Add gathering sounds. Added spatialized resource-node gathering audio hooks with assignable normal/depleted gathering cues and a server-authoritative multicast trigger after successful harvests, keeping multiplayer clients aligned while remaining silent until audio assets are assigned.
- [ ] Add fire sounds.
- [x] Add fire sounds. Added campfire loop, ignition, and extinguish audio hooks with spatialized components, replicated lit-state loop control, and server-triggered multicast event cues so fire audio follows the authoritative campfire state once assets are assigned.
- [ ] Add unattended and poorly maintained fire risk for campfires and other open-flame sources.
- [ ] Add grass and forest ignition checks from irresponsible fire placement, wind/weather, dry fuel, nearby vegetation, and burn duration.
- [ ] Add shelter/structure ignition risk when fires are placed too close to primitive shelters, wood piles, flammable crafting stations, or settlement objects.
+7
View File
@@ -330,6 +330,13 @@ Each resource node exposes `GatheringSound` and `DepletedGatheringSound` slots,
with a spatialized `GatheringAudioComponent`; the system remains silent until
resource-specific placeholder or final cues are assigned.
Campfire audio is split between a persistent spatialized loop and short
server-triggered event cues. `AAgrarianCampfire` exposes `FireLoopSound`,
`IgniteSound`, and `ExtinguishSound` slots. Replicated lit-state updates start
or stop the loop on clients, while the authoritative server multicasts ignition
and extinguish events so the audio follows the same state changes as light,
smoke, warmth, fuel, and persistence.
Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`.
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
the same visual update path as natural fuel depletion.
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Verify MVP fire sound hooks follow authoritative campfire state."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FIRE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.h"
FIRE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianCampfire.cpp"
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
REQUIRED = {
FIRE_H: [
"TObjectPtr<UAudioComponent> FireLoopAudioComponent;",
"TObjectPtr<UAudioComponent> FireEventAudioComponent;",
"TObjectPtr<USoundBase> FireLoopSound;",
"TObjectPtr<USoundBase> IgniteSound;",
"TObjectPtr<USoundBase> ExtinguishSound;",
"UFUNCTION(NetMulticast, Unreliable)",
"void MulticastPlayFireEventSound(bool bIgnited);",
],
FIRE_CPP: [
"#include \"Components/AudioComponent.h\"",
"FireLoopAudioComponent = CreateDefaultSubobject<UAudioComponent>",
"FireEventAudioComponent = CreateDefaultSubobject<UAudioComponent>",
"FireLoopAudioComponent->bAllowSpatialization = true",
"FireEventAudioComponent->bAllowSpatialization = true",
"AAgrarianCampfire::MulticastPlayFireEventSound_Implementation",
"MulticastPlayFireEventSound(bLit);",
"FireLoopAudioComponent->Play();",
"FireLoopAudioComponent->Stop();",
],
TDD: [
"Campfire audio is split between a persistent spatialized loop",
"`FireLoopSound`",
"`IgniteSound`",
"`ExtinguishSound`",
],
ROADMAP: [
"[x] Add fire sounds.",
],
}
def main() -> None:
missing = []
for path, snippets in REQUIRED.items():
text = path.read_text(encoding="utf-8")
for snippet in snippets:
if snippet not in text:
missing.append(f"{path.relative_to(ROOT)} missing {snippet!r}")
if missing:
raise SystemExit("FAILED: " + "; ".join(missing))
print("OK: fire sound hooks follow authoritative campfire state.")
if __name__ == "__main__":
main()
+53
View File
@@ -7,6 +7,7 @@
#include "AgrarianPersistentActorComponent.h"
#include "AgrarianSurvivalComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/AudioComponent.h"
#include "Components/PointLightComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
@@ -108,6 +109,16 @@ AAgrarianCampfire::AAgrarianCampfire()
SmokeEffect->SetRelativeLocation(FVector(0.0f, 0.0f, 80.0f));
SmokeEffect->SetVisibility(false);
FireLoopAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("FireLoopAudioComponent"));
FireLoopAudioComponent->SetupAttachment(RootComponent);
FireLoopAudioComponent->bAutoActivate = false;
FireLoopAudioComponent->bAllowSpatialization = true;
FireEventAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("FireEventAudioComponent"));
FireEventAudioComponent->SetupAttachment(RootComponent);
FireEventAudioComponent->bAutoActivate = false;
FireEventAudioComponent->bAllowSpatialization = true;
PersistentActorComponent = CreateDefaultSubobject<UAgrarianPersistentActorComponent>(TEXT("PersistentActorComponent"));
PersistentActorComponent->ActorTypeId = TEXT("campfire");
}
@@ -281,6 +292,23 @@ void AAgrarianCampfire::OnRep_FireState()
UpdateVisualState();
}
void AAgrarianCampfire::MulticastPlayFireEventSound_Implementation(bool bIgnited)
{
if (GetNetMode() == NM_DedicatedServer || !FireEventAudioComponent)
{
return;
}
USoundBase* EventSound = bIgnited ? IgniteSound : ExtinguishSound;
if (!EventSound)
{
return;
}
FireEventAudioComponent->SetSound(EventSound);
FireEventAudioComponent->Play();
}
EAgrarianWeatherType AAgrarianCampfire::GetCurrentWeather() const
{
if (const UWorld* World = GetWorld())
@@ -296,11 +324,17 @@ EAgrarianWeatherType AAgrarianCampfire::GetCurrentWeather() const
void AAgrarianCampfire::SetLit(bool bNewLit)
{
const bool bChanged = bLit != bNewLit;
if (bLit != bNewLit)
{
bLit = bNewLit;
}
if (HasAuthority() && bChanged)
{
MulticastPlayFireEventSound(bLit);
}
UpdateVisualState();
}
@@ -323,6 +357,25 @@ void AAgrarianCampfire::UpdateVisualState()
SmokeEffect->DeactivateSystem();
}
}
if (FireLoopAudioComponent)
{
if (bLit && FireLoopSound)
{
if (FireLoopAudioComponent->Sound != FireLoopSound)
{
FireLoopAudioComponent->SetSound(FireLoopSound);
}
if (!FireLoopAudioComponent->IsPlaying())
{
FireLoopAudioComponent->Play();
}
}
else if (FireLoopAudioComponent->IsPlaying())
{
FireLoopAudioComponent->Stop();
}
}
}
void AAgrarianCampfire::WarmNearbyCharacters(float DeltaSeconds)
+20
View File
@@ -12,6 +12,8 @@
class UPointLightComponent;
class UParticleSystemComponent;
class UAgrarianPersistentActorComponent;
class UAudioComponent;
class USoundBase;
class UStaticMeshComponent;
UCLASS(Blueprintable)
@@ -49,6 +51,12 @@ public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Fire|Effects")
TObjectPtr<UParticleSystemComponent> SmokeEffect;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Fire|Audio")
TObjectPtr<UAudioComponent> FireLoopAudioComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Fire|Audio")
TObjectPtr<UAudioComponent> FireEventAudioComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Fire|Persistence")
TObjectPtr<UAgrarianPersistentActorComponent> PersistentActorComponent;
@@ -85,6 +93,15 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Weather")
bool bWetWeatherCanExtinguish = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Audio")
TObjectPtr<USoundBase> FireLoopSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Audio")
TObjectPtr<USoundBase> IgniteSound;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Fire|Audio")
TObjectPtr<USoundBase> ExtinguishSound;
virtual FText GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const override;
virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override;
virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override;
@@ -113,6 +130,9 @@ protected:
UFUNCTION()
void OnRep_FireState();
UFUNCTION(NetMulticast, Unreliable)
void MulticastPlayFireEventSound(bool bIgnited);
EAgrarianWeatherType GetCurrentWeather() const;
void SetLit(bool bNewLit);
void UpdateVisualState();