第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIS_CurrencyContainer.h"
#include "GIS_CurrencySystemComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyContainer)
void FGIS_CurrencyContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
{
for (int32 Index : RemovedIndices)
{
FGIS_CurrencyEntry& Entry = Entries[Index];
if (OwningComponent)
{
OwningComponent->OnCurrencyEntryRemoved(Entry, Index);
}
Entry.PrevAmount = 0;
}
}
void FGIS_CurrencyContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
{
for (int32 Index : AddedIndices)
{
FGIS_CurrencyEntry& Entry = Entries[Index];
if (OwningComponent)
{
OwningComponent->OnCurrencyEntryAdded(Entry, Index);
}
Entry.PrevAmount = Entry.Amount;
}
}
void FGIS_CurrencyContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
{
for (int32 Index : ChangedIndices)
{
FGIS_CurrencyEntry& Entry = Entries[Index];
if (OwningComponent)
{
OwningComponent->OnCurrencyEntryUpdated(Entry, Index, Entry.PrevAmount, Entry.Amount);
}
Entry.PrevAmount = Entry.Amount;
}
}

View File

@@ -0,0 +1,51 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Exchange/GIS_CurrencyDefinition.h"
#include "GIS_InventorySubsystem.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyDefinition)
FGIS_CurrencyExchangeRate::FGIS_CurrencyExchangeRate(const UGIS_CurrencyDefinition* InCurrency, float InExchangeRate)
{
Currency = InCurrency;
ExchangeRate = InExchangeRate;
}
bool UGIS_CurrencyDefinition::TryGetExchangeRateTo(const UGIS_CurrencyDefinition* OtherCurrency, double& ExchangeRate) const
{
if (OtherCurrency == nullptr)
{
ExchangeRate = -1;
return false;
}
//same currency
if (OtherCurrency == this)
{
ExchangeRate = 1;
return true;
}
const FGIS_CurrencyExchangeRate RootExchangeRate = GetRootExchangeRate();
const FGIS_CurrencyExchangeRate OtherRootExchangeRate = OtherCurrency->GetRootExchangeRate();
if (RootExchangeRate.Currency != OtherRootExchangeRate.Currency)
{
ExchangeRate = -1;
return false;
}
ExchangeRate = RootExchangeRate.ExchangeRate / OtherRootExchangeRate.ExchangeRate;
return true;
}
FGIS_CurrencyExchangeRate UGIS_CurrencyDefinition::GetRootExchangeRate(double AdditionalExchangeRate) const
{
if (ParentCurrency)
{
return ParentCurrency->GetRootExchangeRate(AdditionalExchangeRate * ExchangeRateToParent);
}
return FGIS_CurrencyExchangeRate(this, AdditionalExchangeRate);
}

View File

@@ -0,0 +1,44 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIS_CurrencyEntry.h"
#include "GIS_CurrencyDefinition.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyEntry)
FGIS_CurrencyEntry::FGIS_CurrencyEntry()
{
Definition = nullptr;
Amount = 0;
}
FGIS_CurrencyEntry::FGIS_CurrencyEntry(const TObjectPtr<const UGIS_CurrencyDefinition>& InDefinition, float InAmount)
{
Definition = InDefinition;
Amount = InAmount;
}
FGIS_CurrencyEntry::FGIS_CurrencyEntry(float InAmount, const TObjectPtr<const UGIS_CurrencyDefinition>& InDefinition)
{
Definition = InDefinition;
Amount = InAmount;
}
bool FGIS_CurrencyEntry::Equals(const FGIS_CurrencyEntry& Other) const
{
return Amount == Other.Amount && Definition == Other.Definition;
}
FString FGIS_CurrencyEntry::ToString() const
{
return FString::Format(TEXT("{0} {1}"), {Definition ? Definition->GetName() : TEXT("None"), Amount});
}
bool FGIS_CurrencyEntry::operator==(const FGIS_CurrencyEntry& Rhs) const
{
return Equals(Rhs);
}
bool FGIS_CurrencyEntry::operator!=(const FGIS_CurrencyEntry& Rhs) const
{
return !Equals(Rhs);
}

View File

