180 lines
12 KiB
Markdown
180 lines
12 KiB
Markdown
# Commission board / accept UI — plan
|
||
|
||
Implementation plan for the forum **commission board** UI (GDD §13.1 / §13.2). **Not implemented yet** —
|
||
this is the design to review before coding. Pairs with `COMMISSIONS.md` (objective backlog) and the
|
||
runtime system in `Source/NakedDesire/Commissions/` (`UMissionSubsystem`, `UCommission`,
|
||
`UCommissionObjective`).
|
||
|
||
## Goal & scope
|
||
|
||
The forum is the player-facing surface for commissions (§13). For the vertical slice this is the
|
||
**minimal board** PLAN VS‑4 calls for: list offered commissions, accept/abandon, and track active ones
|
||
with live objective progress. No threads, no other-user feed, no profile tab yet (§13 forum scope note).
|
||
|
||
The runtime already does the work; this is **pure presentation + input**:
|
||
- `UMissionSubsystem::GetOfferedCommissions()` / `GetAcceptedCommissions()`
|
||
- `AcceptCommission(UCommission*)` / `AbandonCommission(UCommission*)`
|
||
- `OnBoardChanged` (rebuild signal) / `OnCommissionCompleted(UCommission*)` (feedback)
|
||
- per-commission: `GetTitle / GetPosterUsername / GetTier / GetReward / GetObjectives / GetState`, plus
|
||
`OnStateChanged` / `OnCompleted`
|
||
- per-objective: `GetDescription()`, `GetProgress()` (0..1), `IsSatisfied()`, `OnStateChanged`
|
||
|
||
## Reuse the established pattern
|
||
|
||
Follow `WardrobeScreenWidget` / `WardrobeInventoryWidget` exactly (CommonUI):
|
||
- Screen = `UCommonActivatableWidget` pushed onto `UGameLayoutWidget`'s `WidgetStack`.
|
||
- Lists rebuilt from the subsystem on a change delegate; rows are child widgets with click delegates.
|
||
- Subscribe in `NativeOnActivated`, unsubscribe in `NativeDestruct`.
|
||
- C++ owns logic + `BindWidget` references; Blueprint owns layout/visuals (GDD §17.5).
|
||
|
||
## Widget breakdown (new C++ classes, BP subclasses for visuals)
|
||
|
||
### 1. `UCommissionBoardScreenWidget : UCommonActivatableWidget`
|
||
The screen. Two sections: **Board** (offered) and **Active** (accepted).
|
||
- BindWidgets: `UVerticalBox* OfferedList`, `UVerticalBox* ActiveList` (or `UScrollBox`), optional
|
||
`UCommonTextBlock* EmptyBoardLabel`.
|
||
- EditDefaultsOnly: `TSubclassOf<UCommissionEntryWidget> EntryWidgetClass`.
|
||
- `NativeOnActivated`: grab `UMissionSubsystem`, bind `OnBoardChanged → Rebuild`,
|
||
`OnCommissionCompleted → HandleCompleted` (toast/flash), call `Rebuild()`.
|
||
- `NativeDestruct`: unbind both.
|
||
- `Rebuild()`: `OfferedList/ActiveList->ClearChildren()`; for each commission create an entry widget,
|
||
`Init(Commission, Subsystem)`, add to the right list. (Membership only changes on accept / complete /
|
||
abandon / day‑roll, all of which fire `OnBoardChanged`, so a full rebuild is cheap and correct.)
|
||
|
||
### 2. `UCommissionEntryWidget : UCommonUserWidget`
|
||
One commission row.
|
||
- BindWidgets: `UCommonTextBlock* TitleText, PosterText, TierText, RewardText`,
|
||
`UVerticalBox* ObjectiveList`, `UCommonButtonBase* ActionButton`, `UCommonTextBlock* ActionLabel`.
|
||
- EditDefaultsOnly: `TSubclassOf<UCommissionObjectiveRowWidget> ObjectiveRowClass`.
|
||
- `Init(UCommission*, UMissionSubsystem*)`: cache both, subscribe `Commission->OnStateChanged →
|
||
RefreshActionState`, render header (title/poster/tier), reward summary from `FCommissionReward`
|
||
(money / XP / followers — hide zero fields), build an objective row per `GetObjectives()`.
|
||
- Action button is state-driven (`Commission->GetState()`):
|
||
- `Offered` → label **Accept** → `Subsystem->AcceptCommission(Commission)`.
|
||
- `Accepted` → label **Abandon** → `Subsystem->AbandonCommission(Commission)`.
|
||
- `Completed` / `Expired` → button hidden/disabled (the rebuild usually removes it from the board).
|
||
- **Compact mode** for the HUD tracker: a `bCompact` flag (or a BP variant) hides poster / reward /
|
||
action button and shows just title + objective rows, so the tracker (#4) reuses this same widget.
|
||
- `NativeDestruct`: unsubscribe from the commission.
|
||
|
||
### 3. `UCommissionObjectiveRowWidget : UCommonUserWidget`
|
||
One objective line.
|
||
- BindWidgets: `UCommonTextBlock* DescriptionText`, `UProgressBar* ProgressBar` (BindWidgetOptional),
|
||
`UImage* DoneCheck` (BindWidgetOptional).
|
||
- `Init(UCommissionObjective*)`: set `GetDescription()`, subscribe `OnStateChanged → RefreshDone`.
|
||
- Progress: objectives have **no per-frame progress delegate** — only `OnStateChanged` (fires on
|
||
satisfied). For the continuous ones (`RequiredHoldSeconds` holds, `RunNakedDistance`) drive the bar
|
||
from `NativeTick` polling `GetProgress()` **while the screen is open** (cheap, self-contained). Binary
|
||
objectives just toggle `DoneCheck` on `OnStateChanged`. (If polling ever feels wasteful, add an
|
||
`OnProgressChanged` delegate to `UCommissionObjective` later — not needed for the slice.)
|
||
|
||
### 4. `UCommissionTrackerWidget : UCommonUserWidget`
|
||
Always-on HUD element showing accepted commissions' current objectives + live progress while the player
|
||
is in the world. **Passive HUD — not activatable, never captures input.**
|
||
- Hosted as a `BindWidget` child of the existing `UHUDWidget` (the always-on HUD), so it shows during
|
||
normal play and not just at the PC.
|
||
- BindWidgets: `UVerticalBox* TrackerList`; EditDefaultsOnly `TSubclassOf<UCommissionEntryWidget>
|
||
TrackerEntryWidgetClass`.
|
||
- `NativeConstruct`: grab `UMissionSubsystem`, bind `OnBoardChanged → Rebuild`, call `Rebuild()`.
|
||
`NativeDestruct`: unbind.
|
||
- `Rebuild()`: clear; for each `GetAcceptedCommissions()` add a `UCommissionEntryWidget` in **compact
|
||
mode** (title + objective rows, no reward/poster/button). Collapse the whole widget when the active
|
||
list is empty.
|
||
- Live progress comes from the reused objective rows' own `NativeTick` poll — no extra wiring here.
|
||
|
||
## Entry point (how the board opens)
|
||
|
||
§13 says phone or PC; the phone stack is Phase 8, so for the slice use the **apartment PC** as an
|
||
interactable, mirroring `AWardrobe`:
|
||
- New `AForumTerminal` (or `AApartmentPC`) `IInteractable` → `Interact` calls
|
||
`HUD->GetGameLayoutWidget()->OpenCommissionBoard()`.
|
||
- `UGameLayoutWidget`: add `OpenCommissionBoard()` + `TSubclassOf<UCommissionBoardScreenWidget>
|
||
CommissionBoardScreenWidgetClass` + the instance ptr (same shape as `OpenWardrobe`).
|
||
- Add a debug input action (or console exec) to open it directly for testing before the PC art exists.
|
||
- Phone "Forum app" entry point is added in Phase 8 and calls the same `OpenCommissionBoard()`.
|
||
|
||
## Data flow & live updates
|
||
|
||
- **Membership** (which list a commission is in): `OnBoardChanged` → full `Rebuild()`.
|
||
- **Action state** of a visible entry (e.g. accepted→completed while open): `Commission->OnStateChanged`
|
||
→ `RefreshActionState`; the subsequent `OnBoardChanged` (fired by the subsystem on completion) rebuilds
|
||
and moves it out of Active.
|
||
- **Objective progress**: `OnStateChanged` for done state + `NativeTick` poll for the progress bar.
|
||
- **Completion feedback**: `OnCommissionCompleted` → brief toast / reward popup (reuse existing
|
||
`UI/` + `Audio/UI` tone). Optional for the slice.
|
||
|
||
## Layout sketch
|
||
|
||
```
|
||
┌─ Commission Board ───────────────────────────────────────────┐
|
||
│ BOARD (offered) ACTIVE (accepted) │
|
||
│ ┌────────────────────────────┐ ┌──────────────────────┐ │
|
||
│ │ Flash a Stranger [Daily]│ │ Beach Streak [Daily] │ │
|
||
│ │ posted by sk8r_gurl │ │ ▸ Run 50 m naked 62% │ │
|
||
│ │ ▸ Expose boobs to 1 person │ │ ▸ while at the beach │ │
|
||
│ │ $150 · 20 XP · +5 followers│ │ [ Abandon ]│ │
|
||
│ │ [ Accept ]│ └──────────────────────┘ │
|
||
│ └────────────────────────────┘ │
|
||
│ … more offered … (empty → "No active │
|
||
│ commissions") │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
(Two columns is the simplest; a Board/Active tab pair is an equivalent alternative — see decisions.)
|
||
|
||
## C++ vs Blueprint split
|
||
|
||
- **C++ (this plan):** the four widget classes above (screen, entry, objective row, HUD tracker) +
|
||
`OpenCommissionBoard` on `GameLayoutWidget` + the PC interactable. All subsystem calls, subscriptions,
|
||
list rebuilds, and button handlers live here.
|
||
- **Blueprint:** `WBP_CommissionBoardScreen`, `WBP_CommissionEntry`, `WBP_CommissionObjectiveRow`,
|
||
`WBP_CommissionTracker` — visual layout only, satisfying the `BindWidget` names above and setting the
|
||
`…WidgetClass` defaults. `WBP_CommissionEntry` carries the full and compact looks (compact = tracker).
|
||
|
||
## Subsystem changes needed
|
||
|
||
None required — the existing API covers it. Nice-to-haves to consider during build:
|
||
- A `GetCompletedCommissions()` getter if a "completed today" section is wanted (data already tracked).
|
||
- Confirm `OnBoardChanged` fires on **every** membership change (it does: accept / abandon / complete /
|
||
day‑roll all call it).
|
||
|
||
## Edge cases to handle
|
||
|
||
- Empty board / empty active list → show a label, not a blank panel.
|
||
- A commission completing while the screen is open → entry shows done, then rebuild removes it from
|
||
Active (and the completion toast fires once).
|
||
- Day roll while the screen is open → `OnBoardChanged` rebuilds; accepted-but-unfinished entries vanish
|
||
(expired) and a fresh offered set appears. Make sure entry widgets unsubscribe on rebuild (`NativeDestruct`).
|
||
- Abandon returns the commission to the board (Offered) in the same session.
|
||
- Re-entrancy: accepting an already-satisfiable commission completes instantly; the entry should tolerate
|
||
`Accepted → Completed` within one frame (drive purely off `GetState()` + the rebuild).
|
||
|
||
## Locked decisions (slice)
|
||
|
||
1. **Layout — two columns** (Board | Active), not tabs. Everything visible at once; simplest.
|
||
2. **Entry point — apartment PC interactable** (`AForumTerminal`) calls `OpenCommissionBoard()`, plus a
|
||
debug console exec to open it before the PC art exists. Phone "Forum app" reuses the same call (Phase 8).
|
||
3. **Progress display — both:** a progress bar (driven by `NativeTick` polling `GetProgress()`) for the
|
||
continuous objectives and a checkmark for binary ones, via `BindWidgetOptional` on the row widget.
|
||
4. **Completion feedback — minimal toast** on commission completion (reuse existing `UI/` + `Audio/UI`).
|
||
5. **In-world HUD objective tracker — yes, in scope.** A separate always-on HUD widget
|
||
(`UCommissionTrackerWidget`, see breakdown #4) shows accepted commissions' current objectives + live
|
||
progress while the player is out in the world (GDD §0.5 'objective-tracker widget'). The board is for
|
||
browse/accept at the PC; the tracker is for following objectives in the field. It reuses the shared
|
||
entry/row widgets in a compact form, so it adds one widget — not a parallel hierarchy.
|
||
6. **Modal behavior — mirror the inventory/wardrobe screens.** The board uses the same input-config /
|
||
pause setup the existing `UCommonActivatableWidget` screens use; read `UInventoryScreenWidget` /
|
||
`UWardrobeScreenWidget` (+ their BP activation / `GetDesiredInputConfig` settings) and match them for a
|
||
consistent menu UX. The HUD tracker is **not** an activatable widget — it's passive HUD and never
|
||
captures input or pauses anything.
|
||
|
||
## Build order (when greenlit)
|
||
|
||
1. Read `UInventoryScreenWidget` / `UWardrobeScreenWidget` input-config + activation settings and reuse
|
||
the same convention for the board (decision #6).
|
||
2. `UCommissionObjectiveRowWidget` → `UCommissionEntryWidget` (incl. `bCompact`) → `UCommissionBoardScreenWidget`.
|
||
3. `UGameLayoutWidget::OpenCommissionBoard()` + class ref; debug console exec to open.
|
||
4. `UCommissionTrackerWidget` on `UHUDWidget` (reuses the entry/row widgets in compact mode).
|
||
5. Author the `WBP_*` assets (board screen, entry, objective row, tracker) against the BindWidget contracts.
|
||
6. `AForumTerminal` interactable (apartment PC) → `OpenCommissionBoard`.
|
||
7. Author a `UCommissionBoardConfig` with a few test commissions and play-test the full accept → satisfy →
|
||
reward → expire loop, plus the HUD tracker updating in the field (ties into the VS‑4 / VS‑7 exit checks). |