diff --git a/Project/Binaries/Win64/UnrealEditor-Gasa.dll b/Project/Binaries/Win64/UnrealEditor-Gasa.dll index f3801ba..8bbd303 100644 --- a/Project/Binaries/Win64/UnrealEditor-Gasa.dll +++ b/Project/Binaries/Win64/UnrealEditor-Gasa.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2230c6cb14601b714954bb70c831b116d14801e27d78748213f2f730644ca305 -size 605696 +oid sha256:457c5440bf798c3fe0ac5681a4d6e59166b427c08fddba63e479c5249ee92114 +size 1010176 diff --git a/Project/Binaries/Win64/UnrealEditor-GasaEditor.dll b/Project/Binaries/Win64/UnrealEditor-GasaEditor.dll index 3d5438c..28e4fff 100644 --- a/Project/Binaries/Win64/UnrealEditor-GasaEditor.dll +++ b/Project/Binaries/Win64/UnrealEditor-GasaEditor.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d558410faddece4149f4930726a1b11728cf235a111e8763bac8a43ba10c228d +oid sha256:36682d8d415863139146fca4b596b67bb232b60f306c82f2b2692780b044f200 size 79360 diff --git a/Project/Gasa.uproject b/Project/Gasa.uproject index 2439597..b33ad17 100644 --- a/Project/Gasa.uproject +++ b/Project/Gasa.uproject @@ -301,10 +301,6 @@ "Name": "VisualStudioSourceCodeAccess", "Enabled": true }, - { - "Name": "GitSourceControl", - "Enabled": true - }, { "Name": "SlateInsights", "Enabled": true diff --git a/Project/Saved/UnrealBuildTool/BuildConfiguration.xml b/Project/Saved/UnrealBuildTool/BuildConfiguration.xml index 261cb7a..ec273d3 100644 --- a/Project/Saved/UnrealBuildTool/BuildConfiguration.xml +++ b/Project/Saved/UnrealBuildTool/BuildConfiguration.xml @@ -1,3 +1,5 @@ + false + diff --git a/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.cpp b/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.cpp index fae9819..119eca9 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.cpp +++ b/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.cpp @@ -8,34 +8,31 @@ UGasaAttributeSet::UGasaAttributeSet() { - InitHealth( 50.f ); + InitHealth( 100.f ); InitMaxHealth( 100.f ); InitMana( 50.f ); InitMaxMana( 50.f ); } -#pragma region Rep Notifies +#pragma region Rep Notifies void UGasaAttributeSet::Client_OnRep_Health( FGameplayAttributeData& PrevHealth ) { // From GAMEPLAYATTRIBUTE_REPNOTIFY static FProperty* UGasaAttributeSetProperty = FindFieldChecked( StaticClass(), GET_MEMBER_NAME_CHECKED( UGasaAttributeSet, Health ) ); GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication( FGameplayAttribute( UGasaAttributeSetProperty ), Health, PrevHealth ); } - void UGasaAttributeSet::Client_OnRep_MaxHealth( FGameplayAttributeData& PrevMaxHealth ) { // From GAMEPLAYATTRIBUTE_REPNOTIFY static FProperty* UGasaAttributeSetProperty = FindFieldChecked( StaticClass(), GET_MEMBER_NAME_CHECKED( UGasaAttributeSet, MaxHealth ) ); GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication( FGameplayAttribute( UGasaAttributeSetProperty ), MaxHealth, PrevMaxHealth ); } - void UGasaAttributeSet::Client_OnRep_Mana( FGameplayAttributeData& PrevMana ) { // From GAMEPLAYATTRIBUTE_REPNOTIFY static FProperty* UGasaAttributeSetProperty = FindFieldChecked( StaticClass(), GET_MEMBER_NAME_CHECKED( UGasaAttributeSet, Mana ) ); GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication( FGameplayAttribute( UGasaAttributeSetProperty ), Mana, PrevMana ); } - void UGasaAttributeSet::Client_OnRep_MaxMana( FGameplayAttributeData& PrevMaxMana ) { // From GAMEPLAYATTRIBUTE_REPNOTIFY @@ -43,6 +40,7 @@ void UGasaAttributeSet::Client_OnRep_MaxMana( FGameplayAttributeData& PrevMaxMan GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication( FGameplayAttribute( UGasaAttributeSetProperty ), MaxMana, PrevMaxMana ); } #pragma endregion Rep Notifies + void UGasaAttributeSet::GetLifetimeReplicatedProps( TArray& OutLifetimeProps ) const { Super::GetLifetimeReplicatedProps( OutLifetimeProps ); diff --git a/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.h b/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.h index 898ea2c..6d4cd2a 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.h +++ b/Project/Source/Gasa/AbilitySystem/GasaAttributeSet.h @@ -23,6 +23,7 @@ public: UGasaAttributeSet(); + UFUNCTION() void Client_OnRep_Health( FGameplayAttributeData& PrevHealth ); UFUNCTION() diff --git a/Project/Source/Gasa/AbilitySystem/GasaAttributeSet_Inlines.h b/Project/Source/Gasa/AbilitySystem/GasaAttributeSet_Inlines.h index bad1e43..3f49d74 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaAttributeSet_Inlines.h +++ b/Project/Source/Gasa/AbilitySystem/GasaAttributeSet_Inlines.h @@ -4,6 +4,7 @@ #include "GasaAttributeSet.h" #include "AbilitySystemComponent.h" +#pragma region Attribute Setters FORCEINLINE void UGasaAttributeSet::SetHealth( float NewVal ) { @@ -40,6 +41,7 @@ void UGasaAttributeSet::SetMaxMana( float NewVal ) AbilityComp->SetNumericAttributeBase( GetMaxManaAttribute(), NewVal ); }; } +#pragma endregion Attribute Setters namespace Gasa { diff --git a/Project/Source/Gasa/AbilitySystem/GasaEffectActor.cpp b/Project/Source/Gasa/AbilitySystem/GasaEffectActor.cpp index 42367ae..e8e8559 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaEffectActor.cpp +++ b/Project/Source/Gasa/AbilitySystem/GasaEffectActor.cpp @@ -10,13 +10,13 @@ AGasaEffectActor::AGasaEffectActor() RootComponent = CreateDefaultSubobject("Root"); } -void AGasaEffectActor::ApplyEffectToTarget(AActor* Target, TSubclassOf EffectClass) +void AGasaEffectActor::ApplyEffectToActor(AActor* Actor, TSubclassOf EffectClass) { - UGasaAbilitySystemComp* AS = GetAbilitySystem(Target, true); + UGasaAbilitySystemComp* AS = GetAbilitySystem(Actor, true); FGameplayEffectContextHandle Context = AS->MakeEffectContext(); - Context.AddSourceObject(Target); + Context.AddSourceObject(Actor); FGameplayEffectSpecHandle Spec = AS->MakeOutgoingSpec( EffectClass, 1.0f, Context ); AS->ApplyGameplayEffectSpecToSelf( * Spec.Data ); diff --git a/Project/Source/Gasa/AbilitySystem/GasaEffectActor.h b/Project/Source/Gasa/AbilitySystem/GasaEffectActor.h index f000ac3..49d3be8 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaEffectActor.h +++ b/Project/Source/Gasa/AbilitySystem/GasaEffectActor.h @@ -1,19 +1,23 @@ #pragma once #include "GasaCommon.h" +#include "Actors/GasaActor.h" +#include "GameFramework/Actor.h" + #include "GasaEffectActor.generated.h" - UCLASS() -class GASA_API AGasaEffectActor : public AActor +class GASA_API AGasaEffectActor : public AGasaActor { GENERATED_BODY() public: - UPROPERTY(EditAnywhere, Category = "Applied Effects") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Gameplay Effects") TSoftClassPtr InstantEffectClass; AGasaEffectActor(); - void ApplyEffectToTarget(AActor* Target, TSubclassOf EffectClass ); + UFUNCTION(BlueprintCallable, Category = "Gameplay Effects") + void ApplyEffectToActor(AActor* Actor, TSubclassOf EffectClass ); }; + diff --git a/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.cpp b/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.cpp index 024e3a2..9b8de52 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.cpp +++ b/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.cpp @@ -4,6 +4,7 @@ #include "GasaAttributeSet.h" #include "GasaAttributeSet_Inlines.h" #include "Components/SphereComponent.h" +#include "Components/StaticMeshComponent.h" AGasaEffectActorDemo::AGasaEffectActorDemo() diff --git a/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.h b/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.h index 852468b..bab5276 100644 --- a/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.h +++ b/Project/Source/Gasa/AbilitySystem/GasaEffectActorDemo.h @@ -1,12 +1,14 @@ #pragma once #include "GasaCommon.h" +#include "Actors/GasaActor.h" +#include "GameFramework/Actor.h" #include "GasaEffectActorDemo.generated.h" // Old demonstration code used before part 37. UCLASS() -class GASA_API AGasaEffectActorDemo : public AActor +class GASA_API AGasaEffectActorDemo : public AGasaActor { GENERATED_BODY() public: diff --git a/Project/Source/Gasa/Actors/CameraMount.h b/Project/Source/Gasa/Actors/CameraMount.h index 60a7b37..e08e6f3 100644 --- a/Project/Source/Gasa/Actors/CameraMount.h +++ b/Project/Source/Gasa/Actors/CameraMount.h @@ -1,10 +1,12 @@ #pragma once +#include "GasaActor.h" #include "GasaCommon.h" +#include "GameFramework/Actor.h" #include "CameraMount.generated.h" UCLASS(Blueprintable) -class GASA_API ACameraMount : public AActor +class GASA_API ACameraMount : public AGasaActor { GENERATED_BODY() public: diff --git a/Project/Source/Gasa/Actors/GasaActor.cpp b/Project/Source/Gasa/Actors/GasaActor.cpp new file mode 100644 index 0000000..e69de29 diff --git a/Project/Source/Gasa/Actors/GasaActor.h b/Project/Source/Gasa/Actors/GasaActor.h new file mode 100644 index 0000000..a6a4ecc --- /dev/null +++ b/Project/Source/Gasa/Actors/GasaActor.h @@ -0,0 +1,33 @@ +#pragma once +#include "Networking/GasaNetLibrary.h" +#include "GameFramework/Actor.h" + +#include "GasaActor.generated.h" + +UCLASS() +class GASA_API AGasaActor : public AActor +{ + GENERATED_BODY() +public: +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime +}; diff --git a/Project/Source/Gasa/Characters/GasaCharacter.cpp b/Project/Source/Gasa/Characters/GasaCharacter.cpp index d224f81..d562697 100644 --- a/Project/Source/Gasa/Characters/GasaCharacter.cpp +++ b/Project/Source/Gasa/Characters/GasaCharacter.cpp @@ -9,7 +9,10 @@ #include "AbilitySystem/GasaAbilitySystemComponent.h" #include "AbilitySystem/GasaAttributeSet.h" +#include "Components/SkeletalMeshComponent.h" +#include "Engine/PostProcessVolume.h" #include "Game/GasaLevelScriptActor.h" +#include "Materials/MaterialInstanceDynamic.h" void AGasaCharacter::SetHighlight(EHighlight Desired) { diff --git a/Project/Source/Gasa/Characters/GasaCharacter.h b/Project/Source/Gasa/Characters/GasaCharacter.h index 6a71200..4e2bd73 100644 --- a/Project/Source/Gasa/Characters/GasaCharacter.h +++ b/Project/Source/Gasa/Characters/GasaCharacter.h @@ -5,6 +5,7 @@ #include "GasaCommon.h" #include "Game/GasaPlayerState.h" +#include "Networking/GasaNetLibrary.h" #include "GasaCharacter.generated.h" @@ -59,6 +60,28 @@ public: AGasaCharacter(); FORCEINLINE AGasaPlayerState* GetGasaPlayerState() { return GetPlayerState(); } + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime #pragma region IAbilitySystem FORCEINLINE UAttributeSet* GetAttributes() { return Attributes; } diff --git a/Project/Source/Gasa/Characters/PlayerCharacter.cpp b/Project/Source/Gasa/Characters/PlayerCharacter.cpp index cfe19cc..1ec3939 100644 --- a/Project/Source/Gasa/Characters/PlayerCharacter.cpp +++ b/Project/Source/Gasa/Characters/PlayerCharacter.cpp @@ -25,10 +25,13 @@ void APlayerCharacter::PossessedBy(AController* NewController) AbilitySystem->InitAbilityActorInfo(PS, this); } - AGasaPlayerController* PC = GetController(); - AGasaHUD* HUD = PC->GetHUD(); - FWidgetControllerData Data = { PC, PS, AbilitySystem, Attributes }; - HUD->InitHostWidget(& Data); + if (IsLocallyControlled()) + { + AGasaPlayerController* PC = GetController(); + AGasaHUD* HUD = PC->GetHUD(); + FWidgetControllerData Data = { PC, PS, AbilitySystem, Attributes }; + HUD->InitHostWidget(& Data); + } } // TODO(Ed): We need to setup Net Slime... diff --git a/Project/Source/Gasa/Game/GasaGameInstance.cpp b/Project/Source/Gasa/Game/GasaGameInstance.cpp index 1b1b707..3424c08 100644 --- a/Project/Source/Gasa/Game/GasaGameInstance.cpp +++ b/Project/Source/Gasa/Game/GasaGameInstance.cpp @@ -1,5 +1,84 @@ #include "GasaGameInstance.h" +#include "Engine/NetDriver.h" +#include "Engine/World.h" +using namespace Gasa; + +#pragma region GameFramework +// TODO(Ed): Make a NetLog + +void UGasaGameInstance::NotifyGameFrameworkClassReady(EGameFrameworkClassFlag ClassReady) +{ + switch (ClassReady) + { + case EGameFrameworkClassFlag::GameMode: + GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::GameMode; + NetLog("Gameplay Framework class ready: Game State", ELogV::Log, LogGasaNet ); + break; + case EGameFrameworkClassFlag::GameState: + GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::GameState; + NetLog("Gameplay Framework class ready: Game State", ELogV::Log, LogGasaNet ); + break; + case EGameFrameworkClassFlag::PlayerController: + GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::PlayerController; + NetLog("Gameplay Framework class ready: Player Controller", ELogV::Log, LogGasaNet); + break; + case EGameFrameworkClassFlag::PlayerState: + GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::PlayerState; + NetLog("Gameplay Framework class ready: Player State", ELogV::Log, LogGasaNet); + break; + case EGameFrameworkClassFlag::Levels: + GameFrameworkClassesState |= (uint32)EGameFrameworkClassFlag::Levels; + NetLog("Gameplay Framework class ready: Levels", ELogV::Log, LogGasaNet); + break; + } + ProcessGameFrameworkState(); +} + +void UGasaGameInstance::ProcessGameFrameworkState() +{ + switch (GameFrameworkState) + { + case EGameFrameworkState::Uninitialized: + { + uint32 InitializedFlags = + (uint32)EGameFrameworkClassFlag::GameState | + (uint32)EGameFrameworkClassFlag::PlayerController | + (uint32)EGameFrameworkClassFlag::PlayerState | + (uint32)EGameFrameworkClassFlag::Levels + ; + + if (GetWorld()->NetDriver == nullptr || GetWorld()->NetDriver->IsServer()) + { + InitializedFlags |= (uint32)EGameFrameworkClassFlag::GameMode; + } + + FString MsgGM = "GameMode : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::GameMode )) ); + FString MsgGS = "GameState : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::GameState )) ); + FString MsgPC = "PlayerController: " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::PlayerController ) )); + FString MsgPS = "PlayerState : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::PlayerState ) )); + FString MsgL = "Levels : " + FString::FromInt( Bitfield_IsSet( GameFrameworkClassesState, scast(uint32, EGameFrameworkClassFlag::Levels ) )); + + NetLog(MsgGM, ELogV::Log, LogGasaNet); + NetLog(MsgGS, ELogV::Log, LogGasaNet); + NetLog(MsgPC, ELogV::Log, LogGasaNet); + NetLog(MsgPS, ELogV::Log, LogGasaNet); + NetLog(MsgL, ELogV::Log, LogGasaNet); + + if (GameFrameworkClassesState == InitializedFlags) + { + GameFrameworkState = EGameFrameworkState::Initialized; + NetLog("Gameplay Framework initialized"); + + Event_OnGameFrameworkInitialized.Broadcast(); + } + break; + } + } +} +#pragma endregion GameFramework + +#pragma region GameInstance void UGasaGameInstance::Init() { Super::Init(); @@ -7,5 +86,6 @@ void UGasaGameInstance::Init() DevOptionsCache.CachedDevOptions(); using namespace Gasa; - Log(FString::Printf(TEXT("UObject Size: %d RT: %d"), sizeof(UObject), UObject::StaticClass()->PropertiesSize )); + NetLog(FString::Printf(TEXT("UObject Size: %d RT: %d"), sizeof(UObject), UObject::StaticClass()->PropertiesSize )); } +#pragma region GameInstance diff --git a/Project/Source/Gasa/Game/GasaGameInstance.h b/Project/Source/Gasa/Game/GasaGameInstance.h index 4c50a68..fb23c69 100644 --- a/Project/Source/Gasa/Game/GasaGameInstance.h +++ b/Project/Source/Gasa/Game/GasaGameInstance.h @@ -1,10 +1,32 @@ #pragma once -#include "GasaCommon.h" +#include "Networking/GasaNetLibrary.h" #include "GasaDevOptionsCache.h" +#include "Engine/Engine.h" +#include "Engine/GameInstance.h" #include "GasaGameInstance.generated.h" +UENUM(BlueprintType) +enum class EGameFrameworkClassFlag : uint8 +{ + None = 0 UMETA(Hidden), + GameMode = 1 << 0, + GameState = 1 << 1, + PlayerController = 1 << 2, + PlayerState = 1 << 3, + Levels = 1 << 4 +}; + +UENUM(BlueprintType) +enum class EGameFrameworkState : uint8 +{ + Initialized, + Uninitialized +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnGameFrameworkInitializedSig); + UCLASS(Blueprintable) class GASA_API UGasaGameInstance : public UGameInstance { @@ -14,6 +36,55 @@ public: UPROPERTY(VisibleAnywhere, Category="Dev Cache") FGasaDevOptionsCache DevOptionsCache; +#pragma region GameFramework + UPROPERTY(BlueprintAssignable, Category = "GameFramework") + FOnGameFrameworkInitializedSig Event_OnGameFrameworkInitialized; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameFramework") + EGameFrameworkState GameFrameworkState; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameFramework", meta=(Bitmask, BitmaskEnum = EGameFrameworkClassFlag)) + int32 GameFrameworkClassesState; + + UFUNCTION(BlueprintCallable, Category="GameFramework") + void ClearGameplayFrameworkState() { + Gasa::Log("Clearing game framework state", EGasaVerbosity::Log, LogGasaNet ); // TODO(Ed): Make a default NetLog + GameFrameworkClassesState = scast(int32, EGameFrameworkClassFlag::None); + GameFrameworkState = EGameFrameworkState::Uninitialized; + } + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GameFramework") + FORCEINLINE bool IsGameFrameworkInitialized() { return GameFrameworkState == EGameFrameworkState::Initialized; } + + UFUNCTION(BlueprintCallable, Category="GameFramework") + void NotifyGameFrameworkClassReady(EGameFrameworkClassFlag ClassReady); + + UFUNCTION(BlueprintCallable, Category = "GameFramework", meta=(BlueprintProtected)) + void ProcessGameFrameworkState(); +#pragma endregion GameFramework + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime + #pragma region GameInstance void Init() override; #pragma endregion GameInstance diff --git a/Project/Source/Gasa/Game/GasaGameMode.cpp b/Project/Source/Gasa/Game/GasaGameMode.cpp index 67304d3..2861ca5 100644 --- a/Project/Source/Gasa/Game/GasaGameMode.cpp +++ b/Project/Source/Gasa/Game/GasaGameMode.cpp @@ -1,2 +1,454 @@ #include "GasaGameMode.h" +#include "GasaGameInstance.h" +#include "GasaGameState.h" +#include "GasaPlayerController.h" +#include "GasaPlayerState.h" +#include "Engine/Player.h" +#include "GameFramework/GameSession.h" +#include "GameFramework/GameState.h" +#include "GameFramework/PlayerState.h" +#include "OnlineSubsystem.h" +using namespace Gasa; + +#pragma region Game Framework +void AGasaGameMode::OnGameFrameworkInitialized() +{ + NetLog("OnGameFrameworkInitialized"); + + BP_OnGameFrameworkInitialized(); + if (MatchState == MatchState::WaitingToStart && ReadyToStartMatch()) + StartMatch(); +} + +void AGasaGameMode::OwningClient_OnGameFrameworkInitialized(AGasaPlayerController* PC) +{ + NetLog("OwningClient_OnGameFrameworkInitialized"); + HandleStartingNewPlayer(PC); +} +#pragma endregion Game Framework + +#pragma region GameMode +void AGasaGameMode::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) +{ + Super::HandleStartingNewPlayer_Implementation(NewPlayer); + if (NewPlayer) + NewPlayer->bBlockInput = false; +} + +bool AGasaGameMode::ReadyToStartMatch_Implementation() +{ + if ( ! GetGameInstance()->IsGameFrameworkInitialized()) + { + return false; + } + + // Super::ReadyToStartMatch(); + { + // If bDelayed Start is set, wait for a manual match start + if (bDelayedStart) + { + return false; + } + + // By default start when we have > 0 players + if (GetMatchState() == MatchState::WaitingToStart) + { + if (NumPlayers + NumBots > 0) + { + return true; + } + } + return false; + } +} +#pragma endregion GameMode + +#pragma region GameModeBase +void AGasaGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + NetLog("EndPlay"); +} + +void AGasaGameMode::GenericPlayerInitialization(AController* C) +{ + NetLog("GenericPlayerInitialization: " + C->GetName()); + + // AGameMode::GenericPlayerInitialization(C); + { + APlayerController* PC = Cast(C); + if (PC != nullptr) + { + // Moved to: void AGasaGameMode::SetPlayerDefaults(APawn* PlayerPawn) + // InitializeHUDForPlayer(Cast(C)); + + // Notify the game that we can now be muted and mute others + UpdateGameplayMuteList(PC); + + if (GameSession != nullptr) + { + // Tell the player to enable voice by default or use the push to talk method + PC->ClientEnableNetworkVoice(!GameSession->RequiresPushToTalk()); + } + + ReplicateStreamingStatus(PC); + + bool HidePlayer = false, HideHUD = false, DisableMovement = false, DisableTurning = false; + + // Check to see if we should start in cinematic mode (matinee movie capture) + if (ShouldStartInCinematicMode(PC, HidePlayer, HideHUD, DisableMovement, DisableTurning)) + { + PC->SetCinematicMode(true, HidePlayer, HideHUD, DisableMovement, DisableTurning); + } + } + } +} + +TSubclassOf AGasaGameMode::GetPlayerControllerClassToSpawnForSeamlessTravel(APlayerController* PreviousPlayerController) +{ + NetLog("GetPlayerControllerClassToSpawnForSeamlessTravel: " + PreviousPlayerController->GetName()); + return Super::GetPlayerControllerClassToSpawnForSeamlessTravel(PreviousPlayerController); +} + +void AGasaGameMode::HandleSeamlessTravelPlayer(AController*& Controller) +{ + NetLog("HandleSeamlessTravelPlayer: " + Controller->GetName()); + + // Super::HandleSeamlessTravelPlayer( C ); + + UE_LOG(LogGameMode, Log, TEXT(">> GameMode::HandleSeamlessTravelPlayer: %s "), * Controller->GetName()); + + APlayerController* PC = Cast(Controller); + UClass* PCClassToSpawn = GetPlayerControllerClassToSpawnForSeamlessTravel(PC); + // FUniqueNetIdRepl StoredNativePlatformUniqueNetId = C->GetPlayerState()->NativePlatformUniqueNetId; + + if (PC && PC->GetClass() != PCClassToSpawn) + { + if (PC->Player != nullptr) + { + // We need to spawn a new PlayerController to replace the old one + APlayerController* const NewPC = SpawnPlayerControllerCommon(PC->IsLocalPlayerController() + ? ROLE_SimulatedProxy + : ROLE_AutonomousProxy, PC->GetFocalLocation(), PC->GetControlRotation(), PCClassToSpawn); + if (NewPC == nullptr) + { + NetLog(FString::Printf( + TEXT("Failed to spawn new PlayerController for %s (old class %s)"), *PC->GetHumanReadableName(), *PC->GetClass()->GetName()) + , ELogV::Warning + ); + PC->Destroy(); + return; + } + else + { + PC->SeamlessTravelTo(NewPC); + NewPC->SeamlessTravelFrom(PC); + SwapPlayerControllers(PC, NewPC); + PC = NewPC; + Controller = NewPC; + } + } + else + { + PC->Destroy(); + } + } + else + { + // clear out data that was only for the previous game + Controller->PlayerState->Reset(); + // create a new PlayerState and copy over info; this is necessary because the old GameMode may have used a different PlayerState class + APlayerState* OldPlayerState = Controller->PlayerState; + Controller->InitPlayerState(); + OldPlayerState->SeamlessTravelTo( Controller->PlayerState); + // we don"t need the old PlayerState anymore + //@fixme: need a way to replace PlayerStates that doesn't cause incorrect "player left the game"/"player entered the game" messages + OldPlayerState->Destroy(); + } + + InitSeamlessTravelPlayer(Controller); + + // Initialize hud and other player details, shared with PostLogin + GenericPlayerInitialization(Controller); + + if (AGasaGameState* GS = GetGameState()) + { +#if 0 + UGasaGameInstance* GI = GetGameInstance(); + int32 NumConnections = GI->SessionSettings.bPublicGame + ? GI->SessionSettings.PublicConnections + : GI->SessionSettings.PrivateConnections; + if (GS->OnlinePlayers.Num() < NumConnections) + GS->OnlinePlayers.Init( nullptr, NumConnections ); + + for (AGasaPlayerState* & PS : GS->OnlinePlayers) + { + if (PS == nullptr) + { + PS = C->GetPlayerState(); + PS->SgID = GS->OnlinePlayers.Find( PS ); + break; + } + } +#endif + } + + // Controller->GetPlayerState()->NativePlatformUniqueNetId = StoredNativePlatformUniqueNetId; + NetLog(FString::Printf(TEXT("HandleSeamlessTravelPlayer: %s"), * Controller->GetName()) ); + +#if 0 + if (PC) + PC->Event_NetOwner_OnGameplayFrameworkInitialized.AddDynamic(this, &ThisClass::OwningClient_OnGameFrameworkInitialized); +#endif +} + +void AGasaGameMode::InitializeHUDForPlayer_Implementation(APlayerController* NewPlayer) +{ + // Super::InitializeHUDForPlayer_Implementation(NewPlayer); + NewPlayer->ClientSetHUD(HUDClass); +} + +void AGasaGameMode::InitSeamlessTravelPlayer(AController* NewController) +{ + if (NewController) + NewController->bBlockInput = true; + + // GameMode::InitSeamlessTravelPlayer(NewController); + { + // AGameModeBase::InitSeamlessTravelPlayer(NewController); + { + APlayerController* NewPC = Cast(NewController); + + FString ErrorMessage; + if (!UpdatePlayerStartSpot(NewController, TEXT(""), ErrorMessage)) + NetLog(FString::Printf( TEXT("InitSeamlessTravelPlayer: %s"), *ErrorMessage), ELogV::Warning); + + if (NewPC != nullptr) + { + NewPC->PostSeamlessTravel(); + + if (MustSpectate(NewPC)) + { + NewPC->StartSpectatingOnly(); + } + else + { + NewPC->bPlayerIsWaiting = true; + NewPC->ChangeState(NAME_Spectating); + NewPC->ClientGotoState(NAME_Spectating); + } + } + } + + APlayerController* NewPC = Cast(NewController); + + if (NewPC != nullptr) + { + SetSeamlessTravelViewTarget(NewPC); + + if (!MustSpectate(NewPC)) + { + NumPlayers++; + NumTravellingPlayers--; + } + } + else + { + NumBots++; + } + } + NetLog("InitSeamlessTravelPlayer: " + NewController->GetName()); +} + +void AGasaGameMode::HandleMatchAborted() +{ + Super::HandleMatchAborted(); + NetLog("HandleMatchAborted"); +} + +void AGasaGameMode::Logout(AController* Exiting) +{ + Super::Logout(Exiting); + + Event_OnLogout.Broadcast(Cast(Exiting)); + NetLog("User Logged out: " + Exiting->GetName()); + + if (AGasaGameState* GS = Cast(GetWorld()->GetGameState())) + { +#if 0 + int32 Index = GS->OnlinePlayers.Find(Exiting->GetPlayerState()); + if (Index == INDEX_NONE) + { + NetLog("Could not find exiting player state in online players!", ELogV::Error); + return; + } + GS->OnlinePlayers[Index] = nullptr; +#endif + +#if 0 + IOnlineSessionPtr const SessionInterface = Online::GetSessionInterface(GetWorld()); + if ( ! SessionInterface.IsValid()) + { + NetLog("DestoryCurrentSession: Could not get the session interface.", ELogV::Warning); + return; + } +#endif + } +} + +void AGasaGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) +{ + Super::PreLogin(Options, Address, UniqueId, ErrorMessage); + // TODO(Ed): Refuse players if the server is full. +} + +void AGasaGameMode::PostLogin(APlayerController* NewPlayer) +{ + // AGameMode::PostLogin(NewPlayer); + { + UWorld* World = GetWorld(); + + // Update player count + if (MustSpectate(NewPlayer)) + NumSpectators++; + else if (World->IsInSeamlessTravel() || NewPlayer->HasClientLoadedCurrentWorld()) + NumPlayers++; + else + NumTravellingPlayers++; + + // Save network address for re-associating with reconnecting player, after stripping out port number + FString Address = NewPlayer->GetPlayerNetworkAddress(); + int32 Pos = Address.Find(TEXT(":"), ESearchCase::CaseSensitive); + + NewPlayer->PlayerState->SavedNetworkAddress = (Pos > 0) ? Address.Left(Pos) : Address; + + // Check if this player is reconnecting and already has PlayerState + FindInactivePlayer(NewPlayer); + } + + // AGameModeBase::PostLogin(NewPlayer) + { + // Runs shared initialization that can happen during seamless travel as well + GenericPlayerInitialization(NewPlayer); + + // Perform initialization that only happens on initially joining a server + NewPlayer->ClientCapBandwidth(NewPlayer->Player->CurrentNetSpeed); + + if (MustSpectate(NewPlayer)) + { + NewPlayer->ClientGotoState(NAME_Spectating); + } + else + { + // If NewPlayer is not only a spectator and has a valid ID, add him as a user to the replay. + FUniqueNetIdRepl const& + NewPlayerStateUniqueId = NewPlayer->PlayerState->GetUniqueId(); + if (NewPlayerStateUniqueId.IsValid()) + { + GetGameInstance()->AddUserToReplay(NewPlayerStateUniqueId.ToString()); + } + } + + if (GameSession) + { + GameSession->PostLogin(NewPlayer); + } + } + + if (AGasaGameState* GS = GetGameState()) + { + UGasaGameInstance* GI = GetGameInstance(); + + // int32 numconnections = gi->sessionsettings.bpublicgame + // ? GI->SessionSettings.PublicConnections : GI->SessionSettings.PrivateConnections; + +#if 0 + if (GS->OnlinePlayers.Num() < NumConnections) + { + GS->OnlinePlayers.Init( nullptr, NumConnections ); + } + + for (AGasaPlayerState* & PS : GS->OnlinePlayers) + { + if (PS == nullptr) + { + PS = NewPlayer->GetPlayerState(); + PS->GasaID = GS->OnlinePlayers.Find( PS ); + break; + } + } +#endif + } + + // cont. AGameModeBase::PostLogin(NewPlayer) + { + // Notify Blueprints that a new player has logged in. Calling it here, because this is the first time that the PlayerController can take RPCs + DispatchPostLogin(NewPlayer); + } + + AGasaPlayerController* PC = Cast(NewPlayer); + // if (PC) + // PC->Event_OnNetOwner_GameplayFrameworkInitialized.AddDynamic(this, &ThisClass::OwningClient_OnGameFrameworkInitialized); +} + +void AGasaGameMode::PostSeamlessTravel() +{ + Super::PostSeamlessTravel(); + NetLog("PostSeamlessTravel"); +} + +void AGasaGameMode::SetPlayerDefaults(APawn* PlayerPawn) +{ + InitializeHUDForPlayer(Cast(PlayerPawn->GetController())); + + // Super::SetPlayerDefaults(PlayerPawn); + { + PlayerPawn->SetPlayerDefaults(); + } +} + +void AGasaGameMode::SetSeamlessTravelViewTarget(APlayerController* PC) +{ + Super::SetSeamlessTravelViewTarget(PC); + NetLog("SetSeamlessTravelViewTarget"); +} + +void AGasaGameMode::StartPlay() +{ + if (MatchState == MatchState::EnteringMap) + SetMatchState(MatchState::WaitingToStart); + + // Start match is deferred until the framework is considered initialized. + NetLog("StartPlay"); + + UGasaGameInstance* GI = GetGameInstance(); + GI->Event_OnGameFrameworkInitialized.AddDynamic(this, &ThisClass::OnGameFrameworkInitialized); + GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::GameMode); + + // Not called yet, will wait for initialization of the framework. + //Super::StartPlay(); +} + +void AGasaGameMode::StartToLeaveMap() +{ + NetLog("StartToLeaveMap"); + Super::StartToLeaveMap(); +} + +void AGasaGameMode::SwapPlayerControllers(APlayerController* OldPC, APlayerController* NewPC) +{ + NetLog("SwapPlayerControllers"); + NetLog("Old: " + OldPC->GetName()); + NetLog("New: " + NewPC->GetName()); + + Super::SwapPlayerControllers(OldPC, NewPC); +} + +APlayerController* AGasaGameMode::SpawnPlayerControllerCommon(ENetRole InRemoteRole, FVector const& SpawnLocation, FRotator const& SpawnRotation, + TSubclassOf InPlayerControllerClass) +{ + NetLog("SpawnPlayerControllerCommon"); + return Super::SpawnPlayerControllerCommon(InRemoteRole, SpawnLocation, SpawnRotation, InPlayerControllerClass); +} +#pragma endregion GameModeBase diff --git a/Project/Source/Gasa/Game/GasaGameMode.h b/Project/Source/Gasa/Game/GasaGameMode.h index cd0ba2e..0be8d82 100644 --- a/Project/Source/Gasa/Game/GasaGameMode.h +++ b/Project/Source/Gasa/Game/GasaGameMode.h @@ -1,28 +1,102 @@ -#pragma once +#pragma once #include "GameFramework/GameMode.h" #include "GasaCommon.h" +#include "Networking/GasaNetLibrary_Inlines.h" +#include "Engine/Engine.h" #include "GasaGameMode.generated.h" -UCLASS(Blueprintable) +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLogoutSig, AGasaPlayerController*, PC); + +UCLASS( Blueprintable ) class GASA_API AGasaGameMode : public AGameMode { GENERATED_BODY() public: + UPROPERTY(EditAnywhere, BlueprintAssignable) + FOnLogoutSig Event_OnLogout; + +#pragma region GameFramework + UFUNCTION() + void OnGameFrameworkInitialized(); + + UFUNCTION() + void OwningClient_OnGameFrameworkInitialized(AGasaPlayerController* PC); + + UFUNCTION(BlueprintCallable, meta=(DisplayName = "On Game Framework Initialized")) + void BP_OnGameFrameworkInitialized(); +#pragma endregion GameFramework + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime + +#pragma region GameMode + void HandleMatchAborted() override; + void HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer) override; + + bool ReadyToStartMatch_Implementation() override; +#pragma endregion GameMode + +#pragma region GameModeBase + void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + void GenericPlayerInitialization(AController* C) override; + + TSubclassOf GetPlayerControllerClassToSpawnForSeamlessTravel(APlayerController* PreviousPlayerController) override; + + void HandleSeamlessTravelPlayer(AController*& C) override; + + void InitializeHUDForPlayer_Implementation(APlayerController* NewPlayer) override; + void InitSeamlessTravelPlayer(AController* NewController) override; + + void Logout(AController* Exiting) override; + + void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override; + void PostLogin(APlayerController* NewPlayer) override; + void PostSeamlessTravel() override; + + void SetPlayerDefaults(APawn* PlayerPawn) override; + void SetSeamlessTravelViewTarget(APlayerController* PC) override; + + void StartPlay() override; + void StartToLeaveMap() override; + + void SwapPlayerControllers(APlayerController* OldPC, APlayerController* NewPC) override; + + APlayerController* SpawnPlayerControllerCommon(ENetRole InRemoteRole, FVector const& SpawnLocation, FRotator const& SpawnRotation, TSubclassOf InPlayerControllerClass) override; +#pragma endregion GameModeBase }; namespace Gasa { - inline - AGasaGameMode* GetGameMode(UObject* Context) + inline AGasaGameMode* GetGameMode( UObject* Context ) { - UWorld* World = GEngine->GetWorldFromContextObject(Context, EGetWorldErrorMode::LogAndReturnNull); - if (World == nullptr) + UWorld* World = GEngine->GetWorldFromContextObject( Context, EGetWorldErrorMode::LogAndReturnNull ); + if ( World == nullptr ) { - Log("World is null... are you running in a proper context?", ELogV::Error); + Log( "World is null... are you running in a proper context?", ELogV::Error ); return nullptr; } - return Cast(World->GetAuthGameMode()); + return Cast( World->GetAuthGameMode() ); } } diff --git a/Project/Source/Gasa/Game/GasaGameState.cpp b/Project/Source/Gasa/Game/GasaGameState.cpp index f8b1a60..0a35308 100644 --- a/Project/Source/Gasa/Game/GasaGameState.cpp +++ b/Project/Source/Gasa/Game/GasaGameState.cpp @@ -2,6 +2,10 @@ #include "CogAll.h" #include "CogWindowManager.h" +#include "GasaPlayerState.h" +#include "GasaGameInstance.h" +#include "Net/UnrealNetwork.h" +using namespace Gasa; AGasaGameState::AGasaGameState() { @@ -9,11 +13,92 @@ AGasaGameState::AGasaGameState() PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.SetTickFunctionEnable(true); PrimaryActorTick.bStartWithTickEnabled = true; + + // Replication + + bReplicates = true; + bNetLoadOnClient = false; + NetDormancy = DORM_Awake; + NetCullDistanceSquared = NetCullDist_Default; + NetUpdateFrequency = 10.0f; + MinNetUpdateFrequency = 1.0f; + NetPriority = 5.0f; } -#pragma region GameState +#pragma region GameFramework +void AGasaGameState::OnGameFrameworkInitialized() +{ + NetLog("Received gameplay framework initialization."); + + if (IsServer()) + { + if (PlayerArray.Num() > 0) + { + ListenServerHost = Cast(PlayerArray[0]); + } + else + { + NetLog("Was not able to assign HostingPlayer!", ELogV::Error); + } + } + + BP_OnGameFrameworkInitialized(); +} +#pragma endregion GameFramework + +#pragma region Networking +void AGasaGameState::Client_OnRep_OnlinePlayers() +{ +} +#pragma endregion Networking + +#pragma region Seamless Travel +void AGasaGameState::Multicast_R_NotifySeamlessTravelEnd_Implementation() +{ + NetLog("Multicast_R_NotifySeamlessTravelEnd_Implementation"); + BP_Event_OnSeamlessTravelEnd.Broadcast(); + Event_OnSeamlessTravelEnd.Broadcast(); +} +#pragma endregion Seamless Travel + +#pragma region GameStateBase +void AGasaGameState::HandleBeginPlay() +{ + Super::HandleBeginPlay(); + NetLog("HandleBeginPlay: Directly called from GM"); +} + +void AGasaGameState::SeamlessTravelTransitionCheckpoint(bool bToTransitionMap) +{ + Super::SeamlessTravelTransitionCheckpoint(bToTransitionMap); + + NetLog("SeamlessTravelTransitionCheckpoint"); + NetLog(FString("ToTransitionMap: ") + FString(bToTransitionMap ? "true" : "false")); + + if (bToTransitionMap) + { + Event_OnSeamlessTravelStart.Broadcast(); + } + else + { + Multicast_R_NotifySeamlessTravelEnd(); + } +} +#pragma endregion GameStateBase + +#pragma region Actor void AGasaGameState::BeginPlay() { + Super::BeginPlay(); + + NetLog("BeginPlay"); + + // Notified as initialized here as any possible components should also be initialized by this point. + UGasaGameInstance* + GI = GetGameInstance(); + GI->Event_OnGameFrameworkInitialized.AddDynamic(this, & ThisClass::OnGameFrameworkInitialized); + GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::GameState); + #if ENABLE_COG CogWindowManager = NewObject(this); CogWindowManagerRef = CogWindowManager; @@ -23,6 +108,28 @@ void AGasaGameState::BeginPlay() #endif //ENABLE_COG } +void AGasaGameState::PostInitializeComponents() +{ + NetLog("PostInitializeComponents"); + + Super::PostInitializeComponents(); + + if ( ! GetWorld()->IsEditorWorld() && IsServer()) + { + OnlinePlayers.Empty(); +#if 0 + const auto GI = Cast(GetGameInstance()); + if (GI != nullptr) + { + int32 NumConnections = GI->SessionSettings.bPublicGame + ? GI->SessionSettings.PublicConnections + : GI->SessionSettings.PrivateConnections; + OnlinePlayers.Init(nullptr, NumConnections); + } +#endif + } +} + void AGasaGameState::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); @@ -31,4 +138,14 @@ void AGasaGameState::Tick(float DeltaSeconds) CogWindowManager->Tick(DeltaSeconds); #endif //ENABLE_COG } -#pragma endregion GameState +#pragma endregion Actor + +#pragma region UObject +void AGasaGameState::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AGasaGameState, ListenServerHost); + DOREPLIFETIME(AGasaGameState, OnlinePlayers); +} +#pragma endregion UObject diff --git a/Project/Source/Gasa/Game/GasaGameState.h b/Project/Source/Gasa/Game/GasaGameState.h index b8a9e37..cf9fa6b 100644 --- a/Project/Source/Gasa/Game/GasaGameState.h +++ b/Project/Source/Gasa/Game/GasaGameState.h @@ -1,46 +1,116 @@ -#pragma once - #include "GameFramework/GameState.h" #include "GasaCommon.h" +#include "Engine/Engine.h" +#include "Networking/GasaNetLibrary.h" #include "GasaGameState.generated.h" -UCLASS(Blueprintable) +DECLARE_MULTICAST_DELEGATE( FOnTravelDelegate ); +DECLARE_DYNAMIC_MULTICAST_DELEGATE( FOnTravelSig ); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerCharacterReadySig, APlayerCharacter*, Character); + +UCLASS( Blueprintable ) class GASA_API AGasaGameState : public AGameState { GENERATED_BODY() public: #pragma region Cog - // To make sure it doesn't get garbage collected. - UPROPERTY() - TObjectPtr CogWindowManagerRef; - + // To make sure it doesn't get garbage collected. + UPROPERTY() + TObjectPtr CogWindowManagerRef; + #if ENABLE_COG TObjectPtr CogWindowManager; -#endif // ENABLE_COG +#endif +// ENABLE_COG #pragma endregion Cog AGasaGameState(); + +#pragma region GameFramework + UFUNCTION() + void OnGameFrameworkInitialized(); + + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta=(DisplayName = "Game Framework Initialized")) + void BP_OnGameFrameworkInitialized(); +#pragma endregion GameFramework + +#pragma region Networking + UPROPERTY(Replicated, BlueprintReadOnly) + AGasaPlayerState* ListenServerHost; + + UPROPERTY(ReplicatedUsing = "Client_OnRep_OnlinePlayers", BlueprintReadOnly) + TArray OnlinePlayers; + + UFUNCTION() + void Client_OnRep_OnlinePlayers(); +#pragma endregion Networking + +#pragma region Seamless Travel + UPROPERTY(BlueprintAssignable) + FOnTravelSig Event_OnSeamlessTravelStart; + + UPROPERTY(BlueprintAssignable, meta=(DisplayName="Event: On Seamless Travel End")) + FOnTravelSig BP_Event_OnSeamlessTravelEnd; + + FOnTravelDelegate Event_OnSeamlessTravelEnd; + + UFUNCTION(NetMulticast, Reliable) + void Multicast_R_NotifySeamlessTravelEnd(); +#pragma endregion Seamless Travel + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime #pragma region GameState - void BeginPlay() override; + void HandleBeginPlay() override; +#pragma endregion GameState - void Tick(float DeltaSeconds) override; -#pragma endregion GameState +#pragma region GameStateBase + void SeamlessTravelTransitionCheckpoint(bool bToTransitionMap) override; +#pragma endregion GameStateBase + +#pragma region Actor + void BeginPlay() override; + void PostInitializeComponents() override; + void Tick( float DeltaSeconds ) override; +#pragma endregion Actor + +#pragma region UObject + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; +#pragma endregion UObject }; namespace Gasa { - inline - AGasaGameState* GetGameState(UObject* Context) + inline AGasaGameState* GetGameState( UObject* Context ) { - UWorld* World = GEngine->GetWorldFromContextObject(Context, EGetWorldErrorMode::LogAndReturnNull); - if (World == nullptr) + UWorld* World = GEngine->GetWorldFromContextObject( Context, EGetWorldErrorMode::LogAndReturnNull ); + if ( World == nullptr ) { - Log("World is null... are you running in a proper context?", ELogV::Error); + Log( "World is null... are you running in a proper context?", ELogV::Error ); return nullptr; } - return Cast(World->GetGameState()); + return Cast( World->GetGameState() ); } } diff --git a/Project/Source/Gasa/Game/GasaLevelScriptActor.cpp b/Project/Source/Gasa/Game/GasaLevelScriptActor.cpp index ec21511..904a899 100644 --- a/Project/Source/Gasa/Game/GasaLevelScriptActor.cpp +++ b/Project/Source/Gasa/Game/GasaLevelScriptActor.cpp @@ -1,11 +1,39 @@ #include "GasaLevelScriptActor.h" #include "GasaDevOptions.h" +#include "GasaGameInstance.h" +#include "Engine/PostProcessVolume.h" #include "Kismet/GameplayStatics.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceDynamic.h" +using namespace Gasa; +#pragma region Game Framework +AGasaLevelScriptActor::AGasaLevelScriptActor() +{ + // Replication + + bReplicates = true; + bNetLoadOnClient = true; + NetDormancy = DORM_Awake; + NetCullDistanceSquared = NetCullDist_Default; + NetUpdateFrequency = 10.0f; + MinNetUpdateFrequency = 1.0f; + NetPriority = 1.0f; +} + +void AGasaLevelScriptActor::OnGameFrameworkInitialized() +{ + NetLog("Received game framework initialization."); + BP_OnGameFrameworkInitialized(); +} +#pragma endregion Game Framework + +#pragma region Actor void AGasaLevelScriptActor::BeginPlay() { Super::BeginPlay(); + NetLog("BeginPlay"); using namespace Gasa; @@ -21,4 +49,12 @@ void AGasaLevelScriptActor::BeginPlay() PPV->Settings.WeightedBlendables.Array[0].Object = MID; break; } + + UGasaGameInstance* GI = GetGameInstance(); + if(GI) + GI->Event_OnGameFrameworkInitialized.AddUniqueDynamic(this, & ThisClass::OnGameFrameworkInitialized); + + if (!bOverrideGameplayFrameworkReady) + GI->NotifyGameFrameworkClassReady(EGameFrameworkClassFlag::Levels); } +#pragma endregion Actor diff --git a/Project/Source/Gasa/Game/GasaLevelScriptActor.h b/Project/Source/Gasa/Game/GasaLevelScriptActor.h index f5447d4..0463081 100644 --- a/Project/Source/Gasa/Game/GasaLevelScriptActor.h +++ b/Project/Source/Gasa/Game/GasaLevelScriptActor.h @@ -1,8 +1,9 @@ #pragma once +#include "GasaCommon.h" +#include "Engine/Engine.h" #include "Engine/LevelScriptActor.h" - -#include "GasaCommon.h" +#include "Networking/GasaNetLibrary.h" #include "GasaLevelScriptActor.generated.h" @@ -14,6 +15,41 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Post Process") TObjectPtr GlobalPPV; + AGasaLevelScriptActor(); + +#pragma region GameFramework + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + bool bOverrideGameplayFrameworkReady = false; + + UFUNCTION() + void OnGameFrameworkInitialized(); + + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta=(DisplayName="On Game Framework Initialized")) + void BP_OnGameFrameworkInitialized(); +#pragma endregion GameFramework + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime + #pragma region Actor void BeginPlay() override; #pragma region endActor diff --git a/Project/Source/Gasa/Game/GasaPlayerController.cpp b/Project/Source/Gasa/Game/GasaPlayerController.cpp index 994de32..bb713e6 100644 --- a/Project/Source/Gasa/Game/GasaPlayerController.cpp +++ b/Project/Source/Gasa/Game/GasaPlayerController.cpp @@ -155,19 +155,22 @@ void AGasaPlayerController::BeginPlay() { Super::BeginPlay(); - check(IMC); - - UEnhancedInputLocalPlayerSubsystem* - EILP_Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); - check(EILP_Subsystem); - EILP_Subsystem->AddMappingContext(IMC, 0); + if (IsLocalController()) { - bShowMouseCursor = true; - DefaultMouseCursor = EMouseCursor::Default; - FInputModeGameAndUI MouseMode; - MouseMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); - MouseMode.SetHideCursorDuringCapture(false); - SetInputMode(MouseMode); + check(IMC); + + UEnhancedInputLocalPlayerSubsystem* + EILP_Subsystem = ULocalPlayer::GetSubsystem(GetLocalPlayer()); + check(EILP_Subsystem); + EILP_Subsystem->AddMappingContext(IMC, 0); + { + bShowMouseCursor = true; + DefaultMouseCursor = EMouseCursor::Default; + FInputModeGameAndUI MouseMode; + MouseMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); + MouseMode.SetHideCursorDuringCapture(false); + SetInputMode(MouseMode); + } } } diff --git a/Project/Source/Gasa/Game/GasaPlayerController.h b/Project/Source/Gasa/Game/GasaPlayerController.h index 9674b86..7fcf542 100644 --- a/Project/Source/Gasa/Game/GasaPlayerController.h +++ b/Project/Source/Gasa/Game/GasaPlayerController.h @@ -1,7 +1,9 @@ #pragma once #include "GasaCommon.h" +#include "Engine/Engine.h" #include "GameFramework/PlayerController.h" +#include "Networking/GasaNetLibrary.h" #include "GasaPlayerController.generated.h" @@ -51,12 +53,33 @@ public: AGasaPlayerController(); inline AGasaPlayerState* GetPlayerState(); + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime #pragma region PlayerController void SpawnDefaultHUD() override; void OnPossess(APawn* InPawn) override; - void OnUnPossess() override; void PlayerTick(float DeltaTime) override; diff --git a/Project/Source/Gasa/Game/GasaPlayerState.cpp b/Project/Source/Gasa/Game/GasaPlayerState.cpp index 69d6747..c5ba382 100644 --- a/Project/Source/Gasa/Game/GasaPlayerState.cpp +++ b/Project/Source/Gasa/Game/GasaPlayerState.cpp @@ -5,6 +5,8 @@ AGasaPlayerState::AGasaPlayerState() { + bAutoAbilitySystem = true; + AbilitySystem = CreateDefaultSubobject("Ability System"); AbilitySystem->SetIsReplicated(true); AbilitySystem->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); diff --git a/Project/Source/Gasa/Game/GasaPlayerState.h b/Project/Source/Gasa/Game/GasaPlayerState.h index 47ba22c..2c498e8 100644 --- a/Project/Source/Gasa/Game/GasaPlayerState.h +++ b/Project/Source/Gasa/Game/GasaPlayerState.h @@ -5,6 +5,7 @@ #include "GameFramework/PlayerState.h" #include "GasaCommon.h" +#include "Networking/GasaNetLibrary.h" #include "GasaPlayerState.generated.h" @@ -16,7 +17,7 @@ class GASA_API AGasaPlayerState : public APlayerState public: #pragma region Ability System UPROPERTY(EditAnywhere, Category="Ability System") - bool bAutoAbilitySystem = true; + bool bAutoAbilitySystem; UPROPERTY(EditAnywhere, Category="Ability System") TObjectPtr AbilitySystem; @@ -27,6 +28,28 @@ public: AGasaPlayerState(); +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime + #pragma region IAbilitySystem FORCEINLINE UAttributeSet* GetAttributes() { return Attributes; } FORCEINLINE UAbilitySystemComponent* GetAbilitySystemComponent() const override { return AbilitySystem; } diff --git a/Project/Source/Gasa/Gasa.Build.cs b/Project/Source/Gasa/Gasa.Build.cs index b2abffc..2de60ae 100644 --- a/Project/Source/Gasa/Gasa.Build.cs +++ b/Project/Source/Gasa/Gasa.Build.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using UnrealBuildTool; using ModuleRules = UnrealBuildTool.ModuleRules; using ReadOnlyTargetRules = UnrealBuildTool.ReadOnlyTargetRules; using TargetRules = UnrealBuildTool.TargetRules; @@ -10,7 +10,33 @@ public class Gasa : ModuleRules { public Gasa(ReadOnlyTargetRules Target) : base(Target) { - bUseUnity = false; + bUseUnity = false; + bMergeUnityFiles = false; + IWYUSupport = IWYUSupport.None; + PCHUsage = PCHUsageMode.NoPCHs; + OptimizeCode = CodeOptimization.Never; + MinCpuArchX64 = MinimumCpuArchitectureX64.AVX512; + IncludeOrderVersion = EngineIncludeOrderVersion.Latest; + + bCodeCoverage = false; + bDisableStaticAnalysis = true; + bValidateCircularDependencies = true; + bValidateFormatStrings = false; + bValidateInternalApi = false; + bEnableExceptions = false; + bEnableBufferSecurityChecks = false; + bEnableNonInlinedGenCppWarnings = false; + bEnableUndefinedIdentifierWarnings = false; + bIgnoreUnresolvedSymbols = false; + + bEnableObjCAutomaticReferenceCounting = false; + bEnableObjCExceptions = false; + + var Kilobyte = 1024; + NumIncludedBytesPerUnityCPPOverride = Kilobyte * 32; + MinFilesUsingPrecompiledHeaderOverride = 1; + + PrivatePCHHeaderFile = "GasaColdHeadersPCH.h"; #region Engine PrivateIncludePathModuleNames.AddRange(new string[] { @@ -32,6 +58,7 @@ public class Gasa : ModuleRules "InputCore", "NetCore", "Niagara", + "OnlineSubsystem", "SlateCore", "UMG", }); diff --git a/Project/Source/Gasa/GasaColdHeadersPCH.h b/Project/Source/Gasa/GasaColdHeadersPCH.h new file mode 100644 index 0000000..3e37bb5 --- /dev/null +++ b/Project/Source/Gasa/GasaColdHeadersPCH.h @@ -0,0 +1,53 @@ +#pragma once + +#include "GasaEngineMinimal.h" +#include "GasaCommon.h" +#include "GasaGameplayTags.h" +#include "GasaLibrary.h" +#include "GasaModule.h" +#include "GasaViewportSubsystem.h" +#include "GasaDevOptions.h" +#include "GasaDevOptionsCache.h" + +// Ability System +// #include "AbilitySystem/" +#include "AbilitySystem/GasaAbilitySystem.h" +// #include "AbilitySystem/GasaAbilitySystemComponent.h" +// #include "AbilitySystem/GasaAttributeSet.h" +// #include "GasaEffectActor.h" + +// Actors +#include "Actors/CameraMount.h" + +// Characters +// #include "Characters/GasaCharacter.h" +// #include "Characters/EnemyCharacter.h" +// #include "Characters/PlayerCharacter.h" + +// Game +// #include "Game/GasaGameInstance.h" +// #include "Game/GasaGameMode.h" +// #include "Game/GasaGameState.h" +// #include "Game/GasaLevelScriptActor.h" +// #include "Game/GasaPlayerController.h" +// #include "Game/GasaPlayerController_Inlines.h" +// #include "Game/GasaPlayerState.h" +#include "Game/GasaViewport.h" + +// Networking +// #include "Networking/GasaNetLibrary.h" +// #include "Networking/GasaNetLibrary_Inlines.h" + +// UI +// #include "UI/GasaCanvas.h" +// #include "UI/GasaCanvasPanel.h" +// #include "UI/GasaHUD.h" +// #include "UI/GasaHUD_Inlines.h" +// #include "UI/GasaImage.h" +// #include "UI/GasaOverlay.h" +// #include "UI/GasaProgressBar.h" +// #include "UI/GasaSizeBox.h" +// #include "UI/GasaUserWidget.h" +// #include "UI/HostWidgetController.h" +// #include "UI/HUDHostWidget.h" +// #include "UI/WidgetController.h" diff --git a/Project/Source/Gasa/GasaCommon.h b/Project/Source/Gasa/GasaCommon.h index 5870db4..3662cc1 100644 --- a/Project/Source/Gasa/GasaCommon.h +++ b/Project/Source/Gasa/GasaCommon.h @@ -1,13 +1,16 @@ - -#pragma once +#pragma once -#include "CoreMinimal.h" -// #define private protected +#include "GasaEngineMinimal.h" #define global #define internal static #define local_persist static +#define ccast( Type, Value ) ( *const_cast<(Type)*>( &( Value ) ) ) +#define pcast( Type, Value ) ( *reinterpret_cast<(Type)*>( &( Value ) ) ) +#define rcast( Type, Value ) reinterpret_cast( Value ) +#define scast( Type, Value ) static_cast( Value ) + #pragma region Math #define m_pow2( value ) (value * value) #pragma endregion Math @@ -16,6 +19,9 @@ struct FInputActionValue; struct FOnAttributeChangeData; +class AActor; +class APostProcessVolume; + class IAbilitySystemInterface; class UAbilitySystemComponent; @@ -45,11 +51,13 @@ class AGasaGameState; class AGasaLevelScriptActor; class AGasaPlayerController; class AGasaPlayerState; +class APlayerCharacter; class UGasaAbilitySystemComp; class UGasaAttributeSet; class UGasaDevOptions; class UGasaImage; +class UGasaObject; class UGasaOverlay; class UGasaProgressBar; class UGasaSizeBox; @@ -58,6 +66,21 @@ class UHUDHostWidget; class UWidgetController; #pragma endregion Forwards +#pragma region Bitfields +namespace Gasa +{ + inline + bool Bitfield_IsSet(int32 Bitfield, int32 Bitmask) { + int32 Result = Bitmask == (Bitfield & Bitmask); + return scast(bool, Result); + } + + inline void Bitfield_Set ( int32& Bitfield, int32 BitsToAdd ) { Bitfield |= BitsToAdd; } + inline void Bitfield_Remove( int32& Bitfield, int32 BitsToRemove ) { Bitfield &= (! BitsToRemove); } + inline void Bitfield_Toggle( int32& Bitfield, int32 Bitmask ) { Bitfield ^= Bitmask; } +} +#pragma endregion Bitfields + #pragma region Logging // Straight from the Engine UENUM(BlueprintType) @@ -107,7 +130,6 @@ namespace Gasa { using ELogV = EGasaVerbosity; - //◞ ‸ ◟// // Works for Unreal 5.4, Win64 MSVC (untested in other scenarios, for now) inline void Log( FString Message, EGasaVerbosity Verbosity = EGasaVerbosity::Log diff --git a/Project/Source/Gasa/GasaEngineMinimal.h b/Project/Source/Gasa/GasaEngineMinimal.h new file mode 100644 index 0000000..85194d7 --- /dev/null +++ b/Project/Source/Gasa/GasaEngineMinimal.h @@ -0,0 +1,160 @@ +#pragma once + +/*---------------------------------------------------------------------------- + Low level includes. +----------------------------------------------------------------------------*/ + +#include "CoreTypes.h" + +/*---------------------------------------------------------------------------- + Forward declarations +----------------------------------------------------------------------------*/ + +#include "CoreFwd.h" +#include "UObject/UObjectHierarchyFwd.h" +#include "Containers/ContainersFwd.h" + +/*---------------------------------------------------------------------------- + Commonly used headers +----------------------------------------------------------------------------*/ + +#include "Misc/VarArgs.h" +#include "Logging/LogVerbosity.h" +#include "UObject/ObjectMacros.h" + +// #include "Misc/OutputDevice.h" +// #include "HAL/PlatformCrt.h" +// #include "HAL/PlatformMisc.h" +// #include "Misc/AssertionMacros.h" +// #include "Templates/IsPointer.h" +// #include "HAL/PlatformMemory.h" +// #include "HAL/PlatformAtomics.h" +// #include "Misc/Exec.h" +// #include "HAL/MemoryBase.h" +// #include "HAL/UnrealMemory.h" +// #include "Templates/IsArithmetic.h" +// #include "Templates/AndOrNot.h" +// #include "Templates/IsPODType.h" +// #include "Templates/IsUECoreType.h" +// #include "Templates/IsTriviallyCopyConstructible.h" +// #include "Templates/UnrealTypeTraits.h" +// #include "Templates/EnableIf.h" +// #include "Templates/RemoveReference.h" +// #include "Templates/IntegralConstant.h" +// #include "Templates/IsClass.h" +// #include "Templates/TypeCompatibleBytes.h" +// #include "Traits/IsContiguousContainer.h" +// #include "Templates/UnrealTemplate.h" +// #include "Math/NumericLimits.h" +// #include "HAL/PlatformMath.h" +// #include "Templates/IsTriviallyCopyAssignable.h" +// #include "Templates/IsTriviallyDestructible.h" +// #include "Templates/MemoryOps.h" +// #include "Containers/ContainerAllocationPolicies.h" +// #include "Templates/IsEnumClass.h" +// #include "HAL/PlatformProperties.h" +// #include "Misc/EngineVersionBase.h" +// #include "Internationalization/TextNamespaceFwd.h" +// #include "Serialization/Archive.h" +// #include "Templates/Less.h" +// #include "Templates/Sorting.h" +// #include "Misc/Char.h" +// #include "GenericPlatform/GenericPlatformStricmp.h" +// #include "GenericPlatform/GenericPlatformString.h" +// #include "HAL/PlatformString.h" +// #include "Misc/CString.h" +// #include "Misc/Crc.h" +// #include "Math/UnrealMathUtility.h" +// #include "Containers/UnrealString.h" +// #include "Containers/Array.h" +// #include "Misc/FrameNumber.h" +// #include "Misc/Timespan.h" +// #include "Containers/StringConv.h" +// #include "UObject/UnrealNames.h" +// #include "UObject/NameTypes.h" +// #include "Misc/Parse.h" +// #include "Templates/AlignmentTemplates.h" +// #include "Misc/StructBuilder.h" +// #include "Templates/Decay.h" +// #include "Templates/PointerIsConvertibleFromTo.h" +// #include "Templates/Invoke.h" +// #include "Templates/Function.h" +// #include "Templates/TypeHash.h" + +// #include "Containers/ScriptArray.h" +// #include "Containers/BitArray.h" +// #include "Containers/SparseArray.h" +// #include "Containers/Set.h" + +// #include "Algo/Reverse.h" +// #include "Containers/Map.h" +// #include "Math/IntPoint.h" +// #include "Math/IntVector.h" + +// #include "Logging/LogCategory.h" +// #include "Logging/LogMacros.h" + +// #include "Math/Vector2D.h" +// #include "Math/IntRect.h" +// #include "Misc/ByteSwap.h" +// #include "Containers/EnumAsByte.h" +// #include "HAL/PlatformTLS.h" +// #include "CoreGlobals.h" + +// #include "Templates/SharedPointer.h" +// #include "Internationalization/CulturePointer.h" +// #include "UObject/WeakObjectPtrTemplates.h" +// #include "Delegates/DelegateSettings.h" +// #include "Delegates/IDelegateInstance.h" +// #include "Delegates/DelegateBase.h" +// #include "Delegates/MulticastDelegateBase.h" +// #include "Delegates/IntegerSequence.h" +// #include "Templates/Tuple.h" +// #include "UObject/ScriptDelegates.h" +// #include "Delegates/Delegate.h" +// #include "Internationalization/TextLocalizationManager.h" +// #include "Misc/Optional.h" +// #include "Templates/IsArray.h" +// #include "Templates/RemoveExtent.h" +// #include "Templates/UniquePtr.h" +// #include "Internationalization/Text.h" +// #include "Templates/UniqueObj.h" +// #include "Internationalization/Internationalization.h" +// #include "Math/Vector.h" +// #include "Math/Vector4.h" +// #include "Math/VectorRegister.h" +// #include "Math/TwoVectors.h" +// #include "Math/Edge.h" +// #include "UObject/ObjectVersion.h" +// #include "Math/CapsuleShape.h" +// #include "Math/Rotator.h" +// #include "Misc/DateTime.h" +// #include "Math/RangeBound.h" +// #include "Misc/AutomationEvent.h" +// #include "Math/Range.h" +// #include "Math/RangeSet.h" +// #include "Math/Interval.h" +// #include "Math/Box.h" +// #include "Math/Box2D.h" +// #include "Math/BoxSphereBounds.h" +// #include "Math/OrientedBox.h" +// #include "Math/Axis.h" +// #include "Math/Matrix.h" +// #include "Math/RotationTranslationMatrix.h" +// #include "Math/RotationAboutPointMatrix.h" +// #include "Math/ScaleRotationTranslationMatrix.h" +// #include "Math/RotationMatrix.h" +// #include "Math/Quat.h" +// #include "Math/PerspectiveMatrix.h" +// #include "Math/OrthoMatrix.h" +// #include "Math/TranslationMatrix.h" +// #include "Math/QuatRotationTranslationMatrix.h" +// #include "Math/InverseRotationMatrix.h" +// #include "Math/ScaleMatrix.h" +// #include "Math/MirrorMatrix.h" +// #include "Math/ClipProjectionMatrix.h" +// #include "Math/Float32.h" +// #include "Math/Float16.h" +// #include "Math/Transform.h" +// #include "Math/ConvexHull2d.h" +// #include "Math/UnrealMath.h" \ No newline at end of file diff --git a/Project/Source/Gasa/GasaLibrary.cpp b/Project/Source/Gasa/GasaLibrary.cpp index 351a251..37b1667 100644 --- a/Project/Source/Gasa/GasaLibrary.cpp +++ b/Project/Source/Gasa/GasaLibrary.cpp @@ -8,6 +8,7 @@ #include "Game/GasaGameState.h" #include "Game/GasaPlayerController.h" #include "Kismet/KismetSystemLibrary.h" +#include "Misc/ConfigCacheIni.h" #pragma region Game UGasaDevOptions* UGasaLib::GetGasaDevOptions(UObject* Context) { diff --git a/Project/Source/Gasa/GasaLibrary.h b/Project/Source/Gasa/GasaLibrary.h index 9dee172..84c75d4 100644 --- a/Project/Source/Gasa/GasaLibrary.h +++ b/Project/Source/Gasa/GasaLibrary.h @@ -1,6 +1,7 @@ #pragma once #include "GasaCommon.h" +#include "Kismet/BlueprintFunctionLibrary.h" #include "GasaLibrary.Generated.h" diff --git a/Project/Source/Gasa/GasaModule.h b/Project/Source/Gasa/GasaModule.h index 7d4e9ba..0d9b987 100644 --- a/Project/Source/Gasa/GasaModule.h +++ b/Project/Source/Gasa/GasaModule.h @@ -1,6 +1,7 @@ #pragma once #include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" class GASA_API FGasaModule : public IModuleInterface { diff --git a/Project/Source/Gasa/GasaObject.cpp b/Project/Source/Gasa/GasaObject.cpp new file mode 100644 index 0000000..978564f --- /dev/null +++ b/Project/Source/Gasa/GasaObject.cpp @@ -0,0 +1,77 @@ +#include "GasaObject.h" + +#include "Engine/ActorChannel.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "Engine/NetDriver.h" +#include "GameFramework/Actor.h" +using namespace Gasa; + +UGasaObject::UGasaObject() +{ + bReplicates = false; + bDisconnectOnBadReplication = false; +} + +void UGasaObject::Destroy() +{ + if ( ! IsValid(this)) + { + checkf(GetOwningActor()->HasAuthority() == true, TEXT("Destroy:: Object does not have authority to destroy itself!")); + OnDestroyed(); + Event_OnDestroyed.Broadcast(); + ConditionalBeginDestroy(); + } +} + +void UGasaObject::OnDestroyed() +{ +} + +bool UGasaObject::ReplicateAsSubobject(AActor* ActorResponsible, UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags) +{ + if (!ActorResponsible) + { + NetLog("Actor reponsible is null", ELogV::Error); + return false; + } + if (!bDisconnectOnBadReplication && ActorResponsible != GetOuter()) + { + NetLog("Attempted to replicate whose outer was not set to the actor whose responsible for replicating it as a subobject", ELogV::Error); + return false; + } + return Channel->ReplicateSubobject(this, *Bunch, *RepFlags); +} + +bool UGasaObject::CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) +{ + check(! HasAnyFlags(RF_ClassDefaultObject)); + AActor* Owner = GetOwningActor(); + UNetDriver* NetDriver = Owner->GetNetDriver(); + if (NetDriver) + { + NetDriver->ProcessRemoteFunction(Owner, Function, Parms, OutParms, Stack, this); + return true; + } + return false; +} + +int32 UGasaObject::GetFunctionCallspace(UFunction* Function, FFrame* Stack) +{ + check(GetOuter() != nullptr); + return GetOuter()->GetFunctionCallspace(Function, Stack); +} + +void UGasaObject::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + // Add any Blueprint properties + // This is not required if you do not want the class to be "Blueprintable" + if (const UBlueprintGeneratedClass* BP = Cast(GetClass())) + { + BP->GetLifetimeBlueprintReplicationList(OutLifetimeProps); + } +} + +bool UGasaObject::IsSupportedForNetworking() const +{ + return bReplicates; +} diff --git a/Project/Source/Gasa/GasaObject.h b/Project/Source/Gasa/GasaObject.h new file mode 100644 index 0000000..1d800dc --- /dev/null +++ b/Project/Source/Gasa/GasaObject.h @@ -0,0 +1,80 @@ +#pragma once + +#include "GasaCommon.h" +#include "Networking/GasaNetLibrary.h" +#include "UObject/Object.h" +#include "GasaObject.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGasaObjectEventSig); + +// A UObject which supports replication and other features. +UCLASS( Blueprintable ) +class GASA_API UGasaObject : public UObject +{ + GENERATED_BODY() +public: + + UGasaObject(); + + UFUNCTION(BlueprintPure) + FORCEINLINE AActor* GetOwningActor() const { return GetTypedOuter(); }; + + UPROPERTY(BlueprintAssignable, Category="Lifetime") + FGasaObjectEventSig Event_OnDestroyed; + + UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="Lifetime") + virtual void Destroy(); + + virtual void OnDestroyed(); + + UFUNCTION(BlueprintImplementableEvent, meta=(DisplayName = "On Destroyed")) + void BP_OnDestroyed(); + +#pragma region Replication + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Replication") + bool bReplicates; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Replication") + bool bDisconnectOnBadReplication ; + + virtual bool ReplicateAsSubobject(AActor* ActorResponsible, UActorChannel* Channel, FOutBunch* Bunch, FReplicationFlags* RepFlags); + + UFUNCTION(BlueprintCallable) + void SetIsReplicated(bool DesiredValue) + { + bReplicates = DesiredValue; + } +#pragma endregion Replication + +#pragma region NetSlime + // NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp + FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode( this ); } + FORCEINLINE bool IsClient() const { return Gasa::IsClient( this ); } + FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer( this ); } + FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner( this ); } + FORCEINLINE bool IsServer() const { return Gasa::IsServer( this ); } + FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy( this ); } + FORCEINLINE void NetLog( + FString Message, + EGasaVerbosity Verbosity = EGasaVerbosity::Log, + FLogCategoryBase& Category = LogGasaNet, + bool DumpStack = false, + int32 Line = __builtin_LINE(), + ANSICHAR const* File = __builtin_FILE(), + ANSICHAR const* Func = __builtin_FUNCTION() + ) + { + Gasa::NetLog( this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } +#pragma endregion NetSlime + +#pragma region UObject + bool CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) override; + + int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack) override; + + void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + bool IsSupportedForNetworking() const override; +#pragma endregion UObject +}; diff --git a/Project/Source/Gasa/Networking/GasaNetLibrary.cpp b/Project/Source/Gasa/Networking/GasaNetLibrary.cpp index 169c801..3741f56 100644 --- a/Project/Source/Gasa/Networking/GasaNetLibrary.cpp +++ b/Project/Source/Gasa/Networking/GasaNetLibrary.cpp @@ -1,2 +1,67 @@ #include "GasaNetLibrary.h" +#include "GasaNetLibrary_Inlines.h" +DEFINE_LOG_CATEGORY(LogGasaNet); + +void NetLog( UObject const* Context, FString Message, EGasaVerbosity Verbosity = EGasaVerbosity::Log + , FLogCategoryBase& Category = LogGasaNet + , bool DumpStack = false + , int32 Line = __builtin_LINE() + , const ANSICHAR* File = __builtin_FILE() + , const ANSICHAR* Func = __builtin_FUNCTION() ) +{ +#if !UE_BUILD_SHIPPING && !NO_LOGGING + ELogVerbosity::Type EngineVerbosity = (ELogVerbosity::Type) Verbosity; + if ((EngineVerbosity & ELogVerbosity::VerbosityMask) > ELogVerbosity::COMPILED_IN_MINIMUM_VERBOSITY) + return; + if ((EngineVerbosity & ELogVerbosity::VerbosityMask) > Category.GetVerbosity()) + return; + if ( Category.IsSuppressed(EngineVerbosity)) + return; + + AActor const* Actor = nullptr; + FString ActorLevel; + { + if (Context != nullptr) + { + if (Context->GetClass()->IsChildOf(AActor::StaticClass())) + Actor = Cast(Context); + else if (Context->GetClass()->IsChildOf(UActorComponent::StaticClass())) + Actor = Cast(Context)->GetOwner(); + // Its assumed that all GasaObjects have an outer actor + else if (Context->IsA(UGasaObject::StaticClass())) + Actor = Cast(Context->GetOuter()); + } + if (Actor) + { + if (Actor->HasLocalNetOwner()) + ActorLevel = TEXT("Net Owner"); + + else if (Actor->HasAuthority()) + ActorLevel = TEXT("Server Authorized"); + + else + ActorLevel = TEXT("No Authority"); + } + else + ActorLevel = TEXT("Local"); + } + + FString NetMode = FString::Printf(TEXT("%-16s"), * GetNetworkModeStr(Context)); + ActorLevel = FString::Printf(TEXT("%-18s"), * ActorLevel); + FString Name = FString::Printf(TEXT("%-40s"), * Context->GetName()); + FString FullMsg = NetMode + " " + ActorLevel + " " + Name + " : " + Message; + + static UE::Logging::Private::FStaticBasicLogDynamicData LOG_Dynamic; + static UE::Logging::Private::FStaticBasicLogRecord + LOG_Static(TEXT("%s -- %hs %hs(%d)"), File, Line, EngineVerbosity, LOG_Dynamic); + + SET_WARN_COLOR(COLOR_PURPLE) + + if (DumpStack) + FDebug::DumpStackTraceToLog(EngineVerbosity); + BasicLog(Category, &LOG_Static, * FullMsg, File, Func, Line); + + CLEAR_WARN_COLOR() +#endif +} \ No newline at end of file diff --git a/Project/Source/Gasa/Networking/GasaNetLibrary.h b/Project/Source/Gasa/Networking/GasaNetLibrary.h index 575ac03..98a9a33 100644 --- a/Project/Source/Gasa/Networking/GasaNetLibrary.h +++ b/Project/Source/Gasa/Networking/GasaNetLibrary.h @@ -1,4 +1,22 @@ -#pragma once +// NetSlime: Ol'Reliable +#pragma once + +#include "GasaCommon.h" + +#define DOREPLIFETIME_DEFAULT_GAS(Class, ReplicatedVar) \ + DOREPLIFETIME_CONDITION_NOTIFY(Class, ReplicatedVar, COND_None, REPNOTIFY_Always) + +DECLARE_LOG_CATEGORY_EXTERN(LogGasaNet, Log, All); + +UENUM(BlueprintType) +enum class ENetworkMode : uint8 +{ + Standalone, + DedicatedServer, + ListenServer, + Client, + MAX, +}; namespace Gasa { @@ -12,6 +30,32 @@ namespace Gasa constexpr float NetCullDist_VeryFar = 10000.0f * 10000.0f; constexpr float NetCullDist_VisualMax = 15000.0f * 15000.0f; - #define DOREPLIFETIME_DEFAULT_GAS(Class, ReplicatedVar) \ - DOREPLIFETIME_CONDITION_NOTIFY(Class, ReplicatedVar, COND_None, REPNOTIFY_Always) + void DrawNetCullingSphere(UObject const* Context, float Duration, float Thickness); + + ENetworkMode GetNetworkMode(UObject const* Context); + FString GetNetworkModeStr(UObject const* Context); + + bool IsClient(UObject const* Context); + bool IsListenServer(UObject const* Context); + + bool IsNetOwner(UObject const* Context); + bool IsNetOwner(UGasaObject const* Context); + bool IsNetOwner(AActor const* Context); + + bool IsServer(UObject const* Context); + + bool IsSimulatedProxy(UObject const* Context); + bool IsSimulatedProxy(UGasaObject const* Context); + bool IsSimulatedProxy(AActor const* Context); + + void NetLog( UObject const* Context, FString Message, EGasaVerbosity Verbosity = EGasaVerbosity::Log + , FLogCategoryBase& Category = LogGasaNet + , bool DumpStack = false + , int32 Line = __builtin_LINE() + , const ANSICHAR* File = __builtin_FILE() + , const ANSICHAR* Func = __builtin_FUNCTION() ); + + bool ServerAuthorized(UObject const* Context); + bool ServerAuthorized(UGasaObject const* Context); + bool ServerAuthorized(AActor const* Context); } diff --git a/Project/Source/Gasa/Networking/GasaNetLibrary_Inlines.h b/Project/Source/Gasa/Networking/GasaNetLibrary_Inlines.h new file mode 100644 index 0000000..c2a621d --- /dev/null +++ b/Project/Source/Gasa/Networking/GasaNetLibrary_Inlines.h @@ -0,0 +1,278 @@ +#include "GasaNetLibrary.h" +#include "GasaObject.h" +#include "Engine/NetDriver.h" +#include "Game/GasaGameMode.h" +#include "Kismet/KismetMathLibrary.h" +#include "Kismet/KismetSystemLibrary.h" + +namespace Gasa +{ + // TODO(Ed): Profile these... + + inline + void DrawNetCullingSphere(const UObject* Context, float Duration, float Thickness) + { + const AActor* actor = nullptr; + + if (Context->IsA(UGasaObject::StaticClass())) + actor = Cast(Context->GetOuter()); + + else if (Context->IsA(AActor::StaticClass())) + actor = Cast(Context); + + if (actor) + UKismetSystemLibrary::DrawDebugSphere(actor + , actor->GetActorLocation() + , UKismetMathLibrary::Sqrt(actor->NetCullDistanceSquared) * 2 + , 12 + , FLinearColor(FColor::Emerald) + , Duration + , Thickness); + } + + inline + ENetworkMode GetNetworkMode(UObject const* Context) + { + if (Context == nullptr) + { + Log("Context is null...", ELogV::Error); + return scast(ENetworkMode, ENetMode::NM_MAX); + } + UWorld* World = Context->GetWorld(); + if (World == nullptr) { + Log("World is null... are you running in a proper context?", ELogV::Error); + return scast(ENetworkMode, ENetMode::NM_MAX); + } + + if (IsValid(World) == false) + return ENetworkMode::Standalone; + + ENetworkMode NetMode = scast(ENetworkMode, World->GetNetMode()); + return NetMode; + } + + inline + FString GetNetworkModeStr(UObject const* Context) + { + FString Str; + if (Context == nullptr) + return Str; + switch (GetNetworkMode(Context)) + { + case ENetworkMode::Standalone: + Str = TEXT("Standalone"); + break; + case ENetworkMode::ListenServer: + Str = TEXT("ListenServer"); + break; + case ENetworkMode::DedicatedServer: + Str = TEXT("DedicatedServer"); + break; + case ENetworkMode::Client: + Str = TEXT("Client"); + break; + } + return Str; + } + + inline + bool IsClient(UObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + UNetDriver* NetDriver = Context->GetWorld()->NetDriver; + bool Result = NetDriver && ! NetDriver->IsServer(); + return Result; + } + + inline + bool IsListenServer(UObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + UNetDriver* NetDriver = Context->GetWorld()->NetDriver; + bool Result = NetDriver && NetDriver->GetNetMode() == ENetMode::NM_ListenServer; + return Result; + } + + inline + bool IsNetOwner(UObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + AActor const* Actor = nullptr; + + if (Context->IsA(AActor::StaticClass())) + Actor = Cast(Context); + else if (Context->GetClass()->IsChildOf(UActorComponent::StaticClass())) + Actor = Cast(Context)->GetOwner(); + // Its assumed that all GasaObjects have an outer actor + else if (Context->IsA(UGasaObject::StaticClass())) + Actor = Cast(Context->GetOuter()); + else + { + UObject const* Outermost = Context->GetOutermostObject(); + if (Outermost->IsA(AActor::StaticClass())) + Actor = Cast(Outermost); + } + + if (Actor == nullptr) + { + Log("Could not get actor reference", ELogV::Warning, LogGasaNet); + return false; + } + bool Result = Actor->HasLocalNetOwner(); + return Result; + } + + inline + bool IsNetOwner(UGasaObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + AActor const* Actor = Cast(Context->GetOuter()); + if (Actor == nullptr) + { + Log("Could not get actor reference", ELogV::Warning, LogGasaNet); + return false; + } + bool Result = Actor->HasLocalNetOwner(); + return Result; + } + + inline + bool IsNetOwner(AActor const* Actor) + { + if (Actor == nullptr || Actor->GetWorld() == nullptr) + return false; + bool Result = Actor->HasLocalNetOwner(); + return Result; + } + + inline + bool IsServer(UObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + UNetDriver* NetDriver = Context->GetWorld()->NetDriver; + bool Result = NetDriver && NetDriver->IsServer(); + return Result; + } + + inline + bool IsSimulatedProxy(UObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + AActor const* Actor = nullptr; + + if (Context->IsA(AActor::StaticClass())) + Actor = Cast(Context); + else if (Context->GetClass()->IsChildOf(UActorComponent::StaticClass())) + Actor = Cast(Context)->GetOwner(); + // Its assumed that all GasaObjects have an outer actor + else if (Context->IsA(UGasaObject::StaticClass())) + Actor = Cast(Context->GetOuter()); + else + { + UObject const* Outermost = Context->GetOutermostObject(); + if (Outermost->IsA(AActor::StaticClass())) + Actor = Cast(Outermost); + } + + if (Actor == nullptr) + { + Log("Could not get actor reference", ELogV::Warning, LogGasaNet); + return false; + } + bool Result = Actor->GetLocalRole() == ENetRole::ROLE_SimulatedProxy; + return Result; + } + + inline + bool IsSimulatedProxy(UGasaObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + AActor const* Actor = Cast(Context->GetOuter()); + if (Actor == nullptr) + { + Log("Could not get actor reference", ELogV::Warning, LogGasaNet); + return false; + } + bool Result = Actor->GetLocalRole() == ENetRole::ROLE_SimulatedProxy; + return Result; + } + + inline + bool IsSimulatedProxy(AActor const* Actor) + { + if (Actor == nullptr || Actor->GetWorld() == nullptr) + return false; + bool Result = Actor->GetLocalRole() == ENetRole::ROLE_SimulatedProxy; + return Result; + } + + inline + bool ServerAuthorized(UObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + AActor const* Actor = nullptr; + + if (Context->IsA(AActor::StaticClass())) + Actor = Cast(Context); + else if (Context->GetClass()->IsChildOf(UActorComponent::StaticClass())) + Actor = Cast(Context)->GetOwner(); + // Its assumed that all GasaObjects have an outer actor + else if (Context->IsA(UGasaObject::StaticClass())) + Actor = Cast(Context->GetOuter()); + else + { + UObject const* Outermost = Context->GetOutermostObject(); + if (Outermost->IsA(AActor::StaticClass())) + Actor = Cast(Outermost); + } + + if (Actor == nullptr) + { + Log("Could not get actor reference", ELogV::Warning, LogGasaNet); + return false; + } + bool Result = Actor->HasAuthority(); + return Result; + } + + inline + bool ServerAuthorized(UGasaObject const* Context) + { + if (Context == nullptr || Context->GetWorld() == nullptr) + return false; + + AActor const* Actor = Cast(Context->GetOuter()); + if (Actor == nullptr) + { + Log("Could not get actor reference", ELogV::Warning, LogGasaNet); + return false; + } + bool Result = Actor->HasAuthority(); + return Result; + } + + inline + bool ServerAuthorized(AActor const* Actor) + { + if (Actor == nullptr || Actor->GetWorld() == nullptr) + return false; + bool Result = Actor->HasAuthority(); + return Result; + } +} diff --git a/Project/Source/Gasa/UI/GasaHUD.cpp b/Project/Source/Gasa/UI/GasaHUD.cpp index 5d1a502..3284826 100644 --- a/Project/Source/Gasa/UI/GasaHUD.cpp +++ b/Project/Source/Gasa/UI/GasaHUD.cpp @@ -8,10 +8,10 @@ using namespace Gasa; void AGasaHUD::InitHostWidget(FWidgetControllerData const* WidgetControllerData) { - HostWidget = CreateWidget( GetWorld() + HostWidget = CreateWidget( GetWorld() , GetDevOptions()->Template_HUD_HostUI.LoadSynchronous() ); - HostWidgetController = NewObject(this, GetDevOptions()->Template_HostWidgetController.Get()); + HostWidgetController = NewObject(this, GetDevOptions()->Template_HostWidgetController.Get()); HostWidgetController->Data = (* WidgetControllerData); HostWidget->SetWidgetController(HostWidgetController); HostWidgetController->BindCallbacksToDependencies(); diff --git a/Project/Source/Gasa/UI/HostWIdgetController.cpp b/Project/Source/Gasa/UI/HostWIdgetController.cpp index acafe03..34c3010 100644 --- a/Project/Source/Gasa/UI/HostWIdgetController.cpp +++ b/Project/Source/Gasa/UI/HostWIdgetController.cpp @@ -3,6 +3,9 @@ #include "AbilitySystem/GasaAttributeSet.h" #include "GameplayEffectTypes.h" + + + #pragma region Attribute Changed Callbacks // Attribute Changed Callbacks are generated by GasaGen/GasaGen_HostWidgetController.cpp diff --git a/Project/Source/GasaEditor.Target.cs b/Project/Source/GasaEditor.Target.cs index 028878a..5ed491e 100644 --- a/Project/Source/GasaEditor.Target.cs +++ b/Project/Source/GasaEditor.Target.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.IO; using System.Runtime; +using UnrealBuildTool; using BuildSettingsVersion = UnrealBuildTool.BuildSettingsVersion; using TargetInfo = UnrealBuildTool.TargetInfo; using TargetRules = UnrealBuildTool.TargetRules; @@ -17,6 +18,7 @@ public class GasaEditorTarget : TargetRules bUseUnityBuild = true; // bUseXGEController = false; + LinkType = TargetLinkType.Modular; ExtraModuleNames.Add("Gasa"); ExtraModuleNames.Add("GasaEditor"); diff --git a/Project/Source/GasaGen/GasaGen.cpp b/Project/Source/GasaGen/GasaGen.cpp index a8a1ffe..3b73540 100644 --- a/Project/Source/GasaGen/GasaGen.cpp +++ b/Project/Source/GasaGen/GasaGen.cpp @@ -18,6 +18,7 @@ using namespace gen; #include "GasaGen_ChangeBPActionMenu.cpp" #include "GasaGen_DevOptionsCache.cpp" #include "GasaGen_HostWidgetController.cpp" +#include "GasaGen_NetSlime.cpp" int gen_main() { @@ -80,6 +81,7 @@ int gen_main() gen_UGasaAttributeSet(); gen_FGasaDevOptionsCache(); gen_UHostWidgetController(); + // gen_netslime_interfaces(); // One offs if (0) diff --git a/Project/Source/GasaGen/GasaGenCommon.cpp b/Project/Source/GasaGen/GasaGenCommon.cpp index 5711000..1f1e09f 100644 --- a/Project/Source/GasaGen/GasaGenCommon.cpp +++ b/Project/Source/GasaGen/GasaGenCommon.cpp @@ -13,6 +13,9 @@ using namespace gen; #define path_config path_source "Config/" #define path_module_gasa path_source "Gasa/" #define path_gasa_ability_system path_module_gasa "AbilitySystem/" +#define path_gasa_actors path_module_gasa "Actors/" +#define path_gasa_characters path_module_gasa "Characters/" +#define path_gasa_game path_module_gasa "Game/" #define path_gasa_ui path_module_gasa "UI/" constexpr StrC str_DECLARE_CLASS = txt("DECLARE_CLASS("); diff --git a/Project/Source/GasaGen/GasaGen_NetSlime.cpp b/Project/Source/GasaGen/GasaGen_NetSlime.cpp new file mode 100644 index 0000000..04ea26f --- /dev/null +++ b/Project/Source/GasaGen/GasaGen_NetSlime.cpp @@ -0,0 +1,118 @@ +// Used in the GasaGen.cpp translation unit +#if GASA_INTELLISENSE_DIRECTIVES +#pragma once +#define GEN_EXPOSE_BACKEND +#include "gen.hpp" +#include "gen.builder.hpp" +#include "GasaGenCommon.cpp" +#endif + +void gen_netslime_interface(CodeClass aclass) +{ + CodeBody net_slime_class_interface = def_body(ECode::Class_Body); + { + #pragma push_macro("FORCEINLINE") + #undef FORCEINLINE + CodeFn DrawNetCullingSphere = parse_function( code( + FORCEINLINE void DrawNetCullingSphere(float Duration, float Thickness) const final { Gasa::DrawNetCullingSphere(this, Duration, Thickness); } + )); + CodeFn GetNetworkMode = parse_function( code( FORCEINLINE ENetworkMode GetNetworkMode() const { return Gasa::GetNetworkMode(this); } )); + CodeFn IsClient = parse_function( code( FORCEINLINE bool IsClient() const { return Gasa::IsClient(this); } )); + CodeFn IsListenServer = parse_function( code( FORCEINLINE bool IsListenServer() const { return Gasa::IsListenServer(this); } )); + CodeFn IsNetOwner = parse_function( code( FORCEINLINE bool IsNetOwner() const { return Gasa::IsNetOwner(this); } )); + CodeFn IsServer = parse_function( code( FORCEINLINE bool IsServer() const { return Gasa::IsServer(this); } )); + CodeFn IsSimulatedProxy = parse_function( code( FORCEINLINE bool IsSimulatedProxy() const { return Gasa::IsSimulatedProxy(this); } )); + CodeFn NetLog = parse_function( code( + FORCEINLINE void NetLog( FString Message, EGasaVerbosity Verbosity = EGasaVerbosity::Log + , FLogCategoryBase& Category = LogGasaNet + , bool DumpStack = false + , int32 Line = __builtin_LINE() + , const ANSICHAR* File = __builtin_FILE() + , const ANSICHAR* Func = __builtin_FUNCTION() ) + { + Gasa::NetLog(this, Message, Verbosity, Category, DumpStack, Line, File, Func ); + } + )); + CodeFn ServerAuthorized = parse_function( code( FORCEINLINE bool ServerAuthorized() const { return Gasa::ServerAuthorized(this); } )); + #pragma pop_macro("FORCEINLINE") + net_slime_class_interface.append(GetNetworkMode); + net_slime_class_interface.append(IsClient); + net_slime_class_interface.append(IsListenServer); + net_slime_class_interface.append(IsNetOwner); + net_slime_class_interface.append(IsServer); + net_slime_class_interface.append(IsSimulatedProxy); + net_slime_class_interface.append(NetLog); + } + + CodeBody new_body = def_body(ECode::Class_Body); + for(Code code = aclass->Body.begin(); code != aclass->Body.end(); ++ code ) + { + switch (code->Type) + { + default: + new_body.append(code); + break; + + // TODO(Ed): Could this be turned into a singly? void find_and_swap_region_pragma(CodeClass, StrC region) + // IT could return void if its assumed that the Code passed will have destructive edits to the body. + case ECode::Preprocess_Pragma: + { + local_persist bool found = false; + if (found || ! code->Content.starts_with( txt("region NetSlime"))) + { + new_body.append(code); + continue; + } + + // Add pragma + new_body.append(code); + ++ code; + + new_body.append( def_comment( txt("NetSlime interface is generated by GasaGen/GasaGen_NetSlime.cpp"))); + new_body.append(net_slime_class_interface); + + while (code->Type != ECode::Preprocess_Pragma + || ! code->Content.starts_with(txt("endregion NetSlime"))) + ++ code; + + new_body.append(code); + } + break; + } + } + aclass->Body = new_body; +} + +void gen_netslime_interfaces() +{ + Array header_paths = Array::init_reserve(GlobalAllocator, 32); + // header_paths.append(get_cached_string(txt( path_module_gasa "GasaObject.h"))); + // header_paths.append(get_cached_string(txt( path_gasa_actors "GasaActor.h"))); + // header_paths.append(get_cached_string(txt( path_gasa_characters "GasaCharacter.h"))); + // header_paths.append(get_cached_string(txt( path_gasa_game "GasaGameMode.h"))); + // header_paths.append(get_cached_string(txt( path_gasa_game "GasaGameState.h"))); + + for (StringCached path : header_paths) + { + CodeBody original_header = parse_file(path); + CodeBody header_body = def_body(ECode::Global_Body); + for (Code code : original_header) + { + switch (code->Type) { + case ECode::Class: + { + CodeClass aclass = code.cast(); + gen_netslime_interface(aclass); + header_body.append(aclass); + } + break; + default: + header_body.append(code); + } + } + Builder header = Builder::open(path); + header.print(header_body); + header.write(); + format_file(path); + } +} \ No newline at end of file diff --git a/Project/Source/GasaGen/GasaGen_UGasaAttributeSet.cpp b/Project/Source/GasaGen/GasaGen_UGasaAttributeSet.cpp index 54ff97b..88c2a6d 100644 --- a/Project/Source/GasaGen/GasaGen_UGasaAttributeSet.cpp +++ b/Project/Source/GasaGen/GasaGen_UGasaAttributeSet.cpp @@ -272,6 +272,7 @@ void def_attribute_field_value_setters( CodeBody body, Array prope void def_attribute_field_property_setter_inlines( CodeBody body, StrC class_name, Array properties ) { + body.append(def_pragma( txt("region Attribute Setters"))); for ( String property : properties ) { CodeFn generated_get_attribute = parse_function( @@ -288,6 +289,7 @@ void def_attribute_field_property_setter_inlines( CodeBody body, StrC class_name ))); body.append( generated_get_attribute ); } + body.append(def_pragma( txt("endregion Attribute Setters"))); } void def_attribute_field_initers ( CodeBody body, Array properties ) @@ -307,11 +309,10 @@ void def_attribute_field_initers ( CodeBody body, Array properties void impl_attribute_fields( CodeBody body, StrC class_name, Array properties ) { + body.append(fmt_newline); body.append(def_pragma( txt("region Rep Notifies"))); for ( String property : properties ) { - body.append(fmt_newline); - CodeFn field_impl = parse_function( token_fmt( "class_name", class_name, "property", (StrC)property, "from_notice", txt("\n// From GAMEPLAYATTRIBUTE_REPNOTIFY\n"), stringize( @@ -326,6 +327,7 @@ void impl_attribute_fields( CodeBody body, StrC class_name, Array body.append( field_impl ); } body.append( def_pragma( txt("endregion Rep Notifies"))); + body.append(fmt_newline); } inline diff --git a/Project/Source/GasaGen/gen.cpp b/Project/Source/GasaGen/gen.cpp index 04bdc4b..e610387 100644 --- a/Project/Source/GasaGen/gen.cpp +++ b/Project/Source/GasaGen/gen.cpp @@ -1610,7 +1610,7 @@ void CodeConstructor::to_string_fwd( String& result ) if ( ast->InlineCmt ) result.append_fmt( "; // %S\n", ast->InlineCmt->Content ); else - result.append( ";" ); + result.append( ";\n" ); } String CodeClass::to_string() @@ -6278,6 +6278,16 @@ namespace parser move_forward(); preprocess_content.Length++; + if ( current == '\r' && scanner[1] == '\n' ) + { + move_forward(); + move_forward(); + } + else if ( current == '\n' ) + { + move_forward(); + } + Tokens.append( preprocess_content ); return Lex_Continue; // Skip found token, its all handled here. } @@ -7262,7 +7272,7 @@ namespace parser Tokens = Array::init_reserve( LexArena, ( LexAllocator_Size - sizeof( Array::Header ) ) / sizeof( Token ) ); defines_map_arena = Arena_256KB::init(); - defines = HashTable::init( defines_map_arena ); + defines = HashTable::init_reserve( defines_map_arena, 256 ); } internal void deinit() diff --git a/Project/Source/GasaGen/gen.dep.hpp b/Project/Source/GasaGen/gen.dep.hpp index a0423a5..cf2d6d5 100644 --- a/Project/Source/GasaGen/gen.dep.hpp +++ b/Project/Source/GasaGen/gen.dep.hpp @@ -1663,7 +1663,7 @@ struct Array { Header& header = *get_header(); - if ( begin < 0 || end >= header.Num ) + if ( begin < 0 || end > header.Num ) return false; for ( sw idx = begin; idx < end; idx++ ) @@ -1820,13 +1820,11 @@ struct HashTable Type Value; }; + static constexpr f32 CriticalLoadScale = 0.7f; + static HashTable init( AllocatorInfo allocator ) { - HashTable result = { { nullptr }, { nullptr } }; - - result.Hashes = Array::init( allocator ); - result.Entries = Array::init( allocator ); - + HashTable result = init_reserve(allocator, 8); return result; } @@ -1834,21 +1832,19 @@ struct HashTable { HashTable result = { { nullptr }, { nullptr } }; - result.Hashes = Array::init_reserve( allocator, num ); + result.Hashes = Array::init_reserve( allocator, num ); result.Hashes.get_header()->Num = num; + result.Hashes.resize( num ); + result.Hashes.fill( 0, num, -1); - result.Entries = Array::init_reserve( allocator, num ); - + result.Entries = Array::init_reserve( allocator, num ); return result; } void clear( void ) { - for ( sw idx = 0; idx < Hashes.num(); idx++ ) - Hashes[idx] = -1; - - Hashes.clear(); Entries.clear(); + Hashes.fill( 0, Hashes.num(), -1); } void destroy( void ) @@ -1901,32 +1897,19 @@ struct HashTable void rehash( sw new_num ) { - sw idx; sw last_added_index; - HashTable new_ht = init_reserve( Hashes.get_header()->Allocator, new_num ); - - Array::Header* hash_header = new_ht.Hashes.get_header(); - - for ( idx = 0; idx < new_ht.Hashes.num(); ++idx ) - new_ht.Hashes[idx] = -1; - - for ( idx = 0; idx < Entries.num(); ++idx ) + HashTable new_ht = init_reserve( Hashes.get_header()->Allocator, new_num ); + for ( sw idx = 0; idx < Entries.num(); ++idx ) { - Entry& entry = Entries[idx]; - FindResult find_result; - if ( new_ht.Hashes.num() == 0 ) - new_ht.grow(); - - entry = Entries[idx]; + Entry& entry = Entries[idx]; find_result = new_ht.find( entry.Key ); last_added_index = new_ht.add_entry( entry.Key ); if ( find_result.PrevIndex < 0 ) new_ht.Hashes[find_result.HashIndex] = last_added_index; - else new_ht.Entries[find_result.PrevIndex].Next = last_added_index; @@ -1984,11 +1967,10 @@ struct HashTable sw idx; FindResult find_result; - if ( Hashes.num() == 0 ) + if ( full() ) grow(); find_result = find( key ); - if ( find_result.EntryIndex >= 0 ) { idx = find_result.EntryIndex; @@ -2060,7 +2042,9 @@ protected: b32 full() { - return 0.75f * Hashes.num() < Entries.num(); + uw critical_load = uw( CriticalLoadScale * f32(Hashes.num()) ); + b32 result = Entries.num() > critical_load; + return result; } }; @@ -2096,7 +2080,7 @@ struct StrC #define txt( text ) \ StrC \ { \ - sizeof( text ) - 1, text \ + sizeof( (text) ) - 1, (text) \ } StrC to_str( char const* str )