In my tower-defence project I have a class for Stats that extends UObject
. These stats can exist within outer objects of at least three different types; ABasePawn
(directly extends APawn
and is the core class for all towers, enemies, bases, etc.), ABaseModule
(extends UObject
and adds functionality and behaviours to APawn
, such as a turret or spawner on a tower), and Def
(Contains initial definitions for all game-data).
When creating a stat, they are assigned to their outer using NewObject<UStatBase>(outer)
. If a stat’s outer is either ABasePawn*
or a subclass of it, I need to cast from UObject*
to ABasePawn*
in order to access and call methods that are defined in that class. Specifically, the health and shields stat subclasses need to find other stats on that same ABasePawn
.
This Cast call…
ABasePawn* outerPawn = Cast<ABasePawn>(outer);
…gives me the following error:
note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
I have also tried to do this using dynamic_cast<ABasePawn*, UObject>(outer)
and (ABasePawn*) outer
, both to no avail.
This is both my first project in UE4 and C++. I have only previously tinkered with Python and C# so my core knowledge of the programming language still has huge gaps that I am filling-in as I go along. As such, I would appreciate some extra care taken in responses so that I can follow along more easily. Thank you in advance.
Here are the class definitions that I think you might want/need to see. If you need soemthing else, please ask.
StatBase.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "BiomorphTD/StatModifier.h"
#include "BiomorphTD/StatModifierArray.h"
#include "BiomorphTD/DamageEntry.h"
#include "BiomorphTD/DamageEntryArray.h"
#include "StatBase.generated.h"
/**
*
*/
UCLASS()
class BIOMORPHTD_API UStatBase : public UObject
{
GENERATED_BODY()
public:
UStatBase();
UFUNCTION()
void Initialize(UObject* setParent, FName setName, FName setStatCategory, float setBaseValue, float setRegenBase);
UFUNCTION()
void Tick(float DeltaTime);
UFUNCTION()
void UpdateValueTotal();
UFUNCTION()
void UpdateReservationTotal();
UFUNCTION()
void UpdateRegenTotal();
UFUNCTION()
void UpdateCurrentValue();
UFUNCTION()
bool AddDamageEntry(UObject* source, FDamageEntry damageEntry, bool ignoreCategory = false);
UFUNCTION()
bool TryAddModifier(UObject* source, FStatModifier statModifier);
UFUNCTION()
bool TryRemoveModifier(UObject* source, FStatModifier statModifier);
UFUNCTION()
int TryAddModifiers(UObject* source, TArray<FStatModifier> statModifiers);
UFUNCTION()
int TryRemoveModifiers(UObject* source, TArray<FStatModifier> statModifiers);
UFUNCTION()
UObject* getParent();
UFUNCTION()
FName getName();
UFUNCTION()
FName getStatCategory();
UFUNCTION()
TArray<float> getValue();
UFUNCTION()
TArray<float> getReservation();
UFUNCTION()
TArray<float> getRegen();
UFUNCTION()
float getCurrentValue();
UFUNCTION()
float getCurrentDamage();
UFUNCTION()
TMap<UObject*, FDamageEntryArray> getDamageEntries();
UFUNCTION()
bool getTick();
protected:
UFUNCTION()
bool AddModifier(UObject* source, FStatModifier statModifier);
UFUNCTION()
bool RemoveModifier(UObject* source, FStatModifier statModifier);
UFUNCTION()
virtual void CalculateDamage(UObject* source, FDamageEntry damageEntry);
UFUNCTION()
void UpdateStat();
UFUNCTION()
float CalculateTotalModifier(TMap<UObject*, FStatModifier> modifierMap, bool isFactor = false);
protected:
UPROPERTY(VisibleAnywhere, Category = Statistics)
UObject* parent;
UPROPERTY(VisibleAnywhere, Category = Statistics)
FName name;
UPROPERTY(VisibleAnywhere, Category = Statistics)
FName statCategory;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fDamageTotal = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fBaseValue = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fBaseValueModifier = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fValueFactor = 1.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fValueModifier = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fValueTotal = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fReservationBase = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fReservationBaseModifier = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fReservationFactor = 1.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fReservationModifier = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fReservationTotal = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fRegenBase = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fRegenBaseModifier = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fRegenFactor = 1.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fRegenModifier = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fRegenTotal;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fCurrentValue = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
float fCurrentDamage = 0.0f;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> baseValueModifiers;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> valueFactors;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> valueModifiers;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> baseReservationModifiers;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> reservationFactors;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> reservationModifiers;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> baseRegenModifiers;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> regenFactors;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FStatModifier> regenModifiers;
UPROPERTY(VisibleAnywhere, Category = Statistics)
TMap<UObject*, FDamageEntryArray> damageEntries;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheBaseValueModifier = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheValueFactor = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheValueModifier = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheBaseReservationModifier = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheReservationFactor = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheReservationModifier = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheBaseRegenModifier = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheRegenFactor = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheRegenModifier = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bCacheDamage = false;
UPROPERTY(VisibleAnywhere, Category = CacheStatus)
bool bTick = false;
};
StatBase.cpp
#include "StatBase.h"
UStatBase::UStatBase()
{
}
void UStatBase::Initialize(UObject* setParent, FName setName, FName setStatCategory, float setBaseValue, float setRegenBase)
{
parent = setParent;
name = setName;
statCategory = setStatCategory;
fBaseValue = setBaseValue;
fRegenBase = setRegenBase;
UpdateValueTotal();
UpdateCurrentValue();
}
void UStatBase::Tick(float DeltaTime)
{
if (fCurrentDamage == 0.0f && fRegenTotal <= 0.0f)
{
bTick = false;
return;
}
fCurrentDamage -= (fRegenTotal * DeltaTime);
if (fCurrentDamage <= 0.0f)
{
fCurrentDamage = 0.0f;
bTick = false;
}
UpdateCurrentValue();
}
void UStatBase::UpdateValueTotal()
{
fValueTotal = ((fBaseValue + fBaseValueModifier) * fValueFactor) + fValueModifier;
}
void UStatBase::UpdateReservationTotal()
{
fReservationTotal = ((fReservationBase + fReservationBaseModifier) * fReservationFactor) + fReservationModifier;
}
void UStatBase::UpdateRegenTotal()
{
fRegenTotal = ((fRegenBase + fRegenBaseModifier) * fRegenFactor) + fRegenModifier;
}
void UStatBase::UpdateCurrentValue()
{
fCurrentValue = fValueTotal - (fReservationTotal + fCurrentDamage);
}
bool UStatBase::AddModifier(UObject* source, FStatModifier statModifier)
{
TMap<UObject*, FStatModifier> target;
bool cache;
bool output = false;
switch (statModifier.modifierType)
{
case EStatModifierType::SMT_BaseValueModifier:
target = baseValueModifiers;
cache = bCacheBaseValueModifier;
break;
case EStatModifierType::SMT_ValueFactor:
target = valueFactors;
cache = bCacheValueFactor;
break;
case EStatModifierType::SMT_ValueModifier:
target = valueModifiers;
cache = bCacheValueModifier;
break;
case EStatModifierType::SMT_BaseReservationModifier:
target = baseReservationModifiers;
cache = bCacheBaseReservationModifier;
break;
case EStatModifierType::SMT_ReservationFactor:
target = reservationFactors;
cache = bCacheReservationFactor;
break;
case EStatModifierType::SMT_ReservationModifier:
target = reservationModifiers;
cache = bCacheReservationModifier;
break;
case EStatModifierType::SMT_BaseRegenModifier:
target = baseRegenModifiers;
cache = bCacheBaseRegenModifier;
break;
case EStatModifierType::SMT_RegenFactor:
target = regenFactors;
cache = bCacheRegenFactor;
break;
case EStatModifierType::SMT_RegenModifier:
target = regenModifiers;
cache = bCacheRegenModifier;
break;
}
if (!target.Contains(source))
{
target.Add(source, statModifier);
cache = true;
output = true;
}
return output;
}
bool UStatBase::RemoveModifier(UObject* source, FStatModifier statModifier)
{
TMap<UObject*, FStatModifier> target;
bool cache;
bool output = false;
switch (statModifier.modifierType)
{
case EStatModifierType::SMT_BaseValueModifier:
target = baseValueModifiers;
cache = bCacheBaseValueModifier;
break;
case EStatModifierType::SMT_ValueFactor:
target = valueFactors;
cache = bCacheValueFactor;
break;
case EStatModifierType::SMT_ValueModifier:
target = valueModifiers;
cache = bCacheValueModifier;
break;
case EStatModifierType::SMT_BaseReservationModifier:
target = baseReservationModifiers;
cache = bCacheBaseReservationModifier;
break;
case EStatModifierType::SMT_ReservationFactor:
target = reservationFactors;
cache = bCacheReservationFactor;
break;
case EStatModifierType::SMT_ReservationModifier:
target = reservationModifiers;
cache = bCacheReservationModifier;
break;
case EStatModifierType::SMT_BaseRegenModifier:
target = baseRegenModifiers;
cache = bCacheBaseRegenModifier;
break;
case EStatModifierType::SMT_RegenFactor:
target = regenFactors;
cache = bCacheRegenFactor;
break;
case EStatModifierType::SMT_RegenModifier:
target = regenModifiers;
cache = bCacheRegenModifier;
break;
}
if (target.Remove(source) > 0)
{
cache = true;
output = true;
}
return output;
}
bool UStatBase::AddDamageEntry(UObject* source, FDamageEntry damageEntry, bool ignoreCategory)
{
if (!ignoreCategory && damageEntry.statCategory != statCategory)
{
return false;
}
if (!damageEntries.Contains(source))
{
damageEntries.Add(source, FDamageEntryArray());
}
damageEntries[source].Add(damageEntry);
CalculateDamage(source, damageEntry);
fCurrentDamage += damageEntry.fFinalAmount;
bCacheDamage = true;
UpdateStat();
return true;
}
void UStatBase::CalculateDamage(UObject* source, FDamageEntry damageEntry)
{
if (parent == nullptr)
{
return;
}
if (damageEntry.fDamageAmount <= 0.0f)
{
return;
}
if (fCurrentValue < damageEntry.fDamageAmount)
{
damageEntry.fMitigatedAmount = damageEntry.fDamageAmount - fCurrentValue;
}
damageEntry.fFinalAmount = damageEntry.fDamageAmount - damageEntry.fMitigatedAmount;
}
/** Add FStatModifier modifier if modifier of matching type and source does not already exist. */
bool UStatBase::TryAddModifier(UObject* source, FStatModifier statModifier)
{
bool output = AddModifier(source, statModifier);
if (output)
{
UpdateStat();
}
return output;
}
int UStatBase::TryAddModifiers(UObject* source, TArray<FStatModifier> statModifiers)
{
int output = 0;
for (FStatModifier statModifier : statModifiers)
{
if (AddModifier(source, statModifier))
{
output += 1;
}
}
if (output > 0)
{
UpdateStat();
}
return output;
}
bool UStatBase::TryRemoveModifier(UObject* source, FStatModifier statModifier)
{
bool output = RemoveModifier(source, statModifier);
if (output)
{
UpdateStat();
}
return output;
}
int UStatBase::TryRemoveModifiers(UObject* source, TArray<FStatModifier> statModifiers)
{
int output = 0;
for (FStatModifier statModifier : statModifiers)
{
if (RemoveModifier(source, statModifier))
{
output += 1;
}
}
if (output > 0)
{
UpdateStat();
}
return output;
}
void UStatBase::UpdateStat()
{
bool updated = false;
if (bCacheBaseValueModifier)
{
CalculateTotalModifier(baseValueModifiers);
bCacheBaseValueModifier = false;
updated = true;
}
if (bCacheValueFactor)
{
CalculateTotalModifier(valueFactors, true);
bCacheValueFactor = false;
updated = true;
}
if (bCacheValueModifier)
{
CalculateTotalModifier(valueModifiers);
bCacheValueModifier = false;
updated = true;
}
if (bCacheBaseReservationModifier)
{
CalculateTotalModifier(baseReservationModifiers);
bCacheBaseReservationModifier = false;
updated = true;
}
if (bCacheReservationFactor)
{
CalculateTotalModifier(reservationFactors, true);
bCacheReservationFactor = false;
updated = true;
}
if (bCacheReservationModifier)
{
CalculateTotalModifier(reservationModifiers);
bCacheReservationModifier = false;
updated = true;
}
if (bCacheBaseRegenModifier)
{
CalculateTotalModifier(baseRegenModifiers);
bCacheBaseRegenModifier = false;
updated = true;
}
if (bCacheRegenFactor)
{
CalculateTotalModifier(regenFactors, true);
bCacheRegenFactor = false;
updated = true;
}
if (bCacheRegenModifier)
{
CalculateTotalModifier(regenModifiers);
bCacheRegenModifier = false;
updated = true;
}
if (bCacheDamage)
{
bCacheDamage = false;
updated = true;
}
if (updated)
{
UpdateValueTotal();
UpdateCurrentValue();
if (bTick == false && fRegenTotal > 0.0f && fCurrentDamage > 0.0f)
{
bTick = true;
}
}
}
float UStatBase::CalculateTotalModifier(TMap<UObject*, FStatModifier> modifierMap, bool isFactor)
{
float total = 0.0f;
if (isFactor)
{
total = 1.0f;
}
for (TPair<UObject*, FStatModifier> modifierEntry : modifierMap)
{
total += modifierEntry.Value.value;
}
return total;
}
UObject* UStatBase::getParent()
{
return parent;
}
FName UStatBase::getName()
{
return name;
}
FName UStatBase::getStatCategory()
{
return statCategory;
}
TArray<float> UStatBase::getValue()
{
return TArray<float>{fBaseValue, fBaseValueModifier, fValueFactor, fValueModifier, fValueTotal};
}
TArray<float> UStatBase::getReservation()
{
return TArray<float>{fReservationBase, fReservationBaseModifier, fReservationFactor, fReservationModifier, fReservationTotal};
}
TArray<float> UStatBase::getRegen()
{
return TArray<float>{fRegenBase, fRegenBaseModifier, fRegenFactor, fRegenModifier, fRegenTotal};
}
float UStatBase::getCurrentValue()
{
return fCurrentValue;
}
float UStatBase::getCurrentDamage()
{
return fCurrentDamage;
}
TMap<UObject*, FDamageEntryArray> UStatBase::getDamageEntries()
{
return damageEntries;
}
bool UStatBase::getTick()
{
return bTick;
}
StatHealth.h
#pragma once
#include "CoreMinimal.h"
#include "StatBase.h"
#include "BiomorphTD/BasePawn.h"
#include "BiomorphTD/DamageCategory.h"
#include "StatHealth.generated.h"
/**
*
*/
UCLASS()
class BIOMORPHTD_API UStatHealth : public UStatBase
{
GENERATED_BODY()
protected:
void CalculateDamage(UObject* source, FDamageEntry damageEntry) override;
};
StatHealth.cpp
#include "StatHealth.h"
void UStatHealth::CalculateDamage(UObject* source, FDamageEntry damageEntry)
{
UObject* outer = GetOuter();
if (outer == nullptr)
{
return;
}
if (damageEntry.fDamageAmount <= 0.0f)
{
return;
}
if (TSubclassOf<ABasePawn>(outer->GetClass()))
{
ABasePawn* outerPawn = Cast<ABasePawn>(outer);
if (outerPawn != nullptr)
{
UStatBase* armor = outerPawn->FindStatByName(FName(TEXT("Armor")));
UStatBase* shields = outerPawn->FindStatByName(FName(TEXT("Shields")));
if (shields != nullptr && shields->getCurrentValue() > 0.0f)
{
shields->AddDamageEntry(source, damageEntry, true);
}
if (armor != nullptr && armor->getCurrentValue() > 0.0f)
{
float tempDamage = 0.0f;
tempDamage = damageEntry.fDamageAmount * FMath::Pow(0.975, armor->getCurrentValue());
damageEntry.fMitigatedAmount = damageEntry.fDamageAmount - tempDamage;
}
}
}
}
StatShields.h
#pragma once
#include "CoreMinimal.h"
#include "StatBase.h"
#include "BiomorphTD/BasePawn.h"
#include "BiomorphTD/DamageCategory.h"
#include "StatShields.generated.h"
/**
*
*/
UCLASS()
class BIOMORPHTD_API UStatShields : public UStatBase
{
GENERATED_BODY()
protected:
void CalculateDamage(UObject* source, FDamageEntry damageEntry) override;
};
StatShields.cpp
#include "StatShields.h"
void UStatShields::CalculateDamage(UObject* source, FDamageEntry damageEntry)
{
UObject* outer = GetOuter();
if (outer == nullptr)
{
return;
}
if (damageEntry.fDamageAmount <= 0.0f)
{
return;
}
float tempDamage = 0.0f;
tempDamage = damageEntry.fDamageAmount;
if (damageEntry.damageCategory == EDamageCategory::DC_Chemical)
{
tempDamage = damageEntry.fDamageAmount * 0.5f;
}
if (fCurrentValue < tempDamage)
{
if (TSubclassOf<ABasePawn>(outer->GetClass()))
{
ABasePawn* outerPawn = Cast<ABasePawn>(outer);
if (outerPawn != nullptr)
{
UStatBase* health = outerPawn->FindStatByName(FName(TEXT("Health")));
FDamageEntry tempDamageEntry = *NewObject<FDamageEntry>(health);
tempDamageEntry.statCategory = damageEntry.statCategory;
tempDamageEntry.damageCategory = damageEntry.damageCategory;
tempDamageEntry.damageType = damageEntry.damageType;
tempDamageEntry.fDamageAmount = damageEntry.fDamageAmount;
tempDamageEntry.fMitigatedAmount = damageEntry.fMitigatedAmount;
tempDamageEntry.fFinalAmount = damageEntry.fFinalAmount;
if (tempDamageEntry.damageCategory == EDamageCategory::DC_Chemical)
{
tempDamageEntry.fDamageAmount *= 2.0f;
}
if (health != nullptr)
{
health->AddDamageEntry(source, tempDamageEntry, true);
}
}
}
tempDamage = fCurrentValue;
}
if (damageEntry.damageCategory == EDamageCategory::DC_Chemical)
{
damageEntry.fMitigatedAmount = tempDamage;
}
damageEntry.fFinalAmount = tempDamage - damageEntry.fMitigatedAmount;
}