Files
UnrealEngine/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemCheatManagerExtension.cpp
2025-05-18 13:04:45 +08:00

687 lines
29 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AbilitySystemCheatManagerExtension.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "AbilitySystemLog.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "Logging/LogScopedVerbosityOverride.h"
#include "UObject/Package.h"
#include "UObject/UObjectIterator.h"
namespace UE::AbilitySystem::Private
{
/**
* Common logic used to fuzzy-find a requested class (or alternatively, a passed-in asset path).
*/
template<typename ClassToFind>
TSubclassOf<ClassToFind> FuzzyFindClass(FString SearchString)
{
TSubclassOf<ClassToFind> FoundClass;
// See if we passed in a class name of a Class that already exists in memory.
// If we passed-in Default__, just remove that part since we're looking for Classes, not CDO names.
SearchString.RemoveFromStart(TEXT("Default__"));
const int SearchStringLen = SearchString.Len();
int BestClassMatchLen = INT_MAX;
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
const bool bClassMatches = ClassIt->IsChildOf(ClassToFind::StaticClass());
if (!bClassMatches)
{
continue;
}
// Class name search
const FString ClassName = ClassIt->GetName();
const int ClassNameLen = ClassName.Len();
if (ClassNameLen < BestClassMatchLen && ClassNameLen >= SearchStringLen)
{
bool bContains = ClassName.Contains(SearchString);
if (bContains)
{
FoundClass = *ClassIt;
BestClassMatchLen = ClassNameLen;
}
}
}
// If it wasn't a class name, then perhaps it was the path to a specific asset
if (!FoundClass)
{
FSoftObjectPath SoftObjectPath{ SearchString };
if (UObject* ReferencedObject = SoftObjectPath.ResolveObject())
{
if (UPackage* ReferencedPackage = Cast<UPackage>(ReferencedObject))
{
ReferencedObject = ReferencedPackage->FindAssetInPackage();
}
if (UBlueprint* ReferencedBlueprint = Cast<UBlueprint>(ReferencedObject))
{
FoundClass = ReferencedBlueprint->GeneratedClass;
}
else if (ClassToFind* ReferencedGA = Cast<ClassToFind>(ReferencedObject))
{
FoundClass = ReferencedGA->GetClass();
}
}
}
return FoundClass;
}
}
struct FScopedCanActivateAbilityLogGatherer : public FOutputDevice
{
#if NO_LOGGING
FScopedCanActivateAbilityLogGatherer(const FNoLoggingCategory& InLogCategoryToCapture) {}
#else
FScopedCanActivateAbilityLogGatherer(const FLogCategoryBase& InLogCategoryToCapture)
: LogCategoryToCapture{ InLogCategoryToCapture.GetCategoryName() }
{
GLog->AddOutputDevice(this);
}
~FScopedCanActivateAbilityLogGatherer()
{
GLog->RemoveOutputDevice(this);
}
#endif
/** Structure for the Log Entries */
struct FLogEntry
{
const FName Category;
const ELogVerbosity::Type Verbosity;
const FString Text;
};
TArray<FLogEntry>&& GetCapturedLogs()
{
FScopeLock _(&CriticalSection);
return MoveTemp(Logs);
}
protected:
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& InCategory) override
{
if (InCategory != LogCategoryToCapture)
{
return;
}
FScopeLock _(&CriticalSection);
Logs.Emplace(FLogEntry{ InCategory, Verbosity, V });
}
FCriticalSection CriticalSection;
FName LogCategoryToCapture;
TArray<FLogEntry> Logs;
};
UAbilitySystemCheatManagerExtension::UAbilitySystemCheatManagerExtension()
{
#if WITH_SERVER_CODE && UE_WITH_CHEAT_MANAGER
if (HasAnyFlags(RF_ClassDefaultObject))
{
UCheatManager::RegisterForOnCheatManagerCreated(FOnCheatManagerCreated::FDelegate::CreateLambda(
[](UCheatManager* CheatManager)
{
CheatManager->AddCheatManagerExtension(NewObject<ThisClass>(CheatManager));
}));
}
#endif
}
void UAbilitySystemCheatManagerExtension::AbilityGrant(const FString& AssetSearchString) const
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? AbilitySystemGlobals.GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("%s did not have AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
if (AssetSearchString.IsEmpty())
{
AbilityListGranted();
return;
}
// We couldn't find anything the user was searching for, so early out
TSubclassOf<UGameplayAbility> ActivateAbilityClass = UE::AbilitySystem::Private::FuzzyFindClass<UGameplayAbility>(AssetSearchString);
if (!ActivateAbilityClass)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Could not find a valid Gameplay Ability based on Search String '%s'"), *AssetSearchString);
if (PC->GetWorld()->IsPlayInEditor())
{
UE_LOG(LogConsoleResponse, Log, TEXT("Reminder: If it's a non-native class, make sure it's loaded in the Editor."));
}
return;
}
// Check if it's already granted
if (const FGameplayAbilitySpec* ExistingSpec = ASC->FindAbilitySpecFromClass(ActivateAbilityClass))
{
UE_LOG(LogConsoleResponse, Log, TEXT("Existing Ability Spec '%s' on Player '%s' (It is already granted)."), *GetNameSafe(*ActivateAbilityClass), *GetNameSafe(PC));
return;
}
// If we're not the authority, we need to send the command to the server because we can't grant locally.
if (!ASC->IsOwnerActorAuthoritative())
{
const FString ServerCommand = FString::Printf(TEXT("%hs %s"), __func__, *AssetSearchString);
PC->ServerExec(ServerCommand);
UE_LOG(LogConsoleResponse, Log, TEXT("Sent Command 'ServerExec %s' from Player '%s' to Server because only the authority can grant abilities."), *ServerCommand, *GetNameSafe(PC));
return;
}
// It wasn't granted, let's grant it now.
FGameplayAbilitySpec AbilitySpec{ ActivateAbilityClass };
FGameplayAbilitySpecHandle SpecHandle = ASC->GiveAbility(AbilitySpec);
if (SpecHandle.IsValid())
{
UE_LOG(LogConsoleResponse, Log, TEXT("Successfully Granted '%s' on Player '%s'."), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("Failed to Grant '%s' on Player '%s'."), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
}
}
void UAbilitySystemCheatManagerExtension::AbilityListGranted() const
{
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? UAbilitySystemGlobals::Get().GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("%s did not have AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
const UEnum* ExecutionEnumPtr = FindObject<UEnum>(nullptr, TEXT("/Script/GameplayAbilities.EGameplayAbilityNetExecutionPolicy"), true);
check(ExecutionEnumPtr && TEXT("Couldn't locate EGameplayAbilityNetExecutionPolicy enum!"));
const UEnum* SecurityEnumPtr = FindObject<UEnum>(nullptr, TEXT("/Script/GameplayAbilities.EGameplayAbilityNetSecurityPolicy"), true);
check(SecurityEnumPtr && TEXT("Couldn't locate EGameplayAbilityNetSecurityPolicy enum!"));
UE_LOG(LogConsoleResponse, Log, TEXT("Granted abilities to %s (ASC: '%s'):"), *PC->GetName(), *ASC->GetFullName());
for (FGameplayAbilitySpec& Activatable : ASC->GetActivatableAbilities())
{
const TCHAR* ActiveText = Activatable.IsActive() ? TEXT("**ACTIVE**") : TEXT("");
UE_LOG(LogConsoleResponse, Log, TEXT(" %s (%s - %s) %s"), *Activatable.Ability->GetName(), *ExecutionEnumPtr->GetDisplayNameTextByIndex(Activatable.Ability->GetNetExecutionPolicy()).ToString(), *SecurityEnumPtr->GetDisplayNameTextByIndex(Activatable.Ability->GetNetSecurityPolicy()).ToString(), ActiveText);
}
}
void UAbilitySystemCheatManagerExtension::AbilityActivate(const FString& PartialName) const
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? AbilitySystemGlobals.GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("%s did not have AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
if (PartialName.IsEmpty())
{
AbilityListGranted();
return;
}
// Start by figuring out if we're trying to execute by GameplayTag.
const FName SearchName{ TCHAR_TO_ANSI(*PartialName), FNAME_Find };
if (SearchName != NAME_None)
{
const FGameplayTag GameplayTag = FGameplayTag::RequestGameplayTag(SearchName, false);
if (GameplayTag.IsValid())
{
FGameplayTagContainer GameplayTagContainer{ GameplayTag };
TArray<FGameplayAbilitySpec*> MatchingSpecs;
constexpr bool bTagRequirementsMustMatch = false;
ASC->GetActivatableGameplayAbilitySpecsByAllMatchingTags(GameplayTagContainer, MatchingSpecs, bTagRequirementsMustMatch);
if (MatchingSpecs.Num() > 0)
{
FString MatchingAbilitiesString = FString::JoinBy(MatchingSpecs, TEXT(", "), [](FGameplayAbilitySpec* Item) { return Item->GetDebugString(); });
bool bSuccess = ASC->TryActivateAbilitiesByTag(GameplayTagContainer);
if (bSuccess)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Requested Tag '%s' successfully executed one of: %s."), *SearchName.ToString(), *MatchingAbilitiesString);
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("Requested Tag '%s' was expected to execute one of: %s. But it failed to do so due to tag requirements."), *SearchName.ToString(), *MatchingAbilitiesString);
}
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("Requested Tag '%s' matched no given Gameplay Abilities to %s."), *SearchName.ToString(), *GetNameSafe(PC));
}
return;
}
}
// We couldn't find anything the user was searching for, so early out
TSubclassOf<UGameplayAbility> ActivateAbilityClass = UE::AbilitySystem::Private::FuzzyFindClass<UGameplayAbility>(PartialName);
if (!ActivateAbilityClass)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Could not find a valid Gameplay Ability based on Search String '%s'"), *PartialName);
return;
}
// Check if we already have this ability granted, because it affects how we can execute it.
const FGameplayAbilitySpec* ExistingSpec = ASC->FindAbilitySpecFromClass(ActivateAbilityClass);
// If we're not the authority, we should check if we need to send the command to the server
if (!ASC->IsOwnerActorAuthoritative())
{
const UGameplayAbility* Ability = ActivateAbilityClass.GetDefaultObject();
EGameplayAbilityNetExecutionPolicy::Type NetExecutionPolicy = Ability->GetNetExecutionPolicy();
const bool bMustActivateLocally = (NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalOnly || NetExecutionPolicy == EGameplayAbilityNetExecutionPolicy::LocalPredicted);
if (!ExistingSpec && bMustActivateLocally)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Cannot Activate '%s' on '%s' as the ability is not granted. Since its NetExecutionPolicy must activate locally, you must first grant it (use the AbilityGrant command)."), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
return;
}
const bool bSendToServer = (NetExecutionPolicy >= EGameplayAbilityNetExecutionPolicy::ServerInitiated);
if (bSendToServer)
{
const FString ServerCommand = FString::Printf(TEXT("%hs %s"), __func__, *PartialName);
PC->ServerExec(ServerCommand);
UE_LOG(LogConsoleResponse, Log, TEXT("Sent Command '%s' from Player '%s' to Server (Reason: Net Execution Policy)."), *ServerCommand, *GetNameSafe(PC));
return;
}
}
// We found what the user was searching for, so let's try to activate it. First, let's assume the Ability was already granted.
LOG_SCOPE_VERBOSITY_OVERRIDE(LogAbilitySystem, ELogVerbosity::VeryVerbose);
FScopedCanActivateAbilityLogGatherer LogGatherer{ LogAbilitySystem };
FScopedCanActivateAbilityLogEnabler LogEnabler;
if (FGameplayAbilitySpec* GrantedAbilitySpec = ASC->FindAbilitySpecFromClass(ActivateAbilityClass))
{
constexpr bool bAllowRemoteActivation = false;
bool bSuccess = ASC->TryActivateAbility(GrantedAbilitySpec->Handle, bAllowRemoteActivation);
#if !NO_LOGGING
if (bSuccess)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Successfully Activated previously Granted Ability '%s' on Player '%s'."), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("Failed to Activate previously Granted Ability '%s' on Player '%s'. Logs:"), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
TArray<FScopedCanActivateAbilityLogGatherer::FLogEntry> CapturedLogs = LogGatherer.GetCapturedLogs();
for (const FScopedCanActivateAbilityLogGatherer::FLogEntry& LogEntry : CapturedLogs)
{
FMsg::LogV(__FILE__, __LINE__, LogConsoleResponse.GetCategoryName(), LogEntry.Verbosity, *LogEntry.Text, {});
}
}
#endif
return;
}
// It wasn't granted, let's grant it, then activate it.
FGameplayAbilitySpec AbilitySpec{ ActivateAbilityClass };
FGameplayAbilitySpecHandle SpecHandle = ASC->GiveAbilityAndActivateOnce(AbilitySpec);
#if !NO_LOGGING
if (SpecHandle.IsValid())
{
UE_LOG(LogConsoleResponse, Log, TEXT("Successfully Granted, then Activated '%s' on Player '%s'."), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("Failed to Grant & Activate '%s' on Player '%s'. Logs:"), *GetNameSafe(ActivateAbilityClass.Get()), *GetNameSafe(PC));
TArray<FScopedCanActivateAbilityLogGatherer::FLogEntry> CapturedLogs = LogGatherer.GetCapturedLogs();
for (const FScopedCanActivateAbilityLogGatherer::FLogEntry& LogEntry : CapturedLogs)
{
FMsg::LogV(__FILE__, __LINE__, LogConsoleResponse.GetCategoryName(), LogEntry.Verbosity, *LogEntry.Text, {});
}
}
#endif
}
void UAbilitySystemCheatManagerExtension::AbilityCancel(const FString& PartialName) const
{
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? UAbilitySystemGlobals::Get().GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("PlayerController %s did not have an AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
FString SearchString = PartialName;
SearchString.RemoveFromStart(TEXT("Default__"));
bool bFound = false;
for (FGameplayAbilitySpec& GASpec : ASC->GetActivatableAbilities())
{
if (GASpec.IsActive())
{
if (!GASpec.Ability || !GASpec.Ability->GetName().Contains(SearchString))
{
continue;
}
TArray<UGameplayAbility*> ActiveAbilities = GASpec.GetAbilityInstances();
if (ActiveAbilities.Num() > 0)
{
for (UGameplayAbility* Instance : ActiveAbilities)
{
if (Instance)
{
UE_LOG(LogConsoleResponse, Log, TEXT("%s (%s): Cancelling (instanced) %s"), *PC->GetName(), *ASC->GetName(), *Instance->GetName());
Instance->CancelAbility(GASpec.Handle, ASC->AbilityActorInfo.Get(), Instance->GetCurrentActivationInfoRef(), true);
bFound = true;
}
}
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("%s (%s): Cancelling (non-instanced) %s"), *PC->GetName(), *ASC->GetName(), *GASpec.Ability->GetName());
PRAGMA_DISABLE_DEPRECATION_WARNINGS
GASpec.Ability->CancelAbility(GASpec.Handle, ASC->AbilityActorInfo.Get(), GASpec.ActivationInfo, true);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
bFound = true;
}
}
}
if (!bFound)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Found no Active Gameplay Abilities on %s (%s) that matched '%s'"), *PC->GetName(), *ASC->GetName(), *SearchString);
}
}
void UAbilitySystemCheatManagerExtension::EffectListActive() const
{
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? UAbilitySystemGlobals::Get().GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("PlayerController %s did not have an AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
UE_LOG(LogConsoleResponse, Log, TEXT("Active Gameplay Effects on %s (%s):"), *PC->GetName(), *ASC->GetName());
const FActiveGameplayEffectsContainer& ActiveGEContainer = ASC->GetActiveGameplayEffects();
for (const FActiveGameplayEffectHandle& ActiveGEHandle : ActiveGEContainer.GetAllActiveEffectHandles())
{
if (const FActiveGameplayEffect* ActiveGE = ASC->GetActiveGameplayEffect(ActiveGEHandle))
{
const UGameplayEffect* GESpecDef = ActiveGE->Spec.Def;
const FString ActiveState = ActiveGE->bIsInhibited ? FString(TEXT("Inhibited")) : FString::Printf(TEXT("Active (Stack: %d)"), ActiveGE->Spec.GetStackCount());
UE_LOG(LogConsoleResponse, Log, TEXT(" [%u] %s: %s"), GetTypeHash(ActiveGEHandle), *GetNameSafe(GESpecDef), *ActiveState);
}
}
}
void UAbilitySystemCheatManagerExtension::EffectApply(const FString& PartialName, float EffectLevel /*= FGameplayEffectConstants::INVALID_LEVEL*/) const
{
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? UAbilitySystemGlobals::Get().GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("PlayerController %s did not have an AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
// We couldn't find anything the user was searching for, so early out
TSubclassOf<UGameplayEffect> GameplayEffectClass = UE::AbilitySystem::Private::FuzzyFindClass<UGameplayEffect>(PartialName);
if (!GameplayEffectClass)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Could not find a valid Gameplay Effect based on Search String '%s'"), *PartialName);
return;
}
// Create a GameplayEffectSpec that executes the passed-in parameters
FGameplayEffectContextHandle GEContextHandle = ASC->MakeEffectContext();
APawn* Pawn = PC->GetPawn();
GEContextHandle.AddInstigator(Pawn, Pawn);
GEContextHandle.AddOrigin(Pawn->GetActorLocation());
FGameplayEffectSpec GESpec{ GameplayEffectClass.GetDefaultObject(), GEContextHandle, EffectLevel };
// We need to create a new valid prediction key (as if we just started activating an ability) so the GE will fire even if we are not the authority.
FPredictionKey FakePredictionKey = (PC->GetLocalRole() == ENetRole::ROLE_Authority) ? FPredictionKey() : FPredictionKey::CreateNewPredictionKey(ASC);
FActiveGameplayEffectHandle ActiveGEHandle = ASC->ApplyGameplayEffectSpecToSelf(GESpec, FakePredictionKey);
if (ActiveGEHandle.IsValid())
{
UE_LOG(LogConsoleResponse, Log, TEXT("Successfully Applied (Non-Instant) Gameplay Effect '%s' on Player '%s'."), *GameplayEffectClass->GetName(), *PC->GetName());
}
else if (ActiveGEHandle.WasSuccessfullyApplied())
{
UE_LOG(LogConsoleResponse, Log, TEXT("Executed (Instant) Gameplay Effect '%s' on Player '%s'."), *GameplayEffectClass->GetName(), *PC->GetName());
}
else
{
UE_LOG(LogConsoleResponse, Log, TEXT("Failed to Apply Gameplay Effect '%s' on Player '%s'."), *GameplayEffectClass->GetName(), *PC->GetName());
}
}
void UAbilitySystemCheatManagerExtension::EffectRemove(const FString& NameOrHandle) const
{
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
APlayerController* PC = GetPlayerController();
UAbilitySystemComponent* ASC = PC ? AbilitySystemGlobals.GetAbilitySystemComponentFromActor(PC->GetPawn()) : nullptr;
if (!ASC)
{
UE_LOG(LogConsoleResponse, Log, TEXT("PlayerController %s did not have an AbilitySystemComponent"), *GetNameSafe(PC));
return;
}
uint32 SearchHandleValue = 0;
if (NameOrHandle.IsNumeric())
{
int64 SearchHash64 = FCString::Atoi64(*NameOrHandle);
if (SearchHash64 > 0)
{
SearchHandleValue = static_cast<uint32>(SearchHash64 & UINT_MAX);
}
}
bool bAnyMatch = false;
const FActiveGameplayEffectsContainer& ActiveGEContainer = ASC->GetActiveGameplayEffects();
for (const FActiveGameplayEffectHandle& ActiveGEHandle : ActiveGEContainer.GetAllActiveEffectHandles())
{
if (const FActiveGameplayEffect* ActiveGE = ASC->GetActiveGameplayEffect(ActiveGEHandle))
{
const UGameplayEffect* GESpecDef = ActiveGE->Spec.Def;
const uint32 ActiveGEHandleValue = GetTypeHash(ActiveGEHandle);
const bool bMatch = (SearchHandleValue > 0) ? (ActiveGEHandleValue == SearchHandleValue) : GESpecDef->GetName().Contains(NameOrHandle);
if (bMatch)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Removing Active GE Handle %u (%s)"), ActiveGEHandleValue, *GESpecDef->GetName());
ASC->RemoveActiveGameplayEffect(ActiveGEHandle);
bAnyMatch = true;
}
}
}
if (!bAnyMatch)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Found no Active Gameplay Effects on %s (%s) that matched '%s'"), *PC->GetName(), *ASC->GetName(), *NameOrHandle);
}
}
/**
* Handy wrapper function that executes a named exec function with the given Arguments, but through the CheatManager. This allows the function to execute with context
* if RPC'd to the server, thus routing to the proper PlayerController. All of the functions support -server as an optional parameter, routing the function directly to the server.
*/
void ForwardToAbilitySystemCheatManagerExtension(const FName& ExecFunctionName, const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
if (APlayerController* PlayerController = World->GetFirstPlayerController())
{
APawn* PCPawn = PlayerController->GetPawnOrSpectator();
if (UCheatManager* CheatManager = PlayerController->CheatManager)
{
// All of these commands allow -server to force to be sent to the server
TArray<FString> MutableArgs = Args;
const bool bExecuteOnServer = MutableArgs.RemoveSingle(TEXT("-server")) > 0;
const FString Cmd = FString::Printf(TEXT("%s %s"), *ExecFunctionName.ToString(), *FString::Join(MutableArgs, TEXT(" ")));
if (bExecuteOnServer)
{
OutputDevice.Logf(TEXT("Executing 'ServerExec %s':"), *Cmd);
PlayerController->ServerExec(Cmd);
}
else
{
const bool bSuccess = CheatManager->ProcessConsoleExec(*Cmd, OutputDevice, PCPawn);
if (!bSuccess)
{
OutputDevice.Logf(TEXT("CheatManager %s on PlayerController %s did not process command '%s'"), *GetNameSafe(CheatManager), *GetNameSafe(PlayerController), *Cmd);
}
}
}
else
{
OutputDevice.Logf(TEXT("PlayerController %s did not have a CheatManager"), *GetNameSafe(PlayerController));
}
}
else
{
OutputDevice.Logf(TEXT("Could not find Player Controller in %s"), *GetNameSafe(World));
}
}
//
// AbilitySystem.Ability Debug Commands forward to the CheatManager
//
FAutoConsoleCommand DebugAbilitySystemAbilityGrantCommand(TEXT("AbilitySystem.Ability.Grant"), TEXT("<ClassName/AssetName>. Grants an Ability to the Player. Granting only happens on the Authority, so this command will be sent to the Server."),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
if (Args.Num() < 1)
{
OutputDevice.Logf(TEXT("Missing Arguments: <ClassName/AssetName>"));
OutputDevice.Logf(TEXT(" ClassName: If supplied, the ClassName of the loaded GameplayAbility to Grant."));
OutputDevice.Logf(TEXT(" AssetName: If supplied, the AssetName of the loaded GameplayAbility to Grant."));
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityListGranted), Args, World, OutputDevice);
return;
}
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityGrant), Args, World, OutputDevice);
}), ECVF_Cheat);
FAutoConsoleCommand DebugAbilitySystemAbilityCancelCommand(TEXT("AbilitySystem.Ability.Cancel"), TEXT("[-Server] <PartialName>. Cancels (prematurely Ends) a currently executing Gameplay Ability"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
if (Args.Num() < 1)
{
OutputDevice.Logf(TEXT("Missing Arguments: <PartialName>"));
OutputDevice.Logf(TEXT(" -Server: Indicates this command should be sent to the server, rather than executed locally."));
OutputDevice.Logf(TEXT(" PartialName: A substring of the currently active abilities to cancel. All matching abilities will be cancelled."));
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityListGranted), Args, World, OutputDevice);
return;
}
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityCancel), Args, World, OutputDevice);
}), ECVF_Cheat);
FAutoConsoleCommand DebugAbilitySystemAbilityActivateCommand(TEXT("AbilitySystem.Ability.Activate"), TEXT("[-Server] <TagName/ClassName/AssetName>. Activate a Gameplay Ability.\nSubstring name matching works for Activation Tags (on already granted abilities), Asset Paths (on non-granted abilities), or Class Names on both."),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
if (Args.Num() < 1)
{
OutputDevice.Logf(TEXT("This command Activates an Ability. If the Ability is not already granted, then it is activated on the server using GiveAbilityAndActivateOnce, which will remove it after completion."));
OutputDevice.Logf(TEXT("Missing Arguments: <TagName or ClassName or AssetName>"));
OutputDevice.Logf(TEXT(" -Server: Indicates this command should be sent to the server, rather than executed locally."));
OutputDevice.Logf(TEXT(" TagName: If the argument is a GameplayTag, it will be activated using TryActivateAbilitiesByTag which requires the Gameplay Ability be previously granted."));
OutputDevice.Logf(TEXT(" ClassName or AssetName: A substring of class/asset. Only the best match that is currently loaded will be considered."));
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityListGranted), Args, World, OutputDevice);
return;
}
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityActivate), Args, World, OutputDevice);
}), ECVF_Cheat);
FAutoConsoleCommand DebugAbilitySystemAbilityListGrantedCommand(TEXT("AbilitySystem.Ability.ListGranted"), TEXT("List the Gameplay Abilities that are granted to the local player"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, AbilityListGranted), Args, World, OutputDevice);
}), ECVF_Cheat);
//
// AbilitySystem.Effect Debug Commands forward to the CheatManager
//
FAutoConsoleCommand DebugAbilitySystemEffectListActiveCommand(TEXT("AbilitySystem.Effect.ListActive"), TEXT("[-Server] Lists all of the Gameplay Effects currently active on the Player"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, EffectListActive), Args, World, OutputDevice);
}), ECVF_Cheat);
FAutoConsoleCommand DebugAbilitySystemEffectRemoveCommand(TEXT("AbilitySystem.Effect.Remove"), TEXT("[-Server] <Handle/Name>. Remove a Gameplay Effect that is currently active on the Player"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
if (Args.Num() < 1)
{
OutputDevice.Logf(TEXT("Missing Arguments: [-Server] <Handle or Name>"));
OutputDevice.Logf(TEXT(" -Server: Indicates this command should be sent to the server, rather than executed locally."));
OutputDevice.Logf(TEXT(" Handle: The numeric handle of the Gameplay Effect to remove (found through EffectListActive). This is not compatible with -server."));
OutputDevice.Logf(TEXT(" Name: Partial name of the Gameplay Effect to remove."));
return;
}
// Let's just make sure the user isn't about to do something that's going to be profoundly confusing
const bool bExecuteOnServer = Args.Contains("-server");
if (bExecuteOnServer)
{
const bool bArgIsHandle = Args.ContainsByPredicate([](const FString& Value) { return FCString::Atoi64(*Value) > 0; });
if (bArgIsHandle)
{
UE_LOG(LogConsoleResponse, Log, TEXT("Error: Search by Handle Value is not permitted with -server (because ActiveGE Handles are not replicated to the same handle value)"));
return;
}
}
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, EffectRemove), Args, World, OutputDevice);
}), ECVF_Cheat);
FAutoConsoleCommand DebugAbilitySystemEffectApply(TEXT("AbilitySystem.Effect.Apply"), TEXT("[-Server] <Class/AssetName> [Level]. Apply a Gameplay Effect on the Player. Substring name matching works for Asset Tags, Asset Paths, or Class Names. Use -Server to send to the server (default is apply locally)."),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, UWorld* World, FOutputDevice& OutputDevice)
{
if (Args.Num() < 1)
{
OutputDevice.Logf(TEXT("Missing Arguments: [-Server] <MatchString> [Level]"));
OutputDevice.Logf(TEXT(" -Server: Indicates this command should be sent to the server, rather than predicted locally."));
OutputDevice.Logf(TEXT(" MatchString: Supply a substring of a class or asset name to match. Only loaded GameplayEffects are searched."));
OutputDevice.Logf(TEXT(" Level: Optionally supply a Level such as 4. Can be omitted (in which case the GE is level-less)."));
return;
}
ForwardToAbilitySystemCheatManagerExtension(GET_FUNCTION_NAME_CHECKED(UAbilitySystemCheatManagerExtension, EffectApply), Args, World, OutputDevice);
}), ECVF_Cheat);