Initial push to repo

This commit is contained in:
2026-07-03 19:56:31 +02:00
commit 4cf4176d57
1305 changed files with 43455 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,381 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Components/StatForgeComponent.h"
#include "Config/StatForgeDeveloperSettings.h"
#include "Data/GameplayStatEffect.h"
FActiveGameplayStatEffect FActiveGameplayStatEffect::CreateNew(UObject* SourceObject, UGameplayStatEffect* Effect, int32 Stacks)
{
if (!Effect || !SourceObject) return FActiveGameplayStatEffect();
FActiveGameplayStatEffect ActiveEffect;
ActiveEffect.SourceObject = SourceObject;
ActiveEffect.GameplayEffectClass = Effect->GetClass();
ActiveEffect.StackCount = Stacks;
ActiveEffect.GameplayEffectInstance = Effect;
ActiveEffect.RemainingDuration = Effect->Duration;
ActiveEffect.NextTickDuration = Effect->TickPeriod;
ActiveEffect.bIsTicking = ActiveEffect.NextTickDuration > 0.f;
ActiveEffect.Handle = FGuid::NewGuid();
return ActiveEffect;
}
UStatForgeComponent::UStatForgeComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
float UStatForgeComponent::GetStatValue(const FName StatName) const
{
for (const FStatRuntimeDef& Def : StatArray)
{
if (Def.StatDef.StatName == StatName)
{
return Def.CurrentValue;
}
}
return 0.f;
}
void UStatForgeComponent::BeginPlay()
{
Super::BeginPlay();
TMap<FName, float> DataValueMap;
if (const UStatForgeDeveloperSettings* DeveloperSettings = GetDefault<UStatForgeDeveloperSettings>())
{
for (const auto& StatPair : InitialStatValueArray)
{
FStatDef Def = DeveloperSettings->GetStatByName(StatPair.Key);
if (Def.IsValid())
{
StatArray.Add(FStatRuntimeDef(Def));
FGameplayStatEffectMod Mod;
Mod.AffectedStat = StatPair.Key;
Mod.ModOperation = EModOperation::Override;
Mod.CalculationType = EGameplayStatEffectModCalculation::Flat;
Mod.FlatValue = StatPair.Value;
ApplyModToStat(Mod, DataValueMap);
}
}
}
for (const TSubclassOf<UGameplayStatEffect>& Effect : InitialEffectArray)
{
ApplyGameplayStatEffect(Effect, DataValueMap);
}
}
void UStatForgeComponent::TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
TickEffects(DeltaTime);
}
void UStatForgeComponent::IsStat(FName StatName, FName CheckedName, EIsStatResult& Result)
{
Result = StatName.IsEqual(CheckedName)? EIsStatResult::Yes : EIsStatResult::No;
}
void UStatForgeComponent::Internal_ApplyGameplayStatEffect(UGameplayStatEffect* Effect)
{
if (!CanApplyEffect(Effect)) return;
PreGameplayEffectExecute(Effect);
for (const FGameplayStatEffectMod& Modifier : Effect->Modifiers)
{
ApplyModToStat(Modifier, Effect->GetDataValueMap());
}
PostGameplayEffectExecute(Effect);
}
void UStatForgeComponent::ApplyModToStat(const FGameplayStatEffectMod& Mod, const TMap<FName, float> DataValueMap)
{
FStatRuntimeDef* StatDef = GetStatRef(Mod.AffectedStat);
if (!StatDef) return;
PreStatModify(StatDef->StatDef.StatName);
const float OldValue = StatDef->BaseValue;
const float ModValue = Mod.GetModValue(this, DataValueMap);
switch (Mod.ModOperation)
{
case EModOperation::Add:
StatDef->BaseValue += ModValue;
break;
case EModOperation::MultiplyAdditive:
case EModOperation::MultiplyCompound:
StatDef->BaseValue *= ModValue;
break;
case EModOperation::Override:
StatDef->BaseValue = ModValue;
break;
}
RecalculateStatCurrentValue(Mod.AffectedStat, OldValue);
}
void UStatForgeComponent::RecalculateStatCurrentValue(const FName StatName, const float OldValue)
{
FStatRuntimeDef* StatDef = GetStatRef(StatName);
if (!StatDef) return;
const float BaseValue = StatDef->BaseValue;
float AdditiveSum = 0.f;
float MultiAddSum = 0.f;
float MultiCompound = 1.f;
bool bHasOverride = false;
float OverrideValue = 0.f;
for (FActiveGameplayStatEffect& Effect : ActiveEffectsArray)
{
if (Effect.bIsTicking) continue;
for (auto Mod : Effect.GameplayEffectInstance->Modifiers)
{
if (Mod.AffectedStat != StatName) continue;
const float StackCount = Effect.StackCount;
const float ModValue = Mod.GetModValue(this, Effect.GameplayEffectInstance->GetDataValueMap());
switch (Mod.ModOperation) {
case EModOperation::Add:
{
AdditiveSum += (ModValue * StackCount);
break;
}
case EModOperation::MultiplyAdditive:
{
MultiAddSum += (ModValue * StackCount);
break;
}
case EModOperation::MultiplyCompound:
{
MultiCompound *= (ModValue * StackCount);
break;
}
case EModOperation::Override:
{
bHasOverride = true;
OverrideValue = ModValue;
break;
}
}
}
if (bHasOverride)
{
break;
}
}
float Result = (BaseValue + AdditiveSum) * (1.f + MultiAddSum) * MultiCompound;
if (bHasOverride)
{
Result = OverrideValue;
}
StatDef->CurrentValue = Result;
PostStatModify(StatName);
OnStatChanged.Broadcast(StatName, OldValue, StatDef->CurrentValue);
}
FGuid UStatForgeComponent::ApplyGameplayStatEffect(const TSubclassOf<UGameplayStatEffect> EffectClass, TMap<FName, float> DataValueMap, int32 Stacks)
{
if (!EffectClass) return FGuid();
UGameplayStatEffect* Effect = NewObject<UGameplayStatEffect>(this, EffectClass);
check(Effect);
Effect->AppendDataValueMap(DataValueMap);
FGuid GuidToReturn;
if (Effect->Duration == GameplayStatEffectDuration_Instant)
{
Internal_ApplyGameplayStatEffect(Effect);
}
else
{
FActiveGameplayStatEffect ActiveEffect = FActiveGameplayStatEffect::CreateNew(this, Effect, Stacks);
GuidToReturn = ActiveEffect.Handle;
ActiveEffectsArray.Add(ActiveEffect);
AddGameplayTags(Effect->CharacterTags);
for (const FName StatName : GetAffectedStatsFromEffect(ActiveEffect.GameplayEffectInstance))
{
const float OldValue = GetStatValue(StatName);
RecalculateStatCurrentValue(StatName, OldValue);
}
}
return GuidToReturn;
}
bool UStatForgeComponent::RemoveGameplayStatEffect(FGuid EffectId)
{
for (int32 i = ActiveEffectsArray.Num() - 1; i >= 0; --i)
{
if (ActiveEffectsArray[i].Handle == EffectId)
{
RemoveGameplayTags(ActiveEffectsArray[i].GameplayEffectInstance->CharacterTags);
TArray<FName> AffectedStats = GetAffectedStatsFromEffect(ActiveEffectsArray[i].GameplayEffectInstance.Get());
ActiveEffectsArray.RemoveAt(i);
for (const FName& Stat : AffectedStats)
{
const float OldValue = GetStatValue(Stat);
RecalculateStatCurrentValue(Stat, OldValue);
}
return true;
}
}
return false;
}
bool UStatForgeComponent::HasStat(const FName& StatName) const
{
for (const FStatRuntimeDef& Stat : StatArray)
{
if (Stat.StatDef.StatName == StatName)
{
return true;
}
}
return false;
}
void UStatForgeComponent::AddGameplayTag(const FGameplayTag& NewTag)
{
if (NewTag.IsValid())
{
CharacterGameplayTags.AddTag(NewTag);
NotifyGameplayTagsChanged();
}
}
void UStatForgeComponent::AddGameplayTags(const FGameplayTagContainer& NewTags)
{
if(NewTags.Num() > 0)
{
CharacterGameplayTags.AppendTags(NewTags);
NotifyGameplayTagsChanged();
}
}
bool UStatForgeComponent::RemoveGameplayTag(const FGameplayTag& NewTag)
{
if (NewTag.IsValid())
{
CharacterGameplayTags.RemoveTag(NewTag);
NotifyGameplayTagsChanged();
return true;
}
return false;
}
void UStatForgeComponent::RemoveGameplayTags(const FGameplayTagContainer& NewTags)
{
if(NewTags.Num() > 0)
{
CharacterGameplayTags.RemoveTags(NewTags);
NotifyGameplayTagsChanged();
}
}
void UStatForgeComponent::NotifyGameplayTagsChanged() const
{
OnTagsChanged.Broadcast();
}
TArray<FName> UStatForgeComponent::GetAffectedStatsFromEffect(UGameplayStatEffect* Effect)
{
TArray<FName> ChangedStats;
for (auto Mod : Effect->Modifiers)
{
ChangedStats.AddUnique(Mod.AffectedStat);
}
return ChangedStats;
}
FStatRuntimeDef* UStatForgeComponent::GetStatRef(const FName StatName)
{
for (FStatRuntimeDef& Def : StatArray)
{
if (Def.StatDef.StatName == StatName)
{
return &Def;
}
}
return nullptr;
}
void UStatForgeComponent::TickEffects(float DeltaTime)
{
TArray<FName> StatNames;
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)
{
RemoveGameplayStatEffect(ActiveEffectsArray[i].Handle);
continue;
}
if (ActiveEffectsArray[i].GameplayEffectInstance->TickPeriod > 0.f)
{
ActiveEffectsArray[i].NextTickDuration -= DeltaTime;
if (ActiveEffectsArray[i].NextTickDuration <= 0)
{
ActiveEffectsArray[i].NextTickDuration = ActiveEffectsArray[i].GameplayEffectInstance->TickPeriod;
Internal_ApplyGameplayStatEffect(ActiveEffectsArray[i].GameplayEffectInstance);
}
}
}
for (const FName StatName : StatNames)
{
const float OldValue = GetStatValue(StatName);
RecalculateStatCurrentValue(StatName, OldValue);
}
#if WITH_EDITOR
for (const auto& Stat : StatArray)
{
StatValuesPreview.Add(Stat.StatDef.StatName, GetStatValue(Stat.StatDef.StatName));
}
#endif
}
bool UStatForgeComponent::CanApplyEffect(const UGameplayStatEffect* StatEffect) const
{
if (!StatEffect) return false;
return GetGameplayTags().HasAll(StatEffect->TagsRequiredToApply);
}
bool UStatForgeComponent::CanTickEffect(const UGameplayStatEffect* StatEffect) const
{
if (!StatEffect) return false;
return GetGameplayTags().HasAll(StatEffect->TagsRequiredToTick);
}

