// 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; 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()) { 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() { 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(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(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(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(GetController())) { AIController->StopMovement(); } ClearNavigationMove(); 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); }