Updated NPC plan

This commit is contained in:
2026-06-01 19:12:00 +03:00
parent 192bd94bb7
commit 06fb3353b2
2 changed files with 89 additions and 0 deletions
+79
View File
@@ -432,3 +432,82 @@ Use this section for in-flight decisions, blockers, and open questions that emer
- **~~README contradiction — masturbation gating.~~ RESOLVED (2026-05-30, §20 #26).** Home masturbation is always available to every player; *in-session* (public) masturbation is the Slut-path unlock. README §5.1 / §7.2 / §6.7 / §13.4 / §14.1 and §20 #26 are updated. Phase 4 / VS-2 implementation: gate only the in-session quick-action entry on Slut investment.
- **Stale §5.3 Slave path text** in the README still says "(cuffs require NPC help to remove)" — contradicts the Key + minigame removal flow. Phase 6 / Phase 10 work should not implement an NPC removal path; if encountered, treat it as stale documentation.
- _empty beyond this point_
---
## NPC Lively-City — Editor / Blueprint Handoff (added 2026-06-01)
> Self-contained checklist + behavior-tree design for finishing the NPC crowd + Walker/Stalker behavior **in-editor on another machine**. The C++ foundation already exists in `Source/NakedDesire/NPC/`: `NPCType.h` (`ENPCType`), `NPCTypeDefinition` (DataAsset), `ANPC` accessors + pooling hooks, `NPCDirectorSubsystem` + `NPCDirectorConfig`, and per-observer observation weight in `StatsManager`. Everything below is editor/Blueprint work **except** the one flagged code prerequisite.
### 0. Prerequisite — perception → blackboard wire ✅ DONE (2026-06-01)
`ANPCAIController::OnTargetPerceptionUpdate` now writes the `Player` blackboard key on each sight transition (set when sensed, cleared when lost) and resets `bHasReacted` on loss, so the BT below can branch. **The blackboard keys `Player` (Object) and `bHasReacted` (Bool) must exist in `BB_NPC` with those exact names** or the writes silently no-op.
### 1. Data assets to author
- [ ] **`DA_Walker`** (`UNPCTypeDefinition`): `Type=Walker`, `ObservationWeight≈0.35`, `bStopsToObserve=false`, `ReactionMontage`=quick glance/head-turn. (Tune weight against the VS-1 dial.)
- [ ] **`DA_Stalker`**: `Type=Stalker`, `ObservationWeight≈1.5`, `bStopsToObserve=true`, `ObserveDurationSeconds≈5`, `ReactionMontage`=notice/stare.
- [ ] **`DA_NPCDirector`** (`UNPCDirectorConfig`): set `MaxNPCs` (≥ largest target), `TargetCountDay`/`TargetCountNight`, `SpawnRadiusMin`/`Max`, `DespawnRadius` (> Max), `UpdateInterval` (~0.5s), and a weighted `SpawnTable` (Walker class high weight, Stalker low). Tune all values.
### 2. NPC blueprint classes
- [ ] **`BP_NPC_Walker` / `BP_NPC_Stalker`** (subclasses of `ANPC`): assign the matching `NPCTypeDefinition`, the final lightweight **skeletal mesh + standard locomotion AnimBP** (no MetaHuman, no motion matching). URO/`OnlyTickPoseWhenRendered` is already set in C++.
- [ ] Ensure `BP_NPCController` (the `ANPCAIController` BP) does **not** auto-run the BT on possess — the pool hooks (§5) own BT start/stop.
### 3. GameInstance
- [ ] On the project GameInstance BP (`UNakedDesireGameInstance`), assign `DA_NPCDirector` to the **`NPCDirector`** property (same place `CommissionBoard` is set).
### 4. Level
- [ ] **Remove `BPA_NPCSpawner`** from the level — `UNPCDirectorSubsystem` replaces it (world subsystem, auto-instantiated, no placement).
- [ ] Place `BP_NPCTargetLocation` wander destinations around the streets; confirm the **NavMesh** covers the playable area (the director spawns on reachable nav points).
### 5. Pool lifecycle (events on `BP_NPC_*`)
- [ ] **`OnActivatedFromPool`** → `Run Behavior Tree (BT_NPC)`, set `bHasReacted=false`, optionally pick an initial destination.
- [ ] **`OnDeactivatedToPool`** → `Stop Logic` on the brain + `Clear Focus` (so hidden/pooled NPCs don't tick AI).
### 6. Behavior tree (`BB_NPC` / `BT_NPC`)
**Blackboard keys:**
| Key | Type | Purpose |
|-----|------|---------|
| `Player` | Object | Perceived player pawn — set/cleared by the §0 code wire. Drives the react branch. |
| `TargetLocation` | Object *(or Vector)* | Current wander destination (`BP_NPCTargetLocation`). |
| `SpawnLocation` | Vector | Leash/home point (already used). |
| `bHasReacted` | Bool | Whether the notice reaction already played this sighting (prevents replay). |
**Tree shape:**
```
Root
└── Selector "what should I do"
├── [A] Sequence "stop & observe" ← Stalker-type only
│ decorators:
│ • Blackboard: Player Is Set (Observer Aborts: Both)
│ • ShouldStopToObserve == true (BP decorator, calls pawn fn)
│ children:
│ ├── BTT_PlayNoticeReaction (guard: bHasReacted == false → play montage, set true)
│ ├── BTT_StopMovement
│ ├── BTT_StareAtPlayer (SetFocus = Player, face them) [exists]
│ └── BTT_Wait (GetObserveDuration) then ClearFocus
└── [B] Sequence "wander" (loop) ← default; Walkers live here
service: BTS_NoticePlayer (glance-while-walking for Walkers)
children:
├── BTT_PickDestination → sets TargetLocation
├── MoveTo TargetLocation [built-in]
└── BTT_Wait (random idle 14s) (+ optional look-around / check-phone)
```
**Node notes:**
- **`[A]` decorators are the entire Walker/Stalker fork.** `Player Is Set` + `ShouldStopToObserve()==true` → only Stalkers (and future Snitch/Blogger) enter; Walkers fail the second decorator and fall through to wander. Set **Observer Aborts: Both** on `Player Is Set` so a Stalker interrupts its walk the instant it sees the player and resumes wandering when the player leaves.
- **`ShouldStopToObserve` decorator** = a **Blueprint decorator** (`BTDecorator_BlueprintBase`): controlled pawn → cast `ANPC` → return `ShouldStopToObserve()`. No C++ — the accessor is already `BlueprintPure`.
- **`BTT_StareAtPlayer`** (exists): `SetFocus(Player)` / `RotateToFaceBBEntry`. Follow with **Wait** whose duration = `GetObserveDuration()` (read in a tiny BP task), or fold the whole stop+face+wait+clear into one `BTT_Observe`.
- **`BTS_NoticePlayer`** (service on `[B]`, ~0.3s): if `Player Is Set` and `bHasReacted==false` → play `NPCTypeDefinition.ReactionMontage` (glance) and set `bHasReacted=true`. This gives **Walkers a reaction without stopping** (MoveTo keeps running). Stalkers react in `[A]` instead.
- **`BTT_PickDestination`**: gather all `BP_NPCTargetLocation` actors → pick random/nearest-unvisited → set `TargetLocation`. (EQS or a random reachable nav point near `SpawnLocation` are fine alternatives.)
**Why embarrassment needs no BT code:** the BT never touches `StatsManager`. A Stalker entering `[A]` stops and faces the player → sustains line-of-sight → the controller's perception keeps the player in its observer set → `ComputeObservedExposureRate` climbs at the Stalker's high `ObservationWeight`. A Walker passes with brief/broken LOS at low weight. Behavior and the dial reinforce each other automatically.
### 7. Compile & verify
- [ ] Build the C++ module (after the §0 change); fix any errors.
- [ ] Walk a navmeshed greybox street: ~1020 NPCs wander believably and varied; Stalkers stop & stare, Walkers glance & pass.
- [ ] Stand nude near a Stalker → embarrassment climbs faster than near a Walker (observation weight) and faster than fully clothed (coverage, VS-1).
- [ ] Cross the spawn ring repeatedly → no hitch (pooling), no in-view pop (off-camera spawn).
- [ ] Day→night phase change visibly changes density.
@@ -3,6 +3,7 @@
#include "NPCAIController.h"
#include "NPC.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Perception/AISense_Sight.h"
#include "Perception/AISenseConfig_Sight.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
@@ -68,4 +69,13 @@ void ANPCAIController::OnTargetPerceptionUpdate(AActor* Actor, FAIStimulus Stimu
bCurrentlyObserving = bSensed;
PlayerCharacter->StatsManager->SetObserved(bSensed, GetPawn(), GetObservationWeight());
// Surface the sighting to the behavior tree: the react/wander branch keys off "Player", and a
// fresh sighting re-arms the one-shot notice reaction (see the BT handoff in PLAN.md).
if (UBlackboardComponent* BB = GetBlackboardComponent())
{
BB->SetValueAsObject(TEXT("Player"), bSensed ? PlayerCharacter : nullptr);
if (!bSensed)
BB->SetValueAsBool(TEXT("bHasReacted"), false);
}
}