Files
inventory-system-plugin/Source/ItemWorld/Public/HeldItemComponent.h
Bonchellon 7f7e043a88 Initial commit: Item Interaction Ecosystem plugin (UE5.7)
Server-authoritative, data-driven item/inventory/interaction/carry/equipment/
world-slot/placement/crafting framework. 8 modules, compiles against UE 5.7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:49:56 +03:00

152 lines
5.0 KiB
C++

// Copyright ExByte Studios. Item Interaction Ecosystem.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "ItemEnums.h"
#include "ItemInstanceData.h"
#include "ItemInteractionInterfaces.h"
#include "HeldItemComponent.generated.h"
class APickupItemActor;
/** Replicated snapshot of what the character is currently holding (section 11.1). */
USTRUCT(BlueprintType)
struct ITEMWORLD_API FHeldItemState
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "Held")
bool bHasHeldItem = false;
UPROPERTY(BlueprintReadOnly, Category = "Held")
FGuid InstanceId;
UPROPERTY(BlueprintReadOnly, Category = "Held")
FName ItemId;
UPROPERTY(BlueprintReadOnly, Category = "Held")
EHeldItemMode Mode = EHeldItemMode::None;
UPROPERTY(BlueprintReadOnly, Category = "Held")
TObjectPtr<AActor> HeldWorldActor = nullptr;
UPROPERTY(BlueprintReadOnly, Category = "Held")
FName HoldSocket = NAME_None;
UPROPERTY(BlueprintReadOnly, Category = "Held")
float MoveSpeedMultiplier = 1.f;
UPROPERTY(BlueprintReadOnly, Category = "Held")
bool bBlocksSprint = false;
UPROPERTY(BlueprintReadOnly, Category = "Held")
bool bBlocksClimb = false;
/** Snapshot of the carried item (so we can drop/install it without the actor). */
UPROPERTY(BlueprintReadOnly, Category = "Held")
FItemInstanceData Item;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHeldItemChanged, const FHeldItemState&, NewState);
/**
* Physically carries one bulky item attached to the character (section 11.2-11.3).
*
* This is the "two-handed carry" path for batteries, canisters, wheels, crates,
* corpses, etc. - distinct from equipment (which keeps the item in the inventory
* and only spawns a visual). Server-authoritative; visuals/penalties apply on all
* clients via OnRep. Implements IHeldItemInterface so the resolver can build
* held+target interactions.
*/
UCLASS(ClassGroup = (Inventory), meta = (BlueprintSpawnableComponent))
class ITEMWORLD_API UHeldItemComponent : public UActorComponent, public IHeldItemInterface
{
GENERATED_BODY()
public:
UHeldItemComponent();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held")
FName CarrySocket = FName("CarrySocket");
/** Hold distance in front of the view for physics-handle carry (section 11.4). */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held")
float PhysicsHoldDistance = 200.f;
UPROPERTY(BlueprintAssignable, Category = "Held")
FOnHeldItemChanged OnHeldItemChanged;
UFUNCTION(BlueprintPure, Category = "Held")
bool HasHeldItemBP() const { return HeldItem.bHasHeldItem; }
UFUNCTION(BlueprintPure, Category = "Held")
const FHeldItemState& GetHeldItemState() const { return HeldItem; }
UFUNCTION(BlueprintPure, Category = "Held")
bool CanHoldItem(const FItemInstanceData& Item) const;
/** Client entry: ask the server to drop the carried item (bind to the Drop input). */
UFUNCTION(BlueprintCallable, Category = "Held")
void RequestDrop();
// --- Server entry points (section 11.1) ---
/** Server: pick up and carry a world item actor attached to a socket (World -> Held). */
bool ServerHoldWorldItem(AActor* WorldItem);
/** Server: carry a world item via a physics handle instead of a fixed socket (section 11.4). */
bool ServerHoldWorldItemPhysics(AActor* WorldItem);
/** Server: drop the carried item back into the world (Held -> World). */
bool ServerDropHeldItem();
/** Server: relinquish the carried item without spawning a drop (e.g. on install). */
bool ServerConsumeHeldItem(FItemInstanceData& OutItem);
/**
* Server: hand off the carried item AND its actor to a new owner (e.g. a world
* slot) without destroying the actor. Clears the held state. Returns false if
* nothing was held.
*/
bool ServerHandOffHeldItem(FItemInstanceData& OutItem, AActor*& OutActor);
/** Server: update the carried item's data in place (e.g. after pouring out fuel). */
bool ServerUpdateHeldItem(const FItemInstanceData& NewItem);
//~ IHeldItemInterface
virtual bool GetHeldItem(FItemInstanceData& OutItem) const override;
virtual bool HasHeldItem() const override { return HeldItem.bHasHeldItem; }
protected:
UPROPERTY(ReplicatedUsing = OnRep_HeldItem)
FHeldItemState HeldItem;
UFUNCTION(Server, Reliable)
void Server_Drop();
UFUNCTION()
void OnRep_HeldItem(FHeldItemState OldState);
/** Applies/removes the movement-speed penalty on the owning character. */
void ApplyMovementState();
bool HasAuthority() const;
private:
/** Owning character's unmodified MaxWalkSpeed, captured the first time we modify it. */
UPROPERTY(Transient)
float CachedBaseWalkSpeed = 0.f;
/** Created on demand for physics-handle carry. */
UPROPERTY(Transient)
TObjectPtr<class UPhysicsHandleComponent> PhysicsHandle = nullptr;
void UpdatePhysicsCarryTarget();
};