Improvements to watering and some stat forge fixes

This commit is contained in:
2026-09-14 21:15:31 +02:00
parent f7e5adc1cc
commit aad09dbaa5
26 changed files with 282 additions and 151 deletions

View File

@@ -195,6 +195,7 @@ ExpIncreaseEffectClass=/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/G
GlobalSettingsDataSheet=/Game/GameData/CSV/GlobalKeyValueSettings.GlobalKeyValueSettings
ItemSeedCarouselClass=None
RemovableStaticMeshClass=/Script/Engine.BlueprintGeneratedClass'/Game/Prefabs/BP_RemovableStaticMeshActor.BP_RemovableStaticMeshActor_C'
MovementDisableEffectClass=/Script/Engine.BlueprintGeneratedClass'/Game/Blueprints/GameEffects/GSE_MovementDisabled.GSE_MovementDisabled_C'
[/Script/InteractionSystem.InteractionSettings]
DefaultInteractionWidgetClass=/Script/UMG.WidgetBlueprintGeneratedClass'/Game/UI/InteractionUI/WBP_Eleri_Interaction.WBP_Eleri_Interaction_C'

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -15,7 +15,7 @@ FActiveGameplayStatEffect FActiveGameplayStatEffect::CreateNew(UObject* SourceOb
ActiveEffect.GameplayEffectClass = Effect->GetClass();
ActiveEffect.StackCount = Stacks;
ActiveEffect.GameplayEffectInstance = Effect;
ActiveEffect.RemainingDuration = Effect->Duration;
ActiveEffect.RemainingDuration = Effect->GetDuration();
ActiveEffect.NextTickDuration = Effect->TickPeriod;
ActiveEffect.bIsTicking = ActiveEffect.NextTickDuration > 0.f;
ActiveEffect.Handle = FGuid::NewGuid();
@@ -192,7 +192,7 @@ FGuid UStatForgeComponent::ApplyGameplayStatEffect(const TSubclassOf<UGameplaySt
Effect->AppendDataValueMap(DataValueMap);
FGuid GuidToReturn;
if (Effect->Duration == GameplayStatEffectDuration_Instant)
if (Effect->DurationType == EDurationType::Instant)
{
Internal_ApplyGameplayStatEffect(Effect);
}
@@ -341,20 +341,23 @@ void UStatForgeComponent::TickEffects(float DeltaTime)
{
StatNames.Append(GetAffectedStatsFromEffect(ActiveEffectsArray[i].GameplayEffectInstance));
if (!CanTickEffect(ActiveEffectsArray[i].GameplayEffectInstance.Get()))
if (ActiveEffectsArray[i].GameplayEffectInstance->DurationType == EDurationType::Duration)
{
ActiveEffectsArray[i].RemainingDuration = 0;
}
else
{
ActiveEffectsArray[i].RemainingDuration -= DeltaTime;
}
if (!CanTickEffect(ActiveEffectsArray[i].GameplayEffectInstance.Get()))
{
ActiveEffectsArray[i].RemainingDuration = 0;
}
else
{
ActiveEffectsArray[i].RemainingDuration -= DeltaTime;
}
if (ActiveEffectsArray[i].RemainingDuration <= 0.f)
{
RemoveGameplayStatEffect(ActiveEffectsArray[i].Handle);
if (ActiveEffectsArray[i].RemainingDuration <= 0.f)
{
RemoveGameplayStatEffect(ActiveEffectsArray[i].Handle);
continue;
continue;
}
}
if (ActiveEffectsArray[i].GameplayEffectInstance->TickPeriod > 0.f)

View File

@@ -15,8 +15,18 @@ float UStatForgeFunctionLibrary::GetStatValue(const FName StatName, AActor* Acto
return StatForgeComponent->GetStatValue(StatName);
}
bool UStatForgeFunctionLibrary::HasTag(const FGameplayTag& Tag, AActor* Actor)
{
if (!Actor) return false;
const UStatForgeComponent* StatForgeComponent = Actor->GetComponentByClass<UStatForgeComponent>();
if(!StatForgeComponent) return false;
return StatForgeComponent->GetGameplayTags().HasTagExact(Tag);
}
FGuid UStatForgeFunctionLibrary::ApplyGameplayStatEffect(const TSubclassOf<UGameplayStatEffect> EffectClass,
TMap<FName, float> DataValueMap, AActor* Actor, int32 Stacks)
TMap<FName, float> DataValueMap, AActor* Actor, int32 Stacks)
{
if (!EffectClass || !Actor) return FGuid();

View File

@@ -9,6 +9,14 @@
class UGameplayStatEffect;
UENUM(BlueprintType)
enum class EDurationType : uint8
{
Instant,
Duration,
Infinite
};
UENUM(BlueprintType)
enum class EModOperation : uint8
{
@@ -64,9 +72,12 @@ class STATFORGE_API UGameplayStatEffect : public UObject
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Duration")
EDurationType DurationType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Duration", meta=(EditCondition = "DurationType==EDurationType::Duration", EditConditionHides))
float Duration = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Duration")
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Duration", meta=(EditCondition = "DurationType==EDurationType::Duration", EditConditionHides))
float TickPeriod = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modifiers")
@@ -78,16 +89,29 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags")
FGameplayTagContainer CharacterTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags|Requirements", meta = (EditCondition = "Duration==0"))
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags|Requirements", meta = (EditCondition = "DurationType!=EDurationType::Duration"))
FGameplayTagContainer TagsRequiredToApply;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags|Requirements", meta = (EditCondition = "Duration!=0"))
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags|Requirements", meta = (EditCondition = "DurationType==EDurationType::Duration"))
FGameplayTagContainer TagsRequiredToTick;
UFUNCTION(BlueprintCallable, Category = "Stat Forge|Effect")
void AppendDataValueMap(TMap<FName, float> InDataValueMap) { DataValueMap.Append(InDataValueMap); }
const TMap<FName, float>& GetDataValueMap() { return DataValueMap; }
float GetDuration() const
{
switch (DurationType)
{
case EDurationType::Instant:
return 0.f;
case EDurationType::Duration:
return Duration;
case EDurationType::Infinite:
return -1.f;
}
return 0.f;
}
protected:

View File

@@ -3,6 +3,7 @@
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "StatForgeFunctionLibrary.generated.h"
@@ -14,10 +15,13 @@ UCLASS()
class STATFORGE_API UStatForgeFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "Stat Forge")
static float GetStatValue(UPARAM(meta = (GetOptions = "StatForgeDeveloperSettings.GetStatNames")) const FName StatName, AActor* Actor);
UFUNCTION(BlueprintPure, Category = "Stat Forge")
static bool HasTag(UPARAM(ref) const FGameplayTag& Tag, AActor* Actor);
UFUNCTION(BlueprintCallable, Category = "Stat Forge", meta = (AutoCreateRefTerm = "DataValueMap"))
static FGuid ApplyGameplayStatEffect(const TSubclassOf<UGameplayStatEffect> EffectClass, TMap<FName, float> DataValueMap, AActor* Actor, int32 Stacks = 1);

View File

@@ -15,6 +15,7 @@ STATFORGE_MAKE_STAT(FriendPointsMultiplier)
STATFORGE_MAKE_STAT(AlchemyMultiplier)
STATFORGE_MAKE_STAT(BazarCustomerSpawnRate)
STATFORGE_MAKE_STAT(BazarCustomerWill)
STATFORGE_MAKE_STAT(WateringAreaScaleMultiplier)
STATFORGE_MAKE_STAT(AlchemyXp)
STATFORGE_MAKE_STAT(BotanyXp)

View File

@@ -17,6 +17,7 @@ STATFORGE_DECLARE_STAT(FriendPointsMultiplier)
STATFORGE_DECLARE_STAT(AlchemyMultiplier)
STATFORGE_DECLARE_STAT(BazarCustomerSpawnRate)
STATFORGE_DECLARE_STAT(BazarCustomerWill)
STATFORGE_DECLARE_STAT(WateringAreaScaleMultiplier)
STATFORGE_DECLARE_STAT(AlchemyXp)
STATFORGE_DECLARE_STAT(BotanyXp)

View File

@@ -1,14 +1,12 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "WaterBall.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"
#include "DrawDebugHelpers.h"
#include "Engine/OverlapResult.h"
#include "Engine/World.h"
#include "ProjectEleri/Items/SeedCarousel.h"
#include "Helpers/StatForgeFunctionLibrary.h"
#include "ProjectEleri/Data/StatDefinitions.h"
// Sets default values
AWaterBall::AWaterBall()
@@ -27,18 +25,14 @@ void AWaterBall::BeginPlay()
Super::BeginPlay();
PlayerCharacter = Cast<AMyCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
PlayerCameraManager = Cast<APlayerCameraManager>(UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0));
MaxPitch = FMath::Abs(PlayerCameraManager->ViewPitchMin) + PlayerCameraManager->ViewPitchMax;
if (MaxPitch == 0) MaxPitch = 1;
PitchOffset = FMath::Abs(PlayerCameraManager->ViewPitchMax);
HighlightBox->SetStaticMesh(HighlightBoxMesh);
HighlightBox->SetVisibility(true);
HighlightBox->SetMaterial(0, HighlightBoxMaterial);
HighlightBox->SetRelativeScale3D(FVector(10.0f, 10.0f, 10.0f));
HighlightBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
ActorsToIgnore.Add(this);
ActorsToIgnore.Add(PlayerCharacter);
const float ScaleMultiplier = FMath::Min(UStatForgeFunctionLibrary::GetStatValue(FStat::WateringAreaScaleMultiplier, PlayerCharacter), 1.f);
HighlightBox->SetRelativeScale3D(FVector(5.0f * ScaleMultiplier));
HighlightBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
// Called every frame
@@ -52,64 +46,14 @@ void AWaterBall::Tick(float DeltaTime)
HighlightBox->SetVisibility(false);
if (!PlayerCharacter->IsWatering()) return;
CurrentPitch = PlayerCharacter->Pitch;
CurrentPitch += PitchOffset;
if (CurrentPitch > 100.0f)
{
CurrentPitch -= 360.0f;
}
FPredictProjectilePathParams PredictParams;
float Drag = FMath::Lerp(4.0f, 1.1f, FMath::Abs(CurrentPitch / MaxPitch));
FVector ForwardVec = UKismetMathLibrary::FindLookAtRotation(PlayerCharacter->GetCameraWorldPosition(), PlayerCharacter->GetActorLocation()).Quaternion().GetForwardVector();
FVector Offset = (ForwardVec * FVector(2000.f, 2000.f, 0.f));
FVector EndLocation = PlayerCharacter->GetActorLocation() + Offset;
FVector StartingLoc = PlayerCharacter->GetActorLocation();
PredictParams.StartLocation = StartingLoc;
LaunchVelocity =
UKismetMathLibrary::GetDirectionUnitVector(StartingLoc, EndLocation) * FVector(500.0f * Drag, 500.0f * Drag, -30000.f);
PredictParams.LaunchVelocity = LaunchVelocity;
PredictParams.MaxSimTime = 10.0f;
PredictParams.bTraceWithCollision = true;
PredictParams.SimFrequency = 10.0f;
PredictParams.bTraceComplex = true;
for (int32 i = 0; i < ActorsToIgnore.Num(); i++)
{
PredictParams.ActorsToIgnore.Add(ActorsToIgnore[i]);
}
FPredictProjectilePathResult PredictResults;
UGameplayStatics::PredictProjectilePath(GetWorld(), PredictParams, PredictResults);
HighlightBox->SetVisibility(true);
FVector HighlightBoxLocation = PredictResults.PathData[PredictResults.PathData.Num() - 1].Location;
HighlightBoxLocation.Z += 5;
HighlightBox->SetWorldLocation(HighlightBoxLocation);
const FVector ForwardVec = UKismetMathLibrary::FindLookAtRotation(PlayerCharacter->GetCameraWorldPosition(), PlayerCharacter->GetActorLocation()).Quaternion().GetForwardVector();
const FVector Offset = (ForwardVec * FVector(700.f, 700.f, 0.f)) + FVector(0.f, 0.f, -115.f);
HighlightBox->SetWorldLocation(PlayerCharacter->GetActorLocation() + Offset);
}
TArray<ASeedCarousel*> AWaterBall::GetOverlappingSeeds()
{
TArray<ASeedCarousel*> Seeds;
TArray<FOverlapResult> OverlapResults;
FCollisionObjectQueryParams OverlapParams;
OverlapParams.AddObjectTypesToQuery(ECC_WorldDynamic);
FVector BoundsMin, BoundsMax;
HighlightBox->GetLocalBounds(BoundsMin, BoundsMax);
float Radius = (FVector::Distance(BoundsMin, BoundsMax) * HighlightBox->GetComponentScale().X) / PI;
GetWorld()->OverlapMultiByObjectType(OverlapResults, HighlightBox->GetComponentLocation(), FQuat::Identity, OverlapParams, FCollisionShape::MakeSphere(Radius));
//DrawDebugSphere(GetWorld(), HighlightBox->GetComponentLocation(), Radius, 16, FColor::Red, false, 5.f);
for (const FOverlapResult& OverlapResult : OverlapResults)
{
if (ASeedCarousel* Carousel = Cast<ASeedCarousel>(OverlapResult.GetActor()))
{
Seeds.Add(Carousel);
}
}
return Seeds;
}
void AWaterBall::ThrowBall_Implementation()
{

View File

@@ -36,10 +36,6 @@ protected:
FVector LaunchVelocity;
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<AActor*> ActorsToIgnore;
UPROPERTY(BlueprintReadWrite)
AMyCharacter* PlayerCharacter;
@@ -49,15 +45,6 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
UMaterialInstance* HighlightBoxMaterial;
UPROPERTY(BlueprintReadWrite)
float CurrentPitch;
UPROPERTY(BlueprintReadWrite)
float PitchOffset;
UPROPERTY(BlueprintReadWrite)
float MaxPitch;
UPROPERTY(BlueprintReadWrite)
bool bThrowing;
@@ -79,6 +66,4 @@ public:
UFUNCTION(BlueprintCallable)
FVector GetLaunchVelocity() { return LaunchVelocity; }
UFUNCTION(BlueprintCallable)
TArray<ASeedCarousel*> GetOverlappingSeeds();
};

View File

@@ -357,6 +357,68 @@ void ASeedBedActor::WaterAllSeedSlots()
WaterSeedInSlot(SlotCoordinate);
}
}
int32 ASeedBedActor::GatherSlotsInRadius(const FVector& WorldLocation, float Radius, TArray<FIntVector2>& OutSlotCoordinates) const
{
OutSlotCoordinates.Reset();
// A radius of zero only ever covers the point itself, so there is nothing to gather
if (Radius <= 0.f)
{
return 0;
}
const float RadiusSquared = FMath::Square(Radius);
for (const TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
{
// Only living plants are watered, an empty or dead slot has nothing to soak up
if (!SlotHasLivingPlant(SlotPair.Value))
{
continue;
}
FVector SlotWorldLocation;
if (!GetSlotWorldLocation(SlotPair.Key, SlotWorldLocation))
{
continue;
}
// Only the plane of the bed counts, so watering from above or from uphill reaches the same slots
if (FVector::DistSquaredXY(WorldLocation, SlotWorldLocation) > RadiusSquared)
{
continue;
}
OutSlotCoordinates.Add(SlotPair.Key);
}
return OutSlotCoordinates.Num();
}
int32 ASeedBedActor::WaterSlotsInRadius(const FVector& WorldLocation, float Radius)
{
// The slots are gathered first, a listener of the refresh is free to change the map while we water
TArray<FIntVector2> SlotCoordinates;
const int32 WateredSlotCount = GatherSlotsInRadius(WorldLocation, Radius, SlotCoordinates);
for (const FIntVector2& SlotCoordinate : SlotCoordinates)
{
WaterSeedInSlot(SlotCoordinate);
}
// The cache is what the game state falls back to once the bed is streamed out, so it has to know
// about the watering right away. Nothing was watered, so there is nothing to cache either.
if (WateredSlotCount > 0)
{
if (AEleriGameState* GameState = Cast<AEleriGameState>(UGameplayStatics::GetGameState(this)))
{
GameState->CacheSeedData(SeedBedIndex, PrepareSeedSaveData());
}
}
return WateredSlotCount;
}
//------------------------------------------------------------------------------------------------
// GRID GENERATION
//------------------------------------------------------------------------------------------------
@@ -623,7 +685,12 @@ void ASeedBedActor::HighlightSlot(FIntVector2 SlotCoordinate)
void ASeedBedActor::StopHighlightingSlot()
{
HighlightDecal->SetVisibility(false);
// The decal only exists once BeginPlay created it, a highlight is stopped on beds that never got one as well
if (IsValid(HighlightDecal))
{
HighlightDecal->SetVisibility(false);
}
HighlightedSlotIndex = FIntVector2::NoneValue;
}
@@ -650,6 +717,14 @@ int32 ASeedBedActor::GetFreeSlotCount() const
return FreeSlotCount;
}
bool ASeedBedActor::FindClosestFreeSlot(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate) const
{
float DistanceSquared = 0.f;
// The distance of the slot is only interesting for the planting highlight, the plain lookup ignores it
return FindClosestFreeSlotWithDistance(WorldLocation, OutSlotCoordinate, DistanceSquared);
}
bool ASeedBedActor::FindClosestFreeSlotWithDistance(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate, float& OutDistanceSquared) const
{
bool bFoundSlot = false;
float ClosestDistanceSquared = 0.f;
@@ -667,7 +742,8 @@ bool ASeedBedActor::FindClosestFreeSlot(const FVector& WorldLocation, FIntVector
continue;
}
const float DistanceSquared = FVector::DistSquared(WorldLocation, SlotWorldLocation);
// Only the plane of the bed counts, a player standing uphill has to reach the slot just as well
const float DistanceSquared = FVector::DistSquaredXY(WorldLocation, SlotWorldLocation);
if (!bFoundSlot || DistanceSquared < ClosestDistanceSquared)
{
bFoundSlot = true;
@@ -676,6 +752,8 @@ bool ASeedBedActor::FindClosestFreeSlot(const FVector& WorldLocation, FIntVector
}
}
OutDistanceSquared = ClosestDistanceSquared;
return bFoundSlot;
}

View File

@@ -196,10 +196,24 @@ public:
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
void WaterAllSeedSlots();
/** Waters every planted slot whose world location lies within the given radius around a world point.
* Returns the number of slots that were watered and caches the new slot data into the game state. */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
int32 WaterSlotsInRadius(const FVector& WorldLocation, float Radius);
/** Collects the coordinates of every planted slot whose world location lies within the given radius.
* Returns the number of collected slots, OutSlotCoordinates is emptied first. */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
int32 GatherSlotsInRadius(const FVector& WorldLocation, float Radius, TArray<FIntVector2>& OutSlotCoordinates) const;
/** Finds the free slot that is closest to the given world location */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
bool FindClosestFreeSlot(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate) const;
/** Same as FindClosestFreeSlot, but also reports how far away the slot is, as a squared distance in XY */
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
bool FindClosestFreeSlotWithDistance(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate, float& OutDistanceSquared) const;
/** World location a plant of the given slot is placed at */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
bool GetSlotWorldLocation(FIntVector2 SlotCoordinate, FVector& OutWorldLocation) const;

View File

@@ -23,7 +23,12 @@
#include "Subsystem/InteractionSubsystem.h"
#include "Interface/InteractableActorInterface.h"
#include "EngineUtils.h"
#include "Components/StatForgeComponent.h"
#include "Helpers/StatForgeFunctionLibrary.h"
#include "ProjectEleri/Data/StatDefinitions.h"
#include "ProjectEleri/Items/SeedBedActor.h"
#include "ProjectEleri/Settings/EleriGameSettings.h"
#include "ProjectEleri/System/EleriGameplayTags.h"
AEleriPlayerController::AEleriPlayerController(const FObjectInitializer &ObjectInitializer)
: Super(ObjectInitializer)
@@ -53,6 +58,30 @@ void AEleriPlayerController::BeginPlay()
AlchemyManager = Cast<AAlchemyManager>(UGameplayStatics::GetActorOfClass(GetWorld(), AAlchemyManager::StaticClass()));
SelectionManagerInventorySlots = NewObject<USelectionManager>();
ToggleMovement(true);
}
void AEleriPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
ToggleMovement(true);
Super::EndPlay(EndPlayReason);
}
void AEleriPlayerController::ToggleMovement(bool bActive)
{
UStatForgeComponent* StatForgeComponent = UMainBlueprintFunctionLibrary::GetStatForgeComponentFromPlayer(this);
if (!StatForgeComponent) return;
if (bActive)
{
StatForgeComponent->RemoveGameplayStatEffect(MovementDisabledEffect);
return;
}
const UEleriGameSettings* GameSettings = UEleriGameSettings::GetEleriGameSettings();
ensure(GameSettings);
MovementDisabledEffect = StatForgeComponent->ApplyGameplayStatEffect(GameSettings->MovementDisableEffectClass, TMap<FName, float>());
}
void AEleriPlayerController::Tick(float DeltaTime)
@@ -332,19 +361,20 @@ void AEleriPlayerController::OnMove(const FInputActionValue &Value)
if (IsInMenu())
return;
FVector2D AnalogueValue = Value.Get<FInputActionValue::Axis2D>();
const FVector2D AnalogueValue = Value.Get<FInputActionValue::Axis2D>();
MovementDirection = AnalogueValue;
if (PlayerCharacter)
{
FRotator CtrlRotation = GetControlRotation();
FRotator YawRotation(0, CtrlRotation.Yaw, 0);
if (UStatForgeFunctionLibrary::HasTag(CharacterState::MovementDisabled, PlayerCharacter))
{
return;
}
// FVector ForwardFacing = UKismetMathLibrary::GetForwardVector(bFlying? PlayerCameraManager->GetCameraRotation() : CharRotForward);
float AppliedMovementVelocity = 1.f; // MovementVelocity * (bFlying? 0.5f : 1.f);
const float AppliedMovementVelocity = UStatForgeFunctionLibrary::GetStatValue(FStat::MovementSpeedMultiplier, PlayerCharacter);
FVector ForwardVec = PlayerCharacter->GetPlayerCamera()->GetForwardVector();
FVector RightVec = PlayerCharacter->GetPlayerCamera()->GetRightVector();
const FVector ForwardVec = PlayerCharacter->GetPlayerCamera()->GetForwardVector();
const FVector RightVec = PlayerCharacter->GetPlayerCamera()->GetRightVector();
PlayerCharacter->AddMovementInput(ForwardVec, MovementDirection.Y * AppliedMovementVelocity);
PlayerCharacter->AddMovementInput(RightVec, MovementDirection.X * AppliedMovementVelocity);
@@ -446,10 +476,13 @@ void AEleriPlayerController::OnWateringActionStart(const FInputActionValue &Valu
MainGameUI->IsAlchemyOpen() ||
IsInMenu())
return;
Watering = true;
PlayerCharacter->BeginWatering();
OnWatering.Broadcast(Watering);
OnChangeInputContext.Broadcast(2);
ToggleMovement(false);
}
void AEleriPlayerController::OnWateringActionEnd(const FInputActionValue &Value)
@@ -463,6 +496,8 @@ void AEleriPlayerController::OnWateringActionEnd(const FInputActionValue &Value)
PlayerCharacter->EndWatering();
OnWatering.Broadcast(Watering);
OnChangeInputContext.Broadcast(1);
ToggleMovement(true);
}
void AEleriPlayerController::OnWateringThrowAction(const FInputActionValue &Value)

