Sadly I’ve read that document better than a dozen times now it feels. Its never lead to a proper solution ![]()
I went through the trouble of re-implementing this with another fellas videos name KyleL. Its an expanded upon video of delgoodies tutorial, It yields the exact same result. https://www.youtube.com/watch?v=0wnLCXdg1_8
// CMC Tutorial Copyright (c) 2023 Kyle Lautenbach
// Packed move data example adapted from SMN1 + SMN2 - Copyright (c) 2021 Reddy-dev (https://github.com/Reddy-dev/SMN2)
// Wall running example adapted from Zippy - Copyright (c) 2022 William (https://github.com/delgoodie/Zippy)
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "MWCharacterMovementComponent.generated.h"
class FCustomNetworkMoveData : public FCharacterNetworkMoveData {
public:
typedef FCharacterNetworkMoveData Super;
virtual void ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) override;
virtual bool Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap, ENetworkMoveType MoveType) override;
//SAFE variables
bool bWantsToSprintMoveData = false;
//UNSAFE variables
float MaxCustomSpeedMoveData = 800.f;
FVector LaunchVelocityCustomMoveData = FVector(0.f, 0.f, 0.f);
//This bypasses the limitations of the typical compressed flags used in past versions of UE4.
//You would still use bitflags like this in games in order to improve network performance.
//Imagine hundreds of clients sending this info every tick. Even a small saving can add up significantly, especially when considering server costs.
//It is up to you to decide if you prefer sending bools like above or using the lightweight bitflag approach like below.
//If you're making P2P games, casual online, or co-op vs AI, etc, then you might not care too much about maximising efficiency. The bool approach might be more readable.
uint8 MovementFlagCustomMoveData = 0;
};
class FCustomCharacterNetworkMoveDataContainer : public FCharacterNetworkMoveDataContainer {
public:
FCustomCharacterNetworkMoveDataContainer();
FCustomNetworkMoveData CustomDefaultMoveData[3];
};
//Class FCustomSavedMove
class FCustomSavedMove : public FSavedMove_Character {
public:
typedef FSavedMove_Character Super;
bool bWantsToSprintSaved = false;
float SavedMaxCustomSpeed = 800.f;
virtual uint8 GetCompressedFlags() const override;
virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const override;
virtual void SetMoveFor(ACharacter* Character, float InDeltaTime, FVector const& NewAccel, class FNetworkPredictionData_Client_Character& ClientData) override;
virtual void PrepMoveFor(class ACharacter* Character) override;
virtual void Clear() override;
};
class FCustomNetworkPredictionData_Client : public FNetworkPredictionData_Client_Character {
public:
FCustomNetworkPredictionData_Client(const UCharacterMovementComponent& ClientMovement);
typedef FNetworkPredictionData_Client_Character Super;
virtual FSavedMovePtr AllocateNewMove() override;
};
class AMonsterWarsCharacter;
UCLASS()
class MONSTERWARS_API UMWCharacterMovementComponent : public UCharacterMovementComponent {
GENERATED_BODY()
public:
UMWCharacterMovementComponent(const FObjectInitializer& ObjectInitializer);
//Reference to our network prediction buddy, the custom saved move class created above.
friend class FCustomSavedMove;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Sprinting")
bool bWantsToSprint;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category = "Sprinting")
bool bIsSprinting;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Sprinting")
float CustomMaxSpeed;
UFUNCTION(BlueprintCallable, Category = "Sprinting")
virtual bool CanSprint() const;
virtual void PhysCustom(float deltaTime, int32 Iterations) override;
UFUNCTION(BlueprintCallable, Category = "Sprinting")
void SprintPressed();
UFUNCTION(BlueprintCallable, Category = "Sprinting")
void SprintReleased();
public:
virtual void ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations) override;
protected:
//Helper functions
float OwnerCapsuleRadius() const;
float OwnerCapsuleHalfHeight() const;
protected:
UPROPERTY(Transient, DuplicateTransient)
TObjectPtr<AMonsterWarsCharacter> CustomCharacter;
public:
virtual void BeginPlay() override;
virtual float GetMaxSpeed() const override;
virtual void UpdateCharacterStateBeforeMovement(float DeltaSeconds) override;
virtual void UpdateCharacterStateAfterMovement(float DeltaSeconds) override;
virtual void OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity) override;
virtual bool CanAttemptJump() const override;
virtual bool DoJump(bool bReplayingMoves) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
public:
FCustomCharacterNetworkMoveDataContainer MoveDataContainer;
virtual void UpdateFromCompressedFlags(uint8 Flags) override;
virtual void MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector& NewAccel) override;
virtual class FNetworkPredictionData_Client* GetPredictionData_Client() const override;
};
// CMC Tutorial Copyright (c) 2023 Kyle Lautenbach
// Packed move data example adapted from SMN1 + SMN2 - Copyright (c) 2021 Reddy-dev (https://github.com/Reddy-dev/SMN2)
// Wall running example adapted from Zippy - Copyright (c) 2022 William (https://github.com/delgoodie/Zippy)
#include "MWCharacterMovementComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/PhysicsVolume.h"
//Let's include our custom character
#include "../../Characters/MonsterWarsCharacter.h"
#include "Components/CapsuleComponent.h"
//Network types required for replication (we need this for GetLifetimeReplicatedProps)
#include "Net/UnrealNetwork.h"
#include "UObject/CoreNetTypes.h"
UMWCharacterMovementComponent::UMWCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {
CustomMaxSpeed = 800.0f;
SetIsReplicatedByDefault(true);
SetNetworkMoveDataContainer(MoveDataContainer);
}
void UMWCharacterMovementComponent::BeginPlay() {
Super::BeginPlay();
CustomCharacter = Cast<AMonsterWarsCharacter>(PawnOwner);
}
float UMWCharacterMovementComponent::GetMaxSpeed() const {
return bIsSprinting ? CustomMaxSpeed : Super::GetMaxSpeed();
}
bool UMWCharacterMovementComponent::CanSprint() const {
if (CustomCharacter && IsMovingOnGround() && bWantsToSprint) {
FVector Forward = CharacterOwner->GetActorForwardVector();
FVector MoveDirection = Velocity.GetSafeNormal();
float VelocityDot = FVector::DotProduct(Forward, MoveDirection);
return VelocityDot > 0.7f;
}
return false;
}
void UMWCharacterMovementComponent::SprintPressed() {
this->bWantsToSprint = true;
}
void UMWCharacterMovementComponent::SprintReleased() {
this->bWantsToSprint = false;
}
bool UMWCharacterMovementComponent::CanAttemptJump() const {
return Super::CanAttemptJump();
}
bool UMWCharacterMovementComponent::DoJump(bool bReplayingMoves) {
if (Super::DoJump(bReplayingMoves)) {
return true;
}
return false;
}
void UMWCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations) {
if (GetOwner()->GetLocalRole() == ROLE_SimulatedProxy) {
return;
}
Super::PhysCustom(deltaTime, Iterations);
}
void UMWCharacterMovementComponent::ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations) {
Super::ProcessLanded(Hit, remainingTime, Iterations);
}
void UMWCharacterMovementComponent::UpdateCharacterStateBeforeMovement(float DeltaSeconds) {
Super::UpdateCharacterStateBeforeMovement(DeltaSeconds);
// Proxies get replicated state. We don't need to run this logic for them.
if (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy) {
//Sprinting
if (CanSprint()) {
bIsSprinting = true;
} else {
bIsSprinting = false;
}
}
}
void UMWCharacterMovementComponent::UpdateCharacterStateAfterMovement(float DeltaSeconds) {
Super::UpdateCharacterStateAfterMovement(DeltaSeconds);
}
void UMWCharacterMovementComponent::OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity) {
Super::OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
}
float UMWCharacterMovementComponent::OwnerCapsuleRadius() const {
return CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius();
}
float UMWCharacterMovementComponent::OwnerCapsuleHalfHeight() const {
return CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
}
//Standard replication function
void UMWCharacterMovementComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION(ThisClass, bIsSprinting, COND_SimulatedOnly);
}
void UMWCharacterMovementComponent::MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector& NewAccel) {
FCustomNetworkMoveData* CurrentMoveData = static_cast<FCustomNetworkMoveData*>(GetCurrentNetworkMoveData());
if (CurrentMoveData != nullptr) {
bWantsToSprint = CurrentMoveData->bWantsToSprintMoveData;
CustomMaxSpeed = CurrentMoveData->MaxCustomSpeedMoveData;
}
Super::MoveAutonomous(ClientTimeStamp, DeltaTime, CompressedFlags, NewAccel);
}
//Sends the Movement Data
bool FCustomNetworkMoveData::Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap, ENetworkMoveType MoveType) {
Super::Serialize(CharacterMovement, Ar, PackageMap, MoveType);
SerializeOptionalValue<bool>(Ar.IsSaving(), Ar, bWantsToSprintMoveData, false);
SerializeOptionalValue<float>(Ar.IsSaving(), Ar, MaxCustomSpeedMoveData, 800.f);
//SerializeOptionalValue<FVector>(Ar.IsSaving(), Ar, LaunchVelocityCustomMoveData, FVector(0.f, 0.f, 0.f));
//SerializeOptionalValue<uint8>(Ar.IsSaving(), Ar, MovementFlagCustomMoveData, 0);
return !Ar.IsError();
}
void FCustomNetworkMoveData::ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) {
Super::ClientFillNetworkMoveData(ClientMove, MoveType);
const FCustomSavedMove& CurrentSavedMove = static_cast<const FCustomSavedMove&>(ClientMove);
bWantsToSprintMoveData = CurrentSavedMove.bWantsToSprintSaved;
MaxCustomSpeedMoveData = CurrentSavedMove.SavedMaxCustomSpeed;
//LaunchVelocityCustomMoveData = CurrentSavedMove.SavedLaunchVelocityCustom;
//MovementFlagCustomMoveData = CurrentSavedMove.SavedMovementFlagCustom;
}
//Combines Flags together as an optimization option by the engine to send less data over the network
bool FCustomSavedMove::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const {
FCustomSavedMove* NewMovePtr = static_cast<FCustomSavedMove*>(NewMove.Get());
if(bWantsToSprintSaved != NewMovePtr->bWantsToSprintSaved) {
return false;
}
if (SavedMaxCustomSpeed != NewMovePtr->SavedMaxCustomSpeed) {
return false;
}
return Super::CanCombineWith(NewMove, Character, MaxDelta);
}
//Saves Move before Using
void FCustomSavedMove::SetMoveFor(ACharacter* Character, float InDeltaTime, FVector const& NewAccel, FNetworkPredictionData_Client_Character& ClientData) {
Super::SetMoveFor(Character, InDeltaTime, NewAccel, ClientData);
//This is where you set the saved move in case a packet is dropped containing this to minimize corrections
UMWCharacterMovementComponent* CharacterMovement = Cast<UMWCharacterMovementComponent>(Character->GetCharacterMovement());
if (CharacterMovement) {
bWantsToSprintSaved = CharacterMovement->bWantsToSprint;
SavedMaxCustomSpeed = CharacterMovement->CustomMaxSpeed;
}
}
//This is called usually when a packet is dropped and resets the compressed flag to its saved state
void FCustomSavedMove::PrepMoveFor(ACharacter* Character) {
Super::PrepMoveFor(Character);
UMWCharacterMovementComponent* CharacterMovementComponent = Cast<UMWCharacterMovementComponent>(Character->GetCharacterMovement());
if (CharacterMovementComponent) {
CharacterMovementComponent->bWantsToSprint = bWantsToSprintSaved;
CharacterMovementComponent->CustomMaxSpeed = SavedMaxCustomSpeed;
}
}
//Just used to reset the data in a saved move.
void FCustomSavedMove::Clear() {
Super::Clear();
bWantsToSprintSaved = false;
SavedMaxCustomSpeed = 800.f;
}
//Acquires prediction data from clients (boilerplate code)
FNetworkPredictionData_Client* UMWCharacterMovementComponent::GetPredictionData_Client() const {
check(PawnOwner != NULL);
if (!ClientPredictionData) {
UMWCharacterMovementComponent* MutableThis = const_cast<UMWCharacterMovementComponent*>(this);
MutableThis->ClientPredictionData = new FCustomNetworkPredictionData_Client(*this);
}
return ClientPredictionData;
}
uint8 FCustomSavedMove::GetCompressedFlags() const {
return Super::GetCompressedFlags();
}
FCustomNetworkPredictionData_Client::FCustomNetworkPredictionData_Client(const UCharacterMovementComponent& ClientMovement) : Super(ClientMovement) {
}
FSavedMovePtr FCustomNetworkPredictionData_Client::AllocateNewMove() {
return FSavedMovePtr(new FCustomSavedMove());
}
void UMWCharacterMovementComponent::UpdateFromCompressedFlags(uint8 Flags) {
Super::UpdateFromCompressedFlags(Flags);
}
FCustomCharacterNetworkMoveDataContainer::FCustomCharacterNetworkMoveDataContainer() {
NewMoveData = &CustomDefaultMoveData[0];
PendingMoveData = &CustomDefaultMoveData[1];
OldMoveData = &CustomDefaultMoveData[2];
}
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "../Components/Movement/MWCharacterMovementComponent.h"
#include "MonsterWarsCharacter.generated.h"
UCLASS(config=Game)
class AMonsterWarsCharacter : public ACharacter
{
GENERATED_BODY()
/** Camera boom positioning the camera behind the character */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* CameraBoom;
/** Follow camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* FollowCamera;
/** Jump Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* JumpAction;
/** Move Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* MoveAction;
/** Look Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* RunAction;
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Movement)
class UMWCharacterMovementComponent* CustomMovementComponent;
public:
AMonsterWarsCharacter(const FObjectInitializer& ObjectInitializer);
virtual void Jump() override;
virtual void Landed(const FHitResult& Hit) override;
/** Called for movement input */
void Move(const FInputActionValue& Value);
void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode);
void StopMove(const FInputActionValue& Value);
/** Called for looking input */
void Look(const FInputActionValue& Value);
void Run(const FInputActionValue& Value);
void StopRun(const FInputActionValue& Value);
void AdjustJumpingRotation();
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty> & OutLifetimeProps) const override;
protected:
// APawn interface
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual void Tick(float DeltaTime) override;
// To add mapping context
virtual void BeginPlay();
public:
/** Returns CameraBoom subobject **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
// Copyright Epic Games, Inc. All Rights Reserved.
#include "MonsterWarsCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
//#include "Kismet/GameplayStatics.h"
#include "Net/UnrealNetwork.h"
//////////////////////////////////////////////////////////////////////////
// AMonsterWarsCharacter
AMonsterWarsCharacter::AMonsterWarsCharacter(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer.SetDefaultSubobjectClass<UMWCharacterMovementComponent>(ACharacter::CharacterMovementComponentName)){
// Set size for collision capsule
CustomMovementComponent = Cast<UMWCharacterMovementComponent>(GetCharacterMovement());
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
// Don't rotate when the controller rotates. Let that just affect the camera.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// Configure character movement
//GetCharacterMovement()->bOrientRotationToMovement = true; // Character moves in the direction of input...
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // ...at this rotation rate
// Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
// instead of recompiling to adjust them
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 300.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 100.f;
GetCharacterMovement()->PerchAdditionalHeight = 10.0f;
// Create a camera boom (pulls in towards the player if there is a collision)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f; // The camera follows at this distance behind the character
CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller
// Create a follow camera
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
//this->bReplicates = true;
this->SetReplicates(true);
this->SetReplicateMovement(true);
}
void AMonsterWarsCharacter::BeginPlay() {
Super::BeginPlay();
if (GetLocalRole() != ROLE_Authority) {
UE_LOG(LogTemp, Warning, TEXT("CHARACTER_START_CLIENT"));
} else {
UE_LOG(LogTemp, Warning, TEXT("CHARACTER_START_SERVER"));
}
}
void AMonsterWarsCharacter::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
}
void AMonsterWarsCharacter::AdjustJumpingRotation() {
}
void AMonsterWarsCharacter::Jump() {
Super::Jump();
}
void AMonsterWarsCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode) {
Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode);
switch(GetCharacterMovement()->MovementMode) {
case 1:
GetCharacterMovement()->bOrientRotationToMovement = false;
bUseControllerRotationYaw = true;
GetCharacterMovement()->RotationRate = FRotator(0.f, 0.f, 0.f); // Adjust as needed
break;
case 3:
//TODO: Make this only able to happen between 45 degree offsets from camera forward position.
GetCharacterMovement()->bOrientRotationToMovement = true;
bUseControllerRotationYaw = false;
GetCharacterMovement()->RotationRate = FRotator(0.f, 150.f, 0.f); // Adjust as needed
break;
}
}
void AMonsterWarsCharacter::Landed(const FHitResult& Hit) {
Super::Landed(Hit);
if (GetLocalRole() != ROLE_Authority) {
//UE_LOG(LogTemp, Warning, TEXT("CLIENT: HasLanded"));
} else {
//UE_LOG(LogTemp, Warning, TEXT("SERVER: HasLanded"));
}
}
void AMonsterWarsCharacter::Move(const FInputActionValue& Value) {
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr) {
// find out which way is forward
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
// get forward vector
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
// get right vector
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
// add movement
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
}
void AMonsterWarsCharacter::StopMove(const FInputActionValue& Value) {
}
void AMonsterWarsCharacter::Run(const FInputActionValue& Value) {
CustomMovementComponent->SprintPressed();
}
void AMonsterWarsCharacter::StopRun(const FInputActionValue& Value) {
CustomMovementComponent->SprintReleased();
}
void AMonsterWarsCharacter::Look(const FInputActionValue& Value) {
// input is a Vector2D
FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr) {
// add yaw and pitch input to controller
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
void AMonsterWarsCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) {
// Set up action bindings
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
//Jumping
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
//Moving
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMonsterWarsCharacter::Move);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Completed, this, &AMonsterWarsCharacter::StopMove);
//Running
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Started, this, &AMonsterWarsCharacter::Run);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Completed, this, &AMonsterWarsCharacter::StopRun);
//Looking
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMonsterWarsCharacter::Look);
}
}
void AMonsterWarsCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty> & OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
}
Engine version 5.4.3 source build.
So this ended up being an interesting solution, it was caused due to some kind of corruption that happened when deriving the character blueprint from a regular movement component, and transitioning it to a custom movement component after the character blueprint was already created. Destroying the characters current blueprint, and then deriving it from the c++ character after the custom component has been compiled corrected the issue with the desync between the client and server, and completely fixed my dedicated server and PIE problems. You can see now my character is getting almost no corrections from the server unless I collide with an object its not set up to properly hit yet (like itself).
TLDR: Corrupt blueprint, Delete and rebuild with CMC compiled before new derivation in blueprint.