// Copyright Pacificao. All Rights Reserved. #include "AgrarianResourceNode.h" #include "AgrarianGameCharacter.h" #include "AgrarianInventoryComponent.h" #include "AgrarianItemDefinitionAsset.h" #include "AgrarianSaveGame.h" #include "Components/AudioComponent.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Materials/MaterialInterface.h" #include "TimerManager.h" #include "Net/UnrealNetwork.h" #include "UObject/ConstructorHelpers.h" AAgrarianResourceNode::AAgrarianResourceNode() { bReplicates = true; SetNetCullDistanceSquared(FMath::Square(4500.0f)); Mesh = CreateDefaultSubobject(TEXT("Mesh")); RootComponent = Mesh; Mesh->SetCollisionProfileName(TEXT("BlockAll")); static ConstructorHelpers::FObjectFinder ChamferCubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_ChamferCube.SM_AGR_Placeholder_ChamferCube")); static ConstructorHelpers::FObjectFinder CylinderMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cylinder.SM_AGR_Placeholder_Cylinder")); static ConstructorHelpers::FObjectFinder WoodMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Wood_Resource.M_AGR_GZ_Wood_Resource")); static ConstructorHelpers::FObjectFinder FiberMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.M_AGR_GZ_Fiber_Resource")); if (ChamferCubeMesh.Succeeded()) { Mesh->SetStaticMesh(ChamferCubeMesh.Object); Mesh->SetRelativeScale3D(FVector(0.8f, 0.58f, 0.44f)); } if (WoodMaterial.Succeeded()) { Mesh->SetMaterial(0, WoodMaterial.Object); } ResourceClusterProxy = CreateDefaultSubobject(TEXT("ResourceClusterProxy")); ResourceClusterProxy->SetupAttachment(RootComponent); ResourceClusterProxy->SetCollisionEnabled(ECollisionEnabled::NoCollision); ResourceClusterProxy->SetGenerateOverlapEvents(false); if (CylinderMesh.Succeeded()) { ResourceClusterProxy->SetStaticMesh(CylinderMesh.Object); } if (WoodMaterial.Succeeded()) { ResourceClusterProxy->SetMaterial(0, WoodMaterial.Object); } ResourceClusterProxy->SetRelativeLocation(FVector(0.0f, -18.0f, 28.0f)); ResourceClusterProxy->SetRelativeRotation(FRotator(0.0f, 90.0f, 90.0f)); ResourceClusterProxy->SetRelativeScale3D(FVector(0.12f, 0.12f, 0.7f)); HarvestableMarkerProxy = CreateDefaultSubobject(TEXT("HarvestableMarkerProxy")); HarvestableMarkerProxy->SetupAttachment(RootComponent); HarvestableMarkerProxy->SetCollisionEnabled(ECollisionEnabled::NoCollision); HarvestableMarkerProxy->SetGenerateOverlapEvents(false); if (ChamferCubeMesh.Succeeded()) { HarvestableMarkerProxy->SetStaticMesh(ChamferCubeMesh.Object); } if (FiberMaterial.Succeeded()) { HarvestableMarkerProxy->SetMaterial(0, FiberMaterial.Object); } HarvestableMarkerProxy->SetRelativeLocation(FVector(0.0f, 22.0f, 36.0f)); HarvestableMarkerProxy->SetRelativeScale3D(FVector(0.28f, 0.18f, 0.22f)); GatheringAudioComponent = CreateDefaultSubobject(TEXT("GatheringAudioComponent")); GatheringAudioComponent->SetupAttachment(RootComponent); GatheringAudioComponent->bAutoActivate = false; GatheringAudioComponent->bAllowSpatialization = true; YieldItem.ItemId = TEXT("wood"); YieldItem.DisplayName = FText::FromString(TEXT("Wood")); YieldItem.Quantity = 1; YieldItem.UnitWeight = 1.0f; } void AAgrarianResourceNode::BeginPlay() { Super::BeginPlay(); if (HasAuthority()) { MaxHarvests = FMath::Max(1, MaxHarvests); if (RemainingHarvests > MaxHarvests) { MaxHarvests = RemainingHarvests; } } UpdateDepletedState(); ScheduleRespawnIfNeeded(); } void AAgrarianResourceNode::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AAgrarianResourceNode, RemainingHarvests); } FText AAgrarianResourceNode::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const { if (RemainingHarvests <= 0) { return FText::FromString(TEXT("Depleted")); } if (RequiredToolItemId != NAME_None && !HasRequiredTool(Interactor) && bAllowBareHandGathering) { return FText::FromString(TEXT("Gather by hand")); } return FText::FromString(TEXT("Gather")); } bool AAgrarianResourceNode::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const { if (RemainingHarvests <= 0 || !Interactor) { return false; } return RequiredToolItemId == NAME_None || bAllowBareHandGathering || HasRequiredTool(Interactor); } void AAgrarianResourceNode::Interact_Implementation(AAgrarianGameCharacter* Interactor) { if (!HasAuthority() || !Interactor || RemainingHarvests <= 0) { return; } if (UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent()) { const FAgrarianItemStack Granted = MakeYieldStack(Interactor); if (Inventory->AddItem(Granted)) { RemainingHarvests--; MulticastPlayGatheringSound(RemainingHarvests <= 0); UpdateDepletedState(); ScheduleRespawnIfNeeded(); } } } void AAgrarianResourceNode::OnRep_RemainingHarvests() { 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 { return PersistenceNodeId != NAME_None ? PersistenceNodeId : GetFName(); } FAgrarianSavedResourceNode AAgrarianResourceNode::CaptureResourceSaveState() const { FAgrarianSavedResourceNode SavedNode; SavedNode.ResourceNodeId = GetResourcePersistenceId(); SavedNode.RemainingHarvests = RemainingHarvests; SavedNode.bRespawnsForMvp = bRespawnsForMvp; return SavedNode; } void AAgrarianResourceNode::ApplyResourceSaveState(const FAgrarianSavedResourceNode& SavedNode) { if (!HasAuthority() || SavedNode.ResourceNodeId == NAME_None || SavedNode.ResourceNodeId != GetResourcePersistenceId()) { return; } RemainingHarvests = FMath::Clamp(SavedNode.RemainingHarvests, 0, FMath::Max(1, MaxHarvests)); if (RemainingHarvests > 0) { if (UWorld* World = GetWorld()) { World->GetTimerManager().ClearTimer(RespawnTimerHandle); } } UpdateDepletedState(); ScheduleRespawnIfNeeded(); } bool AAgrarianResourceNode::HasRequiredTool(const AAgrarianGameCharacter* Interactor) const { if (RequiredToolItemId == NAME_None) { return true; } const UAgrarianInventoryComponent* Inventory = Interactor ? Interactor->GetInventoryComponent() : nullptr; return Inventory && Inventory->HasItem(RequiredToolItemId, 1); } int32 AAgrarianResourceNode::GetHarvestQuantityFor(const AAgrarianGameCharacter* Interactor) const { const int32 ToolBonus = HasRequiredTool(Interactor) ? FMath::Max(0, ToolQuantityBonus) : 0; return FMath::Max(1, QuantityPerHarvest + ToolBonus); } FAgrarianItemStack AAgrarianResourceNode::MakeYieldStack(const AAgrarianGameCharacter* Interactor) const { const int32 HarvestQuantity = GetHarvestQuantityFor(Interactor); if (YieldItemDefinition) { return YieldItemDefinition->MakeStack(HarvestQuantity); } FAgrarianItemStack Granted = YieldItem; Granted.Quantity = HarvestQuantity; return Granted; } void AAgrarianResourceNode::UpdateDepletedState() { if (Mesh) { Mesh->SetVisibility(RemainingHarvests > 0, true); Mesh->SetCollisionEnabled(RemainingHarvests > 0 ? ECollisionEnabled::QueryAndPhysics : ECollisionEnabled::NoCollision); } } void AAgrarianResourceNode::ScheduleRespawnIfNeeded() { if (!HasAuthority() || !bRespawnsForMvp || RemainingHarvests > 0) { return; } UWorld* World = GetWorld(); if (!World || World->GetTimerManager().IsTimerActive(RespawnTimerHandle)) { return; } World->GetTimerManager().SetTimer( RespawnTimerHandle, this, &AAgrarianResourceNode::RespawnNode, FMath::Max(1.0f, RespawnDelaySeconds), false); } void AAgrarianResourceNode::RespawnNode() { if (!HasAuthority() || !bRespawnsForMvp) { return; } RemainingHarvests = FMath::Max(1, MaxHarvests); UpdateDepletedState(); }