添加库存系统组件
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include "Character/PHYCharacter.h"
|
||||
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "Equipping/GIS_EquipmentSystemComponent.h"
|
||||
#include "GMS_CharacterMovementSystemComponent.h"
|
||||
#include "Components/RetargeterComponent.h"
|
||||
|
||||
@@ -14,6 +15,7 @@ APHYCharacter::APHYCharacter()
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
InventorySystemComponent = CreateDefaultSubobject<UGIS_InventorySystemComponent>(TEXT("InventorySystem"));
|
||||
EquipmentSystemComponent = CreateDefaultSubobject<UGIS_EquipmentSystemComponent>(TEXT("EquipmentSystem"));
|
||||
MovementSystemComponent = CreateDefaultSubobject<UGMS_CharacterMovementSystemComponent>(TEXT("MovementSystem"));
|
||||
RetargeterComponent = CreateDefaultSubobject<URetargeterComponent>(TEXT("Retargeter"));
|
||||
}
|
||||
@@ -27,6 +29,9 @@ void APHYCharacter::BeginPlay()
|
||||
if (HasAuthority() && InventorySystemComponent)
|
||||
{
|
||||
InventorySystemComponent->InitializeInventorySystem();
|
||||
if (EquipmentSystemComponent)
|
||||
{
|
||||
EquipmentSystemComponent->InitializeEquipmentSystem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
class URetargeterComponent;
|
||||
|
||||
class UGIS_InventorySystemComponent;
|
||||
class UGIS_EquipmentSystemComponent;
|
||||
class UGMS_CharacterMovementSystemComponent;
|
||||
|
||||
|
||||
@@ -24,6 +25,9 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Inventory")
|
||||
UGIS_InventorySystemComponent* GetInventorySystemComponent() const { return InventorySystemComponent; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Equipment")
|
||||
UGIS_EquipmentSystemComponent* GetEquipmentSystemComponent() const { return EquipmentSystemComponent; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PHY|Movement")
|
||||
UGMS_CharacterMovementSystemComponent* GetMovementSystemComponent() const { return MovementSystemComponent; }
|
||||
|
||||
@@ -41,6 +45,13 @@ protected:
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Inventory", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<UGIS_InventorySystemComponent> InventorySystemComponent;
|
||||
|
||||
/**
|
||||
* 角色的装备系统组件(来自 GenericInventorySystem 插件 GIS)。
|
||||
* 监听装备槽变化并驱动装备属性生效。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PHY|Equipment", meta = (AllowPrivateAccess = "true"))
|
||||
TObjectPtr<UGIS_EquipmentSystemComponent> EquipmentSystemComponent;
|
||||
|
||||
/**
|
||||
* 角色的移动系统组件(来自 GenericMovementSystem 插件 GMS)。
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include "GameplayTags/InputTags.h"
|
||||
#include "Components/RetargeterComponent.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "Core/Items/GIS_ItemInfo.h"
|
||||
#include "Core/Items/GIS_ItemInstance.h"
|
||||
#include "Equipping/GIS_EquipmentInterface.h"
|
||||
#include "Equipping/GIS_EquipmentSystemComponent.h"
|
||||
#include "AbilitySystem/PHYClassDefaults.h"
|
||||
#include "AbilitySystem/Attributes/PHYAttributeSet.h"
|
||||
#include "AbilitySystem/Effects/PHYGE_DerivedAttributes.h"
|
||||
@@ -77,6 +82,7 @@ void APHYPlayerCharacter::PossessedBy(AController* NewController)
|
||||
Super::PossessedBy(NewController);
|
||||
|
||||
InitializeGAS();
|
||||
BindEquipmentEventsIfNeeded();
|
||||
// 初始化hud
|
||||
InitializeHUD();
|
||||
if (APHYPlayerState* PS = GetPlayerState<APHYPlayerState>())
|
||||
@@ -107,10 +113,44 @@ void APHYPlayerCharacter::OnRep_PlayerState()
|
||||
{
|
||||
Super::OnRep_PlayerState();
|
||||
InitializeGAS();
|
||||
BindEquipmentEventsIfNeeded();
|
||||
// 初始化hud
|
||||
InitializeHUD();
|
||||
}
|
||||
|
||||
void APHYPlayerCharacter::BindEquipmentEventsIfNeeded()
|
||||
{
|
||||
if (!HasAuthority() || bEquipmentEventBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UGIS_EquipmentSystemComponent* EquipmentSystem = GetEquipmentSystemComponent())
|
||||
{
|
||||
EquipmentSystem->OnEquipmentStateChangedEvent.AddDynamic(this, &ThisClass::OnEquipmentStateChanged);
|
||||
bEquipmentEventBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
void APHYPlayerCharacter::OnEquipmentStateChanged(UObject* Equipment, FGameplayTag SlotTag, bool bEquipped)
|
||||
{
|
||||
if (!HasAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* SourceItem = nullptr;
|
||||
if (IsValid(Equipment) && Equipment->GetClass()->ImplementsInterface(UGIS_EquipmentInterface::StaticClass()))
|
||||
{
|
||||
SourceItem = IGIS_EquipmentInterface::Execute_GetSourceItem(Equipment);
|
||||
}
|
||||
|
||||
if (APHYPlayerState* PS = GetPlayerState<APHYPlayerState>())
|
||||
{
|
||||
PS->HandleItemEquippedState(SourceItem, bEquipped);
|
||||
}
|
||||
}
|
||||
|
||||
void APHYPlayerCharacter::InitializeGAS()
|
||||
{
|
||||
APHYPlayerState* PS = GetPlayerState<APHYPlayerState>();
|
||||
@@ -120,7 +160,7 @@ void APHYPlayerCharacter::InitializeGAS()
|
||||
if (!ASC) return;
|
||||
|
||||
ASC->InitAbilityActorInfo(PS, this);
|
||||
|
||||
|
||||
// Server applies init effects.
|
||||
if (HasAuthority())
|
||||
{
|
||||
@@ -220,3 +260,59 @@ void APHYPlayerCharacter::RegenTick()
|
||||
Spec.Data->SetSetByCallerMagnitude(RegenTags::Tag__Data_Regen_InnerPower, FMath::Max(0.f, InnerPowerDelta));
|
||||
ASC->ApplyGameplayEffectSpecToSelf(*Spec.Data.Get());
|
||||
}
|
||||
|
||||
void APHYPlayerCharacter::ServerUseConsumableByItemId_Implementation(FGuid ItemId, int32 Count)
|
||||
{
|
||||
if (!HasAuthority() || !ItemId.IsValid() || Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UGIS_InventorySystemComponent* Inventory = GetInventorySystemComponent();
|
||||
APHYPlayerState* PS = GetPlayerState<APHYPlayerState>();
|
||||
if (!Inventory || !PS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo SourceInfo;
|
||||
for (const FGIS_ItemInfo& Info : Inventory->GetItemInfos())
|
||||
{
|
||||
if (Info.IsValid() && IsValid(Info.Item) && Info.Item->GetItemId() == ItemId)
|
||||
{
|
||||
SourceInfo = Info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SourceInfo.IsValid() || !IsValid(SourceInfo.Item) || SourceInfo.Amount <= 0)
|
||||
{
|
||||
PS->NotifyOwner(EPHYNotifyType::Warning,
|
||||
NSLOCTEXT("PHY", "Notify_UseConsumable_NotFound", "道具不存在或数量不足"));
|
||||
return;
|
||||
}
|
||||
|
||||
const FGameplayTag ConsumableTag = FGameplayTag::RequestGameplayTag(FName(TEXT("GIS.Item.Type.Consumable")), false);
|
||||
if (!ConsumableTag.IsValid() || !SourceInfo.Item->GetItemTags().HasTag(ConsumableTag))
|
||||
{
|
||||
PS->NotifyOwner(EPHYNotifyType::Warning,
|
||||
FText::Format(NSLOCTEXT("PHY", "Notify_UseConsumable_NotConsumable", "{0} 不是可使用药品"), SourceInfo.Item->GetItemName()));
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 ConsumeCount = FMath::Min(Count, SourceInfo.Amount);
|
||||
FGIS_ItemInfo RemoveRequest(SourceInfo.Item, ConsumeCount, SourceInfo);
|
||||
FGIS_ItemInfo Removed = Inventory->RemoveItem(RemoveRequest);
|
||||
if (!Removed.IsValid() || Removed.Amount <= 0)
|
||||
{
|
||||
PS->NotifyOwner(EPHYNotifyType::Warning,
|
||||
NSLOCTEXT("PHY", "Notify_UseConsumable_RemoveFailed", "消耗失败,请稍后重试"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PS->ApplyConsumableFromItem(SourceInfo.Item, Removed.Amount))
|
||||
{
|
||||
PS->NotifyOwner(EPHYNotifyType::Warning,
|
||||
FText::Format(NSLOCTEXT("PHY", "Notify_UseConsumable_NoEffect", "{0} 当前无法生效"), SourceInfo.Item->GetItemName()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
#include "PHYCharacter.h"
|
||||
#include "AbilitySystem/PHYCharacterClass.h"
|
||||
#include "Pawn/UGC_PawnInterface.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "PHYPlayerCharacter.generated.h"
|
||||
|
||||
class UGIPS_InputSystemComponent;
|
||||
class USpringArmComponent;
|
||||
class UCameraComponent;
|
||||
class URetargeterComponent;
|
||||
class UGIS_ItemInstance;
|
||||
struct FGuid;
|
||||
|
||||
UCLASS()
|
||||
class PHY_API APHYPlayerCharacter : public APHYCharacter,
|
||||
@@ -57,6 +60,16 @@ protected:
|
||||
void InitializeHUD();
|
||||
virtual void OnRep_PlayerState() override;
|
||||
|
||||
/** 服务器:按道具 ID 使用消耗品并扣除数量。 */
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category="PHY|Inventory")
|
||||
void ServerUseConsumableByItemId(FGuid ItemId, int32 Count = 1);
|
||||
|
||||
/** 装备系统事件(服务器):装备/卸下时应用或回滚属性。 */
|
||||
UFUNCTION()
|
||||
void OnEquipmentStateChanged(UObject* Equipment, FGameplayTag SlotTag, bool bEquipped);
|
||||
|
||||
void BindEquipmentEventsIfNeeded();
|
||||
|
||||
/** Server: apply init effects. Both sides: init actor info. */
|
||||
void InitializeGAS();
|
||||
|
||||
@@ -70,4 +83,6 @@ protected:
|
||||
FTimerHandle RegenTimerHandle;
|
||||
void RegenTick();
|
||||
void StopRegen();
|
||||
|
||||
bool bEquipmentEventBound = false;
|
||||
};
|
||||
|
||||
@@ -11,11 +11,35 @@
|
||||
#include "Gameplay/GameLevelInfo.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
#include "Core/Items/GIS_ItemInstance.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// 你可以把这些做成配置/曲线/数据表;这里先给一个默认规则:每升1级给 1 点属性点
|
||||
constexpr int32 AttributePointsPerLevel = 4;
|
||||
|
||||
static FGameplayTag ReqTag(const TCHAR* Name)
|
||||
{
|
||||
return FGameplayTag::RequestGameplayTag(FName(Name), false);
|
||||
}
|
||||
|
||||
static float GetItemNumericAttribute(const UGIS_ItemInstance* Item, const FGameplayTag& Tag)
|
||||
{
|
||||
if (!IsValid(Item) || !Tag.IsValid())
|
||||
{
|
||||
return 0.f;
|
||||
}
|
||||
if (Item->HasFloatAttribute(Tag))
|
||||
{
|
||||
return Item->GetFloatAttribute(Tag);
|
||||
}
|
||||
if (Item->HasIntegerAttribute(Tag))
|
||||
{
|
||||
return static_cast<float>(Item->GetIntegerAttribute(Tag));
|
||||
}
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
APHYPlayerState::APHYPlayerState()
|
||||
@@ -27,7 +51,7 @@ APHYPlayerState::APHYPlayerState()
|
||||
|
||||
// 使用Minimal复制模式(推荐用于PlayerState)
|
||||
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Minimal);
|
||||
|
||||
AttributeSet = CreateDefaultSubobject<UPHYAttributeSet>(TEXT("AttributeSet"));
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* APHYPlayerState::GetAbilitySystemComponent() const
|
||||
@@ -86,11 +110,10 @@ void APHYPlayerState::AddToXP(const int32 InXP)
|
||||
return;
|
||||
}
|
||||
|
||||
// 本地提示:获得经验(只在本地玩家的 PlayerState 上显示即可)
|
||||
if (IsOwnedBy(GetWorld() ? GetWorld()->GetFirstPlayerController() : nullptr))
|
||||
{
|
||||
PushGainedXPNotify(InXP);
|
||||
}
|
||||
// NOTE:
|
||||
// XP/等级的权威逻辑走服务器;客户端通过复制拿到 XP。
|
||||
// “获得经验”的 UI 提示优先在客户端触发(OnRep_XP),
|
||||
// 但 ListenServer/Standalone 的本地玩家通常不会走 OnRep,因此这里需要兜底。
|
||||
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(APHYPlayerState, XP, this);
|
||||
XP += InXP;
|
||||
@@ -98,6 +121,12 @@ void APHYPlayerState::AddToXP(const int32 InXP)
|
||||
// 只在权威端(Server/Standalone)做升级计算,客户端靠复制拿到最终 Level/XP
|
||||
if (HasAuthority())
|
||||
{
|
||||
// ListenServer/Standalone:本地玩家在权威端,OnRep_XP 不会触发,兜底弹“获得经验”
|
||||
if (AController* Controller = GetOwningController(); Controller && Controller->IsLocalController())
|
||||
{
|
||||
PushGainedXPNotify(InXP);
|
||||
}
|
||||
|
||||
const int32 NewLevel = FMath::Clamp(FGameLevelInfo::GetLevelForTotalXP(XP), FGameLevelInfo::MinLevel, FGameLevelInfo::MaxLevel);
|
||||
if (NewLevel > Level)
|
||||
{
|
||||
@@ -116,10 +145,16 @@ void APHYPlayerState::AddToLevel(const int32 InLevel)
|
||||
Level += InLevel;
|
||||
OnLevelChangedDelegate.Broadcast(Level,true);
|
||||
|
||||
// 本地提示:升级
|
||||
if (IsOwnedBy(GetWorld() ? GetWorld()->GetFirstPlayerController() : nullptr))
|
||||
// UI 通知策略:
|
||||
// - 纯客户端:走 OnRep_Level。
|
||||
// - DedicatedServer:没有本地玩家,不需要弹。
|
||||
// - ListenServer/Standalone:本地玩家在权威端,不会走 OnRep,因此这里兜底触发一次。
|
||||
if (HasAuthority())
|
||||
{
|
||||
PushLevelUpNotify(Level);
|
||||
if (AController* Controller = GetOwningController(); Controller && Controller->IsLocalController())
|
||||
{
|
||||
PushLevelUpNotify(Level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +163,33 @@ void APHYPlayerState::PushGameplayNotify(EPHYNotifyType Type, const FText& Messa
|
||||
OnGameplayNotifyDelegate.Broadcast(Type, Message, SoftIcon);
|
||||
}
|
||||
|
||||
|
||||
void APHYPlayerState::NotifyOwner(EPHYNotifyType Type, const FText& Message, TSoftObjectPtr<UTexture2D> SoftIcon)
|
||||
{
|
||||
if (!HasAuthority())
|
||||
{
|
||||
if (AController* Controller = GetOwningController(); Controller && Controller->IsLocalController())
|
||||
{
|
||||
PushGameplayNotify(Type, Message, SoftIcon);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (AController* Controller = GetOwningController(); Controller && Controller->IsLocalController())
|
||||
{
|
||||
PushGameplayNotify(Type, Message, SoftIcon);
|
||||
return;
|
||||
}
|
||||
|
||||
ClientPushGameplayNotify(Type, Message, SoftIcon);
|
||||
}
|
||||
|
||||
void APHYPlayerState::ClientPushGameplayNotify_Implementation(EPHYNotifyType Type, const FText& Message,
|
||||
const TSoftObjectPtr<UTexture2D>& SoftIcon)
|
||||
{
|
||||
PushGameplayNotify(Type, Message, SoftIcon);
|
||||
}
|
||||
|
||||
void APHYPlayerState::PushGainedXPNotify(int32 DeltaXP)
|
||||
{
|
||||
if (DeltaXP <= 0) return;
|
||||
@@ -202,11 +264,38 @@ void APHYPlayerState::SetAttributePoints(const int32 InPoints)
|
||||
void APHYPlayerState::OnRep_Level(int32 OldLevel)
|
||||
{
|
||||
OnLevelChangedDelegate.Broadcast(Level, true);
|
||||
|
||||
// 客户端侧提示升级:DedicatedServer 不会进到这里(也不会有本地控制器)。
|
||||
if (!HasAuthority())
|
||||
{
|
||||
if (Level > OldLevel)
|
||||
{
|
||||
if (AController* Controller = GetOwningController(); Controller && Controller->IsLocalController())
|
||||
{
|
||||
PushLevelUpNotify(Level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APHYPlayerState::OnRep_XP(int32 OldXP)
|
||||
{
|
||||
OnXPChangedDelegate.Broadcast(XP);
|
||||
|
||||
// 仅客户端:本地提示“获得经验”。
|
||||
// - DedicatedServer 上不会有本地玩家,因此不触发。
|
||||
// - ListenServer 的主机本地玩家会走本地分支。
|
||||
if (!HasAuthority())
|
||||
{
|
||||
const int32 Delta = XP - OldXP;
|
||||
if (Delta > 0)
|
||||
{
|
||||
if (AController* Controller = GetOwningController(); Controller && Controller->IsLocalController())
|
||||
{
|
||||
PushGainedXPNotify(Delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APHYPlayerState::OnRep_AttributePoints(int32 OldAttributePoints)
|
||||
@@ -252,3 +341,129 @@ int32 APHYPlayerState::GetXPRequirementForNextLevel() const
|
||||
FGameLevelInfo::GetProgressForTotalXP(XP, L, Into, ToNext);
|
||||
return ToNext;
|
||||
}
|
||||
|
||||
void APHYPlayerState::ApplyItemAttributesWithSign(UGIS_ItemInstance* Item, const float Sign)
|
||||
{
|
||||
if (!HasAuthority() || !IsValid(Item) || FMath::IsNearlyZero(Sign))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* ASC = GetAbilitySystemComponent();
|
||||
const UPHYAttributeSet* AS = GetAttributeSet();
|
||||
if (!ASC || !AS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto AddDelta = [ASC](const FGameplayAttribute& Attribute, const float Delta)
|
||||
{
|
||||
if (!Attribute.IsValid() || FMath::IsNearlyZero(Delta))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ASC->SetNumericAttributeBase(Attribute, ASC->GetNumericAttribute(Attribute) + Delta);
|
||||
};
|
||||
|
||||
AddDelta(UPHYAttributeSet::GetPhysicalAttackAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Attack"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetPhysicalDefenseAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Defense"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetCritChanceAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.CritRate"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetCritDamageAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.CritDamage"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetHitChanceAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Hit"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetDodgeChanceAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Dodge"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetParryChanceAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Parry"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetCounterChanceAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.CounterChance"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetArmorPenetrationAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.ArmorPenetration"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetDamageReductionAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.DamageReduction"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetLifeStealAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.LifeSteal"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetTenacityAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Resilience"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetMoveSpeedAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.MoveSpeed"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetHealthRegenRateAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.HealthRegenRate"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetInnerPowerRegenRateAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.InnerPowerRegenRate"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetMaxHealthAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.MaxHealth"))) * Sign);
|
||||
AddDelta(UPHYAttributeSet::GetMaxInnerPowerAttribute(), GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.MaxInnerPower"))) * Sign);
|
||||
|
||||
// 上限变更后,当前值做一次夹取,避免越界。
|
||||
ASC->SetNumericAttributeBase(UPHYAttributeSet::GetHealthAttribute(), FMath::Clamp(AS->GetHealth(), 0.f, AS->GetMaxHealth()));
|
||||
ASC->SetNumericAttributeBase(UPHYAttributeSet::GetInnerPowerAttribute(), FMath::Clamp(AS->GetInnerPower(), 0.f, AS->GetMaxInnerPower()));
|
||||
}
|
||||
|
||||
void APHYPlayerState::HandleItemEquippedState(UGIS_ItemInstance* Item, const bool bEquipped)
|
||||
{
|
||||
if (!HasAuthority() || !IsValid(Item))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FGuid ItemId = Item->GetItemId();
|
||||
if (!ItemId.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TSoftObjectPtr<UTexture2D> Icon = Item->GetDefinition() ? Item->GetDefinition()->Icon : nullptr;
|
||||
|
||||
if (bEquipped)
|
||||
{
|
||||
if (AppliedEquipmentItems.Contains(ItemId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
AppliedEquipmentItems.Add(ItemId);
|
||||
ApplyItemAttributesWithSign(Item, +1.f);
|
||||
NotifyOwner(EPHYNotifyType::Info,
|
||||
FText::Format(NSLOCTEXT("PHY", "Notify_EquipItem", "装备 {0}"), Item->GetItemName()),
|
||||
Icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!AppliedEquipmentItems.Contains(ItemId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
AppliedEquipmentItems.Remove(ItemId);
|
||||
ApplyItemAttributesWithSign(Item, -1.f);
|
||||
NotifyOwner(EPHYNotifyType::Info,
|
||||
FText::Format(NSLOCTEXT("PHY", "Notify_UnequipItem", "卸下 {0}"), Item->GetItemName()),
|
||||
Icon);
|
||||
}
|
||||
}
|
||||
|
||||
bool APHYPlayerState::ApplyConsumableFromItem(UGIS_ItemInstance* Item, const int32 Count)
|
||||
{
|
||||
if (!HasAuthority() || !IsValid(Item) || Count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* ASC = GetAbilitySystemComponent();
|
||||
const UPHYAttributeSet* AS = GetAttributeSet();
|
||||
if (!ASC || !AS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const float RestoreHealth = GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Consumable.RestoreHealth"))) * static_cast<float>(Count);
|
||||
const float RestoreInnerPower = GetItemNumericAttribute(Item, ReqTag(TEXT("GIS.Attribute.Item.Consumable.RestoreInnerPower"))) * static_cast<float>(Count);
|
||||
|
||||
if (RestoreHealth <= 0.f && RestoreInnerPower <= 0.f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (RestoreHealth > 0.f)
|
||||
{
|
||||
ASC->SetNumericAttributeBase(UPHYAttributeSet::GetHealthAttribute(), FMath::Clamp(AS->GetHealth() + RestoreHealth, 0.f, AS->GetMaxHealth()));
|
||||
}
|
||||
if (RestoreInnerPower > 0.f)
|
||||
{
|
||||
ASC->SetNumericAttributeBase(UPHYAttributeSet::GetInnerPowerAttribute(), FMath::Clamp(AS->GetInnerPower() + RestoreInnerPower, 0.f, AS->GetMaxInnerPower()));
|
||||
}
|
||||
|
||||
const TSoftObjectPtr<UTexture2D> Icon = Item->GetDefinition() ? Item->GetDefinition()->Icon : nullptr;
|
||||
NotifyOwner(EPHYNotifyType::Reward,
|
||||
FText::Format(NSLOCTEXT("PHY", "Notify_UseConsumable", "使用 {0} x{1}"), Item->GetItemName(), FText::AsNumber(Count)),
|
||||
Icon);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
class UAbilitySystemComponent;
|
||||
class UPHYAttributeSet;
|
||||
class UGIS_ItemInstance;
|
||||
|
||||
/**
|
||||
* 玩家状态类,包含GAS支持
|
||||
@@ -57,7 +58,7 @@ public:
|
||||
UAbilitySystemComponent* GetPHYAbilitySystemComponent() const { return AbilitySystemComponent; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Abilities")
|
||||
const UPHYAttributeSet* GetAttributeSet() const { return AttributeSet; }
|
||||
UPHYAttributeSet* GetAttributeSet() const { return AttributeSet; }
|
||||
|
||||
// 原有的ReTargeterTag相关代码
|
||||
FOnPlayerStatChanged OnXPChangedDelegate;
|
||||
@@ -84,6 +85,19 @@ public:
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category="Player|Progress")
|
||||
void ServerAddXP(int32 DeltaXP);
|
||||
|
||||
/** 服务端:根据装备穿脱状态应用/回滚道具属性。 */
|
||||
void HandleItemEquippedState(UGIS_ItemInstance* Item, bool bEquipped);
|
||||
|
||||
/** 服务端:应用药品恢复(Count 为消耗数量)。 */
|
||||
bool ApplyConsumableFromItem(UGIS_ItemInstance* Item, int32 Count);
|
||||
|
||||
/** 向拥有者玩家推送提示(会自动处理 DS/Listen/Standalone)。 */
|
||||
void NotifyOwner(EPHYNotifyType Type, const FText& Message, TSoftObjectPtr<UTexture2D> SoftIcon = nullptr);
|
||||
|
||||
/** 仅拥有者客户端执行:显示提示。 */
|
||||
UFUNCTION(Client, Reliable)
|
||||
void ClientPushGameplayNotify(EPHYNotifyType Type, const FText& Message, const TSoftObjectPtr<UTexture2D>& SoftIcon);
|
||||
|
||||
FGameplayTag GetReTargeterTag() const { return ReTargeterTag; }
|
||||
|
||||
FORCEINLINE int32 GetPlayerLevel() const { return Level; }
|
||||
@@ -134,4 +148,10 @@ private:
|
||||
|
||||
/** 仅本地:推送“获得物品”提示(ItemName x Count) */
|
||||
void PushGainedItemNotify(const FText& ItemName, int32 Count);
|
||||
|
||||
/** 服务端:把道具属性按符号(+1/-1)叠加到角色属性。 */
|
||||
void ApplyItemAttributesWithSign(UGIS_ItemInstance* Item, float Sign);
|
||||
|
||||
/** 已生效的装备道具,避免重复叠加。 */
|
||||
TSet<FGuid> AppliedEquipmentItems;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2026 PHY. All Rights Reserved.
|
||||
|
||||
#include "GameplayTags/PHYInventoryItemTags.h"
|
||||
|
||||
namespace PHYInventoryItemTags
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_Type_Weapon, "GIS.Item.Type.Weapon", "Weapon item category");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_Type_Equipment, "GIS.Item.Type.Equipment", "Equipment item category");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_Type_Consumable, "GIS.Item.Type.Consumable", "Consumable item category");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_Type_Material, "GIS.Item.Type.Material", "Material item category");
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_MainHand, "GIS.Item.EquipSlot.MainHand", "Main hand slot");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_OffHand, "GIS.Item.EquipSlot.OffHand", "Off hand slot");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_Head, "GIS.Item.EquipSlot.Head", "Head slot");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_Chest, "GIS.Item.EquipSlot.Chest", "Chest slot");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_Legs, "GIS.Item.EquipSlot.Legs", "Legs slot");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_Feet, "GIS.Item.EquipSlot.Feet", "Feet slot");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Item_EquipSlot_Accessory, "GIS.Item.EquipSlot.Accessory", "Accessory slot");
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_Attack, "GIS.Attribute.Item.Attack", "Item Attack bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_Defense, "GIS.Attribute.Item.Defense", "Item Defense bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_CritRate, "GIS.Attribute.Item.CritRate", "Item CritRate bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_CritDamage, "GIS.Attribute.Item.CritDamage", "Item CritDamage bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_Hit, "GIS.Attribute.Item.Hit", "Item Hit bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_Dodge, "GIS.Attribute.Item.Dodge", "Item Dodge bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_Parry, "GIS.Attribute.Item.Parry", "Item Parry bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_ArmorPenetration, "GIS.Attribute.Item.ArmorPenetration", "Item ArmorPenetration bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_DamageReduction, "GIS.Attribute.Item.DamageReduction", "Item DamageReduction bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_LifeSteal, "GIS.Attribute.Item.LifeSteal", "Item LifeSteal bonus");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Tag__GIS_Attribute_Item_Resilience, "GIS.Attribute.Item.Resilience", "Item Resilience bonus");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2026 PHY. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
namespace PHYInventoryItemTags
|
||||
{
|
||||
// Item category tags
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_Type_Weapon);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_Type_Equipment);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_Type_Consumable);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_Type_Material);
|
||||
|
||||
// Equipment slot tags
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_MainHand);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_OffHand);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_Head);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_Chest);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_Legs);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_Feet);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Item_EquipSlot_Accessory);
|
||||
|
||||
// Common item attributes used by this project
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_Attack);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_Defense);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_CritRate);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_CritDamage);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_Hit);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_Dodge);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_Parry);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_ArmorPenetration);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_DamageReduction);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_LifeSteal);
|
||||
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__GIS_Attribute_Item_Resilience);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2026 PHY. All Rights Reserved.
|
||||
|
||||
#include "Items/PHYItemBlueprintLibrary.h"
|
||||
|
||||
#include "GIS_ItemInstance.h"
|
||||
|
||||
const UPHYItemFragment_PropertySet* UPHYItemBlueprintLibrary::FindPropertyFragment(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
if (!IsValid(ItemInstance))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return ItemInstance->FindFragmentByClass<UPHYItemFragment_PropertySet>();
|
||||
}
|
||||
|
||||
EPHYItemArchetype UPHYItemBlueprintLibrary::GetItemArchetype(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
if (const UPHYItemFragment_PropertySet* Fragment = FindPropertyFragment(ItemInstance))
|
||||
{
|
||||
return Fragment->ItemArchetype;
|
||||
}
|
||||
|
||||
return EPHYItemArchetype::Material;
|
||||
}
|
||||
|
||||
EPHYEquipSlotType UPHYItemBlueprintLibrary::GetEquipSlotType(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
if (const UPHYItemFragment_PropertySet* Fragment = FindPropertyFragment(ItemInstance))
|
||||
{
|
||||
return Fragment->EquipSlot;
|
||||
}
|
||||
|
||||
return EPHYEquipSlotType::None;
|
||||
}
|
||||
|
||||
bool UPHYItemBlueprintLibrary::IsWeaponItem(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
return GetItemArchetype(ItemInstance) == EPHYItemArchetype::Weapon;
|
||||
}
|
||||
|
||||
bool UPHYItemBlueprintLibrary::IsEquipmentItem(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
const EPHYItemArchetype Archetype = GetItemArchetype(ItemInstance);
|
||||
return Archetype == EPHYItemArchetype::Weapon || Archetype == EPHYItemArchetype::Equipment;
|
||||
}
|
||||
|
||||
bool UPHYItemBlueprintLibrary::IsConsumableItem(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
return GetItemArchetype(ItemInstance) == EPHYItemArchetype::Consumable;
|
||||
}
|
||||
|
||||
FPHYConsumablePayload UPHYItemBlueprintLibrary::GetConsumablePayload(const UGIS_ItemInstance* ItemInstance)
|
||||
{
|
||||
if (const UPHYItemFragment_PropertySet* Fragment = FindPropertyFragment(ItemInstance))
|
||||
{
|
||||
return Fragment->ConsumablePayload;
|
||||
}
|
||||
|
||||
return FPHYConsumablePayload();
|
||||
}
|
||||
|
||||
39
Source/PHYInventory/Private/Items/PHYItemBlueprintLibrary.h
Normal file
39
Source/PHYInventory/Private/Items/PHYItemBlueprintLibrary.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2026 PHY. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Items/PHYItemFragment_PropertySet.h"
|
||||
#include "PHYItemBlueprintLibrary.generated.h"
|
||||
|
||||
class UGIS_ItemInstance;
|
||||
|
||||
UCLASS()
|
||||
class UPHYItemBlueprintLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
static EPHYItemArchetype GetItemArchetype(const UGIS_ItemInstance* ItemInstance);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
static EPHYEquipSlotType GetEquipSlotType(const UGIS_ItemInstance* ItemInstance);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
static bool IsWeaponItem(const UGIS_ItemInstance* ItemInstance);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
static bool IsEquipmentItem(const UGIS_ItemInstance* ItemInstance);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
static bool IsConsumableItem(const UGIS_ItemInstance* ItemInstance);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
static FPHYConsumablePayload GetConsumablePayload(const UGIS_ItemInstance* ItemInstance);
|
||||
|
||||
private:
|
||||
static const UPHYItemFragment_PropertySet* FindPropertyFragment(const UGIS_ItemInstance* ItemInstance);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2026 PHY. All Rights Reserved.
|
||||
|
||||
#include "Items/PHYItemFragment_PropertySet.h"
|
||||
|
||||
#include "GIS_ItemInstance.h"
|
||||
|
||||
void UPHYItemFragment_PropertySet::OnInstanceCreated(UGIS_ItemInstance* ItemInstance) const
|
||||
{
|
||||
if (!IsValid(ItemInstance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const FGIS_GameplayTagFloat& Modifier : BaseFloatModifiers)
|
||||
{
|
||||
if (Modifier.Tag.IsValid())
|
||||
{
|
||||
ItemInstance->SetFloatAttribute(Modifier.Tag, Modifier.Value);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FGIS_GameplayTagInteger& Modifier : BaseIntegerModifiers)
|
||||
{
|
||||
if (Modifier.Tag.IsValid())
|
||||
{
|
||||
ItemInstance->SetIntegerAttribute(Modifier.Tag, Modifier.Value);
|
||||
}
|
||||
}
|
||||
|
||||
Super::OnInstanceCreated(ItemInstance);
|
||||
}
|
||||
|
||||
bool UPHYItemFragment_PropertySet::IsEquippable() const
|
||||
{
|
||||
return ItemArchetype == EPHYItemArchetype::Weapon || ItemArchetype == EPHYItemArchetype::Equipment;
|
||||
}
|
||||
|
||||
bool UPHYItemFragment_PropertySet::IsConsumable() const
|
||||
{
|
||||
return ItemArchetype == EPHYItemArchetype::Consumable;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2026 PHY. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_GameplayTagFloat.h"
|
||||
#include "GIS_GameplayTagInteger.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "PHYItemFragment_PropertySet.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EPHYItemArchetype : uint8
|
||||
{
|
||||
Weapon,
|
||||
Equipment,
|
||||
Consumable,
|
||||
Material,
|
||||
Quest
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EPHYEquipSlotType : uint8
|
||||
{
|
||||
None,
|
||||
MainHand,
|
||||
OffHand,
|
||||
Head,
|
||||
Chest,
|
||||
Legs,
|
||||
Feet,
|
||||
Accessory
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FPHYConsumablePayload
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Consumable")
|
||||
float RestoreHealth = 0.0f;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Consumable")
|
||||
float RestoreInnerPower = 0.0f;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Consumable")
|
||||
float DurationSeconds = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* 统一道具属性片段:
|
||||
* - 定义道具大类(武器/装备/药品...)
|
||||
* - 定义装备槽位
|
||||
* - 定义创建实例时写入的动态属性(可用于随机词条前的基础值)
|
||||
* - 定义药品基础效果载荷
|
||||
*/
|
||||
UCLASS(DisplayName="PHY Item Property Settings", Category="PHY")
|
||||
class UPHYItemFragment_PropertySet : public UGIS_ItemFragment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Item")
|
||||
EPHYItemArchetype ItemArchetype = EPHYItemArchetype::Material;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Item")
|
||||
EPHYEquipSlotType EquipSlot = EPHYEquipSlotType::None;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Item", meta=(Categories="GIS.Item"))
|
||||
FGameplayTagContainer ExtraItemTags;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Attributes", meta=(TitleProperty="{Tag} -> {Value}", Categories="GIS.Attribute"))
|
||||
TArray<FGIS_GameplayTagFloat> BaseFloatModifiers;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Attributes", meta=(TitleProperty="{Tag} -> {Value}", Categories="GIS.Attribute"))
|
||||
TArray<FGIS_GameplayTagInteger> BaseIntegerModifiers;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Consumable")
|
||||
FPHYConsumablePayload ConsumablePayload;
|
||||
|
||||
virtual void OnInstanceCreated(UGIS_ItemInstance* ItemInstance) const override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
bool IsEquippable() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Item")
|
||||
bool IsConsumable() const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user