Added commision objectives
This commit is contained in:
+17
-9
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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.
|
||||
|
||||
@@ -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<AItemPickup>& Pickup : Pickups)
|
||||
{
|
||||
if (Pickup)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UDroppedClothingSubsystem::GetDistanceToNearestDroppedClothing(const FVector& From, float& OutDistance) const
|
||||
{
|
||||
float NearestSq = TNumericLimits<float>::Max();
|
||||
bool bFound = false;
|
||||
|
||||
for (const TObjectPtr<AItemPickup>& 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;
|
||||
}
|
||||
@@ -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<TObjectPtr<AItemPickup>> Pickups;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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<UDroppedClothingSubsystem>() : 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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 <condition> 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;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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(); }
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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"
|
||||
@@ -65,6 +66,17 @@ void AItemPickup::BeginPlay()
|
||||
Super::BeginPlay();
|
||||
|
||||
InteractionHint->SetVisibility(false);
|
||||
|
||||
if (UDroppedClothingSubsystem* Dropped = GetWorld()->GetSubsystem<UDroppedClothingSubsystem>())
|
||||
Dropped->RegisterPickup(this);
|
||||
}
|
||||
|
||||
void AItemPickup::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
if (UDroppedClothingSubsystem* Dropped = GetWorld()->GetSubsystem<UDroppedClothingSubsystem>())
|
||||
Dropped->UnregisterPickup(this);
|
||||
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void AItemPickup::SetItem(UClothingItemInstance* InItem)
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
|
||||
Reference in New Issue
Block a user