285 lines
8.4 KiB
C++
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;
|
|
} |