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);- Validate raccoon exists and status == Idle (not raiding/injured/training)
UResourceSubsystem::SpendResource(Trash, TrashCost)— returns false if can’t afford- Increment
FRaccoonData::TrainingBonusfor selected stat by flat amount (+3, tunable). Base stays immutable — see Raccoon Stats - Clamp resulting
GetEffectiveStatsto 100 per stat (training past cap is wasted) - Broadcast
OnRosterChanged - 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
UStationDataAssetor 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++)
| Function | Signature | Purpose |
|---|---|---|
StartUpkeepTimer | void () | 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. |
ProcessUpkeepTick | void () | C++ — iterates all raccoons. For each non-deserted raccoon: attempt to consume 1 food from Pantry via UStationSubsystem::ModifyStorage(Pantry, -1). See logic below. |
GetDaysRemaining | int32 () | UStationSubsystem::GetCurrentStorage(Pantry) / active raccoon count. Integer division. |
GetDayLength | float () | Returns 1440 real seconds (24 real minutes = 1 full game day). Derived from Game Clock ratio. |
GetTimeUntilNextTick | float () | 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):
| Member | Type | Purpose |
|---|---|---|
OnUpkeepProcessed | FOnUpkeepProcessed (DYNAMIC_MULTICAST_DELEGATE) | BlueprintAssignable. Broadcast after each upkeep tick completes |
OnRaccoonWarning | FOnRaccoonWarning (DYNAMIC_MULTICAST_DELEGATE_TwoParams) | BlueprintAssignable. Params: FGuid RaccoonId, ERaccoonStatus Warning. Fires on hunger or desertion |
UpkeepHour | float | Game-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:
- Raids → food → Pantry → upkeep tick consumes food → need more raids
- Bigger crew = faster burn rate
- 2-raccoon crew burns 2 food/tick. 5-raccoon crew burns 5/tick.
- 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
GetEffectiveStatsand encounter checks -
OnUpkeepProcessedandOnRaccoonWarningdelegates fire correctly
References
Training Corner · Training and Breeding · Raccoon Stats · Pantry · Economy · Station Operation Model