View File

@@ -0,0 +1,100 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Config/StatForgeDeveloperSettings.h"
#include "Data/StatForgeRegistry.h"
#if WITH_EDITOR
void UStatForgeDeveloperSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UStatForgeDeveloperSettings, StatMap))
{
TSet<FName> Seen;
for (int32 i = StatMap.Num() - 1; i >= 0; i--)
{
if (StatMap[i].StatName.IsNone())
{
continue;
}
if (Seen.Contains(StatMap[i].StatName))
{
FMessageDialog::Open(EAppMsgType::Ok,
FText::Format(INVTEXT("StatForge: Duplicate stat name '{0}' removed."),FText::FromName(StatMap[i].StatName)));
StatMap.RemoveAt(i);
}
else
{
Seen.Add(StatMap[i].StatName);
}
}
}
}
void UStatForgeDeveloperSettings::PostReloadConfig(class FProperty* PropertyThatWasLoaded)
{
Super::PostReloadConfig(PropertyThatWasLoaded);
MergeRegistryIntoStatMap();
}
#endif
FStatDef UStatForgeDeveloperSettings::GetStatByName(FName StatName) const
{
for (const FStatDef Def : StatMap)
{
if (Def.StatName == StatName)
{
return Def;
}
}
return FStatDef();
}
TArray<FName> UStatForgeDeveloperSettings::GetStatNames()
{
TArray<FName> Names;
if (const UStatForgeDeveloperSettings* Settings = GetDefault<UStatForgeDeveloperSettings>())
{
for (const FStatDef Def : Settings->StatMap)
{
Names.Add(Def.StatName);
}
}
return Names;
}
const FStatDef* UStatForgeDeveloperSettings::FindStat(const FName& StatName) const
{
return StatMap.FindByPredicate([&](const FStatDef& Def)
{
return Def.StatName == StatName;
});
}
void UStatForgeDeveloperSettings::PostInitProperties()
{
Super::PostInitProperties();
MergeRegistryIntoStatMap();
}
void UStatForgeDeveloperSettings::MergeRegistryIntoStatMap()
{
UE_LOG(LogTemp, Warning, TEXT("StatForge: Merging, registry has %d stats"), FStatForgeRegistry::Get().Num());
for (const FStatDef& Registered : FStatForgeRegistry::Get())
{
const bool bExists = StatMap.ContainsByPredicate([&](const FStatDef& Def)
{
return Def.StatName == Registered.StatName;
});
if (!bExists)
{
StatMap.Add(Registered);
}
}
}

