// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "Interaction/GGS_InteractionSystemComponent.h" #include "Engine/World.h" #include "GameplayBehaviorSmartObjectBehaviorDefinition.h" #include "GGS_LogChannels.h" #include "SmartObjectComponent.h" #include "SmartObjectSubsystem.h" #include "Interaction/GGS_InteractableInterface.h" #include "Interaction/GGS_SmartObjectFunctionLibrary.h" #include "Interaction/GGS_InteractionStructLibrary.h" #include "Net/UnrealNetwork.h" #include "Net/Core/PushModel/PushModel.h" // Sets default values for this component's properties UGGS_InteractionSystemComponent::UGGS_InteractionSystemComponent() { PrimaryComponentTick.bCanEverTick = false; SetIsReplicatedByDefault(true); } void UGGS_InteractionSystemComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); FDoRepLifetimeParams Params; Params.bIsPushBased = true; Params.Condition = COND_OwnerOnly; DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InteractableActor, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, NumsOfInteractableActors, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InteractingOption, Params); DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, InteractionOptions, Params); } UGGS_InteractionSystemComponent* UGGS_InteractionSystemComponent::GetInteractionSystemComponent(const AActor* Actor) { return IsValid(Actor) ? Actor->FindComponentByClass() : nullptr; } void UGGS_InteractionSystemComponent::CycleInteractableActors_Implementation(bool bNext) { if (bInteracting || InteractableActors.Num() <= 1) { return; } int32 Index = InteractableActor != nullptr ? InteractableActors.IndexOfByKey(InteractableActor) : 0; if (!InteractableActors.IsValidIndex(Index)) //一个都没 { return; } if (bNext) { Index = FMath::Clamp(Index + 1, 0, InteractableActors.Num()); } else { Index = FMath::Clamp(Index - 1, 0, InteractableActors.Num()); } if (InteractableActors.IsValidIndex(Index) && InteractableActors[Index] != nullptr && InteractableActors[Index] != InteractableActor) { SetInteractableActor(InteractableActors[Index]); } } void UGGS_InteractionSystemComponent::SearchInteractableActors() { OnSearchInteractableActorsEvent.Broadcast(); } void UGGS_InteractionSystemComponent::SetInteractableActors(TArray NewActors) { if (!GetOwner()->HasAuthority()) { return; } InteractableActors = NewActors; SetInteractableActorsNum(InteractableActors.Num()); OnInteractableActorsChanged(); } void UGGS_InteractionSystemComponent::SetInteractableActorsNum(int32 NewNum) { if (NewNum != NumsOfInteractableActors) { const int32 PrevNum = NumsOfInteractableActors; NumsOfInteractableActors = NewNum; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, NumsOfInteractableActors, this) OnInteractableActorsNumChanged(); } } void UGGS_InteractionSystemComponent::SetInteractableActor(AActor* InActor) { if (InActor != InteractableActor) { AActor* OldActor = InteractableActor; InteractableActor = InActor; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractableActor, this) OnInteractableActorChanged(OldActor); } } FSmartObjectRequestFilter UGGS_InteractionSystemComponent::GetSmartObjectRequestFilter_Implementation() { return DefaultRequestFilter; } void UGGS_InteractionSystemComponent::StartInteraction(int32 NewIndex) { if (bInteracting) { GGS_CLOG(Warning, "Can't start interaction(%d) while already interacting(%d)", NewIndex, InteractingOption) return; } if (!InteractionOptions.IsValidIndex(NewIndex)) { GGS_CLOG(Warning, "Try start invalid interaction(%d)", NewIndex) return; } int32 Prev = InteractingOption; InteractingOption = NewIndex; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this); OnInteractingOptionChanged(Prev); } void UGGS_InteractionSystemComponent::EndInteraction() { if (!bInteracting) { //GGS_CLOG(Warning, TEXT("no need to end interaction when there's no any active interaction.")) return; } int32 Prev = InteractingOption; InteractingOption = INDEX_NONE; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this); OnInteractingOptionChanged(Prev); } void UGGS_InteractionSystemComponent::InstantInteraction(int32 NewIndex) { if (bInteracting) { GGS_CLOG(Warning, "Can't trigger instant interaction(%d) while already interacting(%d)", NewIndex, InteractingOption) return; } if (!InteractionOptions.IsValidIndex(NewIndex)) { GGS_CLOG(Warning, "Try trigger invalid interaction(%d)", NewIndex) return; } int32 Prev = InteractingOption; InteractingOption = NewIndex; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this); OnInteractingOptionChanged(Prev); int32 Prev2 = InteractingOption; InteractingOption = INDEX_NONE; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractingOption, this); OnInteractingOptionChanged(Prev2); } bool UGGS_InteractionSystemComponent::IsInteracting() const { return bInteracting; } int32 UGGS_InteractionSystemComponent::GetInteractingOption() const { return InteractingOption; } void UGGS_InteractionSystemComponent::OnInteractableActorChanged(AActor* OldActor) { if (GetOwner()->GetLocalRole() >= ROLE_Authority) { RefreshOptionsForActor(); } if (IsValid(OldActor) && OldActor->GetClass()->ImplementsInterface(UGGS_InteractableInterface::StaticClass())) { IGGS_InteractableInterface::Execute_OnInteractionDeselected(OldActor, GetOwner()); } if (IsValid(InteractableActor) && InteractableActor->GetClass()->ImplementsInterface(UGGS_InteractableInterface::StaticClass())) { IGGS_InteractableInterface::Execute_OnInteractionSelected(InteractableActor, GetOwner()); } OnInteractableActorChangedEvent.Broadcast(OldActor, InteractableActor); } void UGGS_InteractionSystemComponent::OnInteractableActorsNumChanged() { OnInteractableActorNumChangedEvent.Broadcast(NumsOfInteractableActors); } void UGGS_InteractionSystemComponent::OnInteractableActorsChanged_Implementation() { if (!bInteracting) { // update potential actor. if (!IsValid(InteractableActor) || !InteractableActors.Contains(InteractableActor)) { if (InteractableActors.IsValidIndex(0) && IsValid(InteractableActors[0])) { SetInteractableActor(InteractableActors[0]); } else { SetInteractableActor(nullptr); } } if (bNewActorHasPriority) { if (IsValid(InteractableActor) && InteractableActors.IsValidIndex(0) && InteractableActors[0] != InteractableActor) { SetInteractableActor(InteractableActors[0]); } } } } void UGGS_InteractionSystemComponent::OnSmartObjectEventCallback(const FSmartObjectEventData& EventData) { check(InteractableActor != nullptr); for (int32 i = 0; i < InteractionOptions.Num(); i++) { const FGGS_InteractionOption& Option = InteractionOptions[i]; if (EventData.SmartObjectHandle == Option.RequestResult.SmartObjectHandle && EventData.SlotHandle == Option.RequestResult.SlotHandle) { if (EventData.Reason == ESmartObjectChangeReason::OnOccupied || EventData.Reason == ESmartObjectChangeReason::OnReleased || EventData.Reason == ESmartObjectChangeReason::OnClaimed) { RefreshOptionsForActor(); } } } } void UGGS_InteractionSystemComponent::OnInteractionOptionsChanged() { for (FGGS_InteractionOption& InteractionOption : InteractionOptions) { GGS_CLOG(Verbose, "Available Options:%s", *InteractionOption.ToString()) } OnInteractionOptionsChangedEvent.Broadcast(); } void UGGS_InteractionSystemComponent::OnInteractingOptionChanged_Implementation(int32 PrevOptionIndex) { bool bPrevInteracting = bInteracting; bInteracting = InteractingOption != INDEX_NONE; if (IsValid(InteractableActor) && InteractableActor->GetClass()->ImplementsInterface(UGGS_InteractableInterface::StaticClass())) { if (!bPrevInteracting && bInteracting) { IGGS_InteractableInterface::Execute_OnInteractionStarted(InteractableActor, GetOwner(), InteractingOption); } if (bPrevInteracting && !bInteracting) { IGGS_InteractableInterface::Execute_OnInteractionEnded(InteractableActor, GetOwner(), PrevOptionIndex); } } OnInteractingStateChangedEvent.Broadcast(bInteracting); } void UGGS_InteractionSystemComponent::RefreshOptionsForActor() { USmartObjectSubsystem* Subsystem = USmartObjectSubsystem::GetCurrent(GetWorld()); if (!Subsystem) { return; } // getting new options for current interact actor. TArray NewOptions; { TArray Results; if (IsValid(InteractableActor) && UGGS_SmartObjectFunctionLibrary::FindSmartObjectsWithInteractionEntranceInActor(GetSmartObjectRequestFilter(), InteractableActor, Results, GetOwner())) { for (int32 i = 0; i < Results.Num(); i++) { FGGS_InteractionOption Option; UGGS_InteractionDefinition* FoundDefinition; if (UGGS_SmartObjectFunctionLibrary::FindInteractionDefinitionFromSmartObjectSlot(this, Results[i].SlotHandle, FoundDefinition)) { Option.Definition = FoundDefinition; Option.SlotState = Subsystem->GetSlotState(Results[i].SlotHandle); Option.RequestResult = Results[i]; Option.SlotIndex = i; Option.BehaviorDefinition = Subsystem->GetBehaviorDefinitionByRequestResult(Results[i], USmartObjectBehaviorDefinition::StaticClass()); NewOptions.Add(Option); } } } } // check any options changed. bool bOptionsChanged = false; { if (NewOptions.Num() == InteractionOptions.Num()) { NewOptions.Sort(); for (int OptionIndex = 0; OptionIndex < NewOptions.Num(); OptionIndex++) { const FGGS_InteractionOption& NewOption = NewOptions[OptionIndex]; const FGGS_InteractionOption& CurrentOption = InteractionOptions[OptionIndex]; if (NewOption != CurrentOption) { bOptionsChanged = true; break; } } } else { bOptionsChanged = true; } } if (bOptionsChanged) { // unregister event callbacks for existing options. for (int32 i = 0; i < InteractionOptions.Num(); i++) { auto& Handle = InteractionOptions[i].RequestResult.SlotHandle; if (SlotCallbacks.Contains(Handle)) { if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Handle)) { OnEventDelegate->Remove(SlotCallbacks[Handle]); SlotCallbacks.Remove(Handle); } } } for (FGGS_InteractionOption& Option : InteractionOptions) { if (SlotCallbacks.Contains(Option.RequestResult.SlotHandle)) { if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Option.RequestResult.SlotHandle)) { OnEventDelegate->Remove(SlotCallbacks[Option.RequestResult.SlotHandle]); SlotCallbacks.Remove(Option.RequestResult.SlotHandle); } } // if (Option.DelegateHandle.IsValid()) // { // if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Option.RequestResult.SlotHandle)) // { // OnEventDelegate->Remove(Option.DelegateHandle); // Option.DelegateHandle.Reset(); // } // } } InteractionOptions = NewOptions; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, InteractionOptions, this) GGS_CLOG(Verbose, "Interaction options changed, nums of options:%d", InteractionOptions.Num()) // register slot event callbacks. // for (int32 i = 0; i < InteractionOptions.Num(); i++) // { // FGGS_InteractionOption& Option = InteractionOptions[i]; // if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Option.RequestResult.SlotHandle)) // { // Option.DelegateHandle = OnEventDelegate->AddUObject(this, &ThisClass::OnSmartObjectEventCallback); // } // } for (int32 i = 0; i < InteractionOptions.Num(); i++) { auto& Handle = InteractionOptions[i].RequestResult.SlotHandle; if (FOnSmartObjectEvent* OnEventDelegate = Subsystem->GetSlotEventDelegate(Handle)) { FDelegateHandle DelegateHandle = OnEventDelegate->AddUObject(this, &ThisClass::OnSmartObjectEventCallback); SlotCallbacks.Emplace(Handle, DelegateHandle); } } OnInteractionOptionsChanged(); } }