// 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 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& 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 PhysicsHandle = nullptr; void UpdatePhysicsCarryTarget(); };