View File

@@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Data/GameplayStatEffect.h"
#include "Components/StatForgeComponent.h"
float FGameplayStatEffectMod::GetModValue(UStatForgeComponent* StatComponent, const TMap<FName, float>& DataValueMap) const
{
switch (CalculationType) {
case EGameplayStatEffectModCalculation::Flat:
return FlatValue;
case EGameplayStatEffectModCalculation::CalculationClass:
{
if (!StatCalculationClass) return 0.f;
UStatEffectModCalculation* Calc = NewObject<UStatEffectModCalculation>(StatComponent, StatCalculationClass);
return Calc->GetValue(StatComponent);
}
case EGameplayStatEffectModCalculation::DataValue:
{
if (const float* Value = DataValueMap.Find(DataValueName))
{
return *Value;
}
}
}
return 0.f;
}

View File

@@ -0,0 +1,4 @@
#include "Data/StatForgeDefs.h"

View File

@@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Helpers/StatEffectModCalculation.h"

View File

@@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Helpers/StatForgeFunctionLibrary.h"
#include "Components/StatForgeComponent.h"
float UStatForgeFunctionLibrary::GetStatValue(const FName StatName, AActor* Actor)
{
if (!Actor || StatName.IsNone()) return 0.f;
UStatForgeComponent* StatForgeComponent = Actor->GetComponentByClass<UStatForgeComponent>();
if(!StatForgeComponent) return 0.f;
return StatForgeComponent->GetStatValue(StatName);
}
FGuid UStatForgeFunctionLibrary::ApplyGameplayStatEffect(const TSubclassOf<UGameplayStatEffect> EffectClass,
TMap<FName, float> DataValueMap, AActor* Actor, int32 Stacks)
{
if (!EffectClass || !Actor) return FGuid();
UStatForgeComponent* StatForgeComponent = Actor->GetComponentByClass<UStatForgeComponent>();
if(!StatForgeComponent) return FGuid();
return StatForgeComponent->ApplyGameplayStatEffect(EffectClass, DataValueMap, Stacks);
}

