feat: initial commit, only bugs remaining
This commit is contained in:
		
							
								
								
									
										192
									
								
								Source/UTAD_UI/SkillTree/CharacterStatsComponent.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								Source/UTAD_UI/SkillTree/CharacterStatsComponent.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| #include "CharacterStatsComponent.h" | ||||
| #include "SkillTreeComponent.h" | ||||
| #include "GameFramework/Character.h" | ||||
| #include "GameFramework/CharacterMovementComponent.h" | ||||
|  | ||||
| UCharacterStatsComponent::UCharacterStatsComponent() | ||||
| { | ||||
|     PrimaryComponentTick.bCanEverTick = false; | ||||
|  | ||||
|     // Default base stats | ||||
|     BaseMaxHealth = 100.0f; | ||||
|     BaseDamage = 10.0f; | ||||
|     BaseSpeed = 600.0f; | ||||
|  | ||||
|     // Initialize current values | ||||
|     CurrentHealth = BaseMaxHealth; | ||||
|     MaxHealth = BaseMaxHealth; | ||||
|     CurrentDamage = BaseDamage; | ||||
|     CurrentSpeed = BaseSpeed; | ||||
|  | ||||
|     // Initialize bonuses to 0 | ||||
|     SkillHealthBonus = 0.0f; | ||||
|     SkillDamageBonus = 0.0f; | ||||
|     SkillSpeedBonus = 0.0f; | ||||
|     SkillHealthBonusPercent = 0.0f; | ||||
|     SkillDamageBonusPercent = 0.0f; | ||||
|     SkillSpeedBonusPercent = 0.0f; | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::BeginPlay() | ||||
| { | ||||
|     Super::BeginPlay(); | ||||
|  | ||||
|     // Find the skill tree component on the same actor | ||||
|     AActor* Owner = GetOwner(); | ||||
|     if (Owner) | ||||
|     { | ||||
|         OwnerSkillTree = Owner->FindComponentByClass<USkillTreeComponent>(); | ||||
|  | ||||
|         // Bind to skill tree events | ||||
|         if (OwnerSkillTree) | ||||
|         { | ||||
|             OwnerSkillTree->OnSkillPurchased.AddDynamic(this, &UCharacterStatsComponent::OnSkillPurchased); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     InitializeStats(); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::InitializeStats() | ||||
| { | ||||
|     MaxHealth = BaseMaxHealth; | ||||
|     CurrentHealth = MaxHealth; | ||||
|     CurrentDamage = BaseDamage; | ||||
|     CurrentSpeed = BaseSpeed; | ||||
|  | ||||
|     RecalculateStats(); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::OnSkillPurchased(FName SkillID) | ||||
| { | ||||
|     // Called when a skill is purchased, update all stats | ||||
|     UpdateStatsFromSkillTree(); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::UpdateStatsFromSkillTree() | ||||
| { | ||||
|     if (!OwnerSkillTree) | ||||
|     { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Reset skill bonuses | ||||
|     SkillHealthBonus = 0.0f; | ||||
|     SkillDamageBonus = 0.0f; | ||||
|     SkillSpeedBonus = 0.0f; | ||||
|     SkillHealthBonusPercent = 0.0f; | ||||
|     SkillDamageBonusPercent = 0.0f; | ||||
|     SkillSpeedBonusPercent = 0.0f; | ||||
|  | ||||
|     // Calculate total bonuses from purchased skills | ||||
|     TArray<FSkillNodeRuntime> AllSkills = OwnerSkillTree->GetAllSkillNodes(); | ||||
|     for (const FSkillNodeRuntime& Skill : AllSkills) | ||||
|     { | ||||
|         if (Skill.CurrentState == ESkillNodeState::Purchased) | ||||
|         { | ||||
|             // Add flat bonuses | ||||
|             SkillHealthBonus += Skill.NodeData.HealthBonus; | ||||
|             SkillDamageBonus += Skill.NodeData.DamageBonus; | ||||
|             SkillSpeedBonus += Skill.NodeData.SpeedBonus; | ||||
|  | ||||
|             // Add percentage bonuses | ||||
|             SkillHealthBonusPercent += Skill.NodeData.HealthBonusPercent; | ||||
|             SkillDamageBonusPercent += Skill.NodeData.DamageBonusPercent; | ||||
|             SkillSpeedBonusPercent += Skill.NodeData.SpeedBonusPercent; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     RecalculateStats(); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::RecalculateStats() | ||||
| { | ||||
|     // Calculate final stats: Base + Flat Bonus + (Base * Percentage Bonus) | ||||
|     float OldMaxHealth = MaxHealth; | ||||
|  | ||||
|     MaxHealth = BaseMaxHealth + SkillHealthBonus + (BaseMaxHealth * SkillHealthBonusPercent / 100.0f); | ||||
|     CurrentDamage = BaseDamage + SkillDamageBonus + (BaseDamage * SkillDamageBonusPercent / 100.0f); | ||||
|     CurrentSpeed = BaseSpeed + SkillSpeedBonus + (BaseSpeed * SkillSpeedBonusPercent / 100.0f); | ||||
|  | ||||
|     // Scale current health proportionally if max health changed | ||||
|     if (OldMaxHealth > 0 && MaxHealth != OldMaxHealth) | ||||
|     { | ||||
|         float HealthPercent = CurrentHealth / OldMaxHealth; | ||||
|         CurrentHealth = MaxHealth * HealthPercent; | ||||
|     } | ||||
|  | ||||
|     // Apply speed to character movement | ||||
|     ApplySpeedToMovement(); | ||||
|  | ||||
|     // Broadcast events | ||||
|     OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); | ||||
|     OnDamageChanged.Broadcast(CurrentDamage); | ||||
|     OnSpeedChanged.Broadcast(CurrentSpeed); | ||||
|     OnStatsUpdated.Broadcast(); | ||||
|  | ||||
|     UE_LOG(LogTemp, Log, TEXT("Stats Updated - Health: %.0f/%.0f, Damage: %.0f, Speed: %.0f"), | ||||
|         CurrentHealth, MaxHealth, CurrentDamage, CurrentSpeed); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::TakeDamage(float DamageAmount) | ||||
| { | ||||
|     if (DamageAmount <= 0.0f) | ||||
|     { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.0f, MaxHealth); | ||||
|     OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); | ||||
|  | ||||
|     if (CurrentHealth <= 0.0f) | ||||
|     { | ||||
|         UE_LOG(LogTemp, Warning, TEXT("Character has died!")); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::Heal(float HealAmount) | ||||
| { | ||||
|     if (HealAmount <= 0.0f) | ||||
|     { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     CurrentHealth = FMath::Clamp(CurrentHealth + HealAmount, 0.0f, MaxHealth); | ||||
|     OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::SetHealth(float NewHealth) | ||||
| { | ||||
|     CurrentHealth = FMath::Clamp(NewHealth, 0.0f, MaxHealth); | ||||
|     OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); | ||||
| } | ||||
|  | ||||
| FText UCharacterStatsComponent::GetHealthText() const | ||||
| { | ||||
|     return FText::Format(FText::FromString(TEXT("{0:.0f} / {1:.0f}")), | ||||
|         FText::AsNumber(CurrentHealth), | ||||
|         FText::AsNumber(MaxHealth)); | ||||
| } | ||||
|  | ||||
| FText UCharacterStatsComponent::GetDamageText() const | ||||
| { | ||||
|     return FText::Format(FText::FromString(TEXT("Damage: {0:.0f}")), | ||||
|         FText::AsNumber(CurrentDamage)); | ||||
| } | ||||
|  | ||||
| FText UCharacterStatsComponent::GetSpeedText() const | ||||
| { | ||||
|     return FText::Format(FText::FromString(TEXT("Speed: {0:.0f}")), | ||||
|         FText::AsNumber(CurrentSpeed)); | ||||
| } | ||||
|  | ||||
| void UCharacterStatsComponent::ApplySpeedToMovement() | ||||
| { | ||||
|     // Apply speed to character movement component | ||||
|     ACharacter* CharacterOwner = Cast<ACharacter>(GetOwner()); | ||||
|     if (CharacterOwner && CharacterOwner->GetCharacterMovement()) | ||||
|     { | ||||
|         CharacterOwner->GetCharacterMovement()->MaxWalkSpeed = CurrentSpeed; | ||||
|         UE_LOG(LogTemp, Log, TEXT("Applied movement speed: %.0f"), CurrentSpeed); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										140
									
								
								Source/UTAD_UI/SkillTree/CharacterStatsComponent.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								Source/UTAD_UI/SkillTree/CharacterStatsComponent.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "Components/ActorComponent.h" | ||||
| #include "CharacterStatsComponent.generated.h" | ||||
|  | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, CurrentHealth, float, MaxHealth); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDamageChanged, float, CurrentDamage); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSpeedChanged, float, CurrentSpeed); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnStatsUpdated); | ||||
|  | ||||
| UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) | ||||
| class UTAD_UI_API UCharacterStatsComponent : public UActorComponent | ||||
| { | ||||
|     GENERATED_BODY() | ||||
|  | ||||
| public: | ||||
|     UCharacterStatsComponent(); | ||||
|  | ||||
| protected: | ||||
|     virtual void BeginPlay() override; | ||||
|  | ||||
|     // Base stats (without any bonuses) | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Base Stats") | ||||
|     float BaseMaxHealth; | ||||
|  | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Base Stats") | ||||
|     float BaseDamage; | ||||
|  | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Base Stats") | ||||
|     float BaseSpeed; | ||||
|  | ||||
|     // Current values | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Current Stats") | ||||
|     float CurrentHealth; | ||||
|  | ||||
|     // Calculated stats (base + bonuses) | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Current Stats") | ||||
|     float MaxHealth; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Current Stats") | ||||
|     float CurrentDamage; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Current Stats") | ||||
|     float CurrentSpeed; | ||||
|  | ||||
|     // Skill tree bonuses (flat bonuses) | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses") | ||||
|     float SkillHealthBonus; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses") | ||||
|     float SkillDamageBonus; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses") | ||||
|     float SkillSpeedBonus; | ||||
|  | ||||
|     // Skill tree bonuses (percentage bonuses) | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses") | ||||
|     float SkillHealthBonusPercent; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses") | ||||
|     float SkillDamageBonusPercent; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Bonuses") | ||||
|     float SkillSpeedBonusPercent; | ||||
|  | ||||
| public: | ||||
|     // Initialize stats | ||||
|     UFUNCTION(BlueprintCallable, Category = "Character Stats") | ||||
|     void InitializeStats(); | ||||
|  | ||||
|     // Update stats from skill tree | ||||
|     UFUNCTION(BlueprintCallable, Category = "Character Stats") | ||||
|     void UpdateStatsFromSkillTree(); | ||||
|  | ||||
|     // Called when a skill is purchased | ||||
|     UFUNCTION() | ||||
|     void OnSkillPurchased(FName SkillID); | ||||
|  | ||||
|     // Calculate final stats | ||||
|     UFUNCTION(BlueprintCallable, Category = "Character Stats") | ||||
|     void RecalculateStats(); | ||||
|  | ||||
|     // Getters for current stats | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats") | ||||
|     float GetCurrentHealth() const { return CurrentHealth; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats") | ||||
|     float GetMaxHealth() const { return MaxHealth; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats") | ||||
|     float GetHealthPercentage() const { return MaxHealth > 0 ? CurrentHealth / MaxHealth : 0.0f; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats") | ||||
|     float GetCurrentDamage() const { return CurrentDamage; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats") | ||||
|     float GetCurrentSpeed() const { return CurrentSpeed; } | ||||
|  | ||||
|     // Health management | ||||
|     UFUNCTION(BlueprintCallable, Category = "Character Stats") | ||||
|     void TakeDamage(float DamageAmount); | ||||
|  | ||||
|     UFUNCTION(BlueprintCallable, Category = "Character Stats") | ||||
|     void Heal(float HealAmount); | ||||
|  | ||||
|     UFUNCTION(BlueprintCallable, Category = "Character Stats") | ||||
|     void SetHealth(float NewHealth); | ||||
|  | ||||
|     // Get formatted text for UI | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats UI") | ||||
|     FText GetHealthText() const; | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats UI") | ||||
|     FText GetDamageText() const; | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Character Stats UI") | ||||
|     FText GetSpeedText() const; | ||||
|  | ||||
|     // Events | ||||
|     UPROPERTY(BlueprintAssignable, Category = "Stats Events") | ||||
|     FOnHealthChanged OnHealthChanged; | ||||
|  | ||||
|     UPROPERTY(BlueprintAssignable, Category = "Stats Events") | ||||
|     FOnDamageChanged OnDamageChanged; | ||||
|  | ||||
|     UPROPERTY(BlueprintAssignable, Category = "Stats Events") | ||||
|     FOnSpeedChanged OnSpeedChanged; | ||||
|  | ||||
|     UPROPERTY(BlueprintAssignable, Category = "Stats Events") | ||||
|     FOnStatsUpdated OnStatsUpdated; | ||||
|  | ||||
| private: | ||||
|     // Apply speed to character movement | ||||
|     void ApplySpeedToMovement(); | ||||
|  | ||||
|     // Reference to owner's skill tree component | ||||
|     UPROPERTY() | ||||
|     class USkillTreeComponent* OwnerSkillTree; | ||||
| }; | ||||
							
								
								
									
										249
									
								
								Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,249 @@ | ||||
| #include "SkillTreeBlueprintLibrary.h" | ||||
| #include "SkillTreeComponent.h" | ||||
|  | ||||
| FLinearColor USkillTreeBlueprintLibrary::GetSkillStateColor(ESkillNodeState State) | ||||
| { | ||||
|     switch (State) | ||||
|     { | ||||
|     case ESkillNodeState::Locked: | ||||
|         return FLinearColor(0.3f, 0.3f, 0.3f, 1.0f);  // Gray | ||||
|     case ESkillNodeState::Available: | ||||
|         return FLinearColor(1.0f, 1.0f, 1.0f, 1.0f);  // White | ||||
|     case ESkillNodeState::Selected: | ||||
|         return FLinearColor(1.0f, 0.8f, 0.0f, 1.0f);  // Gold | ||||
|     case ESkillNodeState::Purchased: | ||||
|         return FLinearColor(0.0f, 1.0f, 0.0f, 1.0f);  // Green | ||||
|     default: | ||||
|         return FLinearColor::White; | ||||
|     } | ||||
| } | ||||
|  | ||||
| FText USkillTreeBlueprintLibrary::GetSkillStateText(ESkillNodeState State) | ||||
| { | ||||
|     switch (State) | ||||
|     { | ||||
|     case ESkillNodeState::Locked: | ||||
|         return FText::FromString(TEXT("Locked")); | ||||
|     case ESkillNodeState::Available: | ||||
|         return FText::FromString(TEXT("Available")); | ||||
|     case ESkillNodeState::Selected: | ||||
|         return FText::FromString(TEXT("Selected")); | ||||
|     case ESkillNodeState::Purchased: | ||||
|         return FText::FromString(TEXT("Purchased")); | ||||
|     default: | ||||
|         return FText::FromString(TEXT("Unknown")); | ||||
|     } | ||||
| } | ||||
|  | ||||
| FText USkillTreeBlueprintLibrary::GetSkillAvailabilityText(USkillTreeComponent* SkillTreeComponent, FName SkillID) | ||||
| { | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return FText::FromString(TEXT("No Skill Tree Component")); | ||||
|     } | ||||
|  | ||||
|     FSkillNodeRuntime NodeData; | ||||
|     if (!SkillTreeComponent->GetSkillNodeData(SkillID, NodeData)) | ||||
|     { | ||||
|         return FText::FromString(TEXT("Invalid Skill")); | ||||
|     } | ||||
|  | ||||
|     ESkillNodeState State = SkillTreeComponent->GetSkillState(SkillID); | ||||
|  | ||||
|     switch (State) | ||||
|     { | ||||
|     case ESkillNodeState::Purchased: | ||||
|         return FText::FromString(TEXT("Already Purchased")); | ||||
|     case ESkillNodeState::Selected: | ||||
|         return FText::FromString(TEXT("Selected for Purchase")); | ||||
|     case ESkillNodeState::Available: | ||||
|         return FText::Format(FText::FromString(TEXT("Available - Cost: {0} Points")), NodeData.NodeData.Cost); | ||||
|     case ESkillNodeState::Locked: | ||||
|         { | ||||
|             // Check why it's locked | ||||
|             if (NodeData.NodeData.Prerequisites.Num() > 0) | ||||
|             { | ||||
|                 FString MissingPrereqs; | ||||
|                 for (const FName& PrereqID : NodeData.NodeData.Prerequisites) | ||||
|                 { | ||||
|                     if (!SkillTreeComponent->IsSkillPurchased(PrereqID)) | ||||
|                     { | ||||
|                         FSkillNodeRuntime PrereqData; | ||||
|                         if (SkillTreeComponent->GetSkillNodeData(PrereqID, PrereqData)) | ||||
|                         { | ||||
|                             if (!MissingPrereqs.IsEmpty()) | ||||
|                             { | ||||
|                                 MissingPrereqs += TEXT(", "); | ||||
|                             } | ||||
|                             MissingPrereqs += PrereqData.NodeData.DisplayName.ToString(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return FText::Format(FText::FromString(TEXT("Requires: {0}")), FText::FromString(MissingPrereqs)); | ||||
|             } | ||||
|             return FText::FromString(TEXT("Locked")); | ||||
|         } | ||||
|     default: | ||||
|         return FText::FromString(TEXT("Unknown State")); | ||||
|     } | ||||
| } | ||||
|  | ||||
| FText USkillTreeBlueprintLibrary::FormatSkillDescription(const FSkillNodeData& NodeData) | ||||
| { | ||||
|     FString Description = NodeData.Description.ToString(); | ||||
|  | ||||
|     // Add effect descriptions | ||||
|     FString Effects; | ||||
|  | ||||
|     if (NodeData.HealthBonus > 0) | ||||
|     { | ||||
|         Effects += FString::Printf(TEXT("\n+%.0f Health"), NodeData.HealthBonus); | ||||
|     } | ||||
|     if (NodeData.HealthBonusPercent > 0) | ||||
|     { | ||||
|         Effects += FString::Printf(TEXT("\n+%.0f%% Health"), NodeData.HealthBonusPercent); | ||||
|     } | ||||
|     if (NodeData.DamageBonus > 0) | ||||
|     { | ||||
|         Effects += FString::Printf(TEXT("\n+%.0f Damage"), NodeData.DamageBonus); | ||||
|     } | ||||
|     if (NodeData.DamageBonusPercent > 0) | ||||
|     { | ||||
|         Effects += FString::Printf(TEXT("\n+%.0f%% Damage"), NodeData.DamageBonusPercent); | ||||
|     } | ||||
|     if (NodeData.SpeedBonus > 0) | ||||
|     { | ||||
|         Effects += FString::Printf(TEXT("\n+%.0f Speed"), NodeData.SpeedBonus); | ||||
|     } | ||||
|     if (NodeData.SpeedBonusPercent > 0) | ||||
|     { | ||||
|         Effects += FString::Printf(TEXT("\n+%.0f%% Speed"), NodeData.SpeedBonusPercent); | ||||
|     } | ||||
|  | ||||
|     if (!Effects.IsEmpty()) | ||||
|     { | ||||
|         Description += TEXT("\n\nEffects:") + Effects; | ||||
|     } | ||||
|  | ||||
|     return FText::FromString(Description); | ||||
| } | ||||
|  | ||||
|  | ||||
| float USkillTreeBlueprintLibrary::GetPreviewTotalDamageBonus(USkillTreeComponent* SkillTreeComponent) | ||||
| { | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return 0.0f; | ||||
|     } | ||||
|  | ||||
|     float Total = SkillTreeComponent->GetTotalDamageBonus(); | ||||
|  | ||||
|     // Add selected (but not purchased) skills | ||||
|     TArray<FName> SelectedSkills = SkillTreeComponent->GetSelectedSkills(); | ||||
|     for (const FName& SkillID : SelectedSkills) | ||||
|     { | ||||
|         FSkillNodeRuntime NodeData; | ||||
|         if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData)) | ||||
|         { | ||||
|             Total += NodeData.NodeData.DamageBonus; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
|  | ||||
| float USkillTreeBlueprintLibrary::GetPreviewTotalSpeedBonus(USkillTreeComponent* SkillTreeComponent) | ||||
| { | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return 0.0f; | ||||
|     } | ||||
|  | ||||
|     float Total = SkillTreeComponent->GetTotalSpeedBonus(); | ||||
|  | ||||
|     // Add selected (but not purchased) skills | ||||
|     TArray<FName> SelectedSkills = SkillTreeComponent->GetSelectedSkills(); | ||||
|     for (const FName& SkillID : SelectedSkills) | ||||
|     { | ||||
|         FSkillNodeRuntime NodeData; | ||||
|         if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData)) | ||||
|         { | ||||
|             Total += NodeData.NodeData.SpeedBonus; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| float USkillTreeBlueprintLibrary::GetPreviewTotalHealthBonus(USkillTreeComponent* SkillTreeComponent) | ||||
| { | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return 0.0f; | ||||
|     } | ||||
|  | ||||
|     float Total = SkillTreeComponent->GetTotalHealthBonus(); | ||||
|  | ||||
|     // Add selected (but not purchased) skills | ||||
|     TArray<FName> SelectedSkills = SkillTreeComponent->GetSelectedSkills(); | ||||
|     for (const FName& SkillID : SelectedSkills) | ||||
|     { | ||||
|         FSkillNodeRuntime NodeData; | ||||
|         if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData)) | ||||
|         { | ||||
|             Total += NodeData.NodeData.HealthBonus; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| bool USkillTreeBlueprintLibrary::HasSelectedSkills(USkillTreeComponent* SkillTreeComponent) | ||||
| { | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return SkillTreeComponent->GetSelectedSkills().Num() > 0; | ||||
| } | ||||
|  | ||||
| bool USkillTreeBlueprintLibrary::CanPlayerRespec(USkillTreeComponent* SkillTreeComponent) | ||||
| { | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Check if player has any purchased skills to reset | ||||
|     TArray<FSkillNodeRuntime> AllNodes = SkillTreeComponent->GetAllSkillNodes(); | ||||
|     for (const FSkillNodeRuntime& Node : AllNodes) | ||||
|     { | ||||
|         if (Node.CurrentState == ESkillNodeState::Purchased) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| TArray<FName> USkillTreeBlueprintLibrary::GetSkillConnections(USkillTreeComponent* SkillTreeComponent, FName SkillID) | ||||
| { | ||||
|     TArray<FName> Connections; | ||||
|  | ||||
|     if (!SkillTreeComponent) | ||||
|     { | ||||
|         return Connections; | ||||
|     } | ||||
|  | ||||
|     FSkillNodeRuntime NodeData; | ||||
|     if (SkillTreeComponent->GetSkillNodeData(SkillID, NodeData)) | ||||
|     { | ||||
|         // Return the prerequisites as connections | ||||
|         Connections = NodeData.NodeData.Prerequisites; | ||||
|     } | ||||
|  | ||||
|     return Connections; | ||||
| } | ||||
							
								
								
									
										56
									
								
								Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Source/UTAD_UI/SkillTree/SkillTreeBlueprintLibrary.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "Kismet/BlueprintFunctionLibrary.h" | ||||
| #include "SkillTreeTypes.h" | ||||
| #include "SkillTreeBlueprintLibrary.generated.h" | ||||
|  | ||||
| // Forward declarations | ||||
| class USkillTreeComponent; | ||||
|  | ||||
| UCLASS() | ||||
| class UTAD_UI_API USkillTreeBlueprintLibrary : public UBlueprintFunctionLibrary | ||||
| { | ||||
|     GENERATED_BODY() | ||||
|  | ||||
| public: | ||||
|     // UI Helper Functions | ||||
|  | ||||
|     // Get state color for UI display | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill State Color")) | ||||
|     static FLinearColor GetSkillStateColor(ESkillNodeState State); | ||||
|  | ||||
|     // Get state text for UI display | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill State Text")) | ||||
|     static FText GetSkillStateText(ESkillNodeState State); | ||||
|  | ||||
|     // Check if a skill can be selected with detailed reason | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill Availability Text")) | ||||
|     static FText GetSkillAvailabilityText(USkillTreeComponent* SkillTreeComponent, FName SkillID); | ||||
|  | ||||
|     // Get formatted description with current values | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Format Skill Description")) | ||||
|     static FText FormatSkillDescription(const FSkillNodeData& NodeData); | ||||
|  | ||||
|     // Calculate total stats preview (including selected but not purchased skills) | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Preview Total Health Bonus")) | ||||
|     static float GetPreviewTotalHealthBonus(USkillTreeComponent* SkillTreeComponent); | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Preview Total Damage Bonus")) | ||||
|     static float GetPreviewTotalDamageBonus(USkillTreeComponent* SkillTreeComponent); | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Preview Total Speed Bonus")) | ||||
|     static float GetPreviewTotalSpeedBonus(USkillTreeComponent* SkillTreeComponent); | ||||
|  | ||||
|     // Check if any skills are selected | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Has Selected Skills")) | ||||
|     static bool HasSelectedSkills(USkillTreeComponent* SkillTreeComponent); | ||||
|  | ||||
|     // Check if player can respec | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Can Player Respec")) | ||||
|     static bool CanPlayerRespec(USkillTreeComponent* SkillTreeComponent); | ||||
|  | ||||
|     // Get the connections between skills for line rendering | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Tree UI", meta = (DisplayName = "Get Skill Connections")) | ||||
|     static TArray<FName> GetSkillConnections(USkillTreeComponent* SkillTreeComponent, FName SkillID); | ||||
| }; | ||||
							
								
								
									
										557
									
								
								Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										557
									
								
								Source/UTAD_UI/SkillTree/SkillTreeComponent.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,557 @@ | ||||
| #include "SkillTreeComponent.h" | ||||
| #include "Engine/DataTable.h" | ||||
|  | ||||
| USkillTreeComponent::USkillTreeComponent() | ||||
| { | ||||
|     PrimaryComponentTick.bCanEverTick = false; | ||||
|  | ||||
|     AvailableSkillPoints = 5; // Start with 5 skill points for testing | ||||
|     TotalSkillPointsEarned = 5; | ||||
|     CurrentSelectionCost = 0; | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::BeginPlay() | ||||
| { | ||||
|     Super::BeginPlay(); | ||||
|     InitializeSkillTree(); | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::InitializeSkillTree() | ||||
| { | ||||
|     AllSkills.Empty(); | ||||
|  | ||||
|     if (!SkillDataTable) | ||||
|     { | ||||
|         UE_LOG(LogTemp, Warning, TEXT("SkillTreeComponent: No skill data table assigned!")); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Load all skills from the data table using RowNames as IDs | ||||
|     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; | ||||
|             RuntimeNode.bIsSelected = false; | ||||
|  | ||||
|             // Use RowName as the skill ID | ||||
|             AllSkills.Add(RowName, RuntimeNode); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     UpdateSkillStates(); | ||||
|  | ||||
|     UE_LOG(LogTemp, Log, TEXT("SkillTreeComponent: Initialized with %d skills"), AllSkills.Num()); | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::AddSkillPoints(int32 Amount) | ||||
| { | ||||
|     if (Amount > 0) | ||||
|     { | ||||
|         AvailableSkillPoints += Amount; | ||||
|         TotalSkillPointsEarned += Amount; | ||||
|         OnSkillPointsChanged.Broadcast(AvailableSkillPoints); | ||||
|         UpdateSkillStates(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::RemoveSkillPoints(int32 Amount) | ||||
| { | ||||
|     if (Amount > 0) | ||||
|     { | ||||
|         AvailableSkillPoints = FMath::Max(0, AvailableSkillPoints - Amount); | ||||
|         OnSkillPointsChanged.Broadcast(AvailableSkillPoints); | ||||
|         UpdateSkillStates(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::SelectSkill(FName SkillID) | ||||
| { | ||||
|     FText ErrorMessage; | ||||
|  | ||||
|     // Check if skill exists | ||||
|     if (!AllSkills.Contains(SkillID)) | ||||
|     { | ||||
|         ErrorMessage = FText::FromString(TEXT("Skill does not exist")); | ||||
|         OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|  | ||||
|     // Check if already purchased | ||||
|     if (SkillNode->CurrentState == ESkillNodeState::Purchased) | ||||
|     { | ||||
|         ErrorMessage = FText::FromString(TEXT("Skill already purchased")); | ||||
|         OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Check if already selected | ||||
|     if (SkillNode->bIsSelected) | ||||
|     { | ||||
|         ErrorMessage = FText::FromString(TEXT("Skill already selected")); | ||||
|         OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // For locked skills, we allow selection but will validate on purchase | ||||
|     // This allows players to "plan" their build | ||||
|     if (SkillNode->CurrentState == ESkillNodeState::Locked) | ||||
|     { | ||||
|         // Check if prerequisites would be met with current selection | ||||
|         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; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Add to selection | ||||
|     SkillNode->bIsSelected = true; | ||||
|     SelectedSkills.Add(SkillID); | ||||
|     UpdateSelectionCost(); | ||||
|  | ||||
|     OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||
|     OnSkillStateChanged.Broadcast(SkillID); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::DeselectSkill(FName SkillID) | ||||
| { | ||||
|     if (!AllSkills.Contains(SkillID)) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|  | ||||
|     if (!SkillNode->bIsSelected) | ||||
|     { | ||||
|         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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Remove from selection | ||||
|     SkillNode->bIsSelected = false; | ||||
|     SelectedSkills.Remove(SkillID); | ||||
|     UpdateSelectionCost(); | ||||
|  | ||||
|     OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||
|     OnSkillStateChanged.Broadcast(SkillID); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::ClearSelection() | ||||
| { | ||||
|     for (const FName& SkillID : SelectedSkills) | ||||
|     { | ||||
|         if (FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             SkillNode->bIsSelected = false; | ||||
|             OnSkillStateChanged.Broadcast(SkillID); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     SelectedSkills.Empty(); | ||||
|     UpdateSelectionCost(); | ||||
|     OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::ConfirmPurchase() | ||||
| { | ||||
|     // Validate we can afford it | ||||
|     if (!CanAffordSelection()) | ||||
|     { | ||||
|         FText ErrorMessage = FText::FromString(TEXT("Not enough skill points")); | ||||
|         OnSelectionError.Broadcast(NAME_None, ErrorMessage); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Sort selected skills by purchase order to ensure prerequisites are purchased first | ||||
|     TArray<FName> SortedSelection = SelectedSkills; | ||||
|     SortedSelection.Sort([this](const FName& A, const FName& B) { | ||||
|         FSkillNodeRuntime* NodeA = AllSkills.Find(A); | ||||
|         FSkillNodeRuntime* NodeB = AllSkills.Find(B); | ||||
|         if (NodeA && NodeB) | ||||
|         { | ||||
|             return NodeA->NodeData.PurchaseOrder < NodeB->NodeData.PurchaseOrder; | ||||
|         } | ||||
|         return false; | ||||
|     }); | ||||
|  | ||||
|     // Validate all skills can be purchased in order | ||||
|     for (const FName& SkillID : SortedSelection) | ||||
|     { | ||||
|         FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|         if (!SkillNode) | ||||
|         { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // Check prerequisites (considering already processed skills in this batch) | ||||
|         bool bPrereqsMet = true; | ||||
|         for (const FName& PrereqID : SkillNode->NodeData.Prerequisites) | ||||
|         { | ||||
|             if (!IsSkillPurchased(PrereqID)) | ||||
|             { | ||||
|                 // Check if it's in our current batch and would be purchased before this | ||||
|                 int32 PrereqIndex = SortedSelection.IndexOfByKey(PrereqID); | ||||
|                 int32 CurrentIndex = SortedSelection.IndexOfByKey(SkillID); | ||||
|  | ||||
|                 if (PrereqIndex == INDEX_NONE || PrereqIndex >= CurrentIndex) | ||||
|                 { | ||||
|                     bPrereqsMet = false; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!bPrereqsMet) | ||||
|         { | ||||
|             FText ErrorMessage = FText::Format( | ||||
|                 FText::FromString(TEXT("Cannot purchase {0}: prerequisites not met")), | ||||
|                 SkillNode->NodeData.DisplayName | ||||
|             ); | ||||
|             OnSelectionError.Broadcast(SkillID, ErrorMessage); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Purchase all selected skills | ||||
|     for (const FName& SkillID : SortedSelection) | ||||
|     { | ||||
|         FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|         if (SkillNode) | ||||
|         { | ||||
|             // Mark as purchased | ||||
|             SkillNode->CurrentState = ESkillNodeState::Purchased; | ||||
|             SkillNode->bIsSelected = false; | ||||
|             PurchasedSkills.Add(SkillID); | ||||
|  | ||||
|             // Apply effects | ||||
|             ApplySkillEffects(SkillNode->NodeData); | ||||
|  | ||||
|             // Broadcast purchase event | ||||
|             OnSkillPurchased.Broadcast(SkillID); | ||||
|             OnSkillStateChanged.Broadcast(SkillID); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Deduct skill points | ||||
|     RemoveSkillPoints(CurrentSelectionCost); | ||||
|  | ||||
|     // Clear selection | ||||
|     SelectedSkills.Empty(); | ||||
|     UpdateSelectionCost(); | ||||
|     UpdateSkillStates(); | ||||
|  | ||||
|     OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::IsSkillPurchased(FName SkillID) const | ||||
| { | ||||
|     return PurchasedSkills.Contains(SkillID); | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::IsSkillSelected(FName SkillID) const | ||||
| { | ||||
|     return SelectedSkills.Contains(SkillID); | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::IsSkillAvailable(FName SkillID) const | ||||
| { | ||||
|     const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|     if (!SkillNode) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return SkillNode->CurrentState == ESkillNodeState::Available; | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::CanSelectSkill(FName SkillID, FText& OutErrorMessage) const | ||||
| { | ||||
|     const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|     if (!SkillNode) | ||||
|     { | ||||
|         OutErrorMessage = FText::FromString(TEXT("Skill does not exist")); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (SkillNode->CurrentState == ESkillNodeState::Purchased) | ||||
|     { | ||||
|         OutErrorMessage = FText::FromString(TEXT("Skill already purchased")); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (SkillNode->bIsSelected) | ||||
|     { | ||||
|         OutErrorMessage = FText::FromString(TEXT("Skill already selected")); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     OutErrorMessage = FText::GetEmpty(); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| ESkillNodeState USkillTreeComponent::GetSkillState(FName SkillID) const | ||||
| { | ||||
|     const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|     if (SkillNode) | ||||
|     { | ||||
|         return SkillNode->CurrentState; | ||||
|     } | ||||
|  | ||||
|     return ESkillNodeState::Locked; | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::GetSkillNodeData(FName SkillID, FSkillNodeRuntime& OutNodeData) const | ||||
| { | ||||
|     const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID); | ||||
|     if (SkillNode) | ||||
|     { | ||||
|         OutNodeData = *SkillNode; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| TArray<FSkillNodeRuntime> USkillTreeComponent::GetAllSkillNodes() const | ||||
| { | ||||
|     TArray<FSkillNodeRuntime> Result; | ||||
|     for (const auto& Pair : AllSkills) | ||||
|     { | ||||
|         Result.Add(Pair.Value); | ||||
|     } | ||||
|     return Result; | ||||
| } | ||||
|  | ||||
| float USkillTreeComponent::GetTotalHealthBonus() const | ||||
| { | ||||
|     float Total = 0.0f; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             Total += SkillNode->NodeData.HealthBonus; | ||||
|         } | ||||
|     } | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| float USkillTreeComponent::GetTotalDamageBonus() const | ||||
| { | ||||
|     float Total = 0.0f; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             Total += SkillNode->NodeData.DamageBonus; | ||||
|         } | ||||
|     } | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| float USkillTreeComponent::GetTotalSpeedBonus() const | ||||
| { | ||||
|     float Total = 0.0f; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             Total += SkillNode->NodeData.SpeedBonus; | ||||
|         } | ||||
|     } | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| float USkillTreeComponent::GetTotalHealthBonusPercent() const | ||||
| { | ||||
|     float Total = 0.0f; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             Total += SkillNode->NodeData.HealthBonusPercent; | ||||
|         } | ||||
|     } | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| float USkillTreeComponent::GetTotalDamageBonusPercent() const | ||||
| { | ||||
|     float Total = 0.0f; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             Total += SkillNode->NodeData.DamageBonusPercent; | ||||
|         } | ||||
|     } | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| float USkillTreeComponent::GetTotalSpeedBonusPercent() const | ||||
| { | ||||
|     float Total = 0.0f; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             Total += SkillNode->NodeData.SpeedBonusPercent; | ||||
|         } | ||||
|     } | ||||
|     return Total; | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::ResetAllSkills() | ||||
| { | ||||
|     // Refund all skill points | ||||
|     int32 RefundAmount = 0; | ||||
|     for (const FName& SkillID : PurchasedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             RefundAmount += SkillNode->NodeData.Cost; | ||||
|             RemoveSkillEffects(SkillNode->NodeData); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Clear purchases | ||||
|     PurchasedSkills.Empty(); | ||||
|  | ||||
|     // Reset all skill states | ||||
|     for (auto& Pair : AllSkills) | ||||
|     { | ||||
|         Pair.Value.CurrentState = ESkillNodeState::Locked; | ||||
|         Pair.Value.bIsSelected = false; | ||||
|     } | ||||
|  | ||||
|     // Clear selection | ||||
|     SelectedSkills.Empty(); | ||||
|     CurrentSelectionCost = 0; | ||||
|  | ||||
|     // Add refunded points | ||||
|     AddSkillPoints(RefundAmount); | ||||
|  | ||||
|     // Update states | ||||
|     UpdateSkillStates(); | ||||
|  | ||||
|     // Broadcast events | ||||
|     OnSkillSelectionChanged.Broadcast(CurrentSelectionCost); | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::UpdateSkillStates() | ||||
| { | ||||
|     for (auto& Pair : AllSkills) | ||||
|     { | ||||
|         FSkillNodeRuntime& SkillNode = Pair.Value; | ||||
|  | ||||
|         // Skip if already purchased | ||||
|         if (SkillNode.CurrentState == ESkillNodeState::Purchased) | ||||
|         { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         // Check if prerequisites are met | ||||
|         if (ArePrerequisitesMet(SkillNode.NodeData)) | ||||
|         { | ||||
|             SkillNode.CurrentState = SkillNode.bIsSelected ? | ||||
|                 ESkillNodeState::Selected : ESkillNodeState::Available; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             SkillNode.CurrentState = SkillNode.bIsSelected ? | ||||
|                 ESkillNodeState::Selected : ESkillNodeState::Locked; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::UpdateSelectionCost() | ||||
| { | ||||
|     CurrentSelectionCost = 0; | ||||
|     for (const FName& SkillID : SelectedSkills) | ||||
|     { | ||||
|         if (const FSkillNodeRuntime* SkillNode = AllSkills.Find(SkillID)) | ||||
|         { | ||||
|             CurrentSelectionCost += SkillNode->NodeData.Cost; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::ArePrerequisitesMet(const FSkillNodeData& SkillData) const | ||||
| { | ||||
|     for (const FName& PrereqID : SkillData.Prerequisites) | ||||
|     { | ||||
|         if (!IsSkillPurchased(PrereqID)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool USkillTreeComponent::HasChildrenPurchased(FName SkillID) const | ||||
| { | ||||
|     for (const auto& Pair : AllSkills) | ||||
|     { | ||||
|         // Pair.Key is the RowName (SkillID) | ||||
|         if (Pair.Value.NodeData.Prerequisites.Contains(SkillID) && | ||||
|             IsSkillPurchased(Pair.Key)) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::ApplySkillEffects(const FSkillNodeData& SkillData) | ||||
| { | ||||
|     // This is where you would apply the actual skill effects to the character | ||||
|     // For now, we just log it | ||||
|     UE_LOG(LogTemp, Log, TEXT("Applied skill effects for: %s"), *SkillData.DisplayName.ToString()); | ||||
| } | ||||
|  | ||||
| void USkillTreeComponent::RemoveSkillEffects(const FSkillNodeData& SkillData) | ||||
| { | ||||
|     // This is where you would remove skill effects from the character | ||||
|     // For now, we just log it | ||||
|     UE_LOG(LogTemp, Log, TEXT("Removed skill effects for: %s"), *SkillData.DisplayName.ToString()); | ||||
| } | ||||
							
								
								
									
										163
									
								
								Source/UTAD_UI/SkillTree/SkillTreeComponent.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								Source/UTAD_UI/SkillTree/SkillTreeComponent.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "Components/ActorComponent.h" | ||||
| #include "SkillTreeTypes.h" | ||||
| #include "SkillTreeComponent.generated.h" | ||||
|  | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillPointsChanged, int32, NewSkillPoints); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillPurchased, FName, SkillID); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillSelectionChanged, int32, TotalCost); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSkillStateChanged, FName, SkillID); | ||||
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSelectionError, FName, SkillID, FText, ErrorMessage); | ||||
|  | ||||
| UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) | ||||
| class UTAD_UI_API USkillTreeComponent : public UActorComponent | ||||
| { | ||||
|     GENERATED_BODY() | ||||
|  | ||||
| public: | ||||
|     USkillTreeComponent(); | ||||
|  | ||||
| protected: | ||||
|     virtual void BeginPlay() override; | ||||
|  | ||||
|     // Current skill points available | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Skill Points", SaveGame) | ||||
|     int32 AvailableSkillPoints; | ||||
|  | ||||
|     // Total skill points earned | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Points", SaveGame) | ||||
|     int32 TotalSkillPointsEarned; | ||||
|  | ||||
|     // Data table containing all skill definitions | ||||
|     UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Skill Tree") | ||||
|     class UDataTable* SkillDataTable; | ||||
|  | ||||
|     // All skills loaded from the data table | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree") | ||||
|     TMap<FName, FSkillNodeRuntime> AllSkills; | ||||
|  | ||||
|     // Currently purchased skills | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree", SaveGame) | ||||
|     TArray<FName> PurchasedSkills; | ||||
|  | ||||
|     // Currently selected skills for purchase | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree") | ||||
|     TArray<FName> SelectedSkills; | ||||
|  | ||||
|     // Cache the total cost of selected skills | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill Tree") | ||||
|     int32 CurrentSelectionCost; | ||||
|  | ||||
| public: | ||||
|     // Initialize the skill tree from data table | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Tree") | ||||
|     void InitializeSkillTree(); | ||||
|  | ||||
|     // Skill Points Management | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Points") | ||||
|     void AddSkillPoints(int32 Amount); | ||||
|  | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Points") | ||||
|     void RemoveSkillPoints(int32 Amount); | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Points") | ||||
|     int32 GetAvailableSkillPoints() const { return AvailableSkillPoints; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Points") | ||||
|     int32 GetTotalSkillPointsEarned() const { return TotalSkillPointsEarned; } | ||||
|  | ||||
|     // Selection Management | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Selection") | ||||
|     bool SelectSkill(FName SkillID); | ||||
|  | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Selection") | ||||
|     bool DeselectSkill(FName SkillID); | ||||
|  | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Selection") | ||||
|     void ClearSelection(); | ||||
|  | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Selection") | ||||
|     bool ConfirmPurchase(); | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Selection") | ||||
|     TArray<FName> GetSelectedSkills() const { return SelectedSkills; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Selection") | ||||
|     int32 GetSelectionCost() const { return CurrentSelectionCost; } | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Selection") | ||||
|     bool CanAffordSelection() const { return AvailableSkillPoints >= CurrentSelectionCost; } | ||||
|  | ||||
|     // 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; | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill State") | ||||
|     bool CanSelectSkill(FName SkillID, FText& OutErrorMessage) const; | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill State") | ||||
|     ESkillNodeState GetSkillState(FName SkillID) const; | ||||
|  | ||||
|     // Get skill data | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Data") | ||||
|     bool GetSkillNodeData(FName SkillID, FSkillNodeRuntime& OutNodeData) const; | ||||
|  | ||||
|     UFUNCTION(BlueprintPure, Category = "Skill Data") | ||||
|     TArray<FSkillNodeRuntime> GetAllSkillNodes() const; | ||||
|  | ||||
|     // 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; | ||||
|  | ||||
|     // Reset skills (for respec feature) | ||||
|     UFUNCTION(BlueprintCallable, Category = "Skill Tree") | ||||
|     void ResetAllSkills(); | ||||
|  | ||||
|     // 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; | ||||
|  | ||||
| private: | ||||
|     // Internal helper functions | ||||
|     void UpdateSkillStates(); | ||||
|     void UpdateSelectionCost(); | ||||
|     bool ArePrerequisitesMet(const FSkillNodeData& SkillData) const; | ||||
|     bool HasChildrenPurchased(FName SkillID) const; | ||||
|     void ApplySkillEffects(const FSkillNodeData& SkillData); | ||||
|     void RemoveSkillEffects(const FSkillNodeData& SkillData); | ||||
| }; | ||||
							
								
								
									
										104
									
								
								Source/UTAD_UI/SkillTree/SkillTreeTypes.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								Source/UTAD_UI/SkillTree/SkillTreeTypes.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "Engine/DataTable.h" | ||||
| #include "SkillTreeTypes.generated.h" | ||||
|  | ||||
| // Enum for skill node state | ||||
| UENUM(BlueprintType) | ||||
| enum class ESkillNodeState : uint8 | ||||
| { | ||||
|     Locked      UMETA(DisplayName = "Locked"),         // Can't be purchased yet (missing prerequisites) | ||||
|     Available   UMETA(DisplayName = "Available"),      // Can be purchased | ||||
|     Selected    UMETA(DisplayName = "Selected"),       // Currently selected for purchase | ||||
|     Purchased   UMETA(DisplayName = "Purchased")       // Already owned | ||||
| }; | ||||
|  | ||||
| // Struct for individual skill node data | ||||
| // Note: RowName in the DataTable serves as the unique SkillID | ||||
| USTRUCT(BlueprintType) | ||||
| struct FSkillNodeData : public FTableRowBase | ||||
| { | ||||
|     GENERATED_BODY() | ||||
|  | ||||
|     // Display name for UI | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||
|     FText DisplayName; | ||||
|  | ||||
|     // Description of what the skill does | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||
|     FText Description; | ||||
|  | ||||
|     // Icon for the skill | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||
|     class UTexture2D* Icon; | ||||
|  | ||||
|     // Skill point cost | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||
|     int32 Cost; | ||||
|  | ||||
|     // Purchase order priority (lower = buy first, used when confirming multiple skills) | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||
|     int32 PurchaseOrder; | ||||
|  | ||||
|     // Prerequisites - must have these skills first (RowNames of required skills) | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skill") | ||||
|     TArray<FName> Prerequisites; | ||||
|  | ||||
|     // Effects this skill provides (actual stat bonuses) | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||
|     float HealthBonus; | ||||
|  | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||
|     float DamageBonus; | ||||
|  | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||
|     float SpeedBonus; | ||||
|  | ||||
|     // Percentage bonuses (multiplicative) | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||
|     float HealthBonusPercent; | ||||
|  | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||
|     float DamageBonusPercent; | ||||
|  | ||||
|     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Effects") | ||||
|     float SpeedBonusPercent; | ||||
|  | ||||
|     FSkillNodeData() | ||||
|     { | ||||
|         DisplayName = FText::GetEmpty(); | ||||
|         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 | ||||
| USTRUCT(BlueprintType) | ||||
| struct FSkillNodeRuntime | ||||
| { | ||||
|     GENERATED_BODY() | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill") | ||||
|     FSkillNodeData NodeData; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill") | ||||
|     ESkillNodeState CurrentState; | ||||
|  | ||||
|     UPROPERTY(BlueprintReadOnly, Category = "Skill") | ||||
|     bool bIsSelected; | ||||
|  | ||||
|     FSkillNodeRuntime() | ||||
|     { | ||||
|         CurrentState = ESkillNodeState::Locked; | ||||
|         bIsSelected = false; | ||||
|     } | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user