Updated NPC plan
This commit is contained in:
@@ -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 1–4s) (+ 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: ~10–20 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user