Skip to content

Instantly share code, notes, and snippets.

@Ruhrpottpatriot
Created June 27, 2017 20:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ruhrpottpatriot/04a4060ce0633cfc66a828f575d7d347 to your computer and use it in GitHub Desktop.
Save Ruhrpottpatriot/04a4060ce0633cfc66a828f575d7d347 to your computer and use it in GitHub Desktop.
// Fill out your copyright notice in the Description page of Project Settings.
#include "Fantasy.h"
#include "InventoryComponent.h"
#include "UnrealNetwork.h"
// Sets default values for this component's properties
UInventoryComponent::UInventoryComponent()
{
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
// off to improve performance if you don't need them.
PrimaryComponentTick.bCanEverTick = true;
bReplicates = true;
}
void UInventoryComponent::BeginPlay()
{
Super::BeginPlay();
}
bool UInventoryComponent::IsFreeSlot(int32 index)
{
bool isFree = this->Size > index;
if (index < this->Items.Num())
{
isFree = this->Items[index].Amount == 0;
}
return isFree;
}
bool UInventoryComponent::TryAdd_Implementation(const FName id, const int32 amount, const bool newStack, int32& amountRemaining)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
amountRemaining = amount;
return false;
}
if (newStack)
{
int freeSlot = this->GetFreeSlotIndex();
if (freeSlot == INDEX_NONE)
{
UE_LOG(FantasyGeneral, Verbose, TEXT("Inventory full"));
amountRemaining = amount;
return false;
}
FInventoryItem* item = this->FindItemInDataTable(id);
if (!item)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Could not find item in data table"));
return false;
}
if (item->StackSize >= amount)
{
item->Amount = amount;
this->Items.Insert(*item, freeSlot);
amountRemaining = 0;
this->OnInventoryItemsChanged.Broadcast();
return true;
}
item->Amount = item->StackSize;
return this->TryAdd(id, amount - item->StackSize, true, amountRemaining);
}
amountRemaining = amount;
bool hasAdded = false;
for (FInventoryItem item : this->Items)
{
if (item.Id == id)
{
int32 stackFreeCount = item.StackSize - item.Amount;
if (stackFreeCount > 0)
{
if (stackFreeCount >= amount)
{
item.Amount += amountRemaining;
amountRemaining = 0;
this->OnInventoryItemsChanged.Broadcast();
return true;
}
item.Amount = item.StackSize;
amountRemaining -= stackFreeCount;
this->OnInventoryItemsChanged.Broadcast();
hasAdded = true;
}
}
}
if (amountRemaining > 0)
{
hasAdded = this->TryAdd(id, amountRemaining, true, amountRemaining) ? true : hasAdded;
}
return hasAdded;
}
bool UInventoryComponent::TryRemove_Implementation(const FName id, const int32 amount, int32& removedAmount)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
removedAmount = 0;
return false;
}
int32 loopRemovedCount = 0;
for (int32 i = 0; i < this->Size; i++)
{
if (loopRemovedCount >= amount)
{
break;
}
FInventoryItem item = this->Items[i];
if (item.Id != id)
{
continue;
}
if (item.Amount == AMOUNT_INFINITE)
{
if (amount == AMOUNT_INFINITE)
{
this->RemoveAt(i);
removedAmount = AMOUNT_INFINITE;
this->OnInventoryItemsChanged.Broadcast();
return true;
}
removedAmount = 0;
return false;
}
if (item.Amount > amount)
{
this->Items[i].Amount -= amount;
loopRemovedCount += amount;
break;
}
loopRemovedCount += this->Items[i].Amount;
this->RemoveAt(i);
}
removedAmount = loopRemovedCount;
if (loopRemovedCount > 0)
{
this->OnInventoryItemsChanged.Broadcast();
return true;
}
return false;
}
void UInventoryComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
TArray<FInventoryItem> UInventoryComponent::GetInventoryItems_Implementation()
{
return this->Items;
}
FInventoryItem UInventoryComponent::GetItemAt_Implementation(int32 index)
{
if (!this->Items.IsValidIndex(index))
{
UE_LOG(FantasyGeneral, Verbose, TEXT("Target index is not valid"));
return FInventoryItem();
}
return this->Items[index];
}
int32 UInventoryComponent::GetFreeSpace_Implementation()
{
int32 freeSpace = 0;
for (FInventoryItem item : this->Items)
{
if (item.Amount == 0)
{
freeSpace += 1;
}
}
return freeSpace;
}
int32 UInventoryComponent::GetFreeSlotIndex_Implementation()
{
for (int32 i = 0; i <= this->Items.Num(); i++)
{
if (this->IsFreeSlot(i))
{
return i;
}
}
return INDEX_NONE;
}
int32 UInventoryComponent::CountAmount_Implementation(FName id)
{
int32 amount = 0;
for (FInventoryItem item : this->Items)
{
if (item.Id == id)
{
amount += item.Amount;
}
}
return amount;
}
int32 UInventoryComponent::CountStacks_Implementation(FName id)
{
int32 stackCount = 0;
for (FInventoryItem item : this->Items)
{
if (item.Id == id)
{
stackCount += 1;
}
}
return stackCount;
}
void UInventoryComponent::Clear_Implementation()
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
return;
}
this->Items.Empty(this->Size);
}
void UInventoryComponent::Resize(int32 newSize, bool allowShrink)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
return;
}
this->Items.SetNum(newSize, allowShrink);
this->Size = newSize;
this->OnInventoryItemsChanged.Broadcast();
}
void UInventoryComponent::SetAmount(const int32 index, const int32 amount)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
return;
}
int32 tmpAmount = amount;
if (tmpAmount > this->Items[index].StackSize)
{
tmpAmount = this->Items[index].StackSize;
}
this->Items[index].Amount = tmpAmount;
this->OnInventoryItemsChanged.Broadcast();
}
void UInventoryComponent::RemoveAt_Implementation(int32 index)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
return;
}
this->Items.InsertZeroed(index);
this->OnInventoryItemsChanged.Broadcast();
}
void UInventoryComponent::MoveStack(int32 source, int32 target)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
return;
}
if (target > this->Size || target < 0)
{
UE_LOG(FantasyGeneral, Verbose, TEXT("Target index is outside the array"));
return;
}
this->Items.Swap(source, target);
this->OnInventoryItemsChanged.Broadcast();
}
FInventoryItem* UInventoryComponent::FindItemInDataTable(FName rowName)
{
static ConstructorHelpers::FObjectFinder<UDataTable>itemTableConst(TEXT("DataTable'/Game/FantasyGame/Data/DT_ItemList.DT_ItemList'"));
UDataTable* itemTable = itemTableConst.Object;
if (!itemTable)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Could not find item table"));
return nullptr;
}
static const FString contextString(TEXT("GENERAL"));
return itemTable->FindRow<FInventoryItem>(rowName, contextString);
}
void UInventoryComponent::Init_Implementation(int32 initialSize, const TArray<FInventoryItem>& initialItems)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
return;
}
int32 actualSize = initialSize < initialItems.Num() ? initialItems.Num() : initialSize;
this->Size = actualSize;
this->Items.Init(FInventoryItem(), actualSize);
if (initialItems.Num() > 0)
{
for (int32 i = 0; i < initialItems.Num(); i++)
{
this->Items.Insert(initialItems[i], i);
}
}
this->OnInventoryItemsChanged.Broadcast();
}
bool UInventoryComponent::PlaceAtSlot_Implementation(int32 index, FName id, int32 amount, bool overwrite)
{
if (this->GetOwnerRole() != ROLE_Authority)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Remote tried to access authority function"));
return false;
}
if (!(0 <= index && index < this->Size))
{
UE_LOG(FantasyGeneral, Verbose, TEXT("Target index is outside the array"));
return false;
}
FInventoryItem item = this->Items[index];
if (item.Id != "" || item.Id == "None")
{
UE_LOG(FantasyGeneral, Warning, TEXT("Could not find item in database"))
return false;
}
if (!this->IsFreeSlot(index) && !overwrite)
{
UE_LOG(FantasyGeneral, Warning, TEXT("Cannot replace existing item, unless overwrite is true"))
return false;
}
FInventoryItem* dbItem = this->FindItemInDataTable(id);
dbItem->Amount = amount;
this->Items.Insert(*dbItem, index);
this->OnInventoryItemsChanged.Broadcast();
return true;
}
void UInventoryComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UInventoryComponent, Items);
DOREPLIFETIME(UInventoryComponent, Size);
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
#define AMOUNT_INFINITE -1
// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FInventoryItemsChanged);
UENUM(BlueprintType)
enum class EItemType : uint8
{
Miscellaneous,
Equipment,
Consumable,
Currency
};
UENUM(BlueprintType)
enum class EItemQuality : uint8
{
Poor,
Common,
Uncommon,
Rare,
Epic,
Legendary
};
USTRUCT(BlueprintType)
struct FResourceCost
{
GENERATED_BODY();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
int32 Amount;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
FName ResourceId;
};
USTRUCT(BlueprintType)
struct FItemCost
{
GENERATED_BODY();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
int32 Gold;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
TArray<FResourceCost> Resources;
};
USTRUCT(BlueprintType)
struct FInventoryItem : public FTableRowBase
{
GENERATED_BODY();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
FName Id;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
bool IsDroppable;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
UStaticMesh* WorldMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
int32 Amount;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
int32 StackSize;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
EItemType Type;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
EItemQuality Quality;
//ToDo: Remove buy and sell cost from inventory item struct to reduce network load
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
FItemCost BuyCost;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventory")
FItemCost SellCost;
FInventoryItem()
{
}
};
UCLASS(Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent))
class FANTASY_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UInventoryComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/**
* The total size of the inventory, including empty slots.
*/
UPROPERTY(BlueprintReadOnly, Replicated, Category = "Inventory")
int32 Size;
UPROPERTY(BlueprintAssignable, Category = "Inventory")
FInventoryItemsChanged OnInventoryItemsChanged;
/**
* Clears the inventory of all items, while keeping the size.
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "Inventory")
void Clear();
virtual void Clear_Implementation();
/**
* Calculates the how much of an item with the given id is present in the inventory
* @param id - The id to count for
* @returns The items amount
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure, Category = "Inventory")
int32 CountAmount(FName id);
virtual int32 CountAmount_Implementation(FName id);
/**
* Calculates the number of stacks for an item with the given id in the inventory
* @param id - The id to count for
* @returns The number of stacks
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure, Category = "Inventory")
int32 CountStacks(FName id);
virtual int32 CountStacks_Implementation(FName id);
/**
* Retrieves the first free slot
* @returns Index of the first free slot
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure, Category = "Inventory")
int32 GetFreeSlotIndex();
virtual int32 GetFreeSlotIndex_Implementation();
/**
* Calculates the amount of free slots in the inventory
* @returns The number of free slots
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure, Category = "Inventory")
int32 GetFreeSpace();
virtual int32 GetFreeSpace_Implementation();
/**
* Provides the user with a list of all items in the inventory
* @returns The entire inventory
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure, Category = "Inventory")
TArray<FInventoryItem> GetInventoryItems();
virtual TArray<FInventoryItem> GetInventoryItems_Implementation();
/**
* Retrieves an item from an index
* @returns The item at the index
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure, Category = "Inventory")
FInventoryItem GetItemAt(int32 index);
virtual FInventoryItem GetItemAt_Implementation(int32 index);
/**
* Initialises the inventory to a specific size with the given items.
* @param initialSize - The inventory's size
* @param initialItems - The items to place in the inventory
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "Inventory", meta = (AutoCreateRefTerm = "initialItems"))
void Init(int32 initialSize, const TArray<FInventoryItem>& initialItems);
virtual void Init_Implementation(int32 initialSize, const TArray<FInventoryItem>& initialItems);
/**
* Checks if the provided slot is empty
* @returns True, if the slot is empty, otherwise false
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Inventory")
bool IsFreeSlot(int32 index);
/**
* Moves the stack from the source index to target index, optionally swapping items.
* @param source - The first stack index
* @param target - The second stack index
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Inventory")
void MoveStack(int32 source, int32 target);
/**
* Places an item with the designated index and amount at the specified slot,
* optionally overwriting the existing item
* @param index - The index to place the item at
* @param id - The id of the item to place
* @param amount - The items amount to place
* @param overwrite - If an existing item should be overwritten
* @returns True if the placement was sucessful, otherwise false
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "Inventory")
bool PlaceAtSlot(int32 index, FName id, int32 amount, bool overwrite = false);
virtual bool PlaceAtSlot_Implementation(int32 index, FName id, int32 amount, bool overwrite = false);
/**
* Removes the stack at the given index
* @param index - The index to remove
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "Inventory")
void RemoveAt(int32 index);
virtual void RemoveAt_Implementation(int32 index);
/**
* Resizes the inventory by the given amount,
* optionally shrinking the inventory removing all excess items
* @param newSize - The new total size of the inventory
* @param allowShrink - If the inventory is allowed to shrink
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Inventory")
void Resize(int32 newSize, bool allowShrink = false);
/**
* Sets the items amount at the given index, mot exeeding the maximum stack size
* @param index - The stack's index
* @param amount - The stack's new amount
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Inventory")
void SetAmount(int32 index, int32 amount);
/**
* Tries to add an item with the given id to then inventory filling up existing slots first,
* then creating new slots. The anount that couldn't be added is returned, too.
* @param id - The item's id
* @param amount - The amount to add
* @param newStack - forcibly creates a new stack, even if existing ones could be filled
* @param amountRemaining - The amount that couldn't be added to the inventory
* @returns True if at least one item was added, otherwise false
*/
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintNativeEvent, Category = "Inventory")
bool TryAdd(FName id, int32 amount, bool newStack, int32& amountRemaining);
bool TryAdd_Implementation(FName id, int32 amount, bool newStack, int32& amountRemaining);
/**
* Tries to remove an item with the given id from the inventory..
* @param id - The item's id
* @param amount - The amount to remove
* @param removedAmount - The amount of items that were removed
* @returns True if at least one item was removed, otherwise false
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Inventory")
bool TryRemove(FName id, int32 amount, int32& removedAmount);
bool TryRemove_Implementation(FName id, int32 amount, int32& removedAmount);
private:
UPROPERTY(Replicated)
TArray<FInventoryItem> Items;
static FInventoryItem* FindItemInDataTable(FName rowName);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment