#include "SkillTreeComponent.h" #include "Engine/DataTable.h" USkillTreeComponent::USkillTreeComponent() { PrimaryComponentTick.bCanEverTick = false; AvailableSkillPoints = 5; // Start with 5 skill points for testing TotalSkillPointsEarned = 5; CurrentSelectionCost = 0; } void USkillTreeComponent::BeginPlay() { Super::BeginPlay(); InitializeSkillTree(); } void USkillTreeComponent::InitializeSkillTree() { AllSkills.Empty(); 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(); 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); } } UpdateSkillStates(); 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(); } } void USkillTreeComponent::RemoveSkillPoints(int32 Amount) { if (Amount > 0) { AvailableSkillPoints = FMath::Max(0, AvailableSkillPoints - Amount); OnSkillPointsChanged.Broadcast(AvailableSkillPoints); UpdateSkillStates(); } } bool USkillTreeComponent::SelectSkill(FName SkillID) { FText ErrorMessage; // 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); // Check if already purchased if (SkillNode->CurrentState == ESkillNodeState::Purchased) { 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; } // 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; } } if (!bWouldBeAvailable) { ErrorMessage = FText::FromString(TEXT("Prerequisites not selected. Select prerequisite skills first.")); OnSelectionError.Broadcast(SkillID, ErrorMessage); return false; } } // Add to selection SkillNode->bIsSelected = true; SelectedSkills.Add(SkillID); UpdateSelectionCost(); OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); OnSkillStateChanged.Broadcast(SkillID); return true; } bool USkillTreeComponent::DeselectSkill(FName SkillID) { if (!AllSkills.Contains(SkillID)) { return false; } FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); if (!SkillNode->bIsSelected) { 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(); 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); } } 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; } // 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; } // 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 (!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); // Apply effects ApplySkillEffects(SkillNode->NodeData); // Broadcast purchase event OnSkillPurchased.Broadcast(SkillID); OnSkillStateChanged.Broadcast(SkillID); } } // Deduct skill points RemoveSkillPoints(CurrentSelectionCost); // Clear selection SelectedSkills.Empty(); UpdateSelectionCost(); UpdateSkillStates(); OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); return true; } bool USkillTreeComponent::IsSkillPurchased(FName SkillID) const { return PurchasedSkills.Contains(SkillID); } bool USkillTreeComponent::IsSkillSelected(FName SkillID) const { return SelectedSkills.Contains(SkillID); } bool USkillTreeComponent::IsSkillAvailable(FName SkillID) const { const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); if (!SkillNode) { return false; } return SkillNode->CurrentState == ESkillNodeState::Available; } 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; } if (SkillNode->CurrentState == ESkillNodeState::Purchased) { OutErrorMessage = FText::FromString(TEXT("Skill already purchased")); return false; } if (SkillNode->bIsSelected) { OutErrorMessage = FText::FromString(TEXT("Skill already selected")); return false; } OutErrorMessage = FText::GetEmpty(); return true; } ESkillNodeState USkillTreeComponent::GetSkillState(FName SkillID) const { const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); if (SkillNode) { return SkillNode->CurrentState; } return ESkillNodeState::Locked; } bool USkillTreeComponent::GetSkillNodeData(FName SkillID, FSkillNodeRuntime& OutNodeData) const { const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); if (SkillNode) { OutNodeData = *SkillNode; return true; } return false; } TArray USkillTreeComponent::GetAllSkillNodes() const { 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 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 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 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 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 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; } 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); } } // Clear purchases PurchasedSkills.Empty(); // Reset all skill states for (auto& Pair : AllSkills) { Pair.Value.CurrentState = ESkillNodeState::Locked; Pair.Value.bIsSelected = false; } // Clear selection SelectedSkills.Empty(); CurrentSelectionCost = 0; // Add refunded points AddSkillPoints(RefundAmount); // Update states UpdateSkillStates(); // Broadcast events OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); } void USkillTreeComponent::UpdateSkillStates() { for (auto& Pair : AllSkills) { FSkillNodeRuntime& SkillNode = Pair.Value; // Skip if already purchased if (SkillNode.CurrentState == ESkillNodeState::Purchased) { 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; } } } void USkillTreeComponent::UpdateSelectionCost() { 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 { 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; } 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()); } 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()); }