I am not sure if anyone has had a similar issue. I don’t know if this is a bug or what. I Implemented a ‘Melee’ attack. everything works great except when in multiplayer. The animation replicates and plays for everyone when one of the clients performs the attack. However when the server performs the melee attack it works properly but the animation doesn’t play. Here is my code:
MyCharacter.cpp
void AArenaCharacter::OnMelee()
{
AArenaPlayerController* MyPC = Cast<AArenaPlayerController>(Controller);
if (MyPC && MyPC->IsGameInputAllowed())
{
IdleTime = 0.0f;
bDoingMelee = true;
CurrentWeapon->StartMelee();
}
}
void AArenaCharacter::OnStopMelee()
{
bDoingMelee = false;
HitActors.Empty();
}
bool AArenaCharacter::CanMelee() const
{
return true;
}
void AArenaCharacter::AttackTrace()
{
//Overlapping actors for each box spawned will be stored here
TArray<struct FOverlapResult> OutOverlaps;
//The initial rotation of our box is the same as our character rotation
FQuat Rotation = Instigator->GetTransform().GetRotation();
FVector Start = Instigator->GetTransform().GetLocation() + Rotation.Rotator().Vector() * 100.0f;
FCollisionShape CollisionHitShape;
FCollisionQueryParams CollisionParams;
//We do not want to store the instigator character in the array, so ignore it's collision
CollisionParams.AddIgnoredActor(Instigator);
//Set the channels that will respond to the collision
FCollisionObjectQueryParams CollisionObjectTypes;
CollisionObjectTypes.AddObjectTypesToQuery(ECollisionChannel::ECC_PhysicsBody);
CollisionObjectTypes.AddObjectTypesToQuery(ECollisionChannel::ECC_Pawn);
CollisionObjectTypes.AddObjectTypesToQuery(ECollisionChannel::ECC_WorldStatic);
//Create the box and get the overlapping actors
CollisionHitShape = FCollisionShape::MakeBox(FVector(60.0f, 60.0f, 0.5f));
GetWorld()->OverlapMulti(OutOverlaps, Start, Rotation, CollisionHitShape, CollisionParams, CollisionObjectTypes);
//Process all hit actors
for (int i = 0; i < OutOverlaps.Num(); ++i)
{
//We process each actor only once per Attack execution
if (OutOverlaps[i].GetActor() && !HitActors.Contains(OutOverlaps[i].GetActor()))
{
//Process the actor to deal damage
CurrentWeapon->Melee(OutOverlaps[i].GetActor(), HitActors);
ServerMeleeAttack(CurrentWeapon, OutOverlaps[i].GetActor(), HitActors);
}
}
}
void AArenaCharacter::OnRep_Melee()
{
if (bDoingMelee)
{
CurrentWeapon->StartMelee(true);
}
else
{
CurrentWeapon->StopMelee();
}
}
bool AArenaCharacter::ServerMeleeAttack_Validate(class AArenaRangedWeapon* Weapon, AActor* target, const TArray<AActor*>& HActors)
{
return true;
}
void AArenaCharacter::ServerMeleeAttack_Implementation(class AArenaRangedWeapon* Weapon, AActor* target, const TArray<AActor*>& HActors)
{
Weapon->Melee(target, HActors);
}
MyWeapon.cpp
void AArenaRangedWeapon::StartMelee(bool bFromReplication)
{
if (!bFromReplication && Role < ROLE_Authority)
{
ServerStartMelee();
}
if (bFromReplication || CanMelee())
{
bWantsToMelee = true;
DetermineWeaponState();
float AnimDuration = PlayWeaponAnimation(MeleeAnim);
if (AnimDuration <= 0.0f)
{
AnimDuration = 0.3f;
}
GetWorldTimerManager().SetTimer(this, &AArenaRangedWeapon::StopMelee, AnimDuration, false);
if (Role == ROLE_Authority)
{
//GetWorldTimerManager().SetTimer(this, &AArenaRangedWeapon::StartMelee, FMath::Max(0.1f, AnimDuration - 0.1f), false);
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
PlayWeaponSound(MeleeSound);
}
ServerStartMelee();
}
}
void AArenaRangedWeapon::StopMelee()
{
if (CurrentState == EWeaponState::Meleeing)
{
bWantsToMelee = false;
DetermineWeaponState();
StopWeaponAnimation(MeleeAnim);
}
}
bool AArenaRangedWeapon::ServerStartMelee_Validate()
{
return true;
}
void AArenaRangedWeapon::ServerStartMelee_Implementation()
{
StartMelee();
}
bool AArenaRangedWeapon::ServerStopMelee_Validate()
{
return true;
}
void AArenaRangedWeapon::ServerStopMelee_Implementation()
{
StopMelee();
}
bool AArenaRangedWeapon::CanMelee() const
{
bool bCanMelee = (!MyPawn || MyPawn->CanMelee());
bool bStateOKToMelee = ((CurrentState == EWeaponState::Idle) || (CurrentState == EWeaponState::Firing));
return ((bCanMelee == true) && (bStateOKToMelee == true));
}
void AArenaRangedWeapon::Melee(AActor* ActorToProcess, TArray<AActor*> HitActors)
{
if (!ActorToProcess || HitActors.Contains(ActorToProcess))
{
return;
}
//Add this actor to the array because we are spawning one box per tick and we don't want to hit the same actor twice during the same attack animation
HitActors.AddUnique(ActorToProcess);
FHitResult AttackHitResult;
FDamageEvent AttackDamageEvent;
AArenaCharacter* GameCharacter = Cast<AArenaCharacter>(ActorToProcess);
if (GameCharacter)
{
ActorToProcess->TakeDamage(WeaponConfig.MeleeDamage, AttackDamageEvent, Instigator->GetController(), MyPawn->Controller);
}
}
void AArenaRangedWeapon::DetermineWeaponState()
{
EWeaponState::Type NewState = EWeaponState::Idle;
if (bIsEquipped)
{
if (bPendingReload)
{
if (CanReload() == false)
{
NewState = CurrentState;
}
else
{
NewState = EWeaponState::Reloading;
}
}
else if ((bPendingReload == false) && (bWantsToFire == true) && (CanFire() == true))
{
NewState = EWeaponState::Firing;
}
else if ((bWantsToMelee == true) && (CanMelee() == true))
{
NewState = EWeaponState::Meleeing;
}
}
else if (bPendingEquip)
{
NewState = EWeaponState::Equipping;
}
SetWeaponState(NewState);
}