haxis/Source/UnrealProject/GUI/Lobby/LobbyCharacterSelect.cpp

285 lines
8.4 KiB
C++

// 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<AMenuGameMode>(GetWorld()->GetAuthGameMode());
if(gm)
{
m_onPlayerJoinedDH = gm->onPlayerJoined.AddUObject(this, &ULobbyCharacterSelect::m_OnPlayerJoined);
}
UWorld* const world = GetWorld();
for (TActorIterator<ALobbySpawn> iter(world); iter; ++iter)
{
TArray<ALobbySpawn*>* find = m_spawnPoints.Find(iter->assignedTeam);
if (!find)
{
TArray<ALobbySpawn*> arr;
arr.Add(*iter);
m_spawnPoints.Add(iter->assignedTeam, arr);
}
else
find->Add(*iter);
}
m_requireUpdate = false;
m_updateInterval = 0;
AGameStateBase* gameState = Cast<AGameStateBase>(world->GetGameState());
check(gameState);
gameState->OnPlayerStateChange.AddDynamic(this, &ULobbyCharacterSelect::UpdateVisualCharacters);
}
void ULobbyCharacterSelect::NativeDestruct()
{
Super::NativeDestruct();
// Remove GameMode Callback
AMenuGameMode* gm = Cast<AMenuGameMode>(GetWorld()->GetAuthGameMode());
if(gm)
{
gm->onPlayerJoined.Remove(m_onPlayerJoinedDH);
}
UWorld* const world = GetWorld();
check(world);
AGameStateBase* gameState = Cast<AGameStateBase>(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<AGameStateBase>(world->GetGameState());
check(gameState);
TArray<int32> teamSizes = gameState->GetTeamSizes();
TArray<TArray<APlayerStateBase*>> playersPerTeam = gameState->GetPlayersByTeam();
// Check if players have left
TMap<APlayerStateBase*, VisualSlot> outdatedCharacters = m_visualCharacters;
TArray<APlayerStateBase*> 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<APlayerStateBase*, ACharacterBase*> newCharacters;
TMap<APlayerStateBase*, VisualSlot> 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<ACharacterBase>(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<APlayerControllerBase*, APlayerStateBase*> 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<UDefaultGameInstance>(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<AMenuController>(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<APlayerStateBase>(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;
}