View File

@@ -128,19 +128,38 @@ void AMyCharacter::Tick(float DeltaTime)
if (bIsPlanting)
{
// The reach is measured from the closest free slot and not from the center of the bed: a bed is as big as
// its grid, so its outer slots sit up to 1273 units away from that center and would never be in reach
const float SlotReachSquared = FMath::Square(200.f);
ASeedBedActor* ClosestActor = nullptr;
float ClosestDistance = 9999999999.f;
FIntVector2 ClosestSlotCoordinate = FIntVector2::NoneValue;
float ClosestSlotDistance = SlotReachSquared;
const FVector PointToCheck = GetActorLocation() + (GetActorForwardVector() * 100.f);
const float ThresholdDist = FMath::Pow(1000.f, 2.f);
for (ASeedBedActor* SeedBeds : SeedBedActors)
{
const double Dist = FVector::DistSquaredXY(PointToCheck, SeedBeds->GetActorLocation());
if (Dist <= ClosestDistance && Dist <= ThresholdDist)
if (!IsValid(SeedBeds))
{
ClosestDistance = Dist;
ClosestActor = SeedBeds;
continue;
}
FIntVector2 OutSlotCoordinate;
float OutSlotDistance = 0.f;
if (!SeedBeds->FindClosestFreeSlotWithDistance(PointToCheck, OutSlotCoordinate, OutSlotDistance))
{
continue;
}
// A bed is only a candidate while its closest free slot is in reach, the closest slot wins
if (OutSlotDistance > ClosestSlotDistance)
{
continue;
}
ClosestSlotDistance = OutSlotDistance;
ClosestSlotCoordinate = OutSlotCoordinate;
ClosestActor = SeedBeds;
}
if (LastClosestSeedBedActor)
@@ -150,11 +169,7 @@ void AMyCharacter::Tick(float DeltaTime)
if (ClosestActor)
{
FIntVector2 OutSlotCoordinate;
if (ClosestActor->FindClosestFreeSlot(PointToCheck, OutSlotCoordinate))
{
ClosestActor->HighlightSlot(OutSlotCoordinate);
}
ClosestActor->HighlightSlot(ClosestSlotCoordinate);
}
LastClosestSeedBedActor = ClosestActor;

View File

@@ -102,14 +102,23 @@ private:
UPROPERTY()
USelectionManager* SelectionManagerInventorySlots;
UPROPERTY()
FGuid MovementDisabledEffect;
public:
UPROPERTY()
bool PlacingStuff;
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
// Enable or disable movement with a character tag
UFUNCTION(BlueprintCallable)
void ToggleMovement(bool bActive);
UFUNCTION(BlueprintCallable, BlueprintPure)
FORCEINLINE USelectionManager* GetSelectionManagerInventorySlot() { return SelectionManagerInventorySlots; }

View File

@@ -39,6 +39,8 @@ public:
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Ability System|Effects")
TSubclassOf<UGameplayStatEffect> ExpIncreaseEffectClass;
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Ability System|Effects")
TSubclassOf<UGameplayStatEffect> MovementDisableEffectClass;
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Ability System|Professions")
TArray<float> ProfessionExpArray;

View File

@@ -1,11 +1,18 @@
#include "EleriGameplayTags.h"
namespace TimeOfDay {
namespace CharacterState
{
UE_DEFINE_GAMEPLAY_TAG(MovementDisabled, TEXT("CharacterState.MovementDisabled"))
}
namespace TimeOfDay
{
UE_DEFINE_GAMEPLAY_TAG(TimePassed, TEXT("TimeOfDay.TimePassed"))
}
namespace Stats {
namespace Stats
{
UE_DEFINE_GAMEPLAY_TAG(Energy, TEXT("Stats.Energy"))
UE_DEFINE_GAMEPLAY_TAG(TimesEatenToday, TEXT("Stats.TimesEatenToday"))
UE_DEFINE_GAMEPLAY_TAG(FriendPointsObtainAmount, TEXT("Stats.FriendPointsObtainAmount"))

View File

@@ -2,11 +2,18 @@
#pragma once
#include "NativeGameplayTags.h"
namespace TimeOfDay {
namespace CharacterState
{
PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(MovementDisabled)
}
namespace TimeOfDay
{
PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TimePassed)
}
namespace Stats {
namespace Stats
{
PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Energy)
PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TimesEatenToday)
PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(FriendPointsObtainAmount)