View File

@@ -0,0 +1,26 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "StatForge.h"
#include "Config/StatForgeDeveloperSettings.h"
#define LOCTEXT_NAMESPACE "FStatForgeModule"
void FStatForgeModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
if (UStatForgeDeveloperSettings* Settings = GetMutableDefault<UStatForgeDeveloperSettings>())
{
Settings->EnsureRegistryMerged();
}
}
void FStatForgeModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FStatForgeModule, StatForge)

View File

@@ -0,0 +1,139 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Data/GameplayStatEffect.h"
#include "Data/StatForgeDefs.h"
#include "StatForgeComponent.generated.h"
struct FGameplayStatEffectMod;
USTRUCT()
struct FActiveGameplayStatEffect
{
GENERATED_BODY()
UPROPERTY()
FGuid Handle;
UPROPERTY()
TSubclassOf<UGameplayStatEffect> GameplayEffectClass;
UPROPERTY()
TObjectPtr<UGameplayStatEffect> GameplayEffectInstance;
UPROPERTY()
TObjectPtr<UObject> SourceObject = nullptr;
UPROPERTY()
int32 StackCount = 1;
UPROPERTY()
float RemainingDuration = -1.f;
UPROPERTY()
float NextTickDuration = -1.f;
UPROPERTY()
bool bIsTicking = false;
static FActiveGameplayStatEffect CreateNew(UObject* SourceObject, UGameplayStatEffect* Effect, int32 Stacks = 1);
};
UENUM()
enum class EIsStatResult : uint8
{
Yes,
No
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnStatChanged, FName, StatName, float, OldValue, float, NewValue);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnGameplayTagsChangedDelegate);
UCLASS(Blueprintable, BlueprintType, ClassGroup=(StatForge), meta=(BlueprintSpawnableComponent))
class STATFORGE_API UStatForgeComponent : public UActorComponent
{
GENERATED_BODY()
public:
UStatForgeComponent();
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UFUNCTION(BlueprintCallable, Category = "Stat Forge", meta = (ExpandEnumAsExecs = "Result"))
static void IsStat(FName StatName, UPARAM(meta = (GetOptions = "StatForgeDeveloperSettings.GetStatNames")) FName CheckedName, EIsStatResult& Result);
UFUNCTION(BlueprintPure, Category = "Stat Forge")
float GetStatValue(UPARAM(meta = (GetOptions = "StatForgeDeveloperSettings.GetStatNames")) const FName StatName) const;
UFUNCTION(BlueprintCallable, Category = "Stat Forge", meta = (AutoCreateRefTerm = "DataValueMap"))
FGuid ApplyGameplayStatEffect(const TSubclassOf<UGameplayStatEffect> EffectClass, TMap<FName, float> DataValueMap, int32 Stacks = 1);
UFUNCTION(BlueprintCallable, Category = "Stat Forge")
bool RemoveGameplayStatEffect(FGuid EffectId);
UFUNCTION(BlueprintPure, Category = "Stat Forge")
bool HasStat(const FName& StatName) const;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Stat Forge|Stats", meta = (GetOptions = "StatForgeDeveloperSettings.GetStatNames"))
TMap<FName, float> InitialStatValueArray;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Stat Forge|Stats")
TArray<TSubclassOf<UGameplayStatEffect>> InitialEffectArray;
UPROPERTY(BlueprintAssignable, Category = "Stat Forge|Events")
FOnStatChanged OnStatChanged;
/** Begin TAGS */
UPROPERTY(BlueprintAssignable, Category = Buffs)
FOnGameplayTagsChangedDelegate OnTagsChanged;
UFUNCTION(BlueprintCallable, Category = "Stat Forge|Tags")
void AddGameplayTag(const FGameplayTag& NewTag);
UFUNCTION(BlueprintCallable, Category = "Stat Forge|Tags")
void AddGameplayTags(const FGameplayTagContainer& NewTags);
UFUNCTION(BlueprintCallable, Category = "Stat Forge|Tags")
bool RemoveGameplayTag(const FGameplayTag& NewTag);
UFUNCTION(BlueprintCallable, Category = "Stat Forge|Tags")
void RemoveGameplayTags(const FGameplayTagContainer& NewTags);
const FGameplayTagContainer& GetGameplayTags() const { return CharacterGameplayTags; }
protected:
void NotifyGameplayTagsChanged() const;
/** End TAGS */
virtual void PreGameplayEffectExecute(UGameplayStatEffect* Effect) {}
virtual void PostGameplayEffectExecute(UGameplayStatEffect* Effect) {}
virtual void PreStatModify(const FName StatName) {}
virtual void PostStatModify(const FName StatName) {}
void Internal_ApplyGameplayStatEffect(UGameplayStatEffect* Effect);
virtual void ApplyModToStat(const FGameplayStatEffectMod& Mod, const TMap<FName, float> DataValueMap);
virtual void RecalculateStatCurrentValue(const FName StatName, const float OldValue);
static TArray<FName> GetAffectedStatsFromEffect(UGameplayStatEffect* Effect);
FStatRuntimeDef* GetStatRef(const FName StatName);
void TickEffects(float DeltaTime);
bool CanApplyEffect(const UGameplayStatEffect* StatEffect) const;
bool CanTickEffect(const UGameplayStatEffect* StatEffect) const;
UPROPERTY()
TArray<FStatRuntimeDef> StatArray;
#if WITH_EDITORONLY_DATA
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TMap<FName, float> StatValuesPreview;
#endif
UPROPERTY()
TArray<FActiveGameplayStatEffect> ActiveEffectsArray;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stat Forge|Tags")
FGameplayTagContainer CharacterGameplayTags;
};

View File

@@ -0,0 +1,40 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Data/StatForgeDefs.h"
#include "Engine/DeveloperSettings.h"
#include "StatForgeDeveloperSettings.generated.h"
/**
*
*/
UCLASS(Config = Game, defaultconfig, meta = (DisplayName = "Stat Forge Settings"))
class STATFORGE_API UStatForgeDeveloperSettings : public UDeveloperSettings
{
GENERATED_BODY()
public:
UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Setup")
TArray<FStatDef> StatMap;
#if WITH_EDITOR
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
virtual void PostReloadConfig(class FProperty* PropertyThatWasLoaded) override;
#endif
FStatDef GetStatByName(FName StatName) const;
UFUNCTION()
static TArray<FName> GetStatNames();
const FStatDef* FindStat(const FName& StatName) const;
void EnsureRegistryMerged() { MergeRegistryIntoStatMap(); }
protected:
virtual void PostInitProperties() override;
private:
void MergeRegistryIntoStatMap();
};

View File

@@ -0,0 +1,96 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "StatForgeDefs.h"
#include "UObject/Object.h"
#include "GameplayStatEffect.generated.h"
class UGameplayStatEffect;
UENUM(BlueprintType)
enum class EModOperation : uint8
{
Add,
MultiplyAdditive UMETA(DisplayName = "Multiply Additive"),
MultiplyCompound UMETA(DisplayName = "Multiply Compound"),
Override
};
static constexpr float GameplayStatEffectDuration_Instant = 0.f;
UENUM(BlueprintType)
enum class EGameplayStatEffectModCalculation : uint8
{
Flat,
CalculationClass,
DataValue
};
USTRUCT(BLueprintType)
struct STATFORGE_API FGameplayStatEffectMod
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats", meta = (GetOptions = "StatForgeDeveloperSettings.GetStatNames"))
FName AffectedStat;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats")
EModOperation ModOperation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats")
EGameplayStatEffectModCalculation CalculationType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats", meta = (EditCondition = "CalculationType==EGameplayStatEffectModCalculation::Flat", EditConditionHides))
float FlatValue = 0.f;
UPROPERTY(EditAnywhere, Category = "Stat Forge|Stats", meta = (EditCondition = "CalculationType==EGameplayStatEffectModCalculation::CalculationClass", EditConditionHides, BlueprintBaseOnly, ShowTreeView))
TSubclassOf<class UStatEffectModCalculation> StatCalculationClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats", meta = (EditCondition = "CalculationType==EGameplayStatEffectModCalculation::DataValue", EditConditionHides))
FName DataValueName;
float GetModValue(UStatForgeComponent* StatComponent, const TMap<FName, float>& DataValueMap) const;
};
/**
*
*/
UCLASS(Blueprintable)
class STATFORGE_API UGameplayStatEffect : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Duration")
float Duration = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Duration")
float TickPeriod = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modifiers")
TArray<FGameplayStatEffectMod> Modifiers;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags")
FGameplayTagContainer EffectTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags")
FGameplayTagContainer CharacterTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags|Requirements", meta = (EditCondition = "Duration==0"))
FGameplayTagContainer TagsRequiredToApply;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags|Requirements", meta = (EditCondition = "Duration!=0"))
FGameplayTagContainer TagsRequiredToTick;
UFUNCTION(BlueprintCallable, Category = "Stat Forge|Effect")
void AppendDataValueMap(TMap<FName, float> InDataValueMap) { DataValueMap.Append(InDataValueMap); }
const TMap<FName, float>& GetDataValueMap() { return DataValueMap; }
protected:
UPROPERTY()
TMap<FName, float> DataValueMap;
};

