// Project Lab - NHTV Igad #include "UnrealProject.h" #include "DefaultGameMode.h" #include "DefaultGameState.h" #include "DefaultGameInstance.h" #include "PlayerSpawn.h" #include "NetworkPlayer.h" #include "CreatureSpawn.h" #include "DefaultPlayer.h" #include "DefaultPlayerController.h" #include "DefaultPlayerState.h" #include "GameStateBase.h" #include "SessionManager.h" #if PLATFORM_SPECIFIC_WIN == 0 #include "HeatMapMetrics.h" #endif #if UE_INCLUDE_METRICS #include "MiniMapVolume.h" #include "IngameHUD.h" #include "MiniMapWidget.h" #endif ADefaultGameMode::ADefaultGameMode() { PrimaryActorTick.bCanEverTick = true; // Lobby system starts the match bDelayedStart = true; PlayerControllerClass = ADefaultPlayerController::StaticClass(); DefaultPawnClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/BP_DefaultPlayer")).Class; SpectatorClass = ConstructorHelpers::FClassFinder(TEXT("/Game/Assets/Blueprints/BP_SpectatorPawn")).Class; GameStateClass = ADefaultGameState::StaticClass(); PlayerStateClass = ADefaultPlayerState::StaticClass(); } void ADefaultGameMode::PreInitializeComponents() { Super::PreInitializeComponents(); ADefaultGameState* gameState = GetGameState(); check(gameState); m_playerSpawns.AddZeroed(4); //FString str = UGameplayStatics::ParseOption(OptionsString, FString("teamCount")); //int32 teamCountSetting = FCString::Atoi(*str); //if(teamCountSetting > 1) //{ // gameState->m_mapTeamCount = teamCountSetting; // GWPRINT(L"Set map team count to " + gameState->m_mapTeamCount); //} } void ADefaultGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); // Start game immediately in editor since we can't use seamless travel to obtain the players UWorld* world = GetWorld(); if(world->WorldType == EWorldType::PIE) { m_StartGameStartTimer(); } // Find lobby state blueprint for(TActorIterator it(world); it; ++it) { m_lobbyState = *it; } if(m_lobbyState) { GPRINT("Received traveled lobby state " + m_lobbyState->GetName()); m_totalPlayerCount = m_lobbyState->players.Num(); } else { m_totalPlayerCount = 1; } } void ADefaultGameMode::BeginPlay() { Super::BeginPlay(); #if PLATFORM_SPECIFIC_WIN == 0 HeatMapMetrics::ResetMap(); #endif } void ADefaultGameMode::EndPlay(const EEndPlayReason::Type EndPlayReason) { #if UE_INCLUDE_METRICS if (Metrics::EndSession()) JPRINT("Metrics session ended"); else JERROR("Failed to close metrics session"); #endif Super::EndPlay(EndPlayReason); #if PLATFORM_SPECIFIC_WIN == 0 HeatMapMetrics::ExportToFile(); #endif } void ADefaultGameMode::PostSeamlessTravel() { GPRINT("PostSeamlessTravel"); Super::PostSeamlessTravel(); } void ADefaultGameMode::HandleSeamlessTravelPlayer(AController*& C) { Super::HandleSeamlessTravelPlayer(C); UDefaultGameInstance* inst = Cast(GetGameInstance()); // Register new player ADefaultPlayerController* newPC = Cast(C); if(!newPC) { GERROR("Invalid player genereted by HandleSeamlessTravelPlayer"); } else { GPRINT("Transfering logged in player " + C->GetName()); AGameStateBase* gameState = Cast(GameState); gameState->RegisterPlayer(newPC); } TSharedPtr netID = newPC->PlayerState->UniqueId.GetUniqueNetId(); if(m_lobbyState) { m_lobbyState->players.RemoveAll([netID](FLobbyStatePlayer& item) { return item.netID.IsValid() && netID.IsValid() && (*item.netID == *netID); }); } m_numTravelledPlayers++; if(m_numTravelledPlayers >= m_totalPlayerCount) { // Set a timer to start the game m_StartGameStartTimer(); } else { int32 left = m_totalPlayerCount - m_numTravelledPlayers; GPRINT("Still waiting for " + left + " more players"); for(auto& p : m_lobbyState->players) { FString name = inst->sessionManager->GetPlayerName(*p.netID); GPRINT("--" + name + " [" + p.netID->ToDebugString() + "]"); } } } void ADefaultGameMode::PreLogin(const FString& Options, const FString& Address, const TSharedPtr& UniqueId, FString& ErrorMessage) { Super::PreLogin(Options, Address, UniqueId, ErrorMessage); FString username = "Invalid"; if(GameState->GetMatchState() == MatchState::InProgress) { // Check if player is reconnecting but was previously connected FString uniqueIDString = UniqueId->ToString(); if (m_connectedPlayersNetID.Contains(uniqueIDString)) { GWPRINT(L"Reconnecting player " + username + " / " + uniqueIDString); } else { ErrorMessage = "Game has already started"; } } } APlayerController* ADefaultGameMode::Login(class UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const TSharedPtr& UniqueId, FString& ErrorMessage) { // Register players that ever connected to the game so they can reconnect APlayerController* controller = Super::Login(NewPlayer, InRemoteRole, Portal, Options, UniqueId, ErrorMessage); if (controller) { FString uniqueIDString = UniqueId->ToString(); m_connectedPlayersNetID.Add(uniqueIDString); GWPRINT(L"Registering connected player ID " + uniqueIDString); } // Register logged on player // Note: this should only happen when running a PIE instance or standalone with a startup level // normal login procedure happens in the menu and players are then transfered to the game using seamless travel ADefaultPlayerController* defPC = Cast(controller); if(!defPC) { GERROR("Invalid player genereted by HandleSeamlessTravelPlayer"); } else { GPRINT("Logging in new player " + defPC->GetName()); AGameStateBase* gameState = Cast(GameState); gameState->RegisterPlayer(defPC); } return controller; } void ADefaultGameMode::PostLogin(APlayerController* NewPlayer) { Super::PostLogin(NewPlayer); // Auto assign team when in PIE UWorld* world = GetWorld(); if(world->WorldType == EWorldType::PIE) { AGameStateBase* gameState = Cast(GameState); APlayerStateBase* state = Cast(NewPlayer->PlayerState); state->AutoAssignTeam(); } } void ADefaultGameMode::Logout(AController* Exiting) { GWPRINT(L"Player left the game"); // Unregister players if not playing if (GameState->GetMatchState() != MatchState::InProgress) { if (Exiting->PlayerState) { FString uniqueIDString = Exiting->PlayerState->UniqueId->ToString(); GWPRINT(L"Removing connected player ID " + uniqueIDString); m_connectedPlayersNetID.Remove(uniqueIDString); } } Super::Logout(Exiting); } void ADefaultGameMode::Tick(float DeltaTime) { Super::Tick(DeltaTime); m_gameTimer += DeltaTime; } void ADefaultGameMode::HandleMatchHasStarted() { GWPRINT(L"Match Started"); GetGameState()->gameEnded = false; // Assign team mates ADefaultGameState* gameState = GetWorld()->GetGameState(); TArray> playersByTeam = gameState->GetPlayersByTeam(); for (int32 i = 0; i < playersByTeam.Num(); i++) { auto& arr = playersByTeam[i]; if (arr.Num() == 2) { arr[0]->teamMate = arr[1]; arr[1]->teamMate = arr[0]; } } Super::HandleMatchHasStarted(); } void ADefaultGameMode::HandleMatchHasEnded() { GWPRINT(L"Match Ended"); GetGameState()->gameEnded = true; Super::HandleMatchHasStarted(); } void ADefaultGameMode::RegisterPlayerSpawn(APlayerSpawn& spawn) { if (spawn.assignedTeam > m_playerSpawns.Num() - 1) m_playerSpawns.SetNumZeroed(spawn.assignedTeam + 1); m_playerSpawns[spawn.assignedTeam] = &spawn; } void ADefaultGameMode::RegisterPlayer(class ANetworkPlayer& player) { if(m_players.Contains(&player)) { GWARNING("Player already registered with game mode " + player.GetName()); return; } m_players.Add(&player); } void ADefaultGameMode::UnregisterPlayer(class ANetworkPlayer& player) { m_players.Remove(&player); } void ADefaultGameMode::StartGame() { #if UE_INCLUDE_METRICS // Start a new metrics session, and output the file in the Metrics folder FString filePath = FPaths::GameDir() + FString(L"Metrics/"); if (Metrics::StartSession(filePath.GetCharArray().GetData())) { // Retrieve the active map size for (TActorIterator iter(GetWorld()); iter; ++iter) { AMiniMapVolume* const volume = *iter; volume->SetActorRotation(FRotator(0, 0, 0)); FVector area = volume->area->GetScaledBoxExtent(); const FVector pos = volume->GetActorLocation(); FVector2D min, max; min.X = -area.X + pos.X; min.Y = -area.Y + pos.Y; max.X = area.X + pos.X; max.Y = area.Y + pos.Y; Metrics::SetMapData(min.X, max.X, min.Y, max.Y); break; } JPRINT("New metrics session started"); } else JERROR("Failed to open new metrics session"); #endif GPRINT("Starting Game"); SetMatchState(MatchState::InProgress); m_gameTimer = 0; } APlayerSpawn* ADefaultGameMode::GetOptimalSpawn(int32 team) { check(team > 0); if (m_playerSpawns.Num() == 0) return nullptr; if (m_playerSpawns.Num() == 1) return m_playerSpawns[0]; if (team - 1 < m_playerSpawns.Num()) return m_playerSpawns[team - 1]; // Either no players yet, or something went wrong // Pick random spawn return m_playerSpawns[FMath::Rand() % m_playerSpawns.Num()]; } TArray ADefaultGameMode::GetPlayers() { return m_players; } void ADefaultGameMode::BossHasBeenKilled(int32 team) { TArray players(m_players); // Set winning team in gamestate (replicated) GetGameState()->gameWinningTeam = team; EndMatch(); // Destroy all player pawns so that they respawn as spectators for (int32 i = 0; i < players.Num(); i++) { players[i]->Destroy(true); } TPRINT("Boss was killed. (ADefaultGameMode) the team = " + team); } float ADefaultGameMode::GetGameTime() { return m_gameTimer; } void ADefaultGameMode::m_StartGameStartTimer() { GPRINT("Starting game in 2 seconds"); GetWorld()->GetTimerManager().SetTimer(m_gameStartTimer, this, &ADefaultGameMode::m_GameStart, 2.0f, false); } void ADefaultGameMode::m_GameStart() { StartGame(); }