Error Casting UObject* to user createdsubclass of APawn*

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;
}

Issue Resolved
The cast wasn’t failing at all. Rather it was my use of ‘NewObject’ to instantiate a struct that was failing. Because of where they sat in the code, I kept commenting out both at the same time.