Added commissions system
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "BeFullyNakedNearNPCsObjective.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.BeFullyNakedNearNPCs"
|
||||
|
||||
bool UBeFullyNakedNearNPCsObjective::IsConditionMet() const
|
||||
{
|
||||
return IsFullyNaked() && GetObserverCount() >= RequiredNPCs;
|
||||
}
|
||||
|
||||
FText UBeFullyNakedNearNPCsObjective::GetDescription() const
|
||||
{
|
||||
const FText People = (RequiredNPCs == 1)
|
||||
? LOCTEXT("Person", "1 person")
|
||||
: FText::Format(LOCTEXT("People", "{0} people"), FText::AsNumber(RequiredNPCs));
|
||||
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Be fully naked in front of {0} for {1} seconds"),
|
||||
People, FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return FText::Format(LOCTEXT("Instant", "Get fully naked in front of {0}"), People);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,25 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CoverageObjectiveBase.h"
|
||||
#include "BeFullyNakedNearNPCsObjective.generated.h"
|
||||
|
||||
// §13.4 BeFullyNakedNearNPCs(count, durationSeconds): fully naked while at least `count` NPCs observe,
|
||||
// sustained for the duration. Observer count is inherited from UObserverObjectiveBase.
|
||||
UCLASS(EditInlineNew, DisplayName = "Be Fully Naked Near NPCs")
|
||||
class NAKEDDESIRE_API UBeFullyNakedNearNPCsObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override;
|
||||
|
||||
private:
|
||||
UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1))
|
||||
int32 RequiredNPCs = 1;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "BeFullyNakedObjective.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.BeFullyNaked"
|
||||
|
||||
FText UBeFullyNakedObjective::GetDescription() const
|
||||
{
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Be fully naked for {0} seconds"),
|
||||
FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return LOCTEXT("Instant", "Get fully naked");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CoverageObjectiveBase.h"
|
||||
#include "BeFullyNakedObjective.generated.h"
|
||||
|
||||
// §13.4 BeFullyNaked(durationSeconds): fully unclothed for RequiredHoldSeconds; no NPC requirement.
|
||||
UCLASS(EditInlineNew, DisplayName = "Be Fully Naked")
|
||||
class NAKEDDESIRE_API UBeFullyNakedObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override { return IsFullyNaked(); }
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "BeObservedWhileExposedObjective.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.BeObservedWhileExposed"
|
||||
|
||||
FText UBeObservedWhileExposedObjective::GetDescription() const
|
||||
{
|
||||
const FText People = (RequiredObservers == 1)
|
||||
? LOCTEXT("Person", "someone")
|
||||
: FText::Format(LOCTEXT("People", "{0} people"), FText::AsNumber(RequiredObservers));
|
||||
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Stay exposed in front of {0} for {1} seconds"),
|
||||
People, FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return FText::Format(LOCTEXT("Instant", "Get exposed in front of {0}"), People);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,25 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CoverageObjectiveBase.h"
|
||||
#include "BeObservedWhileExposedObjective.generated.h"
|
||||
|
||||
// At least RequiredObservers NPCs observing while any region is revealing (not necessarily fully nude).
|
||||
// Generalizes "naked near NPCs" to partial exposure.
|
||||
UCLASS(EditInlineNew, DisplayName = "Be Observed While Exposed")
|
||||
class NAKEDDESIRE_API UBeObservedWhileExposedObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override { return IsAnyPartRevealing() && GetObserverCount() >= RequiredObservers; }
|
||||
|
||||
private:
|
||||
UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1))
|
||||
int32 RequiredObservers = 1;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "CoverageObjectiveBase.h"
|
||||
|
||||
#include "NakedDesire/Clothing/ClothingManager.h"
|
||||
#include "NakedDesire/Player/NakedDesireCharacter.h"
|
||||
|
||||
void UCoverageObjectiveBase::OnActivate()
|
||||
{
|
||||
Super::OnActivate(); // observer subscription
|
||||
|
||||
if (Player && Player->ClothingManager)
|
||||
{
|
||||
Player->ClothingManager->OnClothingEquip.AddUniqueDynamic(this, &UCoverageObjectiveBase::HandleClothingChanged);
|
||||
Player->ClothingManager->OnClothingUnequip.AddUniqueDynamic(this, &UCoverageObjectiveBase::HandleClothingChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void UCoverageObjectiveBase::OnDeactivate()
|
||||
{
|
||||
if (Player && Player->ClothingManager)
|
||||
{
|
||||
Player->ClothingManager->OnClothingEquip.RemoveDynamic(this, &UCoverageObjectiveBase::HandleClothingChanged);
|
||||
Player->ClothingManager->OnClothingUnequip.RemoveDynamic(this, &UCoverageObjectiveBase::HandleClothingChanged);
|
||||
}
|
||||
|
||||
Super::OnDeactivate(); // observer unsubscription
|
||||
}
|
||||
|
||||
bool UCoverageObjectiveBase::IsFullyNaked() const
|
||||
{
|
||||
if (!Player || !Player->ClothingManager)
|
||||
return false;
|
||||
|
||||
UClothingManager* CM = Player->ClothingManager;
|
||||
return CM->GetEffectiveCoverage(EBodyPart::Boobs) <= 0.0f
|
||||
&& CM->GetEffectiveCoverage(EBodyPart::Ass) <= 0.0f
|
||||
&& CM->GetEffectiveCoverage(EBodyPart::Genitals) <= 0.0f;
|
||||
}
|
||||
|
||||
bool UCoverageObjectiveBase::IsPartRevealing(EBodyPart Part) const
|
||||
{
|
||||
if (!Player || !Player->ClothingManager)
|
||||
return false;
|
||||
|
||||
return Player->ClothingManager->GetEffectiveCoverage(Part) < Player->ObservationRevealThreshold;
|
||||
}
|
||||
|
||||
bool UCoverageObjectiveBase::IsAnyPartRevealing() const
|
||||
{
|
||||
return IsPartRevealing(EBodyPart::Boobs)
|
||||
|| IsPartRevealing(EBodyPart::Ass)
|
||||
|| IsPartRevealing(EBodyPart::Genitals);
|
||||
}
|
||||
|
||||
void UCoverageObjectiveBase::HandleClothingChanged(UClothingItemInstance* ClothingItemInstance)
|
||||
{
|
||||
NotifyConditionChanged();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "NakedDesire/Clothing/BodyPart.h"
|
||||
#include "ObserverObjectiveBase.h"
|
||||
#include "CoverageObjectiveBase.generated.h"
|
||||
|
||||
class UClothingItemInstance;
|
||||
|
||||
// Shared base for objectives that read body coverage; re-evaluates on any equip / unequip. Inherits
|
||||
// the observer subscription from UObserverObjectiveBase, so coverage objectives can also gate on the
|
||||
// current observer count (e.g. "naked in front of N people") without extra wiring.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UCoverageObjectiveBase : public UObserverObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
virtual void OnActivate() override;
|
||||
virtual void OnDeactivate() override;
|
||||
|
||||
// Fully unclothed: no effective coverage on any of the three exposable regions.
|
||||
bool IsFullyNaked() const;
|
||||
|
||||
// Part reads as "revealing" — below the player's observation reveal threshold.
|
||||
bool IsPartRevealing(EBodyPart Part) const;
|
||||
|
||||
// Any of the three regions is revealing.
|
||||
bool IsAnyPartRevealing() const;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleClothingChanged(UClothingItemInstance* ClothingItemInstance);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "ExposeBodyPartObjective.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.ExposeBodyPart"
|
||||
|
||||
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 UExposeBodyPartObjective::GetDescription() const
|
||||
{
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Expose your {0} for {1} seconds"),
|
||||
BodyPartText(Part), FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return FText::Format(LOCTEXT("Instant", "Expose your {0}"), BodyPartText(Part));
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,25 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "NakedDesire/Clothing/BodyPart.h"
|
||||
#include "CoverageObjectiveBase.h"
|
||||
#include "ExposeBodyPartObjective.generated.h"
|
||||
|
||||
// §13.4 ExposeBodyPart(part, durationSeconds): the named part reads as revealing for the duration.
|
||||
UCLASS(EditInlineNew, DisplayName = "Expose Body Part")
|
||||
class NAKEDDESIRE_API UExposeBodyPartObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override { return IsPartRevealing(Part); }
|
||||
|
||||
private:
|
||||
UPROPERTY(EditDefaultsOnly)
|
||||
EBodyPart Part = EBodyPart::Boobs;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "GatherCrowdObjective.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.GatherCrowd"
|
||||
|
||||
FText UGatherCrowdObjective::GetDescription() const
|
||||
{
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Hold a crowd of {0} watchers for {1} seconds"),
|
||||
FText::AsNumber(RequiredObservers), FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return FText::Format(LOCTEXT("Instant", "Gather a crowd of {0} watchers"), FText::AsNumber(RequiredObservers));
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,25 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ObserverObjectiveBase.h"
|
||||
#include "GatherCrowdObjective.generated.h"
|
||||
|
||||
// Draw a crowd: at least RequiredObservers NPCs observing the player simultaneously. With
|
||||
// RequiredHoldSeconds > 0 the crowd must hold for that long; 0 = the instant the count is reached.
|
||||
UCLASS(EditInlineNew, DisplayName = "Gather a Crowd")
|
||||
class NAKEDDESIRE_API UGatherCrowdObjective : public UObserverObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override { return GetObserverCount() >= RequiredObservers; }
|
||||
|
||||
private:
|
||||
UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 1))
|
||||
int32 RequiredObservers = 3;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "ObserverObjectiveBase.h"
|
||||
|
||||
#include "NakedDesire/Player/NakedDesireCharacter.h"
|
||||
#include "NakedDesire/Stats/StatsManager.h"
|
||||
|
||||
void UObserverObjectiveBase::OnActivate()
|
||||
{
|
||||
if (Player && Player->StatsManager)
|
||||
Player->StatsManager->OnObserversChanged.AddUniqueDynamic(this, &UObserverObjectiveBase::HandleObserversChanged);
|
||||
}
|
||||
|
||||
void UObserverObjectiveBase::OnDeactivate()
|
||||
{
|
||||
if (Player && Player->StatsManager)
|
||||
Player->StatsManager->OnObserversChanged.RemoveDynamic(this, &UObserverObjectiveBase::HandleObserversChanged);
|
||||
}
|
||||
|
||||
int32 UObserverObjectiveBase::GetObserverCount() const
|
||||
{
|
||||
return (Player && Player->StatsManager) ? Player->StatsManager->GetObserverCount() : 0;
|
||||
}
|
||||
|
||||
void UObserverObjectiveBase::HandleObserversChanged()
|
||||
{
|
||||
NotifyConditionChanged();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "NakedDesire/Commissions/CommissionObjective.h"
|
||||
#include "ObserverObjectiveBase.generated.h"
|
||||
|
||||
// Base for objectives that react to how many NPCs are currently observing the player (the same
|
||||
// observer set that drives embarrassment). Re-evaluates whenever that count changes.
|
||||
UCLASS(Abstract)
|
||||
class NAKEDDESIRE_API UObserverObjectiveBase : public UCommissionObjective
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
virtual void OnActivate() override;
|
||||
virtual void OnDeactivate() override;
|
||||
|
||||
int32 GetObserverCount() const;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleObserversChanged();
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "ReachEmbarrassmentObjective.h"
|
||||
|
||||
#include "NakedDesire/Player/NakedDesireCharacter.h"
|
||||
#include "NakedDesire/Stats/StatsManager.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.ReachEmbarrassment"
|
||||
|
||||
void UReachEmbarrassmentObjective::OnActivate()
|
||||
{
|
||||
if (Player && Player->StatsManager)
|
||||
Player->StatsManager->EmbarrassmentUpdate.AddDynamic(this, &UReachEmbarrassmentObjective::HandleEmbarrassmentUpdate);
|
||||
}
|
||||
|
||||
void UReachEmbarrassmentObjective::OnDeactivate()
|
||||
{
|
||||
if (Player && Player->StatsManager)
|
||||
Player->StatsManager->EmbarrassmentUpdate.RemoveDynamic(this, &UReachEmbarrassmentObjective::HandleEmbarrassmentUpdate);
|
||||
}
|
||||
|
||||
bool UReachEmbarrassmentObjective::IsConditionMet() const
|
||||
{
|
||||
return CachedMax > 0.0f && CachedCurrent >= ThresholdFraction * CachedMax;
|
||||
}
|
||||
|
||||
void UReachEmbarrassmentObjective::HandleEmbarrassmentUpdate(float CurrentValue, float MaxValue)
|
||||
{
|
||||
CachedCurrent = CurrentValue;
|
||||
CachedMax = MaxValue;
|
||||
NotifyConditionChanged();
|
||||
}
|
||||
|
||||
FText UReachEmbarrassmentObjective::GetDescription() const
|
||||
{
|
||||
const FText Percent = FText::AsNumber(FMath::RoundToInt(ThresholdFraction * 100.0f));
|
||||
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Keep embarrassment above {0}% for {1} seconds"),
|
||||
Percent, FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return FText::Format(LOCTEXT("Instant", "Push your embarrassment past {0}%"), Percent);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,33 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "NakedDesire/Commissions/CommissionObjective.h"
|
||||
#include "ReachEmbarrassmentObjective.generated.h"
|
||||
|
||||
// Push embarrassment to ThresholdFraction of max. RequiredHoldSeconds = 0 satisfies on reaching it
|
||||
// (ReachEmbarrassment); > 0 requires staying at/above it for the duration (SustainEmbarrassment).
|
||||
UCLASS(EditInlineNew, DisplayName = "Reach Embarrassment")
|
||||
class NAKEDDESIRE_API UReachEmbarrassmentObjective : public UCommissionObjective
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual void OnActivate() override;
|
||||
virtual void OnDeactivate() override;
|
||||
virtual bool IsConditionMet() const override;
|
||||
|
||||
private:
|
||||
UPROPERTY(EditDefaultsOnly, meta = (ClampMin = 0.0, ClampMax = 1.0))
|
||||
float ThresholdFraction = 0.8f;
|
||||
|
||||
UFUNCTION()
|
||||
void HandleEmbarrassmentUpdate(float CurrentValue, float MaxValue);
|
||||
|
||||
float CachedCurrent = 0.0f;
|
||||
float CachedMax = 0.0f;
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "RunNakedDistanceObjective.h"
|
||||
|
||||
#include "TimerManager.h"
|
||||
#include "NakedDesire/Global/Gait.h"
|
||||
#include "NakedDesire/Player/NakedDesireCharacter.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.RunNakedDistance"
|
||||
|
||||
namespace
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
FText URunNakedDistanceObjective::GetDescription() const
|
||||
{
|
||||
return FText::Format(LOCTEXT("Run", "Run {0} m naked"), FText::AsNumber(FMath::RoundToInt(RequiredMeters)));
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,33 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CoverageObjectiveBase.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.
|
||||
UCLASS(EditInlineNew, DisplayName = "Run Naked Distance")
|
||||
class NAKEDDESIRE_API URunNakedDistanceObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "StayUnseenWhileNakedObjective.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.StayUnseenWhileNaked"
|
||||
|
||||
FText UStayUnseenWhileNakedObjective::GetDescription() const
|
||||
{
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Stay fully naked and unseen for {0} seconds"),
|
||||
FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return LOCTEXT("Instant", "Be fully naked with nobody watching");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CoverageObjectiveBase.h"
|
||||
#include "StayUnseenWhileNakedObjective.generated.h"
|
||||
|
||||
// Stealth exhibitionism: be fully naked with ZERO observers for the duration (streak between patrols).
|
||||
UCLASS(EditInlineNew, DisplayName = "Stay Unseen While Naked")
|
||||
class NAKEDDESIRE_API UStayUnseenWhileNakedObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override { return IsFullyNaked() && GetObserverCount() == 0; }
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "WearOnlyUnderwearObjective.h"
|
||||
|
||||
#include "NakedDesire/Clothing/ClothingManager.h"
|
||||
#include "NakedDesire/Player/NakedDesireCharacter.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Commissions.Objectives.WearOnlyUnderwear"
|
||||
|
||||
bool UWearOnlyUnderwearObjective::IsConditionMet() const
|
||||
{
|
||||
if (!Player || !Player->ClothingManager)
|
||||
return false;
|
||||
|
||||
UClothingManager* CM = Player->ClothingManager;
|
||||
|
||||
const bool bUnderwearWorn =
|
||||
CM->IsClothingTypeOn(EClothingSlotType::UnderwearTop) ||
|
||||
CM->IsClothingTypeOn(EClothingSlotType::UnderwearBottom);
|
||||
|
||||
const bool bOuterClear =
|
||||
!CM->IsClothingTypeOn(EClothingSlotType::Outerwear) &&
|
||||
!CM->IsClothingTypeOn(EClothingSlotType::Top) &&
|
||||
!CM->IsClothingTypeOn(EClothingSlotType::Bottom) &&
|
||||
!CM->IsClothingTypeOn(EClothingSlotType::Bodysuit);
|
||||
|
||||
return bUnderwearWorn && bOuterClear;
|
||||
}
|
||||
|
||||
FText UWearOnlyUnderwearObjective::GetDescription() const
|
||||
{
|
||||
if (RequiredHoldSeconds > 0.0f)
|
||||
{
|
||||
return FText::Format(LOCTEXT("Timed", "Stay in just your underwear for {0} seconds"),
|
||||
FText::AsNumber(FMath::RoundToInt(RequiredHoldSeconds)));
|
||||
}
|
||||
|
||||
return LOCTEXT("Instant", "Strip down to just your underwear");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,21 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CoverageObjectiveBase.h"
|
||||
#include "WearOnlyUnderwearObjective.generated.h"
|
||||
|
||||
// Stripped to underwear: at least one underwear slot worn while every outer body-clothing slot
|
||||
// (Outerwear / Top / Bottom / Bodysuit) is empty. Socks / footwear / accessories are ignored.
|
||||
UCLASS(EditInlineNew, DisplayName = "Wear Only Underwear")
|
||||
class NAKEDDESIRE_API UWearOnlyUnderwearObjective : public UCoverageObjectiveBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FText GetDescription() const override;
|
||||
|
||||
protected:
|
||||
virtual bool IsConditionMet() const override;
|
||||
};
|
||||
Reference in New Issue
Block a user