#include "Actors/ClickableActors/Skilling/TreeBase/TreeBase.h"
#include "Components/StaticMeshComponent.h"
#include "BMM/BMMCharacter.h"
#include "Net/UnrealNetwork.h"
#include "Engine/Engine.h"
#include "Game/AbilitySystem/ResourceAttributSet/ResourceAttributeSet.h"
#include "Input/TimedInteractable.h"
#include "AbilitySystemInterface.h"
#include "AbilitySystemComponent.h"
#include "GameplayEffect.h"
#include "GameplayEffectTypes.h"
ATreeBase::ATreeBase() :
TreeHealth(100.0f),
MaxTreeHealth(100.0f),
MaxInteractionDistance(200.0f),
RespawnTime(5.0f),
InteractingPlayerCharacter(nullptr) // Default values and initialize player reference
{
PrimaryActorTick.bCanEverTick = true;
TreeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("TreeMesh"));
RootComponent = TreeMesh;
bIsAxe = false;
RequiredWoodcuttingLevel = 1;
LogsGiven = 1;
ExperienceGiven = 10;
LogType = "DefaultLog"; // Default log type
bIsChoppedDown = false;
bReplicates = true;
SetReplicateMovement(true);
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
ResourceAttributes = CreateDefaultSubobject<UResourceAttributeSet>(TEXT("ResourceAttributes"));
ResourceAttributes->Health.SetCurrentValue(TreeHealth);
ResourceAttributes->MaxHealth.SetCurrentValue(MaxTreeHealth);
}
void ATreeBase::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Log, TEXT("Tree spawned: %s"), *GetName());
ResourceAttributes->Health.SetCurrentValue(TreeHealth);
ResourceAttributes->MaxHealth.SetCurrentValue(MaxTreeHealth);
}
UAbilitySystemComponent* ATreeBase::GetAbilitySystemComponent() const
{
return nullptr; // Return the appropriate AbilitySystemComponent instance if needed
}
void ATreeBase::StartRespawnTimer()
{
if (HasAuthority())
{
GetWorldTimerManager().SetTimer(RespawnTimerHandle, this, &ATreeBase::RespawnTree, RespawnTime, false);
UE_LOG(LogTemp, Log, TEXT("Respawn timer set for %f seconds"), RespawnTime);
}
}
void ATreeBase::ChopDownTree(ABMMCharacter* PlayerCharacter)
{
if (!bIsChoppedDown && HasAuthority() && PlayerCharacter && PlayerCharacter->SkillAttributeSet->WoodcuttingLevel.GetCurrentValue() >= RequiredWoodcuttingLevel)
{
StartRespawnTimer();
bIsChoppedDown = true;
UE_LOG(LogTemp, Log, TEXT("Tree being chopped down: %s by %s"), *GetName(), *PlayerCharacter->GetName());
Interact(PlayerCharacter);
}
else if (bIsChoppedDown)
{
UE_LOG(LogTemp, Warning, TEXT("Tree is already chopped down: %s"), *GetName());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Player %s does not meet the required woodcutting level: %d"), *PlayerCharacter->GetName(), RequiredWoodcuttingLevel);
}
}
void ATreeBase::HandleInteractTimerExpiry(ABMMCharacter* PlayerCharacter)
{
UE_LOG(LogTemp, Log, TEXT("HandleInteractTimerExpiry called for: %s"), *GetName());
if (HasAuthority() && PlayerCharacter)
{
UE_LOG(LogTemp, Log, TEXT("Destroying tree: %s"), *GetName());
PlayerCharacter->UnfreezePlayer(); // Unfreeze the player when the tree is destroyed
StopChopTreeAnimation(PlayerCharacter); // Stop the chopping animation
StopDamageTimer(); // Stop the damage timer
StartRespawnTimer();
UE_LOG(LogTemp, Log, TEXT("Tree %s will respawn in %f seconds"), *GetName(), RespawnTime);
TreeMesh->SetVisibility(false, true);
TreeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("PlayerCharacter is null or not authoritative"));
}
}
void ATreeBase::ApplyDamage()
{
float DamageAmount = 10.0f; // Default damage amount
const float OldHealth = ResourceAttributes->Health.GetCurrentValue();
const float NewHealth = OldHealth - DamageAmount;
ResourceAttributes->Health.SetCurrentValue(NewHealth);
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Hit Tree for %f Damage - Tree Health %f/%f"), DamageAmount, NewHealth, MaxTreeHealth));
if (NewHealth <= 0.0f)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Tree has been destroyed!"));
if (InteractingPlayerCharacter)
{
UE_LOG(LogTemp, Log, TEXT("Unfreezing Interacting Player: %s"), *InteractingPlayerCharacter->GetName());
InteractingPlayerCharacter->UnfreezePlayer(); // Unfreeze the player when the tree is destroyed
}
StartRespawnTimer();
UE_LOG(LogTemp, Log, TEXT("Tree %s will respawn in %f seconds"), *GetName(), RespawnTime);
TreeMesh->SetVisibility(false, true);
TreeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Destroy the actor to handle proper cleanup
Destroy();
}
}
void ATreeBase::RespawnTree()
{
UE_LOG(LogTemp, Log, TEXT("Respawning tree: %s"), *GetName());
if (!HasAuthority())
{
UE_LOG(LogTemp, Error, TEXT("RespawnTree called on non-authoritative actor: %s"), *GetName());
return;
}
// Use AActorSpawner to spawn the new tree actor
FVector SpawnLocation = GetActorLocation();
FRotator SpawnRotation = GetActorRotation();
ATreeBase* NewTree = GetWorld()->SpawnActor<ATreeBase>(GetClass(), SpawnLocation, SpawnRotation);
if (NewTree)
{
NewTree->TreeHealth = MaxTreeHealth;
NewTree->ResourceAttributes->Health.SetCurrentValue(MaxTreeHealth);
NewTree->bIsChoppedDown = false;
NewTree->TreeMesh->SetVisibility(true);
NewTree->TreeMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
NewTree->TreeMesh->SetCollisionResponseToAllChannels(ECR_Block);
NewTree->bReplicates = true;
NewTree->SetReplicateMovement(true);
UE_LOG(LogTemp, Log, TEXT("Tree respawned and is now visible and interactable: %s"), *NewTree->GetName());
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to respawn tree: %s"), *GetName());
}
}
void ATreeBase::Server_RespawnTree_Implementation()
{
RespawnTree();
}
bool ATreeBase::Server_RespawnTree_Validate()
{
return true;
}
void ATreeBase::PlayChopTreeAnimation(ABMMCharacter* PlayerCharacter)
{
if (PlayerCharacter)
{
if (UAnimInstance* AnimInstance = PlayerCharacter->GetMesh()->GetAnimInstance())
{
if (PlayerCharacter->ChopTreeAnimation)
{
AnimInstance->Montage_Play(PlayerCharacter->ChopTreeAnimation);
}
}
}
}
void ATreeBase::StopChopTreeAnimation(ABMMCharacter* PlayerCharacter)
{
if (PlayerCharacter)
{
if (UAnimInstance* AnimInstance = PlayerCharacter->GetMesh()->GetAnimInstance())
{
if (PlayerCharacter->ChopTreeAnimation)
{
AnimInstance->Montage_Stop(0.2f, PlayerCharacter->ChopTreeAnimation);
}
}
}
}
void ATreeBase::StartDamageTimer()
{
GetWorld()->GetTimerManager().SetTimer(DamageTimerHandle, this, &ATreeBase::ApplyDamage, 0.4f, true, 0.0f); // 4 ticks
}
void ATreeBase::StopDamageTimer()
{
GetWorld()->GetTimerManager().ClearTimer(DamageTimerHandle);
}
void ATreeBase::ServerApplyDamage_Implementation(float DamageAmount)
{
ApplyDamage();
}
float ATreeBase::GetHealth() const
{
return ResourceAttributes->Health.GetCurrentValue();
}
void ATreeBase::SetHealth(float NewHealth)
{
TreeHealth = NewHealth;
ResourceAttributes->Health.SetCurrentValue(NewHealth);
}
float ATreeBase::GetMaxHealth() const
{
return ResourceAttributes->MaxHealth.GetCurrentValue();
}
void ATreeBase::SetMaxHealth(float NewMaxHealth)
{
MaxTreeHealth = NewMaxHealth;
ResourceAttributes->MaxHealth.SetCurrentValue(NewMaxHealth);
}
bool ATreeBase::ServerApplyDamage_Validate(float DamageAmount)
{
return true; // Optionally, add validation logic here
}
void ATreeBase::OnRep_Health()
{
// Log the replicated health value
UE_LOG(LogTemp, Log, TEXT("OnRep_Health called. New Health: %f"), TreeHealth);
// Check if health is zero and handle destruction
if (TreeHealth <= 0.0f)
{
HandleInteractTimerExpiry(nullptr);
}
}
void ATreeBase::OnRep_IsChoppedDown()
{
if (bIsChoppedDown)
{
StartRespawnTimer();
UE_LOG(LogTemp, Log, TEXT("Tree %s will respawn in %f seconds"), *GetName(), RespawnTime);
TreeMesh->SetVisibility(false, true);
TreeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
}
void ATreeBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ATreeBase, ResourceAttributes);
DOREPLIFETIME(ATreeBase, RequiredWoodcuttingLevel);
DOREPLIFETIME(ATreeBase, LogsGiven);
DOREPLIFETIME(ATreeBase, ExperienceGiven);
DOREPLIFETIME(ATreeBase, LogType);
DOREPLIFETIME(ATreeBase, TreeHealth); // Replicate TreeHealth
DOREPLIFETIME(ATreeBase, MaxTreeHealth);
DOREPLIFETIME(ATreeBase, MaxInteractionDistance); // Replicate MaxInteractionDistance
DOREPLIFETIME(ATreeBase, RespawnTime);
DOREPLIFETIME(ATreeBase, bIsChoppedDown); // Replicate bIsChoppedDown
}
void ATreeBase::AwardWoodcuttingExperience(ABMMCharacter* PlayerCharacter)
{
if (PlayerCharacter)
{
UE_LOG(LogTemp, Log, TEXT("Starting to award experience"));
if (PlayerCharacter->SkillAttributeSet)
{
PlayerCharacter->SkillAttributeSet->AddWoodcuttingExperience(ExperienceGiven);
UE_LOG(LogTemp, Log, TEXT("Player %s awarded %d woodcutting experience"), *PlayerCharacter->GetName(), ExperienceGiven);
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString::Printf(TEXT("Awarded %d woodcutting experience!"), ExperienceGiven));
if (PlayerCharacter->SkillAttributeSet->WoodcuttingLevel.GetCurrentValue() > 1) // Adjust the condition as needed
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Level Up!"));
}
UE_LOG(LogTemp, Log, TEXT("Finished awarding experience"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("PlayerCharacter's SkillAttributeSet is null"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("PlayerCharacter is null"));
}
}
void ATreeBase::Interact(ABMMCharacter* PlayerCharacter)
{
UE_LOG(LogTemp, Log, TEXT("TreeBase: Interacting with: %s"), *GetName());
if (PlayerCharacter)
{
FVector PlayerLocation = PlayerCharacter->GetActorLocation();
FVector TreeLocation = GetActorLocation();
float Distance = FVector::Dist(PlayerLocation, TreeLocation);
if (Distance > MaxInteractionDistance)
{
UE_LOG(LogTemp, Warning, TEXT("Player %s is too far from tree %s to interact (Distance: %f, Max: %f)"), *PlayerCharacter->GetName(), *GetName(), Distance, MaxInteractionDistance);
return;
}
InteractingPlayerCharacter = PlayerCharacter; // Store the reference to PlayerCharacter
PlayerCharacter->FreezePlayer(); // Freeze the player when starting to chop the tree
UE_LOG(LogTemp, Log, TEXT("Freezing Player: %s"), *PlayerCharacter->GetName());
PlayChopTreeAnimation(PlayerCharacter); // Play the chopping animation
StartDamageTimer(); // Start the damage timer
}
Super::Interact(PlayerCharacter); // Use the base class timer logic
if (PlayerCharacter)
{
AwardWoodcuttingExperience(PlayerCharacter);
}
}
Thanks!