Skip to Content
SDK Integration
Unreal Engine 5ExamplesExposing Cheats

Exposing Cheats

Exposing cheats to the AI agent can speed up tests significantly. Generally, there’s 2 approaches to exposing cheats to the AI agent:

Using Hidden Cheat Functions

The simplest approach: create specific functions for each cheat and keep them hidden from the AI by default. The AI can only use them when explicitly taught through hints or the goal in test cases or steps when the cheat is needed.

UCLASS() class UCheatFunctions : public UFlayerFunctionLibrary { GENERATED_BODY() public: UCheatFunctions() { // Notice we set exposed to false - the hint key doesn't matter here REGISTER_FLAYER_FUNCTION(AddItem, "add_item", "Adds items to inventory", ".*", false); REGISTER_FLAYER_FUNCTION(GodMode, "god_mode", "Toggles god mode", ".*", false); REGISTER_FLAYER_FUNCTION(SetHealth, "set_health", "Sets health", ".*", false); } UFUNCTION() bool AddItem(FString ItemId, int32 Count, UPacketLogger* Logger) { // Implementation } // Other cheat functions... };

What’s good about this approach:

  • Clear, type-safe functions
  • Easy to understand and use
  • Simple implementation
  • Great when you want the AI to use specific cheats directly
  • Good when you don’t have a lot of cheats to expose
UCLASS() class UCheatFunctions : public UFlayerFunctionLibrary { GENERATED_BODY() public: UCheatFunctions() { // All functions hidden by default REGISTER_FLAYER_FUNCTION(GodMode, "god_mode", "Toggles god mode", ".*", false); REGISTER_FLAYER_FUNCTION(AddItem, "add_item", "Adds item to inventory", ".*", false); REGISTER_FLAYER_FUNCTION(KillPlayer, "kill_player", "Kills the player", ".*", false); REGISTER_FLAYER_FUNCTION(Teleport, "teleport", "Teleports player", ".*", false); } UFUNCTION() bool GodMode(UPacketLogger* Logger) { if (UAbilitySystemComponent* ASC = GetPlayerAbilitySystem()) { bool IsEnabled = !ASC->HasMatchingGameplayTag( FGameplayTag::RequestGameplayTag("Cheat.GodMode") ); if (IsEnabled) { ASC->AddGameplayTag(FGameplayTag::RequestGameplayTag("Cheat.GodMode")); Logger->LogToAI(TEXT("God Mode enabled")); } else { ASC->RemoveGameplayTag(FGameplayTag::RequestGameplayTag("Cheat.GodMode")); Logger->LogToAI(TEXT("God Mode disabled")); } return true; } Logger->LogToAI(TEXT("Failed to toggle God Mode: No ability system found"), ELogLevel::Error); return false; } UFUNCTION() bool AddItem(FString ItemId, int32 Count, UPacketLogger* Logger) { if (UInventoryComponent* Inventory = GetPlayerInventory()) { bool Success = Inventory->AddItem(ItemId, Count); if (Success) { Logger->LogToAI(FString::Printf(TEXT("Added %d x %s"), Count, *ItemId)); return true; } Logger->LogToAI(TEXT("Failed to add item"), ELogLevel::Error); return false; } Logger->LogToAI(TEXT("No inventory found"), ELogLevel::Error); return false; } UFUNCTION() bool KillPlayer(UPacketLogger* Logger) { if (APlayerCharacter* Character = GetPlayerCharacter()) { Character->Die(); Logger->LogToAI(TEXT("Player killed")); return true; } Logger->LogToAI(TEXT("No player found"), ELogLevel::Error); return false; } UFUNCTION() bool Teleport(float X, float Y, float Z, UPacketLogger* Logger) { if (APlayerCharacter* Character = GetPlayerCharacter()) { FVector Location(X, Y, Z); Character->SetActorLocation(Location); Logger->LogToAI(FString::Printf(TEXT("Teleported to X=%.1f Y=%.1f Z=%.1f"), X, Y, Z)); return true; } Logger->LogToAI(TEXT("No player found"), ELogLevel::Error); return false; } };

Single Parser Function

A more flexible approach: create one exposed function that takes cheat commands as strings.

UCLASS() class UCheatSystem : public UFlayerFunctionLibrary { GENERATED_BODY() public: UCheatSystem() { // This one is exposed since the security is in knowing the commands REGISTER_FLAYER_FUNCTION_SHORT( RunCheat, "run_cheat", "Executes a cheat command" ); } UFUNCTION() bool RunCheat(FString Command, UPacketLogger* Logger) { // Simple command parsing if (Command == "god") { return EnableGodMode(); } // Command with arguments if (Command.StartsWith("give ")) { FString ItemId; int32 Count; if (ParseGiveCommand(Command, ItemId, Count)) { return GiveItem(ItemId, Count); } } Logger->LogToAI(TEXT("Unknown command")); return false; } };

What’s good about this approach:

  • There’s a single entry point for all cheats
  • AI can’t use cheats unless taught the exact commands
  • Easy to add new cheats without changing the interface
  • Great for games with many cheats or complex commands
UCLASS() class UCheatSystem : public UFlayerFunctionLibrary { GENERATED_BODY() public: enum class ECheatCommand { GodMode, AddItem, KillPlayer, Teleport }; struct FCheatCommand { ECheatCommand Type; TArray<FString> Args; }; UCheatSystem() { // Single exposed function REGISTER_FLAYER_FUNCTION_SHORT( RunCheat, "run_cheat", "Executes a cheat command" ); } UFUNCTION() bool RunCheat(FString Command, UPacketLogger* Logger) { FCheatCommand ParsedCommand; if (!ParseCheatCommand(Command, ParsedCommand, Logger)) { return false; } switch (ParsedCommand.Type) { case ECheatCommand::GodMode: return HandleGodMode(ParsedCommand.Args, Logger); case ECheatCommand::AddItem: return HandleAddItem(ParsedCommand.Args, Logger); case ECheatCommand::KillPlayer: return HandleKillPlayer(ParsedCommand.Args, Logger); case ECheatCommand::Teleport: return HandleTeleport(ParsedCommand.Args, Logger); } Logger->LogToAI(TEXT("Unknown command"), ELogLevel::Error); return false; } private: bool ParseCheatCommand(const FString& CommandStr, FCheatCommand& OutCommand, UPacketLogger\* Logger) { // Format: CommandName(arg1, arg2, ...) int32 OpenParen = CommandStr.Find(TEXT("(")); int32 CloseParen = CommandStr.Find(TEXT(")")); if (OpenParen == INDEX_NONE || CloseParen == INDEX_NONE || CloseParen <= OpenParen) { Logger->LogToAI(TEXT("Invalid command format"), ELogLevel::Error); return false; } // Get command name FString CmdName = CommandStr.Left(OpenParen).TrimStartAndEnd(); // Parse command type if (CmdName.Equals(TEXT("GodMode"), ESearchCase::IgnoreCase)) OutCommand.Type = ECheatCommand::GodMode; else if (CmdName.Equals(TEXT("AddItem"), ESearchCase::IgnoreCase)) OutCommand.Type = ECheatCommand::AddItem; else if (CmdName.Equals(TEXT("KillPlayer"), ESearchCase::IgnoreCase)) OutCommand.Type = ECheatCommand::KillPlayer; else if (CmdName.Equals(TEXT("Teleport"), ESearchCase::IgnoreCase)) OutCommand.Type = ECheatCommand::Teleport; else { Logger->LogToAI(FString::Printf(TEXT("Unknown command: %s"), *CmdName), ELogLevel::Error); return false; } // Get arguments string FString ArgsStr = CommandStr.Mid(OpenParen + 1, CloseParen - OpenParen - 1); // Split arguments if (!ArgsStr.IsEmpty()) { ArgsStr.ParseIntoArray(OutCommand.Args, TEXT(","), true); for (FString& Arg : OutCommand.Args) { Arg = Arg.TrimStartAndEnd(); } } return true; } bool HandleGodMode(const TArray<FString>& Args, UPacketLogger* Logger) { if (UAbilitySystemComponent* ASC = GetPlayerAbilitySystem()) { bool IsEnabled = !ASC->HasMatchingGameplayTag( FGameplayTag::RequestGameplayTag("Cheat.GodMode") ); if (IsEnabled) { ASC->AddGameplayTag(FGameplayTag::RequestGameplayTag("Cheat.GodMode")); Logger->LogToAI(TEXT("God Mode enabled")); } else { ASC->RemoveGameplayTag(FGameplayTag::RequestGameplayTag("Cheat.GodMode")); Logger->LogToAI(TEXT("God Mode disabled")); } return true; } Logger->LogToAI(TEXT("No ability system found"), ELogLevel::Error); return false; } bool HandleAddItem(const TArray<FString>& Args, UPacketLogger* Logger) { if (Args.Num() < 2) { Logger->LogToAI(TEXT("Expected item ID and count"), ELogLevel::Error); return false; } FString ItemId = Args[0]; int32 Count = FCString::Atoi(*Args[1]); if (UInventoryComponent* Inventory = GetPlayerInventory()) { bool Success = Inventory->AddItem(ItemId, Count); if (Success) { Logger->LogToAI(FString::Printf(TEXT("Added %d x %s"), Count, *ItemId)); return true; } Logger->LogToAI(TEXT("Failed to add item"), ELogLevel::Error); return false; } Logger->LogToAI(TEXT("No inventory found"), ELogLevel::Error); return false; } bool HandleKillPlayer(const TArray<FString>& Args, UPacketLogger* Logger) { if (APlayerCharacter* Character = GetPlayerCharacter()) { Character->Die(); Logger->LogToAI(TEXT("Player killed")); return true; } Logger->LogToAI(TEXT("No player found"), ELogLevel::Error); return false; } bool HandleTeleport(const TArray<FString>& Args, UPacketLogger* Logger) { if (Args.Num() < 3) { Logger->LogToAI(TEXT("Expected X, Y, Z coordinates"), ELogLevel::Error); return false; } float X = FCString::Atof(*Args[0]); float Y = FCString::Atof(*Args[1]); float Z = FCString::Atof(*Args[2]); if (APlayerCharacter* Character = GetPlayerCharacter()) { Character->SetActorLocation(FVector(X, Y, Z)); Logger->LogToAI(FString::Printf(TEXT("Teleported to X=%.1f Y=%.1f Z=%.1f"), X, Y, Z)); return true; } Logger->LogToAI(TEXT("No player found"), ELogLevel::Error); return false; } };
Last updated on