// 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(WidgetTree->FindWidget("ToolTipSWidget")); m_sizeBorder = Cast(WidgetTree->FindWidget("SizeBorder")); if ( !skillTreeAsset ) return; TArray widgets; WidgetTree->GetAllWidgets( widgets ); UScrollBox* scrollBox = NULL; m_skillTreeCanvas = WidgetTree->FindWidget("Main_SkillTree"); //m_focusBorder = WidgetTree->FindWidget("FocusBorder"); m_inputCapture = WidgetTree->FindWidget("InputCapture"); m_inputCapture->SetVisibility(ESlateVisibility::SelfHitTestInvisible); for ( int32 i = 0; i < widgets.Num(); i++ ) { UHexagonTile* tmpHex = Cast( widgets[i] ); UScrollBox* tmpScrollBox = Cast( widgets[i] ); //USkillWidget* tmpSkill = Cast( 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("Validation_Image"); if (m_interactive) { validImage->SetVisibility(ESlateVisibility::Visible); } else { validImage->SetVisibility(ESlateVisibility::Hidden); } } void WalkTree(TMap>* a_map, TArray* a_checked, TArray 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* 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> connectedSkillsMap; USkillWidget* rootSkill = NULL; for (int32 i = 0; i < m_skillWidgets.Num(); i++) { TArray points = m_skillWidgets[i]->GetPoints(); if (!rootResult) { bool rootTest = m_skillWidgets[i]->ContainsRoot(points); if (rootTest) { rootResult = true; rootSkill = m_skillWidgets[i]; } } TArray 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 checkedSkills; checkedSkills.Push(rootSkill); TArray* 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 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(); 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(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(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(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(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(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& 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( 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& 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( 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(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(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); } } */