Add wildlife navigation support
This commit is contained in:
@@ -466,7 +466,10 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
|||||||
and Unreal Insights CPU scopes for authoritative game-state time/weather
|
and Unreal Insights CPU scopes for authoritative game-state time/weather
|
||||||
ticking, survival ticking, sky-light refresh, weather-audio refresh, foliage
|
ticking, survival ticking, sky-light refresh, weather-audio refresh, foliage
|
||||||
instance mutation, and weather-provider request/parse/fallback work.
|
instance mutation, and weather-provider request/parse/fallback work.
|
||||||
- [ ] Add navigation support for wildlife.
|
- [x] Add navigation support for wildlife. Wildlife now auto-possesses an AI
|
||||||
|
controller, samples reachable wander points, projects chase/flee targets onto
|
||||||
|
navmesh, requests server-authoritative AI movement when nav data exists, and
|
||||||
|
retains direct movement fallback for early maps without nav data.
|
||||||
- [ ] Add map boundaries or soft limits.
|
- [ ] Add map boundaries or soft limits.
|
||||||
- [ ] Add developer travel command.
|
- [ ] Add developer travel command.
|
||||||
|
|
||||||
|
|||||||
@@ -334,6 +334,16 @@ Use Unreal Data Assets for designer-facing definitions such as:
|
|||||||
|
|
||||||
Data Assets should describe content. Server code should enforce gameplay rules.
|
Data Assets should describe content. Server code should enforce gameplay rules.
|
||||||
|
|
||||||
|
### Wildlife Navigation
|
||||||
|
|
||||||
|
MVP wildlife movement is server authoritative. `AAgrarianWildlifeBase` uses an
|
||||||
|
AI controller and Unreal navigation when nav data exists: wander targets are
|
||||||
|
chosen from reachable nav points, chase/flee targets are projected onto navmesh,
|
||||||
|
and movement requests use `MoveToLocation`. If a test map or early generated
|
||||||
|
tile has no nav data, wildlife falls back to direct movement input so damage,
|
||||||
|
flee, chase, and harvest loops remain functional while map navigation is being
|
||||||
|
authored.
|
||||||
|
|
||||||
### JSON Metadata
|
### JSON Metadata
|
||||||
|
|
||||||
Use JSON files for external terrain/tile pipeline metadata while the pipeline is
|
Use JSON files for external terrain/tile pipeline metadata while the pipeline is
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import unreal
|
|||||||
|
|
||||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||||
CHARACTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
|
CHARACTER_CLASS_PATH = "/Game/Agrarian/Blueprints/Characters/BP_AgrarianPlayerCharacter"
|
||||||
RABBIT_LABEL = "AGR_RabbitWildlife_01"
|
RABBIT_LABELS = ["AGR_DemoRabbitWildlife_01", "AGR_RabbitWildlife_01"]
|
||||||
|
|
||||||
|
|
||||||
def get_actor_label(actor):
|
def get_actor_label(actor):
|
||||||
@@ -27,6 +27,14 @@ def find_actor_by_label(label):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_actor_by_any_label(labels):
|
||||||
|
for label in labels:
|
||||||
|
actor = find_actor_by_label(label)
|
||||||
|
if actor:
|
||||||
|
return actor
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def enum_name(value):
|
def enum_name(value):
|
||||||
return str(value).split(".")[-1].lower()
|
return str(value).split(".")[-1].lower()
|
||||||
|
|
||||||
@@ -45,9 +53,9 @@ def main():
|
|||||||
raise RuntimeError(f"Could not load map: {MAP_PATH}")
|
raise RuntimeError(f"Could not load map: {MAP_PATH}")
|
||||||
|
|
||||||
character_class = load_blueprint_class(CHARACTER_CLASS_PATH)
|
character_class = load_blueprint_class(CHARACTER_CLASS_PATH)
|
||||||
rabbit = find_actor_by_label(RABBIT_LABEL)
|
rabbit = find_actor_by_any_label(RABBIT_LABELS)
|
||||||
if not rabbit:
|
if not rabbit:
|
||||||
raise RuntimeError(f"Could not find placed rabbit wildlife: {RABBIT_LABEL}")
|
raise RuntimeError(f"Could not find placed rabbit wildlife by labels: {RABBIT_LABELS}")
|
||||||
|
|
||||||
character = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world(
|
character = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world(
|
||||||
character_class,
|
character_class,
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Validate native wildlife navigation support wiring."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def read_text(relative_path: str) -> str:
|
||||||
|
path = REPO_ROOT / relative_path
|
||||||
|
if not path.exists():
|
||||||
|
raise AssertionError(f"Missing required file: {relative_path}")
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def require(needle: str, haystack: str, context: str) -> None:
|
||||||
|
if needle not in haystack:
|
||||||
|
raise AssertionError(f"Missing {needle!r} in {context}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
errors: list[str] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
build_cs = read_text("Source/AgrarianGame/AgrarianGame.Build.cs")
|
||||||
|
require('"AIModule"', build_cs, "AgrarianGame.Build.cs")
|
||||||
|
require('"NavigationSystem"', build_cs, "AgrarianGame.Build.cs")
|
||||||
|
except AssertionError as exc:
|
||||||
|
errors.append(str(exc))
|
||||||
|
|
||||||
|
try:
|
||||||
|
header = read_text("Source/AgrarianGame/AgrarianWildlifeBase.h")
|
||||||
|
for marker in [
|
||||||
|
"bUseNavigationMovement",
|
||||||
|
"NavigationAcceptanceRadius",
|
||||||
|
"NavigationRepathDistance",
|
||||||
|
"NavigationProjectionExtent",
|
||||||
|
"ChooseReachableWanderTarget",
|
||||||
|
"ProjectPointToNavigation",
|
||||||
|
"RequestNavigationMove",
|
||||||
|
"DirectMoveTowardLocation",
|
||||||
|
]:
|
||||||
|
require(marker, header, "AgrarianWildlifeBase.h")
|
||||||
|
except AssertionError as exc:
|
||||||
|
errors.append(str(exc))
|
||||||
|
|
||||||
|
try:
|
||||||
|
source = read_text("Source/AgrarianGame/AgrarianWildlifeBase.cpp")
|
||||||
|
for marker in [
|
||||||
|
'#include "AIController.h"',
|
||||||
|
'#include "NavigationSystem.h"',
|
||||||
|
"AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned",
|
||||||
|
"AIControllerClass = AAIController::StaticClass()",
|
||||||
|
"SpawnDefaultController()",
|
||||||
|
"GetRandomReachablePointInRadius",
|
||||||
|
"ProjectPointToNavigation",
|
||||||
|
"MoveToLocation",
|
||||||
|
"EPathFollowingRequestResult::Failed",
|
||||||
|
"StopMovement()",
|
||||||
|
"DirectMoveTowardLocation",
|
||||||
|
]:
|
||||||
|
require(marker, source, "AgrarianWildlifeBase.cpp")
|
||||||
|
except AssertionError as exc:
|
||||||
|
errors.append(str(exc))
|
||||||
|
|
||||||
|
try:
|
||||||
|
roadmap = read_text("AGRARIAN_DEVELOPMENT_ROADMAP.md")
|
||||||
|
require("[x] Add navigation support for wildlife.", roadmap, "AGRARIAN_DEVELOPMENT_ROADMAP.md")
|
||||||
|
docs = read_text("Docs/TechnicalDesignDocument.md")
|
||||||
|
require("Wildlife Navigation", docs, "Docs/TechnicalDesignDocument.md")
|
||||||
|
require("falls back to direct movement input", docs, "Docs/TechnicalDesignDocument.md")
|
||||||
|
except AssertionError as exc:
|
||||||
|
errors.append(str(exc))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
for error in errors:
|
||||||
|
print(f"ERROR: {error}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("Wildlife navigation support is wired.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -15,6 +15,7 @@ public class AgrarianGame : ModuleRules
|
|||||||
"InputCore",
|
"InputCore",
|
||||||
"EnhancedInput",
|
"EnhancedInput",
|
||||||
"AIModule",
|
"AIModule",
|
||||||
|
"NavigationSystem",
|
||||||
"UMG",
|
"UMG",
|
||||||
"Landscape",
|
"Landscape",
|
||||||
"HTTP",
|
"HTTP",
|
||||||
|
|||||||
@@ -3,14 +3,19 @@
|
|||||||
#include "AgrarianWildlifeBase.h"
|
#include "AgrarianWildlifeBase.h"
|
||||||
#include "AgrarianGameCharacter.h"
|
#include "AgrarianGameCharacter.h"
|
||||||
#include "AgrarianInventoryComponent.h"
|
#include "AgrarianInventoryComponent.h"
|
||||||
|
#include "AIController.h"
|
||||||
#include "GameFramework/CharacterMovementComponent.h"
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "Navigation/PathFollowingComponent.h"
|
||||||
|
#include "NavigationSystem.h"
|
||||||
#include "Net/UnrealNetwork.h"
|
#include "Net/UnrealNetwork.h"
|
||||||
|
|
||||||
AAgrarianWildlifeBase::AAgrarianWildlifeBase()
|
AAgrarianWildlifeBase::AAgrarianWildlifeBase()
|
||||||
{
|
{
|
||||||
PrimaryActorTick.bCanEverTick = true;
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
bReplicates = true;
|
bReplicates = true;
|
||||||
|
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
|
||||||
|
AIControllerClass = AAIController::StaticClass();
|
||||||
|
|
||||||
GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
|
GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
|
||||||
GetCharacterMovement()->bOrientRotationToMovement = true;
|
GetCharacterMovement()->bOrientRotationToMovement = true;
|
||||||
@@ -25,6 +30,10 @@ void AAgrarianWildlifeBase::BeginPlay()
|
|||||||
|
|
||||||
SpawnLocation = GetActorLocation();
|
SpawnLocation = GetActorLocation();
|
||||||
Health = FMath::Clamp(Health, 0.0f, MaxHealth);
|
Health = FMath::Clamp(Health, 0.0f, MaxHealth);
|
||||||
|
if (HasAuthority() && !GetController())
|
||||||
|
{
|
||||||
|
SpawnDefaultController();
|
||||||
|
}
|
||||||
ChooseWanderTarget();
|
ChooseWanderTarget();
|
||||||
BroadcastHealthChanged();
|
BroadcastHealthChanged();
|
||||||
BroadcastStateChanged();
|
BroadcastStateChanged();
|
||||||
@@ -159,34 +168,174 @@ void AAgrarianWildlifeBase::ServerThink(float DeltaSeconds)
|
|||||||
|
|
||||||
void AAgrarianWildlifeBase::ChooseWanderTarget()
|
void AAgrarianWildlifeBase::ChooseWanderTarget()
|
||||||
{
|
{
|
||||||
|
FVector ReachableTarget = FVector::ZeroVector;
|
||||||
|
if (ChooseReachableWanderTarget(ReachableTarget))
|
||||||
|
{
|
||||||
|
CurrentMoveTarget = ReachableTarget;
|
||||||
|
ClearNavigationMove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const FVector RandomOffset = FVector(
|
const FVector RandomOffset = FVector(
|
||||||
FMath::FRandRange(-WanderRadius, WanderRadius),
|
FMath::FRandRange(-WanderRadius, WanderRadius),
|
||||||
FMath::FRandRange(-WanderRadius, WanderRadius),
|
FMath::FRandRange(-WanderRadius, WanderRadius),
|
||||||
0.0f);
|
0.0f);
|
||||||
|
|
||||||
CurrentMoveTarget = SpawnLocation + RandomOffset;
|
CurrentMoveTarget = SpawnLocation + RandomOffset;
|
||||||
|
ClearNavigationMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AAgrarianWildlifeBase::MoveTowardTarget()
|
void AAgrarianWildlifeBase::MoveTowardTarget()
|
||||||
{
|
{
|
||||||
FVector DesiredDirection = FVector::ZeroVector;
|
FVector DesiredTarget = GetActorLocation();
|
||||||
|
float DesiredSpeed = WanderSpeed;
|
||||||
|
|
||||||
if (WildlifeState == EAgrarianWildlifeState::Fleeing && FocusActor)
|
if (WildlifeState == EAgrarianWildlifeState::Fleeing && FocusActor)
|
||||||
{
|
{
|
||||||
DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation();
|
FVector DesiredDirection = GetActorLocation() - FocusActor->GetActorLocation();
|
||||||
GetCharacterMovement()->MaxWalkSpeed = FleeSpeed;
|
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)
|
else if (WildlifeState == EAgrarianWildlifeState::Chasing && FocusActor)
|
||||||
{
|
{
|
||||||
DesiredDirection = FocusActor->GetActorLocation() - GetActorLocation();
|
DesiredTarget = FocusActor->GetActorLocation();
|
||||||
GetCharacterMovement()->MaxWalkSpeed = FleeSpeed;
|
DesiredSpeed = FleeSpeed;
|
||||||
}
|
}
|
||||||
else if (WildlifeState == EAgrarianWildlifeState::Wandering)
|
else if (WildlifeState == EAgrarianWildlifeState::Wandering)
|
||||||
{
|
{
|
||||||
DesiredDirection = CurrentMoveTarget - GetActorLocation();
|
DesiredTarget = CurrentMoveTarget;
|
||||||
GetCharacterMovement()->MaxWalkSpeed = WanderSpeed;
|
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;
|
DesiredDirection.Z = 0.0f;
|
||||||
if (!DesiredDirection.IsNearlyZero())
|
if (!DesiredDirection.IsNearlyZero())
|
||||||
{
|
{
|
||||||
@@ -198,6 +347,11 @@ void AAgrarianWildlifeBase::EnterDeadState()
|
|||||||
{
|
{
|
||||||
Health = 0.0f;
|
Health = 0.0f;
|
||||||
SetWildlifeState(EAgrarianWildlifeState::Dead);
|
SetWildlifeState(EAgrarianWildlifeState::Dead);
|
||||||
|
if (AAIController* AIController = Cast<AAIController>(GetController()))
|
||||||
|
{
|
||||||
|
AIController->StopMovement();
|
||||||
|
}
|
||||||
|
ClearNavigationMove();
|
||||||
GetCharacterMovement()->StopMovementImmediately();
|
GetCharacterMovement()->StopMovementImmediately();
|
||||||
GetCharacterMovement()->DisableMovement();
|
GetCharacterMovement()->DisableMovement();
|
||||||
SetLifeSpan(0.0f);
|
SetLifeSpan(0.0f);
|
||||||
|
|||||||
@@ -66,6 +66,18 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Movement", meta = (ClampMin = "0.1"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Movement", meta = (ClampMin = "0.1"))
|
||||||
float DecisionIntervalSeconds = 2.0f;
|
float DecisionIntervalSeconds = 2.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Movement")
|
||||||
|
bool bUseNavigationMovement = true;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Movement", meta = (ClampMin = "1"))
|
||||||
|
float NavigationAcceptanceRadius = 85.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Movement", meta = (ClampMin = "1"))
|
||||||
|
float NavigationRepathDistance = 175.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Movement")
|
||||||
|
FVector NavigationProjectionExtent = FVector(500.0f, 500.0f, 900.0f);
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Harvest")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Wildlife|Harvest")
|
||||||
TArray<FAgrarianItemStack> HarvestYields;
|
TArray<FAgrarianItemStack> HarvestYields;
|
||||||
|
|
||||||
@@ -97,6 +109,11 @@ protected:
|
|||||||
void ServerThink(float DeltaSeconds);
|
void ServerThink(float DeltaSeconds);
|
||||||
void ChooseWanderTarget();
|
void ChooseWanderTarget();
|
||||||
void MoveTowardTarget();
|
void MoveTowardTarget();
|
||||||
|
bool ChooseReachableWanderTarget(FVector& OutTarget) const;
|
||||||
|
bool ProjectPointToNavigation(const FVector& CandidateLocation, FVector& OutProjectedLocation) const;
|
||||||
|
bool RequestNavigationMove(const FVector& TargetLocation);
|
||||||
|
void ClearNavigationMove();
|
||||||
|
void DirectMoveTowardLocation(const FVector& TargetLocation, float MovementSpeed);
|
||||||
void EnterDeadState();
|
void EnterDeadState();
|
||||||
AAgrarianGameCharacter* FindNearestPlayer(float Radius) const;
|
AAgrarianGameCharacter* FindNearestPlayer(float Radius) const;
|
||||||
bool Harvest(AAgrarianGameCharacter* Interactor);
|
bool Harvest(AAgrarianGameCharacter* Interactor);
|
||||||
@@ -112,5 +129,9 @@ protected:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<AActor> FocusActor;
|
TObjectPtr<AActor> FocusActor;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FVector LastNavigationMoveTarget = FVector::ZeroVector;
|
||||||
|
|
||||||
|
bool bHasNavigationMoveTarget = false;
|
||||||
float DecisionTimer = 0.0f;
|
float DecisionTimer = 0.0f;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user