Added commissions system
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "MissionSubsystem.h"
|
||||
|
||||
#include "Commission.h"
|
||||
#include "CommissionBoardConfig.h"
|
||||
#include "CommissionTypes.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "NakedDesire/Global/NakedDesireGameInstance.h"
|
||||
#include "NakedDesire/Global/TimeOfDaySubsystem.h"
|
||||
#include "NakedDesire/Player/NakedDesireCharacter.h"
|
||||
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
|
||||
#include "NakedDesire/SaveGame/SaveSubsystem.h"
|
||||
|
||||
void UMissionSubsystem::OnWorldBeginPlay(UWorld& InWorld)
|
||||
{
|
||||
Super::OnWorldBeginPlay(InWorld);
|
||||
|
||||
if (UTimeOfDaySubsystem* Time = InWorld.GetSubsystem<UTimeOfDaySubsystem>())
|
||||
Time->OnDayChanged.AddUniqueDynamic(this, &UMissionSubsystem::HandleDayChanged);
|
||||
|
||||
BuildBoard();
|
||||
RestoreFromSave();
|
||||
OnBoardChanged.Broadcast();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::Deinitialize()
|
||||
{
|
||||
if (const UWorld* World = GetWorld())
|
||||
{
|
||||
if (UTimeOfDaySubsystem* Time = World->GetSubsystem<UTimeOfDaySubsystem>())
|
||||
Time->OnDayChanged.RemoveDynamic(this, &UMissionSubsystem::HandleDayChanged);
|
||||
}
|
||||
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::AcceptCommission(UCommission* Commission)
|
||||
{
|
||||
if (!Commission || !OfferedCommissions.Contains(Commission))
|
||||
return;
|
||||
|
||||
ANakedDesireCharacter* Player = GetPlayer();
|
||||
if (!Player)
|
||||
return;
|
||||
|
||||
// Move to Accepted before arming: Accept() may complete synchronously (objectives already met),
|
||||
// and HandleCommissionCompleted expects the commission to be in AcceptedCommissions.
|
||||
OfferedCommissions.Remove(Commission);
|
||||
AcceptedCommissions.Add(Commission);
|
||||
Commission->OnCompleted.AddUObject(this, &UMissionSubsystem::HandleCommissionCompleted);
|
||||
|
||||
Commission->Accept(Player);
|
||||
|
||||
PersistState();
|
||||
OnBoardChanged.Broadcast();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::AbandonCommission(UCommission* Commission)
|
||||
{
|
||||
if (!Commission || !AcceptedCommissions.Contains(Commission))
|
||||
return;
|
||||
|
||||
Commission->OnCompleted.RemoveAll(this);
|
||||
Commission->Abandon();
|
||||
|
||||
AcceptedCommissions.Remove(Commission);
|
||||
OfferedCommissions.Add(Commission);
|
||||
|
||||
PersistState();
|
||||
OnBoardChanged.Broadcast();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::HandleDayChanged(int32 NewDay)
|
||||
{
|
||||
// Day rolls expire anything still accepted, then a fresh board is offered. No RestoreFromSave here:
|
||||
// the persisted records are only meaningful for a load, and PersistState below clears them.
|
||||
ExpireAccepted();
|
||||
BuildBoard();
|
||||
PersistState();
|
||||
OnBoardChanged.Broadcast();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::BuildBoard()
|
||||
{
|
||||
OfferedCommissions.Reset();
|
||||
|
||||
const UCommissionBoardConfig* Config = GetBoardConfig();
|
||||
if (!Config)
|
||||
return;
|
||||
|
||||
for (UCommission* Authored : Config->Commissions)
|
||||
{
|
||||
if (!Authored)
|
||||
continue;
|
||||
|
||||
// Duplicate so each run gets fresh objective state and an outer in this world.
|
||||
UCommission* Runtime = DuplicateObject<UCommission>(Authored, this);
|
||||
OfferedCommissions.Add(Runtime);
|
||||
}
|
||||
}
|
||||
|
||||
void UMissionSubsystem::RestoreFromSave()
|
||||
{
|
||||
const UGlobalSaveGameData* Save = GetSave();
|
||||
if (!Save)
|
||||
return;
|
||||
|
||||
ANakedDesireCharacter* Player = GetPlayer();
|
||||
|
||||
for (const FCommissionSaveRecord& Record : Save->GetCommissionRecords())
|
||||
{
|
||||
UCommission** Found = OfferedCommissions.FindByPredicate(
|
||||
[&Record](const UCommission* C) { return C && C->CommissionId == Record.CommissionId; });
|
||||
if (!Found || !*Found)
|
||||
continue;
|
||||
|
||||
UCommission* Commission = *Found;
|
||||
|
||||
if (Record.State == ECommissionState::Accepted)
|
||||
{
|
||||
OfferedCommissions.Remove(Commission);
|
||||
AcceptedCommissions.Add(Commission);
|
||||
Commission->OnCompleted.AddUObject(this, &UMissionSubsystem::HandleCommissionCompleted);
|
||||
Commission->RestoreState(ECommissionState::Accepted, Player);
|
||||
}
|
||||
else if (Record.State == ECommissionState::Completed)
|
||||
{
|
||||
OfferedCommissions.Remove(Commission);
|
||||
CompletedCommissions.Add(Commission);
|
||||
Commission->RestoreState(ECommissionState::Completed, Player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UMissionSubsystem::ExpireAccepted()
|
||||
{
|
||||
for (UCommission* Commission : AcceptedCommissions)
|
||||
{
|
||||
if (!Commission)
|
||||
continue;
|
||||
|
||||
Commission->OnCompleted.RemoveAll(this);
|
||||
Commission->Expire();
|
||||
// TODO(§13.4): apply failurePenalty here once reputation / followers exist.
|
||||
}
|
||||
|
||||
AcceptedCommissions.Reset();
|
||||
CompletedCommissions.Reset();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::HandleCommissionCompleted(UCommission* Commission)
|
||||
{
|
||||
AcceptedCommissions.Remove(Commission);
|
||||
CompletedCommissions.AddUnique(Commission);
|
||||
|
||||
ApplyReward(Commission->GetReward());
|
||||
|
||||
PersistState();
|
||||
OnCommissionCompleted.Broadcast(Commission);
|
||||
OnBoardChanged.Broadcast();
|
||||
}
|
||||
|
||||
void UMissionSubsystem::ApplyReward(const FCommissionReward& Reward)
|
||||
{
|
||||
// Money wires instantly to the save (§23 #23).
|
||||
if (UGlobalSaveGameData* Save = GetSave())
|
||||
Save->Money += Reward.Money;
|
||||
|
||||
// XP credits to the shared pool (currently a float on the character; §7.10 GAS migration later).
|
||||
if (ANakedDesireCharacter* Player = GetPlayer())
|
||||
Player->XP += Reward.XP;
|
||||
|
||||
// TODO: followers — no follower / profile system yet (Phase 8); Reward.Followers is dropped for now.
|
||||
}
|
||||
|
||||
void UMissionSubsystem::PersistState() const
|
||||
{
|
||||
UGlobalSaveGameData* Save = GetSave();
|
||||
if (!Save)
|
||||
return;
|
||||
|
||||
TArray<FCommissionSaveRecord> Records;
|
||||
for (const UCommission* Commission : AcceptedCommissions)
|
||||
{
|
||||
if (Commission)
|
||||
Records.Add({ Commission->CommissionId, ECommissionState::Accepted });
|
||||
}
|
||||
for (const UCommission* Commission : CompletedCommissions)
|
||||
{
|
||||
if (Commission)
|
||||
Records.Add({ Commission->CommissionId, ECommissionState::Completed });
|
||||
}
|
||||
|
||||
Save->SetCommissionRecords(Records);
|
||||
}
|
||||
|
||||
ANakedDesireCharacter* UMissionSubsystem::GetPlayer() const
|
||||
{
|
||||
return Cast<ANakedDesireCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
|
||||
}
|
||||
|
||||
UGlobalSaveGameData* UMissionSubsystem::GetSave() const
|
||||
{
|
||||
UGameInstance* GameInstance = GetWorld() ? GetWorld()->GetGameInstance() : nullptr;
|
||||
USaveSubsystem* SaveSubsystem = GameInstance ? GameInstance->GetSubsystem<USaveSubsystem>() : nullptr;
|
||||
return SaveSubsystem ? SaveSubsystem->GetCurrentSave() : nullptr;
|
||||
}
|
||||
|
||||
UCommissionBoardConfig* UMissionSubsystem::GetBoardConfig() const
|
||||
{
|
||||
const UNakedDesireGameInstance* GameInstance =
|
||||
Cast<UNakedDesireGameInstance>(GetWorld() ? GetWorld()->GetGameInstance() : nullptr);
|
||||
return GameInstance ? GameInstance->CommissionBoard : nullptr;
|
||||
}
|
||||
Reference in New Issue
Block a user