Block 3 — Raid Board & Dispatch (2 days)

Goal: Player walks to the Raid Board, enters the overhead city map, hovers buildings to see target info, selects a target, assigns raccoons, and dispatches. Trash is deducted, crew is flagged Raiding, and an FTNTActiveRaid lands in UTNTRaidSubsystem::ActiveRaids. Raid does not resolve yet — travel, looting, encounters, and AI pawns are Block 4.

Depends on: Block 1 (stations, city map, data model, camera swap), Block 2 (roster, Scout/Hauler types, UTNTRaccoonSubsystem).

Build order: bottom-up. Data model additions → UTNTResourceSubsystem full impl → UTNTCityMapSubsystem extensions → UTNTRaidSubsystem::DispatchRaid partial → DataAsset content → BP_Building interaction wiring → UMG widgets → escape handling. Each section only references things defined above it.

Naming: all gameplay types carry the TNT prefix (UTNT…, FTNT…, ETNT…). Standardised in Block 02; this spec follows the same convention.


Deliverables

Data model additions (C++)

UTNTRaidTargetDataAsset (declared in Block 01) — add dispatch cost field:

UPROPERTY(EditDefaultsOnly, Category = "TNT|Cost")
int32 DispatchCostTrash = 50;

Loot bias, encounter pool, and difficulty are already declared on the Block 01 header. No other struct/enum additions in Block 03.

FTNTRaidMapDisplayData and GetActiveRaidDisplayData are deferred to Block 04 alongside the active-raid monitoring HUD.

UTNTResourceSubsystem — Full Implementation (C++)

Class: UTNTResourceSubsystem : UGameInstanceSubsystem. Header: Source/TrashNTreasure/Public/Subsystems/TNTResourceSubsystem.h · Impl: Source/TrashNTreasure/Private/Subsystems/TNTResourceSubsystem.cpp.

All functions UFUNCTION(BlueprintCallable, Category = "TNT|Resource"):

FunctionSignaturePurpose
AddResourcevoid (ETNTResourceType, int32)Increment counter, broadcast OnResourceChanged
SpendResourcebool (ETNTResourceType, int32)Return false if insufficient. Decrement, broadcast
CanAffordbool (ETNTResourceType, int32) constPure check, no mutation
CanAffordCostbool (FTNTCostData) constMulti-resource check
SpendCostbool (FTNTCostData)Atomic multi-resource deduct (all-or-nothing). Single broadcast per affected resource at the end
GetAmountint32 (ETNTResourceType) constCurrent counter value

Internal state (Ledger : FTNTResourceLedger declared Block 01):

MemberTypePurpose
OnResourceChangedFOnResourceChanged (DYNAMIC_MULTICAST_DELEGATE_TwoParams)BlueprintAssignable. Params: ETNTResourceType, int32 NewAmount. Broadcast on any mutation

SpendCost implementation pattern: snapshot the ledger, apply each deduction, if any underflows restore the snapshot and return false; otherwise commit and broadcast once per affected resource type.

UTNTCityMapSubsystem — Extensions (C++)

Block 01 already shipped SetOverheadCamera, OnOverheadCameraChanged, CityCamera, CachedPlayerPawn, BuildingUnlockStates. Block 03 adds the building lookup, unlock filter, and travel-time helper:

FunctionSignaturePurpose
GetBuildingDataUTNTBuildingDataAsset* (AActor* Building) constResolve BP_Building instance → its UTNTBuildingDataAsset (set per-instance in editor)
GetUnlockedBuildingsTArray<AActor*> ()Filter CachedBuildings by BuildingUnlockStates. MVP: every entry returns true (Average Home + The Dump always unlocked)
GetEstimatedTravelTimeSecondsfloat (const TArray<FGuid>& Crew, AActor* TargetBuilding) constUNavigationSystemV1::GetPathLength(HideoutExit, Building) / (CrewAvgSpeed * SpeedScale). Returns 0 if crew empty or path invalid. Block 04 reuses this inside DispatchRaid to populate FTNTActiveRaid::TravelTime

Internal state additions:

MemberTypePurpose
CachedBuildingsTArray<TWeakObjectPtr<AActor>>All BP_Building instances in L_City. Lazy-resolved on first GetUnlockedBuildings / GetBuildingData call (same pattern as CityCamera from Block 01)
CachedHideoutExitTWeakObjectPtr<AActor>BP_HideoutExit instance in L_City. Lazy-resolved. Used by travel-time helper
SpeedScalefloat (UPROPERTY(EditDefaultsOnly, Category = "TNT|Tuning"))Travel speed tunable, default 1.0. Block 04’s AI pawn MaxSpeed reads the same value

