Files
Naked-Desire/Source/NakedDesire/Player/NakedDesireCharacter.cpp
T

282 lines
10 KiB
C++

// © 2025 Naked People Team. All Rights Reserved.
#include "NakedDesireCharacter.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "NakedDesire/MissionBuilder/MissionsManager.h"
#include "NakedDesire/Stats/StatsManager.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Kismet/GameplayStatics.h"
#include "Internationalization/Text.h"
#include "NakedDesire/Censorship/CensorshipComponent.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingVisualsComponent.h"
#include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Global/NakedDesireHUD.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
#include "NakedDesire/Interaction/InteractionComponent.h"
#include "NakedDesire/UI/GameLayoutWidget.h"
#include "Perception/AIPerceptionStimuliSourceComponent.h"
#include "Perception/AISense_Sight.h"
ANakedDesireCharacter::ANakedDesireCharacter()
{
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->GetNavAgentPropertiesRef().bCanCrouch = true;
ClothingManager = CreateDefaultSubobject<UClothingManager>("Clothing Manager");
StatsManager = CreateDefaultSubobject<UStatsManager>("Stats Manager");
MissionsManager = CreateDefaultSubobject<UMissionsManager>("Missions Manager");
ClothingVisualsComponent = CreateDefaultSubobject<UClothingVisualsComponent>(TEXT("Clothing Visuals Component"));
BoobLCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Boob L Censorship"));
BoobLCensorship->SetupAttachment(GetMesh(), FName(TEXT("boob_l")));
BoobRCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Boob R Censorship"));
BoobRCensorship->SetupAttachment(GetMesh(), FName(TEXT("boob_r")));
VaginaCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Vagina Censorship"));
VaginaCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));
AnalCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Anal Censorship"));
AnalCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));
CensorshipComponent = CreateDefaultSubobject<UCensorshipComponent>(TEXT("Censorship Component"));
StimuliSourceComponent = CreateDefaultSubobject<UAIPerceptionStimuliSourceComponent>(TEXT("Stimuli Source Component"));
InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("Interaction Component"));
}
EGait ANakedDesireCharacter::GetGait() const
{
return Gait;
}
void ANakedDesireCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (StatsManager->Stamina == 0 && Gait == EGait::Run)
{
SetIsRunning(false);
}
if (Gait == EGait::Run && GetCharacterMovement()->Velocity.SizeSquared2D() > 100 && StatsManager->Stamina > 0)
{
GetCharacterMovement()->MaxWalkSpeed = RunSpeed;
StatsManager->DecreaseStamina(7 * DeltaTime);
}
else
{
GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
if (StatsManager->Stamina < StatsManager->MaxStamina)
{
StatsManager->IncreaseStamina(15 * DeltaTime);
}
}
}
void ANakedDesireCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ANakedDesireCharacter::OnLook);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ANakedDesireCharacter::OnMove);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnRunPress);
EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnRunRelease);
EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnCrouchToggle);
EnhancedInputComponent->BindAction(EquipmentAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnEquipmentPress);
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnInteractPress);
}
}
void ANakedDesireCharacter::NotifyControllerChanged()
{
Super::NotifyControllerChanged();
if (const APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0))
{
if (const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
if (MappingContext)
{
InputSystem->AddMappingContext(MappingContext, 0);
}
}
}
}
}
void ANakedDesireCharacter::BeginPlay()
{
Super::BeginPlay();
StimuliSourceComponent->RegisterForSense(UAISense_Sight::StaticClass());
StimuliSourceComponent->RegisterWithPerceptionSystem();
// Initialize after Super::BeginPlay so clothing hydration has populated the
// equipped set; both components sync from that final state.
ClothingVisualsComponent->Initialize(GetMesh(), ClothingManager);
CensorshipComponent->Initialize(ClothingManager, BoobLCensorship, BoobRCensorship, VaginaCensorship, AnalCensorship);
HUD = Cast<ANakedDesireHUD>(UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetHUD());
}
UAISense_Sight::EVisibilityResult ANakedDesireCharacter::CanBeSeenFrom(const FCanBeSeenFromContext& Context,
FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested,
float& OutSightStrength, int32* UserData, const FOnPendingVisibilityQueryProcessedDelegate* Delegate)
{
OutNumberOfLoSChecksPerformed++;
bool bTopVisible = false;
bool bBottomVisible = false;
const float Exposure = ComputeObservedExposure(Context.ObserverLocation, Context.IgnoreActor, bTopVisible, bBottomVisible);
// An observer perceives the player only when a revealing body part is within their line of sight.
if (Exposure > 0.0f)
{
const bool bSeenTop = bTopVisible && RevealAmount(EBodyPart::Boobs) > 0.0f;
OutSeenLocation = GetMesh()->GetBoneLocation(FName(bSeenTop ? TEXT("boobs_root") : TEXT("pelvis")));
OutSightStrength = 1.0f;
return UAISense_Sight::EVisibilityResult::Visible;
}
return UAISense_Sight::EVisibilityResult::NotVisible;
}
float ANakedDesireCharacter::GetObservedExposureFrom(const FVector& ObserverLocation, const AActor* IgnoreActor)
{
bool bTopVisible = false;
bool bBottomVisible = false;
return ComputeObservedExposure(ObserverLocation, IgnoreActor, bTopVisible, bBottomVisible);
}
float ANakedDesireCharacter::ComputeObservedExposure(const FVector& ObserverLocation, const AActor* IgnoreActor,
bool& bOutTopVisible, bool& bOutBottomVisible)
{
const FVector BoobsBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("boobs_root")));
const FVector PelvisBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("pelvis")));
float Exposure = 0.0f;
// Top region -> Boobs. A region is "visible" when its LOS trace is unobstructed (or only hits self).
FHitResult TopHit;
bOutTopVisible = !CheckSight(ObserverLocation, BoobsBoneLocation, TopHit, IgnoreActor) || TopHit.GetActor() == this;
if (bOutTopVisible)
Exposure += RevealAmount(EBodyPart::Boobs);
// Bottom region (pelvis) -> Ass + Genitals; one trace gates both lower parts.
FHitResult BottomHit;
bOutBottomVisible = !CheckSight(ObserverLocation, PelvisBoneLocation, BottomHit, IgnoreActor) || BottomHit.GetActor() == this;
if (bOutBottomVisible)
{
Exposure += RevealAmount(EBodyPart::Ass);
Exposure += RevealAmount(EBodyPart::Genitals);
}
return Exposure;
}
float ANakedDesireCharacter::RevealAmount(const EBodyPart BodyPart)
{
const float Coverage = ClothingManager->GetEffectiveCoverage(BodyPart);
return Coverage < ObservationRevealThreshold ? (1.0f - Coverage) : 0.0f;
}
bool ANakedDesireCharacter::CheckSight(const FVector& StartLocation, const FVector& EndLocation, FHitResult& HitResult,
const AActor* IgnoreActor)
{
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(AILineOfSight), true);
QueryParams.AddIgnoredActor(IgnoreActor);
QueryParams.AddIgnoredActor(this); // ignore self
const bool bHit = GetWorld()->LineTraceSingleByChannel(
HitResult,
StartLocation,
EndLocation,
ECC_Visibility,
QueryParams
);
// DrawDebugLine(GetWorld(), StartLocation, EndLocation, bHit ? FColor::Red : FColor::Green, false, 1.0f);
return bHit;
}
void ANakedDesireCharacter::LogTest()
{
UClothingItemInstance* ClothingItemInstance = ClothingManager->GetSlotClothing(EClothingSlotType::UnderwearTop);
UE_LOG(LogTemp, Warning, TEXT("ANakedDesireCharacter::LogTest %s"), *ClothingItemInstance->GetInstanceId().ToString());
}
void ANakedDesireCharacter::OnLook(const FInputActionValue& Value)
{
const FVector2D MouseValue = Value.Get<FVector2D>();
AddControllerYawInput(MouseValue.X);
AddControllerPitchInput(MouseValue.Y);
}
void ANakedDesireCharacter::OnMove(const FInputActionValue& Value)
{
const FVector2D MoveInput = Value.Get<FVector2D>();
const FRotator Rotator = Controller->GetControlRotation();
const FRotationMatrix Direction = FRotationMatrix(FRotator(0, Rotator.Yaw, 0));
const FVector ForwardDirection = Direction.GetUnitAxis(EAxis::X);
const FVector RightDirection = Direction.GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MoveInput.Y);
AddMovementInput(RightDirection, MoveInput.X);
}
void ANakedDesireCharacter::OnRunPress(const FInputActionValue& Value)
{
SetIsRunning(true);
}
void ANakedDesireCharacter::OnRunRelease(const FInputActionValue& Value)
{
SetIsRunning(false);
}
void ANakedDesireCharacter::OnCrouchToggle(const FInputActionValue& Value)
{
if (GetCharacterMovement()->IsCrouching())
{
UnCrouch();
}
else
{
Crouch();
}
}
void ANakedDesireCharacter::OnEquipmentPress(const FInputActionValue& Value)
{
HUD->GetGameLayoutWidget()->OpenInventory();
}
void ANakedDesireCharacter::OnInteractPress(const FInputActionValue& Value)
{
InteractionComponent->Interact();
}
void ANakedDesireCharacter::NotifyNoticed(ANPCAIController* NPCController)
{
OnNoticed.Broadcast(NPCController);
}
void ANakedDesireCharacter::SetIsRunning(const bool Value)
{
Gait = Value ? EGait::Run : EGait::Walk;
GetCharacterMovement()->MaxWalkSpeed = Gait == EGait::Run ? RunSpeed : WalkSpeed;
}