// Project Lab - NHTV Igad

#include "UnrealProject.h"
#include "DefaultGameInstance.h"
#include "DefaultGameState.h"
#include "KingOfTheHillGameMode.h"
#include "DefaultPlayer.h"
#include "DefaultPlayerState.h"
#include "DefaultPlayerController.h"
#include "CombatTextWidget.h"
#include "CharacterSettings.h"
#include "NetworkPlayer.h"
#include "NetworkGhost.h"
#include "NetworkSwitch.h"
#include "BaseSkillObject.h"
#include "PlayerSpawn.h"
#include "MiniMapWidget.h"
#include "IngameHUD.h"
#include "CombatTextWidget.h"
#include "AbilityInfo.h"
#include "HealthBar.h"
#include "LobbyMenu.h"
#include "ItemBase.h"
#include "SkillTreeWidget.h"
#include "IngameSkillTree.h"
#include "ButtonBarSwitcher.h"
#include "MenuScreenBase.h"

#include "SessionManager.h"
#include "PreCastAbilityEventGroup.h"
#include "MenuScreen.h"
#include "WidgetBlueprintLibrary.h"

// Use Inputmanager when not on playstation
#if PLATFORM_SPECIFIC_WIN == 0
#include "InputManager.hpp"
using namespace Input;
#endif

static UClass* HUDWidgetClass;
static UClass* characterClass;
static UClass* ghostClass;
static UClass* respawnWidgetClass;
static UClass* skilltreeWidgetClass;
static TSubclassOf<class UMenuScreen> menuWidgetClass;
static TSubclassOf<class UMenuPanel> ingameMenuPanelClass;

ADefaultPlayerController::ADefaultPlayerController(const FObjectInitializer& init)
	: Super(init)
{
	menuWidgetClass = ConstructorHelpers::FClassFinder<UMenuScreen>(TEXT("/Game/Assets/GUI/WEEGEE_Menu")).Class;
	ingameMenuPanelClass = ConstructorHelpers::FClassFinder<UMenuPanel>(TEXT("/Game/Assets/GUI/Menus/WEEGEE_IngameSubMenu")).Class;

	characterClassProperties = ConstructorHelpers::FObjectFinder<UCharacterClassPropertySet>(TEXT("/Game/Assets/Abilities/Classes")).Object;
	respawnWidgetClass = ConstructorHelpers::FClassFinder<UUserWidget>(TEXT("/Game/Assets/GUI/WEEGEE_Respawn")).Class;
	HUDWidgetClass =  ConstructorHelpers::FClassFinder<UUserWidget>(TEXT("/Game/Assets/GUI/WEEGEE_HUD")).Class;
	characterClass = ConstructorHelpers::FClassFinder<ANetworkPlayer>(TEXT("/Game/Assets/Blueprints/Creatures/BP_NetworkPlayer")).Class;
	ghostClass = ConstructorHelpers::FClassFinder<ANetworkGhost> (TEXT("/Game/Assets/Blueprints/Creatures/BP_NetworkGhost")).Class;
	skilltreeWidgetClass = ConstructorHelpers::FClassFinder<USkillTreeWidget> (TEXT("/Game/Assets/GUI/WEEGEE_SkillTree")).Class;
	PrimaryActorTick.bCanEverTick = true;

	m_framesSinceMenuFocus = 0;
	m_rightMouseDown = false;
	m_holdPosition = false;
	m_skillTreeInitialized = false;
	m_holdDirection = FVector();
	m_coopModifier = false;
}

void ADefaultPlayerController::BeginPlay()
{
	Super::BeginPlay();

	if(IsLocalController())
	{
		m_hud = CreateWidget<UIngameHUD>(this, HUDWidgetClass);
		m_hud->AddToViewport();
		m_OnPawnChanged();

		// Create skill tree widget
		m_skillTreeWidget = CreateWidget<USkillTreeWidget>(this, skilltreeWidgetClass);
		check(m_skillTreeWidget);
		m_skillTreeWidget->AddToViewport();

		// Build initial tree
		m_skillTreeWidget->BuildFromState(setupState.skills);

		// Load the skill tree
		m_skillTreeWidget->SetIsInteractive(false);
		m_skillTreeWidget->SetIsEnabled(true);
	}

	if(GetWorld()->IsPlayInEditor() && Role == ROLE_Authority)
	{
		// Load setup state from settings (slot 0)
		UCharacterSettings* settings = Cast<UDefaultGameInstance>(GetGameInstance())->GetCharacterSettings();
		if(settings->characterSaves.Num() > 0)
		{
			const FCharacterSave& save = settings->characterSaves[0];
			setupState.customizations = save.characterCustomization;
			setupState.skills = save.skillTreeState;
			setupState.characterClass = save.characterClass;
		}

		// Handle setupState when not comming from seamless travel
		OnRep_Setup();
	}

	this->bShowMouseCursor = true;
}
void ADefaultPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);

	if (m_hud)
		m_hud->RemoveFromViewport();

	if(IsValid(m_defaultPlayer))
	{
		m_defaultPlayer->Destroy();
	}
	if(m_respawnWidget)
	{
		m_respawnWidget->RemoveFromParent();
	}
}

