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;
|
||||
|
||||
// Check if skill exists
|
||||
if (!AllSkills.Contains(SkillID))
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT("Skill does not exist"));
|
||||
@@ -84,55 +83,40 @@ bool USkillTreeComponent::SelectSkill(FName SkillID)
|
||||
}
|
||||
|
||||
FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID);
|
||||
|
||||
// Validate skill state
|
||||
switch (SkillNode->CurrentState)
|
||||
{
|
||||
case ESkillNodeState::Purchased:
|
||||
// Already purchased, cannot select
|
||||
ErrorMessage = FText::FromString(TEXT("Skill already purchased"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
|
||||
case ESkillNodeState::Selected:
|
||||
// Already selected, cannot select again
|
||||
ErrorMessage = FText::FromString(TEXT("Skill already selected"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
|
||||
case ESkillNodeState::Locked:
|
||||
// For locked skills, we allow selection but validate prerequisites
|
||||
// This allows players to "plan" their build
|
||||
{
|
||||
bool bWouldBeAvailable = true;
|
||||
for (const FName& PrereqID : SkillNode->NodeData.Prerequisites)
|
||||
{
|
||||
if (!IsSkillPurchased(PrereqID) && !IsSkillSelected(PrereqID))
|
||||
{
|
||||
bWouldBeAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bWouldBeAvailable)
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT(
|
||||
"Prerequisites not selected. Select prerequisite skills first"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
ErrorMessage = FText::FromString(TEXT("Prerequisites not met or not enough skill points"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
|
||||
case ESkillNodeState::Available:
|
||||
// Available skills can be selected without additional checks
|
||||
// Double-check affordability (should already be Available only if affordable)
|
||||
if ((AvailableSkillPoints - CurrentSelectionCost) < SkillNode->NodeData.Cost)
|
||||
{
|
||||
ErrorMessage = FText::FromString(TEXT("Not enough skill points"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Add to selection
|
||||
SkillNode->CurrentState = ESkillNodeState::Selected;
|
||||
SelectedSkills.Add(SkillID);
|
||||
UpdateSelectionCost();
|
||||
|
||||
UpdateSkillStates();
|
||||
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
OnSkillStateChanged.Broadcast(SkillID);
|
||||
|
||||
@@ -149,57 +133,24 @@ bool USkillTreeComponent::DeselectSkill(FName SkillID)
|
||||
if (SkillNode->CurrentState != ESkillNodeState::Selected)
|
||||
return false;
|
||||
|
||||
// Check if any selected skills depend on this one
|
||||
for (const FName& SelectedID : SelectedSkills)
|
||||
{
|
||||
if (SelectedID != SkillID)
|
||||
{
|
||||
FSkillNodeRuntime* SelectedNode = AllSkills.Find(SelectedID);
|
||||
if (SelectedNode
|
||||
&& SelectedNode->NodeData.Prerequisites.Contains(SkillID))
|
||||
{
|
||||
// Can't deselect if other selected skills depend on it
|
||||
FText ErrorMessage = FText::FromString(
|
||||
TEXT("Other selected skills depend on this one"));
|
||||
OnSelectionError.Broadcast(SkillID, ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
CascadeDeselectDependents(SkillID);
|
||||
|
||||
// Remove from selection and update state
|
||||
SelectedSkills.Remove(SkillID);
|
||||
UpdateSelectionCost();
|
||||
|
||||
// Update state to either Available or Locked based on prerequisites
|
||||
if (ArePrerequisitesMet(SkillNode->NodeData))
|
||||
SkillNode->CurrentState = ESkillNodeState::Available;
|
||||
else
|
||||
SkillNode->CurrentState = ESkillNodeState::Locked;
|
||||
|
||||
UpdateSkillStates();
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
OnSkillStateChanged.Broadcast(SkillID);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
UpdateSelectionCost();
|
||||
|
||||
UpdateSkillStates();
|
||||
|
||||
OnSkillSelectionChanged.Broadcast(CurrentSelectionCost);
|
||||
}
|
||||
|
||||
@@ -339,7 +290,16 @@ bool USkillTreeComponent::CanSelectSkill(FName SkillID,
|
||||
return false;
|
||||
|
||||
case ESkillNodeState::Locked:
|
||||
OutErrorMessage = FText::FromString(TEXT("Prerequisites not met or not enough skill points"));
|
||||
return false;
|
||||
|
||||
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();
|
||||
return true;
|
||||
|
||||
@@ -493,20 +453,40 @@ void USkillTreeComponent::UpdateSkillStates()
|
||||
const FName& SkillID = Pair.Key;
|
||||
FSkillNodeRuntime& SkillNode = Pair.Value;
|
||||
|
||||
// Only update Locked and Available states based on prerequisites
|
||||
// Purchased and Selected states should not be changed
|
||||
switch (SkillNode.CurrentState)
|
||||
{
|
||||
case ESkillNodeState::Purchased:
|
||||
case ESkillNodeState::Selected:
|
||||
// Don't change these states
|
||||
// Never change purchased state
|
||||
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::Available:
|
||||
{
|
||||
// Calculate new state based on prerequisites
|
||||
ESkillNodeState NewState = ArePrerequisitesMet(SkillNode.NodeData)
|
||||
// Skill is Available only if:
|
||||
// 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::Locked;
|
||||
|
||||
@@ -535,14 +515,20 @@ void USkillTreeComponent::UpdateSelectionCost()
|
||||
}
|
||||
|
||||
bool USkillTreeComponent::ArePrerequisitesMet(
|
||||
const FSkillNodeData& SkillData) const
|
||||
const FSkillNodeData& SkillData, bool bIncludeSelected) const
|
||||
{
|
||||
for (const FName& PrereqID : SkillData.Prerequisites)
|
||||
{
|
||||
if (!IsSkillPurchased(PrereqID))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 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 true;
|
||||
}
|
||||
@@ -561,6 +547,30 @@ bool USkillTreeComponent::HasChildrenPurchased(FName SkillID) const
|
||||
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)
|
||||
{
|
||||
// This is where you would apply the actual skill effects to the character
|
||||
@@ -575,4 +585,4 @@ void USkillTreeComponent::RemoveSkillEffects(const FSkillNodeData& SkillData)
|
||||
// For now, we just log it
|
||||
UE_LOG(LogTemp, Log, TEXT("Removed skill effects for: %s"),
|
||||
*SkillData.DisplayName.ToString());
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,18 @@ protected:
|
||||
|
||||
// Current skill points available
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill Points", SaveGame)
|
||||
int32 AvailableSkillPoints;
|
||||
|
||||
int32 AvailableSkillPoints = 0;
|
||||
// Total skill points earned
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Points", SaveGame)
|
||||
int32 TotalSkillPointsEarned;
|
||||
int32 TotalSkillPointsEarned = 0;
|
||||
|
||||
// Data table containing all skill definitions
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Skill Tree")
|
||||
class UDataTable* SkillDataTable;
|
||||
class UDataTable* SkillDataTable = nullptr;
|
||||
|
||||
// All skills loaded from the data table
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Tree")
|
||||
TMap<FName, FSkillNodeRuntime> AllSkills;
|
||||
TMap<FName, FSkillNodeRuntime> AllSkills = {};
|
||||
|
||||
// Currently purchased skills
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Skill Tree", SaveGame)
|
||||
@@ -93,10 +92,8 @@ public:
|
||||
// Skill State Queries
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool IsSkillPurchased(FName SkillID) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool IsSkillSelected(FName SkillID) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill State")
|
||||
bool IsSkillAvailable(FName SkillID) const;
|
||||
|
||||
@@ -116,19 +113,15 @@ public:
|
||||
// Calculate total bonuses from purchased skills
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalHealthBonus() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalDamageBonus() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalSpeedBonus() const;
|
||||
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalHealthBonusPercent() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalDamageBonusPercent() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Skill Bonuses")
|
||||
float GetTotalSpeedBonusPercent() const;
|
||||
|
||||
@@ -139,16 +132,12 @@ public:
|
||||
// Events
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillPointsChanged OnSkillPointsChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillPurchased OnSkillPurchased;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillSelectionChanged OnSkillSelectionChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSkillStateChanged OnSkillStateChanged;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Skill Events")
|
||||
FOnSelectionError OnSelectionError;
|
||||
|
||||
@@ -156,8 +145,9 @@ private:
|
||||
// Internal helper functions
|
||||
void UpdateSkillStates();
|
||||
void UpdateSelectionCost();
|
||||
bool ArePrerequisitesMet(const FSkillNodeData& SkillData) const;
|
||||
bool ArePrerequisitesMet(const FSkillNodeData& SkillData, bool bIncludeSelected = false) const;
|
||||
bool HasChildrenPurchased(FName SkillID) const;
|
||||
void ApplySkillEffects(const FSkillNodeData& SkillData);
|
||||
void RemoveSkillEffects(const FSkillNodeData& SkillData);
|
||||
void CascadeDeselectDependents(FName SkillID);
|
||||
};
|
||||
Reference in New Issue
Block a user