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