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/AgrarianBuildingPlacementComponent.cpp
2026-05-18 11:02:02 -07:00

248 lines
6.9 KiB
C++

// Copyright Pacificao. All Rights Reserved.
#include "AgrarianBuildingPlacementComponent.h"
#include "AgrarianInventoryComponent.h"
#include "DrawDebugHelpers.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/Controller.h"
#include "Engine/World.h"
UAgrarianBuildingPlacementComponent::UAgrarianBuildingPlacementComponent()
{
PrimaryComponentTick.bCanEverTick = true;
SetIsReplicatedByDefault(true);
}
void UAgrarianBuildingPlacementComponent::SetActiveBuildable(TSubclassOf<AActor> BuildClass, const TArray<FAgrarianItemStack>& Cost)
{
ActiveBuildClass = BuildClass;
PlacementCost = Cost;
}
bool UAgrarianBuildingPlacementComponent::GetPlacementPreview(FTransform& OutTransform, FText& FailureReason) const
{
const APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (!OwnerPawn)
{
FailureReason = FText::FromString(TEXT("Only pawns can place buildables."));
return false;
}
FVector ViewLocation;
FRotator ViewRotation;
if (OwnerPawn->GetController())
{
OwnerPawn->GetController()->GetPlayerViewPoint(ViewLocation, ViewRotation);
}
else
{
ViewLocation = OwnerPawn->GetActorLocation();
ViewRotation = OwnerPawn->GetActorRotation();
}
const FVector TraceEnd = ViewLocation + ViewRotation.Vector() * PlacementDistance;
FHitResult Hit;
FCollisionQueryParams Params(SCENE_QUERY_STAT(AgrarianBuildPlacementTrace), false, OwnerPawn);
FVector PlacementLocation = TraceEnd;
if (GetWorld() && GetWorld()->LineTraceSingleByChannel(Hit, ViewLocation, TraceEnd, ECC_Visibility, Params))
{
PlacementLocation = Hit.ImpactPoint + Hit.ImpactNormal * SurfaceOffset;
}
PlacementLocation = SnapLocation(PlacementLocation);
const FRotator PlacementRotation(0.0f, OwnerPawn->GetActorRotation().Yaw, 0.0f);
OutTransform = FTransform(PlacementRotation, PlacementLocation);
return true;
}
bool UAgrarianBuildingPlacementComponent::GetPlacementPreviewState(FTransform& OutTransform, FText& FailureReason) const
{
if (!GetPlacementPreview(OutTransform, FailureReason))
{
return false;
}
return CanPlaceAtTransform(ActiveBuildClass, OutTransform, FailureReason);
}
bool UAgrarianBuildingPlacementComponent::CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const
{
if (!BuildClass)
{
FailureReason = FText::FromString(TEXT("No buildable is selected."));
return false;
}
const AActor* OwnerActor = GetOwner();
if (!OwnerActor)
{
FailureReason = FText::FromString(TEXT("No owner is available."));
return false;
}
if (FVector::DistSquared(OwnerActor->GetActorLocation(), PlacementTransform.GetLocation()) > FMath::Square(PlacementDistance + 150.0f))
{
FailureReason = FText::FromString(TEXT("Placement is too far away."));
return false;
}
if (!GetWorld())
{
FailureReason = FText::FromString(TEXT("No world is available."));
return false;
}
FCollisionQueryParams Params(SCENE_QUERY_STAT(AgrarianBuildPlacementProbe), false, OwnerActor);
const bool bBlocked = GetWorld()->OverlapBlockingTestByChannel(
PlacementTransform.GetLocation() + FVector(0.0f, 0.0f, PlacementProbeRadius),
PlacementTransform.GetRotation(),
ECC_WorldDynamic,
FCollisionShape::MakeSphere(PlacementProbeRadius),
Params);
if (bBlocked)
{
FailureReason = FText::FromString(TEXT("Placement area is blocked."));
return false;
}
return true;
}
bool UAgrarianBuildingPlacementComponent::PlaceActiveBuildable()
{
FTransform PlacementTransform;
FText FailureReason;
if (!GetPlacementPreview(PlacementTransform, FailureReason))
{
FailPlacement(FailureReason);
return false;
}
if (!GetOwner())
{
return false;
}
if (!GetOwner()->HasAuthority())
{
ServerPlaceBuildable(ActiveBuildClass, PlacementTransform);
return true;
}
ServerPlaceBuildable_Implementation(ActiveBuildClass, PlacementTransform);
return true;
}
void UAgrarianBuildingPlacementComponent::ServerPlaceBuildable_Implementation(TSubclassOf<AActor> BuildClass, FTransform PlacementTransform)
{
FText FailureReason;
if (!CanPlaceAtTransform(BuildClass, PlacementTransform, FailureReason) || !HasPlacementCost(FailureReason))
{
FailPlacement(FailureReason);
return;
}
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;
AActor* PlacedActor = GetWorld()->SpawnActor<AActor>(BuildClass, PlacementTransform, SpawnParams);
if (!PlacedActor)
{
FailPlacement(FText::FromString(TEXT("Buildable could not be placed.")));
return;
}
ConsumePlacementCost();
OnBuildPlaced.Broadcast(PlacedActor);
}
void UAgrarianBuildingPlacementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!bShowGhostPreview || !ActiveBuildClass || !GetWorld())
{
return;
}
FTransform PreviewTransform;
FText FailureReason;
const bool bCanPlace = GetPlacementPreviewState(PreviewTransform, FailureReason);
const FColor PreviewColor = bCanPlace ? ValidGhostPreviewColor : InvalidGhostPreviewColor;
const FVector PreviewExtent(
FMath::Max(PlacementProbeRadius, 25.0f),
FMath::Max(PlacementProbeRadius, 25.0f),
FMath::Max(PlacementProbeRadius * 0.5f, 25.0f));
DrawDebugBox(
GetWorld(),
PreviewTransform.GetLocation() + FVector(0.0f, 0.0f, PreviewExtent.Z),
PreviewExtent,
PreviewTransform.GetRotation(),
PreviewColor,
false,
GhostPreviewLifetimeSeconds,
0,
GhostPreviewLineThickness);
OnBuildPreviewUpdated.Broadcast(bCanPlace, PreviewTransform, FailureReason);
}
bool UAgrarianBuildingPlacementComponent::HasPlacementCost(FText& FailureReason) const
{
const UAgrarianInventoryComponent* Inventory = GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
if (!Inventory)
{
FailureReason = FText::FromString(TEXT("No inventory is available."));
return false;
}
for (const FAgrarianItemStack& Cost : PlacementCost)
{
if (!Inventory->HasItem(Cost.ItemId, Cost.Quantity))
{
FailureReason = FText::Format(
FText::FromString(TEXT("Missing build material: {0}")),
Cost.DisplayName.IsEmpty() ? FText::FromName(Cost.ItemId) : Cost.DisplayName);
return false;
}
}
return true;
}
void UAgrarianBuildingPlacementComponent::ConsumePlacementCost()
{
UAgrarianInventoryComponent* Inventory = GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
if (!Inventory)
{
return;
}
for (const FAgrarianItemStack& Cost : PlacementCost)
{
Inventory->RemoveItem(Cost.ItemId, Cost.Quantity);
}
}
void UAgrarianBuildingPlacementComponent::FailPlacement(const FText& Reason)
{
OnBuildPlacementFailed.Broadcast(Reason);
}
FVector UAgrarianBuildingPlacementComponent::SnapLocation(const FVector& Location) const
{
if (!bSnapToGrid || GridSize <= 0.0f)
{
return Location;
}
return FVector(
FMath::GridSnap(Location.X, GridSize),
FMath::GridSnap(Location.Y, GridSize),
Location.Z);
}