From bf15db00663774d8474c4418180cefbb30053552 Mon Sep 17 00:00:00 2001 From: Daniel Poveda Date: Sun, 12 Oct 2025 15:35:17 +0200 Subject: [PATCH] fix: selection --- .../UTAD_UI/SkillTree/SkillTreeComponent.cpp | 804 +++++++++--------- Source/UTAD_UI/SkillTree/SkillTreeTypes.h | 135 ++- 2 files changed, 475 insertions(+), 464 deletions(-) diff --git a/Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp b/Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp index c656a7e..d669b9f 100644 --- a/Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp +++ b/Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp @@ -3,555 +3,569 @@ USkillTreeComponent::USkillTreeComponent() { - PrimaryComponentTick.bCanEverTick = false; + PrimaryComponentTick.bCanEverTick = false; - AvailableSkillPoints = 5; // Start with 5 skill points for testing - TotalSkillPointsEarned = 5; - CurrentSelectionCost = 0; + AvailableSkillPoints = 5; + TotalSkillPointsEarned = 5; + CurrentSelectionCost = 0; } void USkillTreeComponent::BeginPlay() { - Super::BeginPlay(); - InitializeSkillTree(); + Super::BeginPlay(); + InitializeSkillTree(); } void USkillTreeComponent::InitializeSkillTree() { - AllSkills.Empty(); + AllSkills.Empty(); - if (!SkillDataTable) - { - UE_LOG(LogTemp, Warning, TEXT("SkillTreeComponent: No skill data table assigned!")); - return; - } + if (!SkillDataTable) + { + UE_LOG(LogTemp, Warning, + TEXT("SkillTreeComponent: No skill data table assigned!")); + return; + } - // Load all skills from the data table using RowNames as IDs - TArray RowNames = SkillDataTable->GetRowNames(); + // Load all skills from the data table using RowNames as IDs + TArray RowNames = SkillDataTable->GetRowNames(); + for (const FName& RowName : RowNames) + { + FSkillNodeData* Row = SkillDataTable->FindRow( + RowName, TEXT("SkillTreeComponent")); + if (Row) + { + FSkillNodeRuntime RuntimeNode; + RuntimeNode.NodeData = *Row; + RuntimeNode.CurrentState = ESkillNodeState::Locked; - for (const FName& RowName : RowNames) - { - FSkillNodeData* Row = SkillDataTable->FindRow(RowName, TEXT("SkillTreeComponent")); - if (Row) - { - FSkillNodeRuntime RuntimeNode; - RuntimeNode.NodeData = *Row; - RuntimeNode.CurrentState = ESkillNodeState::Locked; - RuntimeNode.bIsSelected = false; + // Use RowName as the skill ID + AllSkills.Add(RowName, RuntimeNode); + } + } - // Use RowName as the skill ID - AllSkills.Add(RowName, RuntimeNode); - } - } + UpdateSkillStates(); - UpdateSkillStates(); - - UE_LOG(LogTemp, Log, TEXT("SkillTreeComponent: Initialized with %d skills"), AllSkills.Num()); + UE_LOG(LogTemp, Log, TEXT("SkillTreeComponent: Initialized with %d skills"), + AllSkills.Num()); } void USkillTreeComponent::AddSkillPoints(int32 Amount) { - if (Amount > 0) - { - AvailableSkillPoints += Amount; - TotalSkillPointsEarned += Amount; - OnSkillPointsChanged.Broadcast(AvailableSkillPoints); - UpdateSkillStates(); - } + if (Amount > 0) + { + AvailableSkillPoints += Amount; + TotalSkillPointsEarned += Amount; + OnSkillPointsChanged.Broadcast(AvailableSkillPoints); + UpdateSkillStates(); + } } void USkillTreeComponent::RemoveSkillPoints(int32 Amount) { - if (Amount > 0) - { - AvailableSkillPoints = FMath::Max(0, AvailableSkillPoints - Amount); - OnSkillPointsChanged.Broadcast(AvailableSkillPoints); - UpdateSkillStates(); - } + if (Amount > 0) + { + AvailableSkillPoints = FMath::Max(0, AvailableSkillPoints - Amount); + OnSkillPointsChanged.Broadcast(AvailableSkillPoints); + UpdateSkillStates(); + } } bool USkillTreeComponent::SelectSkill(FName SkillID) { - FText ErrorMessage; + FText ErrorMessage; - // Check if skill exists - if (!AllSkills.Contains(SkillID)) - { - ErrorMessage = FText::FromString(TEXT("Skill does not exist")); - OnSelectionError.Broadcast(SkillID, ErrorMessage); - return false; - } + // Check if skill exists + if (!AllSkills.Contains(SkillID)) + { + ErrorMessage = FText::FromString(TEXT("Skill does not exist")); + OnSelectionError.Broadcast(SkillID, ErrorMessage); + return false; + } - FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - // Check if already purchased - if (SkillNode->CurrentState == ESkillNodeState::Purchased) - { - ErrorMessage = FText::FromString(TEXT("Skill already purchased")); - OnSelectionError.Broadcast(SkillID, ErrorMessage); - return false; - } + // Validate skill state + switch (SkillNode->CurrentState) + { + case ESkillNodeState::Purchased: + // Already purchased, cannot select + ErrorMessage = FText::FromString(TEXT("Skill already purchased")); + OnSelectionError.Broadcast(SkillID, ErrorMessage); + return false; - // Check if already selected - if (SkillNode->bIsSelected) - { - ErrorMessage = FText::FromString(TEXT("Skill already selected")); - OnSelectionError.Broadcast(SkillID, ErrorMessage); - return false; - } + case ESkillNodeState::Selected: + // Already selected, cannot select again + ErrorMessage = FText::FromString(TEXT("Skill already selected")); + OnSelectionError.Broadcast(SkillID, ErrorMessage); + return false; - // For locked skills, we allow selection but will validate on purchase - // This allows players to "plan" their build - if (SkillNode->CurrentState == ESkillNodeState::Locked) - { - // Check if prerequisites would be met with current selection - bool bWouldBeAvailable = true; - for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) - { - if (!IsSkillPurchased(PrereqID) && !IsSkillSelected(PrereqID)) - { - bWouldBeAvailable = false; - break; - } - } + case ESkillNodeState::Locked: + // For locked skills, we allow selection but validate prerequisites + // This allows players to "plan" their build + bool bWouldBeAvailable = true; + for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) + { + if (!IsSkillPurchased(PrereqID) && !IsSkillSelected(PrereqID)) + { + bWouldBeAvailable = false; + break; + } + } + if (!bWouldBeAvailable) + { + ErrorMessage = FText::FromString(TEXT( + "Prerequisites not selected. Select prerequisite skills first")); + OnSelectionError.Broadcast(SkillID, ErrorMessage); + return false; + } + break; - if (!bWouldBeAvailable) - { - ErrorMessage = FText::FromString(TEXT("Prerequisites not selected. Select prerequisite skills first.")); - OnSelectionError.Broadcast(SkillID, ErrorMessage); - return false; - } - } + case ESkillNodeState::Available: + // Available skills can be selected without additional checks + break; + } - // Add to selection - SkillNode->bIsSelected = true; - SelectedSkills.Add(SkillID); - UpdateSelectionCost(); + // Add to selection + SkillNode->CurrentState = ESkillNodeState::Selected; + SelectedSkills.Add(SkillID); + UpdateSelectionCost(); - OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); - OnSkillStateChanged.Broadcast(SkillID); + OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); + OnSkillStateChanged.Broadcast(SkillID); - return true; + return true; } bool USkillTreeComponent::DeselectSkill(FName SkillID) { - if (!AllSkills.Contains(SkillID)) - { - return false; - } + if (!AllSkills.Contains(SkillID)) + return false; - FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (!SkillNode->bIsSelected) - { - return false; - } + if (SkillNode->CurrentState != ESkillNodeState::Selected) + return false; - // Check if any selected skills depend on this one - for (const FName& SelectedID : SelectedSkills) - { - if (SelectedID != SkillID) - { - FSkillNodeRuntime* SelectedNode = AllSkills.Find(SelectedID); - if (SelectedNode && SelectedNode->NodeData.Prerequisites.Contains(SkillID)) - { - // Can't deselect if other selected skills depend on it - FText ErrorMessage = FText::FromString(TEXT("Other selected skills depend on this one")); - OnSelectionError.Broadcast(SkillID, ErrorMessage); - return false; - } - } - } + // Check if any selected skills depend on this one + for (const FName& SelectedID : SelectedSkills) + { + if (SelectedID != SkillID) + { + FSkillNodeRuntime* SelectedNode = AllSkills.Find(SelectedID); + if (SelectedNode + && SelectedNode->NodeData.Prerequisites.Contains(SkillID)) + { + // Can't deselect if other selected skills depend on it + FText ErrorMessage = FText::FromString( + TEXT("Other selected skills depend on this one")); + OnSelectionError.Broadcast(SkillID, ErrorMessage); + return false; + } + } + } - // Remove from selection - SkillNode->bIsSelected = false; - SelectedSkills.Remove(SkillID); - UpdateSelectionCost(); + // Remove from selection and update state + SelectedSkills.Remove(SkillID); + UpdateSelectionCost(); - OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); - OnSkillStateChanged.Broadcast(SkillID); + // Update state to either Available or Locked based on prerequisites + if (ArePrerequisitesMet(SkillNode->NodeData)) + SkillNode->CurrentState = ESkillNodeState::Available; + else + SkillNode->CurrentState = ESkillNodeState::Locked; - return true; + OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); + OnSkillStateChanged.Broadcast(SkillID); + + return true; } void USkillTreeComponent::ClearSelection() { - for (const FName& SkillID : SelectedSkills) - { - if (FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - SkillNode->bIsSelected = false; - OnSkillStateChanged.Broadcast(SkillID); - } - } + for (const FName& SkillID : SelectedSkills) + { + if (FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + { + // Update state based on prerequisites + if (ArePrerequisitesMet(SkillNode->NodeData)) + SkillNode->CurrentState = ESkillNodeState::Available; + else + SkillNode->CurrentState = ESkillNodeState::Locked; + OnSkillStateChanged.Broadcast(SkillID); + } + } - SelectedSkills.Empty(); - UpdateSelectionCost(); - OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); + SelectedSkills.Empty(); + UpdateSelectionCost(); + OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); } bool USkillTreeComponent::ConfirmPurchase() { - // Validate we can afford it - if (!CanAffordSelection()) - { - FText ErrorMessage = FText::FromString(TEXT("Not enough skill points")); - OnSelectionError.Broadcast(NAME_None, ErrorMessage); - return false; - } + // Validate we can afford it + if (!CanAffordSelection()) + { + FText ErrorMessage = FText::FromString(TEXT("Not enough skill points")); + OnSelectionError.Broadcast(NAME_None, ErrorMessage); + return false; + } - // Sort selected skills by purchase order to ensure prerequisites are purchased first - TArray SortedSelection = SelectedSkills; - SortedSelection.Sort([this](const FName& A, const FName& B) { - FSkillNodeRuntime* NodeA = AllSkills.Find(A); - FSkillNodeRuntime* NodeB = AllSkills.Find(B); - if (NodeA && NodeB) - { - return NodeA->NodeData.PurchaseOrder < NodeB->NodeData.PurchaseOrder; - } - return false; - }); + // Sort selected skills by purchase order to ensure prerequisites are + // purchased first + TArray SortedSelection = SelectedSkills; + SortedSelection.Sort( + [this](const FName& A, const FName& B) + { + FSkillNodeRuntime* NodeA = AllSkills.Find(A); + FSkillNodeRuntime* NodeB = AllSkills.Find(B); + if (NodeA && NodeB) + { + return NodeA->NodeData.PurchaseOrder + < NodeB->NodeData.PurchaseOrder; + } + return false; + }); - // Validate all skills can be purchased in order - for (const FName& SkillID : SortedSelection) - { - FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (!SkillNode) - { - continue; - } + // Validate all skills can be purchased in order + for (const FName& SkillID : SortedSelection) + { + FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + if (!SkillNode) + continue; - // Check prerequisites (considering already processed skills in this batch) - bool bPrereqsMet = true; - for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) - { - if (!IsSkillPurchased(PrereqID)) - { - // Check if it's in our current batch and would be purchased before this - int32 PrereqIndex = SortedSelection.IndexOfByKey(PrereqID); - int32 CurrentIndex = SortedSelection.IndexOfByKey(SkillID); + // Check prerequisites (considering already processed skills in this + // batch) + bool bPrereqsMet = true; + for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) + { + if (!IsSkillPurchased(PrereqID)) + { + // Check if it's in our current batch and would be purchased + // before this + int32 PrereqIndex = SortedSelection.IndexOfByKey(PrereqID); + int32 CurrentIndex = SortedSelection.IndexOfByKey(SkillID); - if (PrereqIndex == INDEX_NONE || PrereqIndex >= CurrentIndex) - { - bPrereqsMet = false; - break; - } - } - } + if (PrereqIndex == INDEX_NONE || PrereqIndex >= CurrentIndex) + { + bPrereqsMet = false; + break; + } + } + } - if (!bPrereqsMet) - { - FText ErrorMessage = FText::Format( - FText::FromString(TEXT("Cannot purchase {0}: prerequisites not met")), - SkillNode->NodeData.DisplayName - ); - OnSelectionError.Broadcast(SkillID, ErrorMessage); - return false; - } - } + if (!bPrereqsMet) + { + FText ErrorMessage = FText::Format( + FText::FromString( + TEXT("Cannot purchase {0}: prerequisites not met")), + SkillNode->NodeData.DisplayName); + OnSelectionError.Broadcast(SkillID, ErrorMessage); + return false; + } + } - // Purchase all selected skills - for (const FName& SkillID : SortedSelection) - { - FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (SkillNode) - { - // Mark as purchased - SkillNode->CurrentState = ESkillNodeState::Purchased; - SkillNode->bIsSelected = false; - PurchasedSkills.Add(SkillID); + // Purchase all selected skills + for (const FName& SkillID : SortedSelection) + { + FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + if (SkillNode) + { + // Mark as purchased + SkillNode->CurrentState = ESkillNodeState::Purchased; + PurchasedSkills.Add(SkillID); - // Apply effects - ApplySkillEffects(SkillNode->NodeData); + // Apply effects + ApplySkillEffects(SkillNode->NodeData); - // Broadcast purchase event - OnSkillPurchased.Broadcast(SkillID); - OnSkillStateChanged.Broadcast(SkillID); - } - } + // Broadcast purchase event + OnSkillPurchased.Broadcast(SkillID); + OnSkillStateChanged.Broadcast(SkillID); + } + } - // Deduct skill points - RemoveSkillPoints(CurrentSelectionCost); + // Deduct skill points + RemoveSkillPoints(CurrentSelectionCost); - // Clear selection - SelectedSkills.Empty(); - UpdateSelectionCost(); - UpdateSkillStates(); + // Clear selection + SelectedSkills.Empty(); + UpdateSelectionCost(); + UpdateSkillStates(); - OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); + OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); - return true; + return true; } bool USkillTreeComponent::IsSkillPurchased(FName SkillID) const { - return PurchasedSkills.Contains(SkillID); + return PurchasedSkills.Contains(SkillID); } bool USkillTreeComponent::IsSkillSelected(FName SkillID) const { - return SelectedSkills.Contains(SkillID); + return SelectedSkills.Contains(SkillID); } bool USkillTreeComponent::IsSkillAvailable(FName SkillID) const { - const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (!SkillNode) - { - return false; - } + const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + if (!SkillNode) + return false; - return SkillNode->CurrentState == ESkillNodeState::Available; + return SkillNode->CurrentState == ESkillNodeState::Available; } -bool USkillTreeComponent::CanSelectSkill(FName SkillID, FText& OutErrorMessage) const +bool USkillTreeComponent::CanSelectSkill(FName SkillID, + FText& OutErrorMessage) const { - const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (!SkillNode) - { - OutErrorMessage = FText::FromString(TEXT("Skill does not exist")); - return false; - } + const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + if (!SkillNode) + { + OutErrorMessage = FText::FromString(TEXT("Skill does not exist")); + return false; + } - if (SkillNode->CurrentState == ESkillNodeState::Purchased) - { - OutErrorMessage = FText::FromString(TEXT("Skill already purchased")); - return false; - } + switch (SkillNode->CurrentState) + { + case ESkillNodeState::Purchased: + OutErrorMessage = FText::FromString(TEXT("Skill already purchased")); + return false; - if (SkillNode->bIsSelected) - { - OutErrorMessage = FText::FromString(TEXT("Skill already selected")); - return false; - } + case ESkillNodeState::Selected: + OutErrorMessage = FText::FromString(TEXT("Skill already selected")); + return false; - OutErrorMessage = FText::GetEmpty(); - return true; + case ESkillNodeState::Locked: + case ESkillNodeState::Available: + OutErrorMessage = FText::GetEmpty(); + return true; + + default: + OutErrorMessage = FText::FromString(TEXT("Unknown skill state")); + return false; + } } ESkillNodeState USkillTreeComponent::GetSkillState(FName SkillID) const { - const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (SkillNode) - { - return SkillNode->CurrentState; - } + const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + if (SkillNode) + return SkillNode->CurrentState; - return ESkillNodeState::Locked; + return ESkillNodeState::Locked; } -bool USkillTreeComponent::GetSkillNodeData(FName SkillID, FSkillNodeRuntime& OutNodeData) const +bool USkillTreeComponent::GetSkillNodeData(FName SkillID, + FSkillNodeRuntime& OutNodeData) const { - const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); - if (SkillNode) - { - OutNodeData = *SkillNode; - return true; - } + const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); + if (SkillNode) + { + OutNodeData = *SkillNode; + return true; + } - return false; + return false; } TArray USkillTreeComponent::GetAllSkillNodes() const { - TArray Result; - for (const auto& Pair : AllSkills) - { - Result.Add(Pair.Value); - } - return Result; + TArray Result; + for (const auto& Pair : AllSkills) + Result.Add(Pair.Value); + + return Result; } float USkillTreeComponent::GetTotalHealthBonus() const { - float Total = 0.0f; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - Total += SkillNode->NodeData.HealthBonus; - } - } - return Total; + float Total = 0.0f; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + Total += SkillNode->NodeData.HealthBonus; + } + + return Total; } float USkillTreeComponent::GetTotalDamageBonus() const { - float Total = 0.0f; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - Total += SkillNode->NodeData.DamageBonus; - } - } - return Total; + float Total = 0.0f; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + Total += SkillNode->NodeData.DamageBonus; + } + + return Total; } float USkillTreeComponent::GetTotalSpeedBonus() const { - float Total = 0.0f; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - Total += SkillNode->NodeData.SpeedBonus; - } - } - return Total; + float Total = 0.0f; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + Total += SkillNode->NodeData.SpeedBonus; + } + + return Total; } float USkillTreeComponent::GetTotalHealthBonusPercent() const { - float Total = 0.0f; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - Total += SkillNode->NodeData.HealthBonusPercent; - } - } - return Total; + float Total = 0.0f; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + Total += SkillNode->NodeData.HealthBonusPercent; + } + + return Total; } float USkillTreeComponent::GetTotalDamageBonusPercent() const { - float Total = 0.0f; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - Total += SkillNode->NodeData.DamageBonusPercent; - } - } - return Total; + float Total = 0.0f; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + Total += SkillNode->NodeData.DamageBonusPercent; + } + + return Total; } float USkillTreeComponent::GetTotalSpeedBonusPercent() const { - float Total = 0.0f; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - Total += SkillNode->NodeData.SpeedBonusPercent; - } - } - return Total; + float Total = 0.0f; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + Total += SkillNode->NodeData.SpeedBonusPercent; + } + + return Total; } void USkillTreeComponent::ResetAllSkills() { - // Refund all skill points - int32 RefundAmount = 0; - for (const FName& SkillID : PurchasedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - RefundAmount += SkillNode->NodeData.Cost; - RemoveSkillEffects(SkillNode->NodeData); - } - } + // Refund all skill points + int32 RefundAmount = 0; + for (const FName& SkillID : PurchasedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + { + RefundAmount += SkillNode->NodeData.Cost; + RemoveSkillEffects(SkillNode->NodeData); + } + } - // Clear purchases - PurchasedSkills.Empty(); + // Clear purchases + PurchasedSkills.Empty(); - // Reset all skill states - for (auto& Pair : AllSkills) - { - Pair.Value.CurrentState = ESkillNodeState::Locked; - Pair.Value.bIsSelected = false; - } + // Reset all skill states + for (auto& Pair : AllSkills) + Pair.Value.CurrentState = ESkillNodeState::Locked; - // Clear selection - SelectedSkills.Empty(); - CurrentSelectionCost = 0; + // Clear selection + SelectedSkills.Empty(); + CurrentSelectionCost = 0; - // Add refunded points - AddSkillPoints(RefundAmount); + // Add refunded points + AddSkillPoints(RefundAmount); - // Update states - UpdateSkillStates(); + // Update states + UpdateSkillStates(); - // Broadcast events - OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); + // Broadcast events + OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); } void USkillTreeComponent::UpdateSkillStates() { - for (auto& Pair : AllSkills) - { - FSkillNodeRuntime& SkillNode = Pair.Value; + for (auto& Pair : AllSkills) + { + FSkillNodeRuntime& SkillNode = Pair.Value; - // Skip if already purchased - if (SkillNode.CurrentState == ESkillNodeState::Purchased) - { - continue; - } + // Only update Locked and Available states based on prerequisites + // Purchased and Selected states should not be changed + switch (SkillNode.CurrentState) + { + case ESkillNodeState::Purchased: + case ESkillNodeState::Selected: + // Don't change these states + continue; - // Check if prerequisites are met - if (ArePrerequisitesMet(SkillNode.NodeData)) - { - SkillNode.CurrentState = SkillNode.bIsSelected ? - ESkillNodeState::Selected : ESkillNodeState::Available; - } - else - { - SkillNode.CurrentState = SkillNode.bIsSelected ? - ESkillNodeState::Selected : ESkillNodeState::Locked; - } - } + case ESkillNodeState::Locked: + case ESkillNodeState::Available: + // Update state based on prerequisites + if (ArePrerequisitesMet(SkillNode.NodeData)) + { + SkillNode.CurrentState = ESkillNodeState::Available; + } + else + { + SkillNode.CurrentState = ESkillNodeState::Locked; + } + break; + } + } } void USkillTreeComponent::UpdateSelectionCost() { - CurrentSelectionCost = 0; - for (const FName& SkillID : SelectedSkills) - { - if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) - { - CurrentSelectionCost += SkillNode->NodeData.Cost; - } - } + CurrentSelectionCost = 0; + for (const FName& SkillID : SelectedSkills) + { + if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) + { + CurrentSelectionCost += SkillNode->NodeData.Cost; + } + } } -bool USkillTreeComponent::ArePrerequisitesMet(const FSkillNodeData& SkillData) const +bool USkillTreeComponent::ArePrerequisitesMet( + const FSkillNodeData& SkillData) const { - for (const FName& PrereqID : SkillData.Prerequisites) - { - if (!IsSkillPurchased(PrereqID)) - { - return false; - } - } - return true; + for (const FName& PrereqID : SkillData.Prerequisites) + { + if (!IsSkillPurchased(PrereqID)) + { + return false; + } + } + return true; } bool USkillTreeComponent::HasChildrenPurchased(FName SkillID) const { - for (const auto& Pair : AllSkills) - { - // Pair.Key is the RowName (SkillID) - if (Pair.Value.NodeData.Prerequisites.Contains(SkillID) && - IsSkillPurchased(Pair.Key)) - { - return true; - } - } - return false; + for (const auto& Pair : AllSkills) + { + // Pair.Key is the RowName (SkillID) + if (Pair.Value.NodeData.Prerequisites.Contains(SkillID) + && IsSkillPurchased(Pair.Key)) + { + return true; + } + } + return false; } void USkillTreeComponent::ApplySkillEffects(const FSkillNodeData& SkillData) { - // This is where you would apply the actual skill effects to the character - // For now, we just log it - UE_LOG(LogTemp, Log, TEXT("Applied skill effects for: %s"), *SkillData.DisplayName.ToString()); + // This is where you would apply the actual skill effects to the character + // For now, we just log it + UE_LOG(LogTemp, Log, TEXT("Applied skill effects for: %s"), + *SkillData.DisplayName.ToString()); } void USkillTreeComponent::RemoveSkillEffects(const FSkillNodeData& SkillData) { - // This is where you would remove skill effects from the character - // For now, we just log it - UE_LOG(LogTemp, Log, TEXT("Removed skill effects for: %s"), *SkillData.DisplayName.ToString()); -} \ No newline at end of file + // This is where you would remove skill effects from the character + // For now, we just log it + UE_LOG(LogTemp, Log, TEXT("Removed skill effects for: %s"), + *SkillData.DisplayName.ToString()); +} diff --git a/Source/UTAD_UI/SkillTree/SkillTreeTypes.h b/Source/UTAD_UI/SkillTree/SkillTreeTypes.h index 7284986..79f2454 100644 --- a/Source/UTAD_UI/SkillTree/SkillTreeTypes.h +++ b/Source/UTAD_UI/SkillTree/SkillTreeTypes.h @@ -8,97 +8,94 @@ UENUM(BlueprintType) enum class ESkillNodeState : uint8 { - Locked UMETA(DisplayName = "Locked"), // Can't be purchased yet (missing prerequisites) - Available UMETA(DisplayName = "Available"), // Can be purchased - Selected UMETA(DisplayName = "Selected"), // Currently selected for purchase - Purchased UMETA(DisplayName = "Purchased") // Already owned + // Can't be purchased yet (missing prerequisites) + Locked UMETA(DisplayName = "Locked"), + + // Can be purchased + Available UMETA(DisplayName = "Available"), + + // Currently selected for purchase + Selected UMETA(DisplayName = "Selected"), + + // Already owned + Purchased UMETA(DisplayName = "Purchased") }; // Struct for individual skill node data -// Note: RowName in the DataTable serves as the unique SkillID USTRUCT(BlueprintType) struct FSkillNodeData : public FTableRowBase { - GENERATED_BODY() + GENERATED_BODY() - // Display name for UI - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") - FText DisplayName; + // RowName will be used as SkillID - // Description of what the skill does - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") - FText Description; + // Display name for UI + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") + FText DisplayName; - // Icon for the skill - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") - class UTexture2D* Icon; + // Description of what the skill does + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") + FText Description; - // Skill point cost - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") - int32 Cost; + // Icon for the skill + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") + class UTexture2D* Icon; - // Purchase order priority (lower = buy first, used when confirming multiple skills) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") - int32 PurchaseOrder; + // Skill point cost + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") + int32 Cost; - // Prerequisites - must have these skills first (RowNames of required skills) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") - TArray Prerequisites; + // Purchase order priority + // (lower = buy first, used when confirming multiple skills) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") + int32 PurchaseOrder; - // Effects this skill provides (actual stat bonuses) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") - float HealthBonus; + // Prerequisites - must have these skills first + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") + TArray Prerequisites; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") - float DamageBonus; + // Effects this skill provides + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float HealthBonus; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float DamageBonus; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float SpeedBonus; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") - float SpeedBonus; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float HealthBonusPercent; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float DamageBonusPercent; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") + float SpeedBonusPercent; - // Percentage bonuses (multiplicative) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") - float HealthBonusPercent; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") - float DamageBonusPercent; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") - float SpeedBonusPercent; - - FSkillNodeData() - { - DisplayName = FText::GetEmpty(); - Description = FText::GetEmpty(); - Icon = nullptr; - Cost = 1; - PurchaseOrder = 0; - HealthBonus = 0.0f; - DamageBonus = 0.0f; - SpeedBonus = 0.0f; - HealthBonusPercent = 0.0f; - DamageBonusPercent = 0.0f; - SpeedBonusPercent = 0.0f; - } + FSkillNodeData() + { + DisplayName = FText::GetEmpty(); + Description = FText::GetEmpty(); + Icon = nullptr; + Cost = 1; + PurchaseOrder = 0; + HealthBonus = 0.0f; + DamageBonus = 0.0f; + SpeedBonus = 0.0f; + HealthBonusPercent = 0.0f; + DamageBonusPercent = 0.0f; + SpeedBonusPercent = 0.0f; + } }; // Runtime struct for tracking skill state USTRUCT(BlueprintType) struct FSkillNodeRuntime { - GENERATED_BODY() + GENERATED_BODY() - UPROPERTY(BlueprintReadOnly, Category = "Skill") - FSkillNodeData NodeData; + UPROPERTY(BlueprintReadOnly, Category = "Skill") + FSkillNodeData NodeData; - UPROPERTY(BlueprintReadOnly, Category = "Skill") - ESkillNodeState CurrentState; + UPROPERTY(BlueprintReadOnly, Category = "Skill") + ESkillNodeState CurrentState; - UPROPERTY(BlueprintReadOnly, Category = "Skill") - bool bIsSelected; - - FSkillNodeRuntime() - { - CurrentState = ESkillNodeState::Locked; - bIsSelected = false; - } -}; \ No newline at end of file + FSkillNodeRuntime() { CurrentState = ESkillNodeState::Locked; } +};