@@ -0,0 +1,550 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Exchange/GIS_CurrencySystemComponent.h"
#include "Engine/World.h"
#include "GIS_InventorySubsystem.h"
#include "GameFramework/Actor.h"
#include "UObject/Object.h"
#include "GIS_InventoryTags.h"
#include "GIS_LogChannels.h"
#include "Net/UnrealNetwork.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencySystemComponent)
UGIS_CurrencySystemComponent::UGIS_CurrencySystemComponent() : Container(this)
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
bReplicateUsingRegisteredSubObjectList = true;
bWantsInitializeComponent = true;
}
UGIS_CurrencySystemComponent* UGIS_CurrencySystemComponent::GetCurrencySystemComponent(const AActor* Actor)
{
return IsValid(Actor) ? Actor->FindComponentByClass<UGIS_CurrencySystemComponent>() : nullptr;
}
void UGIS_CurrencySystemComponent::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, Container);
}
void UGIS_CurrencySystemComponent::InitializeComponent()
{
Super::InitializeComponent();
if (GetWorld() && !GetWorld()->IsGameWorld())
{
return;
}
Container.OwningComponent = this;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::GetAllCurrencies() const
{
TArray<FGIS_CurrencyEntry> Ret;
for (const FGIS_CurrencyEntry& Item : Container.Entries)
{
Ret.Add(FGIS_CurrencyEntry(Item.Definition, Item.Amount));
}
return Ret;
}
void UGIS_CurrencySystemComponent::SetCurrencies(const TArray<FGIS_CurrencyEntry>& InCurrencyInfos)
{
TArray<FGIS_CurrencyEntry> NewEntries = InCurrencyInfos.FilterByPredicate([](const FGIS_CurrencyEntry& Item)
{
return Item.Definition != nullptr && Item.Amount > 0;
});
Container.Entries.Empty();
CurrencyMap.Empty();
Container.Entries = NewEntries;
for (const FGIS_CurrencyEntry& NewItem : NewEntries)
{
CurrencyMap.Add(NewItem.Definition, NewItem.Amount);
}
Container.MarkArrayDirty();
}
void UGIS_CurrencySystemComponent::EmptyCurrencies()
{
Container.Entries.Empty();
CurrencyMap.Empty();
Container.MarkArrayDirty();
}
bool UGIS_CurrencySystemComponent::GetCurrency(TSoftObjectPtr<const UGIS_CurrencyDefinition> CurrencyDefinition, FGIS_CurrencyEntry& OutCurrencyInfo) const
{
if (CurrencyDefinition.IsNull())
{
return false;
}
const UGIS_CurrencyDefinition* Definition = CurrencyDefinition.LoadSynchronous();
return GetCurrencyInternal(Definition, OutCurrencyInfo);
}
bool UGIS_CurrencySystemComponent::GetCurrencies(TArray<TSoftObjectPtr<UGIS_CurrencyDefinition>> CurrencyDefinitions, TArray<FGIS_CurrencyEntry>& OutCurrencyInfos) const
{
TArray<TObjectPtr<const UGIS_CurrencyDefinition>> Definitions;
for (TSoftObjectPtr<const UGIS_CurrencyDefinition> Currency : CurrencyDefinitions)
{
if (const UGIS_CurrencyDefinition* Definition = !Currency.IsNull() ? Currency.LoadSynchronous() : nullptr)
{
Definitions.AddUnique(Definition);
}
}
return GetCurrenciesInternal(Definitions, OutCurrencyInfos);
}
bool UGIS_CurrencySystemComponent::AddCurrency(FGIS_CurrencyEntry CurrencyInfo)
{
return AddCurrencyInternal(CurrencyInfo);
}
bool UGIS_CurrencySystemComponent::RemoveCurrency(FGIS_CurrencyEntry CurrencyInfo)
{
return RemoveCurrencyInternal(CurrencyInfo);
}
bool UGIS_CurrencySystemComponent::HasCurrency(FGIS_CurrencyEntry CurrencyInfo) const
{
return HasCurrencyInternal(CurrencyInfo);
}
bool UGIS_CurrencySystemComponent::HasCurrencies(const TArray<FGIS_CurrencyEntry>& CurrencyInfos)
{
bool bOk = true;
for (auto& Currency : CurrencyInfos)
{
if (!HasCurrencyInternal(Currency))
{
bOk = false;
break;
}
}
return bOk;
}
bool UGIS_CurrencySystemComponent::AddCurrencies(const TArray<FGIS_CurrencyEntry>& CurrencyInfos)
{
for (const FGIS_CurrencyEntry& Currency : CurrencyInfos)
{
AddCurrencyInternal(Currency);
}
return true;
}
bool UGIS_CurrencySystemComponent::RemoveCurrencies(const TArray<FGIS_CurrencyEntry>& CurrencyInfos)
{
for (const FGIS_CurrencyEntry& Currency : CurrencyInfos)
{
RemoveCurrencyInternal(Currency);
}
return true;
}
void UGIS_CurrencySystemComponent::BeginPlay()
{
Super::BeginPlay();
AddInitialCurrencies();
}
void UGIS_CurrencySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
}
#pragma region Static Calculations (To be continued)
#if 0
bool UGIS_CurrencySystemComponent::AddCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo, bool bNotify)
{
if (!CurrencyInfo.Definition || CurrencyInfo.Amount <= 0)
{
FFrame::KismetExecutionMessage(TEXT("An invalid currency definition was passed."), ELogVerbosity::Warning);
return false;
}
auto discreteCurrencyAmounts = ConvertToDiscrete(CurrencyInfo.Definition, CurrencyInfo.Amount);
auto currencyAmountCopy = Container.Entries;
auto added = DiscreteAddition(currencyAmountCopy, discreteCurrencyAmounts);
SetCurrencyInfos(added);
return true;
}
int32 UGIS_CurrencySystemComponent::FindIndexWithCurrency(const UGIS_CurrencyDefinition* Currency, const TArray<FGIS_CurrencyEntry>& List)
{
for (int32 i = 0; i < List.Num(); i++)
{
if (List[i].Definition == Currency)
{
return i;
}
}
return INDEX_NONE;
}
int32 UGIS_CurrencySystemComponent::FindOrCreateCurrencyIndex(const UGIS_CurrencyDefinition* Currency, TArray<FGIS_CurrencyEntry>& Result)
{
int32 Index = FindIndexWithCurrency(Currency, Result);
if (Index != INDEX_NONE)
{
return Index;
}
Result.Add(FGIS_CurrencyEntry(0.0f, Currency));
return Result.Num() - 1;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::DiscreteAddition(const TArray<FGIS_CurrencyEntry>& Lhs, const TArray<FGIS_CurrencyEntry>& Rhs)
{
TArray<FGIS_CurrencyEntry> Result;
TArray<FGIS_CurrencyEntry> TempArray = Lhs; // 复制 Lhs 作为初始结果
for (int32 i = 0; i < Rhs.Num(); i++)
{
// 交替使用 Result 和 TempArray 避免重复分配
TArray<FGIS_CurrencyEntry>& Target = (i % 2 == 0) ? Result : TempArray;
TempArray = DiscreteAddition(TempArray, Rhs[i].Definition, Rhs[i].Amount);
}
// 确保最终结果存储在 Result 中
if (TempArray != Result)
{
Result = TempArray;
}
return Result;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::DiscreteAddition(const TArray<FGIS_CurrencyEntry>& CurrencyAmounts, const UGIS_CurrencyDefinition* Currency, float Amount)
{
TArray<FGIS_CurrencyEntry> Result = CurrencyAmounts; // 复制输入数组
while (true)
{
int32 Index = FindOrCreateCurrencyIndex(Currency, Result);
float Sum = Amount + Result[Index].Amount;
float Mod = FMath::Fmod(Sum, Currency->MaxAmount + 1.0f);
Result[Index] = FGIS_CurrencyEntry(Mod, Currency);
float Overflow = Sum - Mod;
if (Overflow <= 0.0f)
{
break;
}
const UGIS_CurrencyDefinition* OverflowCurrency = Currency->OverflowCurrency;
double Rate;
if (!OverflowCurrency || !Currency->TryGetExchangeRateTo(OverflowCurrency, Rate))
{
return SetAllFractionToMax(CurrencyAmounts, Currency);
}
double OverflowDouble = Overflow * Rate; // 使用 Rate 而非 Amount
int32 OverflowInt = FMath::TruncToInt(OverflowDouble);
double Diff = OverflowDouble - OverflowInt;
if (Diff > 0.0)
{
TArray<FGIS_CurrencyEntry> DiscreteDiff = ConvertToDiscrete(OverflowCurrency, Diff);
Result = DiscreteAddition(Result, DiscreteDiff);
}
Currency = OverflowCurrency;
Amount = static_cast<float>(OverflowInt);
}
return Result;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::SetAllFractionToMax(const TArray<FGIS_CurrencyEntry>& CurrencyAmounts, const UGIS_CurrencyDefinition* Currency)
{
TArray<FGIS_CurrencyEntry> Result = CurrencyAmounts;
while (Currency != nullptr)
{
int32 Index = FindOrCreateCurrencyIndex(Currency, Result);
Result[Index] = FGIS_CurrencyEntry(Currency->MaxAmount, Currency);
Currency = Currency->FractionCurrency;
}
return Result;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::MaxedOutAmount(const UGIS_CurrencyDefinition* Currency)
{
TArray<FGIS_CurrencyEntry> Result;
int32 Index = 0;
while (Currency != nullptr)
{
Result.SetNum(Index + 1);
Result[Index] = FGIS_CurrencyEntry(Currency->MaxAmount, Currency);
Index++;
Currency = Currency->FractionCurrency;
}
return Result;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::ConvertToDiscrete(const UGIS_CurrencyDefinition* Currency, double Amount)
{
TArray<FGIS_CurrencyEntry> Result;
int32 Index = 0;
TArray<FGIS_CurrencyEntry> OverflowCurrencies = ConvertOverflow(Currency, Amount);
bool bMaxed = false;
for (int32 i = OverflowCurrencies.Num() - 1; i >= 0; i--)
{
Result.SetNum(Index + 1);
if (Currency->FractionCurrency == OverflowCurrencies[i].Definition)
{
bMaxed = true;
}
Result[Index] = OverflowCurrencies[i];
Index++;
}
if (!bMaxed)
{
TArray<FGIS_CurrencyEntry> FractionCurrencies = ConvertFraction(Currency, Amount);
for (int32 i = 0; i < FractionCurrencies.Num(); i++)
{
Result.SetNum(Index + 1);
Result[Index] = FractionCurrencies[i];
Index++;
}
}
return Result;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::ConvertOverflow(const UGIS_CurrencyDefinition* Currency, double Amount)
{
TArray<FGIS_CurrencyEntry> Result;
int32 Index = 0;
const UGIS_CurrencyDefinition* NextCurrency = Currency;
Amount = FMath::TruncToDouble(Amount);
while (NextCurrency != nullptr && Amount > 0.0)
{
double Mod = fmod(Amount, NextCurrency->MaxAmount + 1.0);
float IntMod = static_cast<float>(Mod);
if (IntMod > 0.0f)
{
Result.SetNum(Index + 1);
Result[Index] = FGIS_CurrencyEntry(IntMod, NextCurrency);
Index++;
}
if (Amount - IntMod <= 0.0)
{
break;
}
double Rate;
if (!NextCurrency->OverflowCurrency || !NextCurrency->TryGetExchangeRateTo(NextCurrency->OverflowCurrency, Rate))
{
return MaxedOutAmount(NextCurrency);
}
Amount -= Mod;
Amount *= Rate;
NextCurrency = NextCurrency->OverflowCurrency;
}
return Result;
}
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::ConvertFraction(const UGIS_CurrencyDefinition* Currency, double Amount)
{
TArray<FGIS_CurrencyEntry> Result;
if (FMath::IsNearlyZero(fmod(Amount, 1.0)))
{
return Result;
}
int32 Index = 0;
const UGIS_CurrencyDefinition* NextCurrency = Currency;
double DecimalAmount = fmod(Amount, 1.0);
while (NextCurrency != nullptr && DecimalAmount > 0.0)
{
if (!NextCurrency->FractionCurrency)
{
break;
}
double Rate;
if (!NextCurrency->TryGetExchangeRateTo(NextCurrency->FractionCurrency, Rate))
{
break;
}
NextCurrency = NextCurrency->FractionCurrency;
DecimalAmount *= Rate;
int32 Floor = static_cast<int32>(FMath::Floor(DecimalAmount));
if (Floor > 0)
{
Result.SetNum(Index + 1);
Result[Index] = FGIS_CurrencyEntry(static_cast<float>(Floor), NextCurrency);
Index++;
}
DecimalAmount -= Floor;
}
return Result;
}
#endif
#pragma endregion
void UGIS_CurrencySystemComponent::OnCurrencyEntryAdded(const FGIS_CurrencyEntry& Entry, int32 Idx)
{
CurrencyMap.Add(Entry.Definition, Entry.Amount);
OnCurrencyChangedEvent.Broadcast(Entry.Definition, 0, Entry.Amount);
}
void UGIS_CurrencySystemComponent::OnCurrencyEntryRemoved(const FGIS_CurrencyEntry& Entry, int32 Idx)
{
CurrencyMap.Remove(Entry.Definition);
OnCurrencyChangedEvent.Broadcast(Entry.Definition, Entry.PrevAmount, 0);
}
void UGIS_CurrencySystemComponent::OnCurrencyEntryUpdated(const FGIS_CurrencyEntry& Entry, int32 Idx, float OldAmount, float NewAmount)
{
CurrencyMap.Emplace(Entry.Definition, NewAmount);
OnCurrencyChangedEvent.Broadcast(Entry.Definition, OldAmount, NewAmount);
}
void UGIS_CurrencySystemComponent::OnCurrencyChanged(TObjectPtr<const UGIS_CurrencyDefinition> Currency, float OldValue, float NewValue)
{
OnCurrencyChangedEvent.Broadcast(Currency, OldValue, NewValue);
}
void UGIS_CurrencySystemComponent::AddInitialCurrencies_Implementation()
{
if (GetOwner()->HasAuthority())
{
//TODO check records from save game.
if (!DefaultCurrencies.IsEmpty())
{
AddCurrencies(DefaultCurrencies);
}
}
}
bool UGIS_CurrencySystemComponent::GetCurrencyInternal(const TObjectPtr<const UGIS_CurrencyDefinition>& Currency, FGIS_CurrencyEntry& OutCurrencyInfo) const
{
if (CurrencyMap.Contains(Currency))
{
OutCurrencyInfo = FGIS_CurrencyEntry(Currency, CurrencyMap[Currency]);
return true;
}
return false;
}
bool UGIS_CurrencySystemComponent::GetCurrenciesInternal(const TArray<TObjectPtr<const UGIS_CurrencyDefinition>>& Currencies, TArray<FGIS_CurrencyEntry>& OutCurrencies) const
{
TArray<FGIS_CurrencyEntry> Result;
for (int32 i = 0; i < Currencies.Num(); i++)
{
FGIS_CurrencyEntry Info;
if (GetCurrencyInternal(Currencies[i], Info))
{
Result.Add(Info);
}
}
OutCurrencies = Result;
return !OutCurrencies.IsEmpty();
}
bool UGIS_CurrencySystemComponent::AddCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo, bool bNotify)
{
if (!CurrencyInfo.Definition || CurrencyInfo.Amount <= 0)
{
GIS_CLOG(Warning, "An invalid currency definition was passed.")
// FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed."), ELogVerbosity::Warning);
return false;
}
for (int32 i = 0; i < Container.Entries.Num(); i++)
{
// handle adding to existing value.
FGIS_CurrencyEntry& Entry = Container.Entries[i];
if (Entry.Definition == CurrencyInfo.Definition)
{
const float OldValue = Entry.Amount;
const float NewValue = Entry.Amount + CurrencyInfo.Amount;
Entry.Amount = NewValue;
OnCurrencyEntryUpdated(Entry, i, OldValue, NewValue);
Container.MarkItemDirty(Entry);
return true;
}
}
int32 Idx = Container.Entries.AddDefaulted();
Container.Entries[Idx] = CurrencyInfo;
OnCurrencyEntryAdded(Container.Entries[Idx], Idx);
Container.MarkItemDirty(Container.Entries[Idx]);
return true;
}
bool UGIS_CurrencySystemComponent::RemoveCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo, bool bNotify)
{
if (!CurrencyInfo.Definition || CurrencyInfo.Amount <= 0)
{
GIS_CLOG(Warning, "An invalid tag was passed to RemoveItem")
// FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveItem"), ELogVerbosity::Warning);
return false;
}
for (int32 i = 0; i < Container.Entries.Num(); i++)
{
FGIS_CurrencyEntry& Entry = Container.Entries[i];
if (Entry.Definition == CurrencyInfo.Definition)
{
if (Entry.Amount <= CurrencyInfo.Amount)
{
OnCurrencyEntryRemoved(Entry, i);
Container.Entries.RemoveAt(i);
Container.MarkArrayDirty();
}
else
{
const float OldValue = Entry.Amount;
const float NewValue = Entry.Amount - CurrencyInfo.Amount;
Entry.Amount = NewValue;
OnCurrencyEntryUpdated(Entry, i, OldValue, NewValue);
Container.MarkItemDirty(Entry);
}
return true;
}
}
return false;
}
bool UGIS_CurrencySystemComponent::HasCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo) const
{
if (CurrencyMap.Contains(CurrencyInfo.Definition) && CurrencyInfo.Amount > 0)
{
return CurrencyMap[CurrencyInfo.Definition] >= CurrencyInfo.Amount;
}
return false;
}

View File

@@ -0,0 +1,7 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Exchange/Shops/GIS_ShopCondition.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ShopCondition)
// Add default functionality here for any IGShopCondition functions that are not pure virtual.

View File

@@ -0,0 +1,365 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Exchange/Shops/GIS_ShopSystemComponent.h"
#include "UObject/Object.h"
#include "GameFramework/Actor.h"
#include "GIS_CurrencySystemComponent.h"
#include "GIS_InventoryFunctionLibrary.h"
#include "GIS_InventorySubsystem.h"
#include "GIS_InventorySystemComponent.h"
#include "GIS_ItemCollection.h"
#include "GIS_ItemDefinition.h"
#include "GIS_ItemFragment_Shoppable.h"
#include "Items/GIS_ItemInstance.h"
#include "GIS_LogChannels.h"
#include "Exchange/Shops/GIS_ShopCondition.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ShopSystemComponent)
UGIS_ShopSystemComponent::UGIS_ShopSystemComponent()
{
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bCanEverTick = false;
}
UGIS_ShopSystemComponent* UGIS_ShopSystemComponent::GetShopSystemComponent(const AActor* Actor)
{
return IsValid(Actor) ? Actor->FindComponentByClass<UGIS_ShopSystemComponent>() : nullptr;
}
UGIS_InventorySystemComponent* UGIS_ShopSystemComponent::GetInventory() const
{
return OwningInventory;
}
bool UGIS_ShopSystemComponent::BuyItem(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
{
return BuyItemInternal(BuyerInventory, CurrencySystem, ItemInfo);
}
bool UGIS_ShopSystemComponent::SellItem(UGIS_InventorySystemComponent* SellerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
{
return SellItemInternal(SellerInventory, CurrencySystem, ItemInfo);
}
bool UGIS_ShopSystemComponent::CanBuyerBuyItem(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo) const
{
UGIS_ItemCollection* TargetCollection = BuyerInventory->GetCollectionByTag(TargetItemCollectionToAddOnBuy);
if (TargetCollection == nullptr)
{
GIS_CLOG(Warning, "buyer's inventory missing collection named:%s", *TargetItemCollectionToAddOnBuy.ToString());
return false;
}
FGIS_ItemInfo CanAddItemInfo;
if (!TargetCollection->CanAddItem(ItemInfo, CanAddItemInfo))
{
GIS_CLOG(Warning, "buyer's collection can't add this item(%s)", *ItemInfo.GetDebugString());
return false;
}
for (int i = 0; i < BuyConditions.Num(); i++)
{
if (BuyConditions[i]->CanBuy(this, BuyerInventory, CurrencySystem, ItemInfo)) { continue; }
GIS_CLOG(Warning, "buy collection(%s) reject buying item(%s)", *BuyConditions[i].GetObject()->GetClass()->GetName(), *ItemInfo.GetDebugString());
return false;
}
return CanBuyerBuyItemInternal(BuyerInventory, CurrencySystem, ItemInfo);
}
bool UGIS_ShopSystemComponent::CanSellerSellItem(UGIS_InventorySystemComponent* SellerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo) const
{
for (int i = 0; i < SellConditions.Num(); i++)
{
if (SellConditions[i]->CanSell(this, SellerInventory, CurrencySystem, ItemInfo)) { continue; }
GIS_CLOG(Warning, "sell collection(%s) reject selling item(%s)", *SellConditions[i].GetObject()->GetClass()->GetName(), *ItemInfo.GetDebugString());
return false;
}
return CanSellerSellItemInternal(SellerInventory, CurrencySystem, ItemInfo);
}
bool UGIS_ShopSystemComponent::IsItemBuyable(const FGIS_ItemInfo& ItemInfo) const
{
if (OwningInventory == nullptr) { return false; }
if (!ItemInfo.IsValid())
{
GIS_CLOG(Warning, "invalid item to buy.");
return false;
}
UGIS_ItemCollection* ItemCollection = ItemInfo.Item->GetOwningCollection();
if (ItemCollection == nullptr) { ItemCollection = OwningInventory->GetDefaultCollection(); }
if (!ItemCollection->HasItem(ItemInfo.Item, 1))
{
GIS_CLOG(Warning, "shop's inventory doesn't have item:%s", *ItemInfo.Item->GetDefinition()->GetName());
return false;
}
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
if (Shoppable == nullptr)
{
GIS_CLOG(Warning, "item(%s) is not buyable, missing Shoppable fragment!", *ItemInfo.GetDebugString());
return false;
}
if (Shoppable->BuyCurrencyAmounts.IsEmpty())
{
GIS_CLOG(Warning, "item(%s) is not buyable, missing BuyCurrencyAmounts in shoppable fragment!", *ItemInfo.GetDebugString());
return false;
}
return true;
}
bool UGIS_ShopSystemComponent::IsItemSellable(const FGIS_ItemInfo& ItemInfo) const
{
if (!ItemInfo.IsValid())
{
GIS_CLOG(Warning, "invalid item to sell.");
return false;
}
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
if (Shoppable == nullptr)
{
GIS_CLOG(Warning, "item(%s) is not sellable, missing Shoppable fragment!", *ItemInfo.GetDebugString());
return false;
}
if (Shoppable->SellCurrencyAmounts.IsEmpty())
{
GIS_CLOG(Warning, "item(%s) is not sellable, missing SellCurrencyAmounts in shoppable fragment!", *ItemInfo.GetDebugString());
return false;
}
return true;
}
float UGIS_ShopSystemComponent::GetBuyModifierForBuyer_Implementation(UGIS_InventorySystemComponent* BuyerInventory) const
{
return 1 + BuyPriceModifier;
}
// float UGIS_ShopSystemComponent::GetBuyModifierForItem(UGIS_InventorySystemComponent* BuyerInventory, FGIS_ItemInfo ItemInfo) const
// {
// return 1;
// }
float UGIS_ShopSystemComponent::GetSellModifierForSeller_Implementation(UGIS_InventorySystemComponent* SellerInventory) const
{
return 1 + SellPriceModifer;
}
// float UGIS_ShopSystemComponent::GetSellModifierForItem(UGIS_InventorySystemComponent* SellerInventory, const FGIS_ItemInfo& ItemInfo) const
// {
// return 1;
// }
bool UGIS_ShopSystemComponent::TryGetBuyValueForBuyer_Implementation(UGIS_InventorySystemComponent* Buyer, const FGIS_ItemInfo& ItemInfo, TArray<FGIS_CurrencyEntry>& BuyValue) const
{
if (!IsValid(ItemInfo.Item))
{
GIS_CLOG(Warning, "invalid item to buy.");
return false;
}
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
if (!IsValid(Shoppable))
{
GIS_CLOG(Warning, "missing Shoppable fragment for item:%s!", *GetNameSafe(ItemInfo.Item->GetDefinition()));
return false;
}
float Modifier = GetBuyModifierForBuyer(Buyer);
BuyValue = UGIS_InventoryFunctionLibrary::MultiplyCurrencies(Shoppable->BuyCurrencyAmounts, Modifier * ItemInfo.Amount);
return BuyValue.IsEmpty() == false;
}
bool UGIS_ShopSystemComponent::TryGetSellValueForSeller_Implementation(UGIS_InventorySystemComponent* Seller, const FGIS_ItemInfo& ItemInfo, TArray<FGIS_CurrencyEntry>& SellValue) const
{
if (!IsValid(ItemInfo.Item))
{
GIS_CLOG(Warning, "invalid item to sell.");
return false;
}
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
if (!IsValid(Shoppable))
{
GIS_CLOG(Warning, "missing Shoppable fragment for item:%s!", *GetNameSafe(ItemInfo.Item->GetDefinition()));
return false;
}
float Modifier = GetSellModifierForSeller(Seller);
SellValue = UGIS_InventoryFunctionLibrary::MultiplyCurrencies(Shoppable->SellCurrencyAmounts, Modifier * ItemInfo.Amount);
return SellValue.IsEmpty() == false;
}
void UGIS_ShopSystemComponent::BeginPlay()
{
OwningInventory = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetOwner());
if (!OwningInventory)
{
GIS_CLOG(Error, "Requires inventory system component!");
}
{
TArray<UActorComponent*> Components = GetOwner()->GetComponentsByInterface(UGIS_ShopBuyCondition::StaticClass());
BuyConditions.Empty();
for (const auto Component : Components)
{
BuyConditions.Add(Component);
}
}
{
TArray<UActorComponent*> Components = GetOwner()->GetComponentsByInterface(UGIS_ShopSellCondition::StaticClass());
SellConditions.Empty();
for (const auto Component : Components)
{
SellConditions.Add(Component);
}
}
Super::BeginPlay();
}
void UGIS_ShopSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
}
bool UGIS_ShopSystemComponent::CanSellerSellItemInternal_Implementation(UGIS_InventorySystemComponent* SellerInventory, UGIS_CurrencySystemComponent* CurrencySystem,
const FGIS_ItemInfo& ItemInfo) const
{
UGIS_ItemCollection* ItemCollection = ItemInfo.ItemCollection;
if (ItemCollection == nullptr || ItemCollection->GetOwningInventory() == SellerInventory)
{
ItemCollection = SellerInventory->GetDefaultCollection();
}
if (ItemCollection == nullptr)
{
GIS_CLOG(Warning, "seller:%s doesn't have valid default collection.", *GetNameSafe(SellerInventory));
return false;
}
//atleast has one.
if (!ItemCollection->HasItem(ItemInfo.Item, 1))
{
return false;
}
return true;
}
bool UGIS_ShopSystemComponent::CanBuyerBuyItemInternal_Implementation(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo) const
{
TArray<FGIS_CurrencyEntry> BuyPrice;
if (TryGetBuyValueForBuyer(BuyerInventory, ItemInfo, BuyPrice))
{
return CurrencySystem->HasCurrencies(BuyPrice);
}
return false;
}
bool UGIS_ShopSystemComponent::SellItemInternal_Implementation(UGIS_InventorySystemComponent* Seller, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
{
if (!IsValid(Seller) || !ItemInfo.IsValid() || !IsValid(CurrencySystem))
{
GIS_CLOG(Warning, "passed invalid parameters!");
return false;
}
if (!IsItemSellable(ItemInfo))
{
GIS_CLOG(Warning, "item:%s is not sellable", *ItemInfo.GetDebugString());
return false;
}
if (!CanSellerSellItem(Seller, CurrencySystem, ItemInfo))
{
GIS_CLOG(Warning, "seller can sell this item:%s", *ItemInfo.GetDebugString());
return false;
}
UGIS_ItemCollection* ItemCollection = ItemInfo.ItemCollection;
if (ItemCollection == nullptr || ItemCollection->GetOwningInventory() == Seller)
{
ItemCollection = Seller->GetDefaultCollection();
}
if (ItemCollection->RemoveItem(ItemInfo).Amount != ItemInfo.Amount)
{
GIS_CLOG(Error, "Failed to remove item(%s) from inventory!", *ItemInfo.GetDebugString());
return false;
}
TArray<FGIS_CurrencyEntry> SellCurrencyAmount;
if (!TryGetSellValueForSeller(Seller, ItemInfo, SellCurrencyAmount))
{
GIS_CLOG(Error, "can't get sell value for item:%s", *ItemInfo.GetDebugString());
return false;
}
CurrencySystem->AddCurrencies(SellCurrencyAmount);
OwningInventory->AddItem(ItemInfo);
return true;
}
bool UGIS_ShopSystemComponent::BuyItemInternal_Implementation(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
{
if (!IsValid(BuyerInventory) || !ItemInfo.IsValid() || !IsValid(CurrencySystem))
{
return false;
}
if (!IsItemBuyable(ItemInfo))
{
return false;
}
if (!CanBuyerBuyItem(BuyerInventory, CurrencySystem, ItemInfo))
{
return false;
}
UGIS_ItemCollection* TargetCollection = BuyerInventory->GetCollectionByTag(TargetItemCollectionToAddOnBuy);
if (TargetCollection == nullptr)
{
return false;
}
//remove currency from buyer's currency system.
TArray<FGIS_CurrencyEntry> BuyPrice;
if (TryGetBuyValueForBuyer(BuyerInventory, ItemInfo, BuyPrice))
{
CurrencySystem->RemoveCurrencies(BuyPrice);
}
else
{
return false;
}
if (ItemInfo.Item->IsUnique())
{
for (int32 i = 0; i < ItemInfo.Amount; ++i)
{
UGIS_ItemInstance* NewItem = UGIS_InventorySubsystem::Get(this)->CreateItem(BuyerInventory->GetOwner(), ItemInfo.Item->GetDefinition());
TargetCollection->AddItem(NewItem, 1);
}
}
else
{
TargetCollection->AddItem(ItemInfo);
}
// Remove item from shop's inventory.
OwningInventory->RemoveItem(ItemInfo);
// Add currency to shop's currency system.
if (UGIS_CurrencySystemComponent* OwningCurrencySystem = UGIS_CurrencySystemComponent::GetCurrencySystemComponent(GetOwner()))
{
OwningCurrencySystem->AddCurrencies(BuyPrice);
}
return true;
}