539 lines
15 KiB
C++
539 lines
15 KiB
C++
// Copyright Pacificao. All Rights Reserved.
|
|
|
|
#include "AgrarianWildlifeBase.h"
|
|
#include "AgrarianGameCharacter.h"
|
|
#include "AgrarianInventoryComponent.h"
|
|
#include "AIController.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "GameFramework/CharacterMovementComponent.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "Navigation/PathFollowingComponent.h"
|
|
#include "NavigationSystem.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "UObject/ConstructorHelpers.h"
|
|
|
|
namespace
|
|
{
|
|
void ConfigureWildlifeProxyComponent(UStaticMeshComponent* Component, UStaticMesh* MeshAsset, UMaterialInterface* MaterialAsset)
|
|
{
|
|
if (!Component)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Component->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
Component->SetGenerateOverlapEvents(false);
|
|
if (MeshAsset)
|
|
{
|
|
Component->SetStaticMesh(MeshAsset);
|
|
}
|
|
if (MaterialAsset)
|
|
{
|
|
Component->SetMaterial(0, MaterialAsset);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
static ConstructorHelpers::FObjectFinder<UStaticMesh> ChamferCubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_ChamferCube.SM_AGR_Placeholder_ChamferCube"));
|
|
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cube.SM_AGR_Placeholder_Cube"));
|
|
static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cylinder.SM_AGR_Placeholder_Cylinder"));
|
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> FurMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.M_AGR_GZ_Fiber_Resource"));
|
|
|
|
WildlifeBodyProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeBodyProxy"));
|
|
WildlifeBodyProxy->SetupAttachment(RootComponent);
|
|
ConfigureWildlifeProxyComponent(WildlifeBodyProxy, ChamferCubeMesh.Succeeded() ? ChamferCubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
|
|
WildlifeBodyProxy->SetRelativeLocation(FVector(10.0f, 0.0f, -42.0f));
|
|
WildlifeBodyProxy->SetRelativeScale3D(FVector(0.62f, 0.32f, 0.24f));
|
|
|
|
WildlifeHeadProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeHeadProxy"));
|
|
WildlifeHeadProxy->SetupAttachment(WildlifeBodyProxy);
|
|
ConfigureWildlifeProxyComponent(WildlifeHeadProxy, ChamferCubeMesh.Succeeded() ? ChamferCubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
|
|
WildlifeHeadProxy->SetRelativeLocation(FVector(42.0f, 0.0f, 20.0f));
|
|
WildlifeHeadProxy->SetRelativeScale3D(FVector(0.46f, 0.34f, 0.36f));
|
|
|
|
WildlifeEarProxyA = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeEarProxyA"));
|
|
WildlifeEarProxyA->SetupAttachment(WildlifeHeadProxy);
|
|
ConfigureWildlifeProxyComponent(WildlifeEarProxyA, CubeMesh.Succeeded() ? CubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
|
|
WildlifeEarProxyA->SetRelativeLocation(FVector(4.0f, -12.0f, 28.0f));
|
|
WildlifeEarProxyA->SetRelativeRotation(FRotator(0.0f, 0.0f, -10.0f));
|
|
WildlifeEarProxyA->SetRelativeScale3D(FVector(0.08f, 0.08f, 0.38f));
|
|
|
|
WildlifeEarProxyB = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeEarProxyB"));
|
|
WildlifeEarProxyB->SetupAttachment(WildlifeHeadProxy);
|
|
ConfigureWildlifeProxyComponent(WildlifeEarProxyB, CubeMesh.Succeeded() ? CubeMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
|
|
WildlifeEarProxyB->SetRelativeLocation(FVector(4.0f, 12.0f, 28.0f));
|
|
WildlifeEarProxyB->SetRelativeRotation(FRotator(0.0f, 0.0f, 10.0f));
|
|
WildlifeEarProxyB->SetRelativeScale3D(FVector(0.08f, 0.08f, 0.38f));
|
|
|
|
WildlifeTailProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WildlifeTailProxy"));
|
|
WildlifeTailProxy->SetupAttachment(WildlifeBodyProxy);
|
|
ConfigureWildlifeProxyComponent(WildlifeTailProxy, CylinderMesh.Succeeded() ? CylinderMesh.Object : nullptr, FurMaterial.Succeeded() ? FurMaterial.Object : nullptr);
|
|
WildlifeTailProxy->SetRelativeLocation(FVector(-42.0f, 0.0f, 10.0f));
|
|
WildlifeTailProxy->SetRelativeRotation(FRotator(0.0f, 90.0f, 90.0f));
|
|
WildlifeTailProxy->SetRelativeScale3D(FVector(0.12f, 0.12f, 0.24f));
|
|
|
|
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);
|
|
}
|