Add MVP gathering audio hooks
This commit is contained in:
@@ -835,7 +835,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 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 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.
|
||||||
- [ ] Add gathering sounds.
|
- [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.
|
- [ ] Add fire sounds.
|
||||||
- [ ] Add unattended and poorly maintained fire risk for campfires and other open-flame sources.
|
- [ ] 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 grass and forest ignition checks from irresponsible fire placement, wind/weather, dry fuel, nearby vegetation, and burn duration.
|
||||||
|
|||||||
@@ -323,6 +323,13 @@ slots. Cadence is state-aware and driven by horizontal movement, so the MVP can
|
|||||||
remain silent until placeholder or final surface-aware cues are assigned while
|
remain silent until placeholder or final surface-aware cues are assigned while
|
||||||
still giving designers a real hook for step audio in packaged builds.
|
still giving designers a real hook for step audio in packaged builds.
|
||||||
|
|
||||||
|
Gathering audio is owned by `AAgrarianResourceNode`. Successful
|
||||||
|
server-authoritative harvests call a multicast placeholder cue so nearby
|
||||||
|
clients can hear the action without trusting client-side gathering requests.
|
||||||
|
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.
|
||||||
|
|
||||||
Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`.
|
Campfires expose native extinguish logic through `AAgrarianCampfire::Extinguish`.
|
||||||
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
|
Extinguishing clears remaining fuel, turns off replicated lit state, and reuses
|
||||||
the same visual update path as natural fuel depletion.
|
the same visual update path as natural fuel depletion.
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify MVP gathering sound hooks are server-triggered and multicast."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
RESOURCE_H = ROOT / "Source" / "AgrarianGame" / "AgrarianResourceNode.h"
|
||||||
|
RESOURCE_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianResourceNode.cpp"
|
||||||
|
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"
|
||||||
|
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||||
|
|
||||||
|
REQUIRED = {
|
||||||
|
RESOURCE_H: [
|
||||||
|
"TObjectPtr<UAudioComponent> GatheringAudioComponent;",
|
||||||
|
"TObjectPtr<USoundBase> GatheringSound;",
|
||||||
|
"TObjectPtr<USoundBase> DepletedGatheringSound;",
|
||||||
|
"UFUNCTION(NetMulticast, Unreliable)",
|
||||||
|
"void MulticastPlayGatheringSound(bool bDepletedAfterGather);",
|
||||||
|
],
|
||||||
|
RESOURCE_CPP: [
|
||||||
|
"#include \"Components/AudioComponent.h\"",
|
||||||
|
"GatheringAudioComponent = CreateDefaultSubobject<UAudioComponent>",
|
||||||
|
"GatheringAudioComponent->bAllowSpatialization = true",
|
||||||
|
"MulticastPlayGatheringSound(RemainingHarvests <= 0);",
|
||||||
|
"AAgrarianResourceNode::MulticastPlayGatheringSound_Implementation",
|
||||||
|
"GetNetMode() == NM_DedicatedServer",
|
||||||
|
"GatheringAudioComponent->Play();",
|
||||||
|
],
|
||||||
|
TDD: [
|
||||||
|
"Gathering audio is owned by `AAgrarianResourceNode`",
|
||||||
|
"server-authoritative harvests call a multicast placeholder cue",
|
||||||
|
"`GatheringSound` and `DepletedGatheringSound`",
|
||||||
|
],
|
||||||
|
ROADMAP: [
|
||||||
|
"[x] Add gathering 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: gathering sound hooks are server-triggered and multicast.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "AgrarianInventoryComponent.h"
|
#include "AgrarianInventoryComponent.h"
|
||||||
#include "AgrarianItemDefinitionAsset.h"
|
#include "AgrarianItemDefinitionAsset.h"
|
||||||
#include "AgrarianSaveGame.h"
|
#include "AgrarianSaveGame.h"
|
||||||
|
#include "Components/AudioComponent.h"
|
||||||
#include "Components/StaticMeshComponent.h"
|
#include "Components/StaticMeshComponent.h"
|
||||||
#include "Engine/StaticMesh.h"
|
#include "Engine/StaticMesh.h"
|
||||||
#include "Materials/MaterialInterface.h"
|
#include "Materials/MaterialInterface.h"
|
||||||
@@ -67,6 +68,11 @@ AAgrarianResourceNode::AAgrarianResourceNode()
|
|||||||
HarvestableMarkerProxy->SetRelativeLocation(FVector(0.0f, 22.0f, 36.0f));
|
HarvestableMarkerProxy->SetRelativeLocation(FVector(0.0f, 22.0f, 36.0f));
|
||||||
HarvestableMarkerProxy->SetRelativeScale3D(FVector(0.28f, 0.18f, 0.22f));
|
HarvestableMarkerProxy->SetRelativeScale3D(FVector(0.28f, 0.18f, 0.22f));
|
||||||
|
|
||||||
|
GatheringAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("GatheringAudioComponent"));
|
||||||
|
GatheringAudioComponent->SetupAttachment(RootComponent);
|
||||||
|
GatheringAudioComponent->bAutoActivate = false;
|
||||||
|
GatheringAudioComponent->bAllowSpatialization = true;
|
||||||
|
|
||||||
YieldItem.ItemId = TEXT("wood");
|
YieldItem.ItemId = TEXT("wood");
|
||||||
YieldItem.DisplayName = FText::FromString(TEXT("Wood"));
|
YieldItem.DisplayName = FText::FromString(TEXT("Wood"));
|
||||||
YieldItem.Quantity = 1;
|
YieldItem.Quantity = 1;
|
||||||
@@ -134,6 +140,7 @@ void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Inte
|
|||||||
if (Inventory->AddItem(Granted))
|
if (Inventory->AddItem(Granted))
|
||||||
{
|
{
|
||||||
RemainingHarvests--;
|
RemainingHarvests--;
|
||||||
|
MulticastPlayGatheringSound(RemainingHarvests <= 0);
|
||||||
UpdateDepletedState();
|
UpdateDepletedState();
|
||||||
ScheduleRespawnIfNeeded();
|
ScheduleRespawnIfNeeded();
|
||||||
}
|
}
|
||||||
@@ -145,6 +152,23 @@ void AAgrarianResourceNode::OnRep_RemainingHarvests()
|
|||||||
UpdateDepletedState();
|
UpdateDepletedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AAgrarianResourceNode::MulticastPlayGatheringSound_Implementation(bool bDepletedAfterGather)
|
||||||
|
{
|
||||||
|
if (GetNetMode() == NM_DedicatedServer || !GatheringAudioComponent)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
USoundBase* SoundToPlay = (bDepletedAfterGather && DepletedGatheringSound) ? DepletedGatheringSound : GatheringSound;
|
||||||
|
if (!SoundToPlay)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GatheringAudioComponent->SetSound(SoundToPlay);
|
||||||
|
GatheringAudioComponent->Play();
|
||||||
|
}
|
||||||
|
|
||||||
FName AAgrarianResourceNode::GetResourcePersistenceId() const
|
FName AAgrarianResourceNode::GetResourcePersistenceId() const
|
||||||
{
|
{
|
||||||
return PersistenceNodeId != NAME_None ? PersistenceNodeId : GetFName();
|
return PersistenceNodeId != NAME_None ? PersistenceNodeId : GetFName();
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "AgrarianResourceNode.generated.h"
|
#include "AgrarianResourceNode.generated.h"
|
||||||
|
|
||||||
class UStaticMeshComponent;
|
class UStaticMeshComponent;
|
||||||
|
class UAudioComponent;
|
||||||
|
class USoundBase;
|
||||||
class UAgrarianItemDefinitionAsset;
|
class UAgrarianItemDefinitionAsset;
|
||||||
|
|
||||||
UCLASS(Blueprintable)
|
UCLASS(Blueprintable)
|
||||||
@@ -33,6 +35,9 @@ public:
|
|||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Resource|Visuals")
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Resource|Visuals")
|
||||||
TObjectPtr<UStaticMeshComponent> HarvestableMarkerProxy;
|
TObjectPtr<UStaticMeshComponent> HarvestableMarkerProxy;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Agrarian|Resource|Audio")
|
||||||
|
TObjectPtr<UAudioComponent> GatheringAudioComponent;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource")
|
||||||
FAgrarianItemStack YieldItem;
|
FAgrarianItemStack YieldItem;
|
||||||
|
|
||||||
@@ -66,6 +71,12 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Respawn", meta = (ClampMin = "1"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Respawn", meta = (ClampMin = "1"))
|
||||||
int32 MaxHarvests = 5;
|
int32 MaxHarvests = 5;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Audio")
|
||||||
|
TObjectPtr<USoundBase> GatheringSound;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Resource|Audio")
|
||||||
|
TObjectPtr<USoundBase> DepletedGatheringSound;
|
||||||
|
|
||||||
virtual FText GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
virtual FText GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
||||||
virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
virtual bool CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const override;
|
||||||
virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override;
|
virtual void Interact_Implementation(AAgrarianGameCharacter* Interactor) override;
|
||||||
@@ -83,6 +94,9 @@ protected:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void OnRep_RemainingHarvests();
|
void OnRep_RemainingHarvests();
|
||||||
|
|
||||||
|
UFUNCTION(NetMulticast, Unreliable)
|
||||||
|
void MulticastPlayGatheringSound(bool bDepletedAfterGather);
|
||||||
|
|
||||||
bool HasRequiredTool(const AAgrarianGameCharacter* Interactor) const;
|
bool HasRequiredTool(const AAgrarianGameCharacter* Interactor) const;
|
||||||
int32 GetHarvestQuantityFor(const AAgrarianGameCharacter* Interactor) const;
|
int32 GetHarvestQuantityFor(const AAgrarianGameCharacter* Interactor) const;
|
||||||
FAgrarianItemStack MakeYieldStack(const AAgrarianGameCharacter* Interactor) const;
|
FAgrarianItemStack MakeYieldStack(const AAgrarianGameCharacter* Interactor) const;
|
||||||
|
|||||||
Reference in New Issue
Block a user