From aad09dbaa5f33acbc2c5a952d7e8c5066483a9b4 Mon Sep 17 00:00:00 2001 From: "adnan.mujkic" Date: Mon, 14 Sep 2026 21:15:31 +0200 Subject: [PATCH] Improvements to watering and some stat forge fixes --- Config/DefaultGame.ini | 1 + .../GameEffects/GSE_MovementDisabled.uasset | 3 + Content/Materials/M_SquareHighlight.uasset | 4 +- .../Materials/M_SquareHighlight_Inst.uasset | 4 +- Content/Prefabs/BP_WaterBall.uasset | 4 +- .../Seed/E_PossibleSEEDDialogueOptions.uasset | 3 - .../InventorySystem/Seed/InfoDisplay.uasset | 3 - .../InventorySystem/Seed/SeedObjectBP.uasset | 3 - .../InventorySystem/Seed/TypeOfGround.uasset | 3 - .../Blueprints/BP_ThirdPersonCharacter.uasset | 4 +- .../Private/Components/StatForgeComponent.cpp | 31 +++---- .../Helpers/StatForgeFunctionLibrary.cpp | 12 ++- .../Public/Data/GameplayStatEffect.h | 32 +++++++- .../Public/Helpers/StatForgeFunctionLibrary.h | 6 +- Source/ProjectEleri/Data/StatDefinitions.cpp | 1 + Source/ProjectEleri/Data/StatDefinitions.h | 1 + Source/ProjectEleri/GameObjects/WaterBall.cpp | 76 +++-------------- Source/ProjectEleri/GameObjects/WaterBall.h | 15 ---- Source/ProjectEleri/Items/SeedBedActor.cpp | 82 ++++++++++++++++++- Source/ProjectEleri/Items/SeedBedActor.h | 14 ++++ .../Private/EleriPlayerController.cpp | 51 ++++++++++-- Source/ProjectEleri/Private/MyCharacter.cpp | 45 ++++++---- .../Public/EleriPlayerController.h | 9 ++ .../ProjectEleri/Settings/EleriGameSettings.h | 2 + .../ProjectEleri/System/EleriGameplayTags.cpp | 11 ++- .../ProjectEleri/System/EleriGameplayTags.h | 13 ++- 26 files changed, 282 insertions(+), 151 deletions(-) create mode 100644 Content/Blueprints/GameEffects/GSE_MovementDisabled.uasset delete mode 100644 Content/Scripts/InventorySystem/Seed/E_PossibleSEEDDialogueOptions.uasset delete mode 100644 Content/Scripts/InventorySystem/Seed/InfoDisplay.uasset delete mode 100644 Content/Scripts/InventorySystem/Seed/SeedObjectBP.uasset delete mode 100644 Content/Scripts/InventorySystem/Seed/TypeOfGround.uasset diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 064b154c..47251d16 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -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' diff --git a/Content/Blueprints/GameEffects/GSE_MovementDisabled.uasset b/Content/Blueprints/GameEffects/GSE_MovementDisabled.uasset new file mode 100644 index 00000000..834dcc98 --- /dev/null +++ b/Content/Blueprints/GameEffects/GSE_MovementDisabled.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1832a7ebaff23bec9f2e40094c84bac42f8b14327ca0c6fa2e426e2666791118 +size 7105 diff --git a/Content/Materials/M_SquareHighlight.uasset b/Content/Materials/M_SquareHighlight.uasset index 057c866f..2ae2f7f5 100644 --- a/Content/Materials/M_SquareHighlight.uasset +++ b/Content/Materials/M_SquareHighlight.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:800b7d1083afa20439aa5561f27f66c324556efbcf5d0d1e9dc19e74bd22025a -size 16732 +oid sha256:f2677234c738bc58c281a87740a64b93001e732348e83f28a1b5be6961b0ada3 +size 21364 diff --git a/Content/Materials/M_SquareHighlight_Inst.uasset b/Content/Materials/M_SquareHighlight_Inst.uasset index 50589e58..3f608673 100644 --- a/Content/Materials/M_SquareHighlight_Inst.uasset +++ b/Content/Materials/M_SquareHighlight_Inst.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5c37fc20671de01260b17bd3d2ae20938bf4b35414f8db7298bf2717c3e4217 -size 9120 +oid sha256:eaeee71a4b1d166ac4b19224ee2760603bdfe09cd5aa833d5e59dec8a742d07e +size 9583 diff --git a/Content/Prefabs/BP_WaterBall.uasset b/Content/Prefabs/BP_WaterBall.uasset index c413dc4b..4511935f 100644 --- a/Content/Prefabs/BP_WaterBall.uasset +++ b/Content/Prefabs/BP_WaterBall.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b013843e425daee3f75b583a1f4271cdbbbd8bff531797329d0d04ea8ece2c20 -size 381271 +oid sha256:9bf8a587170fefde6d2e3946014a6e9987b02037390abff035eca52d243d2c1a +size 366962 diff --git a/Content/Scripts/InventorySystem/Seed/E_PossibleSEEDDialogueOptions.uasset b/Content/Scripts/InventorySystem/Seed/E_PossibleSEEDDialogueOptions.uasset deleted file mode 100644 index d940d7a5..00000000 --- a/Content/Scripts/InventorySystem/Seed/E_PossibleSEEDDialogueOptions.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a066707d77e58a8d7ba9878502a12fc1ba17467617ccf8b56f25a64ffcce9d1b -size 3665 diff --git a/Content/Scripts/InventorySystem/Seed/InfoDisplay.uasset b/Content/Scripts/InventorySystem/Seed/InfoDisplay.uasset deleted file mode 100644 index d577496e..00000000 --- a/Content/Scripts/InventorySystem/Seed/InfoDisplay.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77d1494a59c7a960df7e6a1c27a582abcd793ec912746e2c7ce67c5ab4072fba -size 24699 diff --git a/Content/Scripts/InventorySystem/Seed/SeedObjectBP.uasset b/Content/Scripts/InventorySystem/Seed/SeedObjectBP.uasset deleted file mode 100644 index aa9ef718..00000000 --- a/Content/Scripts/InventorySystem/Seed/SeedObjectBP.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bae09ca5aad768ce3210b163c2cc8491d94435012bca8ee2150c4fa352f8ad65 -size 112405 diff --git a/Content/Scripts/InventorySystem/Seed/TypeOfGround.uasset b/Content/Scripts/InventorySystem/Seed/TypeOfGround.uasset deleted file mode 100644 index 791acba3..00000000 --- a/Content/Scripts/InventorySystem/Seed/TypeOfGround.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3bb4d70331081ade4123031e374c0eb61c0d40562c43f4d1c839bdbdd5f03a83 -size 2237 diff --git a/Content/ThirdPersonBP/Blueprints/BP_ThirdPersonCharacter.uasset b/Content/ThirdPersonBP/Blueprints/BP_ThirdPersonCharacter.uasset index 2e6dc3fa..f6fed70e 100644 --- a/Content/ThirdPersonBP/Blueprints/BP_ThirdPersonCharacter.uasset +++ b/Content/ThirdPersonBP/Blueprints/BP_ThirdPersonCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1de15680f7c2462104225a429497b27f20e5d32ff397300525d8f9dfe009875 -size 1291979 +oid sha256:0b1d824e6b82c17d14d427b4cd3d3aab279fdf523646d62bc3af7bfb1cf4daf1 +size 1292362 diff --git a/Plugins/StatForge/Source/StatForge/Private/Components/StatForgeComponent.cpp b/Plugins/StatForge/Source/StatForge/Private/Components/StatForgeComponent.cpp index bac009a8..0e42cc2f 100644 --- a/Plugins/StatForge/Source/StatForge/Private/Components/StatForgeComponent.cpp +++ b/Plugins/StatForge/Source/StatForge/Private/Components/StatForgeComponent.cpp @@ -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 TSubclassOfAppendDataValueMap(DataValueMap); FGuid GuidToReturn; - if (Effect->Duration == GameplayStatEffectDuration_Instant) + if (Effect->DurationType == EDurationType::Instant) { Internal_ApplyGameplayStatEffect(Effect); } @@ -340,21 +340,24 @@ void UStatForgeComponent::TickEffects(float DeltaTime) for (int32 i = ActiveEffectsArray.Num() - 1; i >= 0; --i) { StatNames.Append(GetAffectedStatsFromEffect(ActiveEffectsArray[i].GameplayEffectInstance)); - - if (!CanTickEffect(ActiveEffectsArray[i].GameplayEffectInstance.Get())) - { - ActiveEffectsArray[i].RemainingDuration = 0; - } - else - { - ActiveEffectsArray[i].RemainingDuration -= DeltaTime; - } - if (ActiveEffectsArray[i].RemainingDuration <= 0.f) + if (ActiveEffectsArray[i].GameplayEffectInstance->DurationType == EDurationType::Duration) { - RemoveGameplayStatEffect(ActiveEffectsArray[i].Handle); + 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); - continue; + continue; + } } if (ActiveEffectsArray[i].GameplayEffectInstance->TickPeriod > 0.f) diff --git a/Plugins/StatForge/Source/StatForge/Private/Helpers/StatForgeFunctionLibrary.cpp b/Plugins/StatForge/Source/StatForge/Private/Helpers/StatForgeFunctionLibrary.cpp index 1c39a074..a0f94bbb 100644 --- a/Plugins/StatForge/Source/StatForge/Private/Helpers/StatForgeFunctionLibrary.cpp +++ b/Plugins/StatForge/Source/StatForge/Private/Helpers/StatForgeFunctionLibrary.cpp @@ -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(); + if(!StatForgeComponent) return false; + + return StatForgeComponent->GetGameplayTags().HasTagExact(Tag); +} + FGuid UStatForgeFunctionLibrary::ApplyGameplayStatEffect(const TSubclassOf EffectClass, - TMap DataValueMap, AActor* Actor, int32 Stacks) + TMap DataValueMap, AActor* Actor, int32 Stacks) { if (!EffectClass || !Actor) return FGuid(); diff --git a/Plugins/StatForge/Source/StatForge/Public/Data/GameplayStatEffect.h b/Plugins/StatForge/Source/StatForge/Public/Data/GameplayStatEffect.h index be80c248..9a4dfadb 100644 --- a/Plugins/StatForge/Source/StatForge/Public/Data/GameplayStatEffect.h +++ b/Plugins/StatForge/Source/StatForge/Public/Data/GameplayStatEffect.h @@ -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,17 +89,30 @@ 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 InDataValueMap) { DataValueMap.Append(InDataValueMap); } const TMap& 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: UPROPERTY() diff --git a/Plugins/StatForge/Source/StatForge/Public/Helpers/StatForgeFunctionLibrary.h b/Plugins/StatForge/Source/StatForge/Public/Helpers/StatForgeFunctionLibrary.h index 5657a594..95432e82 100644 --- a/Plugins/StatForge/Source/StatForge/Public/Helpers/StatForgeFunctionLibrary.h +++ b/Plugins/StatForge/Source/StatForge/Public/Helpers/StatForgeFunctionLibrary.h @@ -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 EffectClass, TMap DataValueMap, AActor* Actor, int32 Stacks = 1); diff --git a/Source/ProjectEleri/Data/StatDefinitions.cpp b/Source/ProjectEleri/Data/StatDefinitions.cpp index 6e41d0e1..9d8bfd53 100644 --- a/Source/ProjectEleri/Data/StatDefinitions.cpp +++ b/Source/ProjectEleri/Data/StatDefinitions.cpp @@ -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) diff --git a/Source/ProjectEleri/Data/StatDefinitions.h b/Source/ProjectEleri/Data/StatDefinitions.h index d8e75b69..444566db 100644 --- a/Source/ProjectEleri/Data/StatDefinitions.h +++ b/Source/ProjectEleri/Data/StatDefinitions.h @@ -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) diff --git a/Source/ProjectEleri/GameObjects/WaterBall.cpp b/Source/ProjectEleri/GameObjects/WaterBall.cpp index 09ec669d..e801f048 100644 --- a/Source/ProjectEleri/GameObjects/WaterBall.cpp +++ b/Source/ProjectEleri/GameObjects/WaterBall.cpp @@ -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(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0)); PlayerCameraManager = Cast(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)); + + const float ScaleMultiplier = FMath::Min(UStatForgeFunctionLibrary::GetStatValue(FStat::WateringAreaScaleMultiplier, PlayerCharacter), 1.f); + HighlightBox->SetRelativeScale3D(FVector(5.0f * ScaleMultiplier)); HighlightBox->SetCollisionEnabled(ECollisionEnabled::NoCollision); - - ActorsToIgnore.Add(this); - ActorsToIgnore.Add(PlayerCharacter); } // 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 AWaterBall::GetOverlappingSeeds() -{ - TArray Seeds; - TArray 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(OverlapResult.GetActor())) - { - Seeds.Add(Carousel); - } - } - - return Seeds; -} void AWaterBall::ThrowBall_Implementation() { diff --git a/Source/ProjectEleri/GameObjects/WaterBall.h b/Source/ProjectEleri/GameObjects/WaterBall.h index 32f80213..547083e1 100644 --- a/Source/ProjectEleri/GameObjects/WaterBall.h +++ b/Source/ProjectEleri/GameObjects/WaterBall.h @@ -36,10 +36,6 @@ protected: FVector LaunchVelocity; public: - - UPROPERTY(EditAnywhere, BlueprintReadWrite) - TArray 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 GetOverlappingSeeds(); }; diff --git a/Source/ProjectEleri/Items/SeedBedActor.cpp b/Source/ProjectEleri/Items/SeedBedActor.cpp index 41df2b13..884dd724 100644 --- a/Source/ProjectEleri/Items/SeedBedActor.cpp +++ b/Source/ProjectEleri/Items/SeedBedActor.cpp @@ -357,6 +357,68 @@ void ASeedBedActor::WaterAllSeedSlots() WaterSeedInSlot(SlotCoordinate); } } + +int32 ASeedBedActor::GatherSlotsInRadius(const FVector& WorldLocation, float Radius, TArray& 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& 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 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(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; } diff --git a/Source/ProjectEleri/Items/SeedBedActor.h b/Source/ProjectEleri/Items/SeedBedActor.h index b637dec4..ea1b01bb 100644 --- a/Source/ProjectEleri/Items/SeedBedActor.h +++ b/Source/ProjectEleri/Items/SeedBedActor.h @@ -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& 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; diff --git a/Source/ProjectEleri/Private/EleriPlayerController.cpp b/Source/ProjectEleri/Private/EleriPlayerController.cpp index bff6baa2..0a3c3aa4 100644 --- a/Source/ProjectEleri/Private/EleriPlayerController.cpp +++ b/Source/ProjectEleri/Private/EleriPlayerController.cpp @@ -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(UGameplayStatics::GetActorOfClass(GetWorld(), AAlchemyManager::StaticClass())); SelectionManagerInventorySlots = NewObject(); + 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()); } void AEleriPlayerController::Tick(float DeltaTime) @@ -332,19 +361,20 @@ void AEleriPlayerController::OnMove(const FInputActionValue &Value) if (IsInMenu()) return; - FVector2D AnalogueValue = Value.Get(); + const FVector2D AnalogueValue = Value.Get(); MovementDirection = AnalogueValue; if (PlayerCharacter) { - FRotator CtrlRotation = GetControlRotation(); - FRotator YawRotation(0, CtrlRotation.Yaw, 0); + if (UStatForgeFunctionLibrary::HasTag(CharacterState::MovementDisabled, PlayerCharacter)) + { + return; + } + + const float AppliedMovementVelocity = UStatForgeFunctionLibrary::GetStatValue(FStat::MovementSpeedMultiplier, PlayerCharacter); - // FVector ForwardFacing = UKismetMathLibrary::GetForwardVector(bFlying? PlayerCameraManager->GetCameraRotation() : CharRotForward); - float AppliedMovementVelocity = 1.f; // MovementVelocity * (bFlying? 0.5f : 1.f); - - 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) diff --git a/Source/ProjectEleri/Private/MyCharacter.cpp b/Source/ProjectEleri/Private/MyCharacter.cpp index 490171a4..be8570ba 100644 --- a/Source/ProjectEleri/Private/MyCharacter.cpp +++ b/Source/ProjectEleri/Private/MyCharacter.cpp @@ -128,35 +128,50 @@ 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) { LastClosestSeedBedActor->StopHighlightingSlot(); } - + if (ClosestActor) { - FIntVector2 OutSlotCoordinate; - if (ClosestActor->FindClosestFreeSlot(PointToCheck, OutSlotCoordinate)) - { - ClosestActor->HighlightSlot(OutSlotCoordinate); - } + ClosestActor->HighlightSlot(ClosestSlotCoordinate); } - + LastClosestSeedBedActor = ClosestActor; } } diff --git a/Source/ProjectEleri/Public/EleriPlayerController.h b/Source/ProjectEleri/Public/EleriPlayerController.h index d6b8cbcd..68b9ec32 100644 --- a/Source/ProjectEleri/Public/EleriPlayerController.h +++ b/Source/ProjectEleri/Public/EleriPlayerController.h @@ -101,6 +101,9 @@ private: UPROPERTY() USelectionManager* SelectionManagerInventorySlots; + + UPROPERTY() + FGuid MovementDisabledEffect; public: UPROPERTY() @@ -108,8 +111,14 @@ public: 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; } diff --git a/Source/ProjectEleri/Settings/EleriGameSettings.h b/Source/ProjectEleri/Settings/EleriGameSettings.h index f176d7ba..ff4b5087 100644 --- a/Source/ProjectEleri/Settings/EleriGameSettings.h +++ b/Source/ProjectEleri/Settings/EleriGameSettings.h @@ -39,6 +39,8 @@ public: UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Ability System|Effects") TSubclassOf ExpIncreaseEffectClass; + UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Ability System|Effects") + TSubclassOf MovementDisableEffectClass; UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Ability System|Professions") TArray ProfessionExpArray; diff --git a/Source/ProjectEleri/System/EleriGameplayTags.cpp b/Source/ProjectEleri/System/EleriGameplayTags.cpp index 04810d54..0b325979 100644 --- a/Source/ProjectEleri/System/EleriGameplayTags.cpp +++ b/Source/ProjectEleri/System/EleriGameplayTags.cpp @@ -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")) diff --git a/Source/ProjectEleri/System/EleriGameplayTags.h b/Source/ProjectEleri/System/EleriGameplayTags.h index e13f803f..cd246b0c 100644 --- a/Source/ProjectEleri/System/EleriGameplayTags.h +++ b/Source/ProjectEleri/System/EleriGameplayTags.h @@ -2,14 +2,21 @@ #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) PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GatheringDoubleDropChance) PROJECTELERI_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(GatheringHqDropChance) -} \ No newline at end of file +}