feat: reset button and only available if affordable
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								Content/UI/WBP_SkillNode.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/UI/WBP_SkillNode.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Content/UI/WBP_SkillTree.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Content/UI/WBP_SkillTree.uasset
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -75,7 +75,6 @@ bool USkillTreeComponent::SelectSkill(FName SkillID) | |||||||
| { | { | ||||||
| 	FText ErrorMessage; | 	FText ErrorMessage; | ||||||
|  |  | ||||||
| 	// 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")); | ||||||
| @@ -84,55 +83,40 @@ bool USkillTreeComponent::SelectSkill(FName SkillID) | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | 	FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||||
|  |  | ||||||
| 	// Validate skill state |  | ||||||
| 	switch (SkillNode->CurrentState) | 	switch (SkillNode->CurrentState) | ||||||
| 	{ | 	{ | ||||||
| 	case ESkillNodeState::Purchased: | 	case ESkillNodeState::Purchased: | ||||||
| 		// Already purchased, cannot select |  | ||||||
| 		ErrorMessage = FText::FromString(TEXT("Skill already purchased")); | 		ErrorMessage = FText::FromString(TEXT("Skill already purchased")); | ||||||
| 		OnSelectionError.Broadcast(SkillID, ErrorMessage); | 		OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| 	case ESkillNodeState::Selected: | 	case ESkillNodeState::Selected: | ||||||
| 		// 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; | ||||||
|  |  | ||||||
| 	case ESkillNodeState::Locked: | 	case ESkillNodeState::Locked: | ||||||
| 		// For locked skills, we allow selection but validate prerequisites | 		ErrorMessage = FText::FromString(TEXT("Prerequisites not met or not enough skill points")); | ||||||
| 		// This allows players to "plan" their build | 		OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 	case ESkillNodeState::Available: | ||||||
|  | 		// Double-check affordability (should already be Available only if affordable) | ||||||
|  | 		if ((AvailableSkillPoints - CurrentSelectionCost) < SkillNode->NodeData.Cost) | ||||||
| 		{ | 		{ | ||||||
| 			bool bWouldBeAvailable = true; | 			ErrorMessage = FText::FromString(TEXT("Not enough skill points")); | ||||||
| 			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); | 			OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
| 		break; |  | ||||||
|  |  | ||||||
| 	case ESkillNodeState::Available: |  | ||||||
| 		// Available skills can be selected without additional checks |  | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Add to selection |  | ||||||
| 	SkillNode->CurrentState = ESkillNodeState::Selected; | 	SkillNode->CurrentState = ESkillNodeState::Selected; | ||||||
| 	SelectedSkills.Add(SkillID); | 	SelectedSkills.Add(SkillID); | ||||||
| 	UpdateSelectionCost(); | 	UpdateSelectionCost(); | ||||||
|  |  | ||||||
|  | 	UpdateSkillStates(); | ||||||
|  |  | ||||||
| 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||||
| 	OnSkillStateChanged.Broadcast(SkillID); | 	OnSkillStateChanged.Broadcast(SkillID); | ||||||
|  |  | ||||||
| @@ -149,57 +133,24 @@ bool USkillTreeComponent::DeselectSkill(FName SkillID) | |||||||
| 	if (SkillNode->CurrentState != ESkillNodeState::Selected) | 	if (SkillNode->CurrentState != ESkillNodeState::Selected) | ||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| 	// Check if any selected skills depend on this one | 	CascadeDeselectDependents(SkillID); | ||||||
| 	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 and update state |  | ||||||
| 	SelectedSkills.Remove(SkillID); | 	SelectedSkills.Remove(SkillID); | ||||||
| 	UpdateSelectionCost(); | 	UpdateSelectionCost(); | ||||||
|  |  | ||||||
| 	// Update state to either Available or Locked based on prerequisites | 	UpdateSkillStates(); | ||||||
| 	if (ArePrerequisitesMet(SkillNode->NodeData)) |  | ||||||
| 		SkillNode->CurrentState = ESkillNodeState::Available; |  | ||||||
| 	else |  | ||||||
| 		SkillNode->CurrentState = ESkillNodeState::Locked; |  | ||||||
|  |  | ||||||
| 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||||
| 	OnSkillStateChanged.Broadcast(SkillID); |  | ||||||
|  |  | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| void USkillTreeComponent::ClearSelection() | void USkillTreeComponent::ClearSelection() | ||||||
| { | { | ||||||
| 	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(); | 	SelectedSkills.Empty(); | ||||||
| 	UpdateSelectionCost(); | 	UpdateSelectionCost(); | ||||||
|  |  | ||||||
|  | 	UpdateSkillStates(); | ||||||
|  |  | ||||||
| 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -339,7 +290,16 @@ bool USkillTreeComponent::CanSelectSkill(FName	SkillID, | |||||||
| 		return false; | 		return false; | ||||||
|  |  | ||||||
| 	case ESkillNodeState::Locked: | 	case ESkillNodeState::Locked: | ||||||
|  | 		OutErrorMessage = FText::FromString(TEXT("Prerequisites not met or not enough skill points")); | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
| 	case ESkillNodeState::Available: | 	case ESkillNodeState::Available: | ||||||
|  | 		// Double-check affordability | ||||||
|  | 		if ((AvailableSkillPoints - CurrentSelectionCost) < SkillNode->NodeData.Cost) | ||||||
|  | 		{ | ||||||
|  | 			OutErrorMessage = FText::FromString(TEXT("Not enough skill points")); | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
| 		OutErrorMessage = FText::GetEmpty(); | 		OutErrorMessage = FText::GetEmpty(); | ||||||
| 		return true; | 		return true; | ||||||
|  |  | ||||||
| @@ -493,20 +453,40 @@ void USkillTreeComponent::UpdateSkillStates() | |||||||
| 		const FName& SkillID = Pair.Key; | 		const FName& SkillID = Pair.Key; | ||||||
| 		FSkillNodeRuntime& SkillNode = Pair.Value; | 		FSkillNodeRuntime& SkillNode = Pair.Value; | ||||||
|  |  | ||||||
| 		// Only update Locked and Available states based on prerequisites |  | ||||||
| 		// Purchased and Selected states should not be changed |  | ||||||
| 		switch (SkillNode.CurrentState) | 		switch (SkillNode.CurrentState) | ||||||
| 		{ | 		{ | ||||||
| 		case ESkillNodeState::Purchased: | 		case ESkillNodeState::Purchased: | ||||||
| 		case ESkillNodeState::Selected: | 			// Never change purchased state | ||||||
| 			// Don't change these states |  | ||||||
| 			continue; | 			continue; | ||||||
|  |  | ||||||
|  | 		case ESkillNodeState::Selected: | ||||||
|  | 			// If marked as Selected but NOT in SelectedSkills array, it was deselected | ||||||
|  | 			if (!SelectedSkills.Contains(SkillID)) | ||||||
|  | 			{ | ||||||
|  | 				// Check if it can be Available (prerequisites + affordability) | ||||||
|  | 				bool bPrereqsMet = ArePrerequisitesMet(SkillNode.NodeData, true); | ||||||
|  | 				bool bCanAfford = (AvailableSkillPoints - CurrentSelectionCost) >= SkillNode.NodeData.Cost; | ||||||
|  |  | ||||||
|  | 				ESkillNodeState NewState = (bPrereqsMet && bCanAfford) | ||||||
|  | 					? ESkillNodeState::Available | ||||||
|  | 					: ESkillNodeState::Locked; | ||||||
|  |  | ||||||
|  | 				SkillNode.CurrentState = NewState; | ||||||
|  | 				OnSkillStateChanged.Broadcast(SkillID); | ||||||
|  | 			} | ||||||
|  | 			// Keep it Selected | ||||||
|  | 			break; | ||||||
|  |  | ||||||
| 		case ESkillNodeState::Locked: | 		case ESkillNodeState::Locked: | ||||||
| 		case ESkillNodeState::Available: | 		case ESkillNodeState::Available: | ||||||
| 			{ | 			{ | ||||||
| 				// Calculate new state based on prerequisites | 				// Skill is Available only if: | ||||||
| 				ESkillNodeState NewState = ArePrerequisitesMet(SkillNode.NodeData) | 				// 1. Prerequisites are met (purchased or selected) | ||||||
|  | 				// 2. Player has enough skill points to afford it | ||||||
|  | 				bool bPrereqsMet = ArePrerequisitesMet(SkillNode.NodeData, true); | ||||||
|  | 				bool bCanAfford = (AvailableSkillPoints - CurrentSelectionCost) >= SkillNode.NodeData.Cost; | ||||||
|  |  | ||||||
|  | 				ESkillNodeState NewState = (bPrereqsMet && bCanAfford) | ||||||
| 					? ESkillNodeState::Available | 					? ESkillNodeState::Available | ||||||
| 					: ESkillNodeState::Locked; | 					: ESkillNodeState::Locked; | ||||||
|  |  | ||||||
| @@ -535,15 +515,21 @@ void USkillTreeComponent::UpdateSelectionCost() | |||||||
| } | } | ||||||
|  |  | ||||||
| bool USkillTreeComponent::ArePrerequisitesMet( | bool USkillTreeComponent::ArePrerequisitesMet( | ||||||
| 	const FSkillNodeData& SkillData) const | 	const FSkillNodeData& SkillData, bool bIncludeSelected) const | ||||||
| { | { | ||||||
| 	for (const FName& PrereqID : SkillData.Prerequisites) | 	for (const FName& PrereqID : SkillData.Prerequisites) | ||||||
| 	{ | 	{ | ||||||
| 		if (!IsSkillPurchased(PrereqID)) | 		// Check if prerequisite is purchased | ||||||
| 		{ | 		if (IsSkillPurchased(PrereqID)) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		// If including selected, also check if prerequisite is selected | ||||||
|  | 		if (bIncludeSelected && IsSkillSelected(PrereqID)) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		// Prerequisite not met | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -561,6 +547,30 @@ bool USkillTreeComponent::HasChildrenPurchased(FName SkillID) const | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void USkillTreeComponent::CascadeDeselectDependents(FName SkillID) | ||||||
|  | { | ||||||
|  | 	// Find all selected skills that depend on this skill | ||||||
|  | 	TArray<FName> ToDeselect; | ||||||
|  | 	for (const FName& SelectedID : SelectedSkills) | ||||||
|  | 	{ | ||||||
|  | 		if (SelectedID == SkillID) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		FSkillNodeRuntime* SelectedNode = AllSkills.Find(SelectedID); | ||||||
|  | 		if (SelectedNode && SelectedNode->NodeData.Prerequisites.Contains(SkillID)) | ||||||
|  | 		{ | ||||||
|  | 			ToDeselect.Add(SelectedID); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Recursively deselect dependent skills | ||||||
|  | 	for (const FName& DependentID : ToDeselect) | ||||||
|  | 	{ | ||||||
|  | 		CascadeDeselectDependents(DependentID); | ||||||
|  | 		SelectedSkills.Remove(DependentID); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| 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 | ||||||
|   | |||||||
| @@ -24,19 +24,18 @@ protected: | |||||||
|  |  | ||||||
|     // Current skill points available |     // Current skill points available | ||||||
|     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill Points", SaveGame) |     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill Points", SaveGame) | ||||||
|     int32 AvailableSkillPoints; |     int32 AvailableSkillPoints = 0; | ||||||
|  |  | ||||||
|     // Total skill points earned |     // Total skill points earned | ||||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Points", SaveGame) |     UPROPERTY(BlueprintReadOnly, Category = "Skill Points", SaveGame) | ||||||
|     int32 TotalSkillPointsEarned; |     int32 TotalSkillPointsEarned = 0; | ||||||
|  |  | ||||||
|     // Data table containing all skill definitions |     // Data table containing all skill definitions | ||||||
|     UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Skill Tree") |     UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Skill Tree") | ||||||
|     class UDataTable* SkillDataTable; |     class UDataTable* SkillDataTable = nullptr; | ||||||
|  |  | ||||||
|     // All skills loaded from the data table |     // All skills loaded from the data table | ||||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree") |     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree") | ||||||
|     TMap<FName, FSkillNodeRuntime> AllSkills; |     TMap<FName, FSkillNodeRuntime> AllSkills = {}; | ||||||
|  |  | ||||||
|     // Currently purchased skills |     // Currently purchased skills | ||||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree", SaveGame) |     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree", SaveGame) | ||||||
| @@ -93,10 +92,8 @@ public: | |||||||
|     // Skill State Queries |     // Skill State Queries | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill State") |     UFUNCTION(BlueprintPure, Category = "Skill State") | ||||||
|     bool IsSkillPurchased(FName SkillID) const; |     bool IsSkillPurchased(FName SkillID) const; | ||||||
|  |  | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill State") |     UFUNCTION(BlueprintPure, Category = "Skill State") | ||||||
|     bool IsSkillSelected(FName SkillID) const; |     bool IsSkillSelected(FName SkillID) const; | ||||||
|  |  | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill State") |     UFUNCTION(BlueprintPure, Category = "Skill State") | ||||||
|     bool IsSkillAvailable(FName SkillID) const; |     bool IsSkillAvailable(FName SkillID) const; | ||||||
|  |  | ||||||
| @@ -116,19 +113,15 @@ public: | |||||||
|     // Calculate total bonuses from purchased skills |     // Calculate total bonuses from purchased skills | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") |     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") | ||||||
|     float GetTotalHealthBonus() const; |     float GetTotalHealthBonus() const; | ||||||
|  |  | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") |     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") | ||||||
|     float GetTotalDamageBonus() const; |     float GetTotalDamageBonus() const; | ||||||
|  |  | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") |     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") | ||||||
|     float GetTotalSpeedBonus() const; |     float GetTotalSpeedBonus() const; | ||||||
| 	 | 	 | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") |     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") | ||||||
|     float GetTotalHealthBonusPercent() const; |     float GetTotalHealthBonusPercent() const; | ||||||
|  |  | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") |     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") | ||||||
|     float GetTotalDamageBonusPercent() const; |     float GetTotalDamageBonusPercent() const; | ||||||
|  |  | ||||||
|     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") |     UFUNCTION(BlueprintPure, Category = "Skill Bonuses") | ||||||
|     float GetTotalSpeedBonusPercent() const; |     float GetTotalSpeedBonusPercent() const; | ||||||
|  |  | ||||||
| @@ -139,16 +132,12 @@ public: | |||||||
|     // Events |     // Events | ||||||
|     UPROPERTY(BlueprintAssignable, Category = "Skill Events") |     UPROPERTY(BlueprintAssignable, Category = "Skill Events") | ||||||
|     FOnSkillPointsChanged OnSkillPointsChanged; |     FOnSkillPointsChanged OnSkillPointsChanged; | ||||||
|  |  | ||||||
|     UPROPERTY(BlueprintAssignable, Category = "Skill Events") |     UPROPERTY(BlueprintAssignable, Category = "Skill Events") | ||||||
|     FOnSkillPurchased OnSkillPurchased; |     FOnSkillPurchased OnSkillPurchased; | ||||||
|  |  | ||||||
|     UPROPERTY(BlueprintAssignable, Category = "Skill Events") |     UPROPERTY(BlueprintAssignable, Category = "Skill Events") | ||||||
|     FOnSkillSelectionChanged OnSkillSelectionChanged; |     FOnSkillSelectionChanged OnSkillSelectionChanged; | ||||||
|  |  | ||||||
|     UPROPERTY(BlueprintAssignable, Category = "Skill Events") |     UPROPERTY(BlueprintAssignable, Category = "Skill Events") | ||||||
|     FOnSkillStateChanged OnSkillStateChanged; |     FOnSkillStateChanged OnSkillStateChanged; | ||||||
|  |  | ||||||
|     UPROPERTY(BlueprintAssignable, Category = "Skill Events") |     UPROPERTY(BlueprintAssignable, Category = "Skill Events") | ||||||
|     FOnSelectionError OnSelectionError; |     FOnSelectionError OnSelectionError; | ||||||
|  |  | ||||||
| @@ -156,8 +145,9 @@ private: | |||||||
|     // Internal helper functions |     // Internal helper functions | ||||||
|     void UpdateSkillStates(); |     void UpdateSkillStates(); | ||||||
|     void UpdateSelectionCost(); |     void UpdateSelectionCost(); | ||||||
|     bool ArePrerequisitesMet(const FSkillNodeData& SkillData) const; |     bool ArePrerequisitesMet(const FSkillNodeData& SkillData, bool bIncludeSelected = false) const; | ||||||
|     bool HasChildrenPurchased(FName SkillID) const; |     bool HasChildrenPurchased(FName SkillID) const; | ||||||
|     void ApplySkillEffects(const FSkillNodeData& SkillData); |     void ApplySkillEffects(const FSkillNodeData& SkillData); | ||||||
|     void RemoveSkillEffects(const FSkillNodeData& SkillData); |     void RemoveSkillEffects(const FSkillNodeData& SkillData); | ||||||
|  |     void CascadeDeselectDependents(FName SkillID); | ||||||
| }; | }; | ||||||
		Reference in New Issue
	
	Block a user