// © 2025 Naked People Team. All Rights Reserved. #include "Commission.h" #include "CommissionObjective.h" void UCommission::Accept(ANakedDesireCharacter* InPlayer) { if (State != ECommissionState::Offered) return; Player = InPlayer; SetState(ECommissionState::Accepted); ArmObjectives(); // Arming evaluates immediately, so a commission already satisfied on accept completes now. if (AreAllObjectivesSatisfied()) Complete(); } void UCommission::Abandon() { if (State != ECommissionState::Accepted) return; DisarmObjectives(); SetState(ECommissionState::Offered); } void UCommission::Expire() { if (State != ECommissionState::Accepted) return; DisarmObjectives(); SetState(ECommissionState::Expired); } void UCommission::RestoreState(ECommissionState SavedState, ANakedDesireCharacter* InPlayer) { Player = InPlayer; if (SavedState == ECommissionState::Accepted) { SetState(ECommissionState::Accepted); ArmObjectives(); if (AreAllObjectivesSatisfied()) Complete(); } else { SetState(SavedState); } } void UCommission::Complete() { if (State != ECommissionState::Accepted) return; // already resolved — guards against double completion / double payout DisarmObjectives(); SetState(ECommissionState::Completed); OnCompleted.Broadcast(this); } bool UCommission::AreAllObjectivesSatisfied() const { if (Objectives.Num() == 0) return false; // a commission with no objectives never auto-completes for (const UCommissionObjective* Objective : Objectives) { if (!Objective || !Objective->IsSatisfied()) return false; } return true; } void UCommission::ArmObjectives() { // Subscribe to all objectives up front; activation differs by mode. for (UCommissionObjective* Objective : Objectives) { if (Objective) Objective->OnStateChanged.AddUObject(this, &UCommission::HandleObjectiveStateChanged); } if (bSequentialObjectives) { ActiveObjectiveIndex = INDEX_NONE; AdvanceSequential(); } else { for (UCommissionObjective* Objective : Objectives) { if (Objective) Objective->Activate(Player); } } } void UCommission::DisarmObjectives() { for (UCommissionObjective* Objective : Objectives) { if (!Objective) continue; Objective->OnStateChanged.RemoveAll(this); Objective->Deactivate(); } ActiveObjectiveIndex = INDEX_NONE; } void UCommission::AdvanceSequential() { for (int32 Index = 0; Index < Objectives.Num(); ++Index) { UCommissionObjective* Objective = Objectives[Index]; if (!Objective) continue; if (!Objective->IsSatisfied()) { // Activate the first unsatisfied step (only if it isn't already the armed one — Activate // resets progress, so re-activating the live objective would wipe its hold timer). if (ActiveObjectiveIndex != Index) { ActiveObjectiveIndex = Index; Objective->Activate(Player); } return; } } ActiveObjectiveIndex = INDEX_NONE; Complete(); } void UCommission::SetState(ECommissionState NewState) { State = NewState; OnStateChanged.Broadcast(this); } void UCommission::HandleObjectiveStateChanged(UCommissionObjective* Objective) { if (State != ECommissionState::Accepted) return; if (bSequentialObjectives) AdvanceSequential(); // the armed step finished -> activate the next, or complete else if (AreAllObjectivesSatisfied()) Complete(); }