// © 2025 Naked People Team. All Rights Reserved. #include "NakedDesireCharacter.h" #include "NakedDesire/Locations/LocationTrigger.h" #include "NakedDesire/Clothing/ClothingManager.h" #include "Components/CapsuleComponent.h" #include "GameFramework/CharacterMovementComponent.h" #include "NakedDesire/InteractionSystem/InteractionManager.h" #include "NakedDesire/InteractionSystem/InteractionTarget.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/Clothing/ClothingItem.h" #include "NakedDesire/Clothing/ClothingItemInstance.h" #include "NakedDesire/Clothing/ClothingSlotsData.h" #include "NakedDesire/Global/Constants.h" #include "NakedDesire/Global/NakedDesireUserSettings.h" #include "NakedDesire/UI/RadialMenu/RadialMenuController.h" #include "Perception/AIPerceptionStimuliSourceComponent.h" #include "Perception/AISense_Sight.h" ANakedDesireCharacter::ANakedDesireCharacter() { GetCharacterMovement()->MaxWalkSpeed = WalkSpeed; GetCapsuleComponent()->OnComponentBeginOverlap.AddUniqueDynamic(this, &ANakedDesireCharacter::OnBeginOverlap); GetCapsuleComponent()->OnComponentEndOverlap.AddUniqueDynamic(this, &ANakedDesireCharacter::OnEndOverlap); bUseControllerRotationYaw = false; GetCharacterMovement()->bOrientRotationToMovement = true; GetCharacterMovement()->GetNavAgentPropertiesRef().bCanCrouch = true; ClothingManager = CreateDefaultSubobject("Clothing Manager"); StatsManager = CreateDefaultSubobject("Stats Manager"); MissionsManager = CreateDefaultSubobject("Missions Manager"); InteractionManager = CreateDefaultSubobject("Interaction Manager"); RadialMenuController = CreateDefaultSubobject(TEXT("Radial Menu Controller")); NipplesMeshComponent = CreateDefaultSubobject("Nipples"); NipplesMeshComponent->SetupAttachment(GetMesh()); AnalMeshComponent = CreateDefaultSubobject("Anal"); AnalMeshComponent->SetupAttachment(GetMesh()); VaginaMeshComponent = CreateDefaultSubobject("Vagina"); VaginaMeshComponent->SetupAttachment(GetMesh()); HeadMeshComponent = CreateDefaultSubobject("Head"); HeadMeshComponent->SetupAttachment(GetMesh()); NeckMeshComponent = CreateDefaultSubobject("Neck"); NeckMeshComponent->SetupAttachment(GetMesh()); FaceMeshComponent = CreateDefaultSubobject("Face"); FaceMeshComponent->SetupAttachment(GetMesh()); EyesMeshComponent = CreateDefaultSubobject("Eyes"); EyesMeshComponent->SetupAttachment(GetMesh()); BodySuitMeshComponent = CreateDefaultSubobject("Body Suit"); BodySuitMeshComponent->SetupAttachment(GetMesh()); TopMeshComponent = CreateDefaultSubobject("Top"); TopMeshComponent->SetupAttachment(GetMesh()); BottomMeshComponent = CreateDefaultSubobject("Bottom"); BottomMeshComponent->SetupAttachment(GetMesh()); UnderwearTopMeshComponent = CreateDefaultSubobject("Underwear Top"); UnderwearTopMeshComponent->SetupAttachment(GetMesh()); UnderwearBottomMeshComponent = CreateDefaultSubobject("Underwear Bottom"); UnderwearBottomMeshComponent->SetupAttachment(GetMesh()); SocksMeshComponent = CreateDefaultSubobject("Socks"); SocksMeshComponent->SetupAttachment(GetMesh()); FootwearMeshComponent = CreateDefaultSubobject("Footwear"); FootwearMeshComponent->SetupAttachment(GetMesh()); OuterwearMeshComponent = CreateDefaultSubobject("Outerwear"); OuterwearMeshComponent->SetupAttachment(GetMesh()); WristRestraintMeshComponent = CreateDefaultSubobject("Wrist Restraint"); WristRestraintMeshComponent->SetupAttachment(GetMesh()); AnkleRestraintMeshComponent = CreateDefaultSubobject("Ankle Restraint"); AnkleRestraintMeshComponent->SetupAttachment(GetMesh()); NeckRestraintMeshComponent = CreateDefaultSubobject("Neck Restraint"); NeckRestraintMeshComponent->SetupAttachment(GetMesh()); BoobLCensorship = CreateDefaultSubobject(TEXT("Boob L Censorship")); BoobLCensorship->SetupAttachment(GetMesh(), FName(TEXT("boob_l"))); BoobRCensorship = CreateDefaultSubobject(TEXT("Boob R Censorship")); BoobRCensorship->SetupAttachment(GetMesh(), FName(TEXT("boob_r"))); VaginaCensorship = CreateDefaultSubobject(TEXT("Vagina Censorship")); VaginaCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis"))); AnalCensorship = CreateDefaultSubobject(TEXT("Anal Censorship")); AnalCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis"))); StimuliSourceComponent = CreateDefaultSubobject(TEXT("Stimuli Source Component")); } EGait ANakedDesireCharacter::GetGait() const { return Gait; } EStance ANakedDesireCharacter::GetStance() const { return Stance; } 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(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); } } void ANakedDesireCharacter::NotifyControllerChanged() { Super::NotifyControllerChanged(); if (const APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0)) { if (const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer()) { if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem()) { if (MappingContext) { InputSystem->AddMappingContext(MappingContext, 0); } } } } } void ANakedDesireCharacter::BeginPlay() { Super::BeginPlay(); StimuliSourceComponent->RegisterForSense(TSubclassOf()); StimuliSourceComponent->RegisterWithPerceptionSystem(); ClothingManager->OnClothingEquip.AddUniqueDynamic(this, &ANakedDesireCharacter::OnClothingEquip); ClothingManager->OnClothingUnequip.AddUniqueDynamic(this, &ANakedDesireCharacter::OnClothingUnequip); UNakedDesireUserSettings::GetNakedDesireUserSettings()->OnSettingsChanged.AddUniqueDynamic(this, &ANakedDesireCharacter::OnSettingsChanged); } UAISense_Sight::EVisibilityResult ANakedDesireCharacter::CanBeSeenFrom(const FCanBeSeenFromContext& Context, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested, float& OutSightStrength, int32* UserData, const FOnPendingVisibilityQueryProcessedDelegate* Delegate) { const FVector StartLocation = Context.ObserverLocation; const FVector BoobsBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("boobs_root"))); const FVector PelvisBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("pelvis"))); OutNumberOfLoSChecksPerformed++; FHitResult BoobsHitResult; const bool BoobsHit = CheckSight(StartLocation, BoobsBoneLocation, BoobsHitResult, Context.IgnoreActor); FHitResult PelvisHitResult; const bool PelvisHit = CheckSight(StartLocation, PelvisBoneLocation, PelvisHitResult, Context.IgnoreActor); if ((!BoobsHit || BoobsHitResult.GetActor() == this) && ClothingManager->IsBodyPartExposed(EBodyPart::Boobs)) { UE_LOG(LogTemp, Warning, TEXT("Boobs hit")); OutSeenLocation = BoobsBoneLocation; OutSightStrength = 1.0f; return UAISense_Sight::EVisibilityResult::Visible; } if ((!PelvisHit || PelvisHitResult.GetActor() == this) && (ClothingManager->IsBodyPartExposed(EBodyPart::Ass) || ClothingManager->IsBodyPartExposed(EBodyPart::Genitals))) { UE_LOG(LogTemp, Warning, TEXT("Pelvis hit")); OutSeenLocation = PelvisBoneLocation; OutSightStrength = 1.0f; return UAISense_Sight::EVisibilityResult::Visible; } UE_LOG(LogTemp, Warning, TEXT("Not visoble")); return UAISense_Sight::EVisibilityResult::NotVisible; } 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::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (OtherActor->Implements()) { InteractionManager->OnTargetEnter(OtherActor); } if (const ALocationTrigger* AreaTrigger = Cast(OtherActor)) { CurrentArea = AreaTrigger->GetLocationData(); OnAreaEnter.Broadcast(CurrentArea); } } void ANakedDesireCharacter::OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (OtherActor->Implements()) { InteractionManager->OnTargetExit(OtherActor); } if (const ALocationTrigger* AreaTrigger = Cast(OtherActor)) { CurrentArea = nullptr; OnAreaExit.Broadcast(AreaTrigger->GetLocationData()); } } void ANakedDesireCharacter::OnClothingEquip(const UClothingItemInstance* ClothingItemInstance) { if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Ass)) { AnalCensorship->SetVisibility(false); } if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Genitals)) { VaginaCensorship->SetVisibility(false); } if (ClothingItemInstance->GetClothingItem()->HiddenBodyParts.Contains(EBodyPart::Boobs)) { BoobLCensorship->SetVisibility(false); BoobRCensorship->SetVisibility(false); } } void ANakedDesireCharacter::OnClothingUnequip(const UClothingItemInstance* ClothingItemInstance) { if (!UNakedDesireUserSettings::GetNakedDesireUserSettings()->GetIsCensorshipEnabled() && !IS_DEMO) return; if (ClothingManager->IsBodyPartExposed(EBodyPart::Ass)) { AnalCensorship->SetVisibility(true); } if (ClothingManager->IsBodyPartExposed(EBodyPart::Genitals)) { VaginaCensorship->SetVisibility(true); } if (ClothingManager->IsBodyPartExposed(EBodyPart::Boobs)) { BoobLCensorship->SetVisibility(true); BoobRCensorship->SetVisibility(true); } } void ANakedDesireCharacter::OnSettingsChanged(UNakedDesireUserSettings* Settings) { if (Settings->GetIsCensorshipEnabled()) { if (ClothingManager->IsBodyPartExposed(EBodyPart::Ass)) { AnalCensorship->SetVisibility(true); } if (ClothingManager->IsBodyPartExposed(EBodyPart::Genitals)) { VaginaCensorship->SetVisibility(true); } if (ClothingManager->IsBodyPartExposed(EBodyPart::Boobs)) { BoobLCensorship->SetVisibility(true); BoobRCensorship->SetVisibility(true); } } else if (!IS_DEMO) { AnalCensorship->SetVisibility(false); VaginaCensorship->SetVisibility(false); BoobLCensorship->SetVisibility(false); BoobRCensorship->SetVisibility(false); } } void ANakedDesireCharacter::OnLook(const FInputActionValue& Value) { const FVector2D MouseValue = Value.Get(); AddControllerYawInput(MouseValue.X); AddControllerPitchInput(MouseValue.Y); } void ANakedDesireCharacter::OnMove(const FInputActionValue& Value) { const FVector2D MoveInput = Value.Get(); 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) { BuildRadialMenuEntries(); RadialMenuController->OpenMenu(); } void ANakedDesireCharacter::BuildRadialMenuEntries() { if (!SlotsData) { UE_LOG(LogTemp, Warning, TEXT("ANakedDesireCharacter::BuildRadialMenuEntries SlotsData not defined")); return; } TArray Entries; for (const auto& [Key, Value] : SlotsData->Slots) { FRadialMenuEntry Entry; const UClothingItemInstance* EquippedItem = ClothingManager->GetSlotClothing(Key); Entry.bEnabled = true; Entry.DisplayName = Value.Name; Entry.Icon = EquippedItem ? EquippedItem->GetClothingItem()->Icon : Value.Icon; Entry.ItemId = FName(Value.Name.ToString()); Entries.Push(Entry); } RadialMenuController->Entries = Entries; } 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; }