// Project Lab - NHTV Igad #include "UnrealProject.h" #include "DefaultGameInstance.h" #include "MenuController.h" #include "LobbySpawn.h" #include "GameStateBase.h" #include "PlayerStateBase.h" #include "PlayerControllerBase.h" #include "CharacterBase.h" #include "LobbyCharacterSelect.h" #include "MenuGameMode.h" void ULobbyCharacterSelect::m_OnPlayerJoined(APlayerController* pc) { for(auto it = m_visualCharacters.CreateIterator(); it; ++it) { (it.Value()).character->SendInitialAppearance(pc); } } void ULobbyCharacterSelect::NativeConstruct() { Super::NativeConstruct(); // Add GameMode callback AMenuGameMode* gm = Cast(GetWorld()->GetAuthGameMode()); if(gm) { m_onPlayerJoinedDH = gm->onPlayerJoined.AddUObject(this, &ULobbyCharacterSelect::m_OnPlayerJoined); } UWorld* const world = GetWorld(); for (TActorIterator iter(world); iter; ++iter) { TArray* find = m_spawnPoints.Find(iter->assignedTeam); if (!find) { TArray arr; arr.Add(*iter); m_spawnPoints.Add(iter->assignedTeam, arr); } else find->Add(*iter); } m_requireUpdate = false; m_updateInterval = 0; AGameStateBase* gameState = Cast(world->GetGameState()); check(gameState); gameState->OnPlayerStateChange.AddDynamic(this, &ULobbyCharacterSelect::UpdateVisualCharacters); } void ULobbyCharacterSelect::NativeDestruct() { Super::NativeDestruct(); // Remove GameMode Callback AMenuGameMode* gm = Cast(GetWorld()->GetAuthGameMode()); if(gm) { gm->onPlayerJoined.Remove(m_onPlayerJoinedDH); } UWorld* const world = GetWorld(); check(world); AGameStateBase* gameState = Cast(world->GetGameState()); check(gameState); gameState->OnPlayerStateChange.RemoveAll(this); } void ULobbyCharacterSelect::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) { // The updating is called once per frame // This is to prevent double calls when two players leave in one cycle // Ensure that only the server runs this if (!GetOwningPlayer()->HasAuthority()) return; // No need to update this frame if (!m_requireUpdate) return; // Await the interval m_updateInterval -= InDeltaTime; if (m_updateInterval > 0) return; m_requireUpdate = false; m_updateInterval = 0.3f; UWorld* world = GetWorld(); check(world); AGameStateBase* gameState = Cast(world->GetGameState()); check(gameState); TArray teamSizes = gameState->GetTeamSizes(); TArray> playersPerTeam = gameState->GetPlayersByTeam(); // Check if players have left TMap outdatedCharacters = m_visualCharacters; TArray players = gameState->GetPlayers(); for (int32 i = 0; i < players.Num(); i++) { VisualSlot* find = outdatedCharacters.Find(players[i]); if (find) outdatedCharacters.Remove(players[i]); } for (auto iter = outdatedCharacters.CreateIterator(); iter; ++iter) { if (IsValid(iter->Value.character)) iter->Value.character->Destroy(); m_visualCharacters.Remove(iter->Key); } // Check if players switched team TMap newCharacters; TMap newMap; for (int32 i = 1; i < playersPerTeam.Num(); i++) { auto& set = playersPerTeam[i]; for (int32 j = 0; j < set.Num(); j++) { APlayerStateBase* playerState = set[j]; VisualSlot* find = m_visualCharacters.Find(playerState); const int32 team = i - 1; if (!find || find->team != team || find->slot != j) { // Fetch the spawn coordinates if the spawnpoint exists FVector position; FRotator rotation; if (team < m_spawnPoints.Num() && j < m_spawnPoints[team].Num()) { position = m_spawnPoints[team][j]->GetActorLocation(); rotation = m_spawnPoints[team][j]->GetActorRotation(); } // Check if we need to create a new character, else we just repurpose the previous character (like when the player switched slot) ACharacterBase* character; if (!find || !IsValid(find->character)) { FActorSpawnParameters params; params.bNoFail = true; params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; character = GetWorld()->SpawnActor(characterBlueprint, FTransform::Identity, params); if (!IsValid(character)) { JERROR("Failed to spawn Actor"); return; } } else { find->character->ClearEquipment(); character = find->character; } // Set and add character->SetActorLocation(position); character->SetActorRotation(rotation.Quaternion()); newMap.Add(playerState, VisualSlot(team, j, character)); newCharacters.Add(playerState, character); } else newMap.Add(playerState, *find); } } m_visualCharacters = newMap; // Apply the customizations TMap controllers = gameState->GetPlayersByController(); for (auto iter = controllers.CreateIterator(); iter; ++iter) { ACharacterBase** character = newCharacters.Find(iter->Value); if (!character || !IsValid(*character)) continue; (*character)->SetCustomizations(iter->Key->setupState.customizations); (*character)->EquipSkillsFromSkillTreeState(iter->Key->setupState.skills); // Set class specific equipment if(characterClassProperties) { FCharacterClassProperty properties = characterClassProperties->GetCharacterClass(iter->Key->setupState.characterClass); (*character)->EquipItems(properties.classItems); } } } void ULobbyCharacterSelect::Init() { UWorld* const world = GetWorld(); check(world); UDefaultGameInstance* const instance = Cast(world->GetGameInstance()); check(instance); UCharacterSettings* const settings = instance->GetCharacterSettings(); check(settings); // Fetch the characters from the save check(settings->characterSaves.Num() > 0); m_playerCharacterSaves = settings->GetValidCharacters(); m_selection = 0; m_ApplyCharacter(); } void ULobbyCharacterSelect::OnNextCharacter() { if (m_selection == m_playerCharacterSaves.Num() - 1) return; m_selection++; m_ApplyCharacter(); } void ULobbyCharacterSelect::OnPreviousCharacter() { if (m_selection == 0) return; m_selection--; m_ApplyCharacter(); } void ULobbyCharacterSelect::UpdateCharacter_Implementation(FCharacterSave save) { // Blueprint implementation } void ULobbyCharacterSelect::m_ApplyCharacter() { UpdateCharacter(m_playerCharacterSaves[m_selection]); // Apply the selected character to the player controller UWorld* const world = GetWorld(); check(world); AMenuController* const controller = Cast(world->GetFirstPlayerController()); check(controller); FPlayerSetupState state; state.skills = m_playerCharacterSaves[m_selection].skillTreeState; state.customizations = m_playerCharacterSaves[m_selection].characterCustomization; state.characterClass = m_playerCharacterSaves[m_selection].characterClass; controller->SetSetupState(state); } void ULobbyCharacterSelect::UpdateVisualCharacters(APlayerControllerBase* playerController) { if (!IsValid(characterBlueprint)) { JERROR("Character blueprint not assigned"); return; } if (!IsValid(playerController) && playerController != nullptr) return; // Ensure that only the server runs this if (!GetOwningPlayer()->HasAuthority()) return; if (playerController != nullptr) { // Just update the visuals on the specific player APlayerStateBase* playerState = Cast(playerController->PlayerState); if (IsValid(playerState)) { VisualSlot* slot = m_visualCharacters.Find(playerState); if (slot && IsValid(slot->character)) { slot->character->SetCustomizations(playerController->setupState.customizations); slot->character->ClearEquipment(); slot->character->EquipSkillsFromSkillTreeState(playerController->setupState.skills); // Set class specific equipment if(characterClassProperties) { FCharacterClassProperty properties = characterClassProperties->GetCharacterClass(playerController->setupState.characterClass); slot->character->EquipItems(properties.classItems); } } else // Failed to find the character, refresh all m_requireUpdate = true; } } else m_requireUpdate = true; }