Added commision objectives

This commit is contained in:
koritsa
2026-06-01 15:03:37 +03:00
committed by koritsa
parent 9a5a0003b1
commit 4427627e7d
25 changed files with 626 additions and 82 deletions
@@ -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;
};