Skip to content

Instantly share code, notes, and snippets.

@geordiemhall
Last active March 15, 2024 10:27
Show Gist options
  • Save geordiemhall/5bd5ad4737262cf9433422590f7fb227 to your computer and use it in GitHub Desktop.
Save geordiemhall/5bd5ad4737262cf9433422590f7fb227 to your computer and use it in GitHub Desktop.
UE4 Cheat manager with meta-based cheat names
#include "MyCheatManager.h"
#include "Engine/Console.h"
#include "AssetRegistryModule.h"
DEFINE_LOG_CATEGORY(LogCheatManager);
void UMyCheatManager::AddWorkers(int32 NumWorkers)
{
UE_LOG(LogCheatManager, Log, TEXT("%s: AddWorkers: %i"), *GetName(), NumWorkers);
}
void UMyCheatManager::RemoveWorkers(int32 NumWorkers)
{
UE_LOG(LogCheatManager, Log, TEXT("%s: RemoveWorkers: %i"), *GetName(), NumWorkers);
}
void UMyCheatManager::ResetWorkers()
{
UE_LOG(LogCheatManager, Log, TEXT("%s: ResetWorkers"), *GetName());
}
static int32 NumAutoCompletesRegistered = 0;
static const FString TagNamespace = TEXT("CheatFunc:");
void UMyCheatManager::InitCheatManager()
{
Super::InitCheatManager();
if (NumAutoCompletesRegistered <= 0)
{
UE_LOG(LogCheatManager, Verbose, TEXT("Registering cheat manager for autocomplete callbacks: %s"), *GetName());
UConsole::RegisterConsoleAutoCompleteEntries.AddUObject(this, &UMyCheatManager::RegisterAutoCompleteEntries);
NumAutoCompletesRegistered += 1;
}
else
{
UE_LOG(LogCheatManager, Verbose, TEXT("Didn't need to register cheat manager for autocomplete callbacks: %s"), *GetName());
}
}
void UMyCheatManager::BeginDestroy()
{
int32 NumRemoved = UConsole::RegisterConsoleAutoCompleteEntries.RemoveAll(this);
NumAutoCompletesRegistered -= NumRemoved;
Super::BeginDestroy();
}
bool UMyCheatManager::ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor)
{
FString OriginalCmd = Cmd;
FString CommandName;
if (FParse::Token(Cmd, CommandName, true))
{
FAssetData Asset;
if (GetAssetData(GetClass(), Asset))
{
const FName CommandNameTag(TagNamespace + CommandName);
FAssetDataTagMapSharedView::FFindTagResult Result = Asset.TagsAndValues.FindTag(CommandNameTag);
if (Result.IsSet())
{
// Cmd will now just be the arguments since we parsed the command name out of it
const FString CmdString = Result.GetValue() + Cmd;
return CallFunctionByNameWithArguments(*CmdString, Ar, Executor, /* bForceCallWithNonExec */ true);
}
}
}
return CallFunctionByNameWithArguments(*OriginalCmd, Ar, Executor);
}
void UMyCheatManager::GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const
{
Super::GetAssetRegistryTags(OutTags);
#if WITH_EDITOR
const UClass* ClassToSearch = UMyCheatManager::StaticClass();
// Grab the overall class prefix
static const FName CheatPrefixMetaKey = TEXT("CheatPrefix");
const FString ClassCheatPrefix = ClassToSearch->GetMetaData(CheatPrefixMetaKey);
// Find any functions with the Cheat meta (haven't bothered doing any duplicate detection or other validation)
TArray<FName> FunctionNames;
ClassToSearch->GenerateFunctionList(FunctionNames);
for (const FName& Name : FunctionNames)
{
const UFunction* Func = ClassToSearch->FindFunctionByName(Name);
static const FName CheatNameMetaKey = TEXT("Cheat");
if (!Func || !Func->HasMetaData(CheatNameMetaKey))
{
continue;
}
FString CheatName = Func->GetMetaData(CheatNameMetaKey);
// If they didn't provide a custom cheat name, then just default to the function name
if (CheatName.IsEmpty())
{
CheatName = Name.ToString();
}
// Prefix our tags with a namespace to help avoid conflicts
FString CommandNameTag = TagNamespace + ClassCheatPrefix + CheatName;
FAssetRegistryTag CommandTag(FName(CommandNameTag), Name.ToString(), FAssetRegistryTag::TT_Hidden);
OutTags.Add(CommandTag);
}
#endif
}
void UMyCheatManager::RegisterAutoCompleteEntries(TArray<FAutoCompleteCommand>& Commands) const
{
const UClass* ClassToSearch = GetClass();
FAssetData Asset;
if (!GetAssetData(ClassToSearch, Asset))
{
return;
}
for (const auto& Pair : Asset.TagsAndValues.GetMap())
{
const FString TagName = Pair.Key.ToString();
if (!TagName.StartsWith(TagNamespace))
{
continue;
}
const FName FunctionName(Pair.Value);
const UFunction* Func = ClassToSearch->FindFunctionByName(FunctionName);
if (Func == nullptr)
{
continue;
}
// Build help text for each param (matches what the default autocomplete does in UConsole::BuildRuntimeAutoCompleteList)
FString Desc;
for (TFieldIterator<FProperty> PropIt(Func); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
FProperty* Prop = *PropIt;
Desc += FString::Printf(TEXT("%s[%s] "), *Prop->GetName(), *Prop->GetCPPType());
}
FAutoCompleteCommand Entry;
Entry.Command = TagName.Mid(TagNamespace.Len());
Entry.Desc = Desc;
Commands.Add(Entry);
}
}
bool UMyCheatManager::GetAssetData(const UClass* ForClass, FAssetData& AssetData) const
{
const FString PackagePath = FPackageName::ObjectPathToPackageName(ForClass->GetPathName());
TArray<FAssetData> Assets;
FAssetRegistryModule::GetRegistry().GetAssetsByPackageName(FName(PackagePath), Assets);
if (Assets.Num() == 0)
{
UE_LOG(LogCheatManager, Warning, TEXT("Could not find any asset data for package %s, perhaps this isn't a Blueprint subclass?"), *PackagePath);
return false;
}
AssetData = Assets[0];
return true;
}
#pragma once
#include "CoreMinimal.h"
#include "ConsoleSettings.h"
#include "GameFramework/CheatManager.h"
#include "MyCheatManager.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogCheatManager, Log, All);
/**
* Example of a cheat manager that lets you more easily customise the command names
* Note: To use these Cheat meta methods you must make a BP subclass of this,
* then use _that_ as the CheatManager class in your PlayerController,
* since only assets can have data stored about them in the asset registry
*/
UCLASS(meta = (CheatPrefix = "mygame."))
class YOURPROJECT_API UMyCheatManager : public UCheatManager
{
GENERATED_BODY()
public:
UFUNCTION(meta = (Cheat = "workers.add"))
void AddWorkers(int32 NumWorkers = 1);
UFUNCTION(meta = (Cheat = "workers.remove"))
void RemoveWorkers(int32 NumWorkers = 1);
UFUNCTION(meta = (Cheat = "workers.reset"))
void ResetWorkers();
public:
virtual void InitCheatManager() override;
virtual void BeginDestroy() override;
virtual bool ProcessConsoleExec(const TCHAR* Cmd, FOutputDevice& Ar, UObject* Executor) override;
virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag>& OutTags) const override;
private:
void RegisterAutoCompleteEntries(TArray<FAutoCompleteCommand>& Commands) const;
bool GetAssetData(const UClass* ForClass, FAssetData& AssetData) const;
};
@JanSeliv
Copy link

JanSeliv commented Jun 1, 2023

Hey there! I found your gist really helpful when OverrideNativeName stopped working in Unreal Engine 5. I wanted to suggest some changes, but gists are a bit tricky for that.

I bumped into a couple of things:

  • It requires a blueprint to pull data from the asset registry as it was mentioned by you in the code.
  • It doesn't work in Dev/Debug builds since the asset registry is only in the Editor.

To address these issues, I've created a plugin that puts the meta cheat names in an .ini file instead of the Asset Registry. It's super easy to use: just download it from my GitHub page and drop it into your game's Plugins folder.

If you have any ideas or improvements in mind, I'd greatly appreciate your feedback and contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment