fix: selection
This commit is contained in:
		| @@ -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<FName> RowNames = SkillDataTable->GetRowNames(); | ||||
| 	// Load all skills from the data table using RowNames as IDs | ||||
| 	TArray<FName> RowNames = SkillDataTable->GetRowNames(); | ||||
| 	for (const FName& RowName : RowNames) | ||||
| 	{ | ||||
| 		FSkillNodeData* Row = SkillDataTable->FindRow<FSkillNodeData>( | ||||
| 			RowName, TEXT("SkillTreeComponent")); | ||||
| 		if (Row) | ||||
| 		{ | ||||
| 			FSkillNodeRuntime RuntimeNode; | ||||
| 			RuntimeNode.NodeData	 = *Row; | ||||
| 			RuntimeNode.CurrentState = ESkillNodeState::Locked; | ||||
|  | ||||
|     for (const FName& RowName : RowNames) | ||||
|     { | ||||
|         FSkillNodeData* Row = SkillDataTable->FindRow<FSkillNodeData>(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<FName> 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<FName> 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<FSkillNodeRuntime> USkillTreeComponent::GetAllSkillNodes() const | ||||
| { | ||||
|     TArray<FSkillNodeRuntime> Result; | ||||
|     for (const auto& Pair : AllSkills) | ||||
|     { | ||||
|         Result.Add(Pair.Value); | ||||
|     } | ||||
|     return Result; | ||||
| 	TArray<FSkillNodeRuntime> 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()); | ||||
| } | ||||
| 	// 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()); | ||||
| } | ||||
|   | ||||
| @@ -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<FName> 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<FName> 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; | ||||
|     } | ||||
| }; | ||||
| 	FSkillNodeRuntime() { CurrentState = ESkillNodeState::Locked; } | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user