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