Compare commits

...

2 Commits

Author SHA1 Message Date
koritsa fd040d6a01 Updated documentation 2026-05-31 22:37:48 +03:00
koritsa 6feb7dd478 Added phone item 2026-05-31 21:54:33 +03:00
13 changed files with 241 additions and 35 deletions
Binary file not shown.
+11 -7
View File
@@ -80,7 +80,7 @@ State of the C++ module as of the latest pass. File references use `Source/Naked
### 1.1 Implemented
- **Item identity scaffold (GDD §6.1)** — `Items/ItemInstance.h` defines `UItemInstance` as an abstract `UObject` with a stable `FGuid InstanceID`, auto-assigned in `PostInitProperties` and refreshed in `PostDuplicate`. `Clothing/ClothingItemInstance.h` inherits from it and holds per-instance `Condition`, a pointer to the immutable `UClothingItem` definition, and a `StoredItems` array for container slots.
- **Item identity scaffold (GDD §6.1)** — `Items/ItemInstance.h` defines `UItemInstance` as an abstract `UObject` with a stable `FGuid InstanceID`, auto-assigned in `PostInitProperties` and refreshed in `PostDuplicate`. `Clothing/ClothingItemInstance.h` inherits from it and holds per-instance `Condition`, and a pointer to the immutable `UClothingItem` definition. (The legacy per-garment `StoredItems` container array was removed per GDD §27 — only bags hold items; the item list will live on a future bag `UItemInstance` subclass.)
- **Definition / instance split (GDD §6.1, §17.4)** — `Clothing/ClothingItem.h` is the immutable `UPrimaryDataAsset` definition. Most fields align with the current GDD: `Coverage`, `CanExpose`, `ContainerSlots` (typed S/M/L via `EGarmentContainerSlotType` + `FGarmentContainerSlot`), `ProgressionPath`, plus rendering data (`SkeletalMesh`, `Materials`, `StaticMesh`). The `IsUnderwear` and `IsRestrictive` fields also exist but **no longer match the locked spec** (§20 #20, §20 #21) — see §1.4 for the removal / migration work.
- **Progression path enum (GDD §5)** — `Progression/ProgressionPath.h` defines `EProgressionPath { None, Slut, Exhibitionist, Slave }`. Used on `UClothingItem::ProgressionPath`.
- **Body-part enum (GDD §6.3)** — `Clothing/BodyPart.h` defines `EBodyPart { None, Boobs, Ass, Genitals }`. Used on `UClothingItem::CanExpose`.
@@ -96,7 +96,7 @@ State of the C++ module as of the latest pass. File references use `Source/Naked
- **Session manager (GDD §4.1–§4.4)** — `Global/SessionManagerSubsystem.h/.cpp` (`UWorldSubsystem`). Tracks `bSessionActive`, emits `OnSessionStart` / `OnSessionEnd(ESessionLossCause)`; `ESessionLossCause = { SafeReturn, EmbarrassmentMax, EnergyZero, PoliceCapture }`. Apartment `ALocationTrigger` starts a session on exit and safely ends it on re-entry. Subscribes (next-tick after world begin play) to `UStatsManager::EmbarrassmentUpdate` (max-hit → `EmbarrassmentMax`) and `EnergyUpdate` (≤0 → `EnergyZero`). Exposes `bPoliceChaseActive` (+ setter / getter) for the §4.4 loss-precedence rule the resolver owns. Replaces the old `EndGameEmbarrassed` GameMode BP call, which `UStatsManager::IncreaseEmbarrassment` no longer invokes. The `EndGameEmbarrassed` BlueprintImplementableEvent declaration still exists on `ANakedDesireGameMode` but is now dead from C++ and should be removed once BP no longer references it.
- **Session loss resolver (GDD §4.4)** — `Global/SessionLossResolver.h/.cpp` (`UWorldSubsystem`). Single entry point `ResolveLoss(ESessionLossCause)`, bound to `USessionManagerSubsystem::OnSessionEnd`. Applies the police-chase precedence override (any cause → `PoliceCapture` while `bPoliceChaseActive`), then per cause: `EmbarrassmentMax` no-cost; `EnergyZero` destroys every world `AItemPickup` + clears its save record (guaranteed sleep loss); `PoliceCapture` deducts `PoliceCaptureMoneyPenalty` if affordable else flags a holding-cell outcome; `SafeReturn` no loss. Never strips equipped clothing. Autosaves, then broadcasts `OnSessionLossResolved(FinalCause, bWentToHoldingCell)` for the BP presentation / time-skip layer. See §1.3 for the pieces still delegated to BP / later phases.
- **Movement** — `EnhancedInput`, walk / run / crouch (`NakedDesireCharacter.cpp:115-127`), stamina-gated run (`Tick` lines 91-113).
- **Wardrobe storage + management** — `Inventory/InventorySubsystem` (`UGameInstanceSubsystem`) is the single runtime owner of the off-body store. It holds live `UItemInstance`s mirrored from `UGlobalSaveGameData::WardrobeItems` and exposes the atomic moves `AddToWardrobe` / `RemoveFromWardrobe` / `EquipFromWardrobe` / `UnequipToWardrobe`, each mutating the wardrobe + equipped save buckets together and broadcasting `OnWardrobeChanged`. `AClothingManager` stays the body-state authority (owns the `EquippedItems` bucket via the new `EquipSlot` + existing `RemoveClothing`); the bodysuit exclusion rule is now the shared static `UClothingManager::GetBodysuitExcludedSlots` and routes displaced garments back to the wardrobe instead of dropping them to the world. `AWardrobe` is reduced to an interaction shell that forwards to the subsystem (its stale `ClothingItems` array is gone). UI: `WardrobeScreenWidget` inits the inventory list and hosts the `EquipmentSlotMenuWidget` popup (same plumbing as `InventoryScreenWidget`); `WardrobeInventoryWidget` renders the live list and re-renders on `OnWardrobeChanged`; clicking a wardrobe item calls `EquipFromWardrobe`. The slot menu has a single Remove button whose `Init(slot, bAtWardrobe)` flag decides the action — store via `UnequipToWardrobe` when opened at the wardrobe, drop to the world otherwise. **Follow-ups:** `BuyItem` buy-flow not reattached (no caller yet); world pickup (`ItemPickup``TakeClothing`) still doesn't clear the `WorldItems` record; non-clothing wardrobe items (phones/toys) are stored but not yet rendered.
- **Wardrobe storage + management** — `Inventory/InventorySubsystem` (`UGameInstanceSubsystem`) is the single runtime owner of the off-body store. It holds live `UItemInstance`s mirrored from `UGlobalSaveGameData::WardrobeItems` and exposes the atomic moves `AddToWardrobe` / `RemoveFromWardrobe` / `EquipFromWardrobe` / `UnequipToWardrobe`, each mutating the wardrobe + equipped save buckets together and broadcasting `OnWardrobeChanged`. `AClothingManager` stays the body-state authority (owns the `EquippedItems` bucket via the new `EquipSlot` + existing `RemoveClothing`); the bodysuit exclusion rule is now the shared static `UClothingManager::GetBodysuitExcludedSlots` and routes displaced garments back to the wardrobe instead of dropping them to the world. `AWardrobe` is reduced to an interaction shell that forwards to the subsystem (its stale `ClothingItems` array is gone). UI: `WardrobeScreenWidget` inits the inventory list and hosts the `EquipmentSlotMenuWidget` popup (same plumbing as `InventoryScreenWidget`); `WardrobeInventoryWidget` renders the live list and re-renders on `OnWardrobeChanged`; clicking a wardrobe item calls `EquipFromWardrobe`. The slot menu has a single Remove button whose `Init(slot, bAtWardrobe)` flag decides the action — store via `UnequipToWardrobe` when opened at the wardrobe, drop to the world otherwise. **Home-storage model (GDD §6.5 / §10.4 / §28):** the wardrobe is the general home stockpile for **all non-food** items (clothing, sex toys, phones, keys, spare bags) — `WardrobeItems` is already a generic `UItemInstance` list, so this matches with no change. Food is **not** stored in the wardrobe; it lives in the **fridge** (separate fixture, pending — see §1.3). **Follow-ups:** `BuyItem` buy-flow not reattached (no caller yet); world pickup (`ItemPickup``TakeClothing`) still doesn't clear the `WorldItems` record; non-clothing wardrobe items (phones/toys) are stored but not yet rendered in the wardrobe UI.
### 1.2 Partially implemented (deviates from GDD)
@@ -123,7 +123,10 @@ State of the C++ module as of the latest pass. File references use `Source/Naked
- **NPC types (§10.2)** — only one generic `ANPC` class. No Walker / Stalker / Blogger / Snitch / Harasser, no Police, no Wanted-poster mechanic.
- **Calendar, rent, sleep (§2.4, §15.2)** — **core landed (Phase 5):** `UTimeOfDaySubsystem` (`Global/TimeOfDaySubsystem`) owns the clock, rolls the calendar at 04:00, fires `OnHourChanged`/`OnDayChanged`/`OnPhaseChanged`, charges `WEEKLY_RENT` every 7th roll with immediate eviction via `OnCampaignEnded(Evicted)`, and exposes `Sleep()`/`SkipToNextMorning()`/`SkipTime()`. Mission refresh moved to `GameMode::HandleDayChanged`. The apartment **bed interactable** (`Interactables/Bed`) is in — interacting calls `USessionLossResolver::ResolveSleepLoss()` (outside-clothing loss) then `UTimeOfDaySubsystem::Sleep()`, with a BP `PlaySleepTransition` fade hook. **Still to do:** rewire the UltraDynamicSky actor to follow `SetCurrentTime` (and stop the old BP clock from advancing independently — otherwise the clock double-advances); the §4.4 cutscenes calling into the new skip API; the ending/eviction screen reacting to `OnCampaignEnded`; daily follower deposit is stubbed (no follower count until Phase 8); phone charge-to-100% on sleep stubbed (Phase 9). `WEEKLY_RENT` is a §21 tuning placeholder.
- **Item-world AActor (§6.1)** — no `AItemActor` / `AClothingPickup` base.
- **Bag inventory (§6.4)** — absent.
- **Bag inventory (§6.4)** — absent. Per the locked model (GDD §6.4 / §27): bags are the **only** container, capacity is a flat per-bag **item count** (no S/M/L size classes), and any item type can be stored. The container concept is removed from clothing — the per-garment `UClothingItemInstance::StoredItems` array has been deleted; the item list will live on a future bag `UItemInstance` subclass.
- **Dedicated carry slots (§6.5 / §27)** — `Phone`, `Bag`, `Key` are single-item slots on the player, separate from the 18 clothing/accessory/restraint/toy slots and from the old (removed) hand slot. Deliberately **not** in `EClothingSlotType` (it's clothing-named); each carry slot is modeled independently.
- **Phone slot + phone item — done (data + slot).** `Phone/PhoneItemDefinition` (`UItemDefinition`) + `Phone/PhoneItemInstance` (`UItemInstance`, per-instance `CurrentBattery` in `FPhoneInstanceState`; `MaxBattery` on the definition). The slot lives on `UInventorySubsystem`: `EquippedPhone`, `GetEquippedPhone()`, `OnPhoneChanged`, hot-swap on equip (§9.9). `EquipFromWardrobe`/`UnequipToWardrobe` route phones to `EquipPhone`/`UnequipPhone`; the equipped phone persists via the shared `EquippedItems` save bucket and re-hydrates in `EnsureHydrated` (clothing in that bucket is still hydrated by `ClothingManager`). **Follow-ups:** no phone-slot UI widget yet and the wardrobe list (`WardrobeInventoryWidget`) renders clothing only, so a phone is seedable via `StartingSaveData.Phone` / code but not yet equippable via the wardrobe screen; battery drain/charge is Phase 8/9.
- **Bag + key slots — pending** (deferred with the bag system, see above).
- **Theft (§6.3.4)** — new chance-based model (per-tick `P_theft` after grace period) not implemented. No theft timer / probability code at all.
- **Rip & tear (§6.3.5)** — absent. `UClothingItemInstance::Condition` is read-only in code; no decrement source.
- **Expose action (§6.3.6)** — `CanExpose` data exists on the definition; runtime action absent.
@@ -131,7 +134,8 @@ State of the C++ module as of the latest pass. File references use `Source/Naked
- **Bodysuit exclusion rule (§6.5)** — when implementing the new slot enum, `ClothingManager` must auto-unequip `Top`/`Bottom`/`UnderwearTop`/`UnderwearBottom` on bodysuit equip, and vice versa.
- **Toy slots (§6.5)** — `Nipples`, `Anal`, `Vagina` are now **toy slots** (not body-clothing slots, not body-parts conflict). Independent; all three can be active. Items in these slots are sex toys from the Adult Shop (§10.4), follow standard item-identity rules, do **not** contribute to coverage, and may modify lust / embarrassment / pulse plus add audible vibration NPCs may detect.
- **Adult shop, gym, beauty salon, café, convenience store** — none.
- **Food / consumables (§6.7)** — absent. Vocabulary now locked: 2 instant effects (energy restore, lust decrease), 4 timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance). Stacking: different types parallel; same type additive up to per-type cap. Cooking minigames (slice / stir / cook) modulate buff strength; never poison.
- **Food / consumables (§6.7)** — absent. Vocabulary now locked: 2 instant effects (energy restore, lust decrease), 4 timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance). Stacking: different types parallel; same type additive up to per-type cap. Cooking minigames (slice / stir / cook) modulate buff strength; never poison. No `UFoodItemDefinition` / `UFoodItemInstance` types yet.
- **Fridge — food home storage (§6.5 / §10.4 / §28)** — absent. Per the home-storage model, food is stored **only** in the apartment fridge (never the wardrobe). Needs: an `AFridge` interactable actor (mirror `AWardrobe`), a `FridgeItems` save bucket + `Add/Remove/Get` on `UGlobalSaveGameData`, fridge storage + `OnFridgeChanged` on `UInventorySubsystem`, the minimal food item types above, and a fridge screen widget. Tracks with the Phase 10 food system.
- **Recognition, wanted state, news (§7.6–§7.7, §11.1)** — absent. Face-cover bypass via `Face` and `Eyes` slots is part of this work.
- **Underwear selling (§15.1)** — absent. Backed by the `DeliverItemTo` typed objective (player-owned source only, §13.4).
- **Endless mode flag (§3.3)** — absent.
@@ -345,12 +349,12 @@ Phase estimates are rough and assume one engineer. Adjust as we go.
### Phase 8 — Phone + forum UI + battery + livestream (34 weeks)
- Phone as an `AItemActor` (Phase 2 base) with hand / pocket / bag location semantics (§9).
- Phone as an `AItemActor` (Phase 2 base). Usable from the dedicated **phone slot** (§6.5 / §27); placed-in-world streaming exception still applies (§9.1.1).
- `PhoneSubsystem` (§17.1): tickable; owns battery %, active app, livestream session lifecycle, charger interaction.
- **Battery (§9.8):**
- Passive base drain + per-app multiplier (Forum/Bank/Maps/Health Tracker/Gallery ~1×, back-camera ~3×, livestream ~5×). Livestream stacks on top of foreground app.
- Apartment charger interactable: place phone on it, recharges at a fixed rate.
- **Portable powerbank**: consumable `UItemInstance` purchasable at Convenience Store (§10.4). Single-use, restores fixed % when consumed. S-size container slot.
- **Portable powerbank**: consumable `UItemInstance` purchasable at Convenience Store (§10.4). Single-use, restores fixed % when consumed. Stored in the equipped bag like any other item (no size classes, §6.4 / §27).
- **Hard shutdown at 0%**: all apps lock; livestream ends and deposits earnings-to-date; future tips forfeit. Phone wakes from a consumed powerbank.
- **Sleep auto-charges to 100%** (voluntary or energy-zero cutscene).
- Battery % is part of the phone's `UItemInstance` state; persists across saves.
@@ -385,7 +389,7 @@ Phase estimates are rough and assume one engineer. Adjust as we go.
### Phase 10 — Remaining systems & polish
- Bag inventory (§6.4) — uses Phase 2's `AItemActor` for world placement.
- Bag inventory (§6.4) — uses Phase 2's `AItemActor` for world placement. Bag `UItemInstance` subclass holds a count-limited list of any `UItemInstance` (no size classes); add the `Phone` / `Bag` / `Key` carry slots (§6.5 / §27).
- **Food (§6.7):**
- Implement the locked vocabulary: 2 instant effects (energy restore, lust decrease) + 4 timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance).
- Universal built-in effect: every food restores effective max energy to base max (hunger reset; §7.3 hookpoint from Phase 4).
+34 -21
View File
@@ -152,6 +152,7 @@ Each path has a level (e.g., 110). Level gates clothing, missions, and attrib
- **Phone** (one active at a time, but the player may own multiple models simultaneously — §9, §9.9)
- **Consumables** (food, §6.7)
- **Toys** — sex toys for the toy slots (§6.5). Bought at the Adult Shop (§10.4).
- **Keys** — unlock restraints (§10.4.1). Carried in the dedicated key slot (§6.5).
There are no other gadget categories. The phone is the only electronic device the player carries.
@@ -164,12 +165,9 @@ Attributes per clothing instance:
- `condition` — float [0,1]. Starts at 1.0 (new). Decreases via §6.3.4.
- `canExpose` — list of body parts this garment can momentarily reveal without unequipping (e.g., coat → boobs, ass).
- `restrictions` — list of restriction entries that fire while this item is equipped (e.g., wrist cuffs block phone use and changing top clothing; ankle cuffs block running). Most items have an empty list. See §6.3.7 for the full vocabulary.
- `containerSlots` — optional inventory (e.g., pants pocket for phone).
#### 6.3.1 Container slots
Container slot allows player to carry items there.
- `size`: S, M, L - Size of the container slot
- `count`: Count of the container slots
#### 6.3.1 No clothing inventory
Clothing provides no storage of its own — there are no pockets or per-garment container slots. The only item container in the game is the equipped bag (§6.4). All carriable items go in a bag; the phone, bag, and key have their own dedicated equipment slots (§6.5).
#### 6.3.2 Coverage resolution
For each body part `b`:
@@ -239,14 +237,16 @@ Authored examples:
**Commission interaction:** an accepted commission whose objective needs a blocked action (e.g., `PerformAction(masturbate)` with `BlockMasturbate` active) does not auto-fail; it simply cannot progress until the restriction is cleared. The commission's day / week deadline still applies normally.
### 6.4 Bags
- Bag is an equippable item with its own inventory.
- Player can equip **one** bag at a time.
- Bag can be placed in the world (set down) — persists as an AActor.
- A bag is an equippable item with its own inventory — the **only** container in the game.
- Player can equip **one** bag at a time (the bag slot, §6.5).
- **Any** item type can be stored in a bag — clothing, phone, food, toys, keys, powerbanks, etc.
- Capacity is a flat **maximum item count**, authored per bag — different bags hold different numbers of items. There are no item size classes (no S / M / L): one slot holds one item regardless of what it is.
- Bag can be placed in the world (set down) — persists as an AActor with its contents.
- Bags follow the same theft rules as clothing (§6.3.4) and the same session-loss rules as other items (§4.4 / §6.6).
### 6.5 Inventory & Equipment
The player has 18 equipment slots in four groups, plus a bag slot, a hand slot, and container slots exposed by what's currently equipped.
The player has 18 equipment slots in four groups, plus three dedicated carry slots — **phone**, **bag**, and **key**. There is no hand slot and no per-garment container slots; in-session carry storage is the equipped bag's inventory (§6.4), while owned items rest in apartment home storage (wardrobe / fridge) between sessions — see **Home storage** below.
#### Equipment slots
@@ -294,19 +294,30 @@ Restraint slots are governed by §6.3.7 (effects) and §10.4.1 (removal). `NeckR
Sex toys are bought from the Adult Shop (§10.4) and follow the standard item-identity rules (§6.1). Toys do **not** contribute to coverage (§6.3.2); a butt plug under panties leaves `UnderwearBottom`'s coverage value unchanged and the ass remains "covered" by panties for embarrassment math. Toys can be hidden beneath clothing or worn while otherwise nude — the slot is orthogonal to the body-clothing layering rules. Effects (lust modifier, embarrassment modifier, pulse modifier, audible vibration that NPCs may detect) are per-toy and authored on the toy's `UItemInstance`.
#### Other equipment
- **Bag slot** — 1 (see §6.4).
- **Hand slot** — 1. Holds one carriable item at a time (phone, food, key, etc.). Hand-dependent actions (phone use, item pickup, masturbate, expose action) are disabled when an equipped item declares the corresponding `Block*` restriction (§6.3.7); wrist cuffs are the canonical case.
#### Dedicated carry slots
- **Phone slot** — 1. Holds the active phone (§9, §9.9). The phone is usable directly from this slot; an empty slot means no phone is available. `BlockPhoneUse` (§6.3.7) still disables it while a restraint is on.
- **Bag slot** — 1. Holds one bag (§6.4). The bag's inventory is the only general carry storage during a session.
- **Key slot** — 1. Holds the key used for the restraint unlock action (§10.4.1).
#### Container slots
- Each equipped clothing item may expose container slots (e.g., pants pocket). The equipped bag exposes container slots as well. Each container slot has a size class (S / M / L, §6.3.1) that constrains what fits.
- There is no abstract "backpack" inventory. Total carry capacity = sum of container slots provided by currently equipped items.
Each of these slots accepts only its own item type — a phone in the phone slot, a bag in the bag slot, a key in the key slot. Everything else the player carries lives in the equipped bag. Actions that previously implied a free hand (item pickup, masturbate, expose action) are gated only by the relevant `Block*` restriction (§6.3.7); wrist cuffs are the canonical case.
#### Carry storage (in-session)
- The only thing the player carries items in is the equipped bag's inventory (§6.4). Clothing exposes no container slots.
- Capacity is the equipped bag's authored item-count limit. With no bag equipped, the player carries only what is worn plus what occupies the phone, bag, and key slots.
- There are no item size classes — one bag slot holds one item of any type.
#### Home storage (apartment)
Owned items rest in the apartment between sessions, in two fixtures (§10.4):
- **Wardrobe** — stores every **non-food** item: clothing, sex toys, phones, keys, and spare bags. It is the general home stockpile and the loadout point — equip clothing, swap the active phone (§9.9), grab a key or bag before leaving.
- **Fridge** — stores **food / consumables only** (§6.7). Food cannot be kept in the wardrobe.
Home storage is separate from carry storage: the wardrobe and fridge are the apartment stockpiles where owned items live, whereas the equipped bag is the limited inventory carried into a session. Items move between home storage and the player at the fixture.
### 6.6 Item loss summary table
| Situation | Outcome |
|-------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| Session ends safely | All carried / equipped / placed items persist. |
| Session lost, item equipped | Item persists, fully equipped. Loss never forcibly removes worn clothing (§4.4). |
| Session lost, item in a slot on the player (worn, phone, bag, key) | Item persists, fully equipped. Loss never forcibly removes anything held in a slot on the player (§4.4). |
| Session lost, item in equipped bag | Item persists (bag is on the player). |
| Session lost, bag placed in world | Bag and contents are subject to the same theft chance as clothing (§6.3.4) and to sleep-loss if the player sleeps before retrieving them. |
| In-session, clothing past the grace period | Each check is a flat chance of theft (§6.3.4). Picking the item back up resets the grace timer. |
@@ -452,7 +463,7 @@ Movement should be implemented as a state machine on the character with clear tr
## 9. Phone
The phone is the player's diegetic UI hub. It can be used if it is in: hand, pocket (pants with phone-sized container slot), or equipped bag. **Exception:** the livestream app (§9.1.1) continues to operate while the phone is placed in the world, so the player can keep streaming after setting the phone down for a back-camera shot.
The phone is the player's diegetic UI hub. It is usable whenever it occupies the player's **phone slot** (§6.5) — that is the active phone (§9.9). **Exception:** the livestream app (§9.1.1) continues to operate while the phone is placed in the world, so the player can keep streaming after setting the phone down for a back-camera shot.
The phone is battery-powered (§9.8). A dead phone is unusable until charged — no forum, no camera, no maps. Plan accordingly.
@@ -509,7 +520,7 @@ The phone has a finite battery that drains while powered on and recharges at the
- Livestream: ~5× — heaviest drain. Livestream is the only app that ticks in the background and stacks its drain on top of whatever app is in the foreground.
- **Charging.**
- **Apartment charger** — primary source. A charger spot in the apartment recharges the phone at a fixed rate (TBD) while the phone is placed on it.
- **Portable powerbank** — purchasable consumable (Convenience Store, §10.4). Single-use; consuming one restores a fixed percentage of charge (TBD). Occupies one container slot (size S, §6.3.1) in the player's inventory and follows normal item-identity / theft rules.
- **Portable powerbank** — purchasable consumable (Convenience Store, §10.4). Single-use; consuming one restores a fixed percentage of charge (TBD). Stored in the equipped bag like any other item (§6.4) and follows normal item-identity / theft rules.
- **Dead battery (0%).** Hard shutdown:
- All apps lock. Opening the phone shows a "dead battery" overlay; no input passes through.
- An active livestream **ends immediately**. Earnings accrued up to the cut-off are deposited; further viewers / tips that would have arrived are forfeit.
@@ -575,7 +586,7 @@ Sleeping at home fast-forwards 8 hours.
- **Wanted poster mechanic:** wanted posters spawn in the city. Tearing them all down clears the `wanted` tag and stops police spawning until a new Snitch report retriggers it.
### 10.4 Locations
- **Apartment** — safe zone. Wardrobe, bed, PC, kitchen.
- **Apartment** — safe zone. **Wardrobe** (home storage for all non-food items — clothing, sex toys, phones, keys, spare bags; §6.5), **Fridge** (home storage for food / consumables only; §6.5 / §6.7), bed, PC, kitchen.
- **Convenience store** — cooking ingredients; portable phone powerbanks (§9.8).
- **Café** — pre-made food.
- **Clothing shops** — various types, each with a different inventory mix.
@@ -960,11 +971,11 @@ Decisions previously open, now fixed:
8. **Run length:** 90-day campaign. Endless mode unlocked after first completion (rent-eviction disabled, all other systems intact). See §3.3.
9. **Cuff/restraint removal:** Key + timed unlock action only. No Helper NPC, no paid adult-shop service. See §10.4.1.
10. **Voice commands:** Not used. Hotkey-driven only.
11. **Equipment slot list:** 18 slots, locked. Body clothing (8): `Outerwear`, `Top`, `Bottom`, `UnderwearTop`, `UnderwearBottom`, `Bodysuit` (exclusive with the prior four), `Socks`, `Footwear`. Accessories (4): `Head`, `Face`, `Eyes`, `Neck`. Restraints (3, independent): `WristRestraint`, `AnkleRestraint`, `NeckRestraint`. Toys (3, independent): `Nipples`, `Anal`, `Vagina`. Face-cover bypass for recognition is driven by `Face` and `Eyes`. Toys do not contribute to coverage. See §6.5 for the full table.
11. **Equipment slot list:** 18 slots, locked. Body clothing (8): `Outerwear`, `Top`, `Bottom`, `UnderwearTop`, `UnderwearBottom`, `Bodysuit` (exclusive with the prior four), `Socks`, `Footwear`. Accessories (4): `Head`, `Face`, `Eyes`, `Neck`. Restraints (3, independent): `WristRestraint`, `AnkleRestraint`, `NeckRestraint`. Toys (3, independent): `Nipples`, `Anal`, `Vagina`. Face-cover bypass for recognition is driven by `Face` and `Eyes`. Toys do not contribute to coverage. The phone, bag, and key are carried in three separate dedicated slots (§27), not part of the 18. See §6.5 for the full table.
12. **City location list:** locked for launch. Apartment, Convenience Store, Café, Clothing Shops, Gym, Beauty Salon, Adult Shop, Electronics Shop, Streets / Parks / Alleys, **Beach**, **Train Station**, **Casino**. School exterior and hot springs (onsen) were considered and cut. Vertical slice (§18.1) covers the basic shop set; Beach, Train Station, Casino, and Electronics Shop are full-launch additions. See §10.4.
13. **Attribute level-up cost:** XP only. XP is a single shared pool (not per-path); the player chooses which path's attribute pool to spend it on, and may eventually max all three. No money, no separate currency. See §7.10.
14. **Food effect vocabulary:** locked. Two instant effects (energy restore, lust decrease) plus four timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance). Stacking: different types parallel; same type additive up to a per-type cap. No pulse buffs, no caffeine trickle. See §6.7.
15. **Carriable categories:** locked. Clothing, Bags, Phone, Consumables (food), Toys. No other gadget categories beyond the phone. See §6.2.
15. **Carriable categories:** locked. Clothing, Bags, Phone, Consumables (food), Toys, Keys. No other gadget categories beyond the phone. See §6.2.
16. **Casino:** added to the city (§10.4, §10.4.2). Main floor (slots, blackjack, roulette — money only). VIP room — **gated by a per-day entrance fee** (no permanent unlock; pay each day you want in). Permissive dress code; strip-game blackjack and roulette tables that bet clothing. No poker. Mixed pacing: slots instant, table games take in-game minutes. Net EV negative — variance is the appeal.
17. **Police capture, can't pay:** short non-interactive holding-cell cutscene, then time fast-forwards to the next morning. The night served settles the debt; no money owed afterward. Replaces the earlier "skip days proportional to unpaid amount" rule. See §4.4.
18. **Loss precedence during a police chase:** if the player is actively being chased by police (per §10.3) and any other loss condition fires (embarrassment max, energy zero), the loss resolves as **police capture** — the chase wins. The cops catch the player as they collapse. Applies even without a prior `wanted` tag. See §4.4.
@@ -976,6 +987,8 @@ Decisions previously open, now fixed:
24. **Hunger via max-energy decay.** Effective max energy decays over time (a hunger rate); eating any food restores effective max to base max as a built-in universal effect (no per-food authoring). Sleep restores current energy but does NOT reset hunger — only eating does. Floors at a TBD fraction of base max so the player can't be starved into a forced game-over. See §7.3 / §6.7.
25. **Follower income cadence — daily, not weekly.** Passive follower income accrues and auto-deposits to the bank **every in-game day** at the 04:00 day-roll, not in a weekly lump. Resolves the prior §7.9 ↔ §9.4 / §13.3 contradiction in favor of §7.9's daily model. Rent remains a separate **weekly** charge (§15.2); the two cadences are intentionally offset. See §7.9 / §9.4 / §13.3 / §15.1.
26. **Masturbation gating — home vs. public.** Home masturbation (in the apartment) is **always available to every player, regardless of path**. Masturbating *during a session* (outside the apartment) is a **Slut-path unlock** (§5.1). The §14.1 quick-action shows the masturbate entry unconditionally at home; in-session it is hidden until the player has Slut-path investment. Non-Slut players therefore cannot reset lust mid-session and rely on lust-decrease food (§6.7) or returning home. Commission `PerformAction(masturbate)` objectives are public and thus Slut-gated by the generator (§13.4). Resolves the prior §5.1 ↔ §7.2 contradiction. See §7.2 / §5.1 / §14.1.
27. **Bags are the only carry container; clothing has no pockets.** Per-garment container slots and item size classes (S / M / L) are removed. The only thing the player *carries* items in is an equipped bag — capacity is a flat per-bag **item count** (no size constraints), and any item type can go in any bag slot. The player also gains three dedicated single-item carry slots — **phone**, **bag**, and **key** — replacing the old generic hand slot; the phone is usable directly from the phone slot. See §6.3.1 / §6.4 / §6.5.
28. **Home storage is two fixtures: wardrobe + fridge.** Owned items rest in the apartment between sessions. The **wardrobe** holds every non-food item (clothing, sex toys, phones, keys, spare bags) and is the loadout point; the **fridge** holds food / consumables only, and food cannot be kept in the wardrobe. Home storage (the apartment stockpile) is distinct from carry storage (the count-limited equipped bag, §27). See §6.5 / §10.4.
## 21. Open Design Questions
@@ -33,7 +33,4 @@ public:
protected:
virtual void CaptureState(FInstancedStruct& OutState) const override;
virtual void ApplyState(const FInstancedStruct& InState) override;
UPROPERTY(BlueprintReadOnly, Category = "Clothing Item")
TArray<TObjectPtr<UItemInstance>> StoredItems;
};
@@ -7,7 +7,10 @@
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "NakedDesire/Phone/PhoneItemDefinition.h"
#include "NakedDesire/Phone/PhoneItemInstance.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
@@ -19,6 +22,12 @@ const TArray<TObjectPtr<UItemInstance>>& UInventorySubsystem::GetWardrobeItems()
return WardrobeItems;
}
UPhoneItemInstance* UInventorySubsystem::GetEquippedPhone()
{
EnsureHydrated();
return EquippedPhone;
}
void UInventorySubsystem::AddToWardrobe(UItemInstance* Item)
{
EnsureHydrated();
@@ -37,6 +46,12 @@ void UInventorySubsystem::EquipFromWardrobe(UItemInstance* Item)
{
EnsureHydrated();
if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(Item))
{
EquipPhone(Phone);
return;
}
UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
if (!Clothing || !Clothing->GetClothingItemDefinition())
return;
@@ -74,6 +89,12 @@ void UInventorySubsystem::UnequipToWardrobe(UItemInstance* Item)
{
EnsureHydrated();
if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(Item))
{
UnequipPhone(Phone);
return;
}
UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
if (!Clothing || !Clothing->GetClothingItemDefinition())
return;
@@ -90,6 +111,45 @@ void UInventorySubsystem::UnequipToWardrobe(UItemInstance* Item)
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::EquipPhone(UPhoneItemInstance* Phone)
{
if (!Phone || Phone == EquippedPhone)
return;
UGlobalSaveGameData* Save = GetSave();
// Hot-swap: the previously equipped phone returns to the wardrobe (§9.9).
if (EquippedPhone)
{
if (Save)
Save->RemoveEquippedItem(EquippedPhone);
StoreItem(EquippedPhone);
}
UnstoreItem(Phone);
EquippedPhone = Phone;
if (Save)
Save->AddEquippedItem(Phone);
OnPhoneChanged.Broadcast(EquippedPhone);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::UnequipPhone(UPhoneItemInstance* Phone)
{
if (!Phone || EquippedPhone != Phone)
return;
if (UGlobalSaveGameData* Save = GetSave())
Save->RemoveEquippedItem(Phone);
EquippedPhone = nullptr;
StoreItem(Phone);
OnPhoneChanged.Broadcast(nullptr);
OnWardrobeChanged.Broadcast();
}
void UInventorySubsystem::EnsureHydrated()
{
UGlobalSaveGameData* Save = GetSave();
@@ -108,6 +168,23 @@ void UInventorySubsystem::EnsureHydrated()
if (UItemInstance* Instance = UItemInstance::CreateFromRecord(this, Record))
WardrobeItems.Add(Instance);
}
// Only the phone is hydrated from the equipped bucket here; equipped clothing is owned and
// hydrated by the per-character UClothingManager. Pre-check the definition type so we don't
// mint throwaway clothing instances.
EquippedPhone = nullptr;
for (const FItemSaveRecord& Record : Save->GetEquippedItems())
{
const UItemDefinition* Definition = Record.Definition.LoadSynchronous();
if (!Definition || !Definition->IsA<UPhoneItemDefinition>())
continue;
if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(UItemInstance::CreateFromRecord(this, Record)))
{
EquippedPhone = Phone;
break;
}
}
}
void UInventorySubsystem::StoreItem(UItemInstance* Item)
@@ -9,8 +9,10 @@
class UItemInstance;
class UClothingManager;
class UGlobalSaveGameData;
class UPhoneItemInstance;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWardrobeChangedSignature);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPhoneChangedSignature, UPhoneItemInstance*, Phone);
/**
* Runtime owner of the off-body item store (the apartment wardrobe, GDD §6.5 / §10.4).
@@ -31,9 +33,16 @@ public:
UPROPERTY(BlueprintAssignable)
FOnWardrobeChangedSignature OnWardrobeChanged;
// Fires when the phone slot changes; broadcasts the newly-equipped phone, or nullptr when emptied.
UPROPERTY(BlueprintAssignable)
FOnPhoneChangedSignature OnPhoneChanged;
/** Live wardrobe instances (off-body, mirrored from the save). */
const TArray<TObjectPtr<UItemInstance>>& GetWardrobeItems();
/** The phone occupying the dedicated phone slot (§6.5 / §9.9), or nullptr if none is equipped. */
UPhoneItemInstance* GetEquippedPhone();
/** Bring an item into the wardrobe (purchase, world-return). */
void AddToWardrobe(UItemInstance* Item);
@@ -51,12 +60,18 @@ private:
void StoreItem(UItemInstance* Item); // live list + save bucket, no broadcast
void UnstoreItem(UItemInstance* Item); // live list + save bucket, no broadcast
void EquipPhone(UPhoneItemInstance* Phone); // hot-swaps the previous phone back to the wardrobe
void UnequipPhone(UPhoneItemInstance* Phone); // stores the phone back in the wardrobe
UClothingManager* GetPlayerClothingManager() const;
UGlobalSaveGameData* GetSave() const;
UPROPERTY()
TArray<TObjectPtr<UItemInstance>> WardrobeItems;
UPROPERTY()
TObjectPtr<UPhoneItemInstance> EquippedPhone;
// The save the live list was built from; re-hydrate when this changes (e.g. load game).
TWeakObjectPtr<UGlobalSaveGameData> HydratedSave;
};
@@ -0,0 +1,11 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "PhoneItemDefinition.h"
#include "PhoneItemInstance.h"
TSubclassOf<UItemInstance> UPhoneItemDefinition::GetInstanceClass() const
{
return UPhoneItemInstance::StaticClass();
}
@@ -0,0 +1,22 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "PhoneItemDefinition.generated.h"
// Immutable definition for a phone (GDD §6.2 / §9.9). A phone is a regular UItemInstance item that
// lives in the wardrobe / bag and occupies the dedicated phone slot when active (§6.5).
// Phone-tier stats (camera / livestream / battery-capacity multipliers, §9.9) are deferred to Phase 8/9.
UCLASS(BlueprintType)
class NAKEDDESIRE_API UPhoneItemDefinition : public UItemDefinition
{
GENERATED_BODY()
public:
virtual TSubclassOf<UItemInstance> GetInstanceClass() const override;
UPROPERTY(EditDefaultsOnly, Category = "Phone")
float MaxBattery = 100.0f;
};
@@ -0,0 +1,22 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "PhoneItemInstance.h"
#include "PhoneItemDefinition.h"
#include "StructUtils/InstancedStruct.h"
void UPhoneItemInstance::CaptureState(FInstancedStruct& OutState) const
{
FPhoneInstanceState PhoneState;
PhoneState.CurrentBattery = CurrentBattery;
OutState.InitializeAs<FPhoneInstanceState>(PhoneState);
}
void UPhoneItemInstance::ApplyState(const FInstancedStruct& InState)
{
if (const FPhoneInstanceState* PhoneState = InState.GetPtr<FPhoneInstanceState>())
{
CurrentBattery = PhoneState->CurrentBattery;
}
}
@@ -0,0 +1,35 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "PhoneItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "PhoneItemInstance.generated.h"
/** Per-instance mutable state for a phone. Battery is per-phone-instance (§9.9). */
USTRUCT()
struct FPhoneInstanceState : public FItemInstanceState
{
GENERATED_BODY()
UPROPERTY(SaveGame)
float CurrentBattery = 100.0f;
};
UCLASS(BlueprintType)
class NAKEDDESIRE_API UPhoneItemInstance : public UItemInstance
{
GENERATED_BODY()
public:
// Charge of this specific phone, [0,1]. Drain / charge logic lands with the phone battery system (Phase 8/9).
UPROPERTY(BlueprintReadOnly, Category = "Phone Item")
float CurrentBattery = 100.0f;
UPhoneItemDefinition* GetPhoneItemDefinition() const { return Cast<UPhoneItemDefinition>(ItemDefinition); }
protected:
virtual void CaptureState(FInstancedStruct& OutState) const override;
virtual void ApplyState(const FInstancedStruct& InState) override;
};
@@ -62,7 +62,7 @@ void USaveSubsystem::PopulateStartingData(UGlobalSaveGameData* Save) const
if (!GameInstance || !GameInstance->StartingSaveData)
return;
for (UItemDefinition* Definition : GameInstance->StartingSaveData->StartingItems)
for (UItemDefinition* Definition : GameInstance->StartingSaveData->EquippedItems)
{
if (!Definition)
continue;
@@ -6,6 +6,7 @@
#include "Engine/DataAsset.h"
#include "StartingSaveData.generated.h"
class UPhoneItemDefinition;
class UItemDefinition;
UCLASS()
@@ -15,5 +16,11 @@ class NAKEDDESIRE_API UStartingSaveData : public UPrimaryDataAsset
public:
UPROPERTY(EditDefaultsOnly, Category = "Starting State")
TArray<TObjectPtr<UItemDefinition>> StartingItems;
TArray<TObjectPtr<UItemDefinition>> EquippedItems;
UPROPERTY(EditDefaultsOnly, Category = "Starting State")
TArray<TObjectPtr<UItemDefinition>> WardrobeItems;
UPROPERTY(EditDefaultsOnly, Category = "Starting State")
TObjectPtr<UPhoneItemDefinition> Phone;
};