248 lines
6.9 KiB
C++
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);
|
|
}
|