fix: selection
This commit is contained in:
		| @@ -5,7 +5,7 @@ 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; | ||||||
| } | } | ||||||
| @@ -22,22 +22,22 @@ void USkillTreeComponent::InitializeSkillTree() | |||||||
|  |  | ||||||
| 	if (!SkillDataTable) | 	if (!SkillDataTable) | ||||||
| 	{ | 	{ | ||||||
|         UE_LOG(LogTemp, Warning, TEXT("SkillTreeComponent: No skill data table assigned!")); | 		UE_LOG(LogTemp, Warning, | ||||||
|  | 			   TEXT("SkillTreeComponent: No skill data table assigned!")); | ||||||
| 		return; | 		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) | 	for (const FName& RowName : RowNames) | ||||||
| 	{ | 	{ | ||||||
|         FSkillNodeData* Row = SkillDataTable->FindRow<FSkillNodeData>(RowName, TEXT("SkillTreeComponent")); | 		FSkillNodeData* Row = SkillDataTable->FindRow<FSkillNodeData>( | ||||||
|  | 			RowName, TEXT("SkillTreeComponent")); | ||||||
| 		if (Row) | 		if (Row) | ||||||
| 		{ | 		{ | ||||||
| 			FSkillNodeRuntime RuntimeNode; | 			FSkillNodeRuntime RuntimeNode; | ||||||
| 			RuntimeNode.NodeData	 = *Row; | 			RuntimeNode.NodeData	 = *Row; | ||||||
| 			RuntimeNode.CurrentState = ESkillNodeState::Locked; | 			RuntimeNode.CurrentState = ESkillNodeState::Locked; | ||||||
|             RuntimeNode.bIsSelected = false; |  | ||||||
|  |  | ||||||
| 			// Use RowName as the skill ID | 			// Use RowName as the skill ID | ||||||
| 			AllSkills.Add(RowName, RuntimeNode); | 			AllSkills.Add(RowName, RuntimeNode); | ||||||
| @@ -46,7 +46,8 @@ void USkillTreeComponent::InitializeSkillTree() | |||||||
|  |  | ||||||
| 	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) | void USkillTreeComponent::AddSkillPoints(int32 Amount) | ||||||
| @@ -84,27 +85,24 @@ bool USkillTreeComponent::SelectSkill(FName SkillID) | |||||||
|  |  | ||||||
| 	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) | ||||||
| 	{ | 	{ | ||||||
|  | 	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; | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 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: | ||||||
|  | 		// For locked skills, we allow selection but validate prerequisites | ||||||
| 		// This allows players to "plan" their build | 		// This allows players to "plan" their build | ||||||
|     if (SkillNode->CurrentState == ESkillNodeState::Locked) |  | ||||||
|     { |  | ||||||
|         // Check if prerequisites would be met with current selection |  | ||||||
| 		bool bWouldBeAvailable = true; | 		bool bWouldBeAvailable = true; | ||||||
| 		for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) | 		for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) | ||||||
| 		{ | 		{ | ||||||
| @@ -114,17 +112,22 @@ bool USkillTreeComponent::SelectSkill(FName SkillID) | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!bWouldBeAvailable) | 		if (!bWouldBeAvailable) | ||||||
| 		{ | 		{ | ||||||
|             ErrorMessage = FText::FromString(TEXT("Prerequisites not selected. Select prerequisite skills first.")); | 			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; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Add to selection | 	// Add to selection | ||||||
|     SkillNode->bIsSelected = true; | 	SkillNode->CurrentState = ESkillNodeState::Selected; | ||||||
| 	SelectedSkills.Add(SkillID); | 	SelectedSkills.Add(SkillID); | ||||||
| 	UpdateSelectionCost(); | 	UpdateSelectionCost(); | ||||||
|  |  | ||||||
| @@ -137,16 +140,12 @@ bool USkillTreeComponent::SelectSkill(FName SkillID) | |||||||
| 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) | ||||||
| @@ -154,21 +153,28 @@ bool USkillTreeComponent::DeselectSkill(FName SkillID) | |||||||
| 		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 | 				// Can't deselect if other selected skills depend on it | ||||||
|                 FText ErrorMessage = FText::FromString(TEXT("Other selected skills depend on this one")); | 				FText ErrorMessage = FText::FromString( | ||||||
|  | 					TEXT("Other selected skills depend on this one")); | ||||||
| 				OnSelectionError.Broadcast(SkillID, ErrorMessage); | 				OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||||
| 				return false; | 				return false; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|     // Remove from selection | 	// Remove from selection and update state | ||||||
|     SkillNode->bIsSelected = false; |  | ||||||
| 	SelectedSkills.Remove(SkillID); | 	SelectedSkills.Remove(SkillID); | ||||||
| 	UpdateSelectionCost(); | 	UpdateSelectionCost(); | ||||||
|  |  | ||||||
|  | 	// Update state to either Available or Locked based on prerequisites | ||||||
|  | 	if (ArePrerequisitesMet(SkillNode->NodeData)) | ||||||
|  | 		SkillNode->CurrentState = ESkillNodeState::Available; | ||||||
|  | 	else | ||||||
|  | 		SkillNode->CurrentState = ESkillNodeState::Locked; | ||||||
|  |  | ||||||
| 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | 	OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||||
| 	OnSkillStateChanged.Broadcast(SkillID); | 	OnSkillStateChanged.Broadcast(SkillID); | ||||||
|  |  | ||||||
| @@ -181,7 +187,11 @@ void USkillTreeComponent::ClearSelection() | |||||||
| 	{ | 	{ | ||||||
| 		if (FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | 		if (FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||||
| 		{ | 		{ | ||||||
|             SkillNode->bIsSelected = false; | 			// Update state based on prerequisites | ||||||
|  | 			if (ArePrerequisitesMet(SkillNode->NodeData)) | ||||||
|  | 				SkillNode->CurrentState = ESkillNodeState::Available; | ||||||
|  | 			else | ||||||
|  | 				SkillNode->CurrentState = ESkillNodeState::Locked; | ||||||
| 			OnSkillStateChanged.Broadcast(SkillID); | 			OnSkillStateChanged.Broadcast(SkillID); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -201,14 +211,18 @@ bool USkillTreeComponent::ConfirmPurchase() | |||||||
| 		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 | ||||||
|  | 	// purchased first | ||||||
| 	TArray<FName> SortedSelection = SelectedSkills; | 	TArray<FName> SortedSelection = SelectedSkills; | ||||||
|     SortedSelection.Sort([this](const FName& A, const FName& B) { | 	SortedSelection.Sort( | ||||||
|  | 		[this](const FName& A, const FName& B) | ||||||
|  | 		{ | ||||||
| 			FSkillNodeRuntime* NodeA = AllSkills.Find(A); | 			FSkillNodeRuntime* NodeA = AllSkills.Find(A); | ||||||
| 			FSkillNodeRuntime* NodeB = AllSkills.Find(B); | 			FSkillNodeRuntime* NodeB = AllSkills.Find(B); | ||||||
| 			if (NodeA && NodeB) | 			if (NodeA && NodeB) | ||||||
| 			{ | 			{ | ||||||
|             return NodeA->NodeData.PurchaseOrder < NodeB->NodeData.PurchaseOrder; | 				return NodeA->NodeData.PurchaseOrder | ||||||
|  | 					< NodeB->NodeData.PurchaseOrder; | ||||||
| 			} | 			} | ||||||
| 			return false; | 			return false; | ||||||
| 		}); | 		}); | ||||||
| @@ -218,17 +232,17 @@ bool USkillTreeComponent::ConfirmPurchase() | |||||||
| 	{ | 	{ | ||||||
| 		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 | ||||||
|  | 		// batch) | ||||||
| 		bool bPrereqsMet = true; | 		bool bPrereqsMet = true; | ||||||
| 		for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) | 		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 | 				// Check if it's in our current batch and would be purchased | ||||||
|  | 				// before this | ||||||
| 				int32 PrereqIndex  = SortedSelection.IndexOfByKey(PrereqID); | 				int32 PrereqIndex  = SortedSelection.IndexOfByKey(PrereqID); | ||||||
| 				int32 CurrentIndex = SortedSelection.IndexOfByKey(SkillID); | 				int32 CurrentIndex = SortedSelection.IndexOfByKey(SkillID); | ||||||
|  |  | ||||||
| @@ -243,9 +257,9 @@ bool USkillTreeComponent::ConfirmPurchase() | |||||||
| 		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; | ||||||
| 		} | 		} | ||||||
| @@ -259,7 +273,6 @@ bool USkillTreeComponent::ConfirmPurchase() | |||||||
| 		{ | 		{ | ||||||
| 			// 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 | ||||||
| @@ -298,14 +311,13 @@ 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) | ||||||
| @@ -314,34 +326,38 @@ bool USkillTreeComponent::CanSelectSkill(FName SkillID, FText& OutErrorMessage) | |||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|     if (SkillNode->CurrentState == ESkillNodeState::Purchased) | 	switch (SkillNode->CurrentState) | ||||||
| 	{ | 	{ | ||||||
|  | 	case ESkillNodeState::Purchased: | ||||||
| 		OutErrorMessage = FText::FromString(TEXT("Skill already purchased")); | 		OutErrorMessage = FText::FromString(TEXT("Skill already purchased")); | ||||||
| 		return false; | 		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; | ||||||
|     } |  | ||||||
|  |  | ||||||
|  | 	case ESkillNodeState::Locked: | ||||||
|  | 	case ESkillNodeState::Available: | ||||||
| 		OutErrorMessage = FText::GetEmpty(); | 		OutErrorMessage = FText::GetEmpty(); | ||||||
| 		return true; | 		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) | ||||||
| @@ -357,9 +373,8 @@ 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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -369,10 +384,9 @@ float USkillTreeComponent::GetTotalHealthBonus() const | |||||||
| 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -382,10 +396,9 @@ float USkillTreeComponent::GetTotalDamageBonus() const | |||||||
| 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -395,10 +408,9 @@ float USkillTreeComponent::GetTotalSpeedBonus() const | |||||||
| 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -408,10 +420,9 @@ float USkillTreeComponent::GetTotalHealthBonusPercent() const | |||||||
| 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -421,10 +432,9 @@ float USkillTreeComponent::GetTotalDamageBonusPercent() const | |||||||
| 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -434,10 +444,9 @@ float USkillTreeComponent::GetTotalSpeedBonusPercent() const | |||||||
| 	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; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -459,10 +468,7 @@ void USkillTreeComponent::ResetAllSkills() | |||||||
|  |  | ||||||
| 	// 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(); | ||||||
| @@ -484,22 +490,27 @@ void USkillTreeComponent::UpdateSkillStates() | |||||||
| 	{ | 	{ | ||||||
| 		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) | ||||||
| 		{ | 		{ | ||||||
|  | 		case ESkillNodeState::Purchased: | ||||||
|  | 		case ESkillNodeState::Selected: | ||||||
|  | 			// Don't change these states | ||||||
| 			continue; | 			continue; | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Check if prerequisites are met | 		case ESkillNodeState::Locked: | ||||||
|  | 		case ESkillNodeState::Available: | ||||||
|  | 			// Update state based on prerequisites | ||||||
| 			if (ArePrerequisitesMet(SkillNode.NodeData)) | 			if (ArePrerequisitesMet(SkillNode.NodeData)) | ||||||
| 			{ | 			{ | ||||||
|             SkillNode.CurrentState = SkillNode.bIsSelected ? | 				SkillNode.CurrentState = ESkillNodeState::Available; | ||||||
|                 ESkillNodeState::Selected : ESkillNodeState::Available; |  | ||||||
| 			} | 			} | ||||||
| 			else | 			else | ||||||
| 			{ | 			{ | ||||||
|             SkillNode.CurrentState = SkillNode.bIsSelected ? | 				SkillNode.CurrentState = ESkillNodeState::Locked; | ||||||
|                 ESkillNodeState::Selected : ESkillNodeState::Locked; | 			} | ||||||
|  | 			break; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -516,7 +527,8 @@ void USkillTreeComponent::UpdateSelectionCost() | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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) | ||||||
| 	{ | 	{ | ||||||
| @@ -533,8 +545,8 @@ 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; | ||||||
| 		} | 		} | ||||||
| @@ -546,12 +558,14 @@ 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,19 +8,27 @@ | |||||||
| 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() | ||||||
|  |  | ||||||
|  | 	// RowName will be used as SkillID | ||||||
|  |  | ||||||
| 	// Display name for UI | 	// Display name for UI | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||||
| 	FText DisplayName; | 	FText DisplayName; | ||||||
| @@ -37,31 +45,27 @@ struct FSkillNodeData : public FTableRowBase | |||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||||
| 	int32 Cost; | 	int32 Cost; | ||||||
|  |  | ||||||
|     // Purchase order priority (lower = buy first, used when confirming multiple skills) | 	// Purchase order priority | ||||||
|  | 	// (lower = buy first, used when confirming multiple skills) | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||||
| 	int32 PurchaseOrder; | 	int32 PurchaseOrder; | ||||||
|  |  | ||||||
|     // Prerequisites - must have these skills first (RowNames of required skills) | 	// Prerequisites - must have these skills first | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||||
| 	TArray<FName> Prerequisites; | 	TArray<FName> Prerequisites; | ||||||
|  |  | ||||||
|     // Effects this skill provides (actual stat bonuses) | 	// Effects this skill provides | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||||
| 	float HealthBonus; | 	float HealthBonus; | ||||||
|  |  | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||||
| 	float DamageBonus; | 	float DamageBonus; | ||||||
|  |  | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||||
| 	float SpeedBonus; | 	float SpeedBonus; | ||||||
|  |  | ||||||
|     // Percentage bonuses (multiplicative) |  | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||||
| 	float HealthBonusPercent; | 	float HealthBonusPercent; | ||||||
|  |  | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||||
| 	float DamageBonusPercent; | 	float DamageBonusPercent; | ||||||
|  |  | ||||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||||
| 	float SpeedBonusPercent; | 	float SpeedBonusPercent; | ||||||
|  |  | ||||||
| @@ -93,12 +97,5 @@ struct FSkillNodeRuntime | |||||||
| 	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