// Copyright Pacificao. All Rights Reserved. #include "AgrarianWildlifeBase.h" #include "AgrarianGameCharacter.h" #include "AgrarianInventoryComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "Kismet/GameplayStatics.h" #include "Net/UnrealNetwork.h" AAgrarianWildlifeBase::AAgrarianWildlifeBase() { PrimaryActorTick.bCanEverTick = true; bReplicates = true; 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); ChooseWanderTarget(); BroadcastHealthChanged(); BroadcastStateChanged(); } void AAgrarianWildlifeBase::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); if (HasAuthority()) { ServerThink(DeltaSeconds); } } void AAgrarianWildlifeBase::GetLifetimeReplicatedProps(TArray& 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(); } 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() { const FVector RandomOffset = FVector( FMath::FRandRange(-WanderRadius, WanderRadius), FMath::FRandRange(-WanderRadius, WanderRadius), 0.0f); CurrentMoveTarget = SpawnLocation + RandomOffset; } void AAgrarianWildlifeBase::MoveTowardTarget() { FVector DesiredDirection = FVector::ZeroVector; if (WildlifeState == EAgrarianWildlifeState::Fleeing && FocusActor) { DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation(); GetCharacterMovement()->MaxWalkSpeed = FleeSpeed; } else if (WildlifeState == EAgrarianWildlifeState::Chasing && FocusActor) { DesiredDirection = FocusActor->GetActorLocation() - GetActorLocation(); GetCharacterMovement()->MaxWalkSpeed = FleeSpeed; } else if (WildlifeState == EAgrarianWildlifeState::Wandering) { DesiredDirection = CurrentMoveTarget - GetActorLocation(); GetCharacterMovement()->MaxWalkSpeed = WanderSpeed; } DesiredDirection.Z = 0.0f; if (!DesiredDirection.IsNearlyZero()) { AddMovementInput(DesiredDirection.GetSafeNormal()); } } void AAgrarianWildlifeBase::EnterDeadState() { Health = 0.0f; SetWildlifeState(EAgrarianWildlifeState::Dead); GetCharacterMovement()->StopMovementImmediately(); GetCharacterMovement()->DisableMovement(); 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 PlayerPawns; UGameplayStatics::GetAllActorsOfClass(World, AAgrarianGameCharacter::StaticClass(), PlayerPawns); for (AActor* Actor : PlayerPawns) { AAgrarianGameCharacter* Candidate = Cast(Actor); if (!Candidate) { continue; } const float DistanceSq = FVector::DistSquared(GetActorLocation(), Candidate->GetActorLocation()); if (DistanceSq < BestDistanceSq) { BestDistanceSq = DistanceSq; NearestCharacter = Candidate; } } return NearestCharacter; } 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); }