Files
Naked-Desire/COMMISSIONS-BOARD.md
2026-06-03 15:17:02 +03:00

180 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 VS4 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 / dayroll, 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 /
dayroll 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 VS4 / VS7 exit checks).