diff --git a/Content/Itemization/AffixTables/Affixes.uasset b/Content/Itemization/AffixTables/Affixes.uasset index 58de92c..59fb666 100644 Binary files a/Content/Itemization/AffixTables/Affixes.uasset and b/Content/Itemization/AffixTables/Affixes.uasset differ diff --git a/Content/Itemization/BP_ItemDrop.uasset b/Content/Itemization/BP_ItemDrop.uasset index d4d53ad..e403a89 100644 Binary files a/Content/Itemization/BP_ItemDrop.uasset and b/Content/Itemization/BP_ItemDrop.uasset differ diff --git a/Content/Itemization/DropTables/TreasureClass.uasset b/Content/Itemization/DropTables/TreasureClass.uasset index 0bd5858..17a5f0b 100644 Binary files a/Content/Itemization/DropTables/TreasureClass.uasset and b/Content/Itemization/DropTables/TreasureClass.uasset differ diff --git a/Content/Itemization/GameMode/BP_PlayerController.uasset b/Content/Itemization/GameMode/BP_PlayerController.uasset index 37435ac..29bb398 100644 Binary files a/Content/Itemization/GameMode/BP_PlayerController.uasset and b/Content/Itemization/GameMode/BP_PlayerController.uasset differ diff --git a/Content/Itemization/Inventories/WBP_CursorInventory.uasset b/Content/Itemization/Inventories/WBP_CursorInventory.uasset index 5328801..367fb0e 100644 Binary files a/Content/Itemization/Inventories/WBP_CursorInventory.uasset and b/Content/Itemization/Inventories/WBP_CursorInventory.uasset differ diff --git a/Content/Itemization/Inventories/WBP_InventorySlot.uasset b/Content/Itemization/Inventories/WBP_InventorySlot.uasset index ee77032..af21fbd 100644 Binary files a/Content/Itemization/Inventories/WBP_InventorySlot.uasset and b/Content/Itemization/Inventories/WBP_InventorySlot.uasset differ diff --git a/Content/Itemization/Inventories/WBP_ItemSocketOverlay.uasset b/Content/Itemization/Inventories/WBP_ItemSocketOverlay.uasset new file mode 100644 index 0000000..4d571c5 Binary files /dev/null and b/Content/Itemization/Inventories/WBP_ItemSocketOverlay.uasset differ diff --git a/Content/Itemization/Inventories/WBP_SocketSlot.uasset b/Content/Itemization/Inventories/WBP_SocketSlot.uasset new file mode 100644 index 0000000..6a0394e Binary files /dev/null and b/Content/Itemization/Inventories/WBP_SocketSlot.uasset differ diff --git a/Content/Itemization/ItemTables/Ammo.uasset b/Content/Itemization/ItemTables/Ammo.uasset index 018eccc..ecf2d51 100644 Binary files a/Content/Itemization/ItemTables/Ammo.uasset and b/Content/Itemization/ItemTables/Ammo.uasset differ diff --git a/Content/Itemization/ItemTables/Armor.uasset b/Content/Itemization/ItemTables/Armor.uasset index 504780c..79ef484 100644 Binary files a/Content/Itemization/ItemTables/Armor.uasset and b/Content/Itemization/ItemTables/Armor.uasset differ diff --git a/Content/Itemization/ItemTables/Gold.uasset b/Content/Itemization/ItemTables/Gold.uasset index b963809..359fb10 100644 Binary files a/Content/Itemization/ItemTables/Gold.uasset and b/Content/Itemization/ItemTables/Gold.uasset differ diff --git a/Content/Itemization/ItemTables/Misc.uasset b/Content/Itemization/ItemTables/Misc.uasset index 886621a..f31be33 100644 Binary files a/Content/Itemization/ItemTables/Misc.uasset and b/Content/Itemization/ItemTables/Misc.uasset differ diff --git a/Content/Itemization/ItemTables/Potions.uasset b/Content/Itemization/ItemTables/Potions.uasset index 722b607..4964aad 100644 Binary files a/Content/Itemization/ItemTables/Potions.uasset and b/Content/Itemization/ItemTables/Potions.uasset differ diff --git a/Content/Itemization/ItemTables/SocketSettings/BP_SocketSettings.uasset b/Content/Itemization/ItemTables/SocketSettings/BP_SocketSettings.uasset new file mode 100644 index 0000000..c488b77 Binary files /dev/null and b/Content/Itemization/ItemTables/SocketSettings/BP_SocketSettings.uasset differ diff --git a/Content/Itemization/ItemTables/Weapons.uasset b/Content/Itemization/ItemTables/Weapons.uasset index 898ac74..03a7787 100644 Binary files a/Content/Itemization/ItemTables/Weapons.uasset and b/Content/Itemization/ItemTables/Weapons.uasset differ diff --git a/Content/Itemization/WBP_ItemDropDetails.uasset b/Content/Itemization/WBP_ItemDropDetails.uasset index 7497f20..f4fd83a 100644 Binary files a/Content/Itemization/WBP_ItemDropDetails.uasset and b/Content/Itemization/WBP_ItemDropDetails.uasset differ diff --git a/Content/Itemization/WBP_ItemDropTag.uasset b/Content/Itemization/WBP_ItemDropTag.uasset index 4d64f96..9e5eb14 100644 Binary files a/Content/Itemization/WBP_ItemDropTag.uasset and b/Content/Itemization/WBP_ItemDropTag.uasset differ diff --git a/ItemSystem.png b/ItemSystem.png index de47df9..33515b8 100644 Binary files a/ItemSystem.png and b/ItemSystem.png differ diff --git a/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.dll b/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.dll index 427fd67..581fda0 100644 Binary files a/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.dll and b/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.dll differ diff --git a/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.pdb b/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.pdb index b124ae7..a5581c9 100644 Binary files a/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.pdb and b/Plugins/GenericItemization/Binaries/Win64/UnrealEditor-GenericItemization.pdb differ diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstanceTypes.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstanceTypes.cpp index df665aa..c2bc9d9 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstanceTypes.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstanceTypes.cpp @@ -37,6 +37,17 @@ void FAffixInstance::SetAffixDefinition(const FDataTableRowHandle& Handle) /* Items /************************************************************************/ +FItemSocketInstance::FItemSocketInstance() +{ + SocketId = FGuid::NewGuid(); + bIsEmpty = false; +} + +const FConstStructView FItemSocketInstance::GetSocketedItem() const +{ + return FConstStructView(SocketedItemInstance); +} + FItemInstance::FItemInstance() { ItemId = FGuid::NewGuid(); @@ -110,6 +121,8 @@ bool FItemInstance::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOu SafeNetSerializeTArray_WithNetSerialize<31>(Ar, ReplicatedAffixes, Map); // @NOTE: This means we have a practical maximum of 32 Affixes per ItemInstance. Should we expose this somehow? if (Ar.IsLoading()) { + // We need to empty the actual array as we are effectively rebuilding it. + Affixes.Empty(ReplicatedAffixes.Num()); for (const FInstancedStruct& ReplicatedAffix : ReplicatedAffixes) { TInstancedStruct Affix; @@ -118,6 +131,31 @@ bool FItemInstance::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOu } } + // Ar << Sockets; + TArray ReplicatedSockets; + if (Ar.IsSaving()) + { + for (const TInstancedStruct& Socket : Sockets) + { + FInstancedStruct ReplicatedSocket; + ReplicatedSocket.InitializeAs(Socket.GetScriptStruct(), Socket.GetMemory()); + ReplicatedSockets.Add(ReplicatedSocket); + } + } + SafeNetSerializeTArray_WithNetSerialize<31>(Ar, ReplicatedSockets, Map); // @NOTE: This means we have a practical maximum of 32 Sockets per ItemInstance. Should we expose this somehow? + if (Ar.IsLoading()) + { + // We need to empty the actual array as we are effectively rebuilding it. + Sockets.Empty(ReplicatedSockets.Num()); + for (const FInstancedStruct& ReplicatedSocket : ReplicatedSockets) + { + TInstancedStruct Socket; + Socket.InitializeAsScriptStruct(ReplicatedSocket.GetScriptStruct(), ReplicatedSocket.GetMemory()); + + AddSocket(Socket); + } + } + bOutSuccess = true; return true; } @@ -135,6 +173,40 @@ void FItemInstance::SetItemDefinition(const FDataTableRowHandle& Handle) } } +void FItemInstance::AddSocket(TInstancedStruct& NewSocket) +{ + // Let the Socket know what its definition is. + FSetSocketInstanceSocketDefinition(ItemDefinition.GetPtr(), NewSocket.GetMutablePtr()); + Sockets.Add(NewSocket); +} + +TOptional FItemInstance::GetSocket(FGuid SocketId) const +{ + TOptional Result; + for (const TInstancedStruct& Socket : Sockets) + { + if (Socket.Get().SocketId == SocketId) + { + Result.Emplace(FConstStructView(Socket.GetScriptStruct(), Socket.GetMemory())); + } + } + + return Result; +} + +bool FItemInstance::HasSocket(FGuid SocketId) const +{ + for (const TInstancedStruct& Socket : Sockets) + { + if (Socket.Get().SocketId == SocketId) + { + return true; + } + } + + return false; +} + void FFastItemInstance::Initialize(FInstancedStruct& InItemInstance, FInstancedStruct& InUserContextData) { ItemInstance = InItemInstance; diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstancingFunctions.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstancingFunctions.cpp index caf92ff..f22bf7a 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstancingFunctions.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationInstancingFunctions.cpp @@ -6,6 +6,7 @@ #include "GenericItemizationInstanceTypes.h" #include "GenericItemizationPickFunctions.h" #include "GameplayTagsManager.h" +#include "ItemManagement/ItemSocketSettings.h" UItemInstancingFunction::UItemInstancingFunction() { @@ -203,6 +204,40 @@ bool UItemInstancingFunction::CalculateStackCount_Implementation(const FInstance return true; } +bool UItemInstancingFunction::DetermineActiveSockets_Implementation(const FInstancedStruct& ItemInstance, const FInstancedStruct& ItemInstancingContext, TArray& OutActiveSocketDefinitions) const +{ + const FItemInstance* ItemInstancePtr = ItemInstance.GetPtr(); + if (!ItemInstancePtr) + { + return false; + } + + const TInstancedStruct& ItemDefinitionInstance = ItemInstancePtr->GetItemDefinition(); + if (!ItemDefinitionInstance.IsValid()) + { + return false; + } + + const FItemDefinition& ItemDefinition = ItemDefinitionInstance.Get(); + if (ItemDefinition.bStacksOverSockets) + { + // If we are using Stacking functionality instead of Socketing functionality, then we can exit early with no default Active Sockets. + OutActiveSocketDefinitions.Empty(); + return true; + } + + const UItemSocketSettings* const ItemSocketSettingsCDO = ItemDefinition.SocketSettings.GetDefaultObject(); + if (!ItemSocketSettingsCDO) + { + // This isn't necessarily a failure case. + OutActiveSocketDefinitions.Empty(); + return true; + } + + // By default we let the ItemSocketSettings decide what to do. + return ItemSocketSettingsCDO->DetermineActiveSockets(ItemInstance, ItemInstancingContext, OutActiveSocketDefinitions); +} + bool UItemInstancingContextFunction::BuildItemInstancingContext_Implementation(const UItemDropperComponent* ItemDropperComponent, const FInstancedStruct& UserContextData, FInstancedStruct& OutItemInstancingContext) { // Override to provide your own context. diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationStatics.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationStatics.cpp index 6db41a9..48d6fb6 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationStatics.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationStatics.cpp @@ -10,6 +10,7 @@ #include "StructView.h" #include "Engine/DataTable.h" #include "Kismet/KismetSystemLibrary.h" +#include "ItemManagement/ItemSocketSettings.h" TOptional> UGenericItemizationStatics::PickDropTableCollectionEntry(const TInstancedStruct& DropTableCollectionEntry, const FInstancedStruct& ItemInstancingContext, bool bIncludeNoPick) { @@ -393,6 +394,38 @@ bool UGenericItemizationStatics::GenerateItemInstanceFromItemDefinition(const FD MutableItemInstance->StackCount = FMath::Max(1, DesiredStackCount); } + // ===================================================================================== + // 6. Assign all of the Sockets to the ItemInstance that it has access to. + // Also activate those that need to be from the InstancingFunction and by extension the SocketSettings. + { + TArray SocketsToActivate; + bool bDeterminedSocketsToActivate = InstancingFunctionCDO->DetermineActiveSockets(NewItemInstance, ItemInstancingContext, SocketsToActivate); + if (bDeterminedSocketsToActivate) + { + // Add all of the Sockets that the ItemSocketSettings is saying the ItemInstance needs to have. + const UItemSocketSettings* const ItemSocketSettingsCDO = ItemInstanceItemDefinition.Get().SocketSettings.GetDefaultObject(); + if (ItemSocketSettingsCDO) + { + const int32 MaximumSocketCount = ItemInstanceItemDefinition.Get().MaximumSocketCount; + int32 SocketCount = MaximumSocketCount >= 0 ? MaximumSocketCount : INT_MAX; + for (const TInstancedStruct& SocketDefintion : ItemSocketSettingsCDO->GetSocketDefinitions(SocketsToActivate)) + { + if(SocketCount > 0) + { + FItemSocketInstance NewSocket; + NewSocket.SocketDefinitionHandle = SocketDefintion.Get().SocketDefinitionHandle; + NewSocket.bIsEmpty = true; // Empty by default. + + TInstancedStruct SocketInstance = TInstancedStruct::Make(NewSocket); + MutableItemInstance->AddSocket(SocketInstance); + } + + SocketCount--; + } + } + } + } + OutItemInstance = NewItemInstance; return true; } @@ -418,3 +451,45 @@ bool UGenericItemizationStatics::GenerateItemInstanceFromTemplate(const FInstanc return true; } + +bool UGenericItemizationStatics::GetItemAffixes(const TInstancedStruct& Item, TArray>& OutAffixes, bool bIncludeSocketedItems /*= true*/) +{ + const FItemInstance* ItemInstance = Item.GetPtr(); + if (!ItemInstance) + { + return false; + } + + OutAffixes.Empty(ItemInstance->Affixes.Num()); + OutAffixes.Append(ItemInstance->Affixes); + + if (bIncludeSocketedItems) + { + for (const TInstancedStruct& Socket : ItemInstance->Sockets) + { + const FItemSocketInstance* SocketInstance = Socket.GetPtr(); + if (SocketInstance && !SocketInstance->bIsEmpty) + { + const FItemInstance* SocketedItemInstance = SocketInstance->GetSocketedItem().GetPtr(); + if (SocketedItemInstance) + { + const TArray>& Affixes = SocketedItemInstance->Affixes; + for (const TInstancedStruct& Affix : Affixes) + { + const FAffixInstance* AffixInstance = Affix.GetPtr(); + if (AffixInstance) + { + const FAffixDefinition* AffixDefinition = AffixInstance->GetAffixDefinition().GetPtr(); + if (AffixDefinition && AffixDefinition->bShouldAggregateInSockets) + { + OutAffixes.Add(Affix); + } + } + } + } + } + } + } + + return OutAffixes.Num() > 0; +} diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTags.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTags.cpp index c64f96b..02e7333 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTags.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTags.cpp @@ -9,7 +9,10 @@ namespace GenericItemizationGameplayTags GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(AffixType, "Itemization.AffixType", "The overarching type of a particular Affix that creates logical groups of Affixes that might do the same/similar thing."); GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(UserData, "Itemization.UserData", "Arbitrary Data that can contain whatever you like."); GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(Mutator, "Itemization.Mutator", "Arbitrary Data that can contain whatever you like that is appended to the ItemInstancingContext."); + GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(SocketType, "Itemization.SocketType", "The overarching type of an ItemSocket."); GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(ItemInstanceChange, "Itemization.ItemInstanceChange", "Describes a change to an ItemInstance."); - GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(StackCountChange, "Itemization.ItemInstanceChange.StackCount", "The StackCount of an ItemInstance was changed."); + GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(ItemInstanceChange_StackCount, "Itemization.ItemInstanceChange.StackCount", "The StackCount of an ItemInstance was changed."); + GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(ItemInstanceChange_SocketChange_Socketed, "Itemization.ItemInstanceChange.SocketChange.Socketed", "An ItemInstance was Socketed into another ItemInstance."); + GENERICITEMIZATION_API UE_DEFINE_GAMEPLAY_TAG_COMMENT(ItemInstanceChange_SocketChange_Unsocketed, "Itemization.ItemInstanceChange.SocketChange.Unsocketed", "An ItemInstance was Unsocketed from another ItemInstance."); } \ No newline at end of file diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTypes.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTypes.cpp index 7b0b582..ef1ce84 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTypes.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/GenericItemizationTypes.cpp @@ -3,6 +3,7 @@ #include "GenericItemizationTypes.h" #include "GenericItemizationPickFunctions.h" #include "GenericItemizationInstancingFunctions.h" +#include "GenericItemizationTags.h" FItemDropTableCollectionRow::FItemDropTableCollectionRow() { @@ -19,6 +20,7 @@ FItemDefinitionCollection::FItemDefinitionCollection() FItemDefinition::FItemDefinition() { InstancingFunction = UItemInstancingFunction::StaticClass(); + SocketableInto.AddTag(GenericItemizationGameplayTags::SocketType); } bool FItemDefinition::IsSameItemDefinition(const FItemDefinition& Other) const diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemInventoryComponent.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemInventoryComponent.cpp index 12ab0cc..0a05dd6 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemInventoryComponent.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemInventoryComponent.cpp @@ -216,7 +216,7 @@ bool UItemInventoryComponent::SplitItemStack(FGuid ItemToSplit, int32 SplitCount // Then modify the original Item that we split from so that it has the remaining StackCount completing the split. ItemInstances.ModifyItemInstanceWithChangeDescriptor( ItemToSplit, - GenericItemizationGameplayTags::StackCountChange, + GenericItemizationGameplayTags::ItemInstanceChange_StackCount, { GET_MEMBER_NAME_CHECKED(FItemInstance, StackCount) }, [Remainder](FItemInstance* MutableItemInstance) { @@ -325,7 +325,7 @@ bool UItemInventoryComponent::StackItemFromInventory(UItemInventoryComponent* It { ItemInstances.ModifyItemInstanceWithChangeDescriptor( ItemToStackFrom, - GenericItemizationGameplayTags::StackCountChange, + GenericItemizationGameplayTags::ItemInstanceChange_StackCount, { GET_MEMBER_NAME_CHECKED(FItemInstance, StackCount) }, [StackRemainder](FItemInstance* MutableItemInstance) { @@ -344,7 +344,7 @@ bool UItemInventoryComponent::StackItemFromInventory(UItemInventoryComponent* It ItemInstances.ModifyItemInstanceWithChangeDescriptor( ItemToStackWith, - GenericItemizationGameplayTags::StackCountChange, + GenericItemizationGameplayTags::ItemInstanceChange_StackCount, { GET_MEMBER_NAME_CHECKED(FItemInstance, StackCount) }, [ItemToStackFromStackCount, StackRemainder](FItemInstance* MutableItemInstance) { @@ -438,7 +438,7 @@ bool UItemInventoryComponent::StackItemFromItemDrop(AItemDrop* ItemToStackFromIt ItemInstances.ModifyItemInstanceWithChangeDescriptor( ItemToStackWith, - GenericItemizationGameplayTags::StackCountChange, + GenericItemizationGameplayTags::ItemInstanceChange_StackCount, { GET_MEMBER_NAME_CHECKED(FItemInstance, StackCount) }, [ItemToStackFromStackCount, StackRemainder](FItemInstance* MutableItemInstance) { @@ -450,6 +450,183 @@ bool UItemInventoryComponent::StackItemFromItemDrop(AItemDrop* ItemToStackFromIt return true; } +bool UItemInventoryComponent::CanSocketItem_Implementation(const FInstancedStruct& ItemToSocket, FGuid ItemToSocketInto, FGuid& OutSocketId) +{ + const FFastItemInstance* FastItemToSocketIntoPtr = ItemInstances.GetItemInstance(ItemToSocketInto); + if (!FastItemToSocketIntoPtr) + { + return false; + } + + const FItemInstance* ItemToSocketIntoPtr = FastItemToSocketIntoPtr->ItemInstance.GetPtr(); + if (!ItemToSocketIntoPtr) + { + return false; + } + + for (const TInstancedStruct& SocketInstance : ItemToSocketIntoPtr->Sockets) + { + if (SocketInstance.GetPtr() && CanSocketItemIntoSocket(ItemToSocket, ItemToSocketInto, SocketInstance.GetPtr()->SocketId)) + { + OutSocketId = SocketInstance.GetPtr()->SocketId; + return true; + } + } + + return false; +} + +bool UItemInventoryComponent::CanSocketItemIntoSocket_Implementation(const FInstancedStruct& ItemToSocket, FGuid ItemToSocketInto, FGuid SocketId) +{ + const FItemInstance* ItemToSocketInstancePtr = ItemToSocket.GetPtr(); + if (!ItemToSocketInstancePtr) + { + return false; + } + + const FFastItemInstance* FastItemToSocketIntoPtr = ItemInstances.GetItemInstance(ItemToSocketInto); + if (!FastItemToSocketIntoPtr) + { + return false; + } + + const FItemInstance* ItemToSocketIntoPtr = FastItemToSocketIntoPtr->ItemInstance.GetPtr(); + if (!ItemToSocketIntoPtr) + { + return false; + } + + // Check for any empty sockets. We cant socket at all if they are full already. + bool bHasEmptySocket = false; + for (const TInstancedStruct& SocketInstance : ItemToSocketIntoPtr->Sockets) + { + if (SocketInstance.GetPtr() && SocketInstance.GetPtr()->SocketId == SocketId) + { + bHasEmptySocket = SocketInstance.GetPtr()->bIsEmpty; + break; + } + } + + if (!bHasEmptySocket) + { + return false; + } + + const FItemDefinition* const ItemToSocketIntoDefinitionPtr = ItemToSocketIntoPtr->GetItemDefinition().GetPtr(); + if (!ItemToSocketIntoDefinitionPtr) + { + return false; + } + + UItemSocketSettings* const ItemSocketSettingsCDO = ItemToSocketIntoDefinitionPtr->SocketSettings.GetDefaultObject(); + if (!ItemSocketSettingsCDO) + { + return false; + } + + return ItemSocketSettingsCDO->CanSocketInto(ItemToSocket, FastItemToSocketIntoPtr->ItemInstance, SocketId); +} + +bool UItemInventoryComponent::HasAnyEmptyItemSockets(FGuid ItemToSocketInto) +{ + const FItemInstance* ItemToSocketIntoInstance = GetItem(ItemToSocketInto); + if (!ItemToSocketIntoInstance) + { + return false; + } + + for (const TInstancedStruct& SocketInstance : ItemToSocketIntoInstance->Sockets) + { + if (SocketInstance.GetPtr() && SocketInstance.GetPtr()->bIsEmpty) + { + return true; + } + } + + return false; +} + +bool UItemInventoryComponent::SocketItem(FInstancedStruct& ItemToSocket, FGuid ItemToSocketInto, FGuid SocketId) +{ + if (!HasAuthority()) + { + return false; + } + + if (!CanSocketItemIntoSocket(ItemToSocket, ItemToSocketInto, SocketId)) + { + return false; + } + + bool bResult = false; + ItemInstances.ModifyItemInstanceWithChangeDescriptor( + ItemToSocketInto, + GenericItemizationGameplayTags::ItemInstanceChange_SocketChange_Socketed, + { GET_MEMBER_NAME_CHECKED(FItemInstance, Sockets) }, + [&ItemToSocket, SocketId, &bResult](FItemInstance* MutableItemInstance) + { + for (TInstancedStruct& Socket : MutableItemInstance->Sockets) + { + FItemSocketInstance* MutableSocketInstance = Socket.GetMutablePtr(); + if (MutableSocketInstance && MutableSocketInstance->SocketId == SocketId) + { + MutableSocketInstance->SocketedItemInstance = ItemToSocket; + MutableSocketInstance->bIsEmpty = false; + bResult = true; + return; + } + } + } + ); + + return bResult; +} + +bool UItemInventoryComponent::UnsocketItem(FGuid ItemToUnsocketFrom, FGuid SocketId, FInstancedStruct& OutUnsocketedItem) +{ + if (!HasAuthority()) + { + return false; + } + + bool bResult = false; + ItemInstances.ModifyItemInstanceWithChangeDescriptor( + ItemToUnsocketFrom, + GenericItemizationGameplayTags::ItemInstanceChange_SocketChange_Unsocketed, + { GET_MEMBER_NAME_CHECKED(FItemInstance, Sockets) }, + [&OutUnsocketedItem, SocketId, &bResult](FItemInstance* MutableItemInstance) + { + for (TInstancedStruct& Socket : MutableItemInstance->Sockets) + { + FItemSocketInstance* MutableSocketInstance = Socket.GetMutablePtr(); + if (MutableSocketInstance && MutableSocketInstance->SocketId == SocketId) + { + OutUnsocketedItem = MutableSocketInstance->SocketedItemInstance; + MutableSocketInstance->bIsEmpty = true; + bResult = true; + return; + } + } + } + ); + + return bResult; +} + +bool UItemInventoryComponent::GetItemAffixes(const FGuid& ItemId, TArray>& OutAffixes, bool bIncludeSocketedItems /*= true*/) +{ + bool bFoundItem = false; + const FInstancedStruct Item = GetItem(ItemId, bFoundItem); + if (bFoundItem) + { + TInstancedStruct ItemInstance; + ItemInstance.InitializeAsScriptStruct(Item.GetScriptStruct(), Item.GetMemory()); + return UGenericItemizationStatics::GetItemAffixes(ItemInstance, OutAffixes, bIncludeSocketedItems); + } + + return false; +} + TArray UItemInventoryComponent::GetItems() { return ItemInstances.GetItemInstances(); @@ -469,6 +646,17 @@ FInstancedStruct UItemInventoryComponent::GetItem(FGuid ItemId, bool& bSuccessfu return bSuccessful ? ItemInstance->ItemInstance : FInstancedStruct(); } +const FItemInstance* UItemInventoryComponent::GetItem(const FGuid& ItemId) const +{ + const FFastItemInstance* ItemInstance = ItemInstances.GetItemInstance(ItemId); + if (!ItemInstance) + { + return nullptr; + } + + return ItemInstance->ItemInstance.GetPtr(); +} + FInstancedStruct UItemInventoryComponent::GetItemContextData(FGuid ItemId, bool& bSuccessful) { const FFastItemInstance* ItemInstance = ItemInstances.GetItemInstance(ItemId); @@ -512,6 +700,16 @@ void UItemInventoryComponent::K2_OnItemStackCountChanged_Implementation(const FI // Left empty intentionally to be overridden. } +void UItemInventoryComponent::K2_OnItemSocketed_Implementation(const FInstancedStruct& Item, const FInstancedStruct& UserContextData, FGuid SocketId) +{ + // Left empty intentionally to be overridden. +} + +void UItemInventoryComponent::K2_OnItemUnsocketed_Implementation(const FInstancedStruct& Item, const FInstancedStruct& UserContextData, FGuid SocketId) +{ + // Left empty intentionally to be overridden. +} + void UItemInventoryComponent::OnItemInstancePropertyValueChanged(const FFastItemInstance& FastItemInstance, const FGameplayTag& ChangeDescriptor, int32 ChangeId, const FName& PropertyName, const void* OldPropertyValue, const void* NewPropertyValue) { // Left empty intentionally to be overridden. @@ -549,11 +747,85 @@ void UItemInventoryComponent::OnItemInstancePropertyValueChanged_Internal(const OnItemInstancePropertyValueChanged(FastItemInstance, ChangeDescriptor, ChangeId, PropertyName, OldPropertyValue, NewPropertyValue); OnItemPropertyValueChangedDelegate.Broadcast(this, FastItemInstance, ChangeDescriptor, ChangeId, PropertyName, OldPropertyValue, NewPropertyValue); - if (ChangeDescriptor == GenericItemizationGameplayTags::StackCountChange && PropertyName == GET_MEMBER_NAME_CHECKED(FItemInstance, StackCount)) + if (ChangeDescriptor == GenericItemizationGameplayTags::ItemInstanceChange_StackCount && PropertyName == GET_MEMBER_NAME_CHECKED(FItemInstance, StackCount)) { const int32* OldStackCount = static_cast(OldPropertyValue); const int32* NewStackCount = static_cast(NewPropertyValue); OnItemStackCountChangedDelegate.Broadcast(this, FastItemInstance.ItemInstance, FastItemInstance.UserContextData, *OldStackCount, *NewStackCount); K2_OnItemStackCountChanged(FastItemInstance.ItemInstance, FastItemInstance.UserContextData, *OldStackCount, *NewStackCount); } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(FItemInstance, Sockets)) + { + const TArray>* OldSockets = static_cast>*>(OldPropertyValue); + const TArray>* NewSockets = static_cast>*>(NewPropertyValue); + + FGuid ChangedSocketId; + bool bFoundChangedSocketId = false; + for (const TInstancedStruct& OldSocket : *OldSockets) + { + if (bFoundChangedSocketId) + { + break; + } + + for (const TInstancedStruct& NewSocket : *NewSockets) + { + const FItemSocketInstance* const OldSocketInstancePtr = OldSocket.GetPtr(); + const FItemSocketInstance* const NewSocketInstancePtr = NewSocket.GetPtr(); + if (OldSocketInstancePtr && NewSocketInstancePtr && OldSocketInstancePtr->SocketId == NewSocketInstancePtr->SocketId) + { + // Check if the bIsEmpty flag has changed. If it has then we can skip doing a more expensive compare. + bool bEmptyHasChanged = false; + if (OldSocketInstancePtr->bIsEmpty != NewSocketInstancePtr->bIsEmpty) + { + ChangedSocketId = NewSocketInstancePtr->SocketId; + bFoundChangedSocketId = true; + break; + } + + // Since we couldn't determine a change with the bIsEmpty flag we need to check if the ItemInstance itself was changed. + const FItemInstance* const OldSocketItemInstancePtr = OldSocketInstancePtr->SocketedItemInstance.GetPtr(); + const FItemInstance* const NewSocketItemInstancePtr = NewSocketInstancePtr->SocketedItemInstance.GetPtr(); + if (OldSocketInstancePtr && !NewSocketItemInstancePtr) + { + ChangedSocketId = OldSocketInstancePtr->SocketId; + bFoundChangedSocketId = true; + break; + } + + if (!OldSocketInstancePtr && NewSocketItemInstancePtr) + { + ChangedSocketId = NewSocketInstancePtr->SocketId; + bFoundChangedSocketId = true; + break; + } + + if (OldSocketItemInstancePtr && NewSocketItemInstancePtr) + { + if(OldSocketItemInstancePtr->ItemId != NewSocketItemInstancePtr->ItemId) + { + ChangedSocketId = NewSocketInstancePtr->SocketId; + bFoundChangedSocketId = true; + break; + } + } + } + } + } + + if(bFoundChangedSocketId) + { + if (ChangeDescriptor == GenericItemizationGameplayTags::ItemInstanceChange_SocketChange_Socketed) + { + OnItemSocketedDelegate.Broadcast(this, FastItemInstance.ItemInstance, FastItemInstance.UserContextData, ChangedSocketId); + K2_OnItemSocketed(FastItemInstance.ItemInstance, FastItemInstance.UserContextData, ChangedSocketId); + } + else if (ChangeDescriptor == GenericItemizationGameplayTags::ItemInstanceChange_SocketChange_Unsocketed) + { + OnItemUnsocketedDelegate.Broadcast(this, FastItemInstance.ItemInstance, FastItemInstance.UserContextData, ChangedSocketId); + K2_OnItemUnsocketed(FastItemInstance.ItemInstance, FastItemInstance.UserContextData, ChangedSocketId); + } + } + } } diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemSocketSettings.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemSocketSettings.cpp new file mode 100644 index 0000000..44b8f7a --- /dev/null +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemSocketSettings.cpp @@ -0,0 +1,107 @@ +// Copyright Fissure Entertainment, Pty Ltd. All Rights Reserved. + +#include "ItemManagement/ItemSocketSettings.h" +#include "GenericItemizationInstanceTypes.h" + +UItemSocketSettings::UItemSocketSettings() +{ + +} + +bool UItemSocketSettings::CanSocketInto_Implementation(const FInstancedStruct& ItemToSocket, const FInstancedStruct& ItemToSocketInto, const FGuid& SocketId) +{ + const FItemInstance* const ItemToSocketPtr = ItemToSocket.GetPtr(); + const FItemInstance* const ItemToSocketIntoPtr = ItemToSocketInto.GetPtr(); + if (!ItemToSocketPtr || !ItemToSocketIntoPtr) + { + return false; + } + + // Make sure we have the Socket being requested. + TOptional SocketInstanceViewResult = ItemToSocketIntoPtr->GetSocket(SocketId); + if (!SocketInstanceViewResult.IsSet()) + { + return false; + } + + const FConstStructView& SocketInstanceView = *SocketInstanceViewResult; + const FItemSocketInstance* const SocketInstancePtr = SocketInstanceView.GetPtr(); + if (!SocketInstancePtr || !SocketInstancePtr->bIsEmpty) + { + return false; + } + + // We cannot Socket an ItemInstance into itself. + if (ItemToSocketPtr->ItemId == ItemToSocketIntoPtr->ItemId) + { + return false; + } + + const TInstancedStruct& ItemToSocketDefinitionInstance = ItemToSocketPtr->GetItemDefinition(); + const TInstancedStruct& ItemToSocketIntoDefinitionInstance = ItemToSocketIntoPtr->GetItemDefinition(); + const TInstancedStruct& SocketDefinitionInstance = SocketInstancePtr->GetSocketDefinition(); + if (!ItemToSocketDefinitionInstance.IsValid() || !ItemToSocketIntoDefinitionInstance.IsValid()) + { + return false; + } + + const FItemDefinition& ItemToSocketDefinition = ItemToSocketDefinitionInstance.Get(); + const FItemDefinition& ItemToSocketIntoDefinition = ItemToSocketIntoDefinitionInstance.Get(); + const FItemSocketDefinition& SocketDefinition = SocketDefinitionInstance.Get(); + + // We cannot Socket anything if we are not socketable. + // We cannot Socket a socketable Item. + if (ItemToSocketIntoDefinition.bStacksOverSockets && !ItemToSocketDefinition.bStacksOverSockets) + { + return false; + } + + // Check our requirements are met. + if (!ItemToSocketDefinition.SocketableInto.IsEmpty() && !ItemToSocketDefinition.SocketableInto.HasTag(SocketDefinition.SocketType)) + { + return false; + } + + if (!SocketDefinition.AcceptsItemTypes.IsEmpty() && !SocketDefinition.AcceptsItemTypes.HasTag(ItemToSocketDefinition.ItemType)) + { + return false; + } + + if (!SocketDefinition.AcceptsQualityTypes.IsEmpty() && !SocketDefinition.AcceptsQualityTypes.HasTag(ItemToSocketPtr->QualityType)) + { + return false; + } + + return true; +} + +TArray> UItemSocketSettings::GetSocketDefinitions(TArray SocketDefinitionIndexes) const +{ + TArray> OutSocketDefinitions; + for (const int32& Index : SocketDefinitionIndexes) + { + if (SocketDefinitions.IsValidIndex(Index)) + { + OutSocketDefinitions.Add(SocketDefinitions[Index]); + } + } + + return OutSocketDefinitions; +} + +TArray UItemSocketSettings::GetSocketDefinitions() const +{ + TArray Result; + for (const TInstancedStruct& SocketDefinition : SocketDefinitions) + { + Result.Add(FConstStructView(SocketDefinition.GetScriptStruct(), SocketDefinition.GetMemory())); + } + + return Result; +} + +bool UItemSocketSettings::DetermineActiveSockets_Implementation(const FInstancedStruct& ItemInstance, const FInstancedStruct& ItemInstancingContext, TArray& OutActiveSocketDefinitions) const +{ + OutActiveSocketDefinitions.Empty(); + return true; +} \ No newline at end of file diff --git a/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemStackSettings.cpp b/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemStackSettings.cpp index 27bd27a..4f12a01 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemStackSettings.cpp +++ b/Plugins/GenericItemization/Source/GenericItemization/Private/ItemManagement/ItemStackSettings.cpp @@ -2,7 +2,6 @@ #include "ItemManagement/ItemStackSettings.h" #include "GenericItemizationInstanceTypes.h" -#include "StructView.h" UItemStackSettings::UItemStackSettings() { @@ -11,10 +10,8 @@ UItemStackSettings::UItemStackSettings() bool UItemStackSettings::CanStackWith_Implementation(const FInstancedStruct& ItemToStackFrom, const FInstancedStruct& ItemToStackWith, int32& OutRemainder) const { - const FConstStructView ItemToStackFromView = FConstStructView(ItemToStackFrom); - const FConstStructView ItemToStackWithView = FConstStructView(ItemToStackWith); - const FItemInstance* const ItemToStackFromPtr = ItemToStackFromView.GetPtr(); - const FItemInstance* const ItemToStackWithPtr = ItemToStackWithView.GetPtr(); + const FItemInstance* const ItemToStackFromPtr = ItemToStackFrom.GetPtr(); + const FItemInstance* const ItemToStackWithPtr = ItemToStackWith.GetPtr(); if (!ItemToStackFromPtr || !ItemToStackWithPtr) { return false; diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstanceTypes.h b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstanceTypes.h index 254117b..abd02cf 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstanceTypes.h +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstanceTypes.h @@ -8,6 +8,8 @@ #include "Engine/NetSerialization.h" #include "Net/Serialization/FastArraySerializer.h" #include "GenericItemizationTableTypes.h" +#include "ItemManagement/ItemSocketSettings.h" +#include "StructView.h" #include "GenericItemizationInstanceTypes.generated.h" /************************************************************************/ @@ -85,6 +87,48 @@ struct FItemInstancingContext }; +/** + * Facilitates nesting an ItemInstance inside of another ItemInstance. + */ +USTRUCT(BlueprintType) +struct FItemSocketInstance +{ + GENERATED_BODY() + +public: + + friend struct FSetSocketInstanceSocketDefinition; + friend class UItemInventoryComponent; + + FItemSocketInstance(); + + /* The unique id of this SocketInstance. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FGuid SocketId; + + /* Whether or not this SocketInstance currently has a socketed Item. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool bIsEmpty; + + /* Handle of the actual SocketDefinition, this is serialized instead of the SocketDefinition itself. */ + UPROPERTY() + FGuid SocketDefinitionHandle; + + const TInstancedStruct& GetSocketDefinition() const { return SocketDefinition; } + const FConstStructView GetSocketedItem() const; + +protected: + + /* The static data that describes this Socket. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, NotReplicated, meta = (AllowPrivateAccess)) + TInstancedStruct SocketDefinition; + + /* The ItemInstance that might be socketed into this SocketInstance. */ + UPROPERTY(BlueprintReadOnly, meta = (BaseStruct = "/Script/GenericItemization.ItemInstance", AllowPrivateAccess)) + FInstancedStruct SocketedItemInstance; + +}; + /** * An actual instance of an Item that was generated. */ @@ -133,6 +177,10 @@ struct FItemInstance UPROPERTY(EditAnywhere, BlueprintReadOnly) int32 StackCount; + /* All of the actual Sockets that this Item has that other ItemInstances could be placed into. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (BaseStruct = "/Script/GenericItemization.ItemSocketInstance")) + TArray> Sockets; + bool HasAnyAffixOfType(const FGameplayTag& AffixType) const; bool IsValid() const; @@ -142,6 +190,15 @@ struct FItemInstance const TInstancedStruct& GetItemDefinition() const { return ItemDefinition; } void SetItemDefinition(const FDataTableRowHandle& Handle); + /* Adds a new SocketInstance to this ItemInstance. */ + void AddSocket(TInstancedStruct& NewSocket); + + /* Returns a view into the SocketInstance of the given SocketId if it exists on this ItemInstance. */ + TOptional GetSocket(FGuid SocketId) const; + + /* Returns true if this ItemInstance has a SocketInstance with the given SocketId .*/ + bool HasSocket(FGuid SocketId) const; + protected: /* The static data that describes this Item. */ @@ -163,6 +220,31 @@ struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2bStacksOverSockets && ItemDefinition->SocketSettings && Socket) + { + const UItemSocketSettings* const ItemSocketSettingsCDO = ItemDefinition->SocketSettings.GetDefaultObject(); + if (ItemSocketSettingsCDO) + { + for (const TInstancedStruct& SocketDefinition : ItemSocketSettingsCDO->SocketDefinitions) + { + if (SocketDefinition.Get().SocketDefinitionHandle == Socket->SocketDefinitionHandle) + { + Socket->SocketDefinition = SocketDefinition; + } + } + } + } + } +}; + USTRUCT() struct FItemInstanceChange { diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstancingFunctions.h b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstancingFunctions.h index 68582d2..43a7852 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstancingFunctions.h +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationInstancingFunctions.h @@ -46,6 +46,10 @@ class UItemInstancingFunction : public UObject UFUNCTION(BlueprintNativeEvent) bool CalculateStackCount(const FInstancedStruct& ItemInstance, const FInstancedStruct& ItemInstancingContext, int32& OutStackCount) const; + /* Returns all of the SocketDefinitions on the ItemSocketSettings that will be set to Active on the ItemInstance when its generated. */ + UFUNCTION(BlueprintNativeEvent) + bool DetermineActiveSockets(const FInstancedStruct& ItemInstance, const FInstancedStruct& ItemInstancingContext, TArray& OutActiveSocketDefinitions) const; + /* Returns the Maximum Item Level. */ int32 GetMaximumItemLevel() const { return MaximumItemLevel; } diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationStatics.h b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationStatics.h index 456816a..c993e05 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationStatics.h +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationStatics.h @@ -98,4 +98,15 @@ class GENERICITEMIZATION_API UGenericItemizationStatics : public UBlueprintFunct UFUNCTION(BlueprintCallable, Category = "Generic Itemization") static bool GenerateItemInstanceFromTemplate(const FInstancedStruct& ItemInstanceTemplate, FInstancedStruct& OutItemInstanceCopy); + /** + * Returns all of the AffixInstances for the passed in ItemInstance. Optionally also aggregating all AffixInstances from any Socketed ItemInstances as well. + * + * @param Item The ItemInstance to gather Affixes for. + * @param OutAffixes All of the Affix Instances that we found. + * @param bIncludeSocketedItems Whether or not we will gather Affixes from Items socketed into this one. + * @return True if we could find any Affixes. + */ + UFUNCTION(BlueprintCallable, Category = "Generic Itemization") + static bool GetItemAffixes(const TInstancedStruct& Item, TArray>& OutAffixes, bool bIncludeSocketedItems = true); + }; diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTags.h b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTags.h index b87ea02..bbef8ef 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTags.h +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTags.h @@ -12,7 +12,10 @@ namespace GenericItemizationGameplayTags GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(AffixType); GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UserData); GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Mutator); + GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(SocketType); GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ItemInstanceChange); - GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(StackCountChange); + GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ItemInstanceChange_StackCount); + GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ItemInstanceChange_SocketChange_Socketed); + GENERICITEMIZATION_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(ItemInstanceChange_SocketChange_Unsocketed); } \ No newline at end of file diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTypes.h b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTypes.h index 4f0fd4b..ef32893 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTypes.h +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/GenericItemizationTypes.h @@ -13,6 +13,7 @@ class UItemDefinitionCollectionPickFunction; class UItemDropTableCollectionPickFunction; class UItemInstancingFunction; class UItemStackSettings; +class UItemSocketSettings; /************************************************************************/ /* Items @@ -307,14 +308,30 @@ struct FItemDefinition UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (RequiredAssetDataTags = "RowStructure=/Script/GenericItemization.AffixDefinitionEntry")) TObjectPtr AffixPool; + /* Whether or not we are using StackSettings or SocketSettings, we can't use both. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (InlineEditConditionToggle)) + bool bStacksOverSockets = true; + + /* Describes how ItemInstances of this ItemDefinition can be stacked. Cannot use Sockets if using Stacks. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (EditCondition = "bStacksOverSockets")) + TSubclassOf StackSettings; + + /* Describes the Sockets that are available to ItemInstances of this ItemDefinition. Cannot use Stacks if using Sockets. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (EditCondition = "!bStacksOverSockets")) + TSubclassOf SocketSettings; + + /* The Maximum number of Sockets this ItemDefinition allows ItemInstance of it to have. A value of -1 indicates no maximum. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (UIMin = "-1", ClampMin = "-1", EditCondition = "!bStacksOverSockets")) + int32 MaximumSocketCount = -1; + + /* The types of Sockets that ItemInstances of this ItemDefinition can be socketed into. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (Categories = "Itemization.SocketType", EditCondition = "bStacksOverSockets")) + FGameplayTagContainer SocketableInto; + /* Custom Data that can contain whatever you like. */ UPROPERTY(EditAnywhere, BlueprintReadOnly) TArray CustomUserData; - /* Describes how ItemInstances of this ItemDefinition can be stacked. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TSubclassOf StackSettings; - /* Returns true if Other is the same as this ItemDefinition. */ bool IsSameItemDefinition(const FItemDefinition& Other) const; }; @@ -397,6 +414,10 @@ struct FAffixDefinition UPROPERTY(EditAnywhere, BlueprintReadOnly) bool bSpawnable = true; + /* Will this Affix appear in the aggregated list of Affixes if the ItemInstance it belongs to is inside of a Socket. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly) + bool bShouldAggregateInSockets = true; + /* The Chance that this Affix is chosen in the Item Instancing Process. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (UIMin = "0", ClampMin = "0")) int32 PickChance = 0; diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemInventoryComponent.h b/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemInventoryComponent.h index 5698b2e..d7cafcc 100644 --- a/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemInventoryComponent.h +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemInventoryComponent.h @@ -14,6 +14,8 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FItemInventoryComponentItemTakenS DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FItemInventoryComponentItemChangedSignature, UItemInventoryComponent*, ItemInventoryComponent, const FInstancedStruct&, Item, const FInstancedStruct&, UserContextData); DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FItemInventoryComponentItemRemovedSignature, UItemInventoryComponent*, ItemInventoryComponent, const FInstancedStruct&, Item, const FInstancedStruct&, UserContextData); DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FItemInventoryComponentItemChangedStackCountSignature, UItemInventoryComponent*, ItemInventoryComponent, const FInstancedStruct&, Item, const FInstancedStruct&, UserContextData, int32, OldStackCount, int32, NewStackCount); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FItemInventoryComponentItemChangedSocketChangeSignature, UItemInventoryComponent*, ItemInventoryComponent, const FInstancedStruct&, Item, const FInstancedStruct&, UserContextData, FGuid, SocketId); + DECLARE_MULTICAST_DELEGATE_SevenParams(FItemInventoryComponentItemPropertyValueChangedSignature, UItemInventoryComponent* /*ItemInventoryComponent*/, const FFastItemInstance& /*FastItemInstance*/, const FGameplayTag& /*ChangeDescriptor*/, int32 /*ChangeId*/, const FName& /*PropertyName*/, const void* /*OldPropertyValue*/, const void* /*NewPropertyValue*/); /** @@ -50,6 +52,14 @@ class GENERICITEMIZATION_API UItemInventoryComponent : public UActorComponent UPROPERTY(BlueprintAssignable, meta = (DisplayName = "On Item Stack Count Changed")) FItemInventoryComponentItemChangedStackCountSignature OnItemStackCountChangedDelegate; + /* Called when an ItemInstance in the Inventory had another ItemInstance socketed into it. */ + UPROPERTY(BlueprintAssignable, meta = (DisplayName = "On Item Socketed")) + FItemInventoryComponentItemChangedSocketChangeSignature OnItemSocketedDelegate; + + /* Called when an ItemInstance in the Inventory had an ItemInstance unsocketed from it. */ + UPROPERTY(BlueprintAssignable, meta = (DisplayName = "On Item Unsocketed")) + FItemInventoryComponentItemChangedSocketChangeSignature OnItemUnsocketedDelegate; + /* Called when an ItemInstance in the Inventory had a property value changed. */ FItemInventoryComponentItemPropertyValueChangedSignature OnItemPropertyValueChangedDelegate; @@ -161,6 +171,65 @@ class GENERICITEMIZATION_API UItemInventoryComponent : public UActorComponent UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Generic Itemization") virtual bool StackItemFromItemDrop(AItemDrop* ItemToStackFromItemDrop, FGuid ItemToStackWith, bool& bOutItemToStackFromWasExpunged, bool bDestroyItemDrop = true); + /** + * Checks if ItemToSocket can be socketed into any Sockets on ItemToSocketInto. Returns the first Socket that will accept ItemToSocket. + * + * @param ItemToSocket The ItemInstance that we are trying to socket into ItemToSocketInto. + * @param ItemToSocketInto The ItemInstance that we are trying to socket ItemToSocket into. + * @param OutSocketId The SocketId of the first Socket that will accept the ItemToSocket. + * @return True if we can socket the Item. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Generic Itemization") + bool CanSocketItem(const FInstancedStruct& ItemToSocket, FGuid ItemToSocketInto, FGuid& OutSocketId); + + /** + * Checks if the SocketId on the ItemToSocketInto will accept the ItemToSocket. + * + * @param ItemToSocket The ItemInstance that we are trying to socket into ItemToSocketInto. + * @param ItemToSocketInto The ItemInstance that we are trying to socket ItemToSocket into. + * @param SocketId The SocketId of the Socket that we are trying to socket into. + * @return True if we can socket the Item. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Generic Itemization") + bool CanSocketItemIntoSocket(const FInstancedStruct& ItemToSocket, FGuid ItemToSocketInto, FGuid SocketId); + + /* Returns true if any sockets on the ItemToSocketInto are empty. */ + UFUNCTION(BlueprintCallable, Category = "Generic Itemization") + bool HasAnyEmptyItemSockets(FGuid ItemToSocketInto); + + /** + * Sockets the ItemToSocket into the ItemToSocketInto's socket at SocketId. + * + * @param ItemToSocket The ItemInstance that we are trying to socket into ItemToSocketInto. + * @param ItemToSocketInto The ItemInstance that we are trying to socket ItemToSocket into. + * @param SocketId The SocketId of the Socket that we are trying to socket into. + * @return True if the socketing operation was successful. + */ + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Generic Itemization") + virtual bool SocketItem(UPARAM(Ref) FInstancedStruct& ItemToSocket, FGuid ItemToSocketInto, FGuid SocketId); + + /** + * Unsockets an Item from the SocketId on the ItemToUnsocketFrom. + * + * @param ItemToUnsocketFrom The ItemInstance that we are trying to unsocket a socketed ItemInstance from. + * @param SocketId The SocketId of the Socket that we are trying to unsocket from. + * @param OutUnsocketedItem The ItemInstance that was removed from the socket if successful. + * @return True if the unsocketing operation was successful. + */ + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Generic Itemization") + virtual bool UnsocketItem(FGuid ItemToUnsocketFrom, FGuid SocketId, FInstancedStruct& OutUnsocketedItem); + + /** + * Returns all of the AffixInstances for the passed in ItemInstance. Optionally also aggregating all AffixInstances from any Socketed ItemInstances as well. + * + * @param Item The ItemInstance to gather Affixes for. + * @param OutAffixes All of the Affix Instances that we found. + * @param bIncludeSocketedItems Whether or not we will gather Affixes from Items socketed into this one. + * @return True if we could find any Affixes. + */ + UFUNCTION(BlueprintCallable, Category = "Generic Itemization") + bool GetItemAffixes(const FGuid& Item, TArray>& OutAffixes, bool bIncludeSocketedItems = true); + /* Returns a copy of all of the Items this Inventory currently contains. */ UFUNCTION(BlueprintCallable, Category = "Generic Itemization") TArray GetItems(); @@ -172,6 +241,7 @@ class GENERICITEMIZATION_API UItemInventoryComponent : public UActorComponent /* Gets a copy of the ItemInstance with the given ItemId. */ UFUNCTION(BlueprintCallable, Category = "Generic Itemization") FInstancedStruct GetItem(FGuid ItemId, bool& bSuccessful); + const FItemInstance* GetItem(const FGuid& ItemId) const; /* Gets a copy of the ItemInstances UserContextData with the given ItemId. */ UFUNCTION(BlueprintCallable, Category = "Generic Itemization") @@ -217,6 +287,16 @@ class GENERICITEMIZATION_API UItemInventoryComponent : public UActorComponent UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "On Item Stack Count Changed")) void K2_OnItemStackCountChanged(const FInstancedStruct& Item, const FInstancedStruct& UserContextData, int32 OldStackCount, int32 NewStackCount); + /* Called when an ItemInstance in the Inventory had another ItemInstance socketed into it. */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "On Item Socketed")) + void K2_OnItemSocketed(const FInstancedStruct& Item, const FInstancedStruct& UserContextData, FGuid SocketId); + + /* Called when an ItemInstance in the Inventory had an ItemInstance unsocketed from it. */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "On Item Socketed")) + void K2_OnItemUnsocketed(const FInstancedStruct& Item, const FInstancedStruct& UserContextData, FGuid SocketId); + +private: + /** * Called when an individual property on an Item has been changed. * @@ -229,8 +309,6 @@ class GENERICITEMIZATION_API UItemInventoryComponent : public UActorComponent */ virtual void OnItemInstancePropertyValueChanged(const FFastItemInstance& FastItemInstance, const FGameplayTag& ChangeDescriptor, int32 ChangeId, const FName& PropertyName, const void* OldPropertyValue, const void* NewPropertyValue); -private: - /* Called natively by the FFastItemInstancesContainer to notify the Inventory of an Item being Added. */ void OnAddedItemInstance(const FFastItemInstance& FastItemInstance); diff --git a/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemSocketSettings.h b/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemSocketSettings.h new file mode 100644 index 0000000..0a43a12 --- /dev/null +++ b/Plugins/GenericItemization/Source/GenericItemization/Public/ItemManagement/ItemSocketSettings.h @@ -0,0 +1,85 @@ +// Copyright Fissure Entertainment, Pty Ltd. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "InstancedStruct.h" +#include "GameplayTagContainer.h" +#include "StructView.h" +#include "ItemSocketSettings.generated.h" + +/** + * Defines a Socket that ItemInstances can be placed into. + */ +USTRUCT(BlueprintType) +struct FItemSocketDefinition +{ + GENERATED_BODY() + +public: + + /* The type of Socket this is. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (Categories = "Itemization.SocketType")) + FGameplayTag SocketType; + + /* The ItemTypes that can be placed into this Socket. Leaving this empty means any ItemType will be accepted. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (Categories = "Itemization.ItemType")) + FGameplayTagContainer AcceptsItemTypes; + + /* The Item QualityTypes that can be placed into this Socket. Leaving this empty means any QualityType will be accepted. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (Categories = "Itemization.QualityType")) + FGameplayTagContainer AcceptsQualityTypes; + + /* Identifies this Socket uniquely. */ + FGuid SocketDefinitionHandle = FGuid::NewGuid(); + +}; + +/** + * Describes what Sockets an ItemInstance will have. + */ +UCLASS(ClassGroup = ("Generic Itemization"), Blueprintable, Abstract) +class UItemSocketSettings : public UObject +{ + GENERATED_BODY() + +public: + + friend class UItemInstancingFunction; + friend struct FSetSocketInstanceSocketDefinition; + + UItemSocketSettings(); + + /** + * Checks if ItemToSocket can be socketed into the SocketInstance on ItemToSocketInto. + * + * Restrictions on Socketing are as follows: + * ItemToSocketInto must be socketable. Obviously. + * ItemToSocket cannot itself be socketable. Socket depth is therefore always 1. + * ItemToSocket cannot be the same ItemInstance as ItemToSocketInto. Logically we cannot socket an ItemInstance into itself. + * + * @param ItemToSocket The ItemInstance that we want to socket into the SocketInstance. + * @param ItemToSocketInto The ItemInstance that owns the SocketInstance. + * @param SocketId The Id of the SocketInstance of the ItemToSocketInto that we want to check if ItemToSocket can be socketed into. + * @return True if we can socket the ItemInstance into the others SocketInstance. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Item Stack Settings") + bool CanSocketInto(const FInstancedStruct& ItemToSocket, const FInstancedStruct& ItemToSocketInto, const FGuid& SocketId); + + /* Returns all of the SocketDefinitions corresponding to the array indexes. */ + TArray> GetSocketDefinitions(TArray SocketDefinitionIndexes) const; + TArray GetSocketDefinitions() const; + +protected: + + /* All of the Sockets that an ItemInstance with these settings can have. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, meta = (BaseStruct = "/Script/GenericItemization.ItemSocketDefinition"), Category = "Item Socket Settings") + TArray> SocketDefinitions; + + /* Returns all of the SocketDefinitions on the ItemSocketSettings that will be set to Active on the ItemInstance when its generated. Default implementation returns nothing. */ + UFUNCTION(BlueprintNativeEvent) + bool DetermineActiveSockets(const FInstancedStruct& ItemInstance, const FInstancedStruct& ItemInstancingContext, TArray& OutActiveSocketDefinitions) const; + +}; + diff --git a/README.md b/README.md index 70fae9a..cd43b7f 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,10 @@ Join us on Discord to talk about the plugin! >>>>5.2.1.6 [User Data](#item-definition-user-data) >>>>5.2.1.7 [Item Instancing Function](#item-definition-instancing-function) >>>>5.2.1.8 [Item Stack Settings](#item-definition-stack-settings) +>>>>5.2.1.9 [Item Socket Settings](#item-definition-socket-settings) >>> >>>5.2.2 [Item Instance](#item-instance) +>>>5.2.3 [Item Sockets](#item-sockets) >> >>5.3 [Affixes](#affixes) >>>5.3.1 [Affix Definition](#affix-definition) @@ -59,6 +61,7 @@ Join us on Discord to talk about the plugin! >>>>5.3.1.4 [Occurs for Item Type, Quality Type and Quality Level](#affix-definition-occurs-for) >>>>5.3.1.5 [Required Item Affix Level](#affix-definition-required-affix-level) >>>>5.3.1.6 [Modifiers](#affix-definition-modifiers) +>>>>5.3.1.7 [Should Aggregate In Sockets](#affix-definition-aggregate-sockets) >>> >>>5.3.2 [Affix Instance](#affix-instance) >> @@ -195,7 +198,7 @@ Once it is integrated as explained above, you are free to move ahead and add Ite The Class Layout of the entire Generic Itemization Plugin is displayed below. It shows the relationships between different classes and struct types. As well as how some of their properties relate to one another. It also shows all of the functions available to each class. -![Item System Layout](https://fissureentertainment.com/devilsd/UnrealEngine/GenericItemization/Documentation/ItemSystem_1_2.png) +![Item System Layout](https://fissureentertainment.com/devilsd/UnrealEngine/GenericItemization/Documentation/ItemSystem_1_3.png) You will want to view it in full as a separate window in order to make out its details. @@ -490,6 +493,17 @@ The Sample Project implements stackable Items in the form of both Potions and Go **[⬆ Back to Top](#table-of-contents)** + +### 5.2.1.9 Item Socket Settings + +Items that can contains Sockets will enable and define the settings which govern the Sockets that they can contain. + +The `ItemSocketSettings` is an Object that describes the `SocketDefinition`s of the maximum number of Sockets that an Item can be assigned during the Item Instancing Process. + +The `SocketDefinition` is a simple structure that describes restrictions on what `ItemType`s and `QualityType`s of Items can be inserted into that particular Socket. The `ItemSocketSettings` can choose to define any number of Sockets and they do not all have to be the same. This way you can have different Sockets accepting different types of Items. + +**[⬆ Back to Top](#table-of-contents)** + ### 5.2.2 Item Instance @@ -505,6 +519,29 @@ The Struct type of an `ItemInstance` can only be overridden from the C++ functio **[⬆ Back to Top](#table-of-contents)** + +### 5.2.3 Item Sockets + +Sockets on Items are a nesting mechanism for allowing an `ItemInstance` to contain another `ItemInstance`. The main driver behind providing a feature like this is that it can be made to cause inheritance of properties and `Affixes` of the nested `ItemInstance` onto the `ItemInstance` it is socketed into. This is a powerful concept from a gameplay perspective as it can provide endless combination of Items and drive replayability. + +`ItemDefinition`s contain 2 main properties that describe significant functionality, they are the `StackSettings` and `SocketSettings`. An `ItemDefinition` can only enable one of these at a time. This enforces restrictions on the types of Items that can contain Sockets. Another major restriction on the socketing system is that `ItemDefinition`s that choose to enable the `SocketSettings` cannot be socketed into another `ItemInstance`. This forces the socketing depth to 1 and reduces complexity. + +![Affixes](https://fissureentertainment.com/devilsd/UnrealEngine/GenericItemization/Documentation/Socketing.JPG) + +Further control over how sockets behave on an Item are expressed by the `SocketableInto` and `MaximumSocketCount` properties on the `ItemDefinition`. + +>**SocketableInto** + +This property gives control to the `ItemDefinition` on what types of sockets it can be inserted into. The `ItemSocketDefinition` of a socket describes its `SocketType` that corresponds directly to this container. + +>**MaximumSocketCount** + +The `ItemSocketSettings` on the `ItemDefinition` describe all of the potential available sockets that an `ItemInstance` can ever have. The `MaximumSocketCount` property provides further control to the `ItemDefinition` for placing restrictions on the maximum amount of sockets generated during the Item Instancing Process. + +Determination of the sockets that are generated onto an `ItemInstance` are exposed by the `DetermineActiveSockets` function, which is available to override on the `UItemInstancingFunction` and `UItemSocketSettings` Objects. The Sample Project overrides the `ItemSocketSettings` version of the function to implement some functionality for socket selection based on the `QualityType` of the `ItemInstance` to help demonstrate this functionality. + +**[⬆ Back to Top](#table-of-contents)** + ### 5.3 Affixes @@ -597,6 +634,19 @@ There are no special restrictions or considerations for their usage and are enti **[⬆ Back to Top](#table-of-contents)** + +### 5.3.1.7 Should Aggregate in Sockets + +Affixes can decide if they should appear in the Affix list of an Item when the `ItemInstance` they belong to is inside of a Socket of another `ItemInstance`. + +Currently this is manifested in the `UGenericItemizationStatics::GetItemAffixes` and `UItemInventoryComponent::GetItemAffixes` functions. + +These functions check for if the Affix has the `bShouldAggregateInSockets` property enabled on the `AffixDefinition`. If it doesn't, then it will not be returned as part of the list, when that Affix comes from a Socketed `ItemInstance`. + +![Affixes](https://fissureentertainment.com/devilsd/UnrealEngine/GenericItemization/Documentation/Socketed.JPG) + +**[⬆ Back to Top](#table-of-contents)** + ### 5.3.2 Affix Instance