Why on the city-map subsystem (not raid): travel time needs BP_HideoutExit + building positions + NavMesh — all L_City concerns. UTNTRaidSubsystem calls into it for dispatch in Block 04, so the helper lives where the level data lives.

Lazy resolution pattern (mirrors CityCamera):

if (!CachedBuildings.Num()):
    TArray<AActor*> Found
    UGameplayStatics::GetAllActorsOfClass(this, BP_Building::StaticClass(), Found)
    CachedBuildings = Found  // wrap in TWeakObjectPtr on insert
if (!CachedHideoutExit.IsValid()):
    UGameplayStatics::GetAllActorsOfClass(this, BP_HideoutExit::StaticClass(), Found)
    if (Found.Num() > 0): CachedHideoutExit = Found[0]

UTNTRaidSubsystem::DispatchRaid — Partial (C++)

Block 01 declared UTNTRaidSubsystem : UGameInstanceSubsystem and ActiveRaids : TArray<FTNTActiveRaid>. Block 03 adds the dispatch entry point and forward-declares the encounter delegate so Block 04 has somewhere to broadcast from without re-touching the header.

Header additions (Source/TrashNTreasure/Public/Subsystems/TNTRaidSubsystem.h):

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRaidDispatched, FGuid, RaidId);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnEncounterPending, FGuid, RaidId, UTNTEncounterDataAsset*, Encounter);
 
UFUNCTION(BlueprintCallable, Category = "TNT|Raid")
FGuid DispatchRaid(UTNTRaidTargetDataAsset* Target, const TArray<FGuid>& CrewIds);
 
UPROPERTY(BlueprintAssignable, Category = "TNT|Raid")
FOnRaidDispatched OnRaidDispatched;
 
UPROPERTY(BlueprintAssignable, Category = "TNT|Raid")
FOnEncounterPending OnEncounterPending;  // declared now, broadcast in Block 04

DispatchRaid steps (Block 03 scope):

  1. Validate Target != nullptr and CrewIds.Num() >= 1. Bail with empty FGuid() on failure.
  2. Resolve every CrewId via UTNTRaccoonSubsystem::GetRaccoon; require all Status == Idle. Reject otherwise.
  3. UTNTResourceSubsystem::CanAfford(Trash, Target->DispatchCostTrash) — bail if false.
  4. SpendResource(Trash, Target->DispatchCostTrash) — must succeed (we just checked).
  5. For each CrewId: UTNTRaccoonSubsystem::SetStatus(Id, ETNTRaccoonStatus::Raiding).
  6. Build FTNTActiveRaid:
    • RaidId = FGuid::NewGuid()
    • Target (soft ref)
    • CrewRaccoonIds = CrewIds
    • Phase = ETNTRaidPhase::Traveling (placeholder — Block 04 owns the state machine)
    • All other travel/loot/encounter fields default-zeroed; Block 04’s DispatchRaid extension fills them
  7. Append to ActiveRaids.
  8. OnRaidDispatched.Broadcast(RaidId).
  9. Return RaidId.

Block 04 extends DispatchRaid to compute travel time (via UTNTCityMapSubsystem::GetEstimatedTravelTimeSeconds), roll encounter chance, snapshot bIsDaytimeRaid, and spawn ATNTRaccoonAIPawn group at BP_HideoutExit. Those steps are spec’d in Block 04, not duplicated here.

Raid Target DataAssets

Author into Content/TrashNTreasure/Data/RaidTargets/:

AssetLoot Bias (Trash / Currency / Scrap / Food / Valuables)DifficultyEncountersDispatchCostTrashRecruitChance
DA_Target_AverageHome0.25 / 0.10 / 0.10 / 0.30 / 0.25Easy1–2500.0
DA_Target_TheDump0.50 / 0.05 / 0.20 / 0.15 / 0.10Easy0–1300.20

Encounter pool fields reference the encounter DAs below.

Encounter DataAssets (stubs)

Author into Content/TrashNTreasure/Data/Encounters/. Stat-check fields and threshold values populated per Block 04’s encounter table; resolution logic is wired in Block 04. Block 03 only needs the assets to exist so target encounter pools can reference them.

  • DA_Encounter_AggressiveDog — checks Speed / Strength.
  • DA_Encounter_TriggeredAlarm — checks Stealth / Cunning.

Building DataAssets

Author into Content/TrashNTreasure/Data/Buildings/:

  • DA_Building_AverageHome — display name “Average Home”, RaidTarget = DA_Target_AverageHome, visual type enum.
  • DA_Building_TheDump — display name “The Dump”, RaidTarget = DA_Target_TheDump, visual type enum.

