Block 6 — Training & Upkeep (2 days)

Goal: Player spends trash to train raccoons. Food upkeep creates survival pressure — raccoons cost food to keep, starving raccoons desert. The loop becomes real.

Depends on: Block 5 (Pantry storage, resource counters, food routing), Block 2 (roster).


Deliverables

URaccoonSubsystem::TrainStat (C++)

UFUNCTION(BlueprintCallable)
bool TrainStat(FGuid RaccoonId, EStatType Stat, int32 TrashCost);
  1. Validate raccoon exists and status == Idle (not raiding/injured/training)
  2. UResourceSubsystem::SpendResource(Trash, TrashCost) — returns false if can’t afford
  3. Increment FRaccoonData::TrainingBonus for selected stat by flat amount (+3, tunable). Base stays immutable — see Raccoon Stats
  4. Clamp resulting GetEffectiveStats to 100 per stat (training past cap is wasted)
  5. Broadcast OnRosterChanged
  6. Return true

Training is instant for MVP. No cooldown, no “Training” status flag. Add real-time cooldown post-MVP if pacing needs it.

Training Corner Station UI (UTNTTrainingUI)

UMG widget opened from Training Corner station:

  • Raccoon selector — dropdown/list of idle raccoons (from GetRaccoonsByStatus(Idle))
  • Stat selector — 5 buttons (Speed, Stealth, Strength, Cunning, Luck) showing current value
  • Cost display — “30 Trash” (tunable, stored on UStationDataAsset or hardcoded for MVP)
  • Train button — calls URaccoonSubsystem::TrainStat. Grayed out if can’t afford or no raccoon selected.
  • Result feedback — stat value visibly increments after training

DataAsset: DA_TrainingCorner, relevant stat TBD (Cunning or Strength for post-MVP coach effectiveness), no storage cap.

UUpkeepSubsystem — Full Implementation (C++)

FunctionSignaturePurpose
StartUpkeepTimervoid ()Called in ATNTGameMode::BeginPlay. Binds to the Game Clock system — upkeep fires when GameClockTime crosses the upkeep hour (default 06:00 = 360 game-minutes). 1 real min = 1 game hr, so a full day = 24 real minutes. See Game Clock for the time ratio.
ProcessUpkeepTickvoid ()C++ — iterates all raccoons. For each non-deserted raccoon: attempt to consume 1 food from Pantry via UStationSubsystem::ModifyStorage(Pantry, -1). See logic below.
GetDaysRemainingint32 ()UStationSubsystem::GetCurrentStorage(Pantry) / active raccoon count. Integer division.
GetDayLengthfloat ()Returns 1440 real seconds (24 real minutes = 1 full game day). Derived from Game Clock ratio.
GetTimeUntilNextTickfloat ()Game-clock time remaining until the next upkeep hour. Reads from clock system.

Internal state (no owned collections — operates on FRaccoonData via URaccoonSubsystem and Pantry storage via UStationSubsystem):

MemberTypePurpose
OnUpkeepProcessedFOnUpkeepProcessed (DYNAMIC_MULTICAST_DELEGATE)BlueprintAssignable. Broadcast after each upkeep tick completes
OnRaccoonWarningFOnRaccoonWarning (DYNAMIC_MULTICAST_DELEGATE_TwoParams)BlueprintAssignable. Params: FGuid RaccoonId, ERaccoonStatus Warning. Fires on hunger or desertion
UpkeepHourfloatGame-minutes value when upkeep fires (default 360 = 06:00). UPROPERTY(EditDefaultsOnly)

Upkeep Tick Logic (in ProcessUpkeepTick)

For each raccoon where Status != Deserted:
  pantryFood = UStationSubsystem::GetCurrentStorage(Pantry)
  if pantryFood > 0:
    UStationSubsystem::ModifyStorage(Pantry, -1)
    if raccoon.Status == Hungry:
      raccoon.ConsecutiveHungryTicks = 0
      URaccoonSubsystem::SetStatus(Id, Idle)  // recover from hunger
  else:
    raccoon.ConsecutiveHungryTicks++
    if raccoon.ConsecutiveHungryTicks >= 3:
      URaccoonSubsystem::RemoveRaccoon(Id)  // DESERT — permanent
      broadcast OnRaccoonWarning(Id, Deserted)
    elif raccoon.ConsecutiveHungryTicks == 2:
      broadcast OnRaccoonWarning(Id, Hungry)  // "[Name] is thinking about leaving"
    else:
      URaccoonSubsystem::SetStatus(Id, Hungry)

broadcast OnUpkeepProcessed

Hunger Effects

  • Hungry status: −20% all stats (applied in GetEffectiveStats — check status, multiply by 0.8, floor). Can’t be assigned to raids.
  • Tick 2 warning: UI notification: “[Name] is thinking about leaving”
  • Tick 3 desertion: Raccoon permanently removed. UI notification: “[Name] has deserted the crew.”

Pantry Upkeep Display (update UTNTPantryUI)

Add to existing Pantry UI from Block 5:

  • Current food / cap
  • Burn rate: “[N] food per day” (= non-deserted raccoon count)
  • Days remaining: GetDaysRemaining() — ”~[N] days of food left”
  • Time to next tick: countdown display from GetTimeUntilNextTick()
  • Color warning when days remaining < 2

Food Economy Pressure

This block makes the loop real:

  1. Raids → food → Pantry → upkeep tick consumes food → need more raids
  2. Bigger crew = faster burn rate
  3. 2-raccoon crew burns 2 food/tick. 5-raccoon crew burns 5/tick.
  4. Player must balance crew growth against food supply

Done When

  • Player trains a raccoon at Training Corner (trash deducted, stat increases, visible in roster)
  • Upkeep tick fires once per game-day via Game Clock (every 24 real minutes at 06:00 game time)
  • Each tick: 1 food consumed per raccoon from Pantry
  • Insufficient food → hungry status applied (−20% stats, can’t raid)
  • 3 consecutive hungry ticks → raccoon deserts with warning on tick 2
  • Pantry UI shows stock, burn rate, days remaining, countdown to next tick
  • 4-raccoon crew visibly burns food faster than 2-raccoon crew
  • Hunger stat penalty reflected in GetEffectiveStats and encounter checks
  • OnUpkeepProcessed and OnRaccoonWarning delegates fire correctly

References

Training Corner · Training and Breeding · Raccoon Stats · Pantry · Economy · Station Operation Model