void ADefaultPlayerController::Destroyed()
{
	Super::Destroyed();

#if PLATFORM_SPECIFIC_WIN == 0
	GPRINT("Unbinding Input manager bindings on " + GetName());
	InputManager* inputManager = InputManager::GetInstance();

	inputManager->DeregisterCallback(IJP_TRIGGERRIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility0);
	inputManager->DeregisterCallback(IJP_FACEDOWN, IME_PRESSED, this, &ADefaultPlayerController::CastAbility1);
	inputManager->DeregisterCallback(IJP_FACELEFT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility2);
	inputManager->DeregisterCallback(IJP_FACEUP, IME_PRESSED, this, &ADefaultPlayerController::CastAbility3);
	inputManager->DeregisterCallback(IJP_FACERIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility4);
	inputManager->DeregisterCallback(IJP_TRIGGERRIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility0);
	inputManager->DeregisterCallback(IJP_FACEDOWN, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility1);
	inputManager->DeregisterCallback(IJP_FACELEFT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility2);
	inputManager->DeregisterCallback(IJP_FACEUP, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility3);
	inputManager->DeregisterCallback(IJP_FACERIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility4);
	inputManager->DeregisterCallback(IJP_START, IME_PRESSED, this, &ADefaultPlayerController::ToggleMenu);
	inputManager->DeregisterCallback(IJP_SHOULDERLEFT, IME_PRESSED, this, &ADefaultPlayerController::ToggleSkillTree);
	inputManager->DeregisterCallback(IJP_SHOULDERLEFT, IME_RELEASED, this, &ADefaultPlayerController::ToggleSkillTree);
	inputManager->DeregisterCallback(IJP_TRIGGERLEFT, IME_PRESSED, this, &ADefaultPlayerController::CoopModifierOn);
	inputManager->DeregisterCallback(IJP_TRIGGERLEFT, IME_RELEASED, this, &ADefaultPlayerController::CoopModifierOff);
#endif
}