View File

@@ -0,0 +1,11 @@
#pragma once
#include "StatForgeRegistry.h"
// -Flat Stats-
// MAKE_STAT(Health)
// -Grouped Stats-
// MAKE_STAT_NS(Combat, AttackSpeed)
// MAKE_STAT_NS(Locomotion, MovementSpeed)

View File

@@ -0,0 +1,72 @@
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Helpers/StatEffectModCalculation.h"
#include "StatForgeDefs.generated.h"
UENUM(BlueprintType)
enum class EStatDefClampMode : uint8
{
None,
FlatValue,
Stat
};
USTRUCT(BlueprintType)
struct STATFORGE_API FStatDefClampMode
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats")
EStatDefClampMode ClampMode = EStatDefClampMode::None;
UPROPERTY(EditAnywhere, BlueprintReadWrite, DisplayName = "Value", Category = "Stat Forge|Stats", meta = (EditCondition = "ClampMode==EStatDefClampMode::FlatValue", EditConditionHides))
float ClampModeFlatValue = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, DisplayName = "Stat Reference", Category = "Stat Forge|Stats", meta = (EditCondition = "ClampMode==EStatDefClampMode::Stat", EditConditionHides, GetOptions = "StatForgeDeveloperSettings.GetStatNames"))
FName ClampModeStatValue;
};
USTRUCT(BlueprintType)
struct STATFORGE_API FStatDef
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats")
FName StatName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats")
FText StatDescriptiveName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stat Forge|Stats")
FStatDefClampMode ClampMode;
bool IsValid() const { return !StatName.IsNone(); }
};
USTRUCT(BlueprintType)
struct STATFORGE_API FStatRuntimeDef
{
GENERATED_BODY()
FStatRuntimeDef() { StatDef = FStatDef(); }
explicit FStatRuntimeDef(const FStatDef& Def)
{
StatDef = Def;
}
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stat Forge|Stats")
FStatDef StatDef;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stat Forge|Stats")
float BaseValue = 0.f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Stat Forge|Stats")
float CurrentValue = 0.f;
float GetBaseValue() const { return BaseValue; }
float GetCurrentValue() const { return CurrentValue; }
};

