diff --git a/Source/Editor/Private/Asset/AssetTypeAction_FactionCollection.cpp b/Source/Editor/Private/Asset/AssetTypeAction_FactionCollection.cpp new file mode 100644 index 0000000..3aeea0c --- /dev/null +++ b/Source/Editor/Private/Asset/AssetTypeAction_FactionCollection.cpp @@ -0,0 +1,39 @@ +// Copyright 2015-2020 Piperift. All Rights Reserved. + +#include "Asset/AssetTypeAction_FactionCollection.h" + +#include "AssetToolsModule.h" +#include "ContentBrowserModule.h" +#include "FactionCollection.h" +#include "FactionsModule.h" + + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + + +////////////////////////////////////////////////////////////////////////// +// FAssetTypeAction_FactionCollection + +FText FAssetTypeAction_FactionCollection::GetName() const +{ + return LOCTEXT("FAssetTypeAction_FactionCollectionName", "Faction Collection"); +} + +FColor FAssetTypeAction_FactionCollection::GetTypeColor() const +{ + return FColor(170, 66, 244); +} + +UClass* FAssetTypeAction_FactionCollection::GetSupportedClass() const +{ + return UFactionCollection::StaticClass(); +} + +uint32 FAssetTypeAction_FactionCollection::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/FactionCollectionFactory.cpp b/Source/Editor/Private/Asset/FactionCollectionFactory.cpp new file mode 100644 index 0000000..b061c54 --- /dev/null +++ b/Source/Editor/Private/Asset/FactionCollectionFactory.cpp @@ -0,0 +1,33 @@ +// Copyright 2015-2020 Piperift. All Rights Reserved. + +#include "Asset/FactionCollectionFactory.h" + +#include +#include +#include "Kismet2/SClassPickerDialog.h" +#include "FactionCollection.h" + + +#define LOCTEXT_NAMESPACE "FactionCollection" + +UFactionCollectionFactory::UFactionCollectionFactory() + : Super() +{ + SupportedClass = UFactionCollection::StaticClass(); + + bCreateNew = true; + bEditAfterNew = true; +} + +UObject* UFactionCollectionFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + if(Class != nullptr) + { + // if we have no asset class, use the passed-in class instead + check(Class == UFactionCollection::StaticClass() || Class->IsChildOf(UFactionCollection::StaticClass())); + return NewObject(InParent, Class, Name, Flags); + } + return nullptr; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/FactionsEditor.cpp b/Source/Editor/Private/FactionsEditor.cpp index 0c98d27..4e49e4e 100644 --- a/Source/Editor/Private/FactionsEditor.cpp +++ b/Source/Editor/Private/FactionsEditor.cpp @@ -11,7 +11,7 @@ #include -//#include "Asset/AssetTypeAction_FactionCollection.h" +#include "Asset/AssetTypeAction_FactionCollection.h" DEFINE_LOG_CATEGORY(LogFactionsEditor) @@ -28,7 +28,7 @@ void FFactionsEditorModule::StartupModule() PrepareAutoGeneratedDefaultEvents(); IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - //RegisterAssetTypeAction(AssetTools, MakeShared()); + RegisterAssetTypeAction(AssetTools, MakeShared()); } void FFactionsEditorModule::ShutdownModule() diff --git a/Source/Editor/Public/Asset/AssetTypeAction_FactionCollection.h b/Source/Editor/Public/Asset/AssetTypeAction_FactionCollection.h new file mode 100644 index 0000000..073911b --- /dev/null +++ b/Source/Editor/Public/Asset/AssetTypeAction_FactionCollection.h @@ -0,0 +1,17 @@ +// Copyright 2015-2020 Piperift. All Rights Reserved. + +#pragma once + +#include "AssetTypeActions_Base.h" + +class FACTIONSEDITOR_API FAssetTypeAction_FactionCollection : public FAssetTypeActions_Base +{ +public: + // IAssetTypeActions interface + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual bool HasActions(const TArray& InObjects) const override { return false; } + virtual uint32 GetCategories() override; + // End of IAssetTypeActions interface +}; diff --git a/Source/Editor/Public/Asset/FactionCollectionFactory.h b/Source/Editor/Public/Asset/FactionCollectionFactory.h new file mode 100644 index 0000000..d283fea --- /dev/null +++ b/Source/Editor/Public/Asset/FactionCollectionFactory.h @@ -0,0 +1,20 @@ +// Copyright 2015-2020 Piperift. All Rights Reserved. + +#pragma once + +#include "Factories/Factory.h" +#include "FactionCollection.h" +#include "FactionCollectionFactory.generated.h" + + +UCLASS() +class UFactionCollectionFactory : public UFactory +{ + GENERATED_BODY() + + UFactionCollectionFactory(); + + // UFactory interface + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + // End of UFactory interface +}; diff --git a/Source/Factions/Private/FactionsSubsystem.cpp b/Source/Factions/Private/FactionsSubsystem.cpp index 7cee846..bdf22dc 100644 --- a/Source/Factions/Private/FactionsSubsystem.cpp +++ b/Source/Factions/Private/FactionsSubsystem.cpp @@ -22,17 +22,33 @@ void UFactionsSubsystem::PostInitProperties() if (!IsDefaultSubobject() && !hasSetTeamIdAttitudeSolver) { - FGenericTeamId::SetAttitudeSolver([this](FGenericTeamId A, FGenericTeamId B) - { + FGenericTeamId::SetAttitudeSolver([this](FGenericTeamId A, FGenericTeamId B) { return GetAttitude(FromTeamId(A), FromTeamId(B)); }); hasSetTeamIdAttitudeSolver = true; } } -const FFactionDescriptor* UFactionsSubsystem::GetDescriptor(FFaction Faction) const +const FFactionDescriptor* UFactionsSubsystem::FindDescriptor( + FFaction Faction, UFactionCollection** OutCollection) const { - return GetFactions().GetDescriptor(Faction); + const FFactionDescriptor* Descriptor = GetFactions().GetDescriptor(Faction); + UFactionCollection* Collection = nullptr; + + for (auto It = Collections.begin(); !Descriptor && It != Collections.end(); ++It) + { + Collection = *It; + if (Collection) + { + Descriptor = Collection->Factions.GetDescriptor(Faction); + } + } + + if (Descriptor && OutCollection) + { + *OutCollection = Collection; + } + return nullptr; } TEnumAsByte UFactionsSubsystem::GetAttitude( @@ -51,16 +67,22 @@ TEnumAsByte UFactionsSubsystem::GetAttitude( } else if (!Source.IsNone()) { - UE_LOG(LogFactions, Warning, TEXT("Tried to get an attitude using an invalid faction ('%s'). All factions must be registered in the Factions Subsystem."), *Source.GetId().ToString()); + UE_LOG(LogFactions, Warning, + TEXT("Tried to get an attitude using an invalid faction ('%s'). All factions must be registered " + "in the Factions Subsystem."), + *Source.GetId().ToString()); } return ETeamAttitude::Neutral; } int32 UFactionsSubsystem::GetFactionIndex(FFaction Faction) const { - return Algo::BinarySearchBy(BakedBehaviors, Faction.GetId(), [](const auto& Behavior) { - return Behavior.Id; - }, FBehaviorSort{}); + return Algo::BinarySearchBy( + BakedBehaviors, Faction.GetId(), + [](const auto& Behavior) { + return Behavior.Id; + }, + FBehaviorSort{}); } FFaction UFactionsSubsystem::FromTeamId(FGenericTeamId TeamId) const @@ -76,8 +98,9 @@ FFaction UFactionsSubsystem::FromTeamId(FGenericTeamId TeamId) const FGenericTeamId UFactionsSubsystem::ToTeamId(FFaction Faction) const { const int32 Index = GetFactionIndex(Faction); - if (Index != INDEX_NONE && - ensureMsgf(Index < FGenericTeamId::NoTeam.GetId(), TEXT("Faction Index exceeded maximum GenericTeamIds. GenericTeamId only supports up to 255 teams"))) + if (Index != INDEX_NONE && ensureMsgf(Index < FGenericTeamId::NoTeam.GetId(), + TEXT("Faction Index exceeded maximum GenericTeamIds. GenericTeamId only " + "supports up to 255 teams"))) { return FGenericTeamId{uint8(Index)}; } @@ -86,11 +109,10 @@ FGenericTeamId UFactionsSubsystem::ToTeamId(FFaction Faction) const FString UFactionsSubsystem::GetDisplayName(const FFaction Faction) const { - const auto* Descriptor = GetDescriptor(Faction); - if (Descriptor) + if (const auto* Descriptor = FindDescriptor(Faction)) { const bool bUseId = Descriptor->bIdAsDisplayName || - (bUseIdsIfDisplayNamesAreEmpty && Descriptor->DisplayName.IsEmpty()); + (bUseIdsIfDisplayNamesAreEmpty && Descriptor->DisplayName.IsEmpty()); if (!bUseId) { return Descriptor->DisplayName.ToString(); @@ -147,6 +169,45 @@ bool UFactionsSubsystem::RemoveRelation(const FFactionRelation& Relation) return false; } +bool UFactionsSubsystem::AddCollection(UFactionCollection* Collection) +{ + if (Collections.Contains(Collection)) + { + UE_LOG(LogFactions, Warning, TEXT("Collection ('%s') has already been added before."), + *Collection->GetName()); + return false; + } + + Collections.Add(Collection); + + bool bFactionExisted = false; + for (const auto& Descriptor : Collection->Factions.List) + { + AddBakedFaction(Descriptor.Key, Descriptor.Value, &bFactionExisted); + if (bFactionExisted) + { + UE_LOG(LogFactions, Warning, TEXT("Added a duplicated faction from a collection. Relation: (%s)"), + *Descriptor.Key.ToString()); + } + } + + bool bRepeated = false; + Relations.List.Reserve(Relations.List.Num() + Collection->Relations.List.Num()); + for (const auto& Relation : Collection->Relations.List) + { + Relations.List.Add(Relation, &bRepeated); + if (bRepeated) + { + UE_LOG(LogFactions, Warning, + TEXT("Added a duplicated relation from a collection. Relation: (%s)"), + *Relation.ToString(Collection)); + } + } + Relations.List.Append(Collection->Relations.List); + + return true; +} + int32 UFactionsSubsystem::ClearFactions() { const int32 Count = Factions.Num(); @@ -217,7 +278,7 @@ bool UFactionsSubsystem::GetActorsByFaction(const FFaction Faction, TArray& OutFactions) const { - const auto& AllFactions = GetFactions().List; - - OutFactions.Reserve(OutFactions.Num() + AllFactions.Num()); - for (const auto& Entry : AllFactions) + OutFactions.Reserve(OutFactions.Num() + BakedBehaviors.Num()); + for (const auto& Behavior : BakedBehaviors) { - OutFactions.Add({Entry.Key}); + OutFactions.Add({Behavior.Id}); } } -bool UFactionsSubsystem::BPGetDescriptor(const FFaction Faction, FFactionDescriptor& Descriptor) const +bool UFactionsSubsystem::BPFindDescriptor( + const FFaction Faction, FFactionDescriptor& Descriptor, UFactionCollection*& Collection) const { - if (auto* Found = GetFactions().GetDescriptor(Faction)) + if (auto* Found = FindDescriptor(Faction, &Collection)) { Descriptor = *Found; return true; @@ -293,37 +353,36 @@ void UFactionsSubsystem::BakeFactions() for (const auto& It : Factions.List) { const auto& Descriptor = It.Value; - BakedBehaviors.Add({ - It.Key, - Descriptor.SelfAttitude, - Descriptor.ExternalAttitude - }); + BakedBehaviors.Add({It.Key, Descriptor.SelfAttitude, Descriptor.ExternalAttitude}); } - BakedBehaviors.Sort([](const auto& A, const auto& B){ + BakedBehaviors.Sort([](const auto& A, const auto& B) { return A.Id.FastLess(B.Id); }); } -void UFactionsSubsystem::AddBakedFaction(FName Id, const FFactionDescriptor& Descriptor) +void UFactionsSubsystem::AddBakedFaction( + FName Id, const FFactionDescriptor& Descriptor, bool* bWasAlreadyAdded) { // Insert sorted - const int32 Index = Algo::LowerBoundBy(BakedBehaviors, Id, [](const auto& Behavior) { - return Behavior.Id; - }, FBehaviorSort{}); + const int32 Index = Algo::LowerBoundBy( + BakedBehaviors, Id, + [](const auto& Behavior) { + return Behavior.Id; + }, + FBehaviorSort{}); check(Index >= 0 && Index <= BakedBehaviors.Num()); - const FBakedFactionBehavior Behavior { - Id, - Descriptor.SelfAttitude, - Descriptor.ExternalAttitude - }; + const FBakedFactionBehavior Behavior{Id, Descriptor.SelfAttitude, Descriptor.ExternalAttitude}; + bool bReplaced = false; if (BakedBehaviors.IsValidIndex(Index)) { - // Since we returned lower bound we already know Id <= Index key. So if Id is not < Index key, they must be equal + // Since we returned lower bound we already know Id <= Index key. So if Id is not < Index key, they + // must be equal if (!FBehaviorSort{}(Id, BakedBehaviors[Index].Id)) { // Found, replace BakedBehaviors[Index] = Behavior; + bReplaced = true; } else { @@ -334,6 +393,11 @@ void UFactionsSubsystem::AddBakedFaction(FName Id, const FFactionDescriptor& Des { BakedBehaviors.Add(Behavior); } + + if (bWasAlreadyAdded) + { + *bWasAlreadyAdded = bReplaced; + } } void UFactionsSubsystem::RemoveBakedFaction(FFaction Faction) diff --git a/Source/Factions/Public/FactionCollection.h b/Source/Factions/Public/FactionCollection.h new file mode 100644 index 0000000..69a2c2a --- /dev/null +++ b/Source/Factions/Public/FactionCollection.h @@ -0,0 +1,43 @@ +// Copyright 2015-2020 Piperift. All Rights Reserved. + +#pragma once + +#include "FactionTable.h" +#include "RelationTable.h" + +#include + +#include "FactionCollection.generated.h" + + +#define LOCTEXT_NAMESPACE "FactionCollection" + +/** + * Struct containing information about a faction. Static use. + */ +UCLASS(Blueprintable) +class FACTIONS_API UFactionCollection : public UPrimaryDataAsset +{ + GENERATED_BODY() + +public: + + UPROPERTY(EditAnywhere, Category = Collection) + FFactionTable Factions; + + UPROPERTY(EditAnywhere, Category = Collection) + FRelationTable Relations; + + + const FFactionTable& GetFactions() const + { + return Factions; + } + + const FRelationTable& GetRelations() const + { + return Relations; + } +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Factions/Public/FactionRelation.h b/Source/Factions/Public/FactionRelation.h index 19af5b7..602524c 100644 --- a/Source/Factions/Public/FactionRelation.h +++ b/Source/Factions/Public/FactionRelation.h @@ -68,6 +68,16 @@ struct FACTIONS_API FFactionRelation { return GetTypeHash(InRelation.Source) ^ GetTypeHash(InRelation.Target); } + + FString ToString(const UObject* Owner) const; }; +inline FString FFactionRelation::ToString(const UObject* Owner) const +{ + FString Value{}; + StaticStruct()->ExportText(Value, this, nullptr, const_cast(Owner), + (PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited | PPF_IncludeTransient), nullptr); + return Value; +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/Factions/Public/FactionsSubsystem.h b/Source/Factions/Public/FactionsSubsystem.h index 286b1cc..255c953 100644 --- a/Source/Factions/Public/FactionsSubsystem.h +++ b/Source/Factions/Public/FactionsSubsystem.h @@ -3,13 +3,14 @@ #pragma once #include "FactionAgentInterface.h" +#include "FactionCollection.h" #include "FactionTable.h" #include "RelationTable.h" #include #include -#include #include +#include #include "FactionsSubsystem.generated.h" @@ -69,6 +70,10 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem UPROPERTY(Transient) TArray BakedBehaviors; + /** Currently added collections */ + UPROPERTY(Transient) + TArray Collections; + static bool hasSetTeamIdAttitudeSolver; @@ -82,10 +87,6 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem return Relations.List.Find({A, B}); } - FFactionTable& GetFactions() - { - return Factions; - } const FFactionTable& GetFactions() const { return Factions; @@ -93,7 +94,6 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem static UFactionsSubsystem* Get(const UObject* ContextObject); - const FFactionDescriptor* GetDescriptor(FFaction Faction) const; /** C++ ONLY API */ @@ -104,8 +104,10 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem bool IsFriendly(const UObject* Source, const UObject* Target) const; bool IsNeutral(const UObject* Source, const UObject* Target) const; - int32 GetFactionIndex(FFaction Faction) const; + const FFactionDescriptor* FindDescriptor( + FFaction Faction, UFactionCollection** OutCollection = nullptr) const; + int32 GetFactionIndex(FFaction Faction) const; FFaction FromTeamId(FGenericTeamId TeamId) const; FGenericTeamId ToTeamId(FFaction Faction) const; @@ -209,6 +211,14 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem UFUNCTION(BlueprintCallable, Category = Factions) bool RemoveRelation(const FFactionRelation& Relation); + /** + * Add a faction collection (along with its factions and relations) + * @param Collection to be added + * @return true if the collection was registered, false if the two factions were the same or invalid. + */ + UFUNCTION(BlueprintCallable, Category = Factions) + bool AddCollection(UFactionCollection* Collection); + /** * Removes all factions * @return number of removed factions @@ -248,35 +258,60 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem * @param Descriptor of the faction, if found * @return true if the faction was valid and information was found */ - UFUNCTION(BlueprintPure, Category = Factions, meta = (DisplayName = "Get Descriptor")) - bool BPGetDescriptor(const FFaction Faction, FFactionDescriptor& Descriptor) const; + UFUNCTION(BlueprintPure, Category = Factions, meta = (DisplayName = "Find Descriptor")) + bool BPFindDescriptor( + const FFaction Faction, FFactionDescriptor& Descriptor, UFactionCollection*& Collection) const; /** Return the faction of an actor. None if the actor doesn't implement FactionAgentInterface */ - UFUNCTION(BlueprintPure, Category = Factions, meta = (DefaultToSelf = "Source", DisplayName = "Get Faction")) - static FFaction BPGetObjectFaction(const UObject* Source) { return GetFaction(Source); } + UFUNCTION( + BlueprintPure, Category = Factions, meta = (DefaultToSelf = "Source", DisplayName = "Get Faction")) + static FFaction BPGetObjectFaction(const UObject* Source) + { + return GetFaction(Source); + } /** Sets the faction of an actor. Won't apply if the actor doesn't implement FactionAgentInterface. * @param Target actor that will receive the new faction * @param Faction that will be set */ - UFUNCTION(BlueprintCallable, Category = Factions, meta = (DefaultToSelf = "Target", DisplayName = "Set Faction")) - static void BPSetObjectFaction(UObject* Target, FFaction Faction) { SetFaction(Target, Faction); } + UFUNCTION(BlueprintCallable, Category = Factions, + meta = (DefaultToSelf = "Target", DisplayName = "Set Faction")) + static void BPSetObjectFaction(UObject* Target, FFaction Faction) + { + SetFaction(Target, Faction); + } /** @return Attitude of Source's faction towards Target's faction */ - UFUNCTION(BlueprintPure, Category = Factions, meta = (DefaultToSelf = "Source", DisplayName = "Get Attitude (Object)")) - TEnumAsByte BPGetObjectAttitude(const UObject* Source, const UObject* Target) const { return GetAttitude(Source, Target); } + UFUNCTION(BlueprintPure, Category = Factions, + meta = (DefaultToSelf = "Source", DisplayName = "Get Attitude (Object)")) + TEnumAsByte BPGetObjectAttitude(const UObject* Source, const UObject* Target) const + { + return GetAttitude(Source, Target); + } /** @return true if Source is Hostile to Target */ - UFUNCTION(BlueprintPure, Category = Factions, meta = (DefaultToSelf = "Source", DisplayName = "Is Hostile (Object)")) - bool BPIsObjectHostile(const UObject* Source, const UObject* Target) { return IsHostile(Source, Target); } + UFUNCTION(BlueprintPure, Category = Factions, + meta = (DefaultToSelf = "Source", DisplayName = "Is Hostile (Object)")) + bool BPIsObjectHostile(const UObject* Source, const UObject* Target) + { + return IsHostile(Source, Target); + } /** @return true if Source is Friendly to Target */ - UFUNCTION(BlueprintPure, Category = Factions, meta = (DefaultToSelf = "Source", DisplayName = "Is Friendly (Object)")) - bool BPIsObjectFriendly(const UObject* Source, const UObject* Target) { return IsFriendly(Source, Target); } + UFUNCTION(BlueprintPure, Category = Factions, + meta = (DefaultToSelf = "Source", DisplayName = "Is Friendly (Object)")) + bool BPIsObjectFriendly(const UObject* Source, const UObject* Target) + { + return IsFriendly(Source, Target); + } /** @return true if One is Neutral to Target */ - UFUNCTION(BlueprintPure, Category = Factions, meta = (DefaultToSelf = "Source", DisplayName = "Is Neutral (Object)")) - bool BPIsObjectNeutral(const UObject* Source, const UObject* Target) { return IsNeutral(Source, Target); } + UFUNCTION(BlueprintPure, Category = Factions, + meta = (DefaultToSelf = "Source", DisplayName = "Is Neutral (Object)")) + bool BPIsObjectNeutral(const UObject* Source, const UObject* Target) + { + return IsNeutral(Source, Target); + } protected: @@ -287,8 +322,26 @@ class FACTIONS_API UFactionsSubsystem : public UWorldSubsystem #endif void BakeFactions(); - void AddBakedFaction(FName Id, const FFactionDescriptor& Descriptor); + void AddBakedFaction(FName Id, const FFactionDescriptor& Descriptor, bool* bWasAlreadyAdded = nullptr); void RemoveBakedFaction(FFaction Faction); + + + /** DEPRECATIONS */ +public: + UE_DEPRECATED(5.1, "This function is deprecated. Use FindDescriptor instead") + const FFactionDescriptor* GetDescriptor(FFaction Faction) const + { + return FindDescriptor(Faction); + } + + UFUNCTION(BlueprintPure, Category = Factions, + meta = (DisplayName = "Get Descriptor", DeprecatedFunction, + DeprecationMessage = "This function is deprecated. Use FindDescriptor instead")) + bool BPGetDescriptor(const FFaction Faction, FFactionDescriptor& Descriptor) const + { + UFactionCollection* Collection = nullptr; + return BPFindDescriptor(Faction, Descriptor, Collection); + } }; @@ -318,8 +371,7 @@ inline void UFactionsSubsystem::SetFaction(UObject* Target, FFaction Faction) IFactionAgentInterface::SetFaction(Target, Faction); } -inline ETeamAttitude::Type UFactionsSubsystem::GetAttitude( - const UObject* Source, const UObject* Target) const +inline ETeamAttitude::Type UFactionsSubsystem::GetAttitude(const UObject* Source, const UObject* Target) const { return GetAttitude(GetFaction(Source), GetFaction(Target)); }