void ADefaultPlayerController::SetupInputComponent()
{
	if (GetWorld()->WorldType == EWorldType::Preview)
		return;

	Super::SetupInputComponent();

#if PLATFORM_SPECIFIC_WIN == 0
	GPRINT("Creating Input manager bindings on " + GetName());
	InputManager* inputManager = InputManager::GetInstance();

	inputManager->RegisterCallback(IJP_TRIGGERRIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility0);
	inputManager->RegisterCallback(IJP_FACEDOWN, IME_PRESSED, this, &ADefaultPlayerController::CastAbility1);
	inputManager->RegisterCallback(IJP_FACELEFT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility2);
	inputManager->RegisterCallback(IJP_FACEUP, IME_PRESSED, this, &ADefaultPlayerController::CastAbility3);
	inputManager->RegisterCallback(IJP_FACERIGHT, IME_PRESSED, this, &ADefaultPlayerController::CastAbility4);
	inputManager->RegisterCallback(IJP_TRIGGERRIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility0);
	inputManager->RegisterCallback(IJP_FACEDOWN, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility1);
	inputManager->RegisterCallback(IJP_FACELEFT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility2);
	inputManager->RegisterCallback(IJP_FACEUP, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility3);
	inputManager->RegisterCallback(IJP_FACERIGHT, IME_RELEASED, this, &ADefaultPlayerController::UnCastAbility4);

	inputManager->RegisterCallback(IJP_START, IME_PRESSED, this, &ADefaultPlayerController::ToggleMenu);
	inputManager->RegisterCallback(IJP_SHOULDERLEFT, IME_PRESSED, this, &ADefaultPlayerController::ToggleSkillTree);
	inputManager->RegisterCallback(IJP_SHOULDERLEFT, IME_RELEASED, this, &ADefaultPlayerController::ToggleSkillTree);
	inputManager->RegisterCallback(IJP_TRIGGERLEFT, IME_PRESSED, this, &ADefaultPlayerController::CoopModifierOn);
	inputManager->RegisterCallback(IJP_TRIGGERLEFT, IME_RELEASED, this, &ADefaultPlayerController::CoopModifierOff);
#endif

	InputComponent->BindAction("ToggleMenu", IE_Pressed, this, &ADefaultPlayerController::ToggleMenu);
	InputComponent->BindAction("ToggleSkillTree", IE_Pressed, this, &ADefaultPlayerController::ToggleSkillTree);
	InputComponent->BindAction("ToggleSkillTree", IE_Released, this, &ADefaultPlayerController::ToggleSkillTree);
	InputComponent->BindAction("CoopModifier", IE_Pressed, this, &ADefaultPlayerController::CoopModifierOn);
	InputComponent->BindAction("CoopModifier", IE_Released, this, &ADefaultPlayerController::CoopModifierOff);


#if PLATFORM_SPECIFIC_WIN == 0
#define ABILITY_KEYBINDING_OP(__name, __id) \
	InputComponent->BindAction(__name, IE_Pressed, this, &ADefaultPlayerController::CastAbility##__id);\
	InputComponent->BindAction(__name, IE_Released, this, &ADefaultPlayerController::UnCastAbility##__id);

#include "AbilityBindings.h"
#else // On playstation, have repeating attack button
	InputComponent->BindAction("Attack", IE_Pressed, this, &ADefaultPlayerController::CastAbility0);
	InputComponent->BindAction("CastAbility1", IE_Pressed, this, &ADefaultPlayerController::CastAbility1);
	InputComponent->BindAction("CastAbility2", IE_Pressed, this, &ADefaultPlayerController::CastAbility2);
	InputComponent->BindAction("CastAbility3", IE_Pressed, this, &ADefaultPlayerController::CastAbility3);
	InputComponent->BindAction("CastAbility4", IE_Pressed, this, &ADefaultPlayerController::CastAbility4);
	InputComponent->BindAction("Attack", IE_Released, this, &ADefaultPlayerController::UnCastAbility0);
	InputComponent->BindAction("CastAbility1", IE_Released, this, &ADefaultPlayerController::UnCastAbility1);
	InputComponent->BindAction("CastAbility2", IE_Released, this, &ADefaultPlayerController::UnCastAbility2);
	InputComponent->BindAction("CastAbility3", IE_Released, this, &ADefaultPlayerController::UnCastAbility3);
	InputComponent->BindAction("CastAbility4", IE_Released, this, &ADefaultPlayerController::UnCastAbility4);
#endif

	// We have 2 versions of the rotation bindings to handle different kinds of devices differently
	// "turn" handles devices that provide an absolute delta, such as a mouse.
	// "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick

	InputComponent->BindAxis("MoveForward", this, &ADefaultPlayerController::MoveForward);
	InputComponent->BindAxis("MoveRight", this, &ADefaultPlayerController::MoveRight);

	InputComponent->BindAxis("TurnRate", this, &ADefaultPlayerController::TurnAtRate);
	InputComponent->BindAxis("LookUp", this, &ADefaultPlayerController::LookUp);
	InputComponent->BindAxis("LookUpRate", this, &ADefaultPlayerController::LookUpAtRate);


	InputComponent->BindAction("RightMouse", IE_Pressed, this, &ADefaultPlayerController::RightMouseDown);
	InputComponent->BindAction("RightMouse", IE_Released, this, &ADefaultPlayerController::RightMouseUp);
	InputComponent->BindAction("LeftMouse", IE_Pressed, this, &ADefaultPlayerController::LeftMouseDown);
	InputComponent->BindAction("LeftMouse", IE_Released, this, &ADefaultPlayerController::LeftMouseUp);
	
	InputComponent->BindAction("HoldPosition", IE_Pressed, this, &ADefaultPlayerController::HoldDown);
	InputComponent->BindAction("HoldPosition", IE_Released, this, &ADefaultPlayerController::HoldUp);

}

void ADefaultPlayerController::Tick(float DeltaSeconds)
{
	APawn* pawn = GetPawnOrSpectator();

	if (IsLocalController() && IsValid(m_defaultPlayer) && IsValid(pawn) && pawn != m_defaultPlayer)
		m_defaultPlayer->SetActorLocation(pawn->GetActorLocation());

	// Tick basic attacks while the user keeps the button held
	if(m_usingBasicAttack)
	{
		m_TickBasicAttack();
	}

	// Set the menu focus state
	if(!m_ingameMenuPanel)
		m_framesSinceMenuFocus++;
	else
		m_framesSinceMenuFocus = 0;


	ANetworkGhost* const asGhost = Cast<ANetworkGhost>(pawn);
	if (IsValid(asGhost) && asGhost->CanRespawn())
	{
		// Respawn the player character when we are done spooking
		SpawnCharacterForClient(asGhost->GetActorLocation(), asGhost->GetActorRotation());
		asGhost->Destroy();
	}

	// Get possessed character, if any
	ANetworkPossessable* const character = Cast<ANetworkPossessable>(pawn);
	if(IsValid(character) && IsLocalController() && PlayerState && HasGameFocus())
	{
		// Do UI / Input updates on this controller
		m_UpdateUI();

#if PLATFORM_SPECIFIC_WIN == 0 
		InputManager* inputManager = InputManager::GetInstance();

		Joystick* joystick = inputManager->joystick;
		if (joystick)
		{
			TeaLib::Math::Vector2f lStick = joystick->GetDelta(IJA_LSTICK);
			if (lStick.Length() > 0.2f)
			{
				FVector forward = (character->CameraBoom->GetForwardVector() * -lStick.y) + (character->CameraBoom->GetRightVector() * lStick.x);
				FRotator current = character->GetActorRotation();
				FRotator difference = forward.Rotation();
				character->SetActorRotation(FRotator(current.Pitch, difference.Yaw, current.Roll));
				{
					MoveForward(-lStick.y);
					MoveRight(lStick.x);
				}
			}
		}
#endif

		FVector2D mousePos;
		if(GetMousePosition(mousePos.X, mousePos.Y))
		{
			FVector location;
			FVector direction;
			if(DeprojectMousePositionToWorld(location, direction))
			{
				const FVector origin = character->TopDownCamera->GetComponentLocation();
				const FVector position = pawn->GetActorLocation();
				const FVector normal = FVector(0, 0, 1);
				float entry = FVector::DotProduct(position - origin, normal) / FVector::DotProduct(direction, normal);

				const FVector lookatPos = origin + direction * entry;

				FVector dif = lookatPos - pawn->GetActorLocation();
				dif.Normalize();
				if(!m_rightMouseDown)
				{
					// Only update lookat if the mouse had moved
					if(m_mouseLast != mousePos)
					{
						FRotator current = pawn->GetActorRotation();
						FRotator difference = dif.Rotation();
						character->SetCharacterRotation(FRotator(current.Pitch, difference.Yaw, current.Roll));
						m_mouseLast = mousePos;
					}
				}
				else
					character->AddMovementInput(dif, 1);
			}
		}
	}
}

void ADefaultPlayerController::Possess(APawn* pawn)
{
	Super::Possess(pawn);

	// Store the initialy posesesed player, since it is the default player.
	if (!IsValid(m_defaultPlayer))
		m_defaultPlayer = Cast<ADefaultPlayer>(pawn);

	// Get possessed character, if any
	ANetworkPlayer* character = Cast<ANetworkPlayer>(pawn);

	// Get possessed ghost, if any
	ANetworkGhost* ghost = Cast<ANetworkGhost>(pawn);

	// Assign player name to character
	ACharacterBase* charBase = Cast<ACharacterBase>(pawn);
	UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
	if(charBase)
		charBase->playerName = inst->sessionManager->GetPlayerName(*PlayerState->UniqueId);

	// Set the character on the player state
	if(Role == ROLE_Authority)
	{
		if(PlayerState)
		{
			// Customize ghost character
			if(ghost)
			{
				TArray<FIngameSkillTreeSkill> skills = m_skillTree->GetSkillsForLevel(Cast<ADefaultPlayerState>(PlayerState));
				TArray<UBaseSkillObject*> equip;
				for(FIngameSkillTreeSkill& s : skills)
				{
					equip.Add(s.skillObject);
				}
				ghost->EquipSkills(equip);
				ghost->SetCustomizations(setupState.customizations);
			}

			Cast<ADefaultPlayerState>(PlayerState)->character = character;
		}

		// Host should handle possess events directly instead of throug OnRep_Pawn
		m_OnPawnChanged();
	}
}

void ADefaultPlayerController::SeamlessTravelFrom(class APlayerController* OldPC)
{
	Super::SeamlessTravelFrom(OldPC);
	m_seamlessTravelDone = true;
	// Handle receiving the setup state
	OnRep_Setup();
}

void ADefaultPlayerController::PawnPendingDestroy(APawn* inPawn)
{
	Super::PawnPendingDestroy(inPawn);

	ANetworkPlayer* asPlayer = Cast<ANetworkPlayer>(inPawn);
	if(IsValid(asPlayer)) // The player is destroyed?
	{
		// Clear learned skills array
		m_alreadyLearnedSkills.Reset();
	}

	bool isKoth = false;
	UWorld* world = GetWorld();
	if(!IsValid(world))
		return;
	if(world->bIsTearingDown)
		return;
	AGameMode* gm = world->GetAuthGameMode();
	if(gm && gm->IsA<AKingOfTheHillGameMode>())
	{
		isKoth = true;
	}


	if (!isKoth && inPawn->IsA(ANetworkPossessable::StaticClass()))
	{
		// Spawn a ghost
		FTransform f;
		f.SetScale3D(FVector(1, 1, 1));
		ANetworkPossessable* asPossessable = Cast<ANetworkPossessable>(inPawn);
		if (asPossessable->GetCharacterMovement()->IsMovingOnGround())
		{
			// We can spawn the ghost at the location of the dead player
			f.SetTranslation(asPossessable->GetActorLocation());
			f.SetRotation(asPossessable->GetActorRotation().Quaternion());
		}
		else if (playerLocationHistory.Num() > 0)
		{
			// The player was falling, we must refer to the location history for a valid spawn spot
			f.SetTranslation(playerLocationHistory[0].location);
			f.SetRotation(playerLocationHistory[0].rotation.Quaternion());
		}

		FActorSpawnParameters params;
		params.bNoFail = true;
		params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
		ANetworkGhost* ghost = GetWorld()->SpawnActor<ANetworkGhost>(ghostClass, f, params);
		ADefaultPlayerState* playerState = Cast<ADefaultPlayerState>(PlayerState);
		check(playerState);
		ghost->SetTeam(playerState->GetTeam());
		check(ghost);
		Possess(ghost);
	}


	// Handle reposessing the default player
	if(IsValid(m_defaultPlayer))
	{
		// Posess the default player if nothing is possessed
		if (isKoth)
		{
			Possess(m_defaultPlayer);
			m_defaultPlayer->OnSpawn();
		}
	}
	else
	{
		GERROR("Can't reposses default player, it hasen't previously been set");
	}

	// Update possessed character on player state
	if(PlayerState)
	{
		Cast<ADefaultPlayerState>(PlayerState)->character = nullptr;
	}

	// Host should handle possess events directly instead of throug OnRep_Pawn
	m_OnPawnChanged();
}

void ADefaultPlayerController::UpdatePlayerPosition(const FVector& location, const FRotator& rotator)
{
	if (playerLocationHistory.Num() == 0)
	{
		playerLocationHistory.Add(ADefaultPlayerController::Location(location, rotator));
	}
	else
	{
		// Add only if distance to last point is larger than 100
		ADefaultPlayerController::Location& mostRecent = playerLocationHistory.Last();
		const FVector2D thisPos2D = FVector2D(location.X, location.Y);
		if (FVector2D::DistSquared(FVector2D(mostRecent.location.X, mostRecent.location.Y), thisPos2D) > 10000)
			playerLocationHistory.Add(ADefaultPlayerController::Location(location, rotator));

		// Ensure that we dont exceed more than 3
		if (playerLocationHistory.Num() > 3)
			playerLocationHistory.RemoveAt(0, playerLocationHistory.Num() - 3);
	}
}

void ADefaultPlayerController::OnRep_Pawn()
{
	Super::OnRep_Pawn();
	m_OnPawnChanged();
}
void ADefaultPlayerController::OnRep_Setup()
{
	Super::OnRep_Setup();
	if(IsLocalController())
	{
		if(!m_skillTreeInitialized)
		{
			// Request the server to initialize the skilltree
			GWPRINT(L"Loading skill tree for " + GetName());
			UCharacterSettings* settings = Cast<UDefaultGameInstance>(GetGameInstance())->GetCharacterSettings();

			// Update state on the widget
			if(m_skillTreeWidget)
			{
				m_skillTreeWidget->BuildFromState(setupState.skills);
			}

			m_skillTreeInitialized = true;
		}
	}

	// Create a the ingame skilltree on clients and server for ability unlock calculation
	if(!m_skillTree)
	{
		m_CreateIngameSkilltree();
		m_skillTree->BuildFromState(setupState.skills);
	}

	// Assign basic attack
	if(characterClassProperties)
	{
		if(characterClassProperties->classes.Num() > 0)
		{
			setupState.characterClass = FMath::Clamp(setupState.characterClass, 0, characterClassProperties->classes.Num()-1);
			m_classProperties = characterClassProperties->classes[setupState.characterClass];
		}
	}
	else
	{
		GERROR("characterClassProperties is not assigned");
	}
}

FCharacterClassProperty ADefaultPlayerController::GetCharacterClassProperties() const
{
	return m_classProperties;
}

class UIngameHUD* ADefaultPlayerController::GetHUD()
{
	return m_hud;
}

void ADefaultPlayerController::OnLevelUpClient_Implementation(const TArray<FIngameSkillTreeSkill>& updatedSkills, const TArray<FIngameSkillTreeSkill>& oldSkills)
{
	m_alreadyLearnedSkills = oldSkills;

	// Show level up hud
	if (m_hud)
		m_hud->OnLevelUp(updatedSkills);
	UpdateLevel();

	// Add abilities to the list of learned abilities
	for (int32 i = 0; i < updatedSkills.Num(); i++)
	{
		UAbilityInfo* info = updatedSkills[i].selectedEffect;
		if (info)
		{
			m_learnedAbilities.Add(info);
		}
	}
}
void ADefaultPlayerController::LearnSkillsForLevel()
{
	ANetworkPlayer* player = Cast<ANetworkPlayer>(GetPawn());
	if(!player)
		return;
	if(!m_skillTree)
		return;

	TArray<FIngameSkillTreeSkill> skills = m_skillTree->GetSkillsForLevel(Cast<ADefaultPlayerState>(PlayerState));

	TArray<UBaseSkillObject*> skillsToEquip;

	// Calculate skill delta
	TArray<FIngameSkillTreeSkill> updatedSkills;
	for (int32 i = 0; i < skills.Num(); i++)
	{
		FIngameSkillTreeSkill& skillNew = skills[i];
		bool found = false;
		for (int32 j = 0; j < m_alreadyLearnedSkills.Num(); j++)
		{
			FIngameSkillTreeSkill& skillOld = m_alreadyLearnedSkills[j];
			if (skillOld.skillObject == skillNew.skillObject)
			{
				if(skillOld.level < skillNew.level)
				{
					updatedSkills.Add(skills[i]);
				}
				found = true;
				break;
			}
		}
		if (!found)
			updatedSkills.Add(skills[i]);

		skillsToEquip.Add(skillNew.skillObject);
	}

	// Override player basic attack by class
	if(player->abilities.Num() < 1)
		player->abilities.SetNumZeroed(1);
	player->abilities[0] = player->basicAttack = m_classProperties.basicAttack;

	// Equip skill items on all replicas of this player
	player->EquipSkills(skillsToEquip);

	// Update customizations
	player->SetCustomizations(setupState.customizations);

	// Learn abilities
	player->LearnSkills_Server(updatedSkills);

	// Equip class specific items, overrides equipment set by the skill tree
	if(m_classProperties.classItems.Num() > 0)
		player->EquipItems(m_classProperties.classItems);

	// Equip more skill items
	if(PlayerState)
	{
		FString steamID = Cast<UDefaultGameInstance>(GetGameInstance())->sessionManager->GetSteamID(*PlayerState->UniqueId);
		if(steamID == "76561198008045751")
		{
			GPRINT("Hoi frank");
		}
	}

	OnLevelUpClient(updatedSkills, m_alreadyLearnedSkills);

	// Update already learned skills
	for(int32 i = 0; i < m_alreadyLearnedSkills.Num(); i++)
	{
		bool found = false;
		for(int32 j = 0; j < skills.Num(); j++)
		{
			if(skills[j].skillObject == m_alreadyLearnedSkills[i].skillObject)
			{
				found = true;
				break;
			}
		}
		if(!found)
		{
			m_alreadyLearnedSkills[i].power = 0.0f;
			m_alreadyLearnedSkills[i].level = 0;
			skills.Add(m_alreadyLearnedSkills[i]);
		}
	}

	m_alreadyLearnedSkills = skills;
}
int32 ADefaultPlayerController::GetCurrentAbilityLevel(class UAbilityInfo* ability)
{
	for(int32 i = 0; i < m_alreadyLearnedSkills.Num(); i++)
	{
		if(m_alreadyLearnedSkills[i].selectedEffect == ability)
		{
			return m_alreadyLearnedSkills[i].level;
		}
	}
	return 0;
}

bool ADefaultPlayerController::GetAbilityButtonLocation(UAbilityInfo* ability, int32& slot, bool& isAlt)
{
	isAlt = false;
	for(slot = 0; slot < m_mainSkillMapping.Num(); slot++)
	{
		if(m_mainSkillMapping[slot] == ability)
			return true;
	}
	isAlt = true;
	for(slot = 0; slot < m_altSkillMapping.Num(); slot++)
	{
		if(m_altSkillMapping[slot] == ability)
			return true;
	}
	return false;
}

void ADefaultPlayerController::ToggleSkillTree()
{
	if(IsLocalController() && HasGameFocus())
	{
		if(!m_skillTreeWidget->IsShow())
		{
			m_skillTreeWidget->Show();
		}
		else
		{
			m_skillTreeWidget->Hide();
		}
	}
}
void ADefaultPlayerController::ToggleMenu()
{
	if(!m_ingameMenu)
	{
		m_ingameMenu = CreateWidget<UMenuScreen>(GetWorld(), menuWidgetClass);
		m_ingameMenu->AddToViewport(50);
	}
	if(!m_ingameMenuPanel)
	{
		m_ingameMenu->OnShow();
		m_ingameMenuPanel = m_ingameMenu->OpenScreenMenu(ingameMenuPanelClass);
	}
	else
	{
		OnHideUI();
	}
	UWidgetBlueprintLibrary::SetFocusToGameViewport();
}
void ADefaultPlayerController::OnHideUI()
{
	if(!m_ingameMenuPanel)
		return;
	m_ingameMenu->OnHide();
	m_ingameMenu->CloseSubMenu(m_ingameMenuPanel);
	m_ingameMenuPanel = nullptr;
	UWidgetBlueprintLibrary::SetFocusToGameViewport();
}

void ADefaultPlayerController::m_OnPawnChanged()
{
	APawn* pawn = GetPawnOrSpectator();
	ANetworkPlayer* player = Cast<ANetworkPlayer>(pawn);
	ANetworkPossessable* possessable = Cast<ANetworkPossessable>(pawn);
	const bool isValid = IsValid(pawn) && pawn->IsA<ANetworkPossessable>();

	if (m_hud)
	{
		// Calculate skill prototypes
		if (m_skillTree)
		{
			TArray<FIngameSkillTreeSkill> skillsetPrototype = m_skillTree->GetSkillsForLevel(1.0f);
			m_hud->NativeOnAssignCharacter(possessable, skillsetPrototype);

			// Set Skill bindings
			m_mainSkillMapping.SetNum(0);
			m_altSkillMapping.SetNum(0);
			for (int32 i = 0; i < skillsetPrototype.Num(); i++)
			{
				switch (skillsetPrototype[i].abilityType)
				{
				case 0:
					m_mainSkillMapping.Add(skillsetPrototype[i].selectedEffect);
					break;
				case 1:
					m_altSkillMapping.Add(skillsetPrototype[i].selectedEffect);
					break;
				}
			}
		}
		m_hud->SetVisibility(isValid ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
	}

	// Create respawn widget on local player
	if(IsLocalController())
	{
		if (isValid)
		{
			if (m_respawnWidget)
			{
				m_respawnWidget->RemoveFromViewport();
				m_respawnWidget = nullptr;
			}
		}
		else // Assume default player
		{
			// Create respawn widget
			if(!m_respawnWidget)
			{
				m_respawnWidget = CreateWidget<UUserWidget>(GetWorld(), respawnWidgetClass);
				m_respawnWidget->AddToViewport(30);
			}
		}
	}
}
void ADefaultPlayerController::m_UpdateUI()
{
	ANetworkPlayer* character = Cast<ANetworkPlayer>(GetPawn());
	if(m_hud && character)
	{
		ADefaultPlayerState* state = Cast<ADefaultPlayerState>(PlayerState);

		TArray<AAbilityState*> abilityStates;
		for(int32 i = 0; i < character->abilities.Num(); ++i)
		{
			UAbilityInfo* ability = character->abilities[i];
			AAbilityState* state = character->GetAbilityState(ability);
			state->cooldownRate = 1.0f - (state->onCooldownTimer / ability->cooldown);
			if (ability->cooldown < 0.1f)
				state->cooldownRate = 1.0f;
			abilityStates.Add(state);
		}
		m_hud->UpdateCooldowns(abilityStates, character);
	}
}

void ADefaultPlayerController::m_CreateIngameSkilltree()
{
	if (!m_skillTree)
	{
		// Create logic-only skilltree
		m_skillTree = GetWorld()->SpawnActor<AIngameSkillTree>(AIngameSkillTree::StaticClass());
		check(m_skillTree);
	}
}

void ADefaultPlayerController::OnCharacterCreated(ANetworkCharacter* character)
{
	if (m_hud)
	{
		m_hud->OnCharacterCreated(character);
	}
	else
	{
		GWWARNING(L"OnCharacterCreated called on a controller without a HUD");
	}
}
void ADefaultPlayerController::OnCharacterDestroyed(ANetworkCharacter* character)
{
	if (m_hud)
	{
		m_hud->OnCharacterDestroyed(character);
	}
	else
	{
		GWWARNING(L"OnCharacterDestroyed called on a controller without a HUD");
	}
}

const TArray<class UAbilityInfo*>& ADefaultPlayerController::GetAbilities(APawn* targetPawn)
{
	if (!IsValid(targetPawn))
		targetPawn = GetPawn();
	ANetworkPlayer* netPlayer = Cast<ANetworkPlayer>(targetPawn);
	if (netPlayer)
	{
		return netPlayer->abilities;
	}
	static TArray<class UAbilityInfo*> dummy;
	return dummy;
}

bool ADefaultPlayerController::HasGameFocus()
{
	return m_framesSinceMenuFocus > 10;
}

void ADefaultPlayerController::CastAbility(int32 index)
{
	if(!HasGameFocus())
		return;

	// Basic Attack?
	if(index == 0)
	{
		m_StartBasicAttack();
		m_usingBasicAttack = true;
	}

	ANetworkPlayer* netPlayer = Cast<ANetworkPlayer>(GetPawn());
	UAbilityInfo* info = GetMappedAbility(index);
	if(netPlayer)
	{
		// Obtain assigned basic attack ability
		if(index == 0)
			info = netPlayer->basicAttack;
		if(info)
		{
			netPlayer->CastAbility(info);
		}
	}
}
void ADefaultPlayerController::UnCastAbility(int32 index)
{
	if(!HasGameFocus())
		return;

	// Basic Attack?
	if(index == 0)
	{
		m_StopBasicAttack();
		m_usingBasicAttack = false;
	}

	// Only main-bar abilities and basic attack
	if(index >= 5)
		return;


	// This casts abilities again which are hold abilities to turn them off
	ANetworkPlayer* netPlayer = Cast<ANetworkPlayer>(GetPawn());
	UAbilityInfo* info = GetMappedAbility(index);

	if (info && info->precastEvent && netPlayer && IsValid(netPlayer->m_currentPreCast))
	{
		netPlayer->m_currentPreCast->StartAbility();
		return;
	}
	if(netPlayer && info)
	{
		if(info->actionType == EAbilityActionType::Hold)
		{
			AAbilityState* state = netPlayer->GetAbilityState(info);
			if(state->toggleState == 1)
				netPlayer->CastAbility(info);
		}
	}
}
UAbilityInfo* ADefaultPlayerController::GetMappedAbility(int32 index)
{
	ANetworkPlayer* netPlayer = Cast<ANetworkPlayer>(GetPawn());
	if(netPlayer)
	{
		if(index == 0)
		{
			if(netPlayer->abilities.Num() > 0)
				return netPlayer->abilities[0];
		}
		else
		{
			bool useAlt = index > 4;
			index = index - (useAlt ? 5 : 1);

			UAbilityInfo* ability = m_hud->buttonBarSwitcher->GetAbilityMapping(index, useAlt);
			if(ability)
			{
				if(m_learnedAbilities.Contains(ability))
				{
					return ability;
				}
			}
		}
	}
	return nullptr;
}

void ADefaultPlayerController::TurnAtRate(float Rate)
{
	if(!HasGameFocus())
		return;

	APawn* pawn = GetPawn();
	if (!pawn)
		return;
	// calculate delta for this frame from the rate information
	pawn->AddControllerYawInput(Rate * 45.f * GetWorld()->GetDeltaSeconds());
}
void ADefaultPlayerController::LookUp(float Rate)
{
	if(!HasGameFocus())
		return;

	APawn* pawn = GetPawn();
	if (!pawn)
		return;
	pawn->AddControllerPitchInput(Rate);
}
void ADefaultPlayerController::LookUpAtRate(float Rate)
{
	if(!HasGameFocus())
		return;

	APawn* pawn = GetPawn();
	if (!pawn)
		return;
	// calculate delta for this frame from the rate information
	pawn->AddControllerPitchInput(Rate * 45.f * GetWorld()->GetDeltaSeconds());
}
void ADefaultPlayerController::MoveForward(float Value)
{
	if(!HasGameFocus())
		return;

	ANetworkPossessable* pawn = Cast<ANetworkPossessable>(GetPawn());
	if (!pawn)
		return;
	if (Value != 0.0f)
	{
		FVector forward = pawn->CameraBoom->GetForwardVector();
		forward.Z = 0;
		forward.Normalize();
		pawn->AddMovementInput(forward, Value);
	}
}
void ADefaultPlayerController::MoveRight(float Value)
{
	if(!HasGameFocus())
		return;

	ANetworkPossessable* pawn = Cast<ANetworkPossessable>(GetPawn());
	if (!pawn)
		return;
	if (Value != 0.0f)
	{
		FVector right = pawn->CameraBoom->GetRightVector();
		right.Z = 0;
		right.Normalize();
		pawn->AddMovementInput(right, Value);
	}
}

#define ABILITY_ATTACK(__id, __name) \
void ADefaultPlayerController::CastAbility##__id()\
{\
	CastAbility(__id);\
}\
void ADefaultPlayerController::UnCastAbility##__id()\
{\
	UnCastAbility(__id);\
}
#define ABILITY_OP(__id, __name) \
void ADefaultPlayerController::CastAbility##__id()\
{\
	if(m_coopModifier)\
		CastAbility(__id + 4);\
	else\
		CastAbility(__id);\
}\
void ADefaultPlayerController::UnCastAbility##__id()\
{\
	UnCastAbility(__id);\
}
#include "AbilityBindings.h"

void ADefaultPlayerController::RightMouseDown()
{
	m_rightMouseDown = true;
}
void ADefaultPlayerController::RightMouseUp()
{
	m_rightMouseDown = false;
}
void ADefaultPlayerController::LeftMouseDown()
{
	if(!HasGameFocus())
		return;

	// Check for switches
	FHitResult result;
	if (GetHitResultUnderCursor(ECollisionChannel::ECC_WorldDynamic, false, result))
	{
		ANetworkPlayer* pawn = Cast<ANetworkPlayer>(GetPawn());
		if (result.Actor.Get() && result.Actor.Get()->IsA(ANetworkSwitch::StaticClass()) && pawn)
		{
			pawn->ToggleSwitch(Cast<ANetworkSwitch>(result.Actor.Get()));
		}
	}
}

void ADefaultPlayerController::ToggleSwitch()
{
	ANetworkPlayer* pawn = Cast<ANetworkPlayer>(GetPawn());
	if (pawn)
	{
		TArray<FOverlapResult> hits;

		FCollisionQueryParams TraceParams(FName(TEXT("OverlapMulti Trace")), false);
		TraceParams.bTraceComplex = false;
		TraceParams.bReturnPhysicalMaterial = false;
		FVector position = pawn->GetActorLocation();
		FQuat rotation = FQuat();
		FCollisionShape colShape = FCollisionShape::MakeSphere(300);

		GetWorld()->OverlapMultiByObjectType(hits, position, rotation, FCollisionObjectQueryParams::AllDynamicObjects, colShape, TraceParams);

		for (size_t i = 0; i < hits.Num(); i++)
		{
			if (hits[i].GetActor()->IsA(ANetworkSwitch::StaticClass()))
			{
				pawn->ToggleSwitch(Cast<ANetworkSwitch>(hits[i].GetActor()));
				break;
			}
		}

	}
}

void ADefaultPlayerController::LeftMouseUp()
{

}
void ADefaultPlayerController::HoldDown()
{
	m_holdPosition = true;
}
void ADefaultPlayerController::HoldUp()
{
	m_holdPosition = false;
}

void ADefaultPlayerController::SpawnCharacterForClient()
{
	check(Role == ROLE_Authority);
	UWorld* const world = GetWorld();
	check(world);

	ADefaultPlayerState* playerState = Cast<ADefaultPlayerState>(PlayerState);
	check(playerState);
	ADefaultGameMode* gameMode = world->GetAuthGameMode<ADefaultGameMode>();
	check(gameMode);
	if(gameMode->GetMatchState() != MatchState::InProgress)
		return;

	int32 team = playerState->GetTeam();

	ADefaultGameMode* mode = Cast<ADefaultGameMode>(world->GetAuthGameMode());
	check(mode);

	// Get the spawns and spawn randomly
	APlayerSpawn* const spawn = mode->GetOptimalSpawn(team);
	if (IsValid(spawn))
	{
		SpawnCharacterForClient(spawn->GetActorLocation() + FVector(0, 0, 120), spawn->GetActorRotation());
	}
	else
		JERROR("No spawn points available");
}
void ADefaultPlayerController::SpawnCharacterForClient(const FVector& location, const FRotator& rotation)
{
	check(Role == ROLE_Authority);
	UWorld* const world = GetWorld();
	check(world);

	ADefaultPlayerState* playerState = Cast<ADefaultPlayerState>(PlayerState);
	check(playerState);
	ADefaultGameMode* gameMode = world->GetAuthGameMode<ADefaultGameMode>();
	check(gameMode);
	if (gameMode->GetMatchState() != MatchState::InProgress)
		return;

	int32 team = playerState->GetTeam();

	ADefaultGameMode* mode = Cast<ADefaultGameMode>(world->GetAuthGameMode());
	check(mode);

	FTransform f;
	f.SetTranslation(location + FVector(0, 0, 120));
	f.SetRotation(rotation.Quaternion());
	ANetworkPlayer* character = world->SpawnActor<ANetworkPlayer>(characterClass, f);
	if (!character)
	{
		GWERROR(L"Failed to spawn character for client, update client??");
		return;
	}
	character->SetTeam(team);

	mode->RegisterPlayer(*character);
	Possess(character);

#if UE_INCLUDE_METRICS
	if (Role == ROLE_Authority)
	{
		// Register a new player, with name
		std::wstring playerName = std::wstring(L"Player ") + GetUniqueID();
		if (IsValid(playerState))
		{
			playerName = std::wstring() + playerState->PlayerName;
			if (playerName.length() > 512)
				playerName.resize(512);
		}

		// Assign metrics handle
		if (!metricsHandle)
			metricsHandle = &Metrics::RegisterPlayer(playerName, uint8(team));
		character->metricsHandle = metricsHandle;
		metricsHandle->OnPlayerSpawn(location.X, location.Y);
	}
#endif
}

bool ADefaultPlayerController::LearnSkillOnServer_Validate(UBaseSkillObject* object)
{
	return object != nullptr && (m_skills.Find(object) == INDEX_NONE);
}
void ADefaultPlayerController::LearnSkillOnServer_Implementation(UBaseSkillObject* object)
{
	m_skills.Push(object);
}
bool ADefaultPlayerController::UnlearnSkillOnServer_Validate( UBaseSkillObject* object )
{
	return object != nullptr && (m_skills.Find( object ) > INDEX_NONE);
}
void ADefaultPlayerController::UnlearnSkillOnServer_Implementation( UBaseSkillObject* object )
{
	m_skills.Remove( object );
}

void ADefaultPlayerController::OnLearnSkill(UBaseSkillObject* object)
{
	//JPRINT("Learned skill " + object );
	//LearnSkillOnServer( object );
}
void ADefaultPlayerController::OnUnlearnSkill(UBaseSkillObject* object)
{
	//JPRINT("Unlearned skill " + object );
	//UnlearnSkillOnServer( object );
}

TArray<UBaseSkillObject*>& ADefaultPlayerController::GetSkills()
{
	return m_skills;
}

void ADefaultPlayerController::SpawnCombatText_Implementation(const FVector& position, const FString& string, const FLinearColor& color)
{
	if (IsValid(m_hud) && IsValid(m_hud->combatText))
		m_hud->combatText->CreateCombatText(position, string, color);
}
void ADefaultPlayerController::SpawnArcingCombatText_Implementation(const FVector& position, const FString& string, const FLinearColor& color)
{
	if (IsValid(m_hud) && IsValid(m_hud->combatText))
		m_hud->combatText->CreateArcingCombatText(position, string, color);
}

void ADefaultPlayerController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ADefaultPlayerController, m_skills);
	DOREPLIFETIME(ADefaultPlayerController, m_defaultPlayer);
}

void ADefaultPlayerController::UpdateLevel()
{
	if(IsLocalController())
	{
		ADefaultPlayerState* state = Cast<ADefaultPlayerState>(PlayerState);
		if(m_skillTreeWidget && state)
		{
			m_skillTreeWidget->UpdateLevel(state->GetLevel() / (float)state->GetMaxLevel());
		}
	}
}

void ADefaultPlayerController::SetLevel(int32 level)
{
	SetLevel_Server(level);
}

void ADefaultPlayerController::Suicide()
{
	Suicide_Server();
}
bool ADefaultPlayerController::Suicide_Server_Validate()
{
	return true;
}
void ADefaultPlayerController::Suicide_Server_Implementation()
{
#if WITH_EDITORONLY_DATA == 1
	ANetworkPlayer* player = Cast<ANetworkPlayer>(GetPawn());
	if(player && player->abilities[0])
	{
		player->NativeDealDamage(player, 9999999, 1.0f, player->abilities[0]);
	}
#endif
}

bool ADefaultPlayerController::SetLevel_Server_Validate(int32 level)
{
	return true;
}
void ADefaultPlayerController::SetLevel_Server_Implementation(int32 level)
{
#if WITH_EDITORONLY_DATA == 1
	ADefaultPlayerState* state = Cast<ADefaultPlayerState>(PlayerState);
	if(state)
	{
		state->SetLevel(level);
	}
#endif
}

void ADefaultPlayerController::TestWinGame(int32 team)
{
	if(Role == ROLE_Authority)
	{
		ADefaultGameMode* gm = Cast<ADefaultGameMode>(GetWorld()->GetAuthGameMode());
		AKingOfTheHillGameMode* kgm = Cast<AKingOfTheHillGameMode>(GetWorld()->GetAuthGameMode());
		if(kgm)
		{
			kgm->WinGame(team);
		}
		else if(gm)
		{
			gm->EndMatch();

			auto players = gm->GetPlayers();
			// Destroy all player pawns so that they respawn as spectators
			for(int32 i = 0; i < players.Num(); i++)
			{
				if(IsValid(players[i]))
				{
					players[i]->Destroy(true);
				}
			}
		}
	}
}

void ADefaultPlayerController::CoopModifierOn()
{
	if(!HasGameFocus())
		return;
	
	m_coopModifier = true;
	if(m_hud && m_hud->buttonBarSwitcher)
	{
		m_hud->buttonBarSwitcher->ToggleSkillSet(m_coopModifier);
	}
}
void ADefaultPlayerController::CoopModifierOff()
{
	if(!HasGameFocus())
		return;

	m_coopModifier = false;
	if(m_hud && m_hud->buttonBarSwitcher)
	{
		m_hud->buttonBarSwitcher->ToggleSkillSet(m_coopModifier);
	}
}

void ADefaultPlayerController::m_StartBasicAttack()
{
}
void ADefaultPlayerController::m_TickBasicAttack()
{
	ANetworkPlayer* netPlayer = Cast<ANetworkPlayer>(GetPawn());
	if(netPlayer)
	{
		UAbilityInfo* info = netPlayer->basicAttack;

		// Tick normal basic attacks
		if(info && info->actionType == EAbilityActionType::Normal)
		{
			netPlayer->CastAbility(info);
		}
	}
}
void ADefaultPlayerController::m_StopBasicAttack()
{
}