974 lines
35 KiB
C++
974 lines
35 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Grippables/GrippableStaticMeshActor.h"
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(GrippableStaticMeshActor)
|
|
|
|
#include "TimerManager.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "GameFramework/PlayerState.h"
|
|
#include "GripMotionControllerComponent.h"
|
|
#include "VRExpansionFunctionLibrary.h"
|
|
#include "Misc/BucketUpdateSubsystem.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "PhysicsReplication.h"
|
|
#include "GripScripts/VRGripScriptBase.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "Physics/Experimental/PhysScene_Chaos.h"
|
|
|
|
#if WITH_PUSH_MODEL
|
|
#include "Net/Core/PushModel/PushModel.h"
|
|
#endif
|
|
|
|
// #TODO: Pull request this? This macro could be very useful
|
|
/*#define DOREPLIFETIME_CHANGE_NOTIFY(c,v,rncond) \
|
|
{ \
|
|
static UProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(),GET_MEMBER_NAME_CHECKED(c,v)); \
|
|
bool bFound = false; \
|
|
for ( int32 i = 0; i < OutLifetimeProps.Num(); i++ ) \
|
|
{ \
|
|
if ( OutLifetimeProps[i].RepIndex == sp##v->RepIndex ) \
|
|
{ \
|
|
for ( int32 j = 0; j < sp##v->ArrayDim; j++ ) \
|
|
{ \
|
|
OutLifetimeProps[i + j].RepNotifyCondition = rncond; \
|
|
} \
|
|
bFound = true; \
|
|
break; \
|
|
} \
|
|
} \
|
|
check( bFound ); \
|
|
}*/
|
|
|
|
|
|
UOptionalRepStaticMeshComponent::UOptionalRepStaticMeshComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
bReplicateMovement = true;
|
|
}
|
|
|
|
void UOptionalRepStaticMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker)
|
|
{
|
|
Super::PreReplication(ChangedPropertyTracker);
|
|
|
|
// Don't replicate if set to not do it
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeLocation, bReplicateMovement);
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeRotation, bReplicateMovement);
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(USceneComponent, RelativeScale3D, bReplicateMovement);
|
|
|
|
|
|
}
|
|
|
|
void UOptionalRepStaticMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
DOREPLIFETIME(UOptionalRepStaticMeshComponent, bReplicateMovement);
|
|
}
|
|
|
|
//=============================================================================
|
|
AGrippableStaticMeshActor::AGrippableStaticMeshActor(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer.SetDefaultSubobjectClass<UOptionalRepStaticMeshComponent>(TEXT("StaticMeshComponent0")))
|
|
{
|
|
VRGripInterfaceSettings.bDenyGripping = false;
|
|
VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents;
|
|
VRGripInterfaceSettings.bSimulateOnDrop = true;
|
|
VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics;
|
|
VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics;
|
|
VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None;
|
|
VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement;
|
|
VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping;
|
|
VRGripInterfaceSettings.ConstraintStiffness = 1500.0f;
|
|
VRGripInterfaceSettings.ConstraintDamping = 200.0f;
|
|
VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f;
|
|
VRGripInterfaceSettings.SecondarySlotRange = 20.0f;
|
|
VRGripInterfaceSettings.PrimarySlotRange = 20.0f;
|
|
|
|
VRGripInterfaceSettings.bIsHeld = false;
|
|
|
|
this->SetMobility(EComponentMobility::Movable);
|
|
|
|
// Default replication on for multiplayer
|
|
//this->bNetLoadOnClient = false;
|
|
SetReplicatingMovement(true);
|
|
this->bReplicates = true;
|
|
|
|
bRepGripSettingsAndGameplayTags = true;
|
|
bReplicateGripScripts = false;
|
|
|
|
// #TODO we can register them maybe in the future
|
|
// Don't use the replicated list, use our custom replication instead
|
|
bReplicateUsingRegisteredSubObjectList = false;
|
|
|
|
bAllowIgnoringAttachOnOwner = true;
|
|
|
|
// Setting a minimum of every 3rd frame (VR 90fps) for replication consideration
|
|
// Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default
|
|
MinNetUpdateFrequency = 30.0f;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const
|
|
{
|
|
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
|
|
|
// For std properties
|
|
FDoRepLifetimeParams PushModelParams{ COND_None, REPNOTIFY_OnChanged, /*bIsPushBased=*/true };
|
|
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, bReplicateGripScripts, PushModelParams);
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, bRepGripSettingsAndGameplayTags, PushModelParams);
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, bAllowIgnoringAttachOnOwner, PushModelParams);
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, ClientAuthReplicationData, PushModelParams);
|
|
|
|
// For properties with special conditions
|
|
FDoRepLifetimeParams PushModelParamsWithCondition{ COND_Custom, REPNOTIFY_OnChanged, /*bIsPushBased=*/true };
|
|
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, GripLogicScripts, PushModelParamsWithCondition);
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, VRGripInterfaceSettings, PushModelParamsWithCondition);
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, GameplayTags, PushModelParamsWithCondition);
|
|
|
|
DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication);
|
|
|
|
FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true };
|
|
DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, AttachmentWeldReplication, AttachmentReplicationParams);
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker)
|
|
{
|
|
//Super::PreReplication(ChangedPropertyTracker);
|
|
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags);
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, GameplayTags, bRepGripSettingsAndGameplayTags);
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, GripLogicScripts, bReplicateGripScripts);
|
|
|
|
//Super::PreReplication(ChangedPropertyTracker);
|
|
|
|
#if WITH_PUSH_MODEL
|
|
const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent;
|
|
const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent;
|
|
#endif
|
|
|
|
// Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment.
|
|
AttachmentWeldReplication.AttachParent = nullptr;
|
|
AttachmentWeldReplication.AttachComponent = nullptr;
|
|
|
|
GatherCurrentMovement();
|
|
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, ReplicatedMovement, IsReplicatingMovement());
|
|
|
|
// Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it.
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AGrippableStaticMeshActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated());
|
|
|
|
// Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it.
|
|
DOREPLIFETIME_ACTIVE_OVERRIDE_FAST(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated());
|
|
|
|
|
|
#if WITH_PUSH_MODEL
|
|
if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent))
|
|
{
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, AttachmentWeldReplication, this);
|
|
}
|
|
#endif
|
|
|
|
/*PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass());
|
|
if (BPClass != nullptr)
|
|
{
|
|
BPClass->InstancePreReplication(this, ChangedPropertyTracker);
|
|
}
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS*/
|
|
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::GatherCurrentMovement()
|
|
{
|
|
if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent()))
|
|
{
|
|
bool bWasAttachmentModified = false;
|
|
bool bWasRepMovementModified = false;
|
|
|
|
AActor* OldAttachParent = AttachmentWeldReplication.AttachParent;
|
|
USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent;
|
|
|
|
AttachmentWeldReplication.AttachParent = nullptr;
|
|
AttachmentWeldReplication.AttachComponent = nullptr;
|
|
|
|
FRepMovement& RepMovement = GetReplicatedMovement_Mutable();
|
|
|
|
UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(GetRootComponent());
|
|
if (RootPrimComp && RootPrimComp->IsSimulatingPhysics())
|
|
{
|
|
#if UE_WITH_IRIS
|
|
const bool bPrevRepPhysics = GetReplicatedMovement_Mutable().bRepPhysics;
|
|
#endif // UE_WITH_IRIS
|
|
|
|
bool bFoundInCache = false;
|
|
|
|
UWorld* World = GetWorld();
|
|
int ServerFrame = 0;
|
|
if (FPhysScene_Chaos* Scene = static_cast<FPhysScene_Chaos*>(World->GetPhysicsScene()))
|
|
{
|
|
if (const FRigidBodyState* FoundState = Scene->GetStateFromReplicationCache(RootPrimComp, ServerFrame))
|
|
{
|
|
RepMovement.FillFrom(*FoundState, this, Scene->ReplicationCache.ServerFrame);
|
|
bFoundInCache = true;
|
|
}
|
|
}
|
|
|
|
if (!bFoundInCache)
|
|
{
|
|
// fallback to GT data
|
|
FRigidBodyState RBState;
|
|
RootPrimComp->GetRigidBodyState(RBState);
|
|
RepMovement.FillFrom(RBState, this, 0);
|
|
}
|
|
|
|
// Don't replicate movement if we're welded to another parent actor.
|
|
// Their replication will affect our position indirectly since we are attached.
|
|
RepMovement.bRepPhysics = !RootPrimComp->IsWelded();
|
|
|
|
if (!RepMovement.bRepPhysics)
|
|
{
|
|
if (RootComponent->GetAttachParent() != nullptr)
|
|
{
|
|
// Networking for attachments assumes the RootComponent of the AttachParent actor.
|
|
// If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result.
|
|
AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor();
|
|
if (AttachmentWeldReplication.AttachParent != nullptr)
|
|
{
|
|
AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation();
|
|
AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation();
|
|
AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D();
|
|
AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent();
|
|
AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName();
|
|
AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false;
|
|
|
|
// Technically, the values might have stayed the same, but we'll just assume they've changed.
|
|
bWasAttachmentModified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Technically, the values might have stayed the same, but we'll just assume they've changed.
|
|
bWasRepMovementModified = true;
|
|
|
|
#if UE_WITH_IRIS
|
|
// If RepPhysics has changed value then notify the ReplicationSystem
|
|
if (bPrevRepPhysics != GetReplicatedMovement_Mutable().bRepPhysics)
|
|
{
|
|
UpdateReplicatePhysicsCondition();
|
|
}
|
|
#endif // UE_WITH_IRIS
|
|
}
|
|
else if (RootComponent != nullptr)
|
|
{
|
|
// If we are attached, don't replicate absolute position, use AttachmentReplication instead.
|
|
if (RootComponent->GetAttachParent() != nullptr)
|
|
{
|
|
// Networking for attachments assumes the RootComponent of the AttachParent actor.
|
|
// If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result.
|
|
AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParentActor();
|
|
if (AttachmentWeldReplication.AttachParent != nullptr)
|
|
{
|
|
AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation();
|
|
AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation();
|
|
AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D();
|
|
AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent();
|
|
AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName();
|
|
AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false;
|
|
|
|
// Technically, the values might have stayed the same, but we'll just assume they've changed.
|
|
bWasAttachmentModified = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this);
|
|
RepMovement.Rotation = RootComponent->GetComponentRotation();
|
|
RepMovement.LinearVelocity = GetVelocity();
|
|
RepMovement.AngularVelocity = FVector::ZeroVector;
|
|
|
|
// Technically, the values might have stayed the same, but we'll just assume they've changed.
|
|
bWasRepMovementModified = true;
|
|
}
|
|
|
|
bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics);
|
|
RepMovement.bRepPhysics = false;
|
|
}
|
|
#if WITH_PUSH_MODEL
|
|
if (bWasRepMovementModified)
|
|
{
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this);
|
|
}
|
|
|
|
if (bWasAttachmentModified ||
|
|
OldAttachParent != AttachmentWeldReplication.AttachParent ||
|
|
OldAttachComponent != AttachmentWeldReplication.AttachComponent)
|
|
{
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, AttachmentWeldReplication, this);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags)
|
|
{
|
|
bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
|
|
|
|
if (bReplicateGripScripts && !IsUsingRegisteredSubObjectList())
|
|
{
|
|
for (UVRGripScriptBase* Script : GripLogicScripts)
|
|
{
|
|
if (Script && IsValid(Script))
|
|
{
|
|
WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
return WroteSomething;
|
|
}
|
|
|
|
//=============================================================================
|
|
AGrippableStaticMeshActor::~AGrippableStaticMeshActor()
|
|
{
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::BeginPlay()
|
|
{
|
|
// Call the base class
|
|
Super::BeginPlay();
|
|
|
|
// Call all grip scripts begin play events so they can perform any needed logic
|
|
for (UVRGripScriptBase* Script : GripLogicScripts)
|
|
{
|
|
if (Script)
|
|
{
|
|
Script->BeginPlay(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::SetDenyGripping(bool bDenyGripping)
|
|
{
|
|
VRGripInterfaceSettings.bDenyGripping = bDenyGripping;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::SetGripPriority(int NewGripPriority)
|
|
{
|
|
VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {}
|
|
void AGrippableStaticMeshActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { }
|
|
void AGrippableStaticMeshActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { }
|
|
void AGrippableStaticMeshActor::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {}
|
|
void AGrippableStaticMeshActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {}
|
|
void AGrippableStaticMeshActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); }
|
|
void AGrippableStaticMeshActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); }
|
|
void AGrippableStaticMeshActor::OnUsed_Implementation() {}
|
|
void AGrippableStaticMeshActor::OnEndUsed_Implementation() {}
|
|
void AGrippableStaticMeshActor::OnSecondaryUsed_Implementation() {}
|
|
void AGrippableStaticMeshActor::OnEndSecondaryUsed_Implementation() {}
|
|
void AGrippableStaticMeshActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {}
|
|
bool AGrippableStaticMeshActor::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; }
|
|
|
|
bool AGrippableStaticMeshActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator)
|
|
{
|
|
return VRGripInterfaceSettings.bDenyGripping;
|
|
}
|
|
|
|
|
|
EGripInterfaceTeleportBehavior AGrippableStaticMeshActor::TeleportBehavior_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.OnTeleportBehavior;
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::SimulateOnDrop_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.bSimulateOnDrop;
|
|
}
|
|
|
|
EGripCollisionType AGrippableStaticMeshActor::GetPrimaryGripType_Implementation(bool bIsSlot)
|
|
{
|
|
return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType;
|
|
}
|
|
|
|
ESecondaryGripType AGrippableStaticMeshActor::SecondaryGripType_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.SecondaryGripType;
|
|
}
|
|
|
|
EGripMovementReplicationSettings AGrippableStaticMeshActor::GripMovementReplicationType_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.MovementReplicationType;
|
|
}
|
|
|
|
EGripLateUpdateSettings AGrippableStaticMeshActor::GripLateUpdateSetting_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.LateUpdateSetting;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut)
|
|
{
|
|
GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness;
|
|
GripDampingOut = VRGripInterfaceSettings.ConstraintDamping;
|
|
}
|
|
|
|
FBPAdvGripSettings AGrippableStaticMeshActor::AdvancedGripSettings_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.AdvancedGripSettings;
|
|
}
|
|
|
|
float AGrippableStaticMeshActor::GripBreakDistance_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.ConstraintBreakDistance;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix)
|
|
{
|
|
if (OverridePrefix.IsNone())
|
|
bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP";
|
|
|
|
UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController);
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::AllowsMultipleGrips_Implementation()
|
|
{
|
|
return VRGripInterfaceSettings.bAllowMultipleGrips;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld)
|
|
{
|
|
HoldingControllers = VRGripInterfaceSettings.HoldingControllers;
|
|
bIsHeld = VRGripInterfaceSettings.bIsHeld;
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::AddToClientReplicationBucket()
|
|
{
|
|
if (ShouldWeSkipAttachmentReplication(false))
|
|
{
|
|
// The subsystem automatically removes entries with the same function signature so its safe to just always add here
|
|
GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent")));
|
|
ClientAuthReplicationData.bIsCurrentlyClientAuth = true;
|
|
|
|
if (UWorld * World = GetWorld())
|
|
ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::RemoveFromClientReplicationBucket()
|
|
{
|
|
if (ClientAuthReplicationData.bIsCurrentlyClientAuth)
|
|
{
|
|
GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent")));
|
|
CeaseReplicationBlocking();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed)
|
|
{
|
|
if (bGripped)
|
|
{
|
|
OnGripped.Broadcast(Controller, GripInformation);
|
|
}
|
|
else
|
|
{
|
|
OnDropped.Broadcast(Controller, GripInformation, bWasSocketed);
|
|
}
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld)
|
|
{
|
|
if (bIsHeld)
|
|
{
|
|
VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID));
|
|
RemoveFromClientReplicationBucket();
|
|
|
|
VRGripInterfaceSettings.bWasHeld = true;
|
|
VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0;
|
|
}
|
|
else
|
|
{
|
|
VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID));
|
|
VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0;
|
|
|
|
if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld)
|
|
{
|
|
bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false;
|
|
if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false))
|
|
{
|
|
if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(GetRootComponent()))
|
|
{
|
|
if (PrimComp->IsSimulatingPhysics())
|
|
{
|
|
AddToClientReplicationBucket();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference)
|
|
{
|
|
ArrayReference = GripLogicScripts;
|
|
return GripLogicScripts.Num() > 0;
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::PollReplicationEvent()
|
|
{
|
|
if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld)
|
|
return false; // Tell the bucket subsystem to remove us from consideration
|
|
|
|
UWorld *OurWorld = GetWorld();
|
|
if (!OurWorld)
|
|
return false; // Tell the bucket subsystem to remove us from consideration
|
|
|
|
bool bRemoveBlocking = false;
|
|
|
|
if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f)
|
|
{
|
|
// Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server
|
|
// Authed movement that is forcing it to keep momentum.
|
|
//return false; // Tell the bucket subsystem to remove us from consideration
|
|
bRemoveBlocking = true;
|
|
}
|
|
|
|
// Store current transform for resting check
|
|
FTransform CurTransform = this->GetActorTransform();
|
|
|
|
if (!bRemoveBlocking)
|
|
{
|
|
if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation()))
|
|
{
|
|
ClientAuthReplicationData.LastActorTransform = CurTransform;
|
|
|
|
if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(RootComponent))
|
|
{
|
|
// Need to clamp to a max time since start, to handle cases with conflicting collisions
|
|
if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false))
|
|
{
|
|
FRepMovementVR ClientAuthMovementRep;
|
|
if (ClientAuthMovementRep.GatherActorsMovement(this))
|
|
{
|
|
Server_GetClientAuthReplication(ClientAuthMovementRep);
|
|
|
|
if (PrimComp->RigidBodyIsAwake())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bRemoveBlocking = true;
|
|
//return false; // Tell the bucket subsystem to remove us from consideration
|
|
}
|
|
}
|
|
//else
|
|
// {
|
|
// Difference is too small, lets end sending location
|
|
//ClientAuthReplicationData.LastActorTransform = FTransform::Identity;
|
|
// }
|
|
}
|
|
|
|
bool TimedBlockingRelease = false;
|
|
|
|
AActor* TopOwner = GetOwner();
|
|
if (TopOwner != nullptr)
|
|
{
|
|
AActor * tempOwner = TopOwner->GetOwner();
|
|
|
|
// I have an owner so search that for the top owner
|
|
while (tempOwner)
|
|
{
|
|
TopOwner = tempOwner;
|
|
tempOwner = TopOwner->GetOwner();
|
|
}
|
|
|
|
if (APlayerController* PlayerController = Cast<APlayerController>(TopOwner))
|
|
{
|
|
if (APlayerState* PlayerState = PlayerController->PlayerState)
|
|
{
|
|
if (ClientAuthReplicationData.ResetReplicationHandle.IsValid())
|
|
{
|
|
OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle);
|
|
}
|
|
|
|
// Lets clamp the ping to a min / max value just in case
|
|
float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f);
|
|
OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableStaticMeshActor::CeaseReplicationBlocking, clampedPing, false);
|
|
TimedBlockingRelease = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!TimedBlockingRelease)
|
|
{
|
|
CeaseReplicationBlocking();
|
|
}
|
|
|
|
// Tell server to kill us
|
|
Server_EndClientAuthReplication();
|
|
return false; // Tell the bucket subsystem to remove us from consideration
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::CeaseReplicationBlocking()
|
|
{
|
|
if(ClientAuthReplicationData.bIsCurrentlyClientAuth)
|
|
ClientAuthReplicationData.bIsCurrentlyClientAuth = false;
|
|
|
|
ClientAuthReplicationData.LastActorTransform = FTransform::Identity;
|
|
|
|
if (ClientAuthReplicationData.ResetReplicationHandle.IsValid())
|
|
{
|
|
if (UWorld * OurWorld = GetWorld())
|
|
{
|
|
OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
{
|
|
RemoveFromClientReplicationBucket();
|
|
|
|
// Call all grip scripts begin play events so they can perform any needed logic
|
|
for (UVRGripScriptBase* Script : GripLogicScripts)
|
|
{
|
|
if (Script)
|
|
{
|
|
Script->EndPlay(EndPlayReason);
|
|
}
|
|
}
|
|
|
|
Super::EndPlay(EndPlayReason);
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::Server_EndClientAuthReplication_Validate()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::Server_EndClientAuthReplication_Implementation()
|
|
{
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
if (FPhysScene* PhysScene = World->GetPhysicsScene())
|
|
{
|
|
if (IPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication())
|
|
{
|
|
PhysicsReplication->RemoveReplicatedTarget(this->GetStaticMeshComponent());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR & newMovement)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR & newMovement)
|
|
{
|
|
if (!VRGripInterfaceSettings.bIsHeld)
|
|
{
|
|
if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN())
|
|
{
|
|
FRepMovement& MovementRep = GetReplicatedMovement_Mutable();
|
|
newMovement.CopyTo(MovementRep);
|
|
OnRep_ReplicatedMovement();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AGrippableStaticMeshActor::ShouldWeSkipAttachmentReplication(bool bConsiderHeld) const
|
|
{
|
|
if ((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client)
|
|
return false;
|
|
|
|
if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive ||
|
|
VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep)
|
|
{
|
|
// First return if we are locally held (owner may not have replicated yet)
|
|
for (const FBPGripPair& Grip : VRGripInterfaceSettings.HoldingControllers)
|
|
{
|
|
if (IsValid(Grip.HoldingController) && Grip.HoldingController->IsLocallyControlled())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// then return if we have a local net owner
|
|
return HasLocalNetOwner();
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::OnRep_AttachmentReplication()
|
|
{
|
|
if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication()))
|
|
//if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (AttachmentWeldReplication.AttachParent)
|
|
{
|
|
if (RootComponent)
|
|
{
|
|
USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent());
|
|
|
|
if (AttachParentComponent)
|
|
{
|
|
RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset);
|
|
RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset);
|
|
RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D);
|
|
|
|
// If we're already attached to the correct Parent and Socket, then the update must be position only.
|
|
// AttachToComponent would early out in this case.
|
|
// Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated
|
|
// if the body is simulated (see AActor::GatherMovement).
|
|
const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent));
|
|
if (bAlreadyAttached)
|
|
{
|
|
// Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above).
|
|
RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None);
|
|
}
|
|
else
|
|
{
|
|
FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform;
|
|
attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded;
|
|
RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
|
|
|
|
// Handle the case where an object was both detached and moved on the server in the same frame.
|
|
// Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached.
|
|
// This is needed because client side movement is ignored when attached
|
|
if (IsReplicatingMovement())
|
|
{
|
|
OnRep_ReplicatedMovement();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::OnRep_ReplicateMovement()
|
|
{
|
|
if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication()))
|
|
//if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (RootComponent)
|
|
{
|
|
const FRepAttachment ReplicationAttachment = GetAttachmentReplication();
|
|
if (!ReplicationAttachment.AttachParent)
|
|
{
|
|
const FRepMovement& RepMove = GetReplicatedMovement();
|
|
|
|
// This "fix" corrects the simulation state not replicating over correctly
|
|
// If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference
|
|
// This change ensures that it is checking against the current state
|
|
if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics)
|
|
{
|
|
// Turn on/off physics sim to match server.
|
|
SyncReplicatedPhysicsSimulation();
|
|
|
|
// It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match
|
|
}
|
|
}
|
|
}
|
|
|
|
Super::OnRep_ReplicateMovement();
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::OnRep_ReplicatedMovement()
|
|
{
|
|
if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication()))
|
|
//if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*if (VRGripInterfaceSettings.HoldingControllers.Num() > 0)
|
|
{
|
|
ShouldWeSkipAttachmentReplication();
|
|
int gg = 0;
|
|
}*/
|
|
|
|
Super::OnRep_ReplicatedMovement();
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::PostNetReceivePhysicState()
|
|
{
|
|
if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication()))
|
|
//if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Super::PostNetReceivePhysicState();
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::MarkComponentsAsGarbage(bool bModify)
|
|
{
|
|
Super::MarkComponentsAsGarbage(bModify);
|
|
|
|
for (int32 i = 0; i < GripLogicScripts.Num(); ++i)
|
|
{
|
|
if (UObject *SubObject = GripLogicScripts[i])
|
|
{
|
|
SubObject->MarkAsGarbage();
|
|
}
|
|
}
|
|
|
|
GripLogicScripts.Empty();
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::PreDestroyFromReplication()
|
|
{
|
|
Super::PreDestroyFromReplication();
|
|
|
|
// Destroy any sub-objects we created
|
|
for (int32 i = 0; i < GripLogicScripts.Num(); ++i)
|
|
{
|
|
if (UObject *SubObject = GripLogicScripts[i])
|
|
{
|
|
OnSubobjectDestroyFromReplication(SubObject); //-V595
|
|
SubObject->PreDestroyFromReplication();
|
|
SubObject->MarkAsGarbage();
|
|
}
|
|
}
|
|
|
|
for (UActorComponent * ActorComp : GetComponents())
|
|
{
|
|
// Pending kill components should have already had this called as they were network spawned and are being killed
|
|
// We only call this on our interfaced components since they are the only ones that should implement grip scripts
|
|
if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()))
|
|
ActorComp->PreDestroyFromReplication();
|
|
}
|
|
|
|
GripLogicScripts.Empty();
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::BeginDestroy()
|
|
{
|
|
Super::BeginDestroy();
|
|
|
|
for (int32 i = 0; i < GripLogicScripts.Num(); i++)
|
|
{
|
|
if (UObject *SubObject = GripLogicScripts[i])
|
|
{
|
|
SubObject->MarkAsGarbage();
|
|
}
|
|
}
|
|
|
|
GripLogicScripts.Empty();
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList)
|
|
{
|
|
Super::GetSubobjectsWithStableNamesForNetworking(ObjList);
|
|
|
|
if (bReplicateGripScripts)
|
|
{
|
|
for (int32 i = 0; i < GripLogicScripts.Num(); ++i)
|
|
{
|
|
if (UObject* SubObject = GripLogicScripts[i])
|
|
{
|
|
ObjList.Add(SubObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
//- Push networking getter / setter functions
|
|
/////////////////////////////////////////////////
|
|
|
|
void AGrippableStaticMeshActor::SetReplicateGripScripts(bool bNewReplicateGripScripts)
|
|
{
|
|
bReplicateGripScripts = bNewReplicateGripScripts;
|
|
#if WITH_PUSH_MODEL
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, bReplicateGripScripts, this);
|
|
#endif
|
|
}
|
|
|
|
TArray<TObjectPtr<UVRGripScriptBase>>& AGrippableStaticMeshActor::GetGripLogicScripts()
|
|
{
|
|
#if WITH_PUSH_MODEL
|
|
if (bReplicateGripScripts)
|
|
{
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, GripLogicScripts, this);
|
|
}
|
|
#endif
|
|
|
|
return GripLogicScripts;
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::SetRepGripSettingsAndGameplayTags(bool bNewRepGripSettingsAndGameplayTags)
|
|
{
|
|
bRepGripSettingsAndGameplayTags = bNewRepGripSettingsAndGameplayTags;
|
|
#if WITH_PUSH_MODEL
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, bRepGripSettingsAndGameplayTags, this);
|
|
#endif
|
|
}
|
|
|
|
void AGrippableStaticMeshActor::SetAllowIgnoringAttachOnOwner(bool bNewAllowIgnoringAttachOnOwner)
|
|
{
|
|
bAllowIgnoringAttachOnOwner = bNewAllowIgnoringAttachOnOwner;
|
|
#if WITH_PUSH_MODEL
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, bAllowIgnoringAttachOnOwner, this);
|
|
#endif
|
|
}
|
|
|
|
FVRClientAuthReplicationData& AGrippableStaticMeshActor::GetClientAuthReplicationData(FVRClientAuthReplicationData& ClientAuthData)
|
|
{
|
|
#if WITH_PUSH_MODEL
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, ClientAuthReplicationData, this);
|
|
#endif
|
|
return ClientAuthReplicationData;
|
|
}
|
|
|
|
FBPInterfaceProperties& AGrippableStaticMeshActor::GetVRGripInterfaceSettings(bool bMarkDirty)
|
|
{
|
|
#if WITH_PUSH_MODEL
|
|
if (bMarkDirty && bRepGripSettingsAndGameplayTags)
|
|
{
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, VRGripInterfaceSettings, this);
|
|
}
|
|
#endif
|
|
|
|
return VRGripInterfaceSettings;
|
|
}
|
|
|
|
FGameplayTagContainer& AGrippableStaticMeshActor::GetGameplayTags()
|
|
{
|
|
#if WITH_PUSH_MODEL
|
|
if (bRepGripSettingsAndGameplayTags)
|
|
{
|
|
MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, GameplayTags, this);
|
|
}
|
|
#endif
|
|
|
|
return GameplayTags;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
//- End Push networking getter / setter functions
|
|
/////////////////////////////////////////////////
|