411 lines
15 KiB
C++
411 lines
15 KiB
C++
// © 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/NakedDesireHUD.h"
|
|
#include "NakedDesire/Global/NakedDesireUserSettings.h"
|
|
#include "NakedDesire/UI/GameLayoutWidget.h"
|
|
#include "NakedDesire/UI/HUDWidget.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<UClothingManager>("Clothing Manager");
|
|
StatsManager = CreateDefaultSubobject<UStatsManager>("Stats Manager");
|
|
MissionsManager = CreateDefaultSubobject<UMissionsManager>("Missions Manager");
|
|
InteractionManager = CreateDefaultSubobject<UInteractionManager>("Interaction Manager");
|
|
RadialMenuController = CreateDefaultSubobject<URadialMenuController>(TEXT("Radial Menu Controller"));
|
|
|
|
NipplesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Nipples");
|
|
NipplesMeshComponent->SetupAttachment(GetMesh());
|
|
AnalMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Anal");
|
|
AnalMeshComponent->SetupAttachment(GetMesh());
|
|
VaginaMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Vagina");
|
|
VaginaMeshComponent->SetupAttachment(GetMesh());
|
|
HeadMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Head");
|
|
HeadMeshComponent->SetupAttachment(GetMesh());
|
|
NeckMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Neck");
|
|
NeckMeshComponent->SetupAttachment(GetMesh());
|
|
FaceMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Face");
|
|
FaceMeshComponent->SetupAttachment(GetMesh());
|
|
EyesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Eyes");
|
|
EyesMeshComponent->SetupAttachment(GetMesh());
|
|
BodySuitMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Body Suit");
|
|
BodySuitMeshComponent->SetupAttachment(GetMesh());
|
|
TopMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Top");
|
|
TopMeshComponent->SetupAttachment(GetMesh());
|
|
BottomMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Bottom");
|
|
BottomMeshComponent->SetupAttachment(GetMesh());
|
|
UnderwearTopMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Underwear Top");
|
|
UnderwearTopMeshComponent->SetupAttachment(GetMesh());
|
|
UnderwearBottomMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Underwear Bottom");
|
|
UnderwearBottomMeshComponent->SetupAttachment(GetMesh());
|
|
SocksMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Socks");
|
|
SocksMeshComponent->SetupAttachment(GetMesh());
|
|
FootwearMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Footwear");
|
|
FootwearMeshComponent->SetupAttachment(GetMesh());
|
|
OuterwearMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Outerwear");
|
|
OuterwearMeshComponent->SetupAttachment(GetMesh());
|
|
WristRestraintMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Wrist Restraint");
|
|
WristRestraintMeshComponent->SetupAttachment(GetMesh());
|
|
AnkleRestraintMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Ankle Restraint");
|
|
AnkleRestraintMeshComponent->SetupAttachment(GetMesh());
|
|
NeckRestraintMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Neck Restraint");
|
|
NeckRestraintMeshComponent->SetupAttachment(GetMesh());
|
|
|
|
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")));
|
|
|
|
StimuliSourceComponent = CreateDefaultSubobject<UAIPerceptionStimuliSourceComponent>(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<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);
|
|
}
|
|
}
|
|
|
|
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(TSubclassOf<UAISense_Sight>());
|
|
StimuliSourceComponent->RegisterWithPerceptionSystem();
|
|
|
|
ClothingManager->OnClothingEquip.AddUniqueDynamic(this, &ANakedDesireCharacter::OnClothingEquip);
|
|
ClothingManager->OnClothingUnequip.AddUniqueDynamic(this, &ANakedDesireCharacter::OnClothingUnequip);
|
|
|
|
HUD = Cast<ANakedDesireHUD>(UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetHUD());
|
|
|
|
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<UInteractionTarget>())
|
|
{
|
|
InteractionManager->OnTargetEnter(OtherActor);
|
|
}
|
|
|
|
if (const ALocationTrigger* AreaTrigger = Cast<ALocationTrigger>(OtherActor))
|
|
{
|
|
CurrentArea = AreaTrigger->GetLocationData();
|
|
OnAreaEnter.Broadcast(CurrentArea);
|
|
}
|
|
}
|
|
|
|
void ANakedDesireCharacter::OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
|
|
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
|
|
{
|
|
if (OtherActor->Implements<UInteractionTarget>())
|
|
{
|
|
InteractionManager->OnTargetExit(OtherActor);
|
|
}
|
|
|
|
if (const ALocationTrigger* AreaTrigger = Cast<ALocationTrigger>(OtherActor))
|
|
{
|
|
CurrentArea = nullptr;
|
|
OnAreaExit.Broadcast(AreaTrigger->GetLocationData());
|
|
}
|
|
}
|
|
|
|
void ANakedDesireCharacter::OnClothingEquip(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(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<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::BuildRadialMenuEntries()
|
|
{
|
|
if (!SlotsData)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("ANakedDesireCharacter::BuildRadialMenuEntries SlotsData not defined"));
|
|
return;
|
|
}
|
|
|
|
TArray<FRadialMenuEntry> 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;
|
|
}
|