Assign each to its placed BP_Building instance in L_City (Block 01 placed the instances; this block fills the DA reference).

Station DataAsset for Raid Board

Author DA_Station_RaidBoard into Content/TrashNTreasure/Data/Stations/:

  • StationType = ETNTStationType::RaidBoard
  • RelevantStat = ETNTStatType::Cunning
  • BaseStorageCap = 0
  • No upgrade tiers
  • UIWidgetClass = nullptr — Raid Board uses the camera swap from Block 01, not a widget

Assign to the placed BP_StationBase Raid Board instance in L_Hideout.

BP_Building — Hover + Select Wiring (Blueprint-only)

Block 01 placed two BP_Building instances with UStaticMeshComponent + UBoxComponent + a UTNTBuildingDataAsset reference. Block 03 layers on the interactive surface:

Material: swap the static mesh’s material for a Material Instance Dynamic created on BeginPlay. Add a scalar parameter HighlightBoost (default 0.0) to drive the hover glow.

Event graph:

Event ReceiveActorBeginCursorOver
  → Set Scalar Parameter (MID, "HighlightBoost", 1.0)
  → Get Game Instance → Get Subsystem (UTNTCityMapSubsystem)
  → Call GetBuildingData(self) → cache as BuildingData
  → Create Widget WBP_BuildingTooltip → set BuildingData → Add to Viewport
  → Cache the spawned widget on self for later removal

Event ReceiveActorEndCursorOver
  → Set Scalar Parameter (MID, "HighlightBoost", 0.0)
  → Remove cached tooltip widget from viewport, clear ref

Event ReceiveActorOnClicked
  → Create Widget WBP_CrewAssignment → set TargetBuilding = self + BuildingData
  → Add to Viewport
  → Get Player Controller → Set Input Mode Game and UI
  → Remove tooltip if still up

Cursor-over events fire because BP_PlayerController::SwitchToCityMapInput uses Set Input Mode Game and UI (see Block 01 amendment below).

UMG Widgets (Blueprint-only)

Match the Block 02 clipboard pattern: WBP_* only, no C++ widget class.

WBP_BuildingTooltipContent/TrashNTreasure/UI/WBP_BuildingTooltip.uasset

Floating widget that follows the cursor. On Tick: Get Owning Player → Get Mouse Position In Viewport → Set Position In Viewport.

Variables (set by spawner):

  • BuildingData : UTNTBuildingDataAsset*

Bindings (text blocks):

  • TargetNameBuildingData->RaidTarget->DisplayName
  • LootBias ← top-2 weighted resources joined as text (e.g. "Food + Valuables")
  • DifficultyRaidTarget->Difficulty enum to text
  • EncounterInfo ← if any open WBP_CrewAssignment reports bScoutInCrew == true: list encounter names + count from RaidTarget->EncounterPool. Else: "???"
  • LockStatus"Unlocked" / "Locked" via UTNTCityMapSubsystem::GetUnlockedBuildings membership (MVP: always unlocked)
  • DispatchCost"{N} trash" from RaidTarget->DispatchCostTrash

WBP_CrewAssignmentContent/TrashNTreasure/UI/WBP_CrewAssignment.uasset

Modal-ish overlay; city map remains visible underneath with a semi-transparent backdrop.

Layout:

  • Header: BuildingData->DisplayName, target loot bias + difficulty summary
  • Body — two columns:
    • Left: idle roster scroll (one WBP_CrewAssignment_RaccoonRow per UTNTRaccoonSubsystem::GetRaccoonsByStatus(Idle) entry)
    • Right: assigned crew list (mirrors selected raccoons, removable)
  • Footer: trash cost, estimated travel time, Dispatch button, Close button

Variables (set by spawner):

  • TargetBuilding : AActor*
  • BuildingData : UTNTBuildingDataAsset*
  • AssignedCrewIds : TArray<FGuid>

Interaction (click-to-toggle, MVP): clicking a row in the idle list adds the raccoon to AssignedCrewIds; clicking in the assigned list removes it. Drag-and-drop is post-MVP polish — keep MVP simple.

Bindings:

  • On Construct:
    • Bind UTNTRaccoonSubsystem::OnRosterChanged → refresh idle list (covers status flips mid-assignment from other systems)
    • Bind UTNTResourceSubsystem::OnResourceChanged → refresh dispatch button enabled state
  • On any assignment change:
    • Recompute estimated travel time via UTNTCityMapSubsystem::GetEstimatedTravelTimeSeconds(AssignedCrewIds, TargetBuilding). Display as "~Xm" (game minutes = real seconds; format with 0 or 1 decimal)
    • Recompute bScoutInCrew = AssignedCrewIds.Any(GetRaccoon(Id).Type == Scout)
    • Recompute affordability: UTNTResourceSubsystem::CanAfford(Trash, BuildingData->RaidTarget->DispatchCostTrash)
    • Enable Dispatch only if AssignedCrewIds.Num() >= 1 AND CanAfford
  • Dispatch click → UTNTRaidSubsystem::DispatchRaid(BuildingData->RaidTarget, AssignedCrewIds). If returned FGuid is valid: close widget + restore input to map-hover mode (Game and UI, cursor visible)
  • Close click → close widget + restore map-hover input mode
  • Escape → routes through BP_PlayerController::HandleEscape (see below); equivalent to Close

