haxis/Source/UnrealProject/GUI/SkillTree/SkillTreeWidget.cpp

678 lines
19 KiB
C++

// Project Lab - NHTV Igad
#include "UnrealProject.h"
#include "SkillTreeWidget.h"
#include "SkillTreeObject.h"
#include "BaseSkillObject.h"
#include "HexagonTile.h"
#include "ScrollBox.h"
#include "SkillWidget.h"
#include "CanvasPanel.h"
#include "PlayerControllerBase.h"
#include "DefaultGameInstance.h"
#include "CharacterSettings.h"
#include "SlateBlueprintLibrary.h"
#include "ToolTipWidget.h"
#include "AbilityInfo.h"
#include "SizeBorder.h"
#include "WidgetLayoutLibrary.h"
int32 roundToNearestOdd1(float a_in)
{
return 2 * FMath::FloorToInt((a_in / 2) + 0.5f) - 2;
}
int32 roundToNearestEven1(float a_in)
{
return 2 * FMath::FloorToInt(a_in / 2);
}
void USkillTreeWidget::NativeOnSkillTreeChanged(ESkillTreeChangeEvent event, UBaseSkillObject* relevantObject)
{
// Recalculate dominantDamageType
int32_t categoryCounters[] = { 0,0,0 };
int32_t largest = 0;
for(USkillWidget* skill : m_skillWidgets)
{
if(skill->selectedEffect >= 0 && skill->skillAsset)
{
if(skill->selectedEffect >= skill->skillAsset->abilityEffects.Num())
{
GERROR("CORRUPT SKILL TREE");
continue;
}
UAbilityInfo* info = skill->skillAsset->abilityEffects[skill->selectedEffect];
int32_t& myCounter = categoryCounters[(size_t)info->abilityCategory];
myCounter++;
if(myCounter >= largest)
{
largest = myCounter;
dominantAbilityCategory = info->abilityCategory;
}
}
}
onSkillTreeChanged.Broadcast(event, relevantObject);
}
USkillTreeWidget::USkillTreeWidget(const FObjectInitializer& init)
: Super( init )
{
m_interactive = true;
m_shown = false;
m_lastFocusedWidget = nullptr;
isValid = false;
placedSkillHexMap = new FHexMap();
for (int32 x = 0; x < 13; x++)
{
placedSkillHexMap->rawdata[x] = 0;
}
}
void USkillTreeWidget::NativeConstruct()
{
Super::NativeConstruct();
UpdateVisibility();
// Get the tooltip widget
m_toolTipWidget = Cast<UToolTipWidget>(WidgetTree->FindWidget("ToolTipSWidget"));
m_sizeBorder = Cast<USizeBorder>(WidgetTree->FindWidget("SizeBorder"));
if ( !skillTreeAsset ) return;
TArray<UWidget*> widgets;
WidgetTree->GetAllWidgets( widgets );
UScrollBox* scrollBox = NULL;
m_skillTreeCanvas = WidgetTree->FindWidget<UCanvasPanel>("Main_SkillTree");
//m_focusBorder = WidgetTree->FindWidget<UBorder>("FocusBorder");
m_inputCapture = WidgetTree->FindWidget<UBorder>("InputCapture");
m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
for ( int32 i = 0; i < widgets.Num(); i++ )
{
UHexagonTile* tmpHex = Cast<UHexagonTile>( widgets[i] );
UScrollBox* tmpScrollBox = Cast<UScrollBox>( widgets[i] );
//USkillWidget* tmpSkill = Cast<USkillWidget>( widgets[i] );
if ( tmpScrollBox ) scrollBox = tmpScrollBox;
//if ( tmpSkill ) m_selectedSkill = tmpSkill;
if ( !tmpHex ) continue;
tiles.Push( tmpHex );
bool test = skillTreeAsset->hexMap.Get( tmpHex->x, tmpHex->y );
if ( test ) tmpHex->SetVisibility( ESlateVisibility::Hidden );
}
if ( m_selectedSkill )
{
m_selectedSkill->parent = this;
m_selectedSkill->SetDragable( true );
}
else
{
YWARNING( "No Selected Skill found!" );
}
}
void USkillTreeWidget::NativeDestruct()
{
delete placedSkillHexMap;
Super::NativeDestruct();
}
void USkillTreeWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime)
{
Super::NativeTick(MyGeometry, DeltaTime);
//if ((m_focusBorder && m_focusBorder->IsHovered()) || draggingWidget)
//{
// //m_inputCapture->SetVisibility(ESlateVisibility::Visible);
//}
//else
//{
// //m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
//}
if (m_toolTipWidget)
{
m_toolTipWidget->showTooltip = false;
FVector2D mousePos;
UWidgetLayoutLibrary::GetMousePositionScaledByDPI(GetOwningPlayer(), mousePos.X, mousePos.Y);
FIntPoint mouseGridPos = GetGridIndex(mousePos);
USkillWidget** sw = tileMap.Find(mouseGridPos);
USkillWidget* skill = sw ? *sw : nullptr;
if (skill)
{
// Reset hovered state
UAbilityInfo* info = skill->GetSelectedEffectAbility();
if (info)
{
m_toolTipWidget->SetTitle(info->name);
m_toolTipWidget->SetText(info->description);
m_toolTipWidget->position = skill->tooltipAreaPosition;
m_toolTipWidget->size = skill->tooltipAreaSize;
m_toolTipWidget->showTooltip = true;
}
skill->hovered = true;
}
if (m_lastFocusedWidget && m_lastFocusedWidget != skill)
{
m_lastFocusedWidget->hovered = false;
}
m_lastFocusedWidget = skill;
}
}
FReply USkillTreeWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
if(!IsInteractive())
return FReply::Handled();
//GWPRINT(L"Skill tree up");
if(draggingWidget && !draggingWidget->IsUsingController())
{
draggingWidget->StopDragging();
return FReply::Handled();
}
return FReply::Handled();
}
FReply USkillTreeWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
if(!IsInteractive())
return FReply::Handled();
//GWPRINT(L"Skill tree down");
FKey button = InMouseEvent.GetEffectingButton();
if(button == EKeys::LeftMouseButton)
{
if(draggingWidget && !draggingWidget->IsUsingController())
{
draggingWidget->StopDragging();
return FReply::Handled();
}
else
{
if(m_lastFocusedWidget)
{
if(m_lastFocusedWidget->hovered)
m_lastFocusedWidget->StartDragging();
}
}
}
else if(button == EKeys::RightMouseButton)
{
if(!draggingWidget && m_lastFocusedWidget)
{
if(m_lastFocusedWidget->hovered)
{
m_lastFocusedWidget->OnRequireSkillEffect();
}
}
}
return FReply::Handled();
}
FReply USkillTreeWidget::NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
//GWPRINT(L"USkillTreeWidget MouseWheel " + InMouseEvent.GetWheelDelta());
if (draggingWidget)
{
float add = 60.0f * FMath::FloorToFloat(InMouseEvent.GetWheelDelta());
draggingWidget->SetSkillRotation(add + draggingWidget->GetSkillRotation());
}
return FReply::Unhandled();
}
void USkillTreeWidget::SetIsInteractive(bool interactive)
{
m_interactive = interactive;
UpdateVisibility();
}
void USkillTreeWidget::UpdateVisibility()
{
if (m_shown)
{
SetVisibility(ESlateVisibility::SelfHitTestInvisible);
}
else
{
SetVisibility(ESlateVisibility::Hidden);
}
UImage* validImage = WidgetTree->FindWidget<UImage>("Validation_Image");
if (m_interactive)
{
validImage->SetVisibility(ESlateVisibility::Visible);
}
else
{
validImage->SetVisibility(ESlateVisibility::Hidden);
}
}
void WalkTree(TMap<USkillWidget*, TArray<USkillWidget*>>* a_map, TArray<USkillWidget*>* a_checked, TArray<USkillWidget*> a_nodes)
{
for (int32 i = 0; i < a_nodes.Num(); i++)
{
if (a_checked->Contains(a_nodes[i])) continue;
a_checked->Push(a_nodes[i]);
TArray<USkillWidget*>* connected = a_map->Find(a_nodes[i]);
if (!connected)
{
YERROR("Could not find the connected skills.");
continue;
}
WalkTree(a_map, a_checked, *connected);
}
}
bool USkillTreeWidget::ValidateSkillTree()
{
bool rootResult = false;
bool connectResult = true;
if (m_skillWidgets.Num() == 0) return true;
TMap<USkillWidget*, TArray<USkillWidget*>> connectedSkillsMap;
USkillWidget* rootSkill = NULL;
for (int32 i = 0; i < m_skillWidgets.Num(); i++)
{
TArray<FIntPoint> points = m_skillWidgets[i]->GetPoints();
if (!rootResult)
{
bool rootTest = m_skillWidgets[i]->ContainsRoot(points);
if (rootTest)
{
rootResult = true;
rootSkill = m_skillWidgets[i];
}
}
TArray<USkillWidget*> connectedSkills = m_skillWidgets[i]->ConnectedSkills(points);
connectedSkillsMap.Add(m_skillWidgets[i], connectedSkills);
if ( connectedSkills.Num() == 0 )
{
connectResult = false;
break;
}
}
if (connectResult && rootResult)
{
if (!rootSkill)
{
YERROR("Root Skill is NULL!");
return false;
}
TArray<USkillWidget*> checkedSkills;
checkedSkills.Push(rootSkill);
TArray<USkillWidget*>* connected = connectedSkillsMap.Find(rootSkill);
if (!connected)
{
YPRINT("Root Skill is not attached to any skills!");
return false;
}
WalkTree(&connectedSkillsMap, &checkedSkills, *connected);
if (m_skillWidgets.Num() != checkedSkills.Num())
connectResult = false;
else
{
for (int32 i = 0; i < m_skillWidgets.Num(); i++)
{
if (!checkedSkills.Contains(m_skillWidgets[i]))
{
connectResult = false;
}
}
}
}
if (m_skillWidgets.Num() == 1 && rootResult)
isValid = true;
else
isValid = (rootResult && connectResult);
if (isValid)
{
UpdateLevel(10.0f);
}
else
{
UpdateLevel(0.0f);
}
return isValid;
}
FSkillTreeState USkillTreeWidget::GetState()
{
FSkillTreeState state;
for(int32 i = 0; i < m_skillWidgets.Num(); i++)
{
FSkillTreeStateObject obj;
USkillWidget* skill = m_skillWidgets[i];
obj.gridIndex = skill->GetGridIndex() /*- FVector2D(1.5,4) * m_skillTreeCanvas->RenderTransform.Scale.X*/;
obj.placedPoints = skill->placedPoints;
obj.skillObject = skill->skillAsset->GetClass();
obj.rotation = skill->GetSkillRotation();
obj.selectedEffect = skill->selectedEffect;
state.objects.Add(obj);
//GWPRINT(L"Saving gridIndex " + obj.gridIndex);
//for(int32 i = 0; i < obj.placedPoints.Num(); i++)
//{
// GWPRINT(L"Saving Placed point " + i + L" " + obj.placedPoints[i].X + L", " + obj.placedPoints[i].Y);
//}
}
return state;
}
TArray<class UBaseSkillObject*> USkillTreeWidget::GetApearanceState()
{
return m_skillClasses;
}
void USkillTreeWidget::Clear()
{
// Remove all placed hexes
auto arrayCopy = m_skillWidgets;
for(int32 i = 0; i < arrayCopy.Num(); i++)
{
arrayCopy[i]->RemoveFromParent();
RemoveSkill(arrayCopy[i], arrayCopy[i]->placedPoints);
}
m_skillWidgets.SetNum(0);
m_skillClasses.SetNum(0);
tileMap.Reset();
// Clear Hex map
for(int32 x = 0; x < 13; x++)
{
placedSkillHexMap->rawdata[x] = 0;
}
isValid = true;
// Trigger Callbacks
NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Cleared, nullptr);
}
void USkillTreeWidget::BuildFromState(const FSkillTreeState& state)
{
Clear();
for(int32 i = 0; i < state.objects.Num(); i++)
{
const FSkillTreeStateObject& obj = state.objects[i];
UBaseSkillObject* baseSkill = obj.skillObject->GetDefaultObject<UBaseSkillObject>();
if(!baseSkill)
continue;
//GWPRINT(L"gridIndex " + obj.gridIndex);
//for(int32 i = 0; i < obj.placedPoints.Num(); i++)
//{
// GWPRINT(L"Placed point " + i + L" " + obj.placedPoints[i].X + L", " + obj.placedPoints[i].Y);
//}
m_selectedSkill = CreateWidget<USkillWidget>(GetWorld(), WidgetTemplate);
m_selectedSkill->skillAsset = baseSkill;
m_selectedSkill->parent = this;
if(m_skillTreeCanvas)
m_selectedSkill->SetRenderScale(m_skillTreeCanvas->RenderTransform.Scale);
m_selectedSkill->SetPlaced();
if(m_skillTreeCanvas)
m_skillTreeCanvas->GetParent()->AddChild(m_selectedSkill);
UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(m_selectedSkill->Slot);
if(slot)
{
slot->SetAutoSize(true);
slot->SetAlignment(FVector2D(-0.5f, -0.5f));
}
m_selectedSkill->SetSelectedEffect(obj.selectedEffect);
m_selectedSkill->placedPoints = obj.placedPoints;
m_selectedSkill->SetSkillRotation(obj.rotation);
m_selectedSkill->PlaceOnGridIndex(obj.gridIndex);
AddSkill(m_selectedSkill, obj.placedPoints);
}
}
void USkillTreeWidget::RemoveSkill(UBaseSkillObject* skillObject)
{
int32 skillID = m_skillClasses.Find(skillObject);
if (skillID == INDEX_NONE) return;
CancelSkill(m_skillWidgets[skillID]);
m_skillWidgets[skillID]->Remove();
}
bool USkillTreeWidget::IsUsingSkill(UBaseSkillObject* skillObject) const
{
return m_skillClasses.Contains(skillObject);
}
USkillWidget* USkillTreeWidget::GetUsedSkill(class UBaseSkillObject* skillObject)
{
for (int32 i = 0; i < m_skillWidgets.Num(); i++)
{
if (m_skillWidgets[i]->skillAsset == skillObject)
return m_skillWidgets[i];
}
return nullptr;
}
void USkillTreeWidget::Save(int32 saveSlot)
{
UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
UCharacterSettings* settings = inst->GetCharacterSettings();
if (saveSlot < 0 || saveSlot >= settings->characterSaves.Num())
{
JERROR("Invalid save slot");
return;
}
settings->characterSaves[saveSlot].skillTreeState = GetState();
inst->SaveSettings();
}
void USkillTreeWidget::Load(int32 saveSlot)
{
UDefaultGameInstance* inst = Cast<UDefaultGameInstance>(GetGameInstance());
UCharacterSettings* settings = inst->GetCharacterSettings();
if (saveSlot < 0 || saveSlot >= settings->characterSaves.Num())
{
JERROR("Invalid save slot");
return;
}
BuildFromState(settings->characterSaves[saveSlot].skillTreeState);
ValidateSkillTree();
}
FIntPoint USkillTreeWidget::GetGridIndex(FVector2D in) const
{
FVector2D result;
UCanvasPanelSlot* tmpSlot = UWidgetLayoutLibrary::SlotAsCanvasSlot(m_skillTreeCanvas);
float viewportScale = UWidgetLayoutLibrary::GetViewportScale(const_cast<USkillTreeWidget*>(this));
FVector2D topLeft;
float scale = 32.0f * (m_skillTreeCanvas->RenderTransform.Scale.X / 2);
float XScale = (scale * 1.73205f);
float YScale = scale;
if (tmpSlot)
{
FVector2D screenTopRight = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY().X * tmpSlot->GetAnchors().Minimum.X, GEngine->GameViewport->Viewport->GetSizeXY().Y * tmpSlot->GetAnchors().Minimum.Y);
FVector2D offset = tmpSlot->GetPosition() * viewportScale;
FVector2D widgetTopLeft = ((tmpSlot->GetSize() * m_skillTreeCanvas->RenderTransform.Scale.X * viewportScale) / 2);
topLeft = screenTopRight + offset - widgetTopLeft;
topLeft /= viewportScale;
//topLeft.X -= 16.0f;
}
float XPreFloor = (in.X - topLeft.X) / XScale;
float YPreFloor = (in.Y - topLeft.Y) / YScale;
int32 XIndex = FMath::FloorToInt(XPreFloor);
int32 YIndex = FMath::FloorToInt(YPreFloor);
/*FVector2D magicOffset = FVector2D(1.5, 4) * m_skillTreeCanvas->RenderTransform.Scale.X;*/
bool odd = (XIndex % 2) == 1;
if (odd)
YIndex = roundToNearestOdd1(YIndex);
else
YIndex = roundToNearestEven1(YIndex);
return FIntPoint(XIndex, (YIndex) / 2.0f);
}
void USkillTreeWidget::StartDragging(USkillWidget* widget)
{
check(draggingWidget == nullptr);
draggingWidget = widget;
m_inputCapture->SetVisibility(ESlateVisibility::Visible);
}
void USkillTreeWidget::StopDragging(USkillWidget* widget)
{
if(draggingWidget != widget)
{
GERROR("Calling StopDragging twice");
}
draggingWidget = nullptr;
m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
}
void USkillTreeWidget::UpdateTextInfo_Implementation()
{
}
void USkillTreeWidget::AddSkill(USkillWidget* a_skill, const TArray<FIntPoint>& a_points)
{
for ( int32 i = 0; i < a_points.Num(); i++ )
{
placedSkillHexMap->Set( a_points[i].X, a_points[i].Y, true );
tileMap.Add(a_points[i], a_skill);
}
UWorld* const world = GetWorld();
if ( !world )
{
YWARNING( "Couldn't get the World." );
return;
}
APlayerControllerBase* const controller = Cast<APlayerControllerBase>( GetOwningPlayer() );
if ( !controller )
{
YWARNING( "Couldn't get the Player." );
return;
}
controller->OnLearnSkill( a_skill->skillAsset );
m_skillWidgets.Add(a_skill);
m_skillClasses.Add(a_skill->skillAsset);
// Trigger Callbacks
NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Added, a_skill->skillAsset);
ValidateSkillTree();
}
void USkillTreeWidget::RemoveSkill( USkillWidget* a_skill, const TArray<FIntPoint>& a_points )
{
m_skillClasses.Remove(a_skill->skillAsset);
m_skillWidgets.Remove(a_skill);
for ( int32 i = 0; i < a_points.Num(); i++ )
{
placedSkillHexMap->Set( a_points[i].X, a_points[i].Y, false );
tileMap.Remove(a_points[i]);
}
UWorld* const world = GetWorld();
if ( !world )
{
YWARNING( "Couldn't get the World." );
return;
}
APlayerControllerBase* const controller = Cast<APlayerControllerBase>( GetOwningPlayer() );
if ( !controller )
{
YWARNING( "Couldn't get the Player." );
return;
}
controller->OnUnlearnSkill( a_skill->skillAsset );
// Trigger Callbacks
NativeOnSkillTreeChanged(ESkillTreeChangeEvent::Removed, a_skill->skillAsset);
}
USkillWidget* USkillTreeWidget::NewSkill( USkillWidget* a_skill, bool controller )
{
return NewSkillFromAsset(a_skill->skillAsset, controller);
}
class USkillWidget* USkillTreeWidget::NewSkillFromAsset(UBaseSkillObject* skillAsset, bool controller /*= false*/)
{
if(draggingWidget)
return NULL;
//a_skill->Disable();
m_selectedSkill = CreateWidget<USkillWidget>(GetWorld(), WidgetTemplate);
m_selectedSkill->skillAsset = skillAsset;
m_selectedSkill->parent = this;
m_selectedSkill->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
m_selectedSkill->SetDragable(true, controller);
m_selectedSkill->SetRenderScale(m_skillTreeCanvas->RenderTransform.Scale);
m_skillTreeCanvas->GetParent()->AddChild(m_selectedSkill);
m_selectedSkill->SetUserFocus(GetOwningPlayer());
UCanvasPanelSlot* slot = Cast<UCanvasPanelSlot>(m_selectedSkill->Slot);
if(slot)
{
slot->SetAutoSize(true);
slot->SetAlignment(FVector2D(-0.5f, -0.5f));
}
if(controller)
{
// Set initial position
m_selectedSkill->MoveSkillAbsolute(lastGridIndex);
}
return m_selectedSkill;
}
void USkillTreeWidget::CancelSkill(USkillWidget* a_skill)
{
a_skill->RemoveFromParent();
ValidateSkillTree();
}
void USkillTreeWidget::Show()
{
m_shown = true;
UpdateVisibility();
}
void USkillTreeWidget::Hide()
{
m_shown = false;
UpdateVisibility();
}
void USkillTreeWidget::UpdateLevel(float level)
{
for (int32 i = 0; i < m_skillWidgets.Num(); i++)
{
m_skillWidgets[i]->UpdateLevel(level);
}
if (IsInteractable())
{
UpdateTextInfo();
}
}
void USkillTreeWidget::UpdateInfo(float skilltreeHeight, float skilltreeOffset)
{
for (int32 i = 0; i < m_skillWidgets.Num(); i++)
{
m_skillWidgets[i]->UpdateInfo(skilltreeHeight, skilltreeOffset);
}
}
/*
void USkillTreeWidget::NativeTick(const FGeometry& MyGeometry, float DeltaTime)
{
FVector2D minPos;
FVector2D maxPos;
FVector2D viewportPos;
USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, MyGeometry.GetLocalSize(), maxPos, viewportPos);
USlateBlueprintLibrary::LocalToViewport(this, MyGeometry, FVector2D(0,0), minPos, viewportPos);
FVector2D viewportSize = maxPos - minPos;
for (int32 i = 0; i < m_skillWidgets.Num(); i++)
{
m_skillWidgets[i]->UpdateInfo(viewportSize.Y, minPos.Y);
}
}
*/