-
Notifications
You must be signed in to change notification settings - Fork 191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ISMLExtendedAttributeProvider #238
+253
−9
Closed
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
e51b2fa
Started work on Extended Attribute Provider Interface (gameplay tags …
budak7273 9040461
Troubleshooting IsDescriptorFilteredOut behavior
budak7273 d4e7b5d
Fix EGetObtainableItemDescriptorsFlags::IncludeSpecial flag not working
budak7273 221e662
Workaround for SML.Registry.Item.SpecialItemDescriptor tag until tags…
budak7273 135d91d
Update ExampleMod demo for Gameplay Tags + GetObtainableItemDescriptors
budak7273 99878cb
Switch back to const argument
budak7273 a3d6584
Replace the other IsA checks too
budak7273 f51c1cc
Merge branch 'dev' into robb-dev
budak7273 a47dfa0
Add all enabled mdos to gameplay tag ini search path. Modified from m…
budak7273 59bb6db
Move SMLGameplayTags to the SML mod folder now that that's supported
budak7273 79b4393
Switch to the "real" way of checking gameplay tag membership now that…
budak7273 6b28a00
Remove all-defaults file (see PR comment)
budak7273 e2022aa
Merge branch 'dev' into robb-dev
budak7273 06f55a1
Progress on Content Tag Registry implementation, currently only with …
budak7273 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file added
BIN
+156 KB
Mods/ExampleMod/Content/ChatCommands/CC_ExampleListSpecialItems.uasset
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified
BIN
+193 Bytes
(100%)
Mods/ExampleMod/Content/Schematics/Milestone/Schematic_ExampleMilestone.uasset
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[/Script/GameplayTags.GameplayTagsList] | ||
GameplayTagList=(Tag="SML.Registry.Item.SpecialItemDescriptor",DevComment="Consider an ItemDescriptor a member of \'IncludeSpecial\' for the purposes of ModContentRegistry::IsDescriptorFilteredOut") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
Mods/SML/Source/SML/Private/Registry/ContentTagRegistry.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
#include "Registry/ContentTagRegistry.h" | ||
|
||
#include "ModLoading/PluginModuleLoader.h" | ||
|
||
DEFINE_LOG_CATEGORY(LogContentTagRegistry); | ||
|
||
FFrame* UContentTagRegistry::ActiveScriptFramePtr{}; | ||
|
||
/** Makes sure provided object instance is valid, crashes with both script call stack and native stack trace if it's not */ | ||
#define NOTIFY_INVALID_REGISTRATION(Context) \ | ||
{ \ | ||
/* Attempt to use cached script frame pointer first, then fallback to global script callstack (which is not available in shipping by default) */ \ | ||
const FString ScriptCallstack = UContentTagRegistry::GetCallStackContext(); \ | ||
UE_LOG(LogContentTagRegistry, Error, TEXT("Attempt to modify content tags failed: %s"), Context); \ | ||
UE_LOG(LogContentTagRegistry, Error, TEXT("Script Callstack: %s"), *ScriptCallstack); \ | ||
ensureMsgf( false, TEXT("%s"), *Context ); \ | ||
} \ | ||
|
||
UContentTagRegistry::UContentTagRegistry() { | ||
} | ||
|
||
UContentTagRegistry* UContentTagRegistry::Get(const UObject* WorldContext) { | ||
if (const UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull)) { | ||
return World->GetSubsystem<UContentTagRegistry>(); | ||
} | ||
return nullptr; | ||
} | ||
|
||
const FGameplayTagContainer UContentTagRegistry::GetGameplayTagContainerFor(const UObject* content) { | ||
const auto record = TagContainerRegistry.Find(content); | ||
return (record == nullptr) ? FGameplayTagContainer() : *record; | ||
} | ||
|
||
bool UContentTagRegistry::CanModifyTagsOf(UObject* content, FString& OutMessage) { | ||
if (bRegistryFrozen) { | ||
OutMessage = FString::Printf(TEXT("Attempt to modify content tags of object '%s' in frozen registry. Make sure your tag changes are happening in the 'Initialization' Lifecycle Phase and not 'Post Initialization'. TODO update this message with the timing we decide on."), *GetPathNameSafe(content)); | ||
return false; | ||
} | ||
if (!IsValid(content)) { | ||
OutMessage = FString::Printf(TEXT("Attempt to modify content tags of an invalid object.")); | ||
return false; | ||
} | ||
// TODO checking to make sure a UClass is being registered and not a joe schmoe arbitrary uobject instance | ||
return true; | ||
} | ||
|
||
void UContentTagRegistry::AddGameplayTagsTo(UObject* content, const FGameplayTagContainer tags) { | ||
FString Context; | ||
if (!CanModifyTagsOf(content, Context)) { | ||
NOTIFY_INVALID_REGISTRATION(*Context); | ||
return; | ||
} | ||
auto& record = TagContainerRegistry.FindOrAdd(content); | ||
UE_LOG(LogContentTagRegistry, Verbose, TEXT("Adding tags %s for class %s"), *tags.ToString(), *GetFullNameSafe(content)); | ||
record.AppendTags(tags); | ||
} | ||
|
||
void UContentTagRegistry::FreezeRegistry() { | ||
bRegistryFrozen = true; | ||
} | ||
|
||
bool UContentTagRegistry::ShouldCreateSubsystem(UObject* Outer) const { | ||
UWorld* WorldOuter = CastChecked<UWorld>(Outer); | ||
return FPluginModuleLoader::ShouldLoadModulesForWorld(WorldOuter); | ||
} | ||
|
||
void UContentTagRegistry::OnActorPreSpawnInitialization(AActor* Actor) { | ||
OnWorldBeginPlayDelegate.AddWeakLambda(this, [&]() { | ||
FreezeRegistry(); | ||
}); | ||
} | ||
|
||
void UContentTagRegistry::OnWorldBeginPlay(UWorld& InWorld) { | ||
OnWorldBeginPlayDelegate.Broadcast(); | ||
} | ||
|
||
FString UContentTagRegistry::GetCallStackContext() { | ||
// Prefer script callstack to the native one | ||
if (ActiveScriptFramePtr != nullptr) { | ||
return ActiveScriptFramePtr->Node->GetPathName(); | ||
} | ||
|
||
// Attempt to capture stack trace | ||
TArray<FProgramCounterSymbolInfo> NativeStackTrace = FPlatformStackWalk::GetStack(1, 10); | ||
if (NativeStackTrace.IsEmpty()) { | ||
FProgramCounterSymbolInfo& Info = NativeStackTrace.Emplace_GetRef(); | ||
TCString<ANSICHAR>::Strcpy(Info.Filename, FProgramCounterSymbolInfo::MAX_NAME_LENGTH, "Unknown"); | ||
Info.LineNumber = 1; | ||
} | ||
|
||
// Find first frame that does not contain internal logic of content registry | ||
int32 FirstExternalFrameIndex = 0; | ||
while (FirstExternalFrameIndex + 1 < NativeStackTrace.Num() && | ||
FCStringAnsi::Strifind(NativeStackTrace[FirstExternalFrameIndex].Filename, __FILE__) != nullptr) { | ||
FirstExternalFrameIndex++; | ||
} | ||
return FString::Printf(TEXT("%hs:%d"), NativeStackTrace[FirstExternalFrameIndex].Filename, NativeStackTrace[FirstExternalFrameIndex].LineNumber); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
#pragma once | ||
|
||
#include "CoreMinimal.h" | ||
#include "Subsystems/WorldSubsystem.h" | ||
#include "GameplayTagContainer.h" | ||
#include "ContentTagRegistry.generated.h" | ||
|
||
DECLARE_LOG_CATEGORY_EXTERN(LogContentTagRegistry, Log, All); | ||
|
||
/** | ||
* Manages Gameplay Tag Containers for content classes. | ||
* Enables any mod to apply Unreal Gameplay Tags to any mod's content, | ||
* or the base game's content, at the UClass level. | ||
* | ||
* Add tags to content via this registry's provided avenues, | ||
* all returned tag containers are const. | ||
* | ||
* Tag associations can only be made before save is loaded, | ||
* after that moment the registry is frozen and no changes can be made after that. | ||
*/ | ||
UCLASS() | ||
class SML_API UContentTagRegistry : public UWorldSubsystem | ||
{ | ||
GENERATED_BODY() | ||
public: | ||
UContentTagRegistry(); | ||
|
||
/** Retrieves content tag registry instance */ | ||
UFUNCTION(BlueprintPure, Category = "Content Tag Registry", DisplayName = "GetContentTagRegistry", meta = (WorldContext = "WorldContext")) | ||
static UContentTagRegistry* Get(const UObject* WorldContext); | ||
|
||
/** | ||
* Get Gameplay Tag container for the supplied class. | ||
* Could be empty if there were no tags registered. | ||
* TODO outvar bool for found/not? | ||
*/ | ||
UFUNCTION(BlueprintPure, Category = "Content Tag Registry") | ||
const FGameplayTagContainer GetGameplayTagContainerFor(const UObject* content); | ||
|
||
/** | ||
* Register gameplay tags from the passed container to the passed class | ||
* TODO FName InRegistrationPluginName? | ||
*/ | ||
UFUNCTION(BlueprintCallable, Category = "Content Tag Registry") | ||
void AddGameplayTagsTo(UObject* content, FGameplayTagContainer tags); | ||
|
||
// TODO ability to remove tags | ||
|
||
// TODO auto register of stuff with ExtendedAttributeProvider being registered in mod content registry | ||
|
||
// Freezes the registry. No new registrations are accepted past this point. | ||
void FreezeRegistry(); | ||
|
||
void OnActorPreSpawnInitialization(AActor* Actor); | ||
|
||
// Begin USubsystem interface | ||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override; | ||
//virtual void Initialize(FSubsystemCollectionBase& Collection) override; | ||
virtual void OnWorldBeginPlay(UWorld& InWorld) override; | ||
// End USubsystem interface | ||
|
||
private: | ||
UPROPERTY() | ||
TMap<UObject*, FGameplayTagContainer> TagContainerRegistry; | ||
|
||
bool bRegistryFrozen{ false }; | ||
|
||
DECLARE_MULTICAST_DELEGATE(FOnWorldBeginPlay); | ||
FOnWorldBeginPlay OnWorldBeginPlayDelegate; | ||
|
||
static FString GetCallStackContext(); | ||
|
||
/** Pointer to the currently active script callstack frame, used for debugging purposes */ | ||
static FFrame* ActiveScriptFramePtr; | ||
|
||
bool CanModifyTagsOf(UObject* content, FString& OutMessage); | ||
|
||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
Mods/SML/Source/SML/Public/Registry/SMLExtendedAttributeProvider.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#pragma once | ||
#include "CoreMinimal.h" | ||
#include "GameplayTagContainer.h" | ||
#include "SMLExtendedAttributeProvider.generated.h" | ||
|
||
/** | ||
* Unreal reflection stub class of ISMLExtendedAttributeProvider. | ||
* Use this instead of ISMLExtendedAttributeProvider for UObject->Implements<ISMLExtendedAttributeProvider>() | ||
*/ | ||
UINTERFACE(Blueprintable) | ||
class SML_API USMLExtendedAttributeProvider : public UInterface { | ||
GENERATED_BODY() | ||
}; | ||
|
||
/** | ||
* Implement this on an asset to enable it to provide Gameplay Tags to other systems. | ||
* | ||
* Example: Implement on an FGItemDescriptor and provide the SML.Registry.Item.SpecialItemDescriptor tag | ||
* for the item to be considered "special" and filtered out by default by GetObtainableItemDescriptors | ||
*/ | ||
class SML_API ISMLExtendedAttributeProvider { | ||
GENERATED_BODY() | ||
public: | ||
|
||
/** | ||
* Get the Gameplay Tags container for this asset. | ||
* Remember, since it's a BlueprintNativeEvent, to call from C++ you must call via ISMLExtendedAttributeProvider::Execute_GetGameplayTagsContainer | ||
*/ | ||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="SML|Tags") | ||
FGameplayTagContainer GetGameplayTagsContainer(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since SML has c++, and this tag is used in c++, it makes more sense to register it using native tags, with the new UE5 macros, rather than registering it in a config file and using the find function every time.
In the .h:
SML_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_SML_Registry_Item_SpecialItemDescriptor)
In the .cpp:
UE_DEFINE_GAMEPLAY_TAG_COMMENT(TAG_SML_Registry_Item_SpecialItemDescriptor, "SML.Registry.Item.SpecialItemDescriptor", "comment goes here")
return ItemTags.HasTag(TAG_SML_Registry_Item_SpecialItemDescriptor);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, but we should then add a tag to examplemod using the config file approach. Any ideas for what that tag could be?