CogSample: progress on projectiles

This commit is contained in:
Arnaud Jamin
2023-10-30 01:11:48 -04:00
parent be498905bc
commit 315782183d
32 changed files with 224 additions and 60 deletions
-1
View File
@@ -87,7 +87,6 @@ void ACogSampleCharacter::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >
DOREPLIFETIME_WITH_PARAMS_FAST(ACogSampleCharacter, ActiveAbilityHandles, Params);
}
//--------------------------------------------------------------------------------------------------------------------------
void ACogSampleCharacter::PostInitializeComponents()
{
@@ -107,4 +107,7 @@ public:
static bool IsActorMatchingTags(const AActor* Actor, const FGameplayTagContainer& RequiredTags, const FGameplayTagContainer& IgnoredTags);
static void MakeOutgoingSpecs(const AActor* Actor, const TArray<TSubclassOf<UGameplayEffect>>& Effects, const TArray<FGameplayEffectSpecHandle>& BakedEffects, TMap<TSubclassOf<UGameplayEffect>, FGameplayEffectSpecHandle>& Result);
static APlayerController* GetFirstLocalPlayerController(UObject* WorldContextObject);
};
+5
View File
@@ -102,6 +102,11 @@ void ACogSampleGameState::Tick(float DeltaSeconds)
FCogDebugPlot::PlotValue(this, "Frame Rate", GAverageFPS);
FCogDebugPlot::PlotValue(this, "Frame Time", GAverageMS);
//const float LocalWorldTime = GetWorld()->GetTimeSeconds();
//const float ServerWorldTime = GetServerWorldTimeSeconds();
//const float ServerWorldTimeDelta = LocalWorldTime - ServerWorldTime;
//FCogDebugPlot::PlotValue(this, "Server World Time Delta", ServerWorldTimeDelta);
CogWindowManager->Tick(DeltaSeconds);
#endif //ENABLE_COG
@@ -11,6 +11,7 @@
#if ENABLE_COG
#include "CogAbilityReplicator.h"
#include "CogDebugDraw.h"
#include "CogDebugPlot.h"
#include "CogDebugReplicator.h"
#include "CogEngineReplicator.h"
#endif //ENABLE_COG
@@ -202,3 +203,29 @@ void ACogSamplePlayerController::Server_SetTarget_Implementation(AActor* Value)
{
Target = Value;
}
//--------------------------------------------------------------------------------------------------------------------------
const ACogSamplePlayerController* ACogSamplePlayerController::GetFirstLocalPlayerController(UObject* WorldContextObject)
{
UWorld* const World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
if (World == nullptr)
{
return nullptr;
}
UGameInstance* const GameInstance = World->GetGameInstance();
if (GameInstance == nullptr)
{
return nullptr;
}
ACogSamplePlayerController* PlayerController = Cast<ACogSamplePlayerController>(GameInstance->GetFirstLocalPlayerController(World));
return PlayerController;
}
//--------------------------------------------------------------------------------------------------------------------------
float ACogSamplePlayerController::GetClientLag() const
{
return (PlayerState != nullptr && GetNetMode() != NM_Standalone) ? (0.0001f * 0.5f * PlayerState->ExactPing) : 0.f;
}
@@ -25,12 +25,16 @@ class ACogSamplePlayerController
public:
static const ACogSamplePlayerController* GetFirstLocalPlayerController(UObject* WorldContextObject);
ACogSamplePlayerController();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
float GetClientLag() const;
//----------------------------------------------------------------------------------------------------------------------
// Possession
//----------------------------------------------------------------------------------------------------------------------
+153 -45
View File
@@ -6,12 +6,74 @@
#include "CogSampleFunctionLibrary_Gameplay.h"
#include "CogSampleFunctionLibrary_Team.h"
#include "CogSampleLogCategories.h"
#include "CogSamplePlayerController.h"
#include "GameFramework/Character.h"
#include "Net/Core/PushModel/PushModel.h"
#include "Net/UnrealNetwork.h"
#if ENABLE_COG
#include "CogDebugLog.h"
#include "CogDebugDraw.h"
#endif //ENABLE_COG
//--------------------------------------------------------------------------------------------------------------------------
// Postulates:
//--------------------------------------------------------------------------------------------------------------------------
//
// Projectiles can be launched by a Player or a NPC.
// Projectiles can hit the world, or another player, or a NPC.
// When hitting characters, projectiles do not hit the characters' capsules, but their animated hit boxes.
// The server do not update animations and therefore do not update animated hit boxes(like Fortnite)
// The server applies the gameplay effects related to projectile hit.
//
//--------------------------------------------------------------------------------------------------------------------------
// Questions/Answers:
//--------------------------------------------------------------------------------------------------------------------------
//
// Where should projectile collision happen ? On the server, or on the client ?
//
// On the clients.If projectiles should hit animated hit boxes, and if hit boxes are not updated on the server,
// then we need to rely on the clients to perform the collision detection.Clients should send the hit result to the
// server, the server should verify if the result is valid(anti cheat), and then apply gameplay effects.
//
// If the projectile is launched by a NPC and hit a player, which client should send the hit result to the server ?
// The client that got hit, or another client that also detected the hit ?
//
//
//
// If the projectile is launched by a player and hit another player, which client should detect and send the hit result
// to the server ? The player that launched the projectile or/ and the player that got hit ?
//
// If the server only receives the hit from the client that got hit, then that client can prevent this message
// from being sent, or he can alter the message to gain advantages. The server needs another information.
// This information can come from the launcher of the projectile.
//
// If the projectile is launched by a NPC and hit another NPC, which client should send the hit result to the server ?
//
// One client could be made responsible to detect hits received by a specific NPC.
//
//--------------------------------------------------------------------------------------------------------------------------
UCogSampleProjectileComponent::UCogSampleProjectileComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SetIsReplicatedByDefault(true);
bAutoActivate = false;
}
//--------------------------------------------------------------------------------------------------------------------------
void UCogSampleProjectileComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams Params;
Params.bIsPushBased = true;
Params.Condition = COND_None;
DOREPLIFETIME_WITH_PARAMS_FAST(UCogSampleProjectileComponent, ServerSpawnLocation, Params);
DOREPLIFETIME_WITH_PARAMS_FAST(UCogSampleProjectileComponent, ServerSpawnRotation, Params);
DOREPLIFETIME_WITH_PARAMS_FAST(UCogSampleProjectileComponent, ServerSpawnVelocity, Params);
}
//--------------------------------------------------------------------------------------------------------------------------
void UCogSampleProjectileComponent::BeginPlay()
{
@@ -20,10 +82,7 @@ void UCogSampleProjectileComponent::BeginPlay()
Creator = UCogSampleFunctionLibrary_Gameplay::GetCreator(GetOwner());
SpawnPrediction = GetOwner()->FindComponentByClass<UCogSampleSpawnPredictionComponent>();
if (GetOwner()->HasAuthority())
{
RegisterAllEffects();
}
RegisterAllEffects();
Collision = Cast<USphereComponent>(CollisionReference.GetComponent(GetOwner()));
if (Collision != nullptr)
@@ -37,14 +96,59 @@ void UCogSampleProjectileComponent::BeginPlay()
AssistanceOverlap->OnComponentBeginOverlap.AddDynamic(this, &UCogSampleProjectileComponent::OnAssistanceOverlapBegin);
}
if (GetOwner()->GetLocalRole() != ROLE_Authority)
{
Activate(false);
}
}
//--------------------------------------------------------------------------------------------------------------------------
void UCogSampleProjectileComponent::Activate(bool bReset)
{
//------------------------------------------------------------------------------------------------
// Save the spawn location and rotation and get them replicated because, we want remote clients
// to tick projectiles on their own, and be synced with the server. To do so they need to
// recompute (to catchup) the projectile movement from its initial location, rotation and velocity.
// If we don't reposition the remote client projectile at its initial values, we get some offset
// because the server doesn't necessarly replicates the location and rotation of the projectile
// at its spawn frame.
//------------------------------------------------------------------------------------------------
if (GetOwner()->GetLocalRole() == ROLE_Authority)
{
COMPARE_ASSIGN_AND_MARK_PROPERTY_DIRTY(UCogSampleProjectileComponent, ServerSpawnLocation, GetOwner()->GetActorLocation(), this);
COMPARE_ASSIGN_AND_MARK_PROPERTY_DIRTY(UCogSampleProjectileComponent, ServerSpawnRotation, GetOwner()->GetActorRotation(), this);
COMPARE_ASSIGN_AND_MARK_PROPERTY_DIRTY(UCogSampleProjectileComponent, ServerSpawnVelocity, Velocity, this);
}
else
{
GetOwner()->SetActorLocationAndRotation(ServerSpawnLocation, ServerSpawnRotation);
Velocity = ServerSpawnVelocity;
}
Super::Activate(bReset);
#if ENABLE_COG
DrawDebugCurrentState(FColor::Green);
if (FCogDebugLog::IsLogCategoryActive(LogCogProjectile))
{
LastDebugLocation = GetOwner()->GetActorLocation();
}
#endif //ENABLE_COG
}
//--------------------------------------------------------------------------
// Catchup after settings LastDebugLocation because Tick will be triggered
// by Catchup and LastDebugLocation is used in Tick debug draw.
//--------------------------------------------------------------------------
if (GetOwner()->GetLocalRole() != ROLE_Authority)
{
if (const ACogSamplePlayerController* Controller = ACogSamplePlayerController::GetFirstLocalPlayerController(this))
{
Catchup(Controller->GetClientLag());
}
}
}
//--------------------------------------------------------------------------------------------------------------------------
void UCogSampleProjectileComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
@@ -68,8 +172,8 @@ void UCogSampleProjectileComponent::TickComponent(float DeltaTime, enum ELevelTi
const float CollisionRadius = Collision != nullptr ? Collision->GetScaledSphereRadius() : 0.0f;
const float AssistanceRadius = AssistanceOverlap != nullptr ? AssistanceOverlap->GetScaledSphereRadius() : 0.0f;
const float DebugRadius = FMath::Max(CollisionRadius, AssistanceRadius);
const FColor Color = SpawnPrediction != nullptr ? SpawnPrediction->GetRoleColor() : FColor(128, 128, 128, 255);
const bool Show = SpawnPrediction == nullptr || SpawnPrediction->GetRole() != ECogSampleSpawnPredictionRole::Replicated;
const FColor Color = (SpawnPrediction != nullptr ? SpawnPrediction->GetRoleColor() : FColor(128, 128, 128, 255)).WithAlpha(IsCatchingUp ? 100 : 255);
if (Show && UpdatedComponent != nullptr)
{
@@ -99,28 +203,39 @@ void UCogSampleProjectileComponent::TickComponent(float DeltaTime, enum ELevelTi
#endif //ENABLE_COG
}
//--------------------------------------------------------------------------------------------------------------------------
//void UCogSampleProjectileComponent::CatchupReplicatedActor(float CatchupDuration)
//{
// COG_LOG_OBJECT(LogCogProjectile, ELogVerbosity::Verbose, Creator.Get(), TEXT("Projectile:%s - Role:%s - CatchupDuration:%0.2f"), *GetName(), *GetRoleName(), CatchupDuration);
//
// const FVector OldPosition = GetOwner()->GetActorLocation();
// const float OldSpeed = Velocity.Length();
//
// TickComponent(CatchupDuration, LEVELTICK_All, nullptr);
//
// COG_LOG_OBJECT(LogCogProjectile, ELogVerbosity::Verbose, Creator, TEXT("Distance:%0.2f - OldSpeed:%0.2f - NewSpeed:%0.2f"),
// (GetOwner()->GetActorLocation() - OldPosition).Length(),
// OldSpeed,
// Velocity.Length());
//
// if (SpawnPrediction != nullptr)
// {
// if (UCogSampleSpawnPrediction* SpawnPrediction2 = SpawnPrediction->GetSpawnPrediction())
// {
// SpawnPrediction2->ProjectileMovement->Velocity = ProjectileMovement->Velocity;
// }
// }
//}
void UCogSampleProjectileComponent::Catchup(float CatchupDuration)
{
COG_LOG_OBJECT(LogCogProjectile, ELogVerbosity::Verbose, Creator.Get(), TEXT("Projectile:%s - Role:%s - CatchupDuration:%0.2f"), *GetName(), *GetRoleName(), CatchupDuration);
IsCatchingUp = true;
TickComponent(CatchupDuration, LEVELTICK_All, nullptr);
IsCatchingUp = false;
#if ENABLE_COG
DrawDebugCurrentState(FColor::Red);
#endif //ENABLE_COG
}
//--------------------------------------------------------------------------------------------------------------------------
#if ENABLE_COG
void UCogSampleProjectileComponent::DrawDebugCurrentState(const FColor& Color, bool DrawVelocity)
{
if (FCogDebugLog::IsLogCategoryActive(LogCogProjectile))
{
float CollisionRadius = Collision != nullptr ? Collision->GetScaledSphereRadius() : 0.0f;
float AssistanceRadius = AssistanceOverlap != nullptr ? AssistanceOverlap->GetScaledSphereRadius() : 0.0f;
float DebugRadius = FMath::Max(CollisionRadius, AssistanceRadius);
FCogDebugDraw::Sphere(LogCogProjectile, GetOwner(), GetOwner()->GetActorLocation(), DebugRadius, Color, true, 0);
if (DrawVelocity)
{
FCogDebugDraw::Arrow(LogCogProjectile, GetOwner(), GetOwner()->GetActorLocation(), GetOwner()->GetActorLocation() + Velocity * 0.1f, Color, true, 0);
}
}
}
#endif //ENABLE_COG
//--------------------------------------------------------------------------------------------------------------------------
void UCogSampleProjectileComponent::RegisterAllEffects()
@@ -181,16 +296,6 @@ bool UCogSampleProjectileComponent::ShouldProcessOverlap(AActor* OtherActor, UPr
return false;
}
////-----------------------------------------------------------------------------------------
//// Don't overlap if OtherActor is the simulated proxy replicated one time from server to
//// client for synch-up
////-----------------------------------------------------------------------------------------
//if (OtherActor->IsA(AGPCoreProjectile::StaticClass()) && OtherActor->GetLocalRole() == ROLE_SimulatedProxy)
//{
// COG_LOG_OBJECT(LogCogProjectile, ELogVerbosity::Verbose, Creator.Get(), TEXT("Skipped:SimulatedProxyProjectile"));
// return false;
//}
if (IsAlreadyProcessingAnOverlap)
{
COG_LOG_OBJECT(LogCogProjectile, ELogVerbosity::Verbose, Creator.Get(), TEXT("Skipped:AlreadyProcessingAnOverlap"));
@@ -369,15 +474,24 @@ bool UCogSampleProjectileComponent::ShouldHit_Implementation(const FHitResult& H
}
//--------------------------------------------------------------------------------------------------------------------------
void UCogSampleProjectileComponent::Hit_Implementation(const FHitResult& HitResult, FCogSampleHitConsequence& hitConsequence)
void UCogSampleProjectileComponent::Hit_Implementation(const FHitResult& HitResult, FCogSampleHitConsequence& HitConsequence)
{
AActor* HitActor = HitResult.GetActor();
#if ENABLE_COG
DrawDebugCurrentState(FColor::White, false);
FCogDebugDraw::Arrow(LogCogProjectile, GetOwner(), HitResult.Location, HitResult.Location + HitResult.Normal * 50.0f, FColor::Red, true, 0);
FCogDebugDraw::Box(LogCogProjectile, GetOwner(), HitResult.Location, FVector(0.0f, 5.0f, 5.0f), FRotationMatrix::MakeFromX(HitResult.Normal).ToQuat(), FColor::Red, true, 0);
FCogDebugDraw::Arrow(LogCogProjectile, GetOwner(), HitResult.ImpactPoint, HitResult.ImpactPoint + HitResult.ImpactNormal * 50.0f, FColor::Yellow, true, 0);
FCogDebugDraw::Box(LogCogProjectile, GetOwner(), HitResult.ImpactPoint, FVector(0.0f, 5.0f, 5.0f), FRotationMatrix::MakeFromX(HitResult.ImpactNormal).ToQuat(), FColor::Yellow, true, 0);
#endif //ENABLE_COG
UAbilitySystemComponent* TargetAbilitySystem = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(HitActor);
if (TargetAbilitySystem == nullptr)
{
return;
}
for (const FCogSampleProjectileEffectConfig& EffectConfig : Effects)
{
for (TSubclassOf<UGameplayEffect> EffectClass : EffectConfig.Effects)
@@ -404,12 +518,6 @@ void UCogSampleProjectileComponent::Hit_Implementation(const FHitResult& HitResu
continue;
}
UAbilitySystemComponent* TargetAbilitySystem = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(HitActor);
if (TargetAbilitySystem == nullptr)
{
continue;
}
Spec->GetContext().AddHitResult(HitResult, true);
TargetAbilitySystem->ApplyGameplayEffectSpecToSelf(*Spec);
}
+25 -10
View File
@@ -7,8 +7,6 @@
#include "GameplayEffect.h"
#include "CogSampleProjectileComponent.generated.h"
//--------------------------------------------------------------------------------------------------------------------------
// FCogSampleProjectileEffectConfig
//--------------------------------------------------------------------------------------------------------------------------
USTRUCT(BlueprintType)
struct FCogSampleProjectileEffectConfig
@@ -25,8 +23,6 @@ struct FCogSampleProjectileEffectConfig
TArray<TSubclassOf<UGameplayEffect>> Effects;
};
//--------------------------------------------------------------------------------------------------------------------------
// FCogSampleHitConsequence
//--------------------------------------------------------------------------------------------------------------------------
USTRUCT(BlueprintType)
struct FCogSampleHitConsequence
@@ -43,8 +39,6 @@ struct FCogSampleHitConsequence
bool HideOnStop = false;
};
//--------------------------------------------------------------------------------------------------------------------------
// UCogSampleProjectileComponent
//--------------------------------------------------------------------------------------------------------------------------
UCLASS(BlueprintType, meta = (BlueprintSpawnableComponent))
class UCogSampleProjectileComponent : public UProjectileMovementComponent
@@ -53,10 +47,16 @@ class UCogSampleProjectileComponent : public UProjectileMovementComponent
public:
UCogSampleProjectileComponent(const FObjectInitializer& ObjectInitializer);
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction);
virtual void Activate(bool bReset) override;
UFUNCTION(BlueprintCallable)
void ClearHitActors();
@@ -70,7 +70,7 @@ protected:
virtual FString GetRoleName() const;
//virtual void CatchupReplicatedActor(float CatchupDuration);
virtual void Catchup(float CatchupDuration);
virtual bool ShouldProcessOverlap(AActor* OtherActor, UPrimitiveComponent* OtherComp, bool RequireValidActor);
@@ -91,11 +91,11 @@ protected:
void RegisterAllEffects();
UPROPERTY(EditAnywhere)
UPROPERTY(EditAnywhere, Category = "Projectile")
FComponentReference CollisionReference;
UPROPERTY(EditAnywhere)
FComponentReference OverlapReference;
UPROPERTY(EditAnywhere, Category = "Projectile")
FComponentReference OverlapReference;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
bool CanHitCreator = false;
@@ -114,6 +114,15 @@ protected:
UPROPERTY()
TWeakObjectPtr<UCogSampleSpawnPredictionComponent> SpawnPrediction = nullptr;
UPROPERTY(Replicated)
FVector ServerSpawnLocation = FVector::ZeroVector;
UPROPERTY(Replicated)
FRotator ServerSpawnRotation = FRotator::ZeroRotator;
UPROPERTY(Replicated)
FVector ServerSpawnVelocity = FVector::ZeroVector;
/** Re-entrancy guard */
bool IsAlreadyProcessingAnOverlap = false;
@@ -122,7 +131,13 @@ protected:
TMap<TSubclassOf<UGameplayEffect>, FGameplayEffectSpecHandle> EffectsMap;
float SpawnTime = 0.0f;
bool IsCatchingUp = false;
#if ENABLE_COG
void DrawDebugCurrentState(const FColor& Color, bool DrawVelocity = true);
FVector LastDebugLocation = FVector::ZeroVector;
#endif //ENABLE_COG
};