Initial commit

This commit is contained in:
DuOtto
2026-08-15 13:52:59 +02:00
commit 88f9481f19
680 changed files with 5222 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using System.Collections.Generic;
public class MaybeThisTimeTarget : TargetRules
{
public MaybeThisTimeTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V7;
IncludeOrderVersion = EngineIncludeOrderVersion.Latest;
ExtraModuleNames.AddRange( new string[] { "MaybeThisTime" } );
}
}
@@ -0,0 +1,36 @@
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using UnrealBuildTool.Rules;
public class MaybeThisTime : ModuleRules
{
public MaybeThisTime(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"GameplayTags",
"GameplayAbilities",
"GameplayTasks",
"NavigationSystem",
"AIModule",
"Slate",
"SlateCore"
});
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
+6
View File
@@ -0,0 +1,6 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MaybeThisTime.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, MaybeThisTime, "MaybeThisTime" );
+6
View File
@@ -0,0 +1,6 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
@@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AModuleActor.h"
// Sets default values
AAModuleActor::AAModuleActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AAModuleActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AAModuleActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
@@ -0,0 +1,180 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseAttributeSet.h"
#include "Net/UnrealNetwork.h"
#include "GameplayEffectExtension.h"
void UBaseAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if(Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
if (Attribute == GetStaminaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxStamina());
}
if (Attribute == GetManaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxMana());
}
if (Attribute == GetSatietyAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxSatiety());
}
}
void UBaseAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
if (Attribute == GetHealthAttribute()) {
}
if (Attribute == GetStaminaAttribute())
{
}
if (Attribute == GetManaAttribute())
{
}
}
void UBaseAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetSatietyAttribute())
{
SetSatiety(FMath::Clamp(GetSatiety(), 0.f, GetMaxSatiety()));
}
}
void UBaseAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Health, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, HealthRegen, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, StaminaRegen, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Mana, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, ManaRegen, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Armor, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, ElementalArmor, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, CritChance, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, CritaDamage, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MoveSpeed, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, SprintSpeedMultiplier, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, SneakSpeedMultiplier, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, SneakDmgMultiplier, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, WeakSpotDmgMultiplier, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MaxSatiety, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Satiety, COND_None, REPNOTIFY_Always);
}
void UBaseAttributeSet::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, MaxHealth, OldMaxHealth);
}
void UBaseAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, Health, OldHealth);
}
void UBaseAttributeSet::OnRep_HealthRegen(const FGameplayAttributeData& OldHealthRegen)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, HealthRegen, OldHealthRegen);
}
void UBaseAttributeSet::OnRep_MaxStamina(const FGameplayAttributeData& OldMaxStamina)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, MaxStamina, OldMaxStamina);
}
void UBaseAttributeSet::OnRep_Stamina(const FGameplayAttributeData& OldStamina)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, Stamina, OldStamina);
}
void UBaseAttributeSet::OnRep_StaminaRegen(const FGameplayAttributeData& OldStaminaRegen)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, StaminaRegen, OldStaminaRegen);
}
void UBaseAttributeSet::OnRep_MaxMana(const FGameplayAttributeData& OldMaxMana)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, MaxMana, OldMaxMana);
}
void UBaseAttributeSet::OnRep_Mana(const FGameplayAttributeData& OldMana)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, Mana, OldMana);
}
void UBaseAttributeSet::OnRep_ManaRegen(const FGameplayAttributeData& OldManaRegen)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, ManaRegen, OldManaRegen);
}
void UBaseAttributeSet::OnRep_Armor(const FGameplayAttributeData& OldArmor)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, Armor, OldArmor);
}
void UBaseAttributeSet::OnRep_ElementalArmor(const FGameplayAttributeData& OldElementalArmor)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, ElementalArmor, OldElementalArmor);
}
void UBaseAttributeSet::OnRep_CritChance(const FGameplayAttributeData& OldCritChance)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, CritChance, OldCritChance);
}
void UBaseAttributeSet::OnRep_CritaDamage(const FGameplayAttributeData& OldCritaDamage)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, CritaDamage, OldCritaDamage);
}
void UBaseAttributeSet::OnRep_MoveSpeed(const FGameplayAttributeData& OldMoveSpeed)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, MoveSpeed, OldMoveSpeed);
}
void UBaseAttributeSet::OnRep_SprintSpeedMultiplier(const FGameplayAttributeData& OldSprintSpeedMultiplier)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, SprintSpeedMultiplier, OldSprintSpeedMultiplier);
}
void UBaseAttributeSet::OnRep_SneakSpeedMultiplier(const FGameplayAttributeData& OldSneakSpeedMultiplier)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, SneakSpeedMultiplier, OldSneakSpeedMultiplier);
}
void UBaseAttributeSet::OnRep_SneakDmgMultiplier(const FGameplayAttributeData& OldSneakDmgMultiplier)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, SneakDmgMultiplier, OldSneakDmgMultiplier);
}
void UBaseAttributeSet::OnRep_WeakSpotDmgMultiplier(const FGameplayAttributeData& OldWeakSpotDmgMultiplier)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, WeakSpotDmgMultiplier, OldWeakSpotDmgMultiplier);
}
void UBaseAttributeSet::OnRep_MaxSatiety(const FGameplayAttributeData& OldMaxSatiety)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, MaxSatiety, OldMaxSatiety);
}
void UBaseAttributeSet::OnRep_Satiety(const FGameplayAttributeData& OldSatiety)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, Satiety, OldSatiety);
}
@@ -0,0 +1,135 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseCharacter.h"
#include "InteractionInterface.h"
#include "ItemData.h"
#include "AbilitySystemComponent.h"
// Sets default values
ABaseCharacter::ABaseCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
if (!IsValid(AbilitySystemComponent))
{
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
}
if(!IsValid(InventoryComponent))
{
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));
InventoryComponent->SetIsReplicated(true);
}
}
// Called when the game starts or when spawned
void ABaseCharacter::BeginPlay()
{
Super::BeginPlay();
if (IsLocallyControlled()) {
UAbilitySystemComponent* ASC = GetAbilitySystemComponent();
if (IsValid(ASC))
{
AttributeSet = ASC->GetSet<UBaseAttributeSet>();
}
}
//if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Hello World")); }
}
// Called every frame
void ABaseCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
InteractionLineTrace();
}
// Called to bind functionality to input
void ABaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
UAbilitySystemComponent* ABaseCharacter::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void ABaseCharacter::InteractionLineTrace()
{
FVector Start;
FRotator Direction;
if (!Controller) return;
Controller->GetPlayerViewPoint(Start, Direction);
FVector End = Start + (Direction.Vector() * InteractionDistance);
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
bool bHit = GetWorld()->LineTraceSingleByChannel(
Hit,
Start,
End,
ECC_Visibility,
Params
);
AActor* NewLookedAtActor = bHit ? Hit.GetActor() : nullptr;
if (NewLookedAtActor == LookedAtActor) return;
if (LookedAtActor && LookedAtActor->Implements<UInteractionInterface>())
{
IInteractionInterface::Execute_LookingAt(LookedAtActor, this, false);
}
LookedAtActor = nullptr;
if (NewLookedAtActor && NewLookedAtActor->Implements<UInteractionInterface>())
{
LookedAtActor = NewLookedAtActor;
IInteractionInterface::Execute_LookingAt(LookedAtActor, this, true);
}
}
void ABaseCharacter::InteractWithInteractable_Implementation(AActor* TargetActor)
{
if (!LookedAtActor || !LookedAtActor->Implements<UInteractionInterface>()) return;
IInteractionInterface::Execute_Interact(LookedAtActor, this);
}
void ABaseCharacter::TryPickup_Implementation(AItemActor* ItemActor)
{
if (!HasAuthority()) return;
if (!ItemActor || !InventoryComponent) return;
FItemData ItemToAdd = ItemActor->GetItemData();
FItemData OutRemainingItem = FItemData();
EAddItemResult Result = InventoryComponent->TryAddItem(ItemToAdd, OutRemainingItem);
switch (Result) {
case EAddItemResult::FullyAdded:
ItemActor->Destroy();
return;
case EAddItemResult::PartiallyAdded:
ItemActor->SetItemObject(OutRemainingItem);
return;
default:
if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Couldnt Pick up Item")); }
return;
}
}
@@ -0,0 +1,14 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseCommand.h"
void UBaseCommand::Interrupt_Implementation()
{
OnCommandFinished.Broadcast(false);
}
void UBaseCommand::Execute_Implementation()
{
OnCommandFinished.Broadcast(false);
}
@@ -0,0 +1,128 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseCommands.h"
#include "InteractionInterface.h"
#include "BaseUnit.h"
// UMoveToCommand
void UMoveToCommand::Execute_Implementation()
{
if (!Owner)
{
Complete(false);
return;
}
AIController = Cast<AAIController>(Owner->GetController());
if (!AIController)
{
Complete(false);
return;
}
// Bind to move completed delegate
AIController->ReceiveMoveCompleted.AddDynamic(this, &UMoveToCommand::OnMoveCompleted);
// Request navigation move
FAIMoveRequest MoveRequest;
MoveRequest.SetGoalLocation(Destination);
MoveRequest.SetAcceptanceRadius(AcceptanceRadius);
MoveRequest.SetUsePathfinding(true);
AIController->MoveTo(MoveRequest);
}
void UMoveToCommand::Interrupt_Implementation()
{
if (AIController)
{
AIController->ReceiveMoveCompleted.RemoveDynamic(
this, &UMoveToCommand::OnMoveCompleted);
AIController->StopMovement();
}
}
void UMoveToCommand::OnMoveCompleted(FAIRequestID RequestID, EPathFollowingResult::Type Result)
{
if (AIController)
{
AIController->ReceiveMoveCompleted.RemoveDynamic(
this, &UMoveToCommand::OnMoveCompleted);
}
switch (Result)
{
case EPathFollowingResult::Success:
Complete(true);
break;
default:
Complete(false);
break;
}
}
// UInteractCommand
void UInteractCommand::Execute_Implementation()
{
if(!TargetActor || !TargetActor->Implements<UInteractionInterface>()) { Complete(false); return; }
// Check if already in range
float DistToTarget = FVector::Dist(Owner->GetActorLocation(), TargetActor->GetActorLocation());
if (DistToTarget <= InteractionRange)
{
DoInteract();
return;
}
// Not in range, create move subcommand
SubMoveCommand = NewObject<UMoveToCommand>();
SubMoveCommand->Owner = Owner;
SubMoveCommand->Destination = TargetActor->GetActorLocation();
// Bind to its completion, ... NOT going through the queue
SubMoveCommand->OnCommandFinished.AddDynamic(this, &UInteractCommand::OnMoveFinished);
SubMoveCommand->Execute();
}
void UInteractCommand::Interrupt_Implementation()
{
if (SubMoveCommand)
{
SubMoveCommand->OnCommandFinished.RemoveDynamic(this, &UInteractCommand::OnMoveFinished);
SubMoveCommand->Interrupt();
SubMoveCommand = nullptr;
}
}
void UInteractCommand::DoInteract()
{
IInteractionInterface::Execute_Interact(TargetActor, Owner);
Complete(true);
}
void UInteractCommand::OnMoveFinished(bool bSuccess)
{
if (SubMoveCommand)
{
SubMoveCommand->OnCommandFinished.RemoveDynamic(this, &UInteractCommand::OnMoveFinished);
SubMoveCommand = nullptr;
}
if (!bSuccess || !TargetActor)
{
Complete(false);
return;
}
float DistToTarget = FVector::Dist(Owner->GetActorLocation(), TargetActor->GetActorLocation());
UE_LOG(LogTemp, Error, TEXT("%f %d"), DistToTarget, InteractionRange);
if (DistToTarget > InteractionRange)
{
Complete(false);
return;
}
DoInteract();
}
+162
View File
@@ -0,0 +1,162 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseUnit.h"
#include "Components/CapsuleComponent.h"
#include "TopDownGameState.h"
#include "Net/UnrealNetwork.h"
#include "Commands/CommandComponent.h"
// Sets default values
ABaseUnit::ABaseUnit()
{
PrimaryActorTick.bCanEverTick = false;
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));
InventoryComponent->SetIsReplicated(true);
AttributeSet = CreateDefaultSubobject<UBaseAttributeSet>( TEXT("AttributeSet"));
CommandComponent = CreateDefaultSubobject<UCommandComponent>(TEXT("CommandComponent"));
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_GameTraceChannel2, ECR_Block);
}
// Called when the game starts or when spawned
void ABaseUnit::BeginPlay()
{
Super::BeginPlay();
if (!HasAuthority()) return;
UAbilitySystemComponent* ASC = GetAbilitySystemComponent();
if (!ASC || !AttributeSet) return;
AttributeSet->SetSatiety(100.f);
AttributeSet->SetMaxSatiety(100.f);
// Apply default resource consumption
for (TSubclassOf<UGameplayEffect> EffectClass : PassiveUpkeepEffects)
{
if (!EffectClass) continue;
FGameplayEffectContextHandle Context = ASC->MakeEffectContext();
ASC->ApplyGameplayEffectToSelf(EffectClass->GetDefaultObject<UGameplayEffect>(), 1.f, Context);
}
}
UAbilitySystemComponent* ABaseUnit::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
void ABaseUnit::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ABaseUnit, DisplayName);
DOREPLIFETIME(ABaseUnit, PrimaryFaction);
}
UInventoryComponent* ABaseUnit::GetInventoryComponent_Implementation() const
{
return InventoryComponent;
}
EOutlinerStatus ABaseUnit::GetOutlinerStatus() const
{
return EOutlinerStatus::OK;
}
void ABaseUnit::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// Unregister from GameState when destroyed
if (ATopDownGameState* GS = GetWorld()->GetGameState<ATopDownGameState>())
{
GS->UnregisterUnit(this);
}
}
/*
void ABaseUnit::InteractionLineTrace()
{
FVector Start;
FRotator Direction;
if (!Controller) return;
Controller->GetPlayerViewPoint(Start, Direction);
FVector End = Start + (Direction.Vector() * InteractionDistance);
FHitResult Hit;
FCollisionQueryParams Params;
Params.AddIgnoredActor(this);
bool bHit = GetWorld()->LineTraceSingleByChannel(
Hit,
Start,
End,
ECC_Visibility,
Params
);
AActor* NewLookedAtActor = bHit ? Hit.GetActor() : nullptr;
if (NewLookedAtActor == LookedAtActor) return;
if (LookedAtActor && LookedAtActor->Implements<UInteractionInterface>())
{
IInteractionInterface::Execute_LookingAt(LookedAtActor, this, false);
}
LookedAtActor = nullptr;
if (NewLookedAtActor && NewLookedAtActor->Implements<UInteractionInterface>())
{
LookedAtActor = NewLookedAtActor;
IInteractionInterface::Execute_LookingAt(LookedAtActor, this, true);
}
}
void ABaseUnit::InteractWithInteractable_Implementation(AActor* TargetActor)
{
if (!LookedAtActor || !LookedAtActor->Implements<UInteractionInterface>()) return;
IInteractionInterface::Execute_Interact(LookedAtActor, this);
}
void ABaseUnit::TryPickup_Implementation(AItemActor* ItemActor)
{
if (!HasAuthority()) return;
if (!ItemActor || !InventoryComponent) return;
FItemData ItemToAdd = ItemActor->GetItemData();
FItemData OutRemainingItem = FItemData();
EAddItemResult Result = InventoryComponent->TryAddItem(ItemToAdd, OutRemainingItem);
switch (Result) {
case EAddItemResult::FullyAdded:
ItemActor->Destroy();
return;
case EAddItemResult::PartiallyAdded:
ItemActor->SetItemObject(OutRemainingItem);
return;
default:
if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("Couldnt Pick up Item")); }
return;
}
}
*/
@@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BaseUnitAIController.h"
@@ -0,0 +1,107 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Commands/CommandComponent.h"
#include "BaseCommand.h"
#include "BaseUnit.h"
// Sets default values for this component's properties
UCommandComponent::UCommandComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
// Called when the game starts
void UCommandComponent::BeginPlay()
{
Super::BeginPlay();
OwnerUnit = Cast<ABaseUnit>(GetOwner());
}
void UCommandComponent::GiveCommand(UBaseCommand* NewCommand, bool bQueue)
{
if (!NewCommand) return;
if (!bQueue) ClearCommandQueue();
NewCommand->Owner = OwnerUnit;
CommandQueue.Add(NewCommand);
if (CommandQueue.Num() == 1) ExecuteCommand(CommandQueue[0]);
OnCommandQueueChanged.Broadcast();
}
void UCommandComponent::ExecuteNextCommand(bool bInterrupt)
{
if (CommandQueue.Num() == 0) return;
if (bInterrupt) CommandQueue[0]->Interrupt();
CommandQueue[0]->OnCommandFinished.RemoveDynamic(this, &UCommandComponent::CommandFinished);
CommandQueue.RemoveAt(0);
if (CommandQueue.Num() == 0) return;
ExecuteCommand(CommandQueue[0]);
}
void UCommandComponent::InterruptCurrentCommand()
{
if (CommandQueue.Num() == 0) return;
CommandQueue[0]->OnCommandFinished.RemoveDynamic(this, &UCommandComponent::CommandFinished);
CommandQueue[0]->Interrupt();
CommandQueue.RemoveAt(0);
OnCommandQueueChanged.Broadcast();
}
void UCommandComponent::ClearCommandQueue()
{
InterruptCurrentCommand();
CommandQueue.Empty();
OnCommandQueueChanged.Broadcast();
}
void UCommandComponent::RemoveCommandFromQueue(UBaseCommand* CommandToRemove)
{
if (!CommandToRemove) return;
if (CommandQueue[0] == CommandToRemove)
{
ExecuteNextCommand(true);
}
else
{
CommandQueue.Remove(CommandToRemove);
}
OnCommandQueueChanged.Broadcast();
}
void UCommandComponent::ExecuteCommand(UBaseCommand* Command)
{
Command->OnCommandFinished.AddDynamic(this, &UCommandComponent::CommandFinished);
Command->Execute();
}
void UCommandComponent::CommandFinished(bool bSuccess)
{
if (bSuccess)
{
}
ExecuteNextCommand(false);
OnCommandQueueChanged.Broadcast();
}
const TArray<UBaseCommand*> UCommandComponent::GetUnitCommandQueue()
{
return CommandQueue;
}
bool UCommandComponent::IsExecutingCommad() const
{
return CommandQueue.Num() != 0;
}
@@ -0,0 +1,162 @@
// FactionManager.cpp
#include "FactionManager.h"
#include "Net/UnrealNetwork.h"
UFactionManager::UFactionManager()
{
SetIsReplicatedByDefault(true);
PrimaryComponentTick.bCanEverTick = false;
}
void UFactionManager::BeginPlay()
{
Super::BeginPlay();
if (!GetOwner()->HasAuthority()) return;
InitializeDefaultFactions();
}
void UFactionManager::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UFactionManager, Factions);
}
void UFactionManager::InitializeDefaultFactions()
{
// Wanderer - neutral to everyone by default
FFactionData Wanderer;
Wanderer.FactionID = FactionID::Wanderer;
Wanderer.DisplayName = FText::FromString("Wanderer");
Wanderer.FactionType = EFactionType::Wanderer;
Wanderer.FactionColor = FLinearColor::Gray;
RegisterFaction(Wanderer);
// Feral - hostile to everyone
FFactionData Feral;
Feral.FactionID = FactionID::Feral;
Feral.DisplayName = FText::FromString("Feral");
Feral.FactionType = EFactionType::Hostile;
Feral.FactionColor = FLinearColor::Red;
// Hostile to all registered factions
Feral.RelationsTo.Add(FFactionRelation{ FactionID::Wanderer, -100.f });
Feral.RelationsTo.Add(FFactionRelation{ FactionID::Wildlife, -100.f });
RegisterFaction(Feral);
// Wildlife - neutral unless provoked (no default relations = 0 = neutral)
FFactionData Wildlife;
Wildlife.FactionID = FactionID::Wildlife;
Wildlife.DisplayName = FText::FromString("Wildlife");
Wildlife.FactionType = EFactionType::Wildlife;
Wildlife.FactionColor = FLinearColor::Green;
RegisterFaction(Wildlife);
// NPC Faction A - Survivors
FFactionData Survivors;
Survivors.FactionID = "Survivors";
Survivors.DisplayName = FText::FromString("Survivors");
Survivors.FactionType = EFactionType::Civilized;
Survivors.FactionColor = FLinearColor::Green;
Survivors.RelationsTo.Add(FFactionRelation{ FactionID::Feral, -100 });
RegisterFaction(Survivors);
// NPC Faction B - Raiders
FFactionData Raiders;
Raiders.FactionID = "Raiders";
Raiders.DisplayName = FText::FromString("Raiders");
Raiders.FactionType = EFactionType::Bandit;
Raiders.FactionColor = FLinearColor::Red;
Raiders.RelationsTo.Add(FFactionRelation{ "Survivors", -75 });
RegisterFaction(Raiders);
}
void UFactionManager::RegisterFaction(FFactionData NewFaction)
{
if (NewFaction.FactionID.IsNone()) return;
// Check if already exists - update if so
FFactionData* Existing = GetFaction(NewFaction.FactionID);
if (Existing)
{
*Existing = NewFaction;
}
else
{
Factions.Add(NewFaction);
}
OnFactionChanged.Broadcast(NewFaction.FactionID);
}
FFactionData* UFactionManager::GetFaction(FName InFactionID)
{
return Factions.FindByPredicate([InFactionID](const FFactionData& F)
{
return F.FactionID == InFactionID;
});
}
const FFactionData* UFactionManager::GetFaction(FName InFactionID) const
{
return Factions.FindByPredicate([InFactionID](const FFactionData& F)
{
return F.FactionID == InFactionID;
});
}
FFactionData UFactionManager::GetFactionCopy(FName FactionID) const
{
const FFactionData* Found = GetFaction(FactionID);
return Found ? *Found : FFactionData();
}
bool UFactionManager::FactionExists(FName FactionID) const
{
return GetFaction(FactionID) != nullptr;
}
float UFactionManager::GetRelation(FName FactionA, FName FactionB) const
{
const FFactionData* FA = GetFaction(FactionA);
if (!FA) return 0.f;
return FA->GetRelationTo(FactionB);
}
ERelationState UFactionManager::GetRelationState(FName FactionA, FName FactionB) const
{
const FFactionData* FA = GetFaction(FactionA);
if (!FA) return ERelationState::Neutral;
return FA->GetRelationStateTo(FactionB);
}
void UFactionManager::ModifyRelation(FName FactionA, FName FactionB, float Delta)
{
FFactionData* FA = GetFaction(FactionA);
if (!FA) return;
float& Relation = FA->FindOrAddRelationTo(FactionB);
Relation = FMath::Clamp(Relation + Delta, -100.f, 100.f);
OnRelationChanged.Broadcast(FactionA, FactionB, Relation);
}
bool UFactionManager::AreHostile(FName FactionA, FName FactionB) const
{
return GetRelationState(FactionA, FactionB) == ERelationState::Hostile || GetRelationState(FactionB, FactionA) == ERelationState::Hostile;
}
bool UFactionManager::AreAllied(FName FactionA, FName FactionB) const
{
return GetRelationState(FactionA, FactionB) == ERelationState::Allied && GetRelationState(FactionB, FactionA) == ERelationState::Allied;
}
void UFactionManager::EvaluateFactionNeeds()
{
}
void UFactionManager::OnRep_Factions()
{
// Clients can react to faction data arriving
// UI updates etc
}
@@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "InteractionInterface.h"
@@ -0,0 +1,416 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "InventoryComponent.h"
#include "Net/UnrealNetwork.h"
#include "BaseCharacter.h"
#include "MyGameplayTags.h"
// Sets default values for this component's properties
UInventoryComponent::UInventoryComponent()
{
PrimaryComponentTick.bCanEverTick = false;
SetIsReplicatedByDefault(true);
SetInventorySize(InventorySize);
ItemList.Init(FItemData(), InventorySize);
}
// Called when the game starts
void UInventoryComponent::BeginPlay()
{
Super::BeginPlay();
}
float UInventoryComponent::AddResource(FItemData& ItemData)
{
float AddableAmount = FMath::FloorToInt(GetFreeCapacity() / ItemData.Definition->WeightPerUnit);
float AmountToAdd = FMath::Clamp(ItemData.CurrentStackSize, 0, AddableAmount);
if (AmountToAdd == 0) return ItemData.CurrentStackSize;
float AmountToReturn = ItemData.CurrentStackSize - AmountToAdd;
for (FItemData& Resource : Resources) {
if (Resource.Definition->ID != ItemData.Definition->ID) continue;
Resource.CurrentStackSize += AmountToAdd;
CurrentWeight += AmountToAdd * ItemData.Definition->WeightPerUnit;
OnInventoryChanged.Broadcast();
return AmountToReturn;
}
ItemData.CurrentStackSize = AmountToAdd;
Resources.Add(ItemData);
CurrentWeight += AmountToAdd * ItemData.Definition->WeightPerUnit;
OnInventoryChanged.Broadcast();
return AmountToReturn;
}
bool UInventoryComponent::ConsumeResource(FName ResourceID, float Amount)
{
for (int32 i = 0; i < Resources.Num(); i++)
{
FItemData& Resource = Resources[i];
if (Resource.Definition->ID != ResourceID) continue;
if (Resource.CurrentStackSize < Amount) return false;
Resource.CurrentStackSize -= Amount;
CurrentWeight -= Amount * Resource.Definition->WeightPerUnit;
if (Resource.CurrentStackSize == 0) Resources.RemoveAt(i);
OnInventoryChanged.Broadcast();
return true;
}
return false;
}
float UInventoryComponent::GetResource(FName ResourceID) const
{
for (const FItemData& Resource : Resources) {
if (Resource.Definition->ID == ResourceID) return Resource.CurrentStackSize;
}
return 0.0f;
}
void UInventoryComponent::OnRep_ItemList()
{
OnInventoryChanged.Broadcast();
}
void UInventoryComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UInventoryComponent, ItemList);
DOREPLIFETIME(UInventoryComponent, InventorySize);
}
const FItemData& UInventoryComponent::GetItemAt(int32 SlotIndex) const
{
static const FItemData InvalidItem;
if (!ItemList.IsValidIndex(SlotIndex)) return InvalidItem;
return ItemList[SlotIndex];
}
const TArray<FItemData> UInventoryComponent::GetAllItems() const
{
return ItemList;
}
EAddItemResult UInventoryComponent::TryAddItem(FItemData NewItem, FItemData& OutRemainingItem)
{
int FreeSlotIndex = 0;
bool PartiallyAdded = false;
if (!NewItem.IsValid()) return EAddItemResult::Failed;
if (NewItem.HasTag(Item_Flag_Resource)) {
EAddItemResult Result;
float AmountLeft = AddResource(NewItem);
if (AmountLeft == 0) Result = EAddItemResult::FullyAdded;
else if (AmountLeft != NewItem.CurrentStackSize) Result = EAddItemResult::PartiallyAdded;
else Result = EAddItemResult::Failed;
OutRemainingItem = NewItem;
OutRemainingItem.CurrentStackSize = AmountLeft;
return Result;
}
// Try add Item to existing Stack
for (int SlotIndex = 0; SlotIndex < InventorySize; SlotIndex++) {
switch (AddItemAt(NewItem, SlotIndex, OutRemainingItem, false, false))
{
case EAddItemResult::FullyAdded:
return EAddItemResult::FullyAdded;
case EAddItemResult::PartiallyAdded:
NewItem = OutRemainingItem;
PartiallyAdded = true;
break;
case EAddItemResult::Failed:
break;
}
}
// Get first free Slot and add the Item there
if (GetFirstFreeSlot(FreeSlotIndex)) {
EAddItemResult a = AddItemAt(NewItem, FreeSlotIndex, OutRemainingItem, false, true);
return a;
}
if (PartiallyAdded) return EAddItemResult::PartiallyAdded;
return EAddItemResult::Failed;
}
EAddItemResult UInventoryComponent::AddItemAt(FItemData NewItem, int32 SlotIndex, FItemData& OutRemainingItem, bool AllowSwitch, bool AllowEmptySlots)
{
OutRemainingItem = FItemData();
if (!ItemList.IsValidIndex(SlotIndex)) return EAddItemResult::Failed; // Check if the Index is valid
if (!NewItem.IsValid() || !NewItem.Definition) return EAddItemResult::Failed; // Check if a valid Item was provided
FItemData& ItemSlot = ItemList[SlotIndex];
if (!AllowEmptySlots && !ItemSlot.IsValid()) return EAddItemResult::Failed; // If the Slot is empty and AllowEmptySlots is false the Item cannot be added
// Add the Item to an emtpy slot
if (AllowEmptySlots && !ItemSlot.IsValid()) {
ItemSlot = NewItem;
OnInventoryChanged.Broadcast();
return EAddItemResult::FullyAdded;
}
// Add Item to existing Stack
if (ItemSlot.Definition && ItemSlot.Definition->ID == NewItem.Definition->ID) {
if (ItemSlot.HasTag(Item_Flag_Stackable)) {
if (ItemSlot.CurrentStackSize + NewItem.CurrentStackSize <= ItemSlot.Definition->MaxStackSize) {
ItemSlot.CurrentStackSize += NewItem.CurrentStackSize;
OnInventoryChanged.Broadcast();
return EAddItemResult::FullyAdded;
}
int32 TotalStackSize = ItemSlot.CurrentStackSize + NewItem.CurrentStackSize;
ItemSlot.CurrentStackSize = ItemSlot.Definition->MaxStackSize;
OutRemainingItem = NewItem;
OutRemainingItem.CurrentStackSize = TotalStackSize - ItemSlot.Definition->MaxStackSize;
OnInventoryChanged.Broadcast();
return EAddItemResult::PartiallyAdded;
}
}
// Switch an Item in an Slot if its not a Quest Item
if(AllowSwitch && !ItemSlot.HasTag(Item_Flag_Quest)){
OutRemainingItem = ItemSlot;
ItemSlot = NewItem;
OnInventoryChanged.Broadcast();
return EAddItemResult::Switched;
}
return EAddItemResult::Failed;
}
void UInventoryComponent::SetItemAt(FItemData NewItem, int32 SlotIndex)
{
if (!ItemList.IsValidIndex(SlotIndex)) return;
ItemList[SlotIndex] = NewItem;
OnInventoryChanged.Broadcast();
}
const TArray<FItemData> UInventoryComponent::FindItemsByID(FName ItemID) const
{
TArray<FItemData> FoundItems;
for (FItemData Item : ItemList) {
if (Item.Definition->ID != ItemID) continue;
FoundItems.Add(Item);
}
return FoundItems;
}
bool UInventoryComponent::GetFirstFreeSlot(int32& SlotIndex) const
{
int32 Index = 0;
for (const FItemData Item : ItemList) {
if (!Item.IsValid()) {
SlotIndex = Index;
return true;
}
Index++;
}
return false;
}
void UInventoryComponent::SetInventorySize(int32 NewInventorySize)
{
InventorySize = NewInventorySize;
ItemList.SetNum(InventorySize);
OnInventoryChanged.Broadcast();
}
int32 UInventoryComponent::GetInventorySize() const
{
return InventorySize;
}
void UInventoryComponent::DropItemData_Implementation(FItemData ItemToDrop)
{
if (!ItemToDrop.IsValid()) return;
FTransform LocationToDrop = GetDropTransform();
if (!LocationToDrop.IsValid()) return; // Currently not nececarry, as the output is always Valid unless the DropTransfrom calculation is Valid surly at some point.
DropItem(ItemToDrop, LocationToDrop);
}
void UInventoryComponent::DropItemAtSlot_Implementation(int32 SlotIndex)
{
if (!ItemList.IsValidIndex(SlotIndex)) return;
FItemData ItemToDrop = GetItemAt(SlotIndex);
RemoveItemAt(SlotIndex);
DropItemData(ItemToDrop);
}
FTransform UInventoryComponent::GetDropTransform() const
{
AActor* Owner = GetOwner();
if (!Owner) return FTransform::Identity;
FVector SpawnLocation = Owner->GetActorLocation() + (Owner->GetActorForwardVector() * DropDistance);
return FTransform(Owner->GetActorRotation(), SpawnLocation);
}
bool UInventoryComponent::ContainsItem(FName ItemID, int32 AmountToFind) const
{
int32 AmountFound = 0;
for (FItemData Item : ItemList) {
if (Item.IsValid() && Item.Definition && Item.Definition->ID == ItemID) {
AmountFound += Item.CurrentStackSize;
}
if (AmountFound >= AmountToFind) {
return true;
}
}
return false;
}
bool UInventoryComponent::ContainsItems(const TMap<FName, int32>& RequiredItems) const
{
for (const auto& Pair : RequiredItems) {
if (!ContainsItem(Pair.Key, Pair.Value)) return false;
}
return true;
}
bool UInventoryComponent::RemoveItem(FName ItemID, int32 Amount)
{
if (!ContainsItem(ItemID, Amount))return false;
for (FItemData& Item : ItemList) {
if (!Item.IsValid() || Item.Definition->ID != ItemID) continue;
Item.CurrentStackSize -= Amount;
if (Item.CurrentStackSize <= 0) {
Amount = FMath::Abs(Item.CurrentStackSize);
Item = FItemData();
if (Amount == 0) {
OnInventoryChanged.Broadcast();
return true;
}
}
else {
OnInventoryChanged.Broadcast();
return true;
}
}
OnInventoryChanged.Broadcast();
return true;
}
bool UInventoryComponent::RemoveItems(const TMap<FName, int32>& RequiredItems)
{
if (!ContainsItems(RequiredItems)) return false;
for (const auto& Pair : RequiredItems) {
RemoveItem(Pair.Key, Pair.Value);
}
OnInventoryChanged.Broadcast();
return true;
}
void UInventoryComponent::RemoveItemAt(int32 SlotIndex)
{
if (!ItemList.IsValidIndex(SlotIndex)) return;
ItemList[SlotIndex] = FItemData();
OnInventoryChanged.Broadcast();
}
int32 UInventoryComponent::GetItemAmount(FName ID) const
{
int32 Amount = 0;
for (const FItemData& Item : ItemList) {
if (Item.IsValid() && Item.Definition->ID == ID) Amount += Item.CurrentStackSize;
}
return Amount;
}
void UInventoryComponent::Sort(ESortType SortType)
{
if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("ToDo: Implement Inventory Sorting")); }
switch (SortType) {
case ESortType::Alpabeticaly:
break;
case ESortType::Type:
break;
case ESortType::Weight:
break;
case ESortType::Amount:
break;
}
OnInventoryChanged.Broadcast();
}
void UInventoryComponent::SetMaxCapacity_Implementation(float NewMaxCapacity)
{
MaxCapacity = NewMaxCapacity;
}
void UInventoryComponent::DropItem_Implementation(FItemData ItemToDrop, const FTransform& SpawnTransform)
{
if (!ItemToDrop.IsValid() || !ItemToDrop.Definition) return;
AItemActor* DroppedActor = GetWorld()->SpawnActorDeferred<AItemActor>(
ItemToDrop.Definition->ItemActorClass,
SpawnTransform,
GetOwner()
);
if (!DroppedActor) return;
DroppedActor->SetItemObject(ItemToDrop);
DroppedActor->FinishSpawning(SpawnTransform);
OnInventoryChanged.Broadcast();
return;
}
@@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "InventoryInterface.h"
+103
View File
@@ -0,0 +1,103 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemActor.h"
#include "Net/UnrealNetwork.h"
#include "BaseCharacter.h"
#include "InventoryInterface.h"
// Sets default values
AItemActor::AItemActor()
{
bAlwaysRelevant = true;
bReplicates = true;
SetReplicateMovement(true);
PrimaryActorTick.bCanEverTick = false;
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
RootComponent = MeshComponent;
MeshComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel2, ECR_Block);
}
// Called when the game starts or when spawned
void AItemActor::BeginPlay()
{
Super::BeginPlay();
if (!ItemData.IsValid()) return;
if (!ItemData.Definition || !ItemData.Definition->StaticMesh)
{
UE_LOG(LogTemp, Error, TEXT("Fuck this Item"));
Destroy();
return;
}
MeshComponent->SetStaticMesh(ItemData.Definition->StaticMesh);
}
void AItemActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AItemActor, ItemData);
}
void AItemActor::Interact_Implementation(AActor* Interactor)
{
if (!HasAuthority()) return;
if (!Interactor || !Interactor->Implements<UInventoryInterface>()) return;
UInventoryComponent* Inventory = IInventoryInterface::Execute_GetInventoryComponent(Interactor);
if (!Inventory) return;
FItemData OutRemainingItem;
EAddItemResult Result = Inventory->TryAddItem(ItemData, OutRemainingItem);
switch (Result)
{
case EAddItemResult::FullyAdded:
Destroy();
return;
case EAddItemResult::PartiallyAdded:
SetItemObject(OutRemainingItem);
return;
default:
return;
}
}
void AItemActor::LookingAt_Implementation(AActor* Interactor, bool IsLooking)
{
if (!Interactor) return;
if (!ItemData.IsValid() || !ItemData.Definition) return;
// ToDo
}
void AItemActor::SetItemObject(FItemData NewItem)
{
ItemData = NewItem;
}
void AItemActor::Destroyed()
{
Super::Destroyed();
// ToDo(maybe): Add other cleanupstuff
}
void AItemActor::OnRep_ItemData()
{
if (!ItemData.Definition ||
!ItemData.Definition->StaticMesh)
{
return;
}
MeshComponent->SetStaticMesh(ItemData.Definition->StaticMesh);
}
FItemData AItemActor::GetItemData() const
{
return ItemData;
}
@@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemData.h"
@@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemDefinition.h"
+115
View File
@@ -0,0 +1,115 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ItemObject.h"
#include "MyGameplayTags.h"
#include "Net/UnrealNetwork.h"
UItemObject::UItemObject()
{
}
UItemObject::~UItemObject()
{
}
void UItemObject::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UItemObject, CurrentStackSize);
DOREPLIFETIME(UItemObject, CurrentDurability);
DOREPLIFETIME(UItemObject, Definition);
}
bool UItemObject::HasTag(FGameplayTag Tag) const
{
return Definition->Tags.HasTag(Tag);;
}
int32 UItemObject::AddToStack(int32 Value)
{
if (!HasTag(Item_Flag_Stackable)) {
CurrentStackSize = 1;
return Value;
}
int32 NewTotal = CurrentStackSize + Value;
CurrentStackSize = FMath::Min(CurrentStackSize + Value, Definition->MaxStackSize);
int32 Overflow = FMath::Max(NewTotal - Definition->MaxStackSize, 0);
CurrentStackSize = FMath::Min(NewTotal, Definition->MaxStackSize);
return Overflow;
}
float UItemObject::AddToDurability(float Value)
{
if (!HasTag(Item_Flag_Durable)) {
CurrentDurability = 1;
return Value;
}
float NewTotal = CurrentDurability + Value;
float Overflow = FMath::Max(NewTotal - Definition->MaxDurability, 0.f);
CurrentDurability = FMath::Min(NewTotal, Definition->MaxDurability);
return Overflow;
}
int32 UItemObject::SetStack(int32 NewValue)
{
if (!HasTag(Item_Flag_Stackable))
{
CurrentStackSize = 1;
return NewValue;
}
int32 ClampedValue = FMath::Clamp(NewValue, 0, Definition->MaxStackSize);
int32 Overflow = NewValue - ClampedValue;
CurrentStackSize = ClampedValue;
return FMath::Max(Overflow, 0);
}
float UItemObject::SetDurability(float NewValue)
{
if (!HasTag(Item_Flag_Durable))
{
CurrentDurability = 1;
return NewValue;
}
float ClampedValue = FMath::Clamp(NewValue, 0.f, Definition->MaxDurability);
float Overflow = NewValue - ClampedValue;
CurrentDurability = ClampedValue;
return FMath::Max(Overflow, 0);
}
void UItemObject::SetItemDefinition(UItemDefinition* NewDefinition)
{
if (!NewDefinition) return;
Definition = NewDefinition;
}
TArray<TSubclassOf<UGameplayEffect>> UItemObject::GetGrantedEffects() const
{
return Definition->GrantedEffects;
}
TArray<TSubclassOf<UGameplayAbility>> UItemObject::GetGrantedAbility() const
{
return Definition->GrantedAbility;
}
@@ -0,0 +1,50 @@
// ItemRegistry.cpp
#include "ItemRegistry.h"
void UItemRegistry::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
ItemDataTable = TSoftObjectPtr<UDataTable>(FSoftObjectPath(TEXT("/Game/Inventory/ItemRegisterDataTable.ItemRegisterDataTable")));
LoadTable();
}
void UItemRegistry::LoadTable()
{
if (ItemDataTable.IsNull())
{
UE_LOG(LogTemp, Warning, TEXT("ItemRegistry: No DataTable assigned!"));
return;
}
LoadedTable = ItemDataTable.LoadSynchronous();
if (!LoadedTable)
{
UE_LOG(LogTemp, Error, TEXT("ItemRegistry: Failed to load DataTable!"));
return;
}
UE_LOG(LogTemp, Log, TEXT("ItemRegistry: Loaded %d items"),LoadedTable->GetRowNames().Num());
}
UItemDefinition* UItemRegistry::GetDefinition(FName ItemID) const
{
if (!LoadedTable || ItemID.IsNone()) return nullptr;
const FItemRegistryRow* Row = LoadedTable->FindRow<FItemRegistryRow>(ItemID, TEXT("ItemRegistry::GetDefinition"));
if (!Row || !Row->Definition) return nullptr;
return Row->Definition;
}
bool UItemRegistry::IsValidItem(FName ItemID) const
{
return GetDefinition(ItemID) != nullptr;
}
TArray<FName> UItemRegistry::GetAllItemIDs() const
{
if (!LoadedTable) return {};
return LoadedTable->GetRowNames();
}
@@ -0,0 +1,285 @@
#include "ModuleActor.h"
#include "ZoneActor.h"
#include "InventoryComponent.h"
#include "BaseUnit.h"
#include "Net/UnrealNetwork.h"
#include <ItemData.h>
#include "ItemRegistry.h"
AModuleActor::AModuleActor()
{
bReplicates = true;
PrimaryActorTick.bCanEverTick = false;
}
void AModuleActor::BeginPlay()
{
Super::BeginPlay();
if (!HasAuthority()) return;
// Auto register with DefaultZone if set in editor
if (DefaultZone)
{
ForceCompleteConstruction(DefaultZone);
}
}
void AModuleActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
if (OwningZone)
{
OwningZone->UnregisterModule(this);
}
}
void AModuleActor::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AModuleActor, Definition);
DOREPLIFETIME(AModuleActor, ModuleState);
DOREPLIFETIME(AModuleActor, ConstructionProgress);
DOREPLIFETIME(AModuleActor, OwningZone);
DOREPLIFETIME(AModuleActor, AssignedWorkers);
}
void AModuleActor::AddResourceToZone(UInventoryComponent* ZoneStorage, FName ResourceID, float Amount)
{
UE_LOG(LogTemp, Warning, TEXT("Adding %f of %s to zone storage"), Amount, *ResourceID.ToString());
if (!ZoneStorage) return;
UItemRegistry* Registry = GetGameInstance()->GetSubsystem<UItemRegistry>();
if (!Registry) return;
UItemDefinition* Def = Registry->GetDefinition(ResourceID);
if (!Def)
{
UE_LOG(LogTemp, Warning, TEXT("ModuleActor: Unknown resource ID: %s"), *ResourceID.ToString());
return;
}
FItemData NewItem;
NewItem.Definition = Def;
NewItem.CurrentStackSize = FMath::FloorToInt(Amount);
NewItem.Definition->Tags.AddTag(Item_Flag_Resource);
FItemData OutRemaining;
EAddItemResult Result = ZoneStorage->TryAddItem(NewItem, OutRemaining);
if (Result == EAddItemResult::PartiallyAdded)
{
UE_LOG(LogTemp, Warning, TEXT("Zone storage full, overflow: %d of %s"),
OutRemaining.CurrentStackSize, *ResourceID.ToString());
}
else if (Result == EAddItemResult::Failed)
{
UE_LOG(LogTemp, Warning, TEXT("Zone storage completely full, could not add %s"),
*ResourceID.ToString());
}
}
bool AModuleActor::CanAffordConstruction(UInventoryComponent* PayingInventory) const
{
if (!Definition || !PayingInventory) return false;
for (const FModuleCost& Cost : Definition->ConstructionCost)
{
if (PayingInventory->GetResource(Cost.ResourceID) < Cost.Amount)
return false;
}
return true;
}
bool AModuleActor::StartConstruction(
AZoneActor* Zone, UInventoryComponent* PayingInventory)
{
if (!HasAuthority()) return false;
if (!Definition || !Zone) return false;
if (!CanAffordConstruction(PayingInventory)) return false;
// Deduct construction cost
for (const FModuleCost& Cost : Definition->ConstructionCost)
{
PayingInventory->ConsumeResource(Cost.ResourceID, Cost.Amount);
}
OwningZone = Zone;
Zone->RegisterModule(this);
ModuleState = EModuleState::UnderConstruction;
ConstructionProgress = 0.f;
OnModuleStateChanged.Broadcast(ModuleState);
return true;
}
void AModuleActor::TickProduction(
float GameHoursDelta, UInventoryComponent* ZoneStorage)
{
if (!HasAuthority() || !Definition) return;
switch (ModuleState)
{
case EModuleState::UnderConstruction:
TickConstruction(GameHoursDelta);
break;
case EModuleState::Active:
TickActiveProduction(GameHoursDelta, ZoneStorage);
break;
default:
break;
}
}
void AModuleActor::TickConstruction(float GameHoursDelta)
{
if (Definition->ConstructionTimeHours <= 0.f)
{
ConstructionProgress = 1.f;
}
else
{
ConstructionProgress += GameHoursDelta / Definition->ConstructionTimeHours;
ConstructionProgress = FMath::Clamp(ConstructionProgress, 0.f, 1.f);
}
if (ConstructionProgress >= 1.f)
{
ModuleState = EModuleState::Inactive;
UpdateWorkingState(); // check if we should go Active immediately
OnModuleStateChanged.Broadcast(ModuleState);
}
}
void AModuleActor::TickActiveProduction(float GameHoursDelta, UInventoryComponent* ZoneStorage)
{
UE_LOG(LogTemp, Warning, TEXT("Module %s ticking production"), *GetName());
if (!ZoneStorage) return;
// Produce outputs
for (const FModuleResourceRate& Rate : Definition->ProducedResources)
{
// Worker-required production scales with worker count
float WorkerMultiplier = 1.f;
if (Rate.bRequiresWorkers)
{
if (AssignedWorkers.Num() == 0) continue;
WorkerMultiplier = FMath::Clamp( (float)AssignedWorkers.Num() / Definition->MaxWorkers, 0.f, 1.f);
}
float AmountToAdd = Rate.AmountPerHour * GameHoursDelta * WorkerMultiplier;
AddResourceToZone(ZoneStorage, Rate.ResourceID, AmountToAdd);
}
}
void AModuleActor::UpdateWorkingState()
{
if (!Definition) return;
if (ModuleState == EModuleState::UnderConstruction) return;
if (ModuleState == EModuleState::Destroyed) return;
bool bShouldBeActive = true;
// Check minimum workers
if (Definition->MinWorkersToOperate > 0 &&
AssignedWorkers.Num() < Definition->MinWorkersToOperate)
{
bShouldBeActive = false;
}
EModuleState NewState = bShouldBeActive ?
EModuleState::Active : EModuleState::Inactive;
if (NewState != ModuleState)
{
ModuleState = NewState;
OnModuleStateChanged.Broadcast(ModuleState);
}
}
bool AModuleActor::AssignWorker(ABaseUnit* Unit)
{
if (!Unit) return false;
if (!Definition) return false;
if (AssignedWorkers.Num() >= Definition->MaxWorkers) return false;
if (AssignedWorkers.Contains(Unit)) return false;
AssignedWorkers.Add(Unit);
UpdateWorkingState();
return true;
}
void AModuleActor::RemoveWorker(ABaseUnit* Unit)
{
AssignedWorkers.Remove(Unit);
UpdateWorkingState();
}
bool AModuleActor::CanBePlacedAt(FVector Location, AZoneActor* Zone) const
{
if (!Definition || !Zone) return false;
// Must be inside zone
if (!Zone->IsLocationInsideZone(Location)) return false;
// Check overlap with other modules
for (AModuleActor* Other : Zone->Modules)
{
if (!Other || Other == this) continue;
float Dist = FVector::Dist2D(Location, Other->GetActorLocation());
float MinDist = Definition->PlacementRadius +
(Other->Definition ? Other->Definition->PlacementRadius : 200.f);
if (Dist < MinDist) return false;
}
return true;
}
void AModuleActor::OnRep_Definition()
{
OnDefinitionLoaded();
}
void AModuleActor::OnRep_State()
{
OnStateChanged(ModuleState);
}
void AModuleActor::OnDefinitionLoaded_Implementation()
{
// Blueprint handles visual setup
}
void AModuleActor::OnStateChanged_Implementation(EModuleState NewState)
{
// Blueprint swaps ghost mesh -> real mesh when construction completes
}
EOutlinerStatus AModuleActor::GetOutlinerStatus() const
{
switch (ModuleState)
{
case EModuleState::UnderConstruction: return EOutlinerStatus::Construction;
case EModuleState::Active: return EOutlinerStatus::OK;
case EModuleState::Inactive: return EOutlinerStatus::Inactive;
case EModuleState::Damaged: return EOutlinerStatus::Critical;
case EModuleState::Destroyed: return EOutlinerStatus::Critical;
default: return EOutlinerStatus::Inactive;
}
}
// Testing
// -----------------------------
void AModuleActor::ForceCompleteConstruction(AZoneActor* Zone)
{
if (!HasAuthority()) return;
OwningZone = Zone;
Zone->RegisterModule(this);
ModuleState = EModuleState::Active;
ConstructionProgress = 1.f;
OnModuleStateChanged.Broadcast(ModuleState);
UE_LOG(LogTemp, Warning, TEXT("Module %s force completed"), *GetName());
}
@@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ModuleDefinition.h"
@@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameplayTags.h"
//6.März
//10:15
// Flags
UE_DEFINE_GAMEPLAY_TAG(Item_Flag_Stackable, "Item.Flag.Stackable");
UE_DEFINE_GAMEPLAY_TAG(Item_Flag_Quest, "Item.Flag.Quest");
UE_DEFINE_GAMEPLAY_TAG(Item_Flag_Consumeable, "Item.Flag.Consumeable");
UE_DEFINE_GAMEPLAY_TAG(Item_Flag_Durable, "Item.Flag.Durable");
UE_DEFINE_GAMEPLAY_TAG(Item_Flag_Resource, "Item.Flag.Resource");
// Equipment Slots
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Belt, "Item.Type.Belt");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Hands, "Item.Type.Hands");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Head, "Item.Type.Head");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Legs, "Item.Type.Legs");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Torso, "Item.Type.Torso");
// Weapons
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Weapon_Ranged, "Item.Type.Weapon.Ranged");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Weapon_Melee, "Item.Type.Weapon.Melee");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Weapon_Melee_TwoHanded, "Item.Type.Weapon.Melee.TwoHanded");
UE_DEFINE_GAMEPLAY_TAG(Item_Type_Weapon_Melee_OneHanded, "Item.Type.Weapon.Melee.OneHanded");
@@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "OutlinerTypes.h"
@@ -0,0 +1,21 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "PlaceableItemActor.h"
void APlaceableItemActor::BeginPlay()
{
Super::BeginPlay();
if (!HasAuthority()) return;
if (!DefaultDefinition) { Destroy(); return; }
ItemData = FItemData();
ItemData.SetItemDefinition(DefaultDefinition);
ItemData.SetDurability(DefaultDurability);
ItemData.SetStack(DefaultStackSize);
if (!ItemData.Definition->StaticMesh) { Destroy(); return; }
MeshComponent->SetStaticMesh(ItemData.Definition->StaticMesh);
}
@@ -0,0 +1,9 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "PlayerUnit.h"
EOutlinerStatus APlayerUnit::GetOutlinerStatus() const
{
return EOutlinerStatus::Player;
}
@@ -0,0 +1,34 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ThirdPersonCharacter.h"
// Sets default values
AThirdPersonCharacter::AThirdPersonCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void AThirdPersonCharacter::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AThirdPersonCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AThirdPersonCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
@@ -0,0 +1,212 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ThirdPersonController.h"
#include "InteractionInterface.h"
#include "Net/UnrealNetwork.h"
#include "UnitManagerComponent.h"
#include "PlayerUnit.h"
#include "TopDownGameState.h"
#include "ZoneActor.h"
#include "ModuleActor.h"
#include "Framework/Application/SlateApplication.h"
AThirdPersonController::AThirdPersonController()
{
UnitManager = CreateDefaultSubobject<UUnitManagerComponent>(TEXT("UnitManager"));
bShowMouseCursor = true;
bEnableClickEvents = true;
bEnableMouseOverEvents = true;
PrimaryActorTick.bCanEverTick = true;
}
void AThirdPersonController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AThirdPersonController, PlayerFactionID);
}
void AThirdPersonController::BeginPlay()
{
Super::BeginPlay();
}
void AThirdPersonController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
HandleHover();
}
bool AThirdPersonController::IsMouseOverUI() const
{
ULocalPlayer* LP = GetLocalPlayer();
if (!LP || !LP->ViewportClient) return false;
FVector2D MousePos;
if (!LP->ViewportClient->GetMousePosition(MousePos)) return false;
FWidgetPath WidgetPath = FSlateApplication::Get()
.LocateWindowUnderMouse(
FSlateApplication::Get().GetCursorPos(),
FSlateApplication::Get().GetInteractiveTopLevelWindows(),
false);
if (!WidgetPath.IsValid()) return false;
for (int32 i = WidgetPath.Widgets.Num() - 1; i >= 0; i--)
{
FString Type = WidgetPath.Widgets[i].Widget->GetTypeAsString();
if (Type == TEXT("SViewport") ||
Type == TEXT("SGameLayerManager"))
{
return false; // hit viewport before any UI
}
if (Type == TEXT("SObjectWidget")) // UMG widget
{
return true;
}
}
return false;
}
TArray<AZoneActor*> AThirdPersonController::GetOwnedZones() const
{
ATopDownGameState* GS = GetWorld()->GetGameState<ATopDownGameState>();
if (!GS) return {};
return GS->GetZonesForFaction(PlayerFactionID);
}
void AThirdPersonController::HandleHover()
{
FHitResult Hit;
GetHitResultUnderCursor(ECC_GameTraceChannel2, false, Hit);
AActor* NewHovered = Hit.GetActor();
// Actor changed
if (NewHovered != LastHoveredActor)
{
// Stop looking at old actor
if (LastHoveredActor && LastHoveredActor->Implements<UInteractionInterface>())
{
IInteractionInterface::Execute_LookingAt(LastHoveredActor, GetPawn(), false);
}
// Start looking at new actor
if (NewHovered && NewHovered->Implements<UInteractionInterface>())
{
IInteractionInterface::Execute_LookingAt(NewHovered, GetPawn(), true);
}
LastHoveredActor = NewHovered;
}
HoveredActor = NewHovered;
}
void AThirdPersonController::HandleLeftClick()
{
if (IsMouseOverUI()) return;
FHitResult Hit;
GetHitResultUnderCursor(ECC_GameTraceChannel2, false, Hit);
AActor* ClickedActor = Hit.GetActor();
if (!ClickedActor) return;
// Try to select an Actor
if (SelectClickedActor(ClickedActor)) return;
}
void AThirdPersonController::HandleRightClick()
{
FHitResult Hit;
GetHitResultUnderCursor(ECC_GameTraceChannel2, false, Hit);
AActor* ClickedActor = Hit.GetActor();
if (!ClickedActor) return;
// Issue Commands
if (ClickedActor && ClickedActor->Implements<UInteractionInterface>())
{
UnitManager->IssueInteractCommand(ClickedActor, bShiftHeld);
}
else
{
UnitManager->IssueMoveCommand(Hit.Location, bShiftHeld);
}
}
bool AThirdPersonController::SelectClickedActor(AActor* ClickedActor)
{
if (ABaseUnit* ClickedUnit = Cast<ABaseUnit>(ClickedActor))
{
SelectUnit(ClickedUnit, bShiftHeld);
return true;
}
if (AZoneActor* ClickedZone = Cast<AZoneActor>(ClickedActor))
{
SelectZone(ClickedZone, bShiftHeld);
return true;
}
if (AModuleActor* ClickedModule = Cast<AModuleActor>(ClickedActor))
{
SelectZone(ClickedModule->OwningZone, bShiftHeld);
return true;
}
ClearUnitSelection();
return false;
}
void AThirdPersonController::SelectUnit(ABaseUnit* Unit, bool bAddToSelection)
{
if (!Unit) return;
if (!bAddToSelection) ClearMySelection();
if (!SelectedUnits.Contains(Unit))
{
SelectedUnits.Add(Unit);
UnitManager->AddUnitToSquad(Unit);
}
OnSelectionChanged.Broadcast();
}
void AThirdPersonController::ClearUnitSelection()
{
SelectedUnits.Empty();
UnitManager->ClearSquad();
OnSelectionChanged.Broadcast();
}
void AThirdPersonController::BoxSelect(TArray<ABaseUnit*> UnitsInBox)
{
ClearUnitSelection();
for (ABaseUnit* Unit : UnitsInBox)
{
if (IsValid(Unit)) SelectedUnits.Add(Unit);
}
OnSelectionChanged.Broadcast();
}
void AThirdPersonController::SelectZone(AZoneActor* Zone, bool bAddToSelection)
{
if (!Zone) return;
if (!bAddToSelection) ClearMySelection();
if (!SelectedZones.Contains(Zone)) SelectedZones.Add(Zone);
OnSelectionChanged.Broadcast();
}
void AThirdPersonController::ClearZoneSelection()
{
SelectedZones.Empty();
OnSelectionChanged.Broadcast();
}
void AThirdPersonController::ClearMySelection()
{
ClearUnitSelection();
ClearZoneSelection();
}
@@ -0,0 +1,73 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TopDownGameMode.h"
#include "ThirdPersonController.h"
#include "BaseUnit.h"
#include "TopDownGameState.h"
#include "FactionManager.h"
#include "PlayerUnit.h"
#include "UnitManagerComponent.h"
ABaseUnit* ATopDownGameMode::SpawnUnit(TSubclassOf<ABaseUnit> UnitClass, FTransform SpawnTransform, FName FactionID)
{
if (!UnitClass) return nullptr;
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
ABaseUnit* Unit = GetWorld()->SpawnActor<ABaseUnit>(UnitClass, SpawnTransform, Params);
if (!Unit) return nullptr;
if (!FactionID.IsNone()) Unit->PrimaryFaction = FactionID;
// Register with GameState
ATopDownGameState* CurrentGameState = GetGameState<ATopDownGameState>();
if (CurrentGameState) CurrentGameState->RegisterUnit(Unit);
return Unit;
}
void ATopDownGameMode::BeginPlay()
{
Super::BeginPlay();
}
void ATopDownGameMode::PostLogin(APlayerController* NewPlayer)
{
Super::PostLogin(NewPlayer);
ATopDownGameState* GS = GetGameState<ATopDownGameState>();
AThirdPersonController* PC = Cast<AThirdPersonController>(NewPlayer);
if (!GS || !GS->FactionManager || !PC) return;
// Create a unique faction for this player
FFactionData PlayerFaction;
PlayerFaction.FactionID = FName(*FString::Printf(TEXT("Player_%d"), GS->FactionManager->Factions.Num()));
PlayerFaction.DisplayName = FText::FromString("Player Faction");
PlayerFaction.FactionType = EFactionType::PlayerFaction;
PlayerFaction.FactionColor = FLinearColor::Blue;
PlayerFaction.RelationsTo.Add(FFactionRelation{ FactionID::Feral, -100 });
GS->FactionManager->RegisterFaction(PlayerFaction);
// Store faction ID on the controller
PC->PlayerFactionID = PlayerFaction.FactionID;
// Spawn PlayerUnit near the controller's pawn
FVector SpawnLocation = PC->GetPawn() ? PC->GetPawn()->GetActorLocation() : FVector::ZeroVector;
FTransform SpawnTransform(FRotator::ZeroRotator, SpawnLocation);
ABaseUnit* SpawnedUnit = SpawnUnit(DefaultPlayerUnitClass, SpawnTransform, PlayerFaction.FactionID);
APlayerUnit* PlayerUnit = Cast<APlayerUnit>(SpawnedUnit);
if (PlayerUnit)
{
PC->PlayerUnit = PlayerUnit;
PC->UnitManager->AddUnitToSquad(PlayerUnit);
PlayerUnit->DisplayName = FText::FromString("Player");
}
GS->OnWorldStateChanged.Broadcast();
}
@@ -0,0 +1,194 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TopDownGameState.h"
#include "FactionManager.h"
#include "Net/UnrealNetwork.h"
#include "BaseUnit.h"
#include "ZoneActor.h"
#include "ThirdPersonController.h"
ATopDownGameState::ATopDownGameState()
{
FactionManager = CreateDefaultSubobject<UFactionManager>(TEXT("FactionManager"));
}
void ATopDownGameState::BroadcastWorldStateChanged()
{
OnWorldStateChanged.Broadcast();
if (HasAuthority())
{
WorldStateVersion++;
}
}
void ATopDownGameState::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
OnWorldStateChanged.Clear();
}
void ATopDownGameState::BeginPlay()
{
Super::BeginPlay();
if (!HasAuthority()) return; // only server runs the timer
GetWorldTimerManager().SetTimer(
GameTickTimerHandle,
this,
&ATopDownGameState::OnGameTick,
RealSecondsPerGameHour,
true // loop
);
}
void ATopDownGameState::OnRep_GameTime()
{
// Clients update their UI when time replicates
OnGameTimeChanged.Broadcast(GameHour, GameDay);
}
void ATopDownGameState::OnRep_WorldStateVersion()
{
OnWorldStateChanged.Broadcast();
}
void ATopDownGameState::TickAllUnits(float GameHoursDelta)
{
for (ABaseUnit* Unit : AllUnits)
{
if (!IsValid(Unit)) continue;
TickUnitNeeds(Unit, GameHoursDelta);
}
}
void ATopDownGameState::TickUnitNeeds(ABaseUnit* Unit, float GameHoursDelta)
{
UAbilitySystemComponent* ASC = Unit->GetAbilitySystemComponent();
if (!ASC) return;
const UBaseAttributeSet* AS = ASC->GetSet<UBaseAttributeSet>();
if (!AS) return;
}
AZoneActor* ATopDownGameState::GetZoneAtLocation(FVector Location) const
{
for (AZoneActor* Zone : AllZones)
{
if (IsValid(Zone) && Zone->IsLocationInsideZone(Location))
{
return Zone;
}
}
return nullptr;
}
FText ATopDownGameState::GetFormattedTime() const
{
int32 Hour = FMath::FloorToInt(GameHour);
int32 Minute = FMath::FloorToInt((GameHour - Hour) * 60.f);
return FText::FromString(FString::Printf(TEXT("%02d:%02d"), Hour, Minute));
}
bool ATopDownGameState::IsNightTime() const
{
return GameHour < 6.f || GameHour >= 20.f;
}
void ATopDownGameState::RegisterUnit(ABaseUnit* Unit)
{
if (!Unit || AllUnits.Contains(Unit)) return;
AllUnits.Add(Unit);
OnWorldStateChanged.Broadcast();
WorldStateVersion++;
}
void ATopDownGameState::UnregisterUnit(ABaseUnit* Unit)
{
AllUnits.Remove(Unit);
OnWorldStateChanged.Broadcast();
WorldStateVersion++;
}
void ATopDownGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ATopDownGameState, GameHour);
DOREPLIFETIME(ATopDownGameState, GameDay);
DOREPLIFETIME(ATopDownGameState, AllUnits);
DOREPLIFETIME(ATopDownGameState, AllZones);
DOREPLIFETIME(ATopDownGameState, WorldStateVersion);
}
void ATopDownGameState::OnGameTick()
{
UE_LOG(LogTemp, Warning, TEXT("GameTick Hour %f"), GameHour);
GameHour += 1.f;
if (GameHour >= 24.f) { GameHour = 0.f; GameDay++; }
OnGameTimeChanged.Broadcast(GameHour, GameDay);
// Tick zones first (they supply units)
for (AZoneActor* Zone : AllZones)
{
if (IsValid(Zone)) Zone->TickZone(1.f);
}
// Then tick unit needs (consumes from own inventory if zone didn't supply)
TickAllUnits(1.f);
if (FactionManager) FactionManager->EvaluateFactionNeeds();
}
void ATopDownGameState::RegisterZone(AZoneActor* Zone)
{
if (!Zone || AllZones.Contains(Zone)) return;
AllZones.Add(Zone);
OnWorldStateChanged.Broadcast();
WorldStateVersion++;
}
void ATopDownGameState::UnregisterZone(AZoneActor* Zone)
{
AllZones.Remove(Zone);
OnWorldStateChanged.Broadcast();
WorldStateVersion++;
}
TArray<FFactionData> ATopDownGameState::GetAllFactions() const
{
if (!FactionManager) return {};
return FactionManager->Factions;
}
float ATopDownGameState::GetPlayerMoney() const
{
AThirdPersonController* PC = Cast<AThirdPersonController>(GetWorld()->GetFirstPlayerController());
if (!PC || !FactionManager) return 0.f;
FFactionData* Faction = FactionManager->GetFaction(PC->PlayerFactionID);
return Faction ? Faction->Money : 0.f;
}
TArray<AZoneActor*> ATopDownGameState::GetZonesForFaction(FName FactionID) const
{
return AllZones.FilterByPredicate([FactionID](const AZoneActor* Zone)
{
return IsValid(Zone) && Zone->OwningFaction == FactionID;
});
}
TArray<ABaseUnit*> ATopDownGameState::GetUnitsForFaction(FName FactionID) const
{
return AllUnits.FilterByPredicate([FactionID](const ABaseUnit* Unit)
{
return IsValid(Unit) && Unit->PrimaryFaction == FactionID;
});
}
@@ -0,0 +1,95 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "UnitManagerComponent.h"
#include "BaseUnit.h"
#include "BaseCommands.h"
#include "Commands/CommandComponent.h"
UUnitManagerComponent::UUnitManagerComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
ABaseUnit* UUnitManagerComponent::GetFreeUnit()
{
for (ABaseUnit* Unit : Squad)
{
if (!Unit->CommandComponent->IsExecutingCommad()) return Unit;
}
return nullptr;
}
void UUnitManagerComponent::IssueCommandToUnit(UBaseCommand* Command, ABaseUnit* Unit)
{
if (!Command || !Unit) return;
Unit->CommandComponent->GiveCommand(Command);
}
void UUnitManagerComponent::IssueCommandToUnits(UBaseCommand* Command, TArray<ABaseUnit*> Units)
{
if (!Command) return;
for (ABaseUnit* Unit : Units)
{
if (!Unit) return;
Unit->CommandComponent->GiveCommand(Command);
}
}
void UUnitManagerComponent::IssueCommandToSquad(UBaseCommand* Command)
{
if (!Command) return;
for (ABaseUnit* Unit : Squad)
{
if (!Unit) return;
Unit->CommandComponent->GiveCommand(Command);
}
}
void UUnitManagerComponent::IssueInteractCommand(AActor* TargetActor, bool bQueue)
{
ABaseUnit* Unit = GetFreeUnit();
if (!Unit) return;
UInteractCommand* Command = NewObject<UInteractCommand>(Unit);
Command->TargetActor = TargetActor;
Unit->CommandComponent->GiveCommand(Command, bQueue);
}
void UUnitManagerComponent::IssueMoveCommand(FVector Location, bool bQueue)
{
for (ABaseUnit* Unit : Squad)
{
if (!Unit) return;
UMoveToCommand* Command = NewObject<UMoveToCommand>(Unit);
Command->Destination = Location;
Unit->CommandComponent->GiveCommand(Command, bQueue);
}
}
void UUnitManagerComponent::AddUnitToSquad(ABaseUnit* Unit)
{
if (!Unit || Squad.Contains(Unit)) return;
Squad.Add(Unit);
}
void UUnitManagerComponent::RemoveUnitFromSquad(ABaseUnit* Unit)
{
if (!Unit || !Squad.Contains(Unit)) return;
Squad.Remove(Unit);
}
void UUnitManagerComponent::ClearSquad()
{
Squad.Empty();
}
TArray<ABaseUnit*> UUnitManagerComponent::GetSquad()
{
return Squad;
}
+300
View File
@@ -0,0 +1,300 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ZoneActor.h"
#include "Components/SphereComponent.h"
#include "InventoryComponent.h"
#include "ModuleActor.h"
#include "TopDownGameState.h"
#include "FactionManager.h"
#include "Net/UnrealNetwork.h"
#include "BaseUnit.h"
AZoneActor::AZoneActor()
{
bReplicates = true;
PrimaryActorTick.bCanEverTick = false; // GameState drives ticking
// Use sphere component as the zone volume - invisible at runtime
ZoneVolume = CreateDefaultSubobject<USphereComponent>(TEXT("ZoneVolume"));
ZoneVolume->SetSphereRadius(Radius);
ZoneVolume->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
ZoneVolume->SetCollisionResponseToAllChannels(ECR_Ignore);
ZoneVolume->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap);
ZoneVolume->SetVisibility(false);
ZoneVolume->SetHiddenInGame(true);
RootComponent = ZoneVolume;
ZoneStorage = CreateDefaultSubobject<UInventoryComponent>(TEXT("ZoneStorage"));
ZoneStorage->SetIsReplicated(true);
// Zones get much larger storage than units
ZoneStorage->SetMaxCapacity(10000.f);
}
void AZoneActor::BeginPlay()
{
Super::BeginPlay();
if (!HasAuthority()) return;
// Generate unique zone ID if not set
if (ZoneID.IsNone())
{
ZoneID = FName(*FString::Printf(TEXT("Zone_%s"),
*FGuid::NewGuid().ToString().Left(8)));
}
// Debug draw zone boundary
DrawDebugCylinder(
GetWorld(),
GetActorLocation() - FVector(0, 0, 100),
GetActorLocation() + FVector(0, 0, 100),
Radius,
32,
FColor::Green,
true, // persistent
-1.f,
0,
5.f
);
// Register with GameState
if (ATopDownGameState* GS = GetWorld()->GetGameState<ATopDownGameState>())
{
GS->RegisterZone(this);
GS->BroadcastWorldStateChanged();
}
}
void AZoneActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
if (ATopDownGameState* GS = GetWorld()->GetGameState<ATopDownGameState>())
{
GS->UnregisterZone(this);
}
}
void AZoneActor::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AZoneActor, ZoneID);
DOREPLIFETIME(AZoneActor, ZoneName);
DOREPLIFETIME(AZoneActor, OwningFaction);
DOREPLIFETIME(AZoneActor, Radius);
DOREPLIFETIME(AZoneActor, Modules);
DOREPLIFETIME(AZoneActor, FactionInfluences);
}
bool AZoneActor::IsLocationInsideZone(FVector Location) const
{
float DistXY = FVector2D::Distance(
FVector2D(GetActorLocation()),
FVector2D(Location));
return DistXY <= Radius;
}
void AZoneActor::SetRadius(float NewRadius)
{
if (!HasAuthority()) return;
Radius = FMath::Clamp(NewRadius, 100.f, 50000.f);
UpdateVolumeSize();
}
void AZoneActor::UpdateVolumeSize()
{
if (USphereComponent* Sphere = Cast<USphereComponent>(ZoneVolume))
{
Sphere->SetSphereRadius(Radius);
}
}
void AZoneActor::OnRep_Radius()
{
UpdateVolumeSize();
}
void AZoneActor::SetOwningFaction(FName NewFaction)
{
if (!HasAuthority()) return;
OwningFaction = NewFaction;
OnZoneChanged.Broadcast(ZoneID);
}
void AZoneActor::ModifyInfluence(FName FactionID, float Delta)
{
if (!HasAuthority()) return;
FFactionInfluence* Found = FactionInfluences.FindByPredicate(
[FactionID](const FFactionInfluence& F)
{
return F.FactionID == FactionID;
});
if (Found)
{
Found->Influence = FMath::Clamp(Found->Influence + Delta, 0.f, 100.f);
}
else
{
FFactionInfluence NewInfluence;
NewInfluence.FactionID = FactionID;
NewInfluence.Influence = FMath::Clamp(Delta, 0.f, 100.f);
FactionInfluences.Add(NewInfluence);
}
// Check if ownership should change
FName Dominant = GetDominantFaction();
if (Dominant != OwningFaction && !Dominant.IsNone())
{
SetOwningFaction(Dominant);
}
OnZoneChanged.Broadcast(ZoneID);
}
FName AZoneActor::GetDominantFaction() const
{
FName Dominant;
float Highest = 0.f;
for (const FFactionInfluence& Influence : FactionInfluences)
{
if (Influence.Influence > Highest)
{
Highest = Influence.Influence;
Dominant = Influence.FactionID;
}
}
return Dominant;
}
bool AZoneActor::CanFactionBuildHere(FName FactionID, float Reputation) const
{
// Owning faction can always build
if (FactionID == OwningFaction) return true;
// No owner - anyone can build
if (OwningFaction.IsNone()) return true;
// Other factions need reputation threshold
// TODO: make threshold configurable per zone
const float ReputationThreshold = 50.f;
return Reputation >= ReputationThreshold;
}
void AZoneActor::RegisterModule(AModuleActor* Module)
{
if (!Module || Modules.Contains(Module)) return;
Modules.Add(Module);
OnZoneChanged.Broadcast(ZoneID);
}
void AZoneActor::UnregisterModule(AModuleActor* Module)
{
Modules.Remove(Module);
OnZoneChanged.Broadcast(ZoneID);
}
// Called by GameState each game hour
void AZoneActor::TickZone(float GameHoursDelta)
{
if (!HasAuthority()) return;
TickModuleProduction(GameHoursDelta);
TickZoneInfluence(GameHoursDelta);
SupplyUnitsInZone(GameHoursDelta);
}
void AZoneActor::TickModuleProduction(float GameHoursDelta)
{
UE_LOG(LogTemp, Warning, TEXT("Zone %s ticking %d modules"),
*ZoneID.ToString(), Modules.Num());
for (AModuleActor* Module : Modules)
{
if (!IsValid(Module)) continue;
Module->TickProduction(GameHoursDelta, ZoneStorage);
}
}
void AZoneActor::TickZoneInfluence(float GameHoursDelta)
{
// Owning faction passively gains influence over time
if (!OwningFaction.IsNone())
{
ModifyInfluence(OwningFaction, 1.f * GameHoursDelta);
}
// All other factions slowly lose influence if they have no presence
// TODO: factor in military units in zone
}
void AZoneActor::SupplyUnitsInZone(float GameHoursDelta)
{
// Find all units inside this zone and supply their upkeep from zone storage
// This replaces the GameState consuming from unit inventory directly
if (!ZoneStorage) return;
TArray<AActor*> OverlappingActors;
ZoneVolume->GetOverlappingActors(OverlappingActors, ABaseUnit::StaticClass());
for (AActor* Actor : OverlappingActors)
{
ABaseUnit* Unit = Cast<ABaseUnit>(Actor);
if (!Unit) continue;
// ToDo
}
}
float AZoneActor::GetPriceForFaction(FName ResourceID, FName FactionID, float Reputation) const
{
const FResourcePrice* Price = ResourcePrices.FindByPredicate(
[ResourceID](const FResourcePrice& P)
{
return P.ResourceID == ResourceID;
});
if (!Price) return 0.f; // free if no price set
// Map reputation -100 to 100 -> price multiplier 2.0 to 0.5
float Multiplier = FMath::GetMappedRangeValueClamped(
FVector2D(-100.f, 100.f),
FVector2D(2.f, 0.5f),
Reputation
);
return Price->BasePrice * Multiplier;
}
void AZoneActor::OnRep_ZoneData()
{
OnZoneChanged.Broadcast(ZoneID);
}
bool AZoneActor::CanFactionUseResources(FName FactionID, float Reputation) const
{
// Owning faction always has access
if (FactionID == OwningFaction) return true;
// No owner - open to all
if (OwningFaction.IsNone()) return true;
// Others need minimum reputation
// Hostile factions can't use resources
return Reputation >= -25.f; // above hostile threshold
}
UInventoryComponent* AZoneActor::GetInventoryComponent_Implementation() const
{
return ZoneStorage;
}
EOutlinerStatus AZoneActor::GetOutlinerStatus() const
{
if (Modules.Num() == 0) return EOutlinerStatus::Warning;
// Check if any modules are critical
for (AModuleActor* Module : Modules)
{
if (IsValid(Module) && Module->ModuleState == EModuleState::Damaged)
return EOutlinerStatus::Critical;
}
return EOutlinerStatus::OK;
}
@@ -0,0 +1,26 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AModuleActor.generated.h"
UCLASS()
class MAYBETHISTIME_API AAModuleActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AAModuleActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
@@ -0,0 +1,170 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "BaseAttributeSet.generated.h"
/**
*
*/
UCLASS()
class MAYBETHISTIME_API UBaseAttributeSet : public UAttributeSet
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MaxHealth) FGameplayAttributeData MaxHealth;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Health) FGameplayAttributeData Health;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_HealthRegen) FGameplayAttributeData HealthRegen;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MaxStamina) FGameplayAttributeData MaxStamina;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Stamina) FGameplayAttributeData Stamina;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_StaminaRegen) FGameplayAttributeData StaminaRegen;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MaxMana) FGameplayAttributeData MaxMana;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Mana) FGameplayAttributeData Mana;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_ManaRegen) FGameplayAttributeData ManaRegen;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Armor) FGameplayAttributeData Armor;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_ElementalArmor) FGameplayAttributeData ElementalArmor;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_CritChance) FGameplayAttributeData CritChance;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_CritaDamage) FGameplayAttributeData CritaDamage;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MoveSpeed) FGameplayAttributeData MoveSpeed;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_SprintSpeedMultiplier) FGameplayAttributeData SprintSpeedMultiplier;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_SneakSpeedMultiplier) FGameplayAttributeData SneakSpeedMultiplier;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_SneakDmgMultiplier) FGameplayAttributeData SneakDmgMultiplier;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_WeakSpotDmgMultiplier) FGameplayAttributeData WeakSpotDmgMultiplier;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_MaxSatiety) FGameplayAttributeData MaxSatiety;
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Satiety) FGameplayAttributeData Satiety;
public:
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
virtual void PostGameplayEffectExecute( const FGameplayEffectModCallbackData& Data) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION() virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
UFUNCTION() virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
UFUNCTION() virtual void OnRep_HealthRegen(const FGameplayAttributeData& OldHealthRegen);
UFUNCTION() virtual void OnRep_MaxStamina(const FGameplayAttributeData& OldMaxStamina);
UFUNCTION() virtual void OnRep_Stamina(const FGameplayAttributeData& OldStamina);
UFUNCTION() virtual void OnRep_StaminaRegen(const FGameplayAttributeData& OldStaminaRegen);
UFUNCTION() virtual void OnRep_MaxMana(const FGameplayAttributeData& OldMaxMana);
UFUNCTION() virtual void OnRep_Mana(const FGameplayAttributeData& OldMana);
UFUNCTION() virtual void OnRep_ManaRegen(const FGameplayAttributeData& OldManaRegen);
UFUNCTION() virtual void OnRep_Armor(const FGameplayAttributeData& OldArmor);
UFUNCTION() virtual void OnRep_ElementalArmor(const FGameplayAttributeData& OldElementalArmor);
UFUNCTION() virtual void OnRep_CritChance(const FGameplayAttributeData& OldCritChance);
UFUNCTION() virtual void OnRep_CritaDamage(const FGameplayAttributeData& OldCritaDamage);
UFUNCTION() virtual void OnRep_MoveSpeed(const FGameplayAttributeData& OldMoveSpeed);
UFUNCTION() virtual void OnRep_SprintSpeedMultiplier(const FGameplayAttributeData& OldSprintSpeedMultiplier);
UFUNCTION() virtual void OnRep_SneakSpeedMultiplier(const FGameplayAttributeData& OldSneakSpeedMultiplier);
UFUNCTION() virtual void OnRep_SneakDmgMultiplier(const FGameplayAttributeData& OldSneakDmgMultiplier);
UFUNCTION() virtual void OnRep_WeakSpotDmgMultiplier(const FGameplayAttributeData& OldWeakSpotDmgMultiplier);
UFUNCTION() virtual void OnRep_MaxSatiety(const FGameplayAttributeData& OldMaxSatiety);
UFUNCTION() virtual void OnRep_Satiety(const FGameplayAttributeData& OldSatiety);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, MaxHealth);
GAMEPLAYATTRIBUTE_VALUE_GETTER(MaxHealth);
GAMEPLAYATTRIBUTE_VALUE_SETTER(MaxHealth);
GAMEPLAYATTRIBUTE_VALUE_INITTER(MaxHealth);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, Health);
GAMEPLAYATTRIBUTE_VALUE_GETTER(Health);
GAMEPLAYATTRIBUTE_VALUE_SETTER(Health);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Health);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, MaxStamina);
GAMEPLAYATTRIBUTE_VALUE_GETTER(MaxStamina);
GAMEPLAYATTRIBUTE_VALUE_SETTER(MaxStamina);
GAMEPLAYATTRIBUTE_VALUE_INITTER(MaxStamina);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, Stamina);
GAMEPLAYATTRIBUTE_VALUE_GETTER(Stamina);
GAMEPLAYATTRIBUTE_VALUE_SETTER(Stamina);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Stamina);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, MaxMana);
GAMEPLAYATTRIBUTE_VALUE_GETTER(MaxMana);
GAMEPLAYATTRIBUTE_VALUE_SETTER(MaxMana);
GAMEPLAYATTRIBUTE_VALUE_INITTER(MaxMana);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, Mana);
GAMEPLAYATTRIBUTE_VALUE_GETTER(Mana);
GAMEPLAYATTRIBUTE_VALUE_SETTER(Mana);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Mana);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, Armor);
GAMEPLAYATTRIBUTE_VALUE_GETTER(Armor);
GAMEPLAYATTRIBUTE_VALUE_SETTER(Armor);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Armor);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, ElementalArmor);
GAMEPLAYATTRIBUTE_VALUE_GETTER(ElementalArmor);
GAMEPLAYATTRIBUTE_VALUE_SETTER(ElementalArmor);
GAMEPLAYATTRIBUTE_VALUE_INITTER(ElementalArmor);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, CritChance);
GAMEPLAYATTRIBUTE_VALUE_GETTER(CritChance);
GAMEPLAYATTRIBUTE_VALUE_SETTER(CritChance);
GAMEPLAYATTRIBUTE_VALUE_INITTER(CritChance);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, CritaDamage);
GAMEPLAYATTRIBUTE_VALUE_GETTER(CritaDamage);
GAMEPLAYATTRIBUTE_VALUE_SETTER(CritaDamage);
GAMEPLAYATTRIBUTE_VALUE_INITTER(CritaDamage);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, MoveSpeed);
GAMEPLAYATTRIBUTE_VALUE_GETTER(MoveSpeed);
GAMEPLAYATTRIBUTE_VALUE_SETTER(MoveSpeed);
GAMEPLAYATTRIBUTE_VALUE_INITTER(MoveSpeed);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, SprintSpeedMultiplier);
GAMEPLAYATTRIBUTE_VALUE_GETTER(SprintSpeedMultiplier);
GAMEPLAYATTRIBUTE_VALUE_SETTER(SprintSpeedMultiplier);
GAMEPLAYATTRIBUTE_VALUE_INITTER(SprintSpeedMultiplier);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, SneakSpeedMultiplier);
GAMEPLAYATTRIBUTE_VALUE_GETTER(SneakSpeedMultiplier);
GAMEPLAYATTRIBUTE_VALUE_SETTER(SneakSpeedMultiplier);
GAMEPLAYATTRIBUTE_VALUE_INITTER(SneakSpeedMultiplier);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, HealthRegen);
GAMEPLAYATTRIBUTE_VALUE_GETTER(HealthRegen);
GAMEPLAYATTRIBUTE_VALUE_SETTER(HealthRegen);
GAMEPLAYATTRIBUTE_VALUE_INITTER(HealthRegen);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, StaminaRegen);
GAMEPLAYATTRIBUTE_VALUE_GETTER(StaminaRegen);
GAMEPLAYATTRIBUTE_VALUE_SETTER(StaminaRegen);
GAMEPLAYATTRIBUTE_VALUE_INITTER(StaminaRegen);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, ManaRegen);
GAMEPLAYATTRIBUTE_VALUE_GETTER(ManaRegen);
GAMEPLAYATTRIBUTE_VALUE_SETTER(ManaRegen);
GAMEPLAYATTRIBUTE_VALUE_INITTER(ManaRegen);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, SneakDmgMultiplier);
GAMEPLAYATTRIBUTE_VALUE_GETTER(SneakDmgMultiplier);
GAMEPLAYATTRIBUTE_VALUE_SETTER(SneakDmgMultiplier);
GAMEPLAYATTRIBUTE_VALUE_INITTER(SneakDmgMultiplier);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, WeakSpotDmgMultiplier);
GAMEPLAYATTRIBUTE_VALUE_GETTER(WeakSpotDmgMultiplier);
GAMEPLAYATTRIBUTE_VALUE_SETTER(WeakSpotDmgMultiplier);
GAMEPLAYATTRIBUTE_VALUE_INITTER(WeakSpotDmgMultiplier);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, MaxSatiety);
GAMEPLAYATTRIBUTE_VALUE_GETTER(MaxSatiety);
GAMEPLAYATTRIBUTE_VALUE_SETTER(MaxSatiety);
GAMEPLAYATTRIBUTE_VALUE_INITTER(MaxSatiety);
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UBaseAttributeSet, Satiety);
GAMEPLAYATTRIBUTE_VALUE_GETTER(Satiety);
GAMEPLAYATTRIBUTE_VALUE_SETTER(Satiety);
GAMEPLAYATTRIBUTE_VALUE_INITTER(Satiety);
};
@@ -0,0 +1,68 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BaseAttributeSet.h"
#include "AbilitySystemInterface.h"
#include "ItemActor.h"
#include "InventoryComponent.h"
#include "BaseCharacter.generated.h"
UCLASS(BlueprintType)
class MAYBETHISTIME_API ABaseCharacter : public ACharacter, public IAbilitySystemInterface, public IInteractionInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ABaseCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
//~ Begin IAbilitySystemInterface
/** Returns our Ability System Component. */
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
//~ End IAbilitySystemInterface
/** Ability System Component. Required to use Gameplay Attributes and Gameplay Abilities. */
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities")
UAbilitySystemComponent* AbilitySystemComponent;
/** Sample Attribute Set. */
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly)
const UBaseAttributeSet* AttributeSet;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities")
UInventoryComponent* InventoryComponent;
//UFUNCTION(BlueprintCallable)
UFUNCTION(BlueprintCallable, Server, Reliable)
void TryPickup(AItemActor* ItemActor);
UPROPERTY(BlueprintReadWrite)
AActor* LookedAtActor = nullptr;
UFUNCTION(BlueprintCallable)
void InteractionLineTrace();
UFUNCTION(BlueprintCallable, Server, Reliable)
void InteractWithInteractable(AActor* TargetActor);
UPROPERTY(BlueprintReadWrite)
int32 InteractionDistance = 500;
};
+35
View File
@@ -0,0 +1,35 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "BaseCommand.generated.h"
/**
*
*/
UCLASS(Abstract, BlueprintType)
class MAYBETHISTIME_API UBaseCommand : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent) void Execute();
UFUNCTION(BlueprintNativeEvent) void Interrupt();
UPROPERTY(BlueprintReadOnly) class ABaseUnit* Owner;
// Broadcasts true if succeeded, false if failed
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCommandFinished, bool, bSuccess);
UPROPERTY(BlueprintAssignable) FOnCommandFinished OnCommandFinished;
protected:
// Subclasses call this when done
void Complete(bool bSuccess)
{
OnCommandFinished.Broadcast(bSuccess);
}
};
@@ -0,0 +1,54 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BaseCommand.h"
#include "AIController.h"
#include "Navigation/PathFollowingComponent.h"
#include "BaseCommands.generated.h"
class AActor;
UCLASS()
class UMoveToCommand : public UBaseCommand
{
GENERATED_BODY()
public:
UPROPERTY() FVector Destination;
UPROPERTY() float AcceptanceRadius = 50.f; // how close = "arrived"
virtual void Execute_Implementation() override;
virtual void Interrupt_Implementation() override;
private:
UPROPERTY() AAIController* AIController;
UFUNCTION() void OnMoveCompleted(FAIRequestID RequestID, EPathFollowingResult::Type Result);
};
UCLASS()
class UInteractCommand : public UBaseCommand
{
GENERATED_BODY()
public:
UPROPERTY() AActor* TargetActor;
UPROPERTY() int32 InteractionRange = 150;
virtual void Execute_Implementation() override;
virtual void Interrupt_Implementation() override;
private:
UPROPERTY() UMoveToCommand* SubMoveCommand;
UFUNCTION() void DoInteract();
UFUNCTION() void OnMoveFinished(bool bSuccess);
};
+48
View File
@@ -0,0 +1,48 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BaseAttributeSet.h"
#include "AbilitySystemInterface.h"
#include "InventoryComponent.h"
#include "BaseCommand.h"
#include "InventoryInterface.h"
#include "OutlinerTypes.h"
#include "InteractionInterface.h"
#include "BaseUnit.generated.h"
class UCommandComponent;
UCLASS()
class MAYBETHISTIME_API ABaseUnit : public ACharacter, public IAbilitySystemInterface, public IInteractionInterface, public IInventoryInterface
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ABaseUnit();
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities") UCommandComponent* CommandComponent;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities") UAbilitySystemComponent* AbilitySystemComponent;
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities") UBaseAttributeSet* AttributeSet;
UPROPERTY(EditDefaultsOnly) TArray<TSubclassOf<UGameplayEffect>> PassiveUpkeepEffects;
UPROPERTY(Replicated, EditAnywhere, BlueprintReadWrite) FName PrimaryFaction;
UPROPERTY(Replicated, EditAnywhere, BlueprintReadWrite) FText DisplayName;
// Inventory
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities") UInventoryComponent* InventoryComponent;
virtual UInventoryComponent* GetInventoryComponent_Implementation() const override;
// HUD
UFUNCTION(BlueprintCallable, BlueprintPure) virtual EOutlinerStatus GetOutlinerStatus() const;
protected:
virtual void BeginPlay() override;
};
@@ -0,0 +1,17 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "BaseUnitAIController.generated.h"
/**
*
*/
UCLASS()
class MAYBETHISTIME_API ABaseUnitAIController : public AAIController
{
GENERATED_BODY()
};
@@ -0,0 +1,45 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CommandComponent.generated.h"
class UBaseCommand;
class ABaseUnit;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MAYBETHISTIME_API UCommandComponent : public UActorComponent
{
GENERATED_BODY()
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Sets default values for this component's properties
UCommandComponent();
ABaseUnit* OwnerUnit;
UFUNCTION(BlueprintCallable) void GiveCommand(UBaseCommand* NewCommand, bool bQueue = false);
UFUNCTION(BlueprintCallable) void ExecuteNextCommand(bool bInterrupt = true);
UFUNCTION(BlueprintCallable) void InterruptCurrentCommand();
UFUNCTION(BlueprintCallable) void ClearCommandQueue();
UFUNCTION(BlueprintCallable) void RemoveCommandFromQueue(UBaseCommand* CommandToRemove);
UFUNCTION(BlueprintCallable) void ExecuteCommand(UBaseCommand* Command);
UFUNCTION(BlueprintCallable) void CommandFinished(bool bSuccess);
UFUNCTION(BlueprintCallable, BlueprintPure) const TArray<UBaseCommand*> GetUnitCommandQueue();
UFUNCTION(BlueprintCallable, BlueprintPure) bool IsExecutingCommad() const;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCommandQueueChanged);
UPROPERTY(BlueprintAssignable) FOnCommandQueueChanged OnCommandQueueChanged;
private:
UPROPERTY() TArray<UBaseCommand*> CommandQueue;
};
@@ -0,0 +1,159 @@
// FactionManager.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "FactionManager.generated.h"
// Relation thresholds
#define FACTION_HOSTILE_THRESHOLD -25.f
#define FACTION_NEUTRAL_THRESHOLD 25.f
#define FACTION_FRIENDLY_THRESHOLD 50.f
#define FACTION_ALLIED_THRESHOLD 75.f
// Hardcoded starting faction IDs
namespace FactionID
{
const FName Wanderer = "Wanderer";
const FName Feral = "Feral";
const FName Wildlife = "Wildlife";
// Add more as needed
}
UENUM(BlueprintType)
enum class EFactionType : uint8
{
PlayerFaction, // player controlled
Civilized, // organized NPC human faction
Hostile, // mindless, attacks everything
Bandit, // attacks weak, avoids strong
Wanderer, // no allegiance
Wildlife // neutral unless provoked
};
UENUM(BlueprintType)
enum class ERelationState : uint8
{
Allied,
Friendly,
Neutral,
Hostile
};
USTRUCT(BlueprintType)
struct FConsumptionRate
{
GENERATED_BODY()
UPROPERTY(EditAnywhere) FName ItemID;
UPROPERTY(EditAnywhere) float AmountPerHour = 1.f;
UPROPERTY(EditAnywhere) bool bPassive = true;
};
USTRUCT(BlueprintType)
struct FFactionNeed
{
GENERATED_BODY()
UPROPERTY(EditAnywhere) FName NeedID;
UPROPERTY(EditAnywhere) float Threshold = 100.f;
UPROPERTY() float Urgency = 0.f;
};
USTRUCT(BlueprintType)
struct FFactionRelation
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
FName TargetFactionID;
UPROPERTY(EditAnywhere)
float Relation = 0.f;
};
USTRUCT(BlueprintType)
struct FFactionData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly) FName FactionID;
UPROPERTY(EditAnywhere, BlueprintReadOnly) FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly) FLinearColor FactionColor = FLinearColor::White;
UPROPERTY(EditAnywhere, BlueprintReadOnly) EFactionType FactionType = EFactionType::Wanderer;
UPROPERTY(EditAnywhere, BlueprintReadOnly) TArray<FFactionRelation> RelationsTo;
UPROPERTY(EditAnywhere, BlueprintReadOnly) TArray<FFactionNeed> Needs;
UPROPERTY(EditAnywhere, BlueprintReadOnly) float Money = 10000000.f;
UPROPERTY(EditAnywhere) bool bInfiniteMoney = true;
float GetRelationTo(FName OtherFaction) const
{
const FFactionRelation* Found = RelationsTo.FindByPredicate([OtherFaction](const FFactionRelation& R) { return R.TargetFactionID == OtherFaction; });
return Found ? Found->Relation : 0.f;
}
ERelationState GetRelationStateTo(FName OtherFaction) const
{
float Relation = GetRelationTo(OtherFaction);
if (Relation >= FACTION_ALLIED_THRESHOLD) return ERelationState::Allied;
if (Relation >= FACTION_FRIENDLY_THRESHOLD) return ERelationState::Friendly;
if (Relation >= FACTION_NEUTRAL_THRESHOLD) return ERelationState::Neutral;
return ERelationState::Hostile;
}
float& FindOrAddRelationTo(FName Faction)
{
FFactionRelation* Found = RelationsTo.FindByPredicate([Faction](const FFactionRelation& R) { return R.TargetFactionID == Faction; });
if (!Found)
{
RelationsTo.Add(FFactionRelation{ Faction , 0});
return RelationsTo.Last().Relation;
}
return Found->Relation;
}
};
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class MAYBETHISTIME_API UFactionManager : public UActorComponent
{
GENERATED_BODY()
public:
UFactionManager();
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(ReplicatedUsing = OnRep_Factions, BlueprintReadOnly) TArray<FFactionData> Factions; // All factions
UFUNCTION(BlueprintCallable) void RegisterFaction(FFactionData NewFaction); // Faction Registration
FFactionData* GetFaction(FName FactionID);
const FFactionData* GetFaction(FName FactionID) const;
UFUNCTION(BlueprintCallable, BlueprintPure) FFactionData GetFactionCopy(FName FactionID) const;
UFUNCTION(BlueprintCallable, BlueprintPure) bool FactionExists(FName FactionID) const;
// --- Relations ---
UFUNCTION(BlueprintCallable, BlueprintPure) float GetRelation(FName FactionA, FName FactionB) const;
UFUNCTION(BlueprintCallable, BlueprintPure) ERelationState GetRelationState(FName FactionA, FName FactionB) const;
UFUNCTION(BlueprintCallable) void ModifyRelation(FName FactionA, FName FactionB, float Delta);
UFUNCTION(BlueprintCallable, BlueprintPure) bool AreHostile(FName FactionA, FName FactionB) const;
UFUNCTION(BlueprintCallable, BlueprintPure) bool AreAllied(FName FactionA, FName FactionB) const;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFactionChanged, FName, FactionID);
UPROPERTY(BlueprintAssignable) FOnFactionChanged OnFactionChanged;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnRelationChanged, FName, FactionA, FName, FactionB, float, NewRelation);
UPROPERTY(BlueprintAssignable) FOnRelationChanged OnRelationChanged;
void EvaluateFactionNeeds();
private:
void InitializeDefaultFactions();
UFUNCTION() void OnRep_Factions();
};
@@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "UObject/Interface.h"
#include "InteractionInterface.generated.h"
/**
*
*/
UINTERFACE(MinimalAPI, Blueprintable)
class UInteractionInterface : public UInterface
{
GENERATED_BODY()
};
class MAYBETHISTIME_API IInteractionInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void Interact(AActor* Interactor);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void LookingAt(AActor* Interactor, bool IsLooking);
};
@@ -0,0 +1,96 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ItemObject.h"
#include <Delegates/DelegateCombinations.h>
#include "ItemData.h"
#include "InventoryComponent.generated.h"
UENUM(BlueprintType)
enum class EAddItemResult : uint8
{
FullyAdded,
PartiallyAdded,
Failed,
Switched
};
UENUM(BlueprintType)
enum class ESortType : uint8
{
Alpabeticaly,
Weight,
Amount,
Type,
None
};
UCLASS(BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MAYBETHISTIME_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UInventoryComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_ItemList) TArray<FItemData> ItemList;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated) int32 InventorySize = 32;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite) TMap<FName, FItemData> EquipedItemMap;
UPROPERTY(EditAnywhere) float MaxCapacity = 1000.f;
UPROPERTY(Replicated) float CurrentWeight = 0.f;
UPROPERTY(Replicated) TArray<FItemData> Resources;
const int32 DropDistance = 200;
public:
// Add resource, returns overflow
float AddResource(FItemData& ItemData);
// Remove resource, returns false if not enough
bool ConsumeResource(FName ResourceID, float Amount);
float GetResource(FName ResourceID) const;
float GetFreeCapacity() const { return MaxCapacity - CurrentWeight; }
UFUNCTION()
void OnRep_ItemList();
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// Event dispatcher
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChanged);
UPROPERTY(BlueprintAssignable, Category = "Inventory") FOnInventoryChanged OnInventoryChanged;
UFUNCTION(BlueprintCallable, BlueprintPure) const FItemData& GetItemAt(int32 SlotIndex) const;
UFUNCTION(BlueprintCallable, BlueprintPure) const TArray<FItemData> GetAllItems() const;
UFUNCTION(BlueprintCallable, BlueprintPure) const TArray<FItemData> FindItemsByID(FName ItemID) const;
UFUNCTION(BlueprintCallable) EAddItemResult TryAddItem(FItemData NewItem, FItemData& OutRemainingItem);
UFUNCTION(BlueprintCallable) EAddItemResult AddItemAt(FItemData NewItem, int32 SlotIndex, FItemData& OutRemainingItem, bool AllowSwitch = false, bool AllowEmptySlots = true);
UFUNCTION(BlueprintCallable) void SetItemAt(FItemData NewItem, int32 SlotIndex);
UFUNCTION(BlueprintCallable) void RemoveItemAt(int32 SlotIndex);
UFUNCTION(BlueprintCallable) bool GetFirstFreeSlot(int32& SlotIndex) const;
UFUNCTION(BlueprintCallable) void SetInventorySize(int32 NewInventorySize);
UFUNCTION(BlueprintCallable) int32 GetInventorySize() const;
UFUNCTION(BlueprintCallable, Server, Reliable) void DropItem(FItemData ItemToDrop, const FTransform& SpawnTransform);
UFUNCTION(BlueprintCallable, Server, Reliable) void DropItemData(FItemData ItemToDrop);
UFUNCTION(BlueprintCallable, Server, Reliable) void DropItemAtSlot(int32 SlotIndex);
FTransform GetDropTransform() const;
UFUNCTION(BlueprintCallable, BlueprintPure) bool ContainsItem(FName ItemID, int32 Amount = 1) const;
UFUNCTION(BlueprintCallable, BlueprintPure) bool ContainsItems(const TMap<FName, int32>& RequiredItems) const;
UFUNCTION(BlueprintCallable) bool RemoveItem(FName ItemID, int32 Amount = 1);
UFUNCTION(BlueprintCallable) bool RemoveItems(const TMap<FName, int32>& RequiredItems);
UFUNCTION(BlueprintCallable) int32 GetItemAmount(FName ID) const;
UFUNCTION(BlueprintCallable) void Sort(ESortType SortType);
UFUNCTION(BlueprintCallable, Server, Reliable) void SetMaxCapacity(float NewMaxCapacity);
};
@@ -0,0 +1,26 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Interface.h"
#include "InventoryInterface.generated.h"
class UInventoryComponent;
UINTERFACE(MinimalAPI, Blueprintable)
class UInventoryInterface : public UInterface
{
GENERATED_BODY()
};
class MAYBETHISTIME_API IInventoryInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
UInventoryComponent* GetInventoryComponent() const;
};
+43
View File
@@ -0,0 +1,43 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractionInterface.h"
#include "ItemData.h"
#include "ItemActor.generated.h"
UCLASS(BlueprintType)
class MAYBETHISTIME_API AItemActor : public AActor, public IInteractionInterface
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AItemActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
virtual void Interact_Implementation(AActor* Interactor) override;
virtual void LookingAt_Implementation(AActor* Interactor, bool IsLooking) override;
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
virtual void Destroyed() override;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_ItemData, Category = "Item", meta = (ExposeOnSpawn = "true"))
FItemData ItemData;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item")
UStaticMeshComponent* MeshComponent;
FItemData GetItemData() const;
void SetItemObject(FItemData NewItem);
UFUNCTION()
void OnRep_ItemData();
};
+86
View File
@@ -0,0 +1,86 @@
#pragma once
#include "CoreMinimal.h"
#include "MyGameplayTags.h"
#include "ItemDefinition.h"
#include "ItemData.generated.h"
USTRUCT(BlueprintType)
struct FItemData
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TObjectPtr<UItemDefinition> Definition = nullptr;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int32 CurrentStackSize = 1;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float CurrentDurability = 1.0f;
bool IsValid() const { return Definition != nullptr; }
bool HasTag(FGameplayTag Tag) const
{
return Definition && Definition->Tags.HasTag(Tag);
}
int32 AddToStack(int32 Value)
{
if (!HasTag(Item_Flag_Stackable)) { CurrentStackSize = 1; return Value; }
int32 NewTotal = CurrentStackSize + Value;
CurrentStackSize = FMath::Min(NewTotal, Definition->MaxStackSize);
return FMath::Max(NewTotal - Definition->MaxStackSize, 0);
}
float AddToDurability(float Value)
{
if (!HasTag(Item_Flag_Durable)) { CurrentDurability = 1; return Value; }
float NewTotal = CurrentDurability + Value;
CurrentDurability = FMath::Min(NewTotal, Definition->MaxDurability);
return FMath::Max(NewTotal - Definition->MaxDurability, 0.f);
}
int32 SetStack(int32 NewValue)
{
if (!HasTag(Item_Flag_Stackable)) { CurrentStackSize = 1; return NewValue; }
int32 Clamped = FMath::Clamp(NewValue, 0, Definition->MaxStackSize);
CurrentStackSize = Clamped;
return FMath::Max(NewValue - Clamped, 0);
}
float SetDurability(float NewValue)
{
if (!HasTag(Item_Flag_Durable)) { CurrentDurability = 1.f; return NewValue; }
float Clamped = FMath::Clamp(NewValue, 0.f, Definition->MaxDurability);
CurrentDurability = Clamped;
return FMath::Max(NewValue - Clamped, 0.f);
}
void SetItemDefinition(UItemDefinition* NewDefinition)
{
if (!NewDefinition) return;
Definition = NewDefinition;
}
TArray<TSubclassOf<UGameplayEffect>> GetGrantedEffects() const
{
return Definition ? Definition->GrantedEffects : TArray<TSubclassOf<UGameplayEffect>>{};
}
TArray<TSubclassOf<UGameplayAbility>> GetGrantedAbility() const
{
return Definition ? Definition->GrantedAbility : TArray<TSubclassOf<UGameplayAbility>>{};
}
// Equality operator needed for replication delta serialization
bool operator==(const FItemData& Other) const
{
return Definition == Other.Definition &&
CurrentStackSize == Other.CurrentStackSize &&
CurrentDurability == Other.CurrentDurability;
}
bool operator!=(const FItemData& Other) const { return !(*this == Other); }
};
@@ -0,0 +1,71 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "GameplayEffect.h"
#include "Abilities/GameplayAbility.h"
#include "ItemDefinition.generated.h"
class AItemActor;
USTRUCT(BlueprintType)
struct FItemRegistryRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly) UItemDefinition* Definition = nullptr;
};
UCLASS(BlueprintType)
class MAYBETHISTIME_API UItemDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FName ID;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText Name;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText Description ;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FGameplayTagContainer Tags;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
int32 MaxStackSize = 1;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float MaxDurability = 1.0f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float WeightPerUnit = 0.1f;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UTexture2D* Icon;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<TSubclassOf<UGameplayEffect>> GrantedEffects;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<TSubclassOf<UGameplayAbility>> GrantedAbility;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<UStaticMesh> StaticMesh;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TObjectPtr<USkeletalMesh> SkeletalMesh;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<AItemActor> ItemActorClass;
UPROPERTY(EditAnywhere)
bool bIsBulkResource = true;
};
+58
View File
@@ -0,0 +1,58 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ItemDefinition.h"
#include "ItemObject.generated.h"
/**
*/
UCLASS(BlueprintType)
class MAYBETHISTIME_API UItemObject : public UObject
{
GENERATED_BODY()
public:
UItemObject();
~UItemObject();
virtual bool IsSupportedForNetworking() const override { return true; }
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(BlueprintReadWrite)
UItemDefinition* Definition;
UPROPERTY(BlueprintReadWrite)
int32 CurrentStackSize = 1;
UPROPERTY(BlueprintReadWrite)
float CurrentDurability = 1.0f;
UFUNCTION(BlueprintCallable, BlueprintPure)
bool HasTag(FGameplayTag Tag) const;
UFUNCTION(BlueprintCallable)
int32 AddToStack(int32 Value);
UFUNCTION(BlueprintCallable)
float AddToDurability(float Value);
UFUNCTION(BlueprintCallable)
int32 SetStack(int32 NewValue);
UFUNCTION(BlueprintCallable)
float SetDurability(float NewValue);
UFUNCTION(BlueprintCallable)
void SetItemDefinition(UItemDefinition* NewDefinition);
UFUNCTION(BlueprintCallable, BlueprintPure)
TArray<TSubclassOf<UGameplayEffect>> GetGrantedEffects() const;
UFUNCTION(BlueprintCallable, BlueprintPure)
TArray<TSubclassOf<UGameplayAbility>> GetGrantedAbility() const;
};
@@ -0,0 +1,35 @@
// ItemRegistry.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "ItemDefinition.h"
#include "ItemRegistry.generated.h"
UCLASS()
class MAYBETHISTIME_API UItemRegistry : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// Set this in your GameInstance Blueprint or config
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSoftObjectPtr<UDataTable> ItemDataTable;
UFUNCTION(BlueprintCallable, BlueprintPure)
UItemDefinition* GetDefinition(FName ItemID) const;
UFUNCTION(BlueprintCallable, BlueprintPure)
bool IsValidItem(FName ItemID) const;
// Get all registered item IDs
UFUNCTION(BlueprintCallable, BlueprintPure)
TArray<FName> GetAllItemIDs() const;
private:
// Cached loaded table
UPROPERTY()
UDataTable* LoadedTable = nullptr;
void LoadTable();
};
+95
View File
@@ -0,0 +1,95 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ModuleDefinition.h"
#include "OutlinerTypes.h"
#include "ModuleActor.generated.h"
class UInventoryComponent;
class AZoneActor;
UENUM(BlueprintType)
enum class EModuleState : uint8
{
UnderConstruction, // ghost/scaffold phase
Active, // working normally
Inactive, // built but not working (no workers, no power etc)
Damaged, // needs repair
Destroyed
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnModuleStateChanged, EModuleState, NewState);
UCLASS()
class MAYBETHISTIME_API AModuleActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AModuleActor();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// --- Definition ---
UPROPERTY(ReplicatedUsing = OnRep_Definition, EditAnywhere, BlueprintReadOnly) UModuleDefinition* Definition;
// --- State ---
UPROPERTY(ReplicatedUsing = OnRep_State, BlueprintReadOnly) EModuleState ModuleState = EModuleState::UnderConstruction;
UPROPERTY(Replicated, BlueprintReadOnly) float ConstructionProgress = 0.f; // 0-1
UFUNCTION(BlueprintCallable, BlueprintPure) EOutlinerStatus GetOutlinerStatus() const;
// --- Zone ---
UPROPERTY(Replicated, BlueprintReadOnly, Category = "Zone") AZoneActor* OwningZone;
UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Zone") AZoneActor* DefaultZone;
void AddResourceToZone(UInventoryComponent* ZoneStorage, FName ResourceID, float Amount);
// --- Workers ---
// Units assigned specifically to this module
UPROPERTY(Replicated, BlueprintReadOnly) TArray<class ABaseUnit*> AssignedWorkers;
// --- Delegates ---
UPROPERTY(BlueprintAssignable) FOnModuleStateChanged OnModuleStateChanged;
// --- Construction ---
UFUNCTION(BlueprintCallable) bool CanAffordConstruction(UInventoryComponent* PayingInventory) const;
UFUNCTION(BlueprintCallable) bool StartConstruction(AZoneActor* Zone, UInventoryComponent* PayingInventory);
// Called by ZoneActor each game hour
void TickProduction(float GameHoursDelta, UInventoryComponent* ZoneStorage);
// --- Workers ---
UFUNCTION(BlueprintCallable) bool AssignWorker(ABaseUnit* Unit);
UFUNCTION(BlueprintCallable) void RemoveWorker(ABaseUnit* Unit);
UFUNCTION(BlueprintCallable, BlueprintPure) int32 GetWorkerCount() const { return AssignedWorkers.Num(); }
UFUNCTION(BlueprintCallable, BlueprintPure) bool IsWorking() const { return ModuleState == EModuleState::Active; }
// --- Placement ---
UFUNCTION(BlueprintCallable, BlueprintPure) bool CanBePlacedAt(FVector Location, AZoneActor* Zone) const;
// Called on clients when definition replicates - sets up visuals
UFUNCTION(BlueprintNativeEvent) void OnDefinitionLoaded();
// Called on clients when state changes - swap ghost/real mesh etc
UFUNCTION(BlueprintNativeEvent) void OnStateChanged(EModuleState NewState);
protected:
void UpdateWorkingState();
void TickConstruction(float GameHoursDelta);
void TickActiveProduction(float GameHoursDelta, UInventoryComponent* ZoneStorage);
UFUNCTION() void OnRep_Definition();
UFUNCTION() void OnRep_State();
private:
// Testing only
UFUNCTION(BlueprintCallable)
void ForceCompleteConstruction(AZoneActor* Zone);
};
@@ -0,0 +1,85 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ModuleDefinition.generated.h"
/**
*
*/
UENUM(BlueprintType)
enum class EZoneModuleType : uint8
{
Storage,
Production,
Medical,
Defensive,
Housing,
Infrastructure
};
USTRUCT(BlueprintType)
struct FModuleResourceRate
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FName ResourceID;
UPROPERTY(EditAnywhere, BlueprintReadWrite) float AmountPerHour = 1.f;
// If true needs workers to function, false = runs automatically
UPROPERTY(EditAnywhere, BlueprintReadWrite) bool bRequiresWorkers = false;
};
USTRUCT(BlueprintType)
struct FModuleCost
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FName ResourceID;
UPROPERTY(EditAnywhere, BlueprintReadWrite) float Amount = 0.f;
};
class AModuleActor;
UCLASS(BlueprintType)
class MAYBETHISTIME_API UModuleDefinition : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// --- Identity ---
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Identity") FName ModuleID;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Identity") FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Identity") FText Description;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Identity") EZoneModuleType ZoneModuleType;
// --- Visuals ---
// The Blueprint actor class that represents this module in the world
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Visuals") TSubclassOf<AModuleActor> ModuleActorClass;
// Ghost/scaffold mesh shown during construction
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Visuals") TSubclassOf<AModuleActor> ConstructionActorClass;
// --- Placement ---
// Radius this module occupies (for overlap checks)
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Placement") float PlacementRadius = 200.f;
// Minimum reputation required to build this module in a zone
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Placement") float RequiredReputation = 0.f;
// --- Construction ---
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Construction") float ConstructionTimeHours = 2.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Construction") TArray<FModuleCost> ConstructionCost;
// --- Workers ---
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Workers") int32 MaxWorkers = 0; // 0 = no workers needed
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Workers") int32 MinWorkersToOperate = 0; // below this = not working
// --- Production / Consumption ---
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Economy") TArray<FModuleResourceRate> ProducedResources;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Economy") TArray<FModuleResourceRate> ConsumedResources;
// --- Storage (for storage modules) ---
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage") float AddedStorageCapacity = 0.f;
};
@@ -0,0 +1,45 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "NativeGameplayTags.h"
#include "CoreMinimal.h"
/**
*
*/
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Flag_Stackable);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Flag_Quest);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Flag_Consumeable);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Flag_Durable);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Flag_Resource);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Belt);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Hands);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Head);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Legs);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Torso);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Weapon_Ranged);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Weapon_Melee);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Weapon_Melee_TwoHanded);
UE_DECLARE_GAMEPLAY_TAG_EXTERN(Item_Type_Weapon_Melee_OneHanded);
/*
3 x 25 marker
2 x 25 dude
5 - 6 x 32 flameers
5 x 32 jumpers
5 x 38 terminators
6 x 38 inner circle
10 x 38 termintator with sword an mace
1 x 48 captain in terminator armor
1 x 48 azazel
1 x 68 big boy
1 x 98 rawr
1 x 16cm x 11cm tonk
*/
@@ -0,0 +1,16 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
UENUM(BlueprintType)
enum class EOutlinerStatus : uint8
{
OK, // green - working normally
Warning, // yellow - needs attention (low resources, no workers)
Critical, // red - not working, under attack, starving
Inactive, // grey - built but idle
Construction, // blue - being built
Player
};
@@ -0,0 +1,30 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "ItemActor.h"
#include "PlaceableItemActor.generated.h"
/**
*
*/
UCLASS(BlueprintType)
class MAYBETHISTIME_API APlaceableItemActor : public AItemActor
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TObjectPtr<UItemDefinition> DefaultDefinition;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 DefaultStackSize = 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float DefaultDurability = 1.f;
};
+21
View File
@@ -0,0 +1,21 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BaseUnit.h"
#include "PlayerUnit.generated.h"
/**
*
*/
UCLASS()
class MAYBETHISTIME_API APlayerUnit : public ABaseUnit
{
GENERATED_BODY()
public:
EOutlinerStatus GetOutlinerStatus() const override;
};
@@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ThirdPersonCharacter.generated.h"
UCLASS()
class MAYBETHISTIME_API AThirdPersonCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AThirdPersonCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
};
@@ -0,0 +1,66 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Controller.h"
#include "GameFramework/PlayerController.h"
#include "ThirdPersonController.generated.h"
class APlayerUnit;
class UUnitManagerComponent;
class AZoneActor;
UCLASS()
class MAYBETHISTIME_API AThirdPersonController : public APlayerController
{
GENERATED_BODY()
public:
AThirdPersonController();
UPROPERTY(VisibleAnywhere, BlueprintReadWrite) UUnitManagerComponent* UnitManager;
UPROPERTY(Replicated, BlueprintReadOnly) FName PlayerFactionID;
UPROPERTY(BlueprintReadWrite) bool bShiftHeld;
// Units
UPROPERTY(Replicated, BlueprintReadOnly) APlayerUnit* PlayerUnit;
UFUNCTION(BlueprintCallable, BlueprintPure) APlayerUnit* GetPlayerUnit() const { return PlayerUnit; }
// Selection
UPROPERTY(BlueprintReadOnly) TArray<ABaseUnit*> SelectedUnits;
UFUNCTION(BlueprintCallable) void SelectUnit(ABaseUnit* Unit, bool bAddToSelection = false);
UFUNCTION(BlueprintCallable) void ClearUnitSelection();
UFUNCTION(BlueprintCallable) void BoxSelect(TArray<ABaseUnit*> UnitsInBox);
UPROPERTY(BlueprintReadOnly) TArray<AZoneActor*> SelectedZones;
UFUNCTION(BlueprintCallable) void SelectZone(AZoneActor* Zone, bool bAddToSelection = false);
UFUNCTION(BlueprintCallable) void ClearZoneSelection();
UFUNCTION(BlueprintCallable) void ClearMySelection();
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSelectionChanged); // Delegate so UI knows when selection changes
UPROPERTY(BlueprintAssignable) FOnSelectionChanged OnSelectionChanged;
UFUNCTION(BlueprintCallable, BlueprintPure) TArray<AZoneActor*> GetOwnedZones() const;
UFUNCTION(BlueprintCallable) bool SelectClickedActor(AActor* ClickedActor);
// Currently hovered interactable
UPROPERTY() AActor* HoveredActor;
UFUNCTION(BlueprintCallable) void HandleHover();
UFUNCTION(BlueprintCallable) void HandleLeftClick();
UFUNCTION(BlueprintCallable) void HandleRightClick();
// Replication
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
private:
UPROPERTY() AActor* LastHoveredActor;
bool IsMouseOverUI() const;
};
@@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "TopDownGameMode.generated.h"
class ABaseUnit;
class APlayerUnit;
/**
*
*/
UCLASS()
class MAYBETHISTIME_API ATopDownGameMode : public AGameMode
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, Category = "Units") TSubclassOf<APlayerUnit> DefaultPlayerUnitClass;
UFUNCTION(BlueprintCallable) ABaseUnit* SpawnUnit( TSubclassOf<ABaseUnit> UnitClass, FTransform SpawnTransform, FName FactionID = NAME_None);
virtual void BeginPlay() override;
void PostLogin(APlayerController* NewPlayer) override;
};
@@ -0,0 +1,80 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "TopDownGameState.generated.h"
class UFactionManager;
class ABaseUnit;
class AZoneActor;
UCLASS()
class MAYBETHISTIME_API ATopDownGameState : public AGameState
{
GENERATED_BODY()
ATopDownGameState();
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UFactionManager* FactionManager;
// Current game time
UPROPERTY(ReplicatedUsing = OnRep_GameTime) float GameHour = 8.f; // start at 8am
UPROPERTY(ReplicatedUsing = OnRep_GameTime) int32 GameDay = 1;
UPROPERTY(EditDefaultsOnly) float RealSecondsPerGameHour = 5.f;
UFUNCTION(BlueprintCallable, BlueprintPure) FText GetFormattedTime() const;
UFUNCTION(BlueprintCallable, BlueprintPure) bool IsNightTime() const;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnGameTimeChanged, float, NewHour, int32, NewDay);
UPROPERTY(BlueprintAssignable) FOnGameTimeChanged OnGameTimeChanged;
// Units
UPROPERTY(Replicated, BlueprintReadOnly) TArray<ABaseUnit*> AllUnits;
void RegisterUnit(ABaseUnit* Unit);
void UnregisterUnit(ABaseUnit* Unit);
void TickAllUnits(float GameHoursDelta);
void TickUnitNeeds(ABaseUnit* Unit, float GameHoursDelta);
UFUNCTION(BlueprintCallable, BlueprintPure) TArray<ABaseUnit*> GetUnitsForFaction(FName FactionID) const;
// Zones
UPROPERTY(Replicated, BlueprintReadOnly) TArray<AZoneActor*> AllZones;
void RegisterZone(AZoneActor* Zone);
void UnregisterZone(AZoneActor* Zone);
AZoneActor* GetZoneAtLocation(FVector Location) const;
UFUNCTION(BlueprintCallable, BlueprintPure) TArray<AZoneActor*> GetZonesForFaction(FName FactionID) const;
// Factions
UFUNCTION(BlueprintCallable, BlueprintPure) TArray<FFactionData> GetAllFactions() const;
UFUNCTION(BlueprintCallable, BlueprintPure) float GetPlayerMoney() const;
// Delegate so outliner knows when to refresh
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWorldStateChanged);
UPROPERTY(BlueprintAssignable, BlueprintCallable) FOnWorldStateChanged OnWorldStateChanged;
UFUNCTION(BlueprintCallable) void BroadcastWorldStateChanged();
void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
protected:
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
private:
FTimerHandle GameTickTimerHandle;
UFUNCTION() void OnGameTick(); // fires every RealSecondsPerGameHour
UFUNCTION() void OnRep_GameTime();
UFUNCTION() void OnRep_WorldStateVersion();
UPROPERTY(ReplicatedUsing = OnRep_WorldStateVersion) int32 WorldStateVersion = 0;
};
@@ -0,0 +1,35 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "UnitManagerComponent.generated.h"
class ABaseUnit;
class UBaseCommand;
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MAYBETHISTIME_API UUnitManagerComponent : public UActorComponent
{
GENERATED_BODY()
public:
UUnitManagerComponent();
UPROPERTY(BlueprintReadOnly) TArray<ABaseUnit*> Squad;
// Issue commands to Units
UFUNCTION(BlueprintCallable, BlueprintPure) ABaseUnit* GetFreeUnit();
UFUNCTION(BlueprintCallable) void IssueCommandToUnit(UBaseCommand* Command, ABaseUnit* Unit);
UFUNCTION(BlueprintCallable) void IssueCommandToUnits(UBaseCommand* Command, TArray<ABaseUnit*> Units);
UFUNCTION(BlueprintCallable) void IssueCommandToSquad(UBaseCommand* Command);
UFUNCTION(BlueprintCallable) void IssueInteractCommand(AActor* TargetActor, bool bQueue = false);
UFUNCTION(BlueprintCallable) void IssueMoveCommand(FVector Location, bool bQueue = false);
// Change squad
UFUNCTION(BlueprintCallable) void AddUnitToSquad(ABaseUnit* Unit);
UFUNCTION(BlueprintCallable) void RemoveUnitFromSquad(ABaseUnit* Unit);
UFUNCTION(BlueprintCallable) void ClearSquad();
UFUNCTION(BlueprintCallable, BlueprintPure) TArray<ABaseUnit*> GetSquad();
};
+130
View File
@@ -0,0 +1,130 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InventoryInterface.h"
#include "OutlinerTypes.h"
#include "ZoneActor.generated.h"
class AModuleActor;
class UInventoryComponent;
class USphereComponent;
USTRUCT(BlueprintType)
struct FFactionInfluence
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) FName FactionID;
UPROPERTY(EditAnywhere, BlueprintReadWrite) float Influence = 0.f;
};
UENUM(BlueprintType)
enum class EBuildPermission : uint8
{
Denied,
Allowed,
AllowedWithReputation
};
USTRUCT(BlueprintType)
struct FResourcePrice
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
FName ResourceID;
UPROPERTY(EditAnywhere)
float BasePrice = 1.f; // price per unit at neutral reputation
};
UCLASS()
class MAYBETHISTIME_API AZoneActor : public AActor, public IInventoryInterface
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AZoneActor();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(Replicated, BlueprintReadOnly)
float ZoneAccount = 10000.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
bool bInfiniteZoneMoney = true; // for now
// --- Identity ---
UPROPERTY(ReplicatedUsing = OnRep_ZoneData, EditAnywhere, BlueprintReadOnly) FName ZoneID;
UPROPERTY(ReplicatedUsing = OnRep_ZoneData, EditAnywhere, BlueprintReadOnly) FText ZoneName;
UPROPERTY(ReplicatedUsing = OnRep_ZoneData, EditAnywhere, BlueprintReadOnly) FName OwningFaction;
UFUNCTION(BlueprintCallable, BlueprintPure) EOutlinerStatus GetOutlinerStatus() const;
// --- Geometry ---
UPROPERTY(ReplicatedUsing = OnRep_Radius, EditAnywhere, BlueprintReadOnly) float Radius = 2000.f; // unreal units
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) USphereComponent* ZoneVolume;
// --- Modules ---
UPROPERTY(Replicated, BlueprintReadWrite) TArray<AModuleActor*> Modules;
// --- Storage ---
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UInventoryComponent* ZoneStorage;
// --- Influence ---
UPROPERTY(ReplicatedUsing = OnRep_ZoneData, BlueprintReadOnly) TArray<FFactionInfluence> FactionInfluences;
// --- Delegates ---
// Delegate for UI/AI to react to faction changes
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZoneChanged, FName, ZoneID);
UPROPERTY(BlueprintAssignable) FOnZoneChanged OnZoneChanged;
// --- Module management ---
UFUNCTION(BlueprintCallable) bool CanFactionBuildHere(FName FactionID, float Reputation) const;
UFUNCTION(BlueprintCallable) void RegisterModule(AModuleActor* Module);
UFUNCTION(BlueprintCallable) void UnregisterModule(AModuleActor* Module);
UFUNCTION(BlueprintCallable) bool IsLocationInsideZone(FVector Location) const;
// --- Radius ---
UFUNCTION(BlueprintCallable) void SetRadius(float NewRadius);
// --- Ownership ---
UFUNCTION(BlueprintCallable) void SetOwningFaction(FName NewFaction);
UFUNCTION(BlueprintCallable) void ModifyInfluence(FName FactionID, float Delta);
FName GetDominantFaction() const;
// --- Tick (called by GameState) ---
void TickZone(float GameHoursDelta);
UPROPERTY(EditAnywhere, BlueprintReadOnly) TArray<FResourcePrice> ResourcePrices;
// Price scales with reputation - better standing = cheaper
float GetPriceForFaction(FName ResourceID, FName FactionID, float Reputation) const;
bool CanFactionUseResources(FName FactionID, float Reputation) const;
virtual UInventoryComponent* GetInventoryComponent_Implementation() const override;
private:
UFUNCTION()
void OnRep_ZoneData();
UFUNCTION()
void OnRep_Radius();
void UpdateVolumeSize();
void TickModuleProduction(float GameHoursDelta);
void TickZoneInfluence(float GameHoursDelta);
void SupplyUnitsInZone(float GameHoursDelta);
};
+15
View File
@@ -0,0 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using System.Collections.Generic;
public class MaybeThisTimeEditorTarget : TargetRules
{
public MaybeThisTimeEditorTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V7;
ExtraModuleNames.AddRange( new string[] { "MaybeThisTime" } );
}
}