Added commision objectives
This commit is contained in:
@@ -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"
|
||||
@@ -63,8 +64,19 @@ void AItemPickup::ShowInteractionProximityHint_Implementation()
|
||||
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