Some massive reworks for seed planting
This commit is contained in:
BIN
Content/Input/IA_PlantSeedAction.uasset
LFS
Normal file
BIN
Content/Input/IA_PlantSeedAction.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/Input/IMC_PlantingContext.uasset
LFS
Normal file
BIN
Content/Input/IMC_PlantingContext.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/Scripts/InventorySystem/Seed/BP_SeedBedActor.uasset
LFS
Normal file
BIN
Content/Scripts/InventorySystem/Seed/BP_SeedBedActor.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/Scripts/InventorySystem/Seed/MI_SeedBedDecal_Inst.uasset
LFS
Normal file
BIN
Content/Scripts/InventorySystem/Seed/MI_SeedBedDecal_Inst.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/Scripts/InventorySystem/Seed/MI_SeedBedMat.uasset
LFS
Normal file
BIN
Content/Scripts/InventorySystem/Seed/MI_SeedBedMat.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/Scripts/InventorySystem/Seed/M_SeedBedDecal.uasset
LFS
Normal file
BIN
Content/Scripts/InventorySystem/Seed/M_SeedBedDecal.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/Textures/Vegetation/T_SeedBedMask.png
LFS
Normal file
BIN
Content/Textures/Vegetation/T_SeedBedMask.png
LFS
Normal file
Binary file not shown.
BIN
Content/Textures/Vegetation/T_SeedBedMask.uasset
LFS
Normal file
BIN
Content/Textures/Vegetation/T_SeedBedMask.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,26 +1,552 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SeedBedActor.h"
|
||||
|
||||
#include "EleriGameState.h"
|
||||
#include "Components/DecalComponent.h"
|
||||
#include "Components/SceneComponent.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "DataAssets/ItemDataAsset.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "Engine/World.h"
|
||||
#include "ItemContainer.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Kismet/KismetMaterialLibrary.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
|
||||
// Sets default values
|
||||
ASeedBedActor::ASeedBedActor()
|
||||
namespace
|
||||
{
|
||||
// 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;
|
||||
/** Destroys the components of a slot and clears the pointers so nothing stale is left in the map */
|
||||
void DestroySlotComponents(FSeedBedSeedSlot& Slot)
|
||||
{
|
||||
if (Slot.SeedBedDecal) Slot.SeedBedDecal->DestroyComponent();
|
||||
if (Slot.PlantStaticMesh) Slot.PlantStaticMesh->DestroyComponent();
|
||||
|
||||
Slot.SeedBedDecal = nullptr;
|
||||
Slot.PlantStaticMesh = nullptr;
|
||||
}
|
||||
|
||||
/** Slots are created at runtime as well, so the components are created with stable, deterministic names */
|
||||
UStaticMeshComponent* CreateSlotMeshComponent(AActor* Owner, USceneComponent* AttachParent, FName ComponentName)
|
||||
{
|
||||
if (!IsValid(Owner) || !IsValid(AttachParent))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UStaticMeshComponent* MeshComponent = NewObject<UStaticMeshComponent>(Owner, ComponentName);
|
||||
if (!MeshComponent)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MeshComponent->SetMobility(EComponentMobility::Movable);
|
||||
MeshComponent->SetupAttachment(AttachParent);
|
||||
MeshComponent->RegisterComponent();
|
||||
Owner->AddInstanceComponent(MeshComponent);
|
||||
|
||||
return MeshComponent;
|
||||
}
|
||||
|
||||
UDecalComponent* CreateSlotDecalComponent(AActor* Owner, USceneComponent* AttachParent, FName ComponentName)
|
||||
{
|
||||
if (!IsValid(Owner) || !IsValid(AttachParent))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UDecalComponent* DecalComponent = NewObject<UDecalComponent>(Owner, ComponentName);
|
||||
if (!DecalComponent)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DecalComponent->SetMobility(EComponentMobility::Movable);
|
||||
DecalComponent->SetupAttachment(AttachParent);
|
||||
DecalComponent->RegisterComponent();
|
||||
DecalComponent->SetVisibility(false);
|
||||
Owner->AddInstanceComponent(DecalComponent);
|
||||
|
||||
return DecalComponent;
|
||||
}
|
||||
|
||||
/** Soft references are filled in by hand, an unset mesh or material must never break the visuals */
|
||||
template <typename TObjectType>
|
||||
TObjectType* ResolveSoftObject(TSoftObjectPtr<TObjectType> SoftObject)
|
||||
{
|
||||
if (SoftObject.IsNull())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return SoftObject.Get() ? SoftObject.Get() : SoftObject.LoadSynchronous();
|
||||
}
|
||||
|
||||
/** A slot counts as planted as soon as an asset or its id is stored, saves may only restore the id */
|
||||
bool SlotHasPlant(const FSeedBedSeedSlot& Slot)
|
||||
{
|
||||
return IsValid(Slot.SlotData.ItemDataAsset) || Slot.SlotData.ItemAssetID.IsValid();
|
||||
}
|
||||
|
||||
/** Growth stages are set up by hand, so a zeroed scale should never make a plant invisible */
|
||||
FVector SanitizeScale(const FVector& Scale)
|
||||
{
|
||||
return FVector(
|
||||
FMath::IsNearlyZero(Scale.X) ? 1.f : Scale.X,
|
||||
FMath::IsNearlyZero(Scale.Y) ? 1.f : Scale.Y,
|
||||
FMath::IsNearlyZero(Scale.Z) ? 1.f : Scale.Z);
|
||||
}
|
||||
}
|
||||
|
||||
ASeedBedActor::ASeedBedActor()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
Root->SetMobility(EComponentMobility::Movable);
|
||||
SetRootComponent(Root);
|
||||
}
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void ASeedBedActor::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
HighlightDecal = NewObject<UDecalComponent>(this, TEXT("Highlight Decal Component"));
|
||||
if (HighlightDecal)
|
||||
{
|
||||
HighlightDecal->SetMobility(EComponentMobility::Movable);
|
||||
HighlightDecal->SetupAttachment(RootComponent);
|
||||
HighlightDecal->RegisterComponent();
|
||||
HighlightDecal->SetVisibility(false);
|
||||
HighlightDecal->SetRelativeRotation(HighlightDecalRotation);
|
||||
AddInstanceComponent(HighlightDecal);
|
||||
HighlightDecal->SetDecalMaterial(ResolveSoftObject(HighlightDecalMaterial));
|
||||
}
|
||||
|
||||
ClearSeedBedGrid();
|
||||
GenerateSeedBedGrid();
|
||||
|
||||
AEleriGameState* GameState = Cast<AEleriGameState>(UGameplayStatics::GetGameState(this));
|
||||
ensure(GameState);
|
||||
|
||||
FSeedBedSaveData SaveData;
|
||||
if (GameState->GetSeedData(SeedBedIndex, SaveData))
|
||||
{
|
||||
PropagateSeedSaveData(SaveData);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameState->CacheSeedData(SeedBedIndex, PrepareSeedSaveData());
|
||||
}
|
||||
|
||||
// The bed can be built in the editor, but slots added through blueprints need their visuals as well
|
||||
RefreshAllSlotVisuals();
|
||||
}
|
||||
|
||||
void ASeedBedActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
AEleriGameState* GameState = Cast<AEleriGameState>(UGameplayStatics::GetGameState(this));
|
||||
ensure(GameState);
|
||||
GameState->CacheSeedData(SeedBedIndex, PrepareSeedSaveData());
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void ASeedBedActor::OnConstruction(const FTransform& Transform)
|
||||
{
|
||||
Super::OnConstruction(Transform);
|
||||
|
||||
RefreshAllSlotVisuals();
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void ASeedBedActor::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// GRID GENERATION
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
void ASeedBedActor::GenerateSeedBedGrid()
|
||||
{
|
||||
GenerateSeedBedGridSized(GridSize.X, GridSize.Y, true);
|
||||
}
|
||||
|
||||
void ASeedBedActor::GenerateSeedBedGridSized(int32 Columns, int32 Rows, bool bRebuildExistingSlots)
|
||||
{
|
||||
const int32 SafeColumns = FMath::Max(1, Columns);
|
||||
const int32 SafeRows = FMath::Max(1, Rows);
|
||||
|
||||
GridSize = FIntVector2(SafeColumns, SafeRows);
|
||||
|
||||
if (bRebuildExistingSlots)
|
||||
{
|
||||
// Slots falling outside of the new grid are dropped, the rest is rebuilt in place
|
||||
TArray<FIntVector2> CoordinatesToRemove;
|
||||
for (const TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||
{
|
||||
if (SlotPair.Key.X < 0 || SlotPair.Key.Y < 0 || SlotPair.Key.X >= SafeColumns || SlotPair.Key.Y >= SafeRows)
|
||||
{
|
||||
CoordinatesToRemove.Add(SlotPair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FIntVector2& Coordinate : CoordinatesToRemove)
|
||||
{
|
||||
if (FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(Coordinate))
|
||||
{
|
||||
DestroySlotComponents(*Slot);
|
||||
}
|
||||
|
||||
SeedBedSeedSlotsMap.Remove(Coordinate);
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 Column = 0; Column < SafeColumns; ++Column)
|
||||
{
|
||||
for (int32 Row = 0; Row < SafeRows; ++Row)
|
||||
{
|
||||
const FIntVector2 Coordinate(Column, Row);
|
||||
if (bRebuildExistingSlots || !SeedBedSeedSlotsMap.Contains(Coordinate))
|
||||
{
|
||||
CreateSlot(Coordinate, bRebuildExistingSlots);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slot visuals also refresh the highlight, so the decals follow the new grid
|
||||
RefreshAllSlotVisuals();
|
||||
}
|
||||
|
||||
void ASeedBedActor::ClearSeedBedGrid()
|
||||
{
|
||||
for (TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||
{
|
||||
DestroySlotComponents(SlotPair.Value);
|
||||
}
|
||||
|
||||
SeedBedSeedSlotsMap.Empty();
|
||||
bPlantingHighlightActive = false;
|
||||
}
|
||||
|
||||
FName ASeedBedActor::GetSlotComponentName(FIntVector2 SlotCoordinate, const TCHAR* Prefix)
|
||||
{
|
||||
return FName(*FString::Printf(TEXT("%s_%d_%d"), Prefix ? Prefix : TEXT("Slot"), SlotCoordinate.X, SlotCoordinate.Y));
|
||||
}
|
||||
|
||||
FVector ASeedBedActor::GetSlotRelativeLocation(FIntVector2 SlotCoordinate) const
|
||||
{
|
||||
// The grid is centered on the actor, so the coordinates are shifted by half a cell
|
||||
if (const FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate))
|
||||
{
|
||||
return Slot->SeedBedDecal->GetRelativeLocation();
|
||||
}
|
||||
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
FSeedBedSaveData ASeedBedActor::PrepareSeedSaveData()
|
||||
{
|
||||
FSeedBedSaveData SaveData;
|
||||
SaveData.Index = SeedBedIndex;
|
||||
for (const auto& Slot : SeedBedSeedSlotsMap)
|
||||
{
|
||||
SaveData.SlotData.Add(Slot.Key, Slot.Value.SlotData);
|
||||
}
|
||||
|
||||
return SaveData;
|
||||
}
|
||||
|
||||
void ASeedBedActor::PropagateSeedSaveData(const FSeedBedSaveData& SeedSaveData)
|
||||
{
|
||||
UItemDatabase* ItemDatabase = UGameplayStatics::GetGameInstance(this)->GetSubsystem<UItemDatabase>();
|
||||
ensure(ItemDatabase);
|
||||
|
||||
SeedBedIndex = SeedSaveData.Index;
|
||||
for (const auto& SavedSlot : SeedSaveData.SlotData)
|
||||
{
|
||||
if (FSeedBedSeedSlot* SeedSlot = SeedBedSeedSlotsMap.Find(SavedSlot.Key))
|
||||
{
|
||||
SeedSlot->SlotData = SavedSlot.Value;
|
||||
|
||||
SeedSlot->SlotData.ItemDataAsset = ItemDatabase->GetDataAssetFromId(SeedSlot->SlotData.ItemAssetID);
|
||||
}
|
||||
}
|
||||
|
||||
RefreshAllSlotVisuals();
|
||||
}
|
||||
|
||||
FSeedBedSeedSlot& ASeedBedActor::CreateSlot(FIntVector2 SlotCoordinate, bool bRebuildIfExisting)
|
||||
{
|
||||
FSeedBedSeedSlot& Slot = SeedBedSeedSlotsMap.FindOrAdd(SlotCoordinate);
|
||||
|
||||
if (bRebuildIfExisting)
|
||||
{
|
||||
DestroySlotComponents(Slot);
|
||||
}
|
||||
|
||||
Slot.SeedBedDecal = CreateSlotDecalComponent(this, GetRootComponent(), GetSlotComponentName(SlotCoordinate, TEXT("Slot")));
|
||||
Slot.SeedBedDynamicMat = UKismetMaterialLibrary::CreateDynamicMaterialInstance(this, ResolveSoftObject(SeedBedDecalMaterial), NAME_None, EMIDCreationFlags::Transient);
|
||||
Slot.SeedBedDecal->SetDecalMaterial(Slot.SeedBedDynamicMat);
|
||||
Slot.SeedBedDecal->DecalSize = SeedBedDecalSize;
|
||||
Slot.SeedBedDecal->SetRelativeRotation(SeedBedDecalRotation);
|
||||
const FVector2D Offset = FVector2D(GridSize.X * SeedBedDecalSize.Z, GridSize.Y * SeedBedDecalSize.Y) * -1.f;
|
||||
const float X = SlotCoordinate.Y * SeedBedDecalSize.Z * 2.f;
|
||||
const float Y = SlotCoordinate.X * SeedBedDecalSize.Y * 2.f;
|
||||
Slot.SeedBedDecal->SetRelativeLocation(
|
||||
FVector(
|
||||
Offset.X + X + SeedBedDecalSize.Z,
|
||||
Offset.Y + Y + SeedBedDecalSize.Y,
|
||||
0.f));
|
||||
|
||||
ApplySlotVisuals(SlotCoordinate, Slot);
|
||||
|
||||
return Slot;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// VISUALS
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
void ASeedBedActor::ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlot& Slot)
|
||||
{
|
||||
const FVector SlotLocation = GetSlotRelativeLocation(SlotCoordinate);
|
||||
|
||||
// SOIL -----------------------------------------------------------------------------------------
|
||||
if (IsValid(Slot.SeedBedDecal))
|
||||
{
|
||||
Slot.SeedBedDecal->SetVisibility(Slot.SlotData.ItemAssetID.IsValid());
|
||||
Slot.SeedBedDynamicMat->SetScalarParameterValue(TEXT("Wetness"), Slot.SlotData.bWateredToday);
|
||||
}
|
||||
|
||||
// PLANT ----------------------------------------------------------------------------------------
|
||||
const bool bHasPlant = SlotHasPlant(Slot) && !Slot.SlotData.bDied;
|
||||
|
||||
if (IsValid(Slot.PlantStaticMesh))
|
||||
{
|
||||
UStaticMesh* PlantMesh = nullptr;
|
||||
FVector PlantOffset = FVector::ZeroVector;
|
||||
FVector PlantScale = FVector::OneVector;
|
||||
|
||||
if (bHasPlant)
|
||||
{
|
||||
const int32 StageIndex = ResolveGrowthStageIndex(Slot);
|
||||
const UItemDataAsset* SeedData = Slot.SlotData.ItemDataAsset;
|
||||
if (IsValid(SeedData) && SeedData->SeedStages.IsValidIndex(StageIndex))
|
||||
{
|
||||
const FSeedStage& Stage = SeedData->SeedStages[StageIndex];
|
||||
PlantMesh = ResolveSoftObject(Stage.Mesh);
|
||||
PlantOffset = Stage.MeshOffset;
|
||||
PlantScale = SanitizeScale(Stage.MeshScale);
|
||||
}
|
||||
|
||||
// A seed without stage meshes still has to show up, the fallback keeps the plant visible
|
||||
if (!PlantMesh)
|
||||
{
|
||||
PlantMesh = ResolveSoftObject(DefaultPlantMesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void ASeedBedActor::RefreshSlotVisual(FIntVector2 SlotCoordinate)
|
||||
{
|
||||
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||
if (!Slot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ApplySlotVisuals(SlotCoordinate, *Slot);
|
||||
|
||||
OnSeedBedSlotChanged.Broadcast(SlotCoordinate, SlotHasPlant(*Slot));
|
||||
}
|
||||
|
||||
void ASeedBedActor::RefreshAllSlotVisuals()
|
||||
{
|
||||
for (TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||
{
|
||||
ApplySlotVisuals(SlotPair.Key, SlotPair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
bool ASeedBedActor::IsPlantableSeedData(const UItemDataAsset* SeedData)
|
||||
{
|
||||
// Only seeds of plants make it into a bed, the meshes are optional and fall back to DefaultPlantMesh
|
||||
return IsValid(SeedData) && SeedData->IsSeed;
|
||||
}
|
||||
|
||||
void ASeedBedActor::HighlightSlot(FIntVector2 SlotCoordinate)
|
||||
{
|
||||
if (!IsSlotFree(SlotCoordinate)) return;
|
||||
|
||||
const FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||
if (!Slot) return;
|
||||
|
||||
const FVector SlotLocation = GetSlotRelativeLocation(SlotCoordinate);
|
||||
|
||||
if (IsValid(HighlightDecal))
|
||||
{
|
||||
HighlightDecal->SetRelativeLocation(SlotLocation + FVector(0.f, 0.f, 0.f));
|
||||
HighlightDecal->DecalSize = HighlightDecalSize;
|
||||
HighlightDecal->SetVisibility(true);
|
||||
}
|
||||
|
||||
HighlightedSlotIndex = SlotCoordinate;
|
||||
}
|
||||
|
||||
void ASeedBedActor::StopHighlightingSlot()
|
||||
{
|
||||
HighlightDecal->SetVisibility(false);
|
||||
HighlightedSlotIndex = FIntVector2::NoneValue;
|
||||
}
|
||||
|
||||
bool ASeedBedActor::IsSlotFree(FIntVector2 SlotCoordinate) const
|
||||
{
|
||||
const FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||
|
||||
return Slot != nullptr && !SlotHasPlant(*Slot);
|
||||
}
|
||||
|
||||
int32 ASeedBedActor::GetFreeSlotCount() const
|
||||
{
|
||||
int32 FreeSlotCount = 0;
|
||||
|
||||
for (const TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||
{
|
||||
if (!SlotHasPlant(SlotPair.Value))
|
||||
{
|
||||
++FreeSlotCount;
|
||||
}
|
||||
}
|
||||
|
||||
return FreeSlotCount;
|
||||
}
|
||||
bool ASeedBedActor::FindClosestFreeSlot(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate) const
|
||||
{
|
||||
bool bFoundSlot = false;
|
||||
float ClosestDistanceSquared = 0.f;
|
||||
|
||||
for (const TPair<FIntVector2, FSeedBedSeedSlot>& SlotPair : SeedBedSeedSlotsMap)
|
||||
{
|
||||
if (SlotHasPlant(SlotPair.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FVector SlotWorldLocation;
|
||||
if (!GetSlotWorldLocation(SlotPair.Key, SlotWorldLocation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float DistanceSquared = FVector::DistSquared(WorldLocation, SlotWorldLocation);
|
||||
if (!bFoundSlot || DistanceSquared < ClosestDistanceSquared)
|
||||
{
|
||||
bFoundSlot = true;
|
||||
ClosestDistanceSquared = DistanceSquared;
|
||||
OutSlotCoordinate = SlotPair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return bFoundSlot;
|
||||
}
|
||||
|
||||
bool ASeedBedActor::GetSlotWorldLocation(FIntVector2 SlotCoordinate, FVector& OutWorldLocation) const
|
||||
{
|
||||
if (!SeedBedSeedSlotsMap.Contains(SlotCoordinate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The location is derived from the grid and the actor transform, so it is valid without any component
|
||||
OutWorldLocation = GetActorTransform().TransformPosition(GetSlotRelativeLocation(SlotCoordinate));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 ASeedBedActor::ResolveGrowthStageIndex(const FSeedBedSeedSlot& Slot) const
|
||||
{
|
||||
const UItemDataAsset* SeedData = Slot.SlotData.ItemDataAsset;
|
||||
if (!IsValid(SeedData))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
// Stages are set up ordered by ChangeOnDay, so the last day that has been reached is the visible one
|
||||
int32 StageIndex = 0;
|
||||
|
||||
for (int32 Index = 0; Index < SeedData->SeedStages.Num(); ++Index)
|
||||
{
|
||||
if (Slot.SlotData.SeedAge < SeedData->SeedStages[Index].ChangeOnDay)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
StageIndex = Index;
|
||||
}
|
||||
|
||||
return StageIndex;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// PLANTING
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
bool ASeedBedActor::PlantSeedInSlot(FIntVector2 SlotCoordinate, UItemDataAsset* SeedData, bool bHighQuality)
|
||||
{
|
||||
if (!IsSlotFree(SlotCoordinate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||
if (!Slot)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A fresh plant starts at day zero, everything that is stored about it is the state of the new seed
|
||||
Slot->SlotData.ItemDataAsset = SeedData;
|
||||
Slot->SlotData.ItemAssetID = SeedData->GetPrimaryAssetId();
|
||||
Slot->SlotData.GrowTime = FMath::Max(0, SeedData->GrowTimeSelf);
|
||||
Slot->SlotData.SeedAge = 0;
|
||||
Slot->SlotData.WateredTimes = 0;
|
||||
Slot->SlotData.bBloomed = false;
|
||||
Slot->SlotData.bWateredToday = false;
|
||||
Slot->SlotData.bDied = false;
|
||||
Slot->SlotData.bHighQuality = bHighQuality;
|
||||
|
||||
Slot->SeedBedDynamicMat->SetScalarParameterValue(TEXT("Wetness"), 0.f);
|
||||
|
||||
RefreshSlotVisual(SlotCoordinate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ASeedBedActor::ClearSeedFromSlot(FIntVector2 SlotCoordinate, bool bRemovePlantMesh)
|
||||
{
|
||||
FSeedBedSeedSlot* Slot = SeedBedSeedSlotsMap.Find(SlotCoordinate);
|
||||
if (!Slot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Slot->SlotData.ItemDataAsset = nullptr;
|
||||
Slot->SlotData.ItemAssetID = FPrimaryAssetId();
|
||||
Slot->SlotData.GrowTime = 0;
|
||||
Slot->SlotData.SeedAge = 0;
|
||||
Slot->SlotData.WateredTimes = 0;
|
||||
Slot->SlotData.bBloomed = false;
|
||||
Slot->SlotData.bWateredToday = false;
|
||||
Slot->SlotData.bDied = false;
|
||||
Slot->SlotData.bHighQuality = false;
|
||||
|
||||
if (bRemovePlantMesh)
|
||||
{
|
||||
RefreshSlotVisual(SlotCoordinate);
|
||||
return;
|
||||
}
|
||||
|
||||
// The mesh is left alone on purpose, a harvested plant stays visible until the slot is cleared for real
|
||||
OnSeedBedSlotChanged.Broadcast(SlotCoordinate, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -6,20 +6,242 @@
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "SeedBedActor.generated.h"
|
||||
|
||||
class ATimeOfDayManager;
|
||||
class UDecalComponent;
|
||||
class UMaterialInterface;
|
||||
class UStaticMesh;
|
||||
class UStaticMeshComponent;
|
||||
class UItemDataAsset;
|
||||
|
||||
/** Called every time a slot of the seed bed has been planted, cleared or visually refreshed */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSeedBedSlotChanged, FIntVector2, SlotCoordinate, bool, bHasPlant);
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FSeedBedSeedSlotData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient)
|
||||
UItemDataAsset* ItemDataAsset;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
FPrimaryAssetId ItemAssetID;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 GrowTime = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 SeedAge = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 WateredTimes = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
bool bBloomed = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
bool bWateredToday = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
bool bDied = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
bool bHighQuality = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FSeedBedSeedSlot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
FSeedBedSeedSlotData SlotData;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
UDecalComponent* SeedBedDecal = nullptr;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
UMaterialInstanceDynamic* SeedBedDynamicMat = nullptr;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
UStaticMeshComponent* PlantStaticMesh;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FSeedBedSaveData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
int32 Index = 0;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
TMap<FIntVector2, FSeedBedSeedSlotData> SlotData;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class PROJECTELERI_API ASeedBedActor : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this actor's properties
|
||||
ASeedBedActor();
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// DATA
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
/** Every slot of the seed bed, keyed by its X / Y coordinate inside the grid */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly)
|
||||
TMap<FIntVector2, FSeedBedSeedSlot> SeedBedSeedSlotsMap;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOnSeedBedSlotChanged OnSeedBedSlotChanged;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||
int32 SeedBedIndex = 0;
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// SEED BED SETUP
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
|
||||
TSoftObjectPtr<UMaterialInterface> SeedBedDecalMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
|
||||
FVector SeedBedDecalSize = FVector(40.f, 40.f, 40.f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
|
||||
FRotator SeedBedDecalRotation = FRotator(-90.f, 0.f, 0.f);
|
||||
|
||||
/** Fallback mesh used when the planted seed has no growth stage set up */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
|
||||
TSoftObjectPtr<UStaticMesh> DefaultPlantMesh;
|
||||
|
||||
/** Dimensions of the grid generated by GenerateSeedBedGrid, X = columns, Y = rows */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Setup")
|
||||
FIntVector2 GridSize = FIntVector2(3, 3);
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// HIGHLIGHT DECAL
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Highlight")
|
||||
TSoftObjectPtr<UMaterialInterface> HighlightDecalMaterial;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Highlight")
|
||||
FVector HighlightDecalSize = FVector(40.f, 40.f, 40.f);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seed Bed|Highlight")
|
||||
FRotator HighlightDecalRotation = FRotator(-90.f, 0.f, 0.f);
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// GRID GENERATION
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
/** Generates the seed bed grid using the GridSize set up above (editor helper) */
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Seed Bed|Setup")
|
||||
void GenerateSeedBedGrid();
|
||||
|
||||
/** Generates / regenerates the seed bed grid with an explicit size (editor helper) */
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Seed Bed|Setup")
|
||||
void GenerateSeedBedGridSized(int32 Columns, int32 Rows, bool bRebuildExistingSlots);
|
||||
|
||||
/** Destroys every slot of the seed bed and clears the slot map (editor helper) */
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Seed Bed|Setup")
|
||||
void ClearSeedBedGrid();
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
|
||||
int32 GetSlotCount() const { return SeedBedSeedSlotsMap.Num(); }
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// PLANTING
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/** Plants a seed into the given slot, returns false when the slot is unknown or already taken */
|
||||
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
|
||||
bool PlantSeedInSlot(FIntVector2 SlotCoordinate, UItemDataAsset* SeedData, bool bHighQuality = false);
|
||||
|
||||
/** Empties a slot, optionally removing the plant mesh as well */
|
||||
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
|
||||
void ClearSeedFromSlot(FIntVector2 SlotCoordinate, bool bRemovePlantMesh = true);
|
||||
|
||||
/** Finds the free slot that is closest to the given world location */
|
||||
UFUNCTION(BlueprintCallable, Category = "Seed Bed")
|
||||
bool FindClosestFreeSlot(const FVector& WorldLocation, FIntVector2& OutSlotCoordinate) const;
|
||||
|
||||
/** World location a plant of the given slot is placed at */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
|
||||
bool GetSlotWorldLocation(FIntVector2 SlotCoordinate, FVector& OutWorldLocation) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
|
||||
bool IsSlotFree(FIntVector2 SlotCoordinate) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
|
||||
int32 GetFreeSlotCount() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
|
||||
bool HasFreeSlot() const { return GetFreeSlotCount() > 0; }
|
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
// VISUALS
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed|Highlight")
|
||||
bool IsHighlightActive() const { return bPlantingHighlightActive; }
|
||||
|
||||
/** Re-applies mesh, material and decal of a single slot */
|
||||
UFUNCTION(BlueprintCallable, Category = "Seed Bed|Visuals")
|
||||
void RefreshSlotVisual(FIntVector2 SlotCoordinate);
|
||||
|
||||
/** Re-applies mesh, material and decal of every slot of the bed */
|
||||
UFUNCTION(BlueprintCallable, Category = "Seed Bed|Visuals")
|
||||
void RefreshAllSlotVisuals();
|
||||
|
||||
/** True when the given asset can be planted in a seed bed at all */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Seed Bed")
|
||||
static bool IsPlantableSeedData(const UItemDataAsset* SeedData);
|
||||
|
||||
FIntVector2 GetHighlightedSlotIndex() const { return HighlightedSlotIndex; }
|
||||
void HighlightSlot(FIntVector2 SlotCoordinate);
|
||||
void StopHighlightingSlot();
|
||||
FSeedBedSaveData PrepareSeedSaveData();
|
||||
void PropagateSeedSaveData(const FSeedBedSaveData& SeedSaveData);
|
||||
|
||||
protected:
|
||||
// Called when the game starts or when spawned
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
virtual void OnConstruction(const FTransform& Transform) override;
|
||||
|
||||
/** Creates (or rebuilds) the components of a single slot and adds it to the slot map */
|
||||
FSeedBedSeedSlot& CreateSlot(FIntVector2 SlotCoordinate, bool bRebuildIfExisting);
|
||||
|
||||
/** Re-applies material, plant mesh and highlight decal of a slot */
|
||||
void ApplySlotVisuals(FIntVector2 SlotCoordinate, FSeedBedSeedSlot& Slot);
|
||||
|
||||
/** Resolves which growth stage mesh of the planted seed has to be shown */
|
||||
int32 ResolveGrowthStageIndex(const FSeedBedSeedSlot& Slot) const;
|
||||
|
||||
/** Name of a mesh / decal component of a slot, names have to stay stable for serialization */
|
||||
static FName GetSlotComponentName(FIntVector2 SlotCoordinate, const TCHAR* Prefix);
|
||||
|
||||
FVector GetSlotRelativeLocation(FIntVector2 SlotCoordinate) const;
|
||||
|
||||
|
||||
/** Whether the highlight decals of the bed are currently shown */
|
||||
bool bPlantingHighlightActive = false;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
FIntVector2 HighlightedSlotIndex = FIntVector2::NoneValue;
|
||||
|
||||
/** Decal used to highlight this slot when the player is looking for a place to plant a seed */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
|
||||
UDecalComponent* HighlightDecal = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
ATimeOfDayManager* TimeOfDayManager = nullptr;
|
||||
|
||||
public:
|
||||
// Called every frame
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,67 +6,71 @@
|
||||
#include "MyCharacter.h"
|
||||
#include "../System/Subsystem/ObjectPersistenceSubsystem.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "TimeOfDayManager.h"
|
||||
#include "Components/StatForgeComponent.h"
|
||||
#include "ProjectEleri/System/MainBlueprintFunctionLibrary.h"
|
||||
#include "ProjectEleri/GameObjects/EleriBaseActor.h"
|
||||
|
||||
AEleriGameState* AEleriGameState::Get(const UObject* WorldContext) {
|
||||
if (WorldContext && WorldContext->GetWorld() && WorldContext->GetWorld()->GetGameState()) {
|
||||
AEleriGameState* AEleriGameState::Get(const UObject* WorldContext)
|
||||
{
|
||||
if (WorldContext && WorldContext->GetWorld() && WorldContext->GetWorld()->GetGameState())
|
||||
{
|
||||
return Cast<AEleriGameState>(WorldContext->GetWorld()->GetGameState());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int32 AEleriGameState::GetFriendshipAmount(const UObject* WorldContext, FGameplayTag NpcTag) {
|
||||
int32 AEleriGameState::GetFriendshipAmount(const UObject* WorldContext, FGameplayTag NpcTag)
|
||||
{
|
||||
if (!Get(WorldContext)) return 0;
|
||||
|
||||
if (const int32* Value = Get(WorldContext)->FriendshipData.Find(NpcTag)) {
|
||||
if (const int32* Value = Get(WorldContext)->FriendshipData.Find(NpcTag))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 AEleriGameState::ModifyFriendshipAmount(const UObject* WorldContext, FGameplayTag NpcTag, int32 Amount) {
|
||||
int32 AEleriGameState::ModifyFriendshipAmount(const UObject* WorldContext, FGameplayTag NpcTag, int32 Amount)
|
||||
{
|
||||
if (!Get(WorldContext)) return 0;
|
||||
|
||||
if (int32* Value = Get(WorldContext)->FriendshipData.Find(NpcTag)) {
|
||||
if (int32* Value = Get(WorldContext)->FriendshipData.Find(NpcTag))
|
||||
{
|
||||
*Value += Amount;
|
||||
return *Value;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
Get(WorldContext)->FriendshipData.Add(NpcTag, Amount);
|
||||
return Amount;
|
||||
}
|
||||
}
|
||||
|
||||
void AEleriGameState::SpawnAllSeeds() {
|
||||
void AEleriGameState::PropagateAllSeedData()
|
||||
{
|
||||
ItemDatabase = GetWorld()->GetGameInstance()->GetSubsystem<UItemDatabase>();
|
||||
if (!ItemDatabase) return;
|
||||
|
||||
CurrentSpawnIndex = 0;
|
||||
GetWorld()->GetTimerManager().SetTimer(WaitActorSpawnHandle, [this]() {
|
||||
int32 CurrentLoopIndex = 0;
|
||||
for (int32 i = 0; i < SaveData.PlantedSeedsList.Num(); i++) {
|
||||
if (i == this->CurrentSpawnIndex && CurrentLoopIndex < 10 && this->CurrentSpawnIndex < SaveData.PlantedSeedsList.Num()) {
|
||||
CurrentLoopIndex++;
|
||||
this->CurrentSpawnIndex++;
|
||||
const FTransform ObjectTransform = SaveData.PlantedSeedsList[i].ObjectTransform;
|
||||
if (ASeedCarousel* SpawnedSeed = Cast<ASeedCarousel>(UGameplayStatics::BeginDeferredActorSpawnFromClass(GetWorld(), SeedCarouselBpClass, ObjectTransform))) {
|
||||
SpawnedSeed->LoadDataFromSaveStruct(SaveData.PlantedSeedsList[i], ItemDatabase);
|
||||
UE_LOG(LogTemp, Verbose, TEXT("Spawning actor: %s"), *SpawnedSeed->GetName());
|
||||
UGameplayStatics::FinishSpawningActor(SpawnedSeed, ObjectTransform, ESpawnActorScaleMethod::OverrideRootScale);
|
||||
SpawnedSeed->OnUpdatePlantEvent();
|
||||
TArray<AActor*> OutActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASeedBedActor::StaticClass(), OutActors);
|
||||
for (AActor* Actor : OutActors)
|
||||
{
|
||||
if (ASeedBedActor* SeedActor = Cast<ASeedBedActor>(Actor))
|
||||
{
|
||||
if (FSeedBedSaveData* SeedSaveData = SaveData.SeedBedData.Find(SeedActor->SeedBedIndex))
|
||||
{
|
||||
SeedActor->PropagateSeedSaveData(*SeedSaveData);
|
||||
}
|
||||
else
|
||||
{
|
||||
CacheSeedData(SeedActor->SeedBedIndex, SeedActor->PrepareSeedSaveData());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->CurrentSpawnIndex == SaveData.PlantedSeedsList.Num() - 1) {
|
||||
GetWorld()->GetTimerManager().ClearTimer(this->WaitActorSpawnHandle);
|
||||
OnPropagateSaveDataComplete.Broadcast();
|
||||
}
|
||||
}, 0.1f, true);
|
||||
}
|
||||
|
||||
void AEleriGameState::PropagateSaveData(UEleriSaveGame* InSaveData) {
|
||||
void AEleriGameState::PropagateSaveData(UEleriSaveGame* InSaveData)
|
||||
{
|
||||
check(InSaveData);
|
||||
AMyCharacter* PlayerCharacter = Cast<AMyCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
|
||||
check(PlayerCharacter);
|
||||
@@ -77,61 +81,56 @@ void AEleriGameState::PropagateSaveData(UEleriSaveGame* InSaveData) {
|
||||
|
||||
// Load all inventories
|
||||
UGameplayStatics::GetAllActorsWithTag(GetWorld(), INVENTORY_TAG, OutActors);
|
||||
for (int32 i = 0; i < OutActors.Num(); i++) {
|
||||
if (UInventoryComponent* Inv = Cast<UInventoryComponent>(OutActors[i]->GetComponentByClass(UInventoryComponent::StaticClass()))) {
|
||||
for (int32 i = 0; i < OutActors.Num(); i++)
|
||||
{
|
||||
if (UInventoryComponent* Inv = Cast<UInventoryComponent>(
|
||||
OutActors[i]->GetComponentByClass(UInventoryComponent::StaticClass())))
|
||||
{
|
||||
Inv->LoadFromSave(SaveData.InventoryData[FName(OutActors[i]->GetName())]);
|
||||
}
|
||||
}
|
||||
|
||||
//Load placed objects
|
||||
if (UObjectPersistenceSubsystem* ObjectPersistenceSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UObjectPersistenceSubsystem>()) {
|
||||
if (UObjectPersistenceSubsystem* ObjectPersistenceSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<
|
||||
UObjectPersistenceSubsystem>())
|
||||
{
|
||||
ObjectPersistenceSubsystem->LoadSavedData(SaveData.PlacedActors);
|
||||
}
|
||||
|
||||
|
||||
//Seed delete and spawn should always go last
|
||||
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASeedCarousel::StaticClass(), OutActors);
|
||||
ActorsToDestroy = OutActors;
|
||||
GetWorld()->GetTimerManager().SetTimer(WaitActorDestructionHandle, [this]() {
|
||||
for (int i = CurrentSpawnIndex; i <= 100; i++) {
|
||||
if (i < ActorsToDestroy.Num()) {
|
||||
UE_LOG(LogTemp, Verbose, TEXT("Destroying actor: %s"), *ActorsToDestroy[i]->GetName());
|
||||
ActorsToDestroy[i]->Destroy();
|
||||
CurrentSpawnIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentSpawnIndex >= ActorsToDestroy.Num()) {
|
||||
ActorsToDestroy.Empty();
|
||||
GetWorld()->GetTimerManager().ClearTimer(WaitActorDestructionHandle);
|
||||
SpawnAllSeeds();
|
||||
}
|
||||
}, 0.1f, true);
|
||||
|
||||
//Promote actor data
|
||||
for (FActorIterator It(GetWorld()); It; ++It) {
|
||||
for (FActorIterator It(GetWorld()); It; ++It)
|
||||
{
|
||||
AActor* Actor = *It;
|
||||
if (!Actor->IsValidLowLevel() || Actor->IsPendingKillPending() || !Cast<ISaveableObjectInterface>(Actor) || !Cast<IActorPersistentIdInterface>(Actor)) {
|
||||
if (!Actor->IsValidLowLevel() || Actor->IsPendingKillPending() || !Cast<ISaveableObjectInterface>(Actor) || !
|
||||
Cast<IActorPersistentIdInterface>(Actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (auto ActorSaveData = SaveData.ActorSaveData.Find(Cast<IActorPersistentIdInterface>(Actor)->GetPersistentId())) {
|
||||
if (auto ActorSaveData = SaveData.ActorSaveData.Find(
|
||||
Cast<IActorPersistentIdInterface>(Actor)->GetPersistentId()))
|
||||
{
|
||||
Cast<ISaveableObjectInterface>(Actor)->RequestLoad(*ActorSaveData);
|
||||
}
|
||||
}
|
||||
|
||||
//Game event data
|
||||
if (GetWorld()->GetGameInstance()) {
|
||||
if (UGameEventSubsystem* GES = GetWorld()->GetGameInstance()->GetSubsystem<UGameEventSubsystem>()) {
|
||||
if (GetWorld()->GetGameInstance())
|
||||
{
|
||||
if (UGameEventSubsystem* GES = GetWorld()->GetGameInstance()->GetSubsystem<UGameEventSubsystem>())
|
||||
{
|
||||
GES->LoadGameEventMapData(SaveData.EventDataMap);
|
||||
}
|
||||
}
|
||||
|
||||
//Load player data
|
||||
PlayerCharacter->SetActorTransform(SaveData.PlayerTransform);
|
||||
if (UStatForgeComponent* StatForgeComponent = UMainBlueprintFunctionLibrary::GetStatForgeComponentFromPlayer(GetWorld())) {
|
||||
if (UStatForgeComponent* StatForgeComponent =
|
||||
UMainBlueprintFunctionLibrary::GetStatForgeComponentFromPlayer(GetWorld()))
|
||||
{
|
||||
StatForgeComponent->RemoveAllGameplayStatEffects();
|
||||
|
||||
for (const FStatRuntimeDef& Stat : SaveData.StatData) {
|
||||
for (const FStatRuntimeDef& Stat : SaveData.StatData)
|
||||
{
|
||||
FGameplayStatEffectMod Mod;
|
||||
Mod.AffectedStat = Stat.StatDef.StatName;
|
||||
Mod.CalculationType = EGameplayStatEffectModCalculation::Flat;
|
||||
@@ -143,22 +142,28 @@ void AEleriGameState::PropagateSaveData(UEleriSaveGame* InSaveData) {
|
||||
|
||||
for (const FActiveGameplayStatEffect_SerializedData& SavedGe : SaveData.GameEffects)
|
||||
{
|
||||
const FGuid Id = StatForgeComponent->ApplyGameplayStatEffect(SavedGe.GameplayEffectClass, SavedGe.DataValueMap, SavedGe.StackCount);
|
||||
const FGuid Id = StatForgeComponent->ApplyGameplayStatEffect(
|
||||
SavedGe.GameplayEffectClass, SavedGe.DataValueMap, SavedGe.StackCount);
|
||||
StatForgeComponent->ModifyGeDuration(Id, SavedGe.RemainingDuration, SavedGe.NextTickDuration);
|
||||
}
|
||||
}
|
||||
|
||||
//Seed delete and spawn should always go last
|
||||
PropagateAllSeedData();
|
||||
}
|
||||
|
||||
const FSaveData& AEleriGameState::PrepareSaveData() {
|
||||
SaveData.Initialize();
|
||||
|
||||
const FSaveData& AEleriGameState::PrepareSaveData()
|
||||
{
|
||||
//SAVE TIME OF DAY
|
||||
ATimeOfDayManager* TimeOfDay = Cast<ATimeOfDayManager>(UGameplayStatics::GetActorOfClass(GetWorld(), ATimeOfDayManager::StaticClass()));
|
||||
ATimeOfDayManager* TimeOfDay = Cast<ATimeOfDayManager>(
|
||||
UGameplayStatics::GetActorOfClass(GetWorld(), ATimeOfDayManager::StaticClass()));
|
||||
check(TimeOfDay);
|
||||
SaveData.SetTimeOFDayData(TimeOfDay->GetDaysPassed(), TimeOfDay->GetMonth(), TimeOfDay->GetYear(), TimeOfDay->GetExactMinutes(), TimeOfDay->GetCurrentHours());
|
||||
SaveData.SetTimeOFDayData(TimeOfDay->GetDaysPassed(), TimeOfDay->GetMonth(), TimeOfDay->GetYear(),
|
||||
TimeOfDay->GetExactMinutes(), TimeOfDay->GetCurrentHours());
|
||||
|
||||
//SAVE BOOK
|
||||
const ABookOfAlchemyManager* Book = Cast<ABookOfAlchemyManager>(UGameplayStatics::GetActorOfClass(GetWorld(), ABookOfAlchemyManager::StaticClass()));
|
||||
const ABookOfAlchemyManager* Book = Cast<ABookOfAlchemyManager>(
|
||||
UGameplayStatics::GetActorOfClass(GetWorld(), ABookOfAlchemyManager::StaticClass()));
|
||||
check(Book);
|
||||
SaveData.SetBookProgressionData(Book->BookProgression);
|
||||
|
||||
@@ -166,24 +171,32 @@ const FSaveData& AEleriGameState::PrepareSaveData() {
|
||||
|
||||
//SAVE INVENTORIES
|
||||
UGameplayStatics::GetAllActorsWithTag(GetWorld(), INVENTORY_TAG, OutActors);
|
||||
for (int32 i = 0; i < OutActors.Num(); i++) {
|
||||
if (UInventoryComponent* Inv = Cast<UInventoryComponent>(OutActors[i]->GetComponentByClass(UInventoryComponent::StaticClass()))) {
|
||||
for (int32 i = 0; i < OutActors.Num(); i++)
|
||||
{
|
||||
if (UInventoryComponent* Inv = Cast<UInventoryComponent>(
|
||||
OutActors[i]->GetComponentByClass(UInventoryComponent::StaticClass())))
|
||||
{
|
||||
SaveData.SetInventoryDataForActor(OutActors[i], Inv);
|
||||
}
|
||||
}
|
||||
|
||||
//SAVE SEEDS IN WORLD
|
||||
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASeedCarousel::StaticClass(), OutActors);
|
||||
for (int32 i = 0; i < OutActors.Num(); i++) {
|
||||
if (ASeedCarousel* SeedActor = Cast<ASeedCarousel>(OutActors[i])) {
|
||||
const FSeedSaveData SeedSaveData = FSeedSaveData(SeedActor);
|
||||
SaveData.SetPlantedSeedData(FName(SeedActor->GetName()), SeedSaveData);
|
||||
}
|
||||
//SAVE PLACED OBJECTS
|
||||
if (UObjectPersistenceSubsystem* ObjectPersistenceSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<
|
||||
UObjectPersistenceSubsystem>())
|
||||
{
|
||||
SaveData.SetPlacedObjectsData(ObjectPersistenceSubsystem->PrepareSaveData());
|
||||
}
|
||||
|
||||
//SAVE PLACED OBJECTS
|
||||
if (UObjectPersistenceSubsystem* ObjectPersistenceSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UObjectPersistenceSubsystem>()) {
|
||||
SaveData.SetPlacedObjectsData(ObjectPersistenceSubsystem->PrepareSaveData());
|
||||
//SAVE SEEDS
|
||||
OutActors.Empty();
|
||||
UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASeedBedActor::StaticClass(), OutActors);
|
||||
for (AActor* SeedActor : OutActors)
|
||||
{
|
||||
if (ASeedBedActor* SeedBedActor = Cast<ASeedBedActor>(SeedActor))
|
||||
{
|
||||
FSeedBedSaveData SeedSaveData = SeedBedActor->PrepareSeedSaveData();
|
||||
CacheSeedData(SeedSaveData.Index, SeedSaveData);
|
||||
}
|
||||
}
|
||||
|
||||
//SAVE ACTORS
|
||||
@@ -199,22 +212,43 @@ const FSaveData& AEleriGameState::PrepareSaveData() {
|
||||
return SaveData;
|
||||
}
|
||||
|
||||
void AEleriGameState::CacheActorData(AEleriBaseActor* EleriActor) {
|
||||
void AEleriGameState::CacheActorData(AEleriBaseActor* EleriActor)
|
||||
{
|
||||
if (!IsValid(EleriActor)) return;
|
||||
if (auto FoundActorData = SaveData.ActorSaveData.Find(EleriActor->GetPersistentId())) {
|
||||
if (auto FoundActorData = SaveData.ActorSaveData.Find(EleriActor->GetPersistentId()))
|
||||
{
|
||||
*FoundActorData = Cast<ISaveableObjectInterface>(EleriActor)->RequestSave();
|
||||
}
|
||||
else {
|
||||
SaveData.ActorSaveData.Add(Cast<IActorPersistentIdInterface>(EleriActor)->GetPersistentId(), Cast<ISaveableObjectInterface>(EleriActor)->RequestSave());
|
||||
else
|
||||
{
|
||||
SaveData.ActorSaveData.Add(Cast<IActorPersistentIdInterface>(EleriActor)->GetPersistentId(),
|
||||
Cast<ISaveableObjectInterface>(EleriActor)->RequestSave());
|
||||
}
|
||||
}
|
||||
|
||||
bool AEleriGameState::RequestActorSaveData(const AEleriBaseActor* EleriActor, FActorSaveData& OutData) const {
|
||||
bool AEleriGameState::RequestActorSaveData(const AEleriBaseActor* EleriActor, FActorSaveData& OutData) const
|
||||
{
|
||||
if (!IsValid(EleriActor)) return false;
|
||||
|
||||
if (auto FoundActorData = SaveData.ActorSaveData.Find(EleriActor->GetPersistentId())) {
|
||||
if (auto FoundActorData = SaveData.ActorSaveData.Find(EleriActor->GetPersistentId()))
|
||||
{
|
||||
OutData = *FoundActorData;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AEleriGameState::CacheSeedData(int32 Index, const FSeedBedSaveData& Data)
|
||||
{
|
||||
SaveData.SetPlantedSeedData(Index, Data);
|
||||
}
|
||||
|
||||
bool AEleriGameState::GetSeedData(int32 Index, FSeedBedSaveData& OutData)
|
||||
{
|
||||
if (const FSeedBedSaveData* FoundData = SaveData.SeedBedData.Find(Index))
|
||||
{
|
||||
OutData = *FoundData;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "ProjectEleri/DialogueSystem/DialogueWidget.h"
|
||||
#include "Subsystem/InteractionSubsystem.h"
|
||||
#include "Interface/InteractableActorInterface.h"
|
||||
#include "EngineUtils.h"
|
||||
#include "ProjectEleri/Items/SeedBedActor.h"
|
||||
|
||||
AEleriPlayerController::AEleriPlayerController(const FObjectInitializer &ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
@@ -188,6 +190,11 @@ void AEleriPlayerController::SetupInputComponent()
|
||||
PlayerEnhancedInputComponent->BindAction(WaterSeedAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnHarvestSeed);
|
||||
}
|
||||
|
||||
if (PlantSeedAction)
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(PlantSeedAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnPlantSeedAction);
|
||||
}
|
||||
|
||||
if (UiBackAction)
|
||||
{
|
||||
PlayerEnhancedInputComponent->BindAction(UiBackAction, ETriggerEvent::Started, this, &AEleriPlayerController::OnControllerBackAction);
|
||||
@@ -1125,7 +1132,7 @@ void AEleriPlayerController::OnInteract(const FInputActionValue &Value)
|
||||
return;
|
||||
|
||||
if (PlayerCharacter->GetIsPlanting())
|
||||
PlayerCharacter->PlantSeed();
|
||||
TryPlantSelectedSeed();
|
||||
|
||||
// Ask the InteractionSystem plugin's subsystem for the closest valid interactable
|
||||
// and fire the interaction on it if we have one.
|
||||
@@ -1152,6 +1159,115 @@ void AEleriPlayerController::OnHarvestSeed(const FInputActionValue &Value)
|
||||
{
|
||||
}
|
||||
|
||||
// PLANTING
|
||||
|
||||
void AEleriPlayerController::SetSeedToPlant(UItemDataAsset *SeedData, bool bHighQuality)
|
||||
{
|
||||
// Anything that is not a seed that fits into a seed bed is rejected, the UI cannot leave us in a bad state
|
||||
SeedToPlant = ASeedBedActor::IsPlantableSeedData(SeedData) ? SeedData : nullptr;
|
||||
bSeedToPlantHighQuality = IsValid(SeedToPlant) && bHighQuality;
|
||||
|
||||
// Selecting a seed is what starts the planting, the beds in range light up for it right away
|
||||
if (IsValid(SeedToPlant))
|
||||
{
|
||||
SetPlantingMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AEleriPlayerController::ClearSeedToPlant()
|
||||
{
|
||||
if (!IsValid(SeedToPlant) && !bSeedToPlantHighQuality)
|
||||
return;
|
||||
|
||||
SeedToPlant = nullptr;
|
||||
bSeedToPlantHighQuality = false;
|
||||
|
||||
// Nothing is being planted anymore, so the beds stop being highlighted
|
||||
if (IsPlantingModeActive())
|
||||
{
|
||||
SetPlantingMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool AEleriPlayerController::IsPlantingModeActive() const
|
||||
{
|
||||
return IsValid(PlayerCharacter) && PlayerCharacter->GetIsPlanting();
|
||||
}
|
||||
|
||||
void AEleriPlayerController::SetPlantingMode(bool bActive)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter))
|
||||
return;
|
||||
|
||||
PlayerCharacter->SetPlanting(bActive);
|
||||
|
||||
// Planting gets its own context so it can take a key over without touching the other contexts
|
||||
if (IsValid(PlantingContext))
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem *Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
|
||||
{
|
||||
if (bActive)
|
||||
{
|
||||
Subsystem->AddMappingContext(PlantingContext, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
Subsystem->RemoveMappingContext(PlantingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AEleriPlayerController::TryPlantSelectedSeed()
|
||||
{
|
||||
if (!IsValid(PlayerCharacter))
|
||||
return;
|
||||
|
||||
// A seed that is not plantable anymore, for example when the inventory was emptied, drops out of planting
|
||||
if (!ASeedBedActor::IsPlantableSeedData(SeedToPlant))
|
||||
{
|
||||
ClearSeedToPlant();
|
||||
return;
|
||||
}
|
||||
|
||||
ASeedBedActor *SeedBed = PlayerCharacter->GetClosestSeedBedActor();
|
||||
if (!IsValid(SeedBed))
|
||||
return;
|
||||
|
||||
const FVector PlayerLocation = PlayerCharacter->GetActorLocation();
|
||||
|
||||
FIntVector2 SlotCoordinate = SeedBed->GetHighlightedSlotIndex();
|
||||
if (SlotCoordinate == FIntVector2::NoneValue)
|
||||
return;
|
||||
|
||||
UInventoryComponent *Inventory = UMainBlueprintFunctionLibrary::GetPlayerInventory(this);
|
||||
if (!IsValid(Inventory))
|
||||
return;
|
||||
|
||||
// The seed is paid for before it is planted, so a slot can never hold a seed the inventory never had
|
||||
const FPrimaryAssetId SeedAssetId = SeedToPlant->GetPrimaryAssetId();
|
||||
if (Inventory->HasItem(SeedAssetId, true, bSeedToPlantHighQuality) <= 0)
|
||||
return;
|
||||
|
||||
if (!SeedBed->PlantSeedInSlot(SlotCoordinate, SeedToPlant, bSeedToPlantHighQuality))
|
||||
return;
|
||||
|
||||
Inventory->RemoveItemFromInventory(SeedAssetId, 1, true, bSeedToPlantHighQuality);
|
||||
|
||||
if (Inventory->HasItem(SeedAssetId, true, bSeedToPlantHighQuality) == INDEX_NONE)
|
||||
{
|
||||
ClearSeedToPlant();
|
||||
}
|
||||
}
|
||||
|
||||
void AEleriPlayerController::OnPlantSeedAction(const FInputActionValue &Value)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter))
|
||||
return;
|
||||
|
||||
TryPlantSelectedSeed();
|
||||
}
|
||||
|
||||
void AEleriPlayerController::StartMinigame(EMinigameType MinigameType, const TSoftObjectPtr<UBaseMinigameDataAsset> &DataAsset)
|
||||
{
|
||||
if (!IsValid(PlayerCharacter) ||
|
||||
|
||||
@@ -14,7 +14,7 @@ void FSaveData::Initialize()
|
||||
BookProgression.Empty();
|
||||
InventoryData.Empty();
|
||||
QuestList.Empty();
|
||||
PlantedSeedsList.Empty();
|
||||
SeedBedData.Empty();
|
||||
PlacedActors.Empty();
|
||||
ActorSaveData.Empty();
|
||||
EventDataMap.Empty();
|
||||
@@ -46,11 +46,11 @@ void FSaveData::SetInventoryDataForActor(AActor* Actor, UInventoryComponent* Inv
|
||||
InventoryData.Add(ActorID, Inventory->PrepareSaveData());
|
||||
}
|
||||
|
||||
void FSaveData::SetPlantedSeedData(FName ActorId, FSeedSaveData SeedSaveData)
|
||||
void FSaveData::SetPlantedSeedData(int32 Index, const FSeedBedSaveData& SeedSaveData)
|
||||
{
|
||||
if(ActorId.IsNone()) return;
|
||||
if(Index < 0) return;
|
||||
|
||||
PlantedSeedsList.Add(SeedSaveData);
|
||||
SeedBedData.Add(Index, SeedSaveData);
|
||||
}
|
||||
|
||||
void FSaveData::SetPlacedObjectsData(TArray<FPlaceableActorInfo> InPlacedActors)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/StatForgeComponent.h"
|
||||
#include "GameFramework/SpringArmComponent.h"
|
||||
#include "ProjectEleri/Items/SeedBedActor.h"
|
||||
|
||||
|
||||
#if WITH_EDITOR
|
||||
@@ -124,12 +125,39 @@ void AMyCharacter::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
// if (CurrentZoomTimer > 0.f) {
|
||||
// const float ZoomDelta = (NextZoomOffsetTarget * DeltaTime) / ZoomTime;
|
||||
// SpringArmRef->TargetArmLength += ZoomDelta;
|
||||
// CurrentZoomTimer -= DeltaTime;
|
||||
// UE_LOG(LogTemp, Warning, TEXT("Zooming in: ZoomDelta:%f"), ZoomDelta);
|
||||
// }
|
||||
if (bIsPlanting)
|
||||
{
|
||||
ASeedBedActor* ClosestActor = nullptr;
|
||||
float ClosestDistance = 9999999999.f;
|
||||
const FVector PointToCheck = GetActorLocation() + (GetActorForwardVector() * 100.f);
|
||||
const float ThresholdDist = FMath::Pow(1000.f, 2.f);
|
||||
|
||||
for (ASeedBedActor* SeedBeds : SeedBedActors)
|
||||
{
|
||||
const double Dist = FVector::DistSquaredXY(PointToCheck, SeedBeds->GetActorLocation());
|
||||
if (Dist <= ClosestDistance && Dist <= ThresholdDist)
|
||||
{
|
||||
ClosestDistance = Dist;
|
||||
ClosestActor = SeedBeds;
|
||||
}
|
||||
}
|
||||
|
||||
if (LastClosestSeedBedActor)
|
||||
{
|
||||
LastClosestSeedBedActor->StopHighlightingSlot();
|
||||
}
|
||||
|
||||
if (ClosestActor)
|
||||
{
|
||||
FIntVector2 OutSlotCoordinate;
|
||||
if (ClosestActor->FindClosestFreeSlot(PointToCheck, OutSlotCoordinate))
|
||||
{
|
||||
ClosestActor->HighlightSlot(OutSlotCoordinate);
|
||||
}
|
||||
}
|
||||
|
||||
LastClosestSeedBedActor = ClosestActor;
|
||||
}
|
||||
}
|
||||
|
||||
void AMyCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
@@ -305,9 +333,28 @@ void AMyCharacter::InitiateLoad()
|
||||
GameInstance->LoadGame();
|
||||
}
|
||||
|
||||
void AMyCharacter::PlantSeed()
|
||||
void AMyCharacter::SetPlanting(bool bActive)
|
||||
{
|
||||
bIs_Planting = !bIs_Planting;
|
||||
bIsPlanting = bActive;
|
||||
|
||||
if (bIsPlanting)
|
||||
{
|
||||
SeedBedActors.Empty();
|
||||
TArray<AActor*> OutActors;
|
||||
UGameplayStatics::GetAllActorsOfClass(this, ASeedBedActor::StaticClass(), OutActors);
|
||||
for (AActor* Actor : OutActors)
|
||||
{
|
||||
SeedBedActors.Add(Cast<ASeedBedActor>(Actor));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LastClosestSeedBedActor)
|
||||
{
|
||||
LastClosestSeedBedActor->StopHighlightingSlot();
|
||||
LastClosestSeedBedActor = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AMyCharacter::BeginWatering()
|
||||
|
||||
@@ -27,20 +27,22 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
|
||||
TSubclassOf<ASeedCarousel> SeedCarouselBpClass;
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Game State", meta=(WorldContext="WorldContext", GameplayTagFilter = "Characters"))
|
||||
static int32 GetFriendshipAmount(const UObject* WorldContext, FGameplayTag NpcTag);
|
||||
UFUNCTION(BlueprintCallable, Category = "Game State", meta = (WorldContext = "WorldContext", GameplayTagFilter = "Characters"))
|
||||
static int32 ModifyFriendshipAmount(const UObject* WorldContext, FGameplayTag NpcTag, int32 Amount);
|
||||
|
||||
void PropagateSaveData(UEleriSaveGame* InSaveData);
|
||||
void SpawnAllSeeds();
|
||||
void PropagateAllSeedData();
|
||||
const FSaveData& PrepareSaveData();
|
||||
|
||||
//Caches actor data but doesn't save it to disk
|
||||
void CacheActorData(AEleriBaseActor* EleriActor);
|
||||
bool RequestActorSaveData(const AEleriBaseActor* EleriActor, FActorSaveData& OutData) const;
|
||||
|
||||
void CacheSeedData(int32 Index, const FSeedBedSaveData& Data);
|
||||
bool GetSeedData(int32 Index, FSeedBedSaveData& OutData);
|
||||
|
||||
UPROPERTY()
|
||||
FOnPropagateSaveDataComplete OnPropagateSaveDataComplete;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
#include "EleriPlayerController.generated.h"
|
||||
|
||||
class ASeedBedActor;
|
||||
class UItemDataAsset;
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class UiInputType : uint8 {
|
||||
NONE,
|
||||
@@ -222,6 +225,14 @@ public:
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* WaterSeedAction;
|
||||
|
||||
/** Action used to plant the selected seed into the closest seed bed slot */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputAction* PlantSeedAction;
|
||||
|
||||
/** Mapping context pushed while planting, it may only contain actions that planting needs */
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Action Input")
|
||||
UInputMappingContext* PlantingContext;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Book Input")
|
||||
UInputAction* BookNextPageAction;
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Book Input")
|
||||
@@ -373,6 +384,41 @@ public:
|
||||
void CloseMinigame(int32 Score);
|
||||
FOnMinigameComplete OnMinigameComplete;
|
||||
|
||||
// PLANTING
|
||||
/** Seed the player selected in the inventory, it is what the planting action plants */
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void SetSeedToPlant(UItemDataAsset* SeedData, bool bHighQuality = false);
|
||||
|
||||
/** Forgets the selected seed, the seed beds stop being highlighted for it */
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void ClearSeedToPlant();
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
||||
UItemDataAsset* GetSeedToPlant() const { return SeedToPlant; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
||||
bool IsSeedToPlantHighQuality() const { return bSeedToPlantHighQuality; }
|
||||
|
||||
/** Plants the selected seed into the closest free slot of the closest seed bed, removes it from the inventory */
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void TryPlantSelectedSeed();
|
||||
|
||||
/** True while the player is in planting mode, the seed beds in range are highlighted then */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
||||
bool IsPlantingModeActive() const;
|
||||
|
||||
/** Enters / leaves planting mode, the highlights of the seed beds in range follow it */
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void SetPlantingMode(bool bActive);
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Planting")
|
||||
UItemDataAsset* SeedToPlant;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "Planting")
|
||||
bool bSeedToPlantHighQuality = false;
|
||||
|
||||
void OnPlantSeedAction(const FInputActionValue& Value);
|
||||
|
||||
// REMOVE MODE
|
||||
protected:
|
||||
public:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/SaveGame.h"
|
||||
#include "ProjectEleri/DialogueSystem/OrderManager.h"
|
||||
#include "ProjectEleri/DialogueSystem/QuestDatabase.h"
|
||||
#include "BookOfAlchemyManager.h"
|
||||
#include "InventoryComponent.h"
|
||||
@@ -13,6 +12,7 @@
|
||||
#include "ProjectEleri/GameEventSystem/GameEventSubsystem.h"
|
||||
#include "Data/GameplayStatEffect.h"
|
||||
#include "Data/StatForgeDefs.h"
|
||||
#include "ProjectEleri/Items/SeedBedActor.h"
|
||||
#include "ProjectEleri/System/TimeOfDayStruct.h"
|
||||
#include "EleriSaveGame.generated.h"
|
||||
|
||||
@@ -54,7 +54,7 @@ struct FSaveData {
|
||||
TArray<FQuestItem> QuestList;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Seeds")
|
||||
TArray<FSeedSaveData> PlantedSeedsList;
|
||||
TMap<int32, FSeedBedSaveData> SeedBedData;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Placed Objects")
|
||||
TArray<FPlaceableActorInfo> PlacedActors;
|
||||
@@ -79,7 +79,7 @@ struct FSaveData {
|
||||
void SetTimeOFDayData(int32 _Day, int32 _Month, int32 _Year, int32 _Minute, int32 _Hour);
|
||||
void SetBookProgressionData(TArray<FBookProgressionInfo> _BookProgression);
|
||||
void SetInventoryDataForActor(AActor* Actor, UInventoryComponent* Inventory);
|
||||
void SetPlantedSeedData(FName ActorId, FSeedSaveData SeedSaveData);
|
||||
void SetPlantedSeedData(int32 Index, const FSeedBedSaveData& SeedSaveData);
|
||||
void SetPlacedObjectsData(TArray<FPlaceableActorInfo> InPlacedActors);
|
||||
void CollectSaveableActorsData(const UWorld* World);
|
||||
void SetGameEventData(const UWorld* World);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "MyCharacter.generated.h"
|
||||
|
||||
class ASeedBedActor;
|
||||
class USpringArmComponent;
|
||||
class UStatForgeComponent;
|
||||
class AEleriPlayerController;
|
||||
@@ -51,8 +52,14 @@ protected:
|
||||
UPROPERTY()
|
||||
AActor* OverlapsWithInteractable;
|
||||
|
||||
//PLANTING STUFF START
|
||||
UPROPERTY()
|
||||
bool bIs_Planting;
|
||||
bool bIsPlanting = false;
|
||||
UPROPERTY()
|
||||
TArray<ASeedBedActor*> SeedBedActors;
|
||||
UPROPERTY()
|
||||
ASeedBedActor* LastClosestSeedBedActor = nullptr;
|
||||
//PLANTING STUFF END
|
||||
|
||||
UPROPERTY()
|
||||
AWaterBall* WaterBallSpawned;
|
||||
@@ -226,8 +233,13 @@ public:
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void InitiateLoad();
|
||||
|
||||
void PlantSeed();
|
||||
inline bool GetIsPlanting() { return bIs_Planting; }
|
||||
/** Sets the planting flag directly, the player controller uses this to keep both sides in sync */
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void SetPlanting(bool bActive);
|
||||
ASeedBedActor* GetClosestSeedBedActor() { return LastClosestSeedBedActor; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure)
|
||||
inline bool GetIsPlanting() { return bIsPlanting; }
|
||||
|
||||
void BeginWatering();
|
||||
void EndWatering() const;
|
||||
|
||||
Reference in New Issue
Block a user