View File

@@ -0,0 +1,81 @@
#pragma once
#include "CoreMinimal.h"
#include "StatForgeDefs.h"
class STATFORGE_API FStatForgeRegistry
{
public:
static TArray<FStatDef>& Get()
{
static TArray<FStatDef> Registry;
return Registry;
}
static void RegisterStat(const FName& StatName)
{
UE_LOG(LogTemp, Warning, TEXT("StatForge: Registering %s"), *StatName.ToString());
const bool bAlreadyRegistered = Get().ContainsByPredicate([&](const FStatDef& Def)
{
return Def.StatName == StatName;
});
if (!bAlreadyRegistered)
{
FStatDef StatDef;
StatDef.StatName = StatName;
Get().Add(StatDef);
}
}
};
#define STATFORGE_DECLARE_STAT(StatName)\
namespace FStat\
{\
extern const FName StatName;\
}\
#define STATFORGE_DECLARE_STAT_NS(Group, StatName)\
namespace FStat\
{\
namespace Group\
{\
extern const FName StatName;\
}\
}\
#define STATFORGE_MAKE_STAT(StatName)\
namespace FStat\
{\
const FName StatName = FName(#StatName);\
namespace Detail\
{\
struct FAutoRegister_##StatName\
{\
FAutoRegister_##StatName() { FStatForgeRegistry::RegisterStat(FName(#StatName)); }\
};\
FAutoRegister_##StatName AutoRegister_##StatName;\
}\
}\
#define STATFORGE_MAKE_STAT_NS(Group, StatName)\
namespace FStat\
{\
namespace Group\
{\
const FName StatName = FName(#Group "." #StatName);\
namespace Detail\
{\
struct FAutoRegister_##Group##_##StatName\
{\
FAutoRegister_##Group##_##StatName()\
{ FStatForgeRegistry::RegisterStat(FName(#Group "." #StatName)); }\
};\
FAutoRegister_##Group##_##StatName AutoRegister_##Group##_##StatName;\
}\
}\
}\

View File

@@ -0,0 +1,22 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "StatEffectModCalculation.generated.h"
class UStatForgeComponent;
/**
*
*/
UCLASS(Blueprintable)
class STATFORGE_API UStatEffectModCalculation : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, Category = "Stat Forge")
float GetValue(UStatForgeComponent* StatForgeComponent);
virtual float GetValue_Implementation(UStatForgeComponent* StatForgeComponent) { return 0.f; }
};

View File

@@ -0,0 +1,24 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "StatForgeFunctionLibrary.generated.h"
class UGameplayStatEffect;
/**
*
*/
UCLASS()
class STATFORGE_API UStatForgeFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
UFUNCTION(BlueprintPure, Category = "Stat Forge")
static float GetStatValue(UPARAM(meta = (GetOptions = "StatForgeDeveloperSettings.GetStatNames")) const FName StatName, 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

@@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Modules/ModuleManager.h"
class FStatForgeModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,54 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class StatForge : ModuleRules
{
public StatForge(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"GameplayTags",
"DeveloperSettings"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@@ -0,0 +1,24 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "StatForge",
"Description": "",
"Category": "Gameplay",
"CreatedBy": "",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "StatForge",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}