WBP_CrewAssignment_RaccoonRow — per-row sub-widget

Content/TrashNTreasure/UI/WBP_CrewAssignment_RaccoonRow.uasset. Displays:

  • Name, type icon
  • Primary + secondary stats from UTNTRaccoonSubsystem::GetEffectiveStats
  • Per-raccoon estimated solo travel time (helpful single-contributor preview)
  • Click event bubbled up to parent WBP_CrewAssignment for assign/unassign

Scout Pre-Raid Intel

Single rule: tooltip’s encounter info reads bScoutInCrew from the open WBP_CrewAssignment widget. No widget open or no Scout → "???". No new subsystem state needed; the lookup is AssignedCrewIds.ContainsByPredicate(Id => GetRaccoon(Id).Type == ETNTRaccoonType::Scout).

Escape Handling

Block 01 binds IA_Escape on BP_PlayerController directly to UTNTCityMapSubsystem::SetOverheadCamera(false). Block 03 layers UI on top — escape needs to peel the UI first.

Refactor the binding into a BP_PlayerController function HandleEscape:

Function HandleEscape
  Get Viewport → find any active WBP_CrewAssignment
  If found:
    Remove from viewport
    Set Input Mode Game and UI (back to map-hover)
    Return
  Else:
    UTNTCityMapSubsystem::SetOverheadCamera(false)

Both the IA_Escape binding and WBP_CrewAssignment’s Close button call HandleEscape so the logic stays in one place.

Block 01 Amendment (small)

BP_PlayerController::SwitchToCityMapInput (Block 01, Player Controller section) sets input mode Game Only. Change to Game and UI so cursor-over events on BP_Building route to the actor. The existing comment “city map needs cursor for click + drag” already supports this; this is a clarification, not a new requirement.


Deferred to Block 04

Spec’d here previously, moved to Block 04 because they all depend on the tick state machine, AI pawns, and encounter rolling that Block 04 introduces:

  • Travel time / encounter roll / bIsDaytimeRaid snapshot inside DispatchRaid (Block 04 extends the entry point)
  • AI pawn group spawn at BP_HideoutExit
  • FTNTActiveRaid tick state machine and phase progression
  • Encounter resolution, OnEncounterResolved, OnRaidCompleted delegates
  • City map active-raid monitoring HUD: FTNTRaidMapDisplayData, UTNTCityMapSubsystem::GetActiveRaidDisplayData, status icons, encounter alert markers
  • WBP_BackupAssignment widget and UTNTRaidSubsystem::DispatchBackup

OnEncounterPending is declared on UTNTRaidSubsystem in Block 03 (so the header doesn’t need touching again) but is only broadcast in Block 04.


Done When

  • Player walks to Raid Board, presses E → switches to overhead city camera (already shipped Block 01; verify still works after BP_Building changes)
  • Pan/zoom works on city map
  • Hovering BP_Building shows WBP_BuildingTooltip with target name, loot bias, difficulty, encounter info or "???", dispatch cost
  • Scout in assigned crew flips encounter info from "???" to real list (re-hover after assignment to refresh)
  • Clicking a building opens WBP_CrewAssignment overlay
  • Idle roster shows in left column; click to add to crew, click in crew column to remove
  • Estimated travel time recomputes live as crew composition changes
  • Dispatch button greys out when AssignedCrewIds.Num() == 0 OR !CanAfford(Trash, cost)
  • Dispatch click: trash deducted, all crew status flipped to Raiding, FTNTActiveRaid appended to ActiveRaids, OnRaidDispatched broadcast, widget closes
  • OnResourceChanged fires on trash deduction
  • Block 02 roster UI reflects status flip live (already wired to OnRosterChanged)
  • Escape closes WBP_CrewAssignment if open; otherwise swaps camera back to hideout
  • All new DataAssets author and load without errors
  • Save/load round-trip preserves ActiveRaids (Block 02’s UTNTSaveGame.ActiveRaids already declared; Block 03 confirms it populates after dispatch)

References

Raid Board · Average Home · The Dump · Raids

0 items under this folder.