This is a script for the Survival Game Template so I guess it is ok to post here. I think I am misunderstanding something fundamental about C++ here, my conversion from C# has been pretty rocky. In C# I would set a scripts variables in the Awake() function and then access other need variables from other scripts in the Start() function. That is what I am trying, and failing, to do here. MyPawn is a reference to the SCharacter script where the Ammo Variables are held.
SWeapon.h (abbreviated)
/** pawn owner */
UPROPERTY(Transient, ReplicatedUsing = OnRep_MyPawn)
class ASCharacter* MyPawn;
SWeapon.cpp (Full thing)
#include "SurvivalGame.h"
#include "SWeapon.h"
#include "SCharacter.h"
#include "STypes.h"
#include "SWeaponPickup.h"
#include "SPlayerController.h"
ASWeapon::ASWeapon(const class FObjectInitializer& PCIP)
: Super(PCIP)
{
Mesh = PCIP.CreateDefaultSubobject<USkeletalMeshComponent>(this, TEXT("WeaponMesh3P"));
Mesh->MeshComponentUpdateFlag = EMeshComponentUpdateFlag::OnlyTickPoseWhenRendered;
Mesh->bReceivesDecals = true;
Mesh->CastShadow = true;
Mesh->SetCollisionObjectType(ECC_WorldDynamic);
Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Mesh->SetCollisionResponseToAllChannels(ECR_Ignore);
Mesh->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
RootComponent = Mesh;
bIsEquipped = false;
CurrentState = EWeaponState::Idle;
AmmoType = EAmmoType::Rifle;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
SetReplicates(true);
bNetUseOwnerRelevancy = true;
MuzzleAttachPoint = TEXT("MuzzleFlashSocket");
StorageSlot = EInventorySlot::Primary;
ShotsPerMinute = 700;
MaxAmmoPerClip = 30;
NoAnimReloadDuration = 1.5f;
NoEquipAnimDuration = 0.5f;
SemiAuto = false;
SemiFire = false;
}
void ASWeapon::PostInitializeComponents()
{
Super::PostInitializeComponents();
/* Setup configuration */
InitializeAmmo();
}
void ASWeapon::InitializeAmmo()
{
if(MyPawn) //this is never returning true
{
switch (AmmoType)
{
case EAmmoType::Rifle:
StartAmmo =MyPawn->GetRifleAmmo();
MaxAmmo = MyPawn->GetMaxRifleAmmo();
break;
case EAmmoType::Shotgun:
StartAmmo = 0;
MaxAmmo = 0;
break;
case EAmmoType::Pistol:
StartAmmo = 0;
MaxAmmo = 0;
break;
case EAmmoType::SniperRifle:
StartAmmo = 0;
MaxAmmo = 0;
break;
default:
StartAmmo = 0;
MaxAmmo = 0;
break;
}
}
CurrentAmmo = FMath::Min(StartAmmo, MaxAmmo);
CurrentAmmoInClip = FMath::Min(MaxAmmoPerClip, StartAmmo);
TimeBetweenShots = 60.0f / ShotsPerMinute;
}
void ASWeapon::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
DetachMeshFromPawn();
StopSimulatingWeaponFire();
}
/*
Return Mesh of Weapon
*/
USkeletalMeshComponent* ASWeapon::GetWeaponMesh() const
{
return Mesh;
}
class ASCharacter* ASWeapon::GetPawnOwner() const
{
return MyPawn;
}
void ASWeapon::SetOwningPawn(ASCharacter* NewOwner)
{
if (MyPawn != NewOwner)
{
Instigator = NewOwner;
MyPawn = NewOwner;
// Net owner for RPC calls.
SetOwner(NewOwner);
}
}
void ASWeapon::OnRep_MyPawn()
{
if (MyPawn)
{
OnEnterInventory(MyPawn);
}
else
{
OnLeaveInventory();
}
}
void ASWeapon::AttachMeshToPawn(EInventorySlot Slot)
{
if (MyPawn)
{
// Remove and hide
DetachMeshFromPawn();
USkeletalMeshComponent* PawnMesh = MyPawn->GetMesh();
FName AttachPoint = MyPawn->GetInventoryAttachPoint(Slot);
Mesh->SetHiddenInGame(false);
Mesh->AttachTo(PawnMesh, AttachPoint, EAttachLocation::SnapToTarget);
}
}
void ASWeapon::DetachMeshFromPawn()
{
Mesh->DetachFromParent();
Mesh->SetHiddenInGame(true);
}
void ASWeapon::OnEquip(bool bPlayAnimation)
{
bPendingEquip = true;
DetermineWeaponState();
if (bPlayAnimation)
{
float Duration = PlayWeaponAnimation(EquipAnim);
if (Duration <= 0.0f)
{
// Failsafe in case animation is missing
Duration = NoEquipAnimDuration;
}
EquipStartedTime = GetWorld()->TimeSeconds;
EquipDuration = Duration;
GetWorldTimerManager().SetTimer(EquipFinishedTimerHandle, this, &ASWeapon::OnEquipFinished, Duration, false);
}
else
{
/* Immediately finish equipping */
OnEquipFinished();
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
PlayWeaponSound(EquipSound);
}
}
void ASWeapon::OnUnEquip()
{
bIsEquipped = false;
StopFire();
if (bPendingEquip)
{
StopWeaponAnimation(EquipAnim);
bPendingEquip = false;
GetWorldTimerManager().ClearTimer(EquipFinishedTimerHandle);
}
if (bPendingReload)
{
StopWeaponAnimation(ReloadAnim);
bPendingReload = false;
GetWorldTimerManager().ClearTimer(TimerHandle_ReloadWeapon);
}
DetermineWeaponState();
}
void ASWeapon::OnEnterInventory(ASCharacter* NewOwner)
{
SetOwningPawn(NewOwner);
AttachMeshToPawn(StorageSlot);
}
void ASWeapon::OnLeaveInventory()
{
if (Role == ROLE_Authority)
{
SetOwningPawn(nullptr);
}
if (IsAttachedToPawn())
{
OnUnEquip();
}
DetachMeshFromPawn();
}
bool ASWeapon::IsEquipped() const
{
return bIsEquipped;
}
bool ASWeapon::IsAttachedToPawn() const // TODO: Review name to more accurately specify meaning.
{
return bIsEquipped || bPendingEquip;
}
void ASWeapon::StartFire()
{
if (Role < ROLE_Authority)
{
ServerStartFire();
}
if (!bWantsToFire)
{
bWantsToFire = true;
DetermineWeaponState();
}
}
void ASWeapon::StopFire()
{
if (Role < ROLE_Authority)
{
ServerStopFire();
}
if (bWantsToFire)
{
bWantsToFire = false;
DetermineWeaponState();
}
}
bool ASWeapon::ServerStartFire_Validate()
{
return true;
}
void ASWeapon::ServerStartFire_Implementation()
{
StartFire();
}
bool ASWeapon::ServerStopFire_Validate()
{
return true;
}
void ASWeapon::ServerStopFire_Implementation()
{
StopFire();
}
bool ASWeapon::CanFire() const
{
bool bPawnCanFire = MyPawn && MyPawn->CanFire();
bool bStateOK = CurrentState == EWeaponState::Idle || CurrentState == EWeaponState::Firing;
bool bSFire;
if (SemiAuto)
{
bSFire = SemiFire;
}
else
{
bSFire = false;
}
return bPawnCanFire && bStateOK && !bPendingReload && !bSFire;
}
FVector ASWeapon::GetAdjustedAim() const
{
ASPlayerController* const PC = Instigator ? Cast<ASPlayerController>(Instigator->Controller) : nullptr;
FVector FinalAim = FVector::ZeroVector;
if (PC)
{
FVector CamLoc;
FRotator CamRot;
PC->GetPlayerViewPoint(CamLoc, CamRot);
FinalAim = CamRot.Vector();
}
else if (Instigator)
{
FinalAim = Instigator->GetBaseAimRotation().Vector();
}
return FinalAim;
}
FVector ASWeapon::GetCameraDamageStartLocation(const FVector& AimDir) const
{
ASPlayerController* PC = MyPawn ? Cast<ASPlayerController>(MyPawn->Controller) : nullptr;
FVector OutStartTrace = FVector::ZeroVector;
if (PC)
{
FRotator DummyRot;
PC->GetPlayerViewPoint(OutStartTrace, DummyRot);
// Adjust trace so there is nothing blocking the ray between the camera and the pawn, and calculate distance from adjusted start
OutStartTrace = OutStartTrace + AimDir * (FVector::DotProduct((Instigator->GetActorLocation() - OutStartTrace), AimDir));
}
return OutStartTrace;
}
FHitResult ASWeapon::WeaponTrace(const FVector& TraceFrom, const FVector& TraceTo) const
{
FCollisionQueryParams TraceParams(TEXT("WeaponTrace"), true, Instigator);
TraceParams.bTraceAsyncScene = true;
TraceParams.bReturnPhysicalMaterial = true;
FHitResult Hit(ForceInit);
GetWorld()->LineTraceSingleByChannel(Hit, TraceFrom, TraceTo, COLLISION_WEAPON, TraceParams);
return Hit;
}
void ASWeapon::HandleFiring()
{
if (CurrentAmmoInClip > 0)
{
if (CanFire())
{
if (GetNetMode() != NM_DedicatedServer)
{
SimulateWeaponFire();
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
FireWeapon();
UseAmmo();
// Update firing FX on remote clients if this is called on server
BurstCounter++;
}
}
}
else if (CanReload())
{
StartReload();
}
else if (MyPawn && MyPawn->IsLocallyControlled())
{
if (GetCurrentAmmo() == 0 && !bRefiring)
{
PlayWeaponSound(OutOfAmmoSound);
}
/* Reload after firing last round */
if (CurrentAmmoInClip <= 0 && CanReload())
{
StartReload();
}
/* Stop weapon fire FX, but stay in firing state */
if (BurstCounter > 0)
{
OnBurstFinished();
}
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
if (Role < ROLE_Authority)
{
ServerHandleFiring();
}
/* Retrigger HandleFiring on a delay for automatic weapons */
bRefiring = (CurrentState == EWeaponState::Firing && TimeBetweenShots > 0.0f);
if (bRefiring)
{
GetWorldTimerManager().SetTimer(TimerHandle_HandleFiring, this, &ASWeapon::HandleFiring, TimeBetweenShots, false);
}
}
/* Make Noise on every shot. The data is managed by the PawnNoiseEmitterComponent created in SBaseCharacter and used by PawnSensingComponent in SZombieCharacter */
if (MyPawn)
{
MyPawn->MakePawnNoise(1.0f);
}
LastFireTime = GetWorld()->GetTimeSeconds();
}
void ASWeapon::SimulateWeaponFire()
{
if (MuzzleFX)
{
MuzzlePSC = UGameplayStatics::SpawnEmitterAttached(MuzzleFX, Mesh, MuzzleAttachPoint);
}
if (!bPlayingFireAnim)
{
PlayWeaponAnimation(FireAnim);
bPlayingFireAnim = true;
}
PlayWeaponSound(FireSound);
if (SemiAuto)
{
SemiFire = true;
}
}
void ASWeapon::StopSimulatingWeaponFire()
{
if (bPlayingFireAnim)
{
StopWeaponAnimation(FireAnim);
bPlayingFireAnim = false;
}
}
void ASWeapon::OnRep_BurstCounter()
{
if (BurstCounter > 0)
{
SimulateWeaponFire();
}
else
{
StopSimulatingWeaponFire();
}
}
bool ASWeapon::ServerHandleFiring_Validate()
{
return true;
}
void ASWeapon::ServerHandleFiring_Implementation()
{
const bool bShouldUpdateAmmo = (CurrentAmmoInClip > 0 && CanFire());
HandleFiring();
if (bShouldUpdateAmmo)
{
UseAmmo();
// Update firing FX on remote clients
BurstCounter++;
}
}
void ASWeapon::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASWeapon, MyPawn);
DOREPLIFETIME_CONDITION(ASWeapon, CurrentAmmo, COND_OwnerOnly);
DOREPLIFETIME_CONDITION(ASWeapon, CurrentAmmoInClip, COND_OwnerOnly);
DOREPLIFETIME_CONDITION(ASWeapon, BurstCounter, COND_SkipOwner);
DOREPLIFETIME_CONDITION(ASWeapon, bPendingReload, COND_SkipOwner);
}
FVector ASWeapon::GetMuzzleLocation() const
{
return Mesh->GetSocketLocation(MuzzleAttachPoint);
}
FVector ASWeapon::GetMuzzleDirection() const
{
return Mesh->GetSocketRotation(MuzzleAttachPoint).Vector();
}
UAudioComponent* ASWeapon::PlayWeaponSound(USoundCue* SoundToPlay)
{
UAudioComponent* AC = nullptr;
if (SoundToPlay && MyPawn)
{
AC = UGameplayStatics::SpawnSoundAttached(SoundToPlay, MyPawn->GetRootComponent());
}
return AC;
}
EWeaponState ASWeapon::GetCurrentState() const
{
return CurrentState;
}
void ASWeapon::SetWeaponState(EWeaponState NewState)
{
const EWeaponState PrevState = CurrentState;
if (PrevState == EWeaponState::Firing && NewState == EWeaponState::Firing && !CanFire())
{
OnBurstFinished();
}
if (PrevState == EWeaponState::Firing && NewState != EWeaponState::Firing)
{
OnBurstFinished();
}
CurrentState = NewState;
if (PrevState != EWeaponState::Firing && NewState == EWeaponState::Firing && CanFire())
{
OnBurstStarted();
}
}
void ASWeapon::OnBurstStarted()
{
// Start firing, can be delayed to satisfy TimeBetweenShots
const float GameTime = GetWorld()->GetTimeSeconds();
if (LastFireTime > 0 && TimeBetweenShots > 0.0f &&
LastFireTime + TimeBetweenShots > GameTime)
{
GetWorldTimerManager().SetTimer(TimerHandle_HandleFiring, this, &ASWeapon::HandleFiring, LastFireTime + TimeBetweenShots - GameTime, false);
}
else
{
HandleFiring();
}
}
void ASWeapon::OnBurstFinished()
{
BurstCounter = 0;
if (GetNetMode() != NM_DedicatedServer)
{
StopSimulatingWeaponFire();
}
GetWorldTimerManager().ClearTimer(TimerHandle_HandleFiring);
bRefiring = false;
SemiFire = false;
}
void ASWeapon::DetermineWeaponState()
{
EWeaponState NewState = EWeaponState::Idle;
if (bIsEquipped)
{
if (bPendingReload)
{
if (CanReload())
{
NewState = EWeaponState::Reloading;
}
else
{
NewState = CurrentState;
}
}
else if (!bPendingReload && bWantsToFire)
{
NewState = EWeaponState::Firing;
}
}
else if (bPendingEquip)
{
NewState = EWeaponState::Equipping;
}
SetWeaponState(NewState);
}
float ASWeapon::GetEquipStartedTime() const
{
return EquipStartedTime;
}
float ASWeapon::GetEquipDuration() const
{
return EquipDuration;
}
float ASWeapon::PlayWeaponAnimation(UAnimMontage* Animation, float InPlayRate, FName StartSectionName)
{
float Duration = 0.0f;
if (MyPawn)
{
if (Animation)
{
Duration = MyPawn->PlayAnimMontage(Animation, InPlayRate, StartSectionName);
}
}
return Duration;
}
void ASWeapon::StopWeaponAnimation(UAnimMontage* Animation)
{
if (MyPawn)
{
if (Animation)
{
MyPawn->StopAnimMontage(Animation);
}
}
}
void ASWeapon::OnEquipFinished()
{
AttachMeshToPawn();
bIsEquipped = true;
bPendingEquip = false;
DetermineWeaponState();
if (MyPawn)
{
// Try to reload empty clip
if (MyPawn->IsLocallyControlled() &&
CurrentAmmoInClip <= 0 &&
CanReload())
{
StartReload();
}
}
}
void ASWeapon::UseAmmo()
{
CurrentAmmoInClip--;
CurrentAmmo--;
if (MyPawn)
{
switch (AmmoType)
{
case EAmmoType::Rifle:
MyPawn->SubtractRifleAmmo(1);
break;
case EAmmoType::Shotgun:
break;
case EAmmoType::Pistol:
break;
case EAmmoType::SniperRifle:
break;
default:
break;
}
}
}
int32 ASWeapon::GiveAmmo(int32 AddAmount)
{
const int32 MissingAmmo = FMath::Max(0, MaxAmmo - CurrentAmmo);
AddAmount = FMath::Min(AddAmount, MissingAmmo);
CurrentAmmo += AddAmount;
/* Push reload request to client */
if (GetCurrentAmmoInClip() <= 0 && CanReload() &&
MyPawn->GetCurrentWeapon() == this)
{
ClientStartReload();
}
/* Return the unused ammo when weapon is filled up */
return FMath::Max(0, AddAmount - MissingAmmo);
}
void ASWeapon::SetAmmoCount(int32 NewTotalAmount)
{
CurrentAmmo = FMath::Min(MaxAmmo, NewTotalAmount);
CurrentAmmoInClip = FMath::Min(MaxAmmoPerClip, CurrentAmmo);
}
int32 ASWeapon::GetCurrentAmmo() const
{
return CurrentAmmo;
}
int32 ASWeapon::GetCurrentAmmoInClip() const
{
return CurrentAmmoInClip;
}
int32 ASWeapon::GetMaxAmmoPerClip() const
{
return MaxAmmoPerClip;
}
int32 ASWeapon::GetMaxAmmo() const
{
return MaxAmmo;
}
void ASWeapon::StartReload(bool bFromReplication)
{
/* Push the request to server */
if (!bFromReplication && Role < ROLE_Authority)
{
ServerStartReload();
}
/* If local execute requested or we are running on the server */
if (bFromReplication || CanReload())
{
bPendingReload = true;
DetermineWeaponState();
float AnimDuration = PlayWeaponAnimation(ReloadAnim);
if (AnimDuration <= 0.0f)
{
AnimDuration = NoAnimReloadDuration;
}
GetWorldTimerManager().SetTimer(TimerHandle_StopReload, this, &ASWeapon::StopSimulateReload, AnimDuration, false);
if (Role == ROLE_Authority)
{
GetWorldTimerManager().SetTimer(TimerHandle_ReloadWeapon, this, &ASWeapon::ReloadWeapon, FMath::Max(0.1f, AnimDuration - 0.1f), false);
}
if (MyPawn && MyPawn->IsLocallyControlled())
{
PlayWeaponSound(ReloadSound);
}
}
}
void ASWeapon::StopSimulateReload()
{
if (CurrentState == EWeaponState::Reloading)
{
bPendingReload = false;
DetermineWeaponState();
StopWeaponAnimation(ReloadAnim);
}
}
void ASWeapon::ReloadWeapon()
{
int32 ClipDelta = FMath::Min(MaxAmmoPerClip - CurrentAmmoInClip, CurrentAmmo - CurrentAmmoInClip);
if (ClipDelta > 0)
{
CurrentAmmoInClip += ClipDelta;
}
}
bool ASWeapon::CanReload()
{
bool bCanReload = (!MyPawn || MyPawn->CanReload());
bool bGotAmmo = (CurrentAmmoInClip < MaxAmmoPerClip) && ((CurrentAmmo - CurrentAmmoInClip) > 0);
bool bStateOKToReload = ((CurrentState == EWeaponState::Idle) || (CurrentState == EWeaponState::Firing));
return (bCanReload && bGotAmmo && bStateOKToReload);
}
void ASWeapon::OnRep_Reload()
{
if (bPendingReload)
{
/* By passing true we do not push back to server and execute it locally */
StartReload(true);
}
else
{
StopSimulateReload();
}
}
void ASWeapon::ServerStartReload_Implementation()
{
StartReload();
}
bool ASWeapon::ServerStartReload_Validate()
{
return true;
}
void ASWeapon::ServerStopReload_Implementation()
{
StopSimulateReload();
}
bool ASWeapon::ServerStopReload_Validate()
{
return true;
}
void ASWeapon::ClientStartReload_Implementation()
{
StartReload();
}