This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
AgrarianGameArchive/Source/AgrarianGame/AgrarianWildlifeBase.cpp
T

475 lines
11 KiB
C++

// Copyright Pacificao. All Rights Reserved.
#include "AgrarianWildlifeBase.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianInventoryComponent.h"
#include "AIController.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Navigation/PathFollowingComponent.h"
#include "NavigationSystem.h"
#include "Net/UnrealNetwork.h"
AAgrarianWildlifeBase::AAgrarianWildlifeBase()
{
PrimaryActorTick.bCanEverTick = true;
bReplicates = true;
NetCullDistanceSquared = FMath::Square(6000.0f);
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AIControllerClass = AAIController::StaticClass();
GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
GetCharacterMovement()->bOrientRotationToMovement = true;
bUseControllerRotationYaw = false;
DisplayName = FText::FromString(TEXT("Wildlife"));
}
void AAgrarianWildlifeBase::BeginPlay()
{
Super::BeginPlay();
SpawnLocation = GetActorLocation();
Health = FMath::Clamp(Health, 0.0f, MaxHealth);
if (HasAuthority() && !GetController())
{
SpawnDefaultController();
}
ChooseWanderTarget();
BroadcastHealthChanged();
BroadcastStateChanged();
}
void AAgrarianWildlifeBase::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (HasAuthority())
{
if (ShouldRunServerThink(DeltaSeconds))
{
const float EffectiveDeltaSeconds = bEnablePerformanceLimits ? ServerThinkAccumulator : DeltaSeconds;
ServerThink(EffectiveDeltaSeconds);
ServerThinkAccumulator = 0.0f;
}
}
}
void AAgrarianWildlifeBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AAgrarianWildlifeBase, WildlifeState);
DOREPLIFETIME(AAgrarianWildlifeBase, Health);
DOREPLIFETIME(AAgrarianWildlifeBase, bHarvested);
}
bool AAgrarianWildlifeBase::IsAlive() const
{
return WildlifeState != EAgrarianWildlifeState::Dead && Health > 0.0f;
}
void AAgrarianWildlifeBase::ApplyWildlifeDamage(float Amount, AActor* InstigatorActor)
{
if (!HasAuthority() || !IsAlive())
{
return;
}
Health = FMath::Clamp(Health - FMath::Max(0.0f, Amount), 0.0f, MaxHealth);
FocusActor = InstigatorActor;
BroadcastHealthChanged();
if (Health <= 0.0f)
{
EnterDeadState();
return;
}
if (Disposition == EAgrarianWildlifeDisposition::Passive || Disposition == EAgrarianWildlifeDisposition::Fleeing)
{
SetWildlifeState(EAgrarianWildlifeState::Fleeing);
}
else if (Disposition == EAgrarianWildlifeDisposition::Aggressive)
{
SetWildlifeState(EAgrarianWildlifeState::Chasing);
}
}
void AAgrarianWildlifeBase::SetWildlifeState(EAgrarianWildlifeState NewState)
{
if (WildlifeState == NewState)
{
return;
}
WildlifeState = NewState;
BroadcastStateChanged();
}
FText AAgrarianWildlifeBase::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
{
if (WildlifeState == EAgrarianWildlifeState::Dead)
{
return bHarvested ? FText::FromString(TEXT("Harvested")) : FText::FromString(TEXT("Harvest"));
}
return DisplayName;
}
bool AAgrarianWildlifeBase::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const
{
return Interactor && WildlifeState == EAgrarianWildlifeState::Dead && !bHarvested;
}
void AAgrarianWildlifeBase::Interact_Implementation(AAgrarianGameCharacter* Interactor)
{
if (HasAuthority())
{
Harvest(Interactor);
}
}
void AAgrarianWildlifeBase::OnRep_Health()
{
BroadcastHealthChanged();
}
void AAgrarianWildlifeBase::OnRep_WildlifeState()
{
BroadcastStateChanged();
}
bool AAgrarianWildlifeBase::ShouldRunServerThink(float DeltaSeconds)
{
if (!bEnablePerformanceLimits || WildlifeState == EAgrarianWildlifeState::Dead)
{
return true;
}
ServerThinkAccumulator += DeltaSeconds;
if (GetNearestPlayerDistanceSquared() <= FMath::Square(FullUpdateRadius))
{
return true;
}
return ServerThinkAccumulator >= FarUpdateIntervalSeconds;
}
void AAgrarianWildlifeBase::ServerThink(float DeltaSeconds)
{
if (!IsAlive())
{
return;
}
DecisionTimer -= DeltaSeconds;
if (DecisionTimer <= 0.0f)
{
DecisionTimer = DecisionIntervalSeconds;
if (Disposition == EAgrarianWildlifeDisposition::Aggressive)
{
FocusActor = FindNearestPlayer(AggroRadius);
SetWildlifeState(FocusActor ? EAgrarianWildlifeState::Chasing : EAgrarianWildlifeState::Wandering);
}
else
{
FocusActor = FindNearestPlayer(FleeRadius);
SetWildlifeState(FocusActor ? EAgrarianWildlifeState::Fleeing : EAgrarianWildlifeState::Wandering);
}
if (WildlifeState == EAgrarianWildlifeState::Wandering && FVector::DistSquared(GetActorLocation(), CurrentMoveTarget) < FMath::Square(125.0f))
{
ChooseWanderTarget();
}
}
MoveTowardTarget();
}
void AAgrarianWildlifeBase::ChooseWanderTarget()
{
FVector ReachableTarget = FVector::ZeroVector;
if (ChooseReachableWanderTarget(ReachableTarget))
{
CurrentMoveTarget = ReachableTarget;
ClearNavigationMove();
return;
}
const FVector RandomOffset = FVector(
FMath::FRandRange(-WanderRadius, WanderRadius),
FMath::FRandRange(-WanderRadius, WanderRadius),
0.0f);
CurrentMoveTarget = SpawnLocation + RandomOffset;
ClearNavigationMove();
}
void AAgrarianWildlifeBase::MoveTowardTarget()
{
FVector DesiredTarget = GetActorLocation();
float DesiredSpeed = WanderSpeed;
if (WildlifeState == EAgrarianWildlifeState::Fleeing && FocusActor)
{
FVector DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation();
DesiredDirection.Z = 0.0f;
if (DesiredDirection.IsNearlyZero())
{
DesiredDirection = GetActorForwardVector();
DesiredDirection.Z = 0.0f;
}
DesiredTarget = GetActorLocation() + DesiredDirection.GetSafeNormal() * FleeRadius;
DesiredSpeed = FleeSpeed;
}
else if (WildlifeState == EAgrarianWildlifeState::Chasing && FocusActor)
{
DesiredTarget = FocusActor->GetActorLocation();
DesiredSpeed = FleeSpeed;
}
else if (WildlifeState == EAgrarianWildlifeState::Wandering)
{
DesiredTarget = CurrentMoveTarget;
DesiredSpeed = WanderSpeed;
}
if (RequestNavigationMove(DesiredTarget))
{
GetCharacterMovement()->MaxWalkSpeed = DesiredSpeed;
return;
}
DirectMoveTowardLocation(DesiredTarget, DesiredSpeed);
}
bool AAgrarianWildlifeBase::ChooseReachableWanderTarget(FVector& OutTarget) const
{
if (!bUseNavigationMovement)
{
return false;
}
const UWorld* World = GetWorld();
const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
if (!NavigationSystem)
{
return false;
}
FNavLocation ReachableLocation;
if (!NavigationSystem->GetRandomReachablePointInRadius(SpawnLocation, WanderRadius, ReachableLocation))
{
return false;
}
OutTarget = ReachableLocation.Location;
return true;
}
bool AAgrarianWildlifeBase::ProjectPointToNavigation(const FVector& CandidateLocation, FVector& OutProjectedLocation) const
{
if (!bUseNavigationMovement)
{
return false;
}
const UWorld* World = GetWorld();
const UNavigationSystemV1* NavigationSystem = World ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) : nullptr;
if (!NavigationSystem)
{
return false;
}
FNavLocation ProjectedLocation;
if (!NavigationSystem->ProjectPointToNavigation(CandidateLocation, ProjectedLocation, NavigationProjectionExtent))
{
return false;
}
OutProjectedLocation = ProjectedLocation.Location;
return true;
}
bool AAgrarianWildlifeBase::RequestNavigationMove(const FVector& TargetLocation)
{
if (!bUseNavigationMovement)
{
return false;
}
AAIController* AIController = Cast<AAIController>(GetController());
if (!AIController)
{
return false;
}
FVector ProjectedTarget = TargetLocation;
if (!ProjectPointToNavigation(TargetLocation, ProjectedTarget))
{
return false;
}
if (FVector::DistSquared(GetActorLocation(), ProjectedTarget) <= FMath::Square(NavigationAcceptanceRadius))
{
AIController->StopMovement();
ClearNavigationMove();
return true;
}
if (bHasNavigationMoveTarget
&& AIController->GetMoveStatus() != EPathFollowingStatus::Idle
&& FVector::DistSquared(LastNavigationMoveTarget, ProjectedTarget) <= FMath::Square(NavigationRepathDistance))
{
return true;
}
const EPathFollowingRequestResult::Type MoveResult = AIController->MoveToLocation(
ProjectedTarget,
NavigationAcceptanceRadius,
true,
true,
true,
true,
nullptr,
true);
if (MoveResult == EPathFollowingRequestResult::Failed)
{
ClearNavigationMove();
return false;
}
LastNavigationMoveTarget = ProjectedTarget;
bHasNavigationMoveTarget = true;
return true;
}
void AAgrarianWildlifeBase::ClearNavigationMove()
{
bHasNavigationMoveTarget = false;
LastNavigationMoveTarget = FVector::ZeroVector;
}
void AAgrarianWildlifeBase::DirectMoveTowardLocation(const FVector& TargetLocation, float MovementSpeed)
{
ClearNavigationMove();
GetCharacterMovement()->MaxWalkSpeed = MovementSpeed;
FVector DesiredDirection = TargetLocation - GetActorLocation();
DesiredDirection.Z = 0.0f;
if (!DesiredDirection.IsNearlyZero())
{
AddMovementInput(DesiredDirection.GetSafeNormal());
}
}
void AAgrarianWildlifeBase::EnterDeadState()
{
Health = 0.0f;
SetWildlifeState(EAgrarianWildlifeState::Dead);
if (AAIController* AIController = Cast<AAIController>(GetController()))
{
AIController->StopMovement();
}
ClearNavigationMove();
GetCharacterMovement()->StopMovementImmediately();
GetCharacterMovement()->DisableMovement();
SetActorTickEnabled(false);
SetLifeSpan(0.0f);
}
AAgrarianGameCharacter* AAgrarianWildlifeBase::FindNearestPlayer(float Radius) const
{
UWorld* World = GetWorld();
if (!World)
{
return nullptr;
}
AAgrarianGameCharacter* NearestCharacter = nullptr;
float BestDistanceSq = FMath::Square(Radius);
TArray<AActor*> PlayerPawns;
UGameplayStatics::GetAllActorsOfClass(World, AAgrarianGameCharacter::StaticClass(), PlayerPawns);
for (AActor* Actor : PlayerPawns)
{
AAgrarianGameCharacter* Candidate = Cast<AAgrarianGameCharacter>(Actor);
if (!Candidate)
{
continue;
}
const float DistanceSq = FVector::DistSquared(GetActorLocation(), Candidate->GetActorLocation());
if (DistanceSq < BestDistanceSq)
{
BestDistanceSq = DistanceSq;
NearestCharacter = Candidate;
}
}
return NearestCharacter;
}
float AAgrarianWildlifeBase::GetNearestPlayerDistanceSquared() const
{
UWorld* World = GetWorld();
if (!World)
{
return TNumericLimits<float>::Max();
}
float BestDistanceSq = TNumericLimits<float>::Max();
TArray<AActor*> PlayerPawns;
UGameplayStatics::GetAllActorsOfClass(World, AAgrarianGameCharacter::StaticClass(), PlayerPawns);
for (AActor* Actor : PlayerPawns)
{
const AAgrarianGameCharacter* Candidate = Cast<AAgrarianGameCharacter>(Actor);
if (!Candidate)
{
continue;
}
BestDistanceSq = FMath::Min(BestDistanceSq, FVector::DistSquared(GetActorLocation(), Candidate->GetActorLocation()));
}
return BestDistanceSq;
}
bool AAgrarianWildlifeBase::Harvest(AAgrarianGameCharacter* Interactor)
{
if (!Interactor || WildlifeState != EAgrarianWildlifeState::Dead || bHarvested)
{
return false;
}
UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent();
if (!Inventory)
{
return false;
}
for (const FAgrarianItemStack& Yield : HarvestYields)
{
if (Yield.IsValidStack())
{
Inventory->AddItem(Yield);
}
}
bHarvested = true;
return true;
}
void AAgrarianWildlifeBase::BroadcastHealthChanged()
{
OnWildlifeHealthChanged.Broadcast(Health, MaxHealth);
}
void AAgrarianWildlifeBase::BroadcastStateChanged()
{
OnWildlifeStateChanged.Broadcast(WildlifeState);
}