From f63c98d5d77922ff0f16fd5aa567cb7fccd9a8a0 Mon Sep 17 00:00:00 2001 From: koritsa Date: Mon, 1 Jun 2026 15:03:37 +0300 Subject: [PATCH] Added commision objectives --- COMMISSIONS.md | 26 ++++-- .../Blueprints/Data/StartingSaveData.uasset | 4 +- PLAN.md | 2 +- .../Clothing/DroppedClothingSubsystem.cpp | 51 +++++++++++ .../Clothing/DroppedClothingSubsystem.h | 40 +++++++++ .../Objectives/BareRegionObjective.cpp | 49 +++++++++++ .../Objectives/BareRegionObjective.h | 33 ++++++++ .../Objectives/CoverageObjectiveBase.cpp | 8 ++ .../Objectives/CoverageObjectiveBase.h | 3 + .../ExposeWhileWalkingObjective.cpp | 28 +++++++ .../Objectives/ExposeWhileWalkingObjective.h | 26 ++++++ .../MoveDistanceFromClothingObjective.cpp | 84 +++++++++++++++++++ .../MoveDistanceFromClothingObjective.h | 39 +++++++++ .../Objectives/RunNakedDistanceObjective.cpp | 55 +----------- .../Objectives/RunNakedDistanceObjective.h | 20 +---- .../Objectives/StayBelowCoverageObjective.cpp | 29 +++++++ .../Objectives/StayBelowCoverageObjective.h | 26 ++++++ .../Objectives/TravelObjectiveBase.cpp | 61 ++++++++++++++ .../Objectives/TravelObjectiveBase.h | 36 ++++++++ .../Objectives/WalkNakedDistanceObjective.cpp | 13 +++ .../Objectives/WalkNakedDistanceObjective.h | 21 +++++ .../WalkNakedWhileObservedObjective.cpp | 14 ++++ .../WalkNakedWhileObservedObjective.h | 25 ++++++ .../NakedDesire/Interactables/ItemPickup.cpp | 14 +++- Source/NakedDesire/Interactables/ItemPickup.h | 1 + 25 files changed, 626 insertions(+), 82 deletions(-) create mode 100644 Source/NakedDesire/Clothing/DroppedClothingSubsystem.cpp create mode 100644 Source/NakedDesire/Clothing/DroppedClothingSubsystem.h create mode 100644 Source/NakedDesire/Commissions/Objectives/BareRegionObjective.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/BareRegionObjective.h create mode 100644 Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.h create mode 100644 Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.h create mode 100644 Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.h create mode 100644 Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.h create mode 100644 Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.h create mode 100644 Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.cpp create mode 100644 Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.h diff --git a/COMMISSIONS.md b/COMMISSIONS.md index 2a296a91..054deb94 100644 --- a/COMMISSIONS.md +++ b/COMMISSIONS.md @@ -41,9 +41,9 @@ content space. - **`ExposeBodyPart(part, duration)`** ✅ — **`UExposeBodyPartObjective`** *(implemented)* - **`BeFullyNakedNearNPCs(count, duration)`** ✅ — **`UBeFullyNakedNearNPCsObjective`** *(implemented)* - `WearOnlyUnderwear(duration)` ✅ — **`UWearOnlyUnderwearObjective`** *(implemented)*. Exhibitionist. -- `BeToplessOnly` / `BeBottomlessOnly(duration)` ✅ — one region bare, the other covered. -- `StayBelowCoverage(threshold, duration)` ✅ — total/region coverage under X (revealing, not nude). -- `ExposeWhileWalking(part, meters)` ✅ — keep a part revealing across N meters (per-part walk). +- `BeToplessOnly` / `BeBottomlessOnly(duration)` ✅ — **`UBareRegionObjective`** *(implemented; `EBareRegion` data value picks the bare half)*. One region bare, the other covered. +- `StayBelowCoverage(threshold, duration)` ✅ — **`UStayBelowCoverageObjective`** *(implemented; names its own threshold)*. Every region under X (revealing, not nude). +- `ExposeWhileWalking(part, meters)` ✅ — **`UExposeWhileWalkingObjective`** *(implemented)*. Keep a part revealing across N meters (per-part walk). - `UseExposeAction(part, count)` ✅ (after VS‑3) — trigger the §6.3.6 flash action N times. ## Observation / NPC reactions (✅ count-based; 🔶 type-based) @@ -65,14 +65,18 @@ content space. - `EnterLocationNaked(tag)` ✅ · `TourLocationsNaked(tagset)` ✅ · `LingerExposed(tag, duration)` ✅ (the last two = `BeFullyNaked` + a `ULocationConstraint`). -- `ReachLocationAwayFromClothing(tag, meters)` ✅ — arrive having left clothes ≥N m behind. +- `ReachLocationAwayFromClothing(tag, meters)` ✅ *(no new class)* — `MoveDistanceFromClothing` + a `ULocationConstraint`: arrive at the location having left clothes ≥N m behind. - `ReturnHomeNaked()` ✅-ish — end the session at the apartment with no clothing (loop-closer). ## Movement / endurance (✅) -- `WalkNakedDistance(meters)` ✅ (in §13.4) · `MoveDistanceFromClothing(meters)` ✅ (in §13.4). +- `WalkNakedDistance(meters)` ✅ (in §13.4) — **`UWalkNakedDistanceObjective`** *(implemented)*. Naked travel at any gait. · `MoveDistanceFromClothing(meters)` ✅ (in §13.4) — **`UMoveDistanceFromClothingObjective`** *(implemented; polls `UDroppedClothingSubsystem` for the nearest dropped garment)*. - `RunNakedDistance(meters)` ✅ — **`URunNakedDistanceObjective`** *(implemented)*. Distance accrues only while running + naked. -- `WalkNakedWhileObserved(meters, minObservers)` ✅. +- `WalkNakedWhileObserved(meters, minObservers)` ✅ — **`UWalkNakedWhileObservedObjective`** *(implemented)*. + +> All four distance objectives share **`UTravelObjectiveBase`** (`UCoverageObjectiveBase` subclass) — it owns the +> clamped distance-sampling timer; subclasses only override `DoesSampleCount()` (e.g. naked+run, part revealing, +> naked+observed). New "travel while X" objectives are a one-method subclass. ## Clothing / outfits / restraints @@ -105,9 +109,13 @@ content space. 1. **Structural** — constraints framework + sequential steps. *(done)* 2. **All-✅ objectives** — done: `StayUnseenWhileNaked`, `GatherCrowd`, `BeObservedWhileExposed`, - `WearOnlyUnderwear`, `RunNakedDistance`, `ReachEmbarrassment`/`SustainEmbarrassment`. Location - objectives (`EnterLocationNaked`, `LingerExposed`, …) need no new class — author them as - `BeFullyNaked` / `ExposeBodyPart` + a `ULocationConstraint`. + `WearOnlyUnderwear`, `ReachEmbarrassment`/`SustainEmbarrassment`, the `UTravelObjectiveBase` distance + family (`RunNakedDistance`, `WalkNakedDistance`, `ExposeWhileWalking`, `WalkNakedWhileObserved`), and the + coverage pair `BareRegion` (topless/bottomless) + `StayBelowCoverage`. Location objectives + (`EnterLocationNaked`, `LingerExposed`, …) need no new class — author them as `BeFullyNaked` / + `ExposeBodyPart` + a `ULocationConstraint`. `MoveDistanceFromClothing` (+ its + `ReachLocationAwayFromClothing` compose) now landed on `UDroppedClothingSubsystem`. Remaining all-✅ + gap: `WearOutfit` needs an outfit tag/id on items. 3. **Location system** — done: `ULocationSubsystem` + `ULocationConstraint`; the whole Location/travel group and `WhileAtLocation` are unblocked. (Build the location *objectives* with the ✅ batch.) 4. **Per-system batches** as VS‑5 (NPC types), VS‑2 (lust/pulse), toys, restraints, and Phase 8 diff --git a/Content/Blueprints/Data/StartingSaveData.uasset b/Content/Blueprints/Data/StartingSaveData.uasset index 5a75c7b8..fe0bca14 100644 --- a/Content/Blueprints/Data/StartingSaveData.uasset +++ b/Content/Blueprints/Data/StartingSaveData.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d230290d469c7e59e8a98294865134b5a0716a662fa26014ccd0b1c83d1cb387 -size 2512 +oid sha256:f06f6e62fefe7bc7b5c96546f130e69e4c6c4caaab7155c3584afe8609f6bd6c +size 1590 diff --git a/PLAN.md b/PLAN.md index fa4a5db8..aa06bd15 100644 --- a/PLAN.md +++ b/PLAN.md @@ -90,7 +90,7 @@ State of the C++ module as of the latest pass. File references use `Source/Naked - **Equip / unequip / drop event** — `Clothing/ClothingManager.cpp:88-163` (slot-based, broadcasts `OnClothingEquip / Unequip / Dropped` with `UClothingItemInstance*`). - **Censorship toggle** — compliance feature on `NakedDesireCharacter` (`BoobL/R`, `Front/BackBottom` static meshes), driven by `UNakedDesireUserSettings`. - **AI sight + behavior tree** — `NPC/NPCAIController.cpp` runs a BT with `Player` / `TargetLocation` / `SpawnLocation` blackboard keys. `NPC/NPCSpawner.cpp` proximity-gated spawn with day / night caps. -- **Commission system — rebuilt (`Commissions/`, slice-first foundation).** Replaces the old `MissionBuilder/` Goal/Restriction model with the §13.4 vocabulary. `UCommissionObjective` is the unified typed step (owns its condition + an optional `RequiredHoldSeconds` hold timer — "expose for N s" vs "expose once" is a data value, not a class); concrete steps derive from a shared base chain — `UCommissionObjective` → `UObserverObjectiveBase` (reacts to the observer count) → `UCoverageObjectiveBase` (re-evaluates on equip/unequip via `ClothingManager::GetEffectiveCoverage`). Implemented steps: `BeFullyNaked`, `ExposeBodyPart`, `BeFullyNakedNearNPCs`, `StayUnseenWhileNaked`, `GatherCrowd`, `BeObservedWhileExposed`, `WearOnlyUnderwear`, `RunNakedDistance` (own distance-sampling timer), `ReachEmbarrassment`/`SustainEmbarrassment`. Location objectives (`EnterLocationNaked`, etc.) are authored as a coverage step + `ULocationConstraint` (no new class). `UCommission` is the `Offered→Accepted→Completed/Expired` state machine + `FCommissionReward` (money/XP/followers). `UMissionSubsystem` (`UWorldSubsystem`, like `TimeOfDaySubsystem`) offers a hand-authored `UCommissionBoardConfig` pool (set on `UNakedDesireGameInstance::CommissionBoard`), drives accept/abandon, **pays rewards instantly on completion** (money→save, XP→character; followers stubbed, Phase 8), **expires accepted commissions on `OnDayChanged`**, and persists state to a new `UGlobalSaveGameData::Commissions` bucket (id-keyed, state-level — objective mid-progress not preserved). Added `UStatsManager::GetObserverCount()` + `OnObserversChanged` so "near NPCs" reuses the embarrassment observer set. **Composition:** objectives gate on **constraints** (`UCommissionConstraint` → `UObservedConstraint`, `UDayPhaseConstraint`, `UWearingSlotConstraint`) — "do X while Y" with no new objective code; and a commission can set `bSequentialObjectives` to require its steps in array order (strip → walk → …). The full objective/constraint idea backlog with feasibility tags lives in `COMMISSIONS.md`. **Follow-ups:** no board/commission UI yet (the old forum widget isn't wired to this); `failurePenalty` is a hook only (no reputation/followers yet); procedural generation + path-filtering + the remaining §13.4 step types (`WalkNakedDistance`, `MoveDistanceFromClothing`, `PerformAction`, `BeObservedByNPCType`, `TakePhotoAtLocation`, `DeliverItemTo`) are Phase 7. +- **Commission system — rebuilt (`Commissions/`, slice-first foundation).** Replaces the old `MissionBuilder/` Goal/Restriction model with the §13.4 vocabulary. `UCommissionObjective` is the unified typed step (owns its condition + an optional `RequiredHoldSeconds` hold timer — "expose for N s" vs "expose once" is a data value, not a class); concrete steps derive from a shared base chain — `UCommissionObjective` → `UObserverObjectiveBase` (reacts to the observer count) → `UCoverageObjectiveBase` (re-evaluates on equip/unequip via `ClothingManager::GetEffectiveCoverage`). Implemented steps: `BeFullyNaked`, `ExposeBodyPart`, `BeFullyNakedNearNPCs`, `StayUnseenWhileNaked`, `GatherCrowd`, `BeObservedWhileExposed`, `WearOnlyUnderwear`, `BareRegion` (topless/bottomless), `StayBelowCoverage`, `ReachEmbarrassment`/`SustainEmbarrassment`, plus the `UTravelObjectiveBase` distance family — `RunNakedDistance`, `WalkNakedDistance`, `ExposeWhileWalking`, `WalkNakedWhileObserved` (shared clamped distance-sampling timer; subclasses only override `DoesSampleCount()`) — and `MoveDistanceFromClothing` (polls the new `UDroppedClothingSubsystem` for the nearest garment you left behind; `ReachLocationAwayFromClothing` is this + a `ULocationConstraint`, no new class). Location objectives (`EnterLocationNaked`, etc.) are authored as a coverage step + `ULocationConstraint` (no new class). `UCommission` is the `Offered→Accepted→Completed/Expired` state machine + `FCommissionReward` (money/XP/followers). `UMissionSubsystem` (`UWorldSubsystem`, like `TimeOfDaySubsystem`) offers a hand-authored `UCommissionBoardConfig` pool (set on `UNakedDesireGameInstance::CommissionBoard`), drives accept/abandon, **pays rewards instantly on completion** (money→save, XP→character; followers stubbed, Phase 8), **expires accepted commissions on `OnDayChanged`**, and persists state to a new `UGlobalSaveGameData::Commissions` bucket (id-keyed, state-level — objective mid-progress not preserved). Added `UStatsManager::GetObserverCount()` + `OnObserversChanged` so "near NPCs" reuses the embarrassment observer set. **Composition:** objectives gate on **constraints** (`UCommissionConstraint` → `UObservedConstraint`, `UDayPhaseConstraint`, `UWearingSlotConstraint`) — "do X while Y" with no new objective code; and a commission can set `bSequentialObjectives` to require its steps in array order (strip → walk → …). The full objective/constraint idea backlog with feasibility tags lives in `COMMISSIONS.md`. **Follow-ups:** no board/commission UI yet (the old forum widget isn't wired to this); `failurePenalty` is a hook only (no reputation/followers yet); procedural generation + path-filtering + the remaining §13.4 step types (`PerformAction`, `BeObservedByNPCType`, `TakePhotoAtLocation`, `DeliverItemTo`) are Phase 7. - **Old mission framework — parked, not deleted.** `MissionBuilder/` (`Mission`/`MissionGoal`/`GoalRestriction`, `FlashGoal`/`MinTimeGoal`, the 3 restrictions, `MissionsConfig`) and the `ANakedDesireCharacter::MissionsManager` component remain on disk but dormant; remove in a cleanup pass once the new system's UI is wired and no Blueprint references the old classes. - **Daily-mission OOB guarded** — `NakedDesireGameMode::RefreshDailyMissions` now clamps `DaysPassed` to the authored array bounds (`NakedDesireGameMode.cpp:70-71`). Still a hand-authored list (see §1.3). - **Location system (GDD §10.4) — unified on `ULocationSubsystem`.** `Locations/LocationSubsystem` (`UWorldSubsystem`) is the single authority on which tagged locations the player occupies: `ALocationTrigger` volumes report player enter/leave (ref-counted, so overlapping boxes of one place don't churn), and it exposes `IsPlayerInLocation(tag)` (hierarchical via `FGameplayTagContainer::HasTag`), `GetCurrentLocation()`, and `OnLocationEntered/Exited`. Locations are identified by `ULocationData::Tag` and **nest** (inside `Location.City.Beach` you also match `Location.City`). `bIsApartment` is **gone**: `USessionManagerSubsystem` now subscribes to the subsystem and starts/ends the session on the native tag `TAG_Location_Apartment` (`Global/NakedDesireGameplayTags`). Commission location gating is `Commissions/Constraints/LocationConstraint`. **Content requirement:** the apartment trigger's `ULocationData` must be tagged `Location.Apartment` (or a child); each trigger box needs overlap-with-Pawn collision. diff --git a/Source/NakedDesire/Clothing/DroppedClothingSubsystem.cpp b/Source/NakedDesire/Clothing/DroppedClothingSubsystem.cpp new file mode 100644 index 00000000..df38d81c --- /dev/null +++ b/Source/NakedDesire/Clothing/DroppedClothingSubsystem.cpp @@ -0,0 +1,51 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "DroppedClothingSubsystem.h" + +#include "NakedDesire/Interactables/ItemPickup.h" + +void UDroppedClothingSubsystem::RegisterPickup(AItemPickup* Pickup) +{ + if (Pickup) + Pickups.AddUnique(Pickup); +} + +void UDroppedClothingSubsystem::UnregisterPickup(AItemPickup* Pickup) +{ + Pickups.RemoveSingleSwap(Pickup); +} + +bool UDroppedClothingSubsystem::HasDroppedClothing() const +{ + for (const TObjectPtr& Pickup : Pickups) + { + if (Pickup) + return true; + } + return false; +} + +bool UDroppedClothingSubsystem::GetDistanceToNearestDroppedClothing(const FVector& From, float& OutDistance) const +{ + float NearestSq = TNumericLimits::Max(); + bool bFound = false; + + for (const TObjectPtr& Pickup : Pickups) + { + if (!Pickup) + continue; + + const float DistSq = FVector::DistSquared(From, Pickup->GetActorLocation()); + if (DistSq < NearestSq) + { + NearestSq = DistSq; + bFound = true; + } + } + + if (bFound) + OutDistance = FMath::Sqrt(NearestSq); + + return bFound; +} \ No newline at end of file diff --git a/Source/NakedDesire/Clothing/DroppedClothingSubsystem.h b/Source/NakedDesire/Clothing/DroppedClothingSubsystem.h new file mode 100644 index 00000000..4e22d720 --- /dev/null +++ b/Source/NakedDesire/Clothing/DroppedClothingSubsystem.h @@ -0,0 +1,40 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "DroppedClothingSubsystem.generated.h" + +class AItemPickup; + +/** + * Single source of truth for which clothing is currently lying in the world (GDD §6.3.4 / §13.4). + * Every AItemPickup self-registers on BeginPlay and unregisters on EndPlay (pickup destroys the actor), + * so the tracked set always matches what is on the ground without per-query actor iteration. + * + * Backs the "move away from your clothing" objective family — distance is measured to the nearest tracked + * pickup. World-scoped and transient; the pickups themselves are persisted via the save's world items. + */ +UCLASS() +class NAKEDDESIRE_API UDroppedClothingSubsystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + // Called by AItemPickup as it spawns / is destroyed. + void RegisterPickup(AItemPickup* Pickup); + void UnregisterPickup(AItemPickup* Pickup); + + // True while at least one clothing pickup exists in the world. + UFUNCTION(BlueprintPure, Category = "Clothing") + bool HasDroppedClothing() const; + + // Distance in cm from From to the nearest tracked pickup. Returns false (and leaves OutDistance + // untouched) when nothing is on the ground — callers decide what "no clothing to move from" means. + bool GetDistanceToNearestDroppedClothing(const FVector& From, float& OutDistance) const; + +private: + UPROPERTY() + TArray> Pickups; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/BareRegionObjective.cpp b/Source/NakedDesire/Commissions/Objectives/BareRegionObjective.cpp new file mode 100644 index 00000000..01178b19 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/BareRegionObjective.cpp @@ -0,0 +1,49 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "BareRegionObjective.h" + +#include "NakedDesire/Clothing/BodyPart.h" + +#define LOCTEXT_NAMESPACE "Commissions.Objectives.BareRegion" + +bool UBareRegionObjective::IsConditionMet() const +{ + switch (Region) + { + case EBareRegion::Top: + // Chest bare, both lower regions still covered. + return IsPartRevealing(EBodyPart::Boobs) + && !IsPartRevealing(EBodyPart::Genitals) + && !IsPartRevealing(EBodyPart::Ass); + + case EBareRegion::Bottom: + // Lower half bare, chest still covered. + return IsPartRevealing(EBodyPart::Genitals) + && IsPartRevealing(EBodyPart::Ass) + && !IsPartRevealing(EBodyPart::Boobs); + + default: + return false; + } +} + +FText UBareRegionObjective::GetDescription() const +{ + const bool bTimed = RequiredHoldSeconds > 0.0f; + + if (Region == EBareRegion::Top) + { + return bTimed + ? FText::Format(LOCTEXT("TopTimed", "Stay topless — chest bare, below covered — for {0} seconds"), + FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds))) + : LOCTEXT("TopInstant", "Go topless — bare your chest while staying covered below"); + } + + return bTimed + ? FText::Format(LOCTEXT("BottomTimed", "Stay bottomless — bare below, chest covered — for {0} seconds"), + FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds))) + : LOCTEXT("BottomInstant", "Go bottomless — bare below while keeping your chest covered"); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/BareRegionObjective.h b/Source/NakedDesire/Commissions/Objectives/BareRegionObjective.h new file mode 100644 index 00000000..3689a935 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/BareRegionObjective.h @@ -0,0 +1,33 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "CoverageObjectiveBase.h" +#include "BareRegionObjective.generated.h" + +// Which half is bare while the other stays covered. +UENUM(BlueprintType) +enum class EBareRegion : uint8 +{ + Top UMETA(DisplayName = "Topless (chest bare, below covered)"), + Bottom UMETA(DisplayName = "Bottomless (below bare, chest covered)"), +}; + +// BeToplessOnly / BeBottomlessOnly: one region reads as revealing while the other stays covered. A +// deliberate half-exposure objective — distinct from BeFullyNaked (all bare) and StayBelowCoverage. +UCLASS(EditInlineNew, DisplayName = "Bare One Region Only") +class NAKEDDESIRE_API UBareRegionObjective : public UCoverageObjectiveBase +{ + GENERATED_BODY() + +public: + virtual FText GetDescription() const override; + +protected: + virtual bool IsConditionMet() const override; + +private: + UPROPERTY(EditDefaultsOnly) + EBareRegion Region = EBareRegion::Top; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.cpp b/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.cpp index c5c75fdd..f720440f 100644 --- a/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.cpp +++ b/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.cpp @@ -47,6 +47,14 @@ bool UCoverageObjectiveBase::IsPartRevealing(EBodyPart Part) const return Player->ClothingManager->GetEffectiveCoverage(Part) < Player->ObservationRevealThreshold; } +float UCoverageObjectiveBase::GetEffectiveCoverage(EBodyPart Part) const +{ + if (!Player || !Player->ClothingManager) + return 0.0f; + + return Player->ClothingManager->GetEffectiveCoverage(Part); +} + bool UCoverageObjectiveBase::IsAnyPartRevealing() const { return IsPartRevealing(EBodyPart::Boobs) diff --git a/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.h b/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.h index d9c77e4b..51749d04 100644 --- a/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.h +++ b/Source/NakedDesire/Commissions/Objectives/CoverageObjectiveBase.h @@ -27,6 +27,9 @@ protected: // Part reads as "revealing" — below the player's observation reveal threshold. bool IsPartRevealing(EBodyPart Part) const; + // Raw effective coverage [0,1] for a part — for objectives that name their own threshold. + float GetEffectiveCoverage(EBodyPart Part) const; + // Any of the three regions is revealing. bool IsAnyPartRevealing() const; diff --git a/Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.cpp b/Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.cpp new file mode 100644 index 00000000..c9ec4579 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.cpp @@ -0,0 +1,28 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "ExposeWhileWalkingObjective.h" + +#define LOCTEXT_NAMESPACE "Commissions.Objectives.ExposeWhileWalking" + +namespace +{ + FText BodyPartText(EBodyPart Part) + { + switch (Part) + { + case EBodyPart::Boobs: return LOCTEXT("Boobs", "boobs"); + case EBodyPart::Ass: return LOCTEXT("Ass", "ass"); + case EBodyPart::Genitals: return LOCTEXT("Genitals", "genitals"); + default: return LOCTEXT("None", "nothing"); + } + } +} + +FText UExposeWhileWalkingObjective::GetDescription() const +{ + return FText::Format(LOCTEXT("Walk", "Walk {0} m with your {1} exposed"), + FText::AsNumber(FMath::RoundToInt(RequiredMeters)), BodyPartText(Part)); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.h b/Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.h new file mode 100644 index 00000000..4165c967 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/ExposeWhileWalkingObjective.h @@ -0,0 +1,26 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "NakedDesire/Clothing/BodyPart.h" +#include "TravelObjectiveBase.h" +#include "ExposeWhileWalkingObjective.generated.h" + +// Keep Part revealing across RequiredMeters of travel — a moving variant of ExposeBodyPart (a parade, +// not a pose). Distance accrues only while the part reads as revealing and the player is moving. +UCLASS(EditInlineNew, DisplayName = "Expose While Walking") +class NAKEDDESIRE_API UExposeWhileWalkingObjective : public UTravelObjectiveBase +{ + GENERATED_BODY() + +public: + virtual FText GetDescription() const override; + +protected: + virtual bool DoesSampleCount() const override { return IsPartRevealing(Part); } + +private: + UPROPERTY(EditDefaultsOnly) + EBodyPart Part = EBodyPart::Boobs; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.cpp b/Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.cpp new file mode 100644 index 00000000..1d20523c --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.cpp @@ -0,0 +1,84 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "MoveDistanceFromClothingObjective.h" + +#include "TimerManager.h" +#include "NakedDesire/Clothing/DroppedClothingSubsystem.h" +#include "NakedDesire/Player/NakedDesireCharacter.h" + +#define LOCTEXT_NAMESPACE "Commissions.Objectives.MoveDistanceFromClothing" + +namespace +{ + constexpr float PollIntervalSeconds = 0.2f; +} + +void UMoveDistanceFromClothingObjective::OnActivate() +{ + if (Player) + Player->GetWorldTimerManager().SetTimer(PollTimer, this, &UMoveDistanceFromClothingObjective::Poll, PollIntervalSeconds, true); +} + +void UMoveDistanceFromClothingObjective::OnDeactivate() +{ + if (Player) + Player->GetWorldTimerManager().ClearTimer(PollTimer); + PollTimer.Invalidate(); +} + +void UMoveDistanceFromClothingObjective::Poll() +{ + NotifyConditionChanged(); +} + +bool UMoveDistanceFromClothingObjective::IsConditionMet() const +{ + const UDroppedClothingSubsystem* Dropped = GetDroppedClothing(); + if (!Dropped || !Player) + return false; + + float Distance = 0.0f; + if (!Dropped->GetDistanceToNearestDroppedClothing(Player->GetActorLocation(), Distance)) + return false; // nothing on the ground to move away from + + return Distance >= RequiredMeters * 100.0f; +} + +float UMoveDistanceFromClothingObjective::GetProgress() const +{ + // While a hold timer is running (stay-away variant) defer to the base's elapsed-hold progress. + if (bSatisfied || RequiredHoldSeconds > 0.0f) + return Super::GetProgress(); + + const UDroppedClothingSubsystem* Dropped = GetDroppedClothing(); + if (!Dropped || !Player) + return 0.0f; + + float Distance = 0.0f; + if (!Dropped->GetDistanceToNearestDroppedClothing(Player->GetActorLocation(), Distance)) + return 0.0f; + + const float TargetCm = RequiredMeters * 100.0f; + return TargetCm > 0.0f ? FMath::Clamp(Distance / TargetCm, 0.0f, 1.0f) : 0.0f; +} + +UDroppedClothingSubsystem* UMoveDistanceFromClothingObjective::GetDroppedClothing() const +{ + UWorld* World = Player ? Player->GetWorld() : nullptr; + return World ? World->GetSubsystem() : nullptr; +} + +FText UMoveDistanceFromClothingObjective::GetDescription() const +{ + if (RequiredHoldSeconds > 0.0f) + { + return FText::Format(LOCTEXT("Timed", "Stay {0} m from your dropped clothes for {1} seconds"), + FText::AsNumber(FMath::RoundToInt(RequiredMeters)), FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds))); + } + + return FText::Format(LOCTEXT("Instant", "Get {0} m away from your dropped clothes"), + FText::AsNumber(FMath::RoundToInt(RequiredMeters))); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.h b/Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.h new file mode 100644 index 00000000..8b156a2d --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/MoveDistanceFromClothingObjective.h @@ -0,0 +1,39 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "NakedDesire/Commissions/CommissionObjective.h" +#include "MoveDistanceFromClothingObjective.generated.h" + +class UDroppedClothingSubsystem; + +// §13.4 MoveDistanceFromClothing(meters): reach RequiredMeters of separation from the nearest clothing +// you left on the ground (UDroppedClothingSubsystem). Distance has no event, so the objective polls. +// Never auto-satisfies when nothing has been dropped — there is nothing to move away from. +// +// ReachLocationAwayFromClothing(tag, meters) needs no new class: author this objective with a +// ULocationConstraint, and the base ANDs the location gate into the satisfaction check. +UCLASS(EditInlineNew, DisplayName = "Move Distance From Clothing") +class NAKEDDESIRE_API UMoveDistanceFromClothingObjective : public UCommissionObjective +{ + GENERATED_BODY() + +public: + virtual FText GetDescription() const override; + virtual float GetProgress() const override; + +protected: + virtual void OnActivate() override; + virtual void OnDeactivate() override; + virtual bool IsConditionMet() const override; + +private: + void Poll(); + UDroppedClothingSubsystem* GetDroppedClothing() const; + + UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1)) + float RequiredMeters = 50.0f; + + FTimerHandle PollTimer; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.cpp b/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.cpp index 3b2df5b8..4294b705 100644 --- a/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.cpp +++ b/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.cpp @@ -3,65 +3,14 @@ #include "RunNakedDistanceObjective.h" -#include "TimerManager.h" #include "NakedDesire/Global/Gait.h" #include "NakedDesire/Player/NakedDesireCharacter.h" #define LOCTEXT_NAMESPACE "Commissions.Objectives.RunNakedDistance" -namespace +bool URunNakedDistanceObjective::DoesSampleCount() const { - constexpr float SampleIntervalSeconds = 0.2f; - constexpr float MaxStepCm = 1000.0f; // ignore single-sample jumps larger than this (teleports / streaming) -} - -void URunNakedDistanceObjective::OnActivate() -{ - Super::OnActivate(); - - AccumulatedCm = 0.0f; - - if (Player) - { - LastLocation = Player->GetActorLocation(); - Player->GetWorldTimerManager().SetTimer(SampleTimer, this, &URunNakedDistanceObjective::SampleDistance, SampleIntervalSeconds, true); - } -} - -void URunNakedDistanceObjective::OnDeactivate() -{ - if (Player) - Player->GetWorldTimerManager().ClearTimer(SampleTimer); - SampleTimer.Invalidate(); - - Super::OnDeactivate(); -} - -void URunNakedDistanceObjective::SampleDistance() -{ - if (bSatisfied || !Player) - return; - - const FVector Now = Player->GetActorLocation(); - const float Delta = FVector::Dist(Now, LastLocation); - LastLocation = Now; - - // Only running, naked, and within any constraints counts toward the distance. - if (IsFullyNaked() && Player->GetGait() == EGait::Run && AreConstraintsMet()) - { - AccumulatedCm += FMath::Min(Delta, MaxStepCm); - if (AccumulatedCm >= RequiredMeters * 100.0f) - MarkSatisfied(); - } -} - -float URunNakedDistanceObjective::GetProgress() const -{ - if (bSatisfied) - return 1.0f; - - const float TargetCm = RequiredMeters * 100.0f; - return TargetCm > 0.0f ? FMath::Clamp(AccumulatedCm / TargetCm, 0.0f, 1.0f) : 0.0f; + return IsFullyNaked() && Player && Player->GetGait() == EGait::Run; } FText URunNakedDistanceObjective::GetDescription() const diff --git a/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.h b/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.h index 7719b150..5b623039 100644 --- a/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.h +++ b/Source/NakedDesire/Commissions/Objectives/RunNakedDistanceObjective.h @@ -3,31 +3,19 @@ #pragma once #include "CoreMinimal.h" -#include "CoverageObjectiveBase.h" +#include "TravelObjectiveBase.h" #include "RunNakedDistanceObjective.generated.h" // Cover RequiredMeters on foot while fully naked AND in the run gait. Distance accrues only while that -// condition (and any constraints) hold; per-sample movement is clamped so a teleport can't satisfy it. +// condition (and any constraints) hold; the base clamps per-sample movement so a teleport can't satisfy it. UCLASS(EditInlineNew, DisplayName = "Run Naked Distance") -class NAKEDDESIRE_API URunNakedDistanceObjective : public UCoverageObjectiveBase +class NAKEDDESIRE_API URunNakedDistanceObjective : public UTravelObjectiveBase { GENERATED_BODY() public: virtual FText GetDescription() const override; - virtual float GetProgress() const override; protected: - virtual void OnActivate() override; - virtual void OnDeactivate() override; - -private: - void SampleDistance(); - - UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1)) - float RequiredMeters = 50.0f; - - float AccumulatedCm = 0.0f; - FVector LastLocation = FVector::ZeroVector; - FTimerHandle SampleTimer; + virtual bool DoesSampleCount() const override; }; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.cpp b/Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.cpp new file mode 100644 index 00000000..a31ed694 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.cpp @@ -0,0 +1,29 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "StayBelowCoverageObjective.h" + +#include "NakedDesire/Clothing/BodyPart.h" + +#define LOCTEXT_NAMESPACE "Commissions.Objectives.StayBelowCoverage" + +bool UStayBelowCoverageObjective::IsConditionMet() const +{ + return GetEffectiveCoverage(EBodyPart::Boobs) <= CoverageThreshold + && GetEffectiveCoverage(EBodyPart::Ass) <= CoverageThreshold + && GetEffectiveCoverage(EBodyPart::Genitals) <= CoverageThreshold; +} + +FText UStayBelowCoverageObjective::GetDescription() const +{ + if (RequiredHoldSeconds > 0.0f) + { + return FText::Format(LOCTEXT("Timed", "Keep every part under {0} covered for {1} seconds"), + FText::AsPercent(CoverageThreshold), FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds))); + } + + return FText::Format(LOCTEXT("Instant", "Bare yourself until every part is under {0} covered"), + FText::AsPercent(CoverageThreshold)); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.h b/Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.h new file mode 100644 index 00000000..d603c53f --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/StayBelowCoverageObjective.h @@ -0,0 +1,26 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "CoverageObjectiveBase.h" +#include "StayBelowCoverageObjective.generated.h" + +// StayBelowCoverage(threshold, duration): every exposable region must read at or below CoverageThreshold +// — revealing all over without being fully nude. Names its own threshold rather than reusing the player's +// reveal cutoff, so it can ask for "barely dressed" independent of the observation tuning. +UCLASS(EditInlineNew, DisplayName = "Stay Below Coverage") +class NAKEDDESIRE_API UStayBelowCoverageObjective : public UCoverageObjectiveBase +{ + GENERATED_BODY() + +public: + virtual FText GetDescription() const override; + +protected: + virtual bool IsConditionMet() const override; + +private: + UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 0, ClampMax = 1)) + float CoverageThreshold = 0.5f; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.cpp b/Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.cpp new file mode 100644 index 00000000..8f144fd6 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.cpp @@ -0,0 +1,61 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "TravelObjectiveBase.h" + +#include "TimerManager.h" +#include "NakedDesire/Player/NakedDesireCharacter.h" + +namespace +{ + constexpr float SampleIntervalSeconds = 0.2f; + constexpr float MaxStepCm = 1000.0f; // ignore single-sample jumps larger than this (teleports / streaming) +} + +void UTravelObjectiveBase::OnActivate() +{ + Super::OnActivate(); + + AccumulatedCm = 0.0f; + + if (Player) + { + LastLocation = Player->GetActorLocation(); + Player->GetWorldTimerManager().SetTimer(SampleTimer, this, &UTravelObjectiveBase::SampleDistance, SampleIntervalSeconds, true); + } +} + +void UTravelObjectiveBase::OnDeactivate() +{ + if (Player) + Player->GetWorldTimerManager().ClearTimer(SampleTimer); + SampleTimer.Invalidate(); + + Super::OnDeactivate(); +} + +void UTravelObjectiveBase::SampleDistance() +{ + if (bSatisfied || !Player) + return; + + const FVector Now = Player->GetActorLocation(); + const float Delta = FVector::Dist(Now, LastLocation); + LastLocation = Now; + + if (DoesSampleCount() && AreConstraintsMet()) + { + AccumulatedCm += FMath::Min(Delta, MaxStepCm); + if (AccumulatedCm >= RequiredMeters * 100.0f) + MarkSatisfied(); + } +} + +float UTravelObjectiveBase::GetProgress() const +{ + if (bSatisfied) + return 1.0f; + + const float TargetCm = RequiredMeters * 100.0f; + return TargetCm > 0.0f ? FMath::Clamp(AccumulatedCm / TargetCm, 0.0f, 1.0f) : 0.0f; +} \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.h b/Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.h new file mode 100644 index 00000000..b0486517 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/TravelObjectiveBase.h @@ -0,0 +1,36 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "CoverageObjectiveBase.h" +#include "TravelObjectiveBase.generated.h" + +// Shared base for "cover RequiredMeters while holds" objectives. Owns the distance-sampling +// timer; per-sample movement is clamped so a teleport / streaming jump can't satisfy it. Subclasses only +// declare what counts via DoesSampleCount() (e.g. naked + running, or a part revealing while walking). +UCLASS(Abstract) +class NAKEDDESIRE_API UTravelObjectiveBase : public UCoverageObjectiveBase +{ + GENERATED_BODY() + +public: + virtual float GetProgress() const override; + +protected: + virtual void OnActivate() override; + virtual void OnDeactivate() override; + + // Whether the latest movement sample should accrue toward the distance (constraints are ANDed on top). + virtual bool DoesSampleCount() const { return false; } + + UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1)) + float RequiredMeters = 50.0f; + +private: + void SampleDistance(); + + float AccumulatedCm = 0.0f; + FVector LastLocation = FVector::ZeroVector; + FTimerHandle SampleTimer; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.cpp b/Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.cpp new file mode 100644 index 00000000..b66b143e --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.cpp @@ -0,0 +1,13 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "WalkNakedDistanceObjective.h" + +#define LOCTEXT_NAMESPACE "Commissions.Objectives.WalkNakedDistance" + +FText UWalkNakedDistanceObjective::GetDescription() const +{ + return FText::Format(LOCTEXT("Walk", "Walk {0} m naked"), FText::AsNumber(FMath::RoundToInt(RequiredMeters))); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.h b/Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.h new file mode 100644 index 00000000..0e521ed9 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/WalkNakedDistanceObjective.h @@ -0,0 +1,21 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "TravelObjectiveBase.h" +#include "WalkNakedDistanceObjective.generated.h" + +// §13.4 WalkNakedDistance(meters): cover RequiredMeters on foot while fully naked, at any gait. The +// general "travel naked" objective — distinct from RunNakedDistance, which additionally requires running. +UCLASS(EditInlineNew, DisplayName = "Walk Naked Distance") +class NAKEDDESIRE_API UWalkNakedDistanceObjective : public UTravelObjectiveBase +{ + GENERATED_BODY() + +public: + virtual FText GetDescription() const override; + +protected: + virtual bool DoesSampleCount() const override { return IsFullyNaked(); } +}; \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.cpp b/Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.cpp new file mode 100644 index 00000000..3e0341d3 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.cpp @@ -0,0 +1,14 @@ +// © 2025 Naked People Team. All Rights Reserved. + + +#include "WalkNakedWhileObservedObjective.h" + +#define LOCTEXT_NAMESPACE "Commissions.Objectives.WalkNakedWhileObserved" + +FText UWalkNakedWhileObservedObjective::GetDescription() const +{ + return FText::Format(LOCTEXT("Walk", "Walk {0} m naked with at least {1} watching"), + FText::AsNumber(FMath::RoundToInt(RequiredMeters)), FText::AsNumber(MinObservers)); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.h b/Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.h new file mode 100644 index 00000000..68ceaf55 --- /dev/null +++ b/Source/NakedDesire/Commissions/Objectives/WalkNakedWhileObservedObjective.h @@ -0,0 +1,25 @@ +// © 2025 Naked People Team. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "TravelObjectiveBase.h" +#include "WalkNakedWhileObservedObjective.generated.h" + +// Cover RequiredMeters while fully naked AND with at least MinObservers watching — a "naked parade past +// a crowd" objective. Distance only accrues while both hold (lose your audience and progress stalls). +UCLASS(EditInlineNew, DisplayName = "Walk Naked While Observed") +class NAKEDDESIRE_API UWalkNakedWhileObservedObjective : public UTravelObjectiveBase +{ + GENERATED_BODY() + +public: + virtual FText GetDescription() const override; + +protected: + virtual bool DoesSampleCount() const override { return IsFullyNaked() && GetObserverCount() >= MinObservers; } + +private: + UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1)) + int32 MinObservers = 1; +}; \ No newline at end of file diff --git a/Source/NakedDesire/Interactables/ItemPickup.cpp b/Source/NakedDesire/Interactables/ItemPickup.cpp index a0ead4fd..7b0b117d 100644 --- a/Source/NakedDesire/Interactables/ItemPickup.cpp +++ b/Source/NakedDesire/Interactables/ItemPickup.cpp @@ -6,6 +6,7 @@ #include "Components/WidgetComponent.h" #include "Kismet/GameplayStatics.h" #include "NakedDesire/Clothing/ClothingItemDefinition.h" +#include "NakedDesire/Clothing/DroppedClothingSubsystem.h" #include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingManager.h" #include "NakedDesire/Player/NakedDesireCharacter.h" @@ -63,8 +64,19 @@ void AItemPickup::ShowInteractionProximityHint_Implementation() void AItemPickup::BeginPlay() { Super::BeginPlay(); - + InteractionHint->SetVisibility(false); + + if (UDroppedClothingSubsystem* Dropped = GetWorld()->GetSubsystem()) + Dropped->RegisterPickup(this); +} + +void AItemPickup::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (UDroppedClothingSubsystem* Dropped = GetWorld()->GetSubsystem()) + Dropped->UnregisterPickup(this); + + Super::EndPlay(EndPlayReason); } void AItemPickup::SetItem(UClothingItemInstance* InItem) diff --git a/Source/NakedDesire/Interactables/ItemPickup.h b/Source/NakedDesire/Interactables/ItemPickup.h index ca11727e..2d017391 100644 --- a/Source/NakedDesire/Interactables/ItemPickup.h +++ b/Source/NakedDesire/Interactables/ItemPickup.h @@ -30,6 +30,7 @@ public: protected: virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; private: UPROPERTY()