2221 lines
96 KiB
C++
2221 lines
96 KiB
C++
|
|
// Lot CAM-B2c - voir LPCameraModeSubsystem.h
|
||
|
|
|
||
|
|
#include "LPCameraModeSubsystem.h"
|
||
|
|
|
||
|
|
#include "Blueprint/UserWidget.h"
|
||
|
|
#include "Blueprint/WidgetTree.h"
|
||
|
|
#include "Components/Button.h"
|
||
|
|
#include "Components/CanvasPanelSlot.h"
|
||
|
|
#include "Components/HorizontalBoxSlot.h"
|
||
|
|
#include "Components/Image.h"
|
||
|
|
#include "Components/PanelWidget.h"
|
||
|
|
#include "Components/TextBlock.h"
|
||
|
|
#include "Engine/Texture2D.h"
|
||
|
|
#include "Widgets/Layout/SBorder.h"
|
||
|
|
#include "Engine/GameInstance.h"
|
||
|
|
#include "Engine/GameViewportClient.h"
|
||
|
|
#include "Engine/World.h"
|
||
|
|
#include "Components/SkeletalMeshComponent.h"
|
||
|
|
#include "Components/StaticMeshComponent.h"
|
||
|
|
#include "Engine/OverlapResult.h"
|
||
|
|
#include "Engine/StaticMesh.h"
|
||
|
|
#include "Framework/Application/SlateApplication.h"
|
||
|
|
#include "GameFramework/Actor.h"
|
||
|
|
#include "GameFramework/Character.h"
|
||
|
|
#include "GameFramework/InputSettings.h"
|
||
|
|
#include "GameFramework/PlayerController.h"
|
||
|
|
#include "GameFramework/PlayerInput.h"
|
||
|
|
#include "LPKeyboardSubsystem.h"
|
||
|
|
#include "LPShowCoupleSubsystem.h"
|
||
|
|
#include "LifePlay.h"
|
||
|
|
#include "Styling/CoreStyle.h"
|
||
|
|
#include "TimerManager.h"
|
||
|
|
#include "UObject/UObjectIterator.h"
|
||
|
|
#include "UObject/UnrealType.h"
|
||
|
|
#include "Widgets/Input/SEditableText.h"
|
||
|
|
#include "Widgets/SOverlay.h"
|
||
|
|
#include "Widgets/Text/STextBlock.h"
|
||
|
|
|
||
|
|
// Espace de noms nomme, pas anonyme : en compilation unitaire, ce fichier peut partager une unite avec
|
||
|
|
// LPDirectControlSubsystem.cpp et LPOutdoorRoomSubsystem.cpp, dont l'espace anonyme a des noms voisins
|
||
|
|
// (MenuClasses, MenuOnScreen, GetObjectProperty) : risque C2374 (lp-build). Appels toujours qualifies.
|
||
|
|
namespace LPCameraModeLocal
|
||
|
|
{
|
||
|
|
const float TickInterval = 0.1f;
|
||
|
|
const int32 ViewportZOrder = 50;
|
||
|
|
|
||
|
|
// Variables de GI et de Room (noms exacts du paragraphe 2 de la SPEC).
|
||
|
|
const FName GIBirdview(TEXT("Birdview"));
|
||
|
|
const FName GIChargenCameras(TEXT("ChargenCameras"));
|
||
|
|
const FName GIPlayerCharacter(TEXT("PlayerCharacter"));
|
||
|
|
const FName GIPC(TEXT("PC"));
|
||
|
|
const FName GIRoom(TEXT("Room"));
|
||
|
|
const FName GISexOn(TEXT("SexOn"));
|
||
|
|
// POVOn est une variable du PC (ecrite par SwitchCamera), pas de GI (CAM-COMMUN2-DIAG, KO 1).
|
||
|
|
const FName PCPOVOn(TEXT("POVOn"));
|
||
|
|
const FName GIBuilderOn(TEXT("BuilderOn"));
|
||
|
|
// Temoin touche C (CAM-B2j) : gardes du PC (BuilderOn, SexOn, SpaceOn) et etat C1 de Room.
|
||
|
|
const FName GISpaceOn(TEXT("SpaceOn"));
|
||
|
|
const FName RoomC1Hidden(TEXT("C1Hidden"));
|
||
|
|
const FName SwitchCameraAction(TEXT("SwitchCamera"));
|
||
|
|
const FName RoomCamC1On(TEXT("CamC1On"));
|
||
|
|
const FName RoomCamC3On(TEXT("CamC3On"));
|
||
|
|
const FName RoomCamRoom(TEXT("CamRoom"));
|
||
|
|
const FName RoomSceneCamera(TEXT("SceneCamera"));
|
||
|
|
const FName RoomC2BoxMin(TEXT("C2BoxMin"));
|
||
|
|
const FName RoomC2BoxMax(TEXT("C2BoxMax"));
|
||
|
|
const FName RoomC2Target(TEXT("C2Target"));
|
||
|
|
// Panneau de fond cache (StaticMeshActor, BlockAll) qui traverse les pieces : ignore par les sondes depuis B2g.
|
||
|
|
const FName GIWall(TEXT("Wall"));
|
||
|
|
|
||
|
|
// Temoin C2 (CAM-B2f) : memes depart (douille head), rayon et acteurs ignores que la sonde de Room.
|
||
|
|
const TCHAR* const LabelC2 = TEXT("Camera: C2 Follow");
|
||
|
|
const FName HeadSocket(TEXT("head"));
|
||
|
|
const float WitnessNearDistance = 100.f;
|
||
|
|
const float WitnessProbeLength = 250.f;
|
||
|
|
const float WitnessProbeRadius = 20.f;
|
||
|
|
const float WitnessInterval = 1.f;
|
||
|
|
// Temoin d'occlusion (CAM-Tocc) : trace toutes les 0,5 s, au plus une ligne par seconde.
|
||
|
|
const float OcclusionInterval = 0.5f;
|
||
|
|
const float OcclusionLogInterval = 1.f;
|
||
|
|
|
||
|
|
// Sortie de CharGen (CAM-B2r) : ShowRender (GI) remet SpaceOn a faux et CloseCharGen ne le remet pas en cours
|
||
|
|
// de partie (CAM-COMMUN6-DIAG § 10) ; MainMenuOn peut rester vrai si CharGen a ete ouvert du menu principal.
|
||
|
|
const FName GICharGenOn(TEXT("CharGenOn"));
|
||
|
|
const FName GIMainMenuOn(TEXT("MainMenuOn"));
|
||
|
|
const FName GIJournalOn(TEXT("JournalOn"));
|
||
|
|
const FName GIContactsOn(TEXT("ContactsOn"));
|
||
|
|
const FName PCMoveCamera(TEXT("MoveCamera"));
|
||
|
|
const TCHAR* const MainMenuClass = TEXT("MainMenu_C");
|
||
|
|
const float CharGenRepairDelay = 10.f;
|
||
|
|
const TCHAR* const RoomCameraLabels[] = {
|
||
|
|
TEXT("Camera: C1 First person"), TEXT("Camera: C2 Follow"), TEXT("Camera: C3 Ceiling"), TEXT("Camera: C3 Room (old)")
|
||
|
|
};
|
||
|
|
|
||
|
|
// Temoin C1 (CAM-B2r) : ecart de FirstPersonSM aux yeux et a l'horizontale, toutes les 0,5 s, au plus une ligne par seconde.
|
||
|
|
const TCHAR* const LabelC1 = TEXT("Camera: C1 First person");
|
||
|
|
const FName PlayerFirstPersonSM(TEXT("FirstPersonSM"));
|
||
|
|
const FName PlayerOccupyingFnt(TEXT("OccupyingFnt"));
|
||
|
|
const FName RoomCamTimer(TEXT("RoomCamTimer"));
|
||
|
|
const FName LeftEyeSocket(TEXT("lEye"));
|
||
|
|
const FName RightEyeSocket(TEXT("rEye"));
|
||
|
|
const float C1Interval = 0.5f;
|
||
|
|
const float C1LogInterval = 1.f;
|
||
|
|
const float C1MaxEyeDistance = 8.f;
|
||
|
|
const float C1MaxPitch = 25.f;
|
||
|
|
const float C1MaxYawGap = 30.f;
|
||
|
|
|
||
|
|
// Camera de scene au clavier (CAM-B2p, P3) : pendant le sexe, ZQSD pilote la camera en vue (la marche de STG
|
||
|
|
// est coupee pendant le sexe). Mode Scene : orbite (Q, D) et avance ou recul (Z, S) autour du couple ;
|
||
|
|
// POV : lacet (Q, D) et tangage (Z, S) de FirstPersonSM de la personne en vue. Aucun mappage n'est change.
|
||
|
|
const FName AxisMoveForward(TEXT("MoveForward"));
|
||
|
|
const FName AxisMoveRight(TEXT("MoveRight"));
|
||
|
|
const FName AxisLeftRight(TEXT("LeftRight"));
|
||
|
|
const FName AxisDownUp(TEXT("DownUp"));
|
||
|
|
const FName AxisScroll(TEXT("ScrollDownUp"));
|
||
|
|
const FName GISexPersons(TEXT("SexPersons"));
|
||
|
|
const FName GISexCameraIndex(TEXT("SexCameraIndex"));
|
||
|
|
const FName RoomMoveObject(TEXT("MoveObject"));
|
||
|
|
const float SexOrbitSpeed = 90.f; // degres par seconde
|
||
|
|
const float SexDollySpeed = 200.f; // cm par seconde
|
||
|
|
const float SexMinDistance = 80.f;
|
||
|
|
const float SexMaxDistance = 800.f;
|
||
|
|
const float SexLookSpeed = 90.f; // degres par seconde
|
||
|
|
const float SexMaxPitch = 80.f;
|
||
|
|
// P3 bis : zoom a la molette vers le couple. Desactive : la molette garde le zoom d'origine de Room (noeud 125).
|
||
|
|
const bool bSexWheelZoom = false;
|
||
|
|
const float SexWheelStep = 40.f; // cm par cran
|
||
|
|
const float SexKeysLogInterval = 1.f;
|
||
|
|
const float SexMaxStep = 0.05f; // pas de temps maximal pris en compte (s)
|
||
|
|
|
||
|
|
// Couple a la souris (CAM-B2p-D) : en mode Scene, clic gauche maintenu sur le couple = glisser-deposer sur la
|
||
|
|
// surface visee (lit ou sol), clic droit maintenu = rotation ; la camera en vue est figee pendant la prise.
|
||
|
|
const FName PelvisSocket(TEXT("pelvis"));
|
||
|
|
const FName RoomFloor(TEXT("Box"));
|
||
|
|
const float CoupleSelectRadius = 120.f; // px, autour du segment bassin-tete projete
|
||
|
|
const float CoupleDragThreshold = 5.f; // px avant le premier deplacement
|
||
|
|
const float CoupleTurnPerPixel = 0.3f; // degres par pixel
|
||
|
|
const float CoupleFloorMargin = 30.f; // cm, marge au bord du sol de Room
|
||
|
|
const float CursorTraceLength = 5000.f;
|
||
|
|
const float CoupleTraceUp = 150.f; // depart du trace vertical au-dessus du pivot
|
||
|
|
const float CoupleTraceDown = 400.f; // fin du trace vertical sous le pivot
|
||
|
|
// G-e (CAM-B2p-H) : la racine du maillage la plus basse est posee a cette hauteur au-dessus de la surface visee.
|
||
|
|
const float CoupleRootAboveSurface = 3.f;
|
||
|
|
|
||
|
|
// Borne dure du couple (CAM-B2p-E, CAM-COMMUN9-DIAG) : pendant le sexe, a chaque image, quelle que soit la source
|
||
|
|
// du deplacement (fleches, molette, glisser, jeu). Pivot dans le sol de Room.Box moins la marge ; racine du
|
||
|
|
// maillage de chaque personne jamais sous le dessus du sol (tolerance) ni au-dessus d'un plafond estime.
|
||
|
|
const float CoupleFloorTolerance = 5.f; // cm sous le dessus du sol toleres pour la racine du maillage
|
||
|
|
const float CoupleCeilingHeight = 350.f; // cm au-dessus du sol (plafond estime : Room n'expose pas de plafond)
|
||
|
|
const float CoupleHeadroom = 120.f; // cm entre la racine du maillage la plus haute et le plafond (corps assis ou allonge)
|
||
|
|
const float CoupleMoveWitness = 5.f; // cm par image hors prise au-dela desquels le temoin ecrit
|
||
|
|
const float CoupleBoundsLogInterval = 1.f;
|
||
|
|
|
||
|
|
// Interieur reel de la piece (CAM-B2p-F, CAM-COMMUN9-DIAG § 6) : Room.Box deborde des murs. Murs = composants de
|
||
|
|
// Room nommes Wall* (« bloque par Room_C_0/Wall2 », LPShowCouple) ; repli : structure AllComponents[0..5] (resizeRoom
|
||
|
|
// ne compense l'echelle qu'a partir de l'indice 6), puis demi-cote d'echelle de la racine.
|
||
|
|
const TCHAR* const WallPrefix = TEXT("Wall");
|
||
|
|
const FName RoomAllComponents(TEXT("AllComponents"));
|
||
|
|
const int32 RoomStructureCount = 6;
|
||
|
|
const float WallMinHeight = 100.f; // un mur fait plus de 100 cm de haut
|
||
|
|
const float WallThinRatio = 0.25f; // petit cote horizontal sous le quart du grand
|
||
|
|
const float CeilingMaxThickness = 50.f;
|
||
|
|
const float CeilingMinHeight = 150.f; // au-dessus du sol
|
||
|
|
const float RootHalfSizePerScale = 50.f; // demi-cote du maillage racine par unite d'echelle (estime : carre de 100 cm)
|
||
|
|
// Marge aux murs = demi-boite du couple (Show couple : etendue X=78 Y=46) : la boite ne touche jamais un mur.
|
||
|
|
const float CoupleWallMargin = 80.f;
|
||
|
|
// Pose libre (G1) : boite du couple testee a la place visee, canal Pawn, composants de Room seulement, sol et lits admis.
|
||
|
|
const FVector CoupleProbeExtent(78.0, 46.0, 50.0);
|
||
|
|
const float CoupleProbeAboveRoot = 58.f; // centre de la boite au-dessus de la racine du maillage (bas a +8 cm)
|
||
|
|
|
||
|
|
// Recadrage et suivi du couple (CAM-B2p-G) : la camera de scene vise le milieu du corps, a distance par defaut ;
|
||
|
|
// elle suit les deplacements du couple hors glisser ; C3 (variable SexC3 du PC) recadre.
|
||
|
|
const FName PCSexC3(TEXT("SexC3"));
|
||
|
|
const float RecenterDistance = 300.f;
|
||
|
|
const float RecenterHeight = 120.f;
|
||
|
|
const float RecenterTargetAboveRoot = 60.f;
|
||
|
|
const float RecenterWallMargin = 20.f;
|
||
|
|
const float FollowMaxDistance = 900.f;
|
||
|
|
// Le pivot est la place des acteurs (l'animation ne la bouge pas) : tout pas compte, meme lent (VAL B2p-G N1).
|
||
|
|
const float FollowMinStep = 0.01f;
|
||
|
|
const float RecenterMinDistance = 50.f; // cm : distance minimale a la cible apres un obstacle (VAL B2p-G N2)
|
||
|
|
|
||
|
|
// G-g (CAM-B2p-H) : clic molette court pendant le sexe = bascule du panneau des positions, par l'evenement
|
||
|
|
// LeftMouseSexOn du PC (le meme que la voie d'Espace). Une glissee a la molette reste l'orbite.
|
||
|
|
const FName PCLeftMouseSexOn(TEXT("LeftMouseSexOn"));
|
||
|
|
const float PanelClickMaxHold = 0.2f; // s, comme HoldTime du PC
|
||
|
|
const float PanelClickMaxMove = 5.f; // px
|
||
|
|
|
||
|
|
// Aide des controles du sexe (CAM-B2p-I) : icone « i » (texture de FAQ du menu principal) dans le panneau des actions
|
||
|
|
// (Cursor), a cote du filtre SearchAction ; les 4 lignes d'origine (SexInterface.TextBlock_1) sont rendues invisibles.
|
||
|
|
const FName GISexInterface(TEXT("SexInterface"));
|
||
|
|
const FName SexHelpLinesName(TEXT("TextBlock_1"));
|
||
|
|
const TCHAR* const SexHelpLinesStart = TEXT("Click any mouse button");
|
||
|
|
const FName PCCursorREF(TEXT("CursorREF"));
|
||
|
|
const FName CursorSearchAction(TEXT("SearchAction"));
|
||
|
|
const FName CursorBelowCursor(TEXT("BelowCursor"));
|
||
|
|
const FName HelpButtonName(TEXT("LPControlsHelp"));
|
||
|
|
const TCHAR* const HelpIconPath = TEXT("/Game/Heavy/New/info.info");
|
||
|
|
const float HelpIconSize = 28.f;
|
||
|
|
const int32 HelpZOrder = 60;
|
||
|
|
|
||
|
|
// Copie de LPDirectControlSubsystem.cpp (MenuClasses) : menus pendant lesquels le texte est masque.
|
||
|
|
const TCHAR* const MenuClasses[] = {
|
||
|
|
TEXT("Journal_C"), TEXT("MainMenu_C"), TEXT("CityMenu_C"), TEXT("StatMenu_C"),
|
||
|
|
TEXT("ContactMenu_C"), TEXT("Inventory_C"), TEXT("CharGen_C"), TEXT("ModManager_C"),
|
||
|
|
TEXT("BodyShapeMenu_C")
|
||
|
|
};
|
||
|
|
|
||
|
|
UObject* ReadObject(const UObject* Container, FName Name)
|
||
|
|
{
|
||
|
|
const FObjectPropertyBase* Prop = Container ? CastField<FObjectPropertyBase>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
return Prop ? Prop->GetObjectPropertyValue_InContainer(Container) : nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ReadBool(const UObject* Container, FName Name)
|
||
|
|
{
|
||
|
|
const FBoolProperty* Prop = Container ? CastField<FBoolProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
return Prop && Prop->GetPropertyValue_InContainer(Container);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ecrit un booleen par reflexion ; rien si la propriete est absente.
|
||
|
|
void WriteBool(UObject* Container, FName Name, bool bValue)
|
||
|
|
{
|
||
|
|
const FBoolProperty* Prop = Container ? CastField<FBoolProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
if (Prop)
|
||
|
|
{
|
||
|
|
Prop->SetPropertyValue_InContainer(Container, bValue);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Poignee de minuterie (FTimerHandle) active ; faux si la propriete est absente ou d'un autre type.
|
||
|
|
bool TimerActive(const UObject* Container, FName Name, const UWorld* World)
|
||
|
|
{
|
||
|
|
const FStructProperty* Prop = Container ? CastField<FStructProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
if (!Prop || !World || Prop->Struct != FTimerHandle::StaticStruct())
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return World->GetTimerManager().IsTimerActive(*Prop->ContainerPtrToValuePtr<FTimerHandle>(Container));
|
||
|
|
}
|
||
|
|
|
||
|
|
int32 ReadInt(const UObject* Container, FName Name)
|
||
|
|
{
|
||
|
|
const FIntProperty* Prop = Container ? CastField<FIntProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
return Prop ? Prop->GetPropertyValue_InContainer(Container) : -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Acteurs valides du tableau d'objets Name de Container.
|
||
|
|
TArray<AActor*> ReadActors(const UObject* Container, FName Name)
|
||
|
|
{
|
||
|
|
TArray<AActor*> Out;
|
||
|
|
const FArrayProperty* Prop = Container ? CastField<FArrayProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
const FObjectPropertyBase* Inner = Prop ? CastField<FObjectPropertyBase>(Prop->Inner) : nullptr;
|
||
|
|
if (!Inner)
|
||
|
|
{
|
||
|
|
return Out;
|
||
|
|
}
|
||
|
|
FScriptArrayHelper Array(Prop, Prop->ContainerPtrToValuePtr<void>(Container));
|
||
|
|
for (int32 Index = 0; Index < Array.Num(); ++Index)
|
||
|
|
{
|
||
|
|
AActor* Actor = Cast<AActor>(Inner->GetObjectPropertyValue(Array.GetRawPtr(Index)));
|
||
|
|
if (IsValid(Actor))
|
||
|
|
{
|
||
|
|
Out.Add(Actor);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Out;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copie de LPDirectControlSubsystem.cpp (AxisValue) : etat brut des touches de l'axe, mappages et remappages suivis.
|
||
|
|
float AxisValue(const APlayerController* PC, FName Axis)
|
||
|
|
{
|
||
|
|
const UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||
|
|
if (!PC || !PC->PlayerInput || !Settings)
|
||
|
|
{
|
||
|
|
return 0.f;
|
||
|
|
}
|
||
|
|
float Value = 0.f;
|
||
|
|
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
||
|
|
{
|
||
|
|
if (Map.AxisName == Axis)
|
||
|
|
{
|
||
|
|
const float Down = Map.Key.IsDigital()
|
||
|
|
? (PC->PlayerInput->IsPressed(Map.Key) ? 1.f : 0.f)
|
||
|
|
: PC->PlayerInput->GetKeyValue(Map.Key);
|
||
|
|
Value += Down * Map.Scale;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return FMath::Clamp(Value, -1.f, 1.f);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Noms des touches du clavier de l'action ou de l'axe Name, mappages en cours (comme AxisValue, remappages suivis) ;
|
||
|
|
// Sign : 1 ou -1 = echelle de l'axe de ce signe, 0 = toutes. Souris, manette et axes analogiques exclus.
|
||
|
|
FString MappedKeys(FName Name, int32 Sign)
|
||
|
|
{
|
||
|
|
TArray<FString> Names;
|
||
|
|
if (const UInputSettings* Settings = UInputSettings::GetInputSettings())
|
||
|
|
{
|
||
|
|
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
||
|
|
{
|
||
|
|
const bool bSign = Sign == 0 || (Sign > 0 ? Map.Scale > 0.f : Map.Scale < 0.f);
|
||
|
|
if (Map.AxisName == Name && bSign && !Map.Key.IsGamepadKey() && !Map.Key.IsMouseButton() && !Map.Key.IsAxis1D())
|
||
|
|
{
|
||
|
|
Names.AddUnique(Map.Key.GetDisplayName(false).ToString());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const FInputActionKeyMapping& Map : Settings->GetActionMappings())
|
||
|
|
{
|
||
|
|
if (Map.ActionName == Name && !Map.Key.IsGamepadKey() && !Map.Key.IsMouseButton() && !Map.Key.IsAxis1D())
|
||
|
|
{
|
||
|
|
Names.AddUnique(Map.Key.GetDisplayName(false).ToString());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Names.Num() > 0 ? FString::Join(Names, TEXT("/")) : FString(TEXT("?"));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Empreinte des mappages en cours (actions et axes) : nombres et CRC, pour verifier qu'une scene ne les change pas.
|
||
|
|
FString MappingSignature()
|
||
|
|
{
|
||
|
|
const UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||
|
|
if (!Settings)
|
||
|
|
{
|
||
|
|
return FString(TEXT("sans reglages d'entree"));
|
||
|
|
}
|
||
|
|
FString Joined;
|
||
|
|
for (const FInputActionKeyMapping& Map : Settings->GetActionMappings())
|
||
|
|
{
|
||
|
|
Joined += FString::Printf(TEXT("%s=%s,%d%d%d;"), *Map.ActionName.ToString(), *Map.Key.ToString(),
|
||
|
|
Map.bShift ? 1 : 0, Map.bCtrl ? 1 : 0, Map.bAlt ? 1 : 0);
|
||
|
|
}
|
||
|
|
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
||
|
|
{
|
||
|
|
Joined += FString::Printf(TEXT("%s=%s,%.2f;"), *Map.AxisName.ToString(), *Map.Key.ToString(), Map.Scale);
|
||
|
|
}
|
||
|
|
return FString::Printf(TEXT("%d actions, %d axes, crc %08X"), Settings->GetActionMappings().Num(), Settings->GetAxisMappings().Num(), FCrc::StrCrc32(*Joined));
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ReadVector(const UObject* Container, FName Name, FVector& Out)
|
||
|
|
{
|
||
|
|
const FStructProperty* Prop = Container ? CastField<FStructProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
if (!Prop || Prop->Struct != TBaseStructure<FVector>::Get())
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
Out = *Prop->ContainerPtrToValuePtr<FVector>(Container);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Vrai si le tableau d'objets Name de Container contient Value.
|
||
|
|
bool ArrayContains(const UObject* Container, FName Name, const UObject* Value)
|
||
|
|
{
|
||
|
|
const FArrayProperty* Prop = (Container && Value) ? CastField<FArrayProperty>(Container->GetClass()->FindPropertyByName(Name)) : nullptr;
|
||
|
|
const FObjectPropertyBase* Inner = Prop ? CastField<FObjectPropertyBase>(Prop->Inner) : nullptr;
|
||
|
|
if (!Inner)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
FScriptArrayHelper Array(Prop, Prop->ContainerPtrToValuePtr<void>(Container));
|
||
|
|
for (int32 Index = 0; Index < Array.Num(); ++Index)
|
||
|
|
{
|
||
|
|
if (Inner->GetObjectPropertyValue(Array.GetRawPtr(Index)) == Value)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Copie de LPDirectControlSubsystem.cpp (MenuOnScreen), l'original n'est pas modifie.
|
||
|
|
bool MenuOnScreen(const UGameInstance* GI)
|
||
|
|
{
|
||
|
|
if (!GI)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (const ULPKeyboardSubsystem* Keys = GI->GetSubsystem<ULPKeyboardSubsystem>())
|
||
|
|
{
|
||
|
|
if (Keys->IsMenuOpen())
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (TObjectIterator<UUserWidget> It; It; ++It)
|
||
|
|
{
|
||
|
|
UUserWidget* Widget = *It;
|
||
|
|
if (!Widget || Widget->HasAnyFlags(RF_ClassDefaultObject) || Widget->GetGameInstance() != GI || !Widget->IsInViewport())
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const ESlateVisibility Visibility = Widget->GetVisibility();
|
||
|
|
if (Visibility == ESlateVisibility::Collapsed || Visibility == ESlateVisibility::Hidden)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const FString ClassName = Widget->GetClass()->GetName();
|
||
|
|
for (const TCHAR* Menu : MenuClasses)
|
||
|
|
{
|
||
|
|
if (ClassName == Menu)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!FSlateApplication::IsInitialized())
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
TSharedPtr<SWidget> Focused = FSlateApplication::Get().GetKeyboardFocusedWidget();
|
||
|
|
return Focused.IsValid() && Focused->GetTypeAsString().Contains(TEXT("Editable"));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Interieur de la piece : XY entre les murs (bornes interieures), Z du dessus du sol au plafond. Source ecrite dans
|
||
|
|
// OutSource. Faux sans sol (Room.Box).
|
||
|
|
struct FRoomInterior
|
||
|
|
{
|
||
|
|
FBox2D Area = FBox2D(ForceInit);
|
||
|
|
double FloorTop = 0.0;
|
||
|
|
double Ceiling = 0.0;
|
||
|
|
int32 Walls = 0;
|
||
|
|
bool bCeilingFound = false;
|
||
|
|
FString Source;
|
||
|
|
FBox FloorBox = FBox(ForceInit);
|
||
|
|
FVector Scale = FVector::OneVector;
|
||
|
|
};
|
||
|
|
|
||
|
|
bool ComputeRoomInterior(const AActor* Room, FRoomInterior& Out)
|
||
|
|
{
|
||
|
|
const UPrimitiveComponent* Floor = Room ? Cast<UPrimitiveComponent>(ReadObject(Room, RoomFloor)) : nullptr;
|
||
|
|
if (!IsValid(Floor))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
Out.FloorBox = Floor->Bounds.GetBox();
|
||
|
|
Out.FloorTop = Out.FloorBox.Max.Z;
|
||
|
|
Out.Ceiling = Out.FloorTop + CoupleCeilingHeight;
|
||
|
|
Out.Area = FBox2D(FVector2D(Out.FloorBox.Min), FVector2D(Out.FloorBox.Max));
|
||
|
|
const FVector2D Center = Out.Area.GetCenter();
|
||
|
|
const USceneComponent* RootComponent = Room->GetRootComponent();
|
||
|
|
Out.Scale = RootComponent ? RootComponent->GetComponentScale() : FVector::OneVector;
|
||
|
|
|
||
|
|
// Un mur resserre la borne de son cote ; un plafond donne la borne haute.
|
||
|
|
FBox2D Walled = Out.Area;
|
||
|
|
auto UseStructure = [&](const UPrimitiveComponent* Comp) -> bool
|
||
|
|
{
|
||
|
|
if (!IsValid(Comp) || Comp == Floor)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
const FBox Bounds = Comp->Bounds.GetBox();
|
||
|
|
const FVector Size = Bounds.GetSize();
|
||
|
|
if (Size.Z < CeilingMaxThickness && Bounds.GetCenter().Z > Out.FloorTop + CeilingMinHeight)
|
||
|
|
{
|
||
|
|
Out.Ceiling = Out.bCeilingFound ? FMath::Min(Out.Ceiling, Bounds.Min.Z) : Bounds.Min.Z;
|
||
|
|
Out.bCeilingFound = true;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (Size.Z < WallMinHeight || FMath::Min(Size.X, Size.Y) > WallThinRatio * FMath::Max(Size.X, Size.Y))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (Size.X < Size.Y)
|
||
|
|
{
|
||
|
|
if (Bounds.GetCenter().X < Center.X)
|
||
|
|
{
|
||
|
|
Walled.Min.X = FMath::Max(Walled.Min.X, Bounds.Max.X);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Walled.Max.X = FMath::Min(Walled.Max.X, Bounds.Min.X);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (Bounds.GetCenter().Y < Center.Y)
|
||
|
|
{
|
||
|
|
Walled.Min.Y = FMath::Max(Walled.Min.Y, Bounds.Max.Y);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Walled.Max.Y = FMath::Min(Walled.Max.Y, Bounds.Min.Y);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
TInlineComponentArray<UPrimitiveComponent*> Components;
|
||
|
|
Room->GetComponents(Components);
|
||
|
|
for (const UPrimitiveComponent* Comp : Components)
|
||
|
|
{
|
||
|
|
if (Comp && Comp->GetName().StartsWith(WallPrefix) && UseStructure(Comp))
|
||
|
|
{
|
||
|
|
++Out.Walls;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Out.Source = TEXT("murs Wall*");
|
||
|
|
if (Out.Walls == 0)
|
||
|
|
{
|
||
|
|
// Repli 1 : structure de la piece (6 premiers composants de AllComponents).
|
||
|
|
const FArrayProperty* Prop = CastField<FArrayProperty>(Room->GetClass()->FindPropertyByName(RoomAllComponents));
|
||
|
|
const FObjectPropertyBase* Inner = Prop ? CastField<FObjectPropertyBase>(Prop->Inner) : nullptr;
|
||
|
|
if (Inner)
|
||
|
|
{
|
||
|
|
FScriptArrayHelper Array(Prop, Prop->ContainerPtrToValuePtr<void>(Room));
|
||
|
|
for (int32 Index = 0; Index < FMath::Min(Array.Num(), RoomStructureCount); ++Index)
|
||
|
|
{
|
||
|
|
if (UseStructure(Cast<UPrimitiveComponent>(Inner->GetObjectPropertyValue(Array.GetRawPtr(Index)))))
|
||
|
|
{
|
||
|
|
++Out.Walls;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Out.Source = TEXT("structure AllComponents");
|
||
|
|
}
|
||
|
|
if (Out.Walls == 0)
|
||
|
|
{
|
||
|
|
// Repli 2 : demi-cote d'echelle de la racine, centre sur Room, dans la boite du sol.
|
||
|
|
const FVector Location = Room->GetActorLocation();
|
||
|
|
const double HalfX = FMath::Abs(Out.Scale.X) * RootHalfSizePerScale;
|
||
|
|
const double HalfY = FMath::Abs(Out.Scale.Y) * RootHalfSizePerScale;
|
||
|
|
Walled.Min.X = FMath::Max(Walled.Min.X, Location.X - HalfX);
|
||
|
|
Walled.Max.X = FMath::Min(Walled.Max.X, Location.X + HalfX);
|
||
|
|
Walled.Min.Y = FMath::Max(Walled.Min.Y, Location.Y - HalfY);
|
||
|
|
Walled.Max.Y = FMath::Min(Walled.Max.Y, Location.Y + HalfY);
|
||
|
|
Out.Source = TEXT("echelle de la racine");
|
||
|
|
}
|
||
|
|
if (Walled.Max.X - Walled.Min.X > 1.0 && Walled.Max.Y - Walled.Min.Y > 1.0)
|
||
|
|
{
|
||
|
|
Out.Area = Walled;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Out.Source = TEXT("sol (murs incoherents)");
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Z de la racine du maillage la plus basse du couple (acteur a defaut de maillage).
|
||
|
|
double CoupleLowestRoot(const TArray<AActor*>& Couple)
|
||
|
|
{
|
||
|
|
double Lowest = TNumericLimits<double>::Max();
|
||
|
|
for (const AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
const ACharacter* Character = Cast<ACharacter>(Person);
|
||
|
|
const USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr;
|
||
|
|
Lowest = FMath::Min(Lowest, Mesh ? Mesh->GetComponentLocation().Z : Person->GetActorLocation().Z);
|
||
|
|
}
|
||
|
|
return Lowest;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Maillage statique du composant, ou nul.
|
||
|
|
const UStaticMesh* ComponentMesh(const UPrimitiveComponent* Comp)
|
||
|
|
{
|
||
|
|
const UStaticMeshComponent* MeshComp = Cast<UStaticMeshComponent>(Comp);
|
||
|
|
return MeshComp ? MeshComp->GetStaticMesh() : nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Lit : meme regle que LPShowCoupleSubsystem (IsBedMesh), recopiee ici (l'original est dans un espace anonyme).
|
||
|
|
bool IsAdmittedBed(const UPrimitiveComponent* Comp)
|
||
|
|
{
|
||
|
|
const UStaticMesh* Mesh = ComponentMesh(Comp);
|
||
|
|
const FString MeshName = Mesh ? Mesh->GetName() : FString();
|
||
|
|
return Mesh && (MeshName.Contains(TEXT("bed"), ESearchCase::IgnoreCase) || MeshName.Contains(TEXT("fnt_ext011"))
|
||
|
|
|| MeshName.Equals(TEXT("fnt_new045")) || MeshName.Equals(TEXT("fnt_new235")));
|
||
|
|
}
|
||
|
|
|
||
|
|
// « Proprietaire/composant (maillage) » pour les lignes de refus (CAM-B2p-H, SPEC G § 5.3).
|
||
|
|
FString DescribeComponent(const UPrimitiveComponent* Comp)
|
||
|
|
{
|
||
|
|
const UStaticMesh* Mesh = ComponentMesh(Comp);
|
||
|
|
return FString::Printf(TEXT("%s/%s (%s)"), *GetNameSafe(Comp ? Comp->GetOwner() : nullptr), *GetNameSafe(Comp),
|
||
|
|
Mesh ? *Mesh->GetName() : TEXT("sans maillage statique"));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pose libre (G1) : la boite du couple a cette place ne recouvre aucun composant bloquant de Room, hors sol et lits.
|
||
|
|
bool CoupleSpotFree(const UWorld* World, const AActor* Room, const UPrimitiveComponent* Floor, const TArray<AActor*>& Couple, const FVector& RootSpot, double Yaw, FString& OutBlocker)
|
||
|
|
{
|
||
|
|
if (!World || !Room)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
FCollisionQueryParams Query(SCENE_QUERY_STAT(LPCoupleSpot), false);
|
||
|
|
Query.AddIgnoredActors(Couple);
|
||
|
|
if (Floor)
|
||
|
|
{
|
||
|
|
Query.AddIgnoredComponent(Floor);
|
||
|
|
}
|
||
|
|
TArray<FOverlapResult> Overlaps;
|
||
|
|
const FVector Center(RootSpot.X, RootSpot.Y, RootSpot.Z + CoupleProbeAboveRoot);
|
||
|
|
World->OverlapMultiByChannel(Overlaps, Center, FRotator(0.0, Yaw, 0.0).Quaternion(), ECC_Pawn, FCollisionShape::MakeBox(CoupleProbeExtent), Query);
|
||
|
|
for (const FOverlapResult& Overlap : Overlaps)
|
||
|
|
{
|
||
|
|
const UPrimitiveComponent* Comp = Overlap.GetComponent();
|
||
|
|
if (!Overlap.bBlockingHit || !Comp || Comp->GetOwner() != Room)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
// Lits franchissables : meme regle que LPShowCoupleSubsystem (IsBedMesh).
|
||
|
|
if (IsAdmittedBed(Comp))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
OutBlocker = DescribeComponent(Comp);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Champ de texte avec le focus clavier (ex. « Type keyword to filter » du menu des positions).
|
||
|
|
TSharedPtr<SWidget> FocusedEditable()
|
||
|
|
{
|
||
|
|
if (!FSlateApplication::IsInitialized())
|
||
|
|
{
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
TSharedPtr<SWidget> Focused = FSlateApplication::Get().GetKeyboardFocusedWidget();
|
||
|
|
return Focused.IsValid() && Focused->GetTypeAsString().Contains(TEXT("Editable")) ? Focused : nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Un widget de la classe ClassName est dans le viewport de GI et visible.
|
||
|
|
bool WidgetOnScreen(const UGameInstance* GI, const TCHAR* ClassName)
|
||
|
|
{
|
||
|
|
for (TObjectIterator<UUserWidget> It; It && GI; ++It)
|
||
|
|
{
|
||
|
|
const UUserWidget* Widget = *It;
|
||
|
|
if (!Widget || Widget->HasAnyFlags(RF_ClassDefaultObject) || Widget->GetGameInstance() != GI || !Widget->IsInViewport())
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const ESlateVisibility Visibility = Widget->GetVisibility();
|
||
|
|
if (Visibility != ESlateVisibility::Collapsed && Visibility != ESlateVisibility::Hidden && Widget->GetClass()->GetName() == ClassName)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||
|
|
{
|
||
|
|
Super::Initialize(Collection);
|
||
|
|
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPCameraModeSubsystem::Tick), LPCameraModeLocal::TickInterval);
|
||
|
|
// A chaque image : un appui bref sur C tient moins de 0,1 s.
|
||
|
|
SwitchKeyHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPCameraModeSubsystem::TickSwitchKey), 0.f);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::Deinitialize()
|
||
|
|
{
|
||
|
|
FTSTicker::GetCoreTicker().RemoveTicker(SwitchKeyHandle);
|
||
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
||
|
|
RemoveWidget();
|
||
|
|
RemoveHelpBox();
|
||
|
|
Super::Deinitialize();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPCameraModeSubsystem::Tick(float DeltaTime)
|
||
|
|
{
|
||
|
|
// Avant les retours de l'indicateur : le filtre du panneau qui a le focus compte comme un menu pour MenuOnScreen.
|
||
|
|
TickControlsHelp();
|
||
|
|
const FString Label = ComputeLabel();
|
||
|
|
const bool bMenu = LPCameraModeLocal::MenuOnScreen(GetGameInstance());
|
||
|
|
bMenuOnScreen = bMenu;
|
||
|
|
RepairAfterCharGen(Label, bMenu, DeltaTime);
|
||
|
|
if (!Label.IsEmpty())
|
||
|
|
{
|
||
|
|
LastEmptyView.Reset();
|
||
|
|
}
|
||
|
|
else if (!bMenu)
|
||
|
|
{
|
||
|
|
// Temoin touche C (CAM-B2j) : vue non reconnue hors menu, une ligne par changement de vue.
|
||
|
|
const UWorld* World = GetGameInstance() ? GetGameInstance()->GetWorld() : nullptr;
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
const AActor* View = PC ? PC->GetViewTarget() : nullptr;
|
||
|
|
const FString ViewText = FString::Printf(TEXT("%s, classe %s"), *GetNameSafe(View), View ? *View->GetClass()->GetName() : TEXT("-"));
|
||
|
|
if (ViewText != LastEmptyView)
|
||
|
|
{
|
||
|
|
LastEmptyView = ViewText;
|
||
|
|
ShownLabel.Reset();
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Indicateur de camera : aucun (vue %s)"), *ViewText);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (Label.IsEmpty() || bMenu)
|
||
|
|
{
|
||
|
|
SetShown(false);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (!EnsureWidget())
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (Label != ShownLabel)
|
||
|
|
{
|
||
|
|
Text->SetText(FText::FromString(Label));
|
||
|
|
ShownLabel = Label;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Indicateur de camera : %s"), *Label);
|
||
|
|
}
|
||
|
|
SetShown(true);
|
||
|
|
if (Label == LPCameraModeLocal::LabelC2)
|
||
|
|
{
|
||
|
|
WitnessC2(DeltaTime);
|
||
|
|
WitnessOcclusion(DeltaTime);
|
||
|
|
}
|
||
|
|
else if (Label == LPCameraModeLocal::LabelC1)
|
||
|
|
{
|
||
|
|
WitnessC1(DeltaTime);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::RepairAfterCharGen(const FString& Label, bool bMenu, float DeltaTime)
|
||
|
|
{
|
||
|
|
UGameInstance* GI = GetGameInstance();
|
||
|
|
if (!GI)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GICharGenOn))
|
||
|
|
{
|
||
|
|
bCharGenSeen = true;
|
||
|
|
CharGenRepairTimeout = LPCameraModeLocal::CharGenRepairDelay;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!bCharGenSeen)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const UWorld* World = GI->GetWorld();
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
const AActor* View = PC ? PC->GetViewTarget() : nullptr;
|
||
|
|
bool bRoomCamera = false;
|
||
|
|
for (const TCHAR* RoomLabel : LPCameraModeLocal::RoomCameraLabels)
|
||
|
|
{
|
||
|
|
bRoomCamera |= Label == RoomLabel;
|
||
|
|
}
|
||
|
|
if (!bRoomCamera || bMenu)
|
||
|
|
{
|
||
|
|
CharGenRepairTimeout -= DeltaTime;
|
||
|
|
if (CharGenRepairTimeout <= 0.f)
|
||
|
|
{
|
||
|
|
bCharGenSeen = false;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("CharGen : sortie sans camera de piece (vue %s, indicateur '%s'), rien remis"), *GetNameSafe(View), *Label);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Vue revenue sur une camera de piece (jamais Birdview, la carte de la ville) : une seule reparation.
|
||
|
|
bCharGenSeen = false;
|
||
|
|
if (!LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISpaceOn))
|
||
|
|
{
|
||
|
|
LPCameraModeLocal::WriteBool(GI, LPCameraModeLocal::GISpaceOn, true);
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("CharGen : SpaceOn remis a vrai (vue %s, indicateur '%s')"), *GetNameSafe(View), *Label);
|
||
|
|
}
|
||
|
|
if (LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GIMainMenuOn) && !LPCameraModeLocal::WidgetOnScreen(GI, LPCameraModeLocal::MainMenuClass))
|
||
|
|
{
|
||
|
|
LPCameraModeLocal::WriteBool(GI, LPCameraModeLocal::GIMainMenuOn, false);
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("CharGen : MainMenuOn remis a faux (menu principal ferme)"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::WitnessC1(float DeltaTime)
|
||
|
|
{
|
||
|
|
C1Cooldown = FMath::Max(0.f, C1Cooldown - DeltaTime);
|
||
|
|
if (C1Cooldown > 0.f)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
C1Cooldown = LPCameraModeLocal::C1Interval;
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
const UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
const ACharacter* Player = Cast<ACharacter>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter));
|
||
|
|
const USkeletalMeshComponent* Mesh = IsValid(Player) ? Player->GetMesh() : nullptr;
|
||
|
|
const USceneComponent* Camera = IsValid(Player) ? Cast<USceneComponent>(LPCameraModeLocal::ReadObject(Player, LPCameraModeLocal::PlayerFirstPersonSM)) : nullptr;
|
||
|
|
if (!PC || !Mesh || !IsValid(Camera))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const FVector Eyes = (Mesh->GetSocketLocation(LPCameraModeLocal::LeftEyeSocket) + Mesh->GetSocketLocation(LPCameraModeLocal::RightEyeSocket)) * 0.5f;
|
||
|
|
const double Distance = FVector::Dist(Camera->GetComponentLocation(), Eyes);
|
||
|
|
const FRotator CameraRotation = Camera->GetComponentRotation();
|
||
|
|
const double YawGap = FRotator::NormalizeAxis(CameraRotation.Yaw - PC->GetControlRotation().Yaw);
|
||
|
|
if (Distance <= LPCameraModeLocal::C1MaxEyeDistance && FMath::Abs(CameraRotation.Pitch) <= LPCameraModeLocal::C1MaxPitch
|
||
|
|
&& FMath::Abs(YawGap) <= LPCameraModeLocal::C1MaxYawGap)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const UObject* RoomObject = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
|
||
|
|
C1Cooldown = LPCameraModeLocal::C1LogInterval;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Temoin C1 : camera a %.0f cm des yeux, tangage %+.0f, ecart de lacet %+.0f, assis=%d, minuterie=%d"),
|
||
|
|
Distance, CameraRotation.Pitch, YawGap,
|
||
|
|
IsValid(LPCameraModeLocal::ReadObject(Player, LPCameraModeLocal::PlayerOccupyingFnt)) ? 1 : 0,
|
||
|
|
LPCameraModeLocal::TimerActive(IsValid(RoomObject) ? RoomObject : nullptr, LPCameraModeLocal::RoomCamTimer, World) ? 1 : 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPCameraModeSubsystem::TickSwitchKey(float DeltaTime)
|
||
|
|
{
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
const UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
const UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||
|
|
bool bDown = false;
|
||
|
|
if (PC && PC->PlayerInput && Settings)
|
||
|
|
{
|
||
|
|
for (const FInputActionKeyMapping& Map : Settings->GetActionMappings())
|
||
|
|
{
|
||
|
|
if (Map.ActionName == LPCameraModeLocal::SwitchCameraAction && PC->PlayerInput->IsPressed(Map.Key))
|
||
|
|
{
|
||
|
|
bDown = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (bDown && !bSwitchKeyDown)
|
||
|
|
{
|
||
|
|
LogSwitchKey(PC);
|
||
|
|
}
|
||
|
|
bSwitchKeyDown = bDown;
|
||
|
|
TickSexCamera(DeltaTime);
|
||
|
|
TickCoupleBounds(DeltaTime);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::TickCoupleBounds(float DeltaTime)
|
||
|
|
{
|
||
|
|
CoupleBoundsCooldown = FMath::Max(0.f, CoupleBoundsCooldown - DeltaTime);
|
||
|
|
CoupleMoveCooldown = FMath::Max(0.f, CoupleMoveCooldown - DeltaTime);
|
||
|
|
UGameInstance* GI = GetGameInstance();
|
||
|
|
const bool bGrabEnded = bGrabJustEnded;
|
||
|
|
bGrabJustEnded = false;
|
||
|
|
if (!GI || !LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISexOn))
|
||
|
|
{
|
||
|
|
bHasLastPivot = false;
|
||
|
|
bHiddenRoomWarned = false;
|
||
|
|
bBoundsLogged = false;
|
||
|
|
// B2p-G : hors sexe, C3 n'a plus de sens (variable du PC remise a faux si la scene a fini en C3).
|
||
|
|
LastSexView.Reset();
|
||
|
|
bSexC3Was = false;
|
||
|
|
const UWorld* EndWorld = GI ? GI->GetWorld() : nullptr;
|
||
|
|
if (APlayerController* EndPC = EndWorld ? EndWorld->GetFirstPlayerController() : nullptr)
|
||
|
|
{
|
||
|
|
if (LPCameraModeLocal::ReadBool(EndPC, LPCameraModeLocal::PCSexC3))
|
||
|
|
{
|
||
|
|
LPCameraModeLocal::WriteBool(EndPC, LPCameraModeLocal::PCSexC3, false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
AActor* Room = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom));
|
||
|
|
const TArray<AActor*> Couple = LPCameraModeLocal::ReadActors(GI, LPCameraModeLocal::GISexPersons);
|
||
|
|
if (!IsValid(Room) || Couple.Num() == 0)
|
||
|
|
{
|
||
|
|
bHasLastPivot = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
FVector Pivot = FVector::ZeroVector;
|
||
|
|
for (const AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
Pivot += Person->GetActorLocation();
|
||
|
|
}
|
||
|
|
Pivot /= Couple.Num();
|
||
|
|
|
||
|
|
// E4 : temoin des deplacements hors prise (source des descentes de COMMUN-9 a designer).
|
||
|
|
// F4 : l'image ou une prise vient de finir porte encore le dernier pas du glisser, elle n'est pas comptee.
|
||
|
|
if (bHasLastPivot && CoupleGrabMode == 0 && !bGrabEnded && FVector::Dist(Pivot, LastCouplePivot) > LPCameraModeLocal::CoupleMoveWitness && CoupleMoveCooldown <= 0.f)
|
||
|
|
{
|
||
|
|
CoupleMoveCooldown = LPCameraModeLocal::CoupleBoundsLogInterval;
|
||
|
|
const UWorld* World = GI->GetWorld();
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
const bool bShift = PC && PC->PlayerInput && (PC->PlayerInput->IsPressed(EKeys::LeftShift) || PC->PlayerInput->IsPressed(EKeys::RightShift));
|
||
|
|
const FVector Moved = Pivot - LastCouplePivot;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : couple deplace hors prise de (%+.0f, %+.0f, %+.0f) ; fleches=%+.0f/%+.0f Maj=%d molette=%+.1f ; Room cachee=%d"),
|
||
|
|
Moved.X, Moved.Y, Moved.Z, LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisLeftRight), LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisDownUp),
|
||
|
|
bShift ? 1 : 0, LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisScroll), Room->IsHidden() ? 1 : 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Piece cachee (scene d'exterieur) : pas de sol de reference, pas de borne.
|
||
|
|
const UPrimitiveComponent* Floor = Cast<UPrimitiveComponent>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomFloor));
|
||
|
|
if (Room->IsHidden() || !IsValid(Floor))
|
||
|
|
{
|
||
|
|
if (!bHiddenRoomWarned)
|
||
|
|
{
|
||
|
|
bHiddenRoomWarned = true;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : pas de borne du couple (Room cachee=%d, sol %s)"), Room->IsHidden() ? 1 : 0, *GetNameSafe(Floor));
|
||
|
|
}
|
||
|
|
TickSceneCamera(GI, Room, Couple, Pivot, bGrabEnded);
|
||
|
|
LastCouplePivot = Pivot;
|
||
|
|
bHasLastPivot = true;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// E1 + F1 : borne XY du pivot entre les murs reels (marge = demi-boite du couple), puis Z de la racine du maillage
|
||
|
|
// de chaque personne entre le dessus du sol et le plafond (trouve ou estime).
|
||
|
|
LPCameraModeLocal::FRoomInterior Interior;
|
||
|
|
if (!LPCameraModeLocal::ComputeRoomInterior(Room, Interior))
|
||
|
|
{
|
||
|
|
LastCouplePivot = Pivot;
|
||
|
|
bHasLastPivot = true;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const double FloorTop = Interior.FloorTop;
|
||
|
|
const double Margin = LPCameraModeLocal::CoupleWallMargin;
|
||
|
|
if (!bBoundsLogged)
|
||
|
|
{
|
||
|
|
// F2 : bornes retenues, une fois par scene.
|
||
|
|
bBoundsLogged = true;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : bornes du couple X [%.0f, %.0f] Y [%.0f, %.0f] sol %.0f plafond %.0f (marge %.0f) ; source %s, murs %d, plafond trouve %d ; sol Box X [%.0f, %.0f] Y [%.0f, %.0f] ; echelle racine (%.2f, %.2f)"),
|
||
|
|
Interior.Area.Min.X, Interior.Area.Max.X, Interior.Area.Min.Y, Interior.Area.Max.Y, FloorTop, Interior.Ceiling, Margin,
|
||
|
|
*Interior.Source, Interior.Walls, Interior.bCeilingFound ? 1 : 0,
|
||
|
|
Interior.FloorBox.Min.X, Interior.FloorBox.Max.X, Interior.FloorBox.Min.Y, Interior.FloorBox.Max.Y, Interior.Scale.X, Interior.Scale.Y);
|
||
|
|
}
|
||
|
|
FVector Correction = FVector::ZeroVector;
|
||
|
|
const FBox2D& Area = Interior.Area;
|
||
|
|
Correction.X = Area.Max.X - Area.Min.X > 2.0 * Margin ? FMath::Clamp(Pivot.X, Area.Min.X + Margin, Area.Max.X - Margin) - Pivot.X : Area.GetCenter().X - Pivot.X;
|
||
|
|
Correction.Y = Area.Max.Y - Area.Min.Y > 2.0 * Margin ? FMath::Clamp(Pivot.Y, Area.Min.Y + Margin, Area.Max.Y - Margin) - Pivot.Y : Area.GetCenter().Y - Pivot.Y;
|
||
|
|
double LowestRoot = TNumericLimits<double>::Max();
|
||
|
|
double HighestRoot = TNumericLimits<double>::Lowest();
|
||
|
|
for (const AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
const ACharacter* Character = Cast<ACharacter>(Person);
|
||
|
|
const USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr;
|
||
|
|
const double RootZ = Mesh ? Mesh->GetComponentLocation().Z : Person->GetActorLocation().Z;
|
||
|
|
LowestRoot = FMath::Min(LowestRoot, RootZ);
|
||
|
|
HighestRoot = FMath::Max(HighestRoot, RootZ);
|
||
|
|
}
|
||
|
|
if (LowestRoot < FloorTop - LPCameraModeLocal::CoupleFloorTolerance)
|
||
|
|
{
|
||
|
|
Correction.Z = FloorTop - LowestRoot;
|
||
|
|
}
|
||
|
|
else if (HighestRoot > Interior.Ceiling - LPCameraModeLocal::CoupleHeadroom)
|
||
|
|
{
|
||
|
|
Correction.Z = Interior.Ceiling - LPCameraModeLocal::CoupleHeadroom - HighestRoot;
|
||
|
|
}
|
||
|
|
if (Correction.SizeSquared() > 0.25)
|
||
|
|
{
|
||
|
|
for (AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
Person->SetActorLocation(Person->GetActorLocation() + Correction);
|
||
|
|
}
|
||
|
|
Pivot += Correction;
|
||
|
|
if (CoupleBoundsCooldown <= 0.f)
|
||
|
|
{
|
||
|
|
CoupleBoundsCooldown = LPCameraModeLocal::CoupleBoundsLogInterval;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : couple ramene dans la piece de (%+.0f, %+.0f, %+.0f) ; sol %.0f, plafond %.0f (racine au plus %.0f) ; prise=%d"),
|
||
|
|
Correction.X, Correction.Y, Correction.Z, FloorTop, Interior.Ceiling, Interior.Ceiling - LPCameraModeLocal::CoupleHeadroom, CoupleGrabMode);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
TickSceneCamera(GI, Room, Couple, Pivot, bGrabEnded);
|
||
|
|
LastCouplePivot = Pivot;
|
||
|
|
bHasLastPivot = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::TickSceneCamera(UGameInstance* GI, AActor* Room, const TArray<AActor*>& Couple, const FVector& Pivot, bool bGrabEnded)
|
||
|
|
{
|
||
|
|
const UWorld* World = GI->GetWorld();
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
AActor* View = PC ? PC->GetViewTarget() : nullptr;
|
||
|
|
AActor* SceneCamera = Cast<AActor>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomSceneCamera));
|
||
|
|
const bool bC3 = LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCSexC3);
|
||
|
|
const bool bOnScene = IsValid(SceneCamera) && View == SceneCamera;
|
||
|
|
if (bOnScene && LastSexView.Get() != SceneCamera)
|
||
|
|
{
|
||
|
|
// G-b : entree en mode Scene (debut de scene, retour depuis le dernier POV).
|
||
|
|
RecenterSceneCamera(GI, Room, SceneCamera, Couple, Pivot, TEXT("entree en Scene"));
|
||
|
|
}
|
||
|
|
else if (bOnScene && bC3 && !bSexC3Was)
|
||
|
|
{
|
||
|
|
// G-d : passage en C3 (variable SexC3 du PC) : recadrage.
|
||
|
|
RecenterSceneCamera(GI, Room, SceneCamera, Couple, Pivot, TEXT("C3"));
|
||
|
|
}
|
||
|
|
else if (bOnScene && bHasLastPivot && CoupleGrabMode == 0 && !bGrabEnded)
|
||
|
|
{
|
||
|
|
// G-c : le couple a bouge hors glisser (fleches, molette, borne, jeu) : la camera suit du meme vecteur.
|
||
|
|
const FVector Moved = Pivot - LastCouplePivot;
|
||
|
|
if (Moved.Size() > LPCameraModeLocal::FollowMinStep)
|
||
|
|
{
|
||
|
|
SceneCamera->AddActorWorldOffset(Moved);
|
||
|
|
}
|
||
|
|
// Distance douce : jamais plus de FollowMaxDistance du couple.
|
||
|
|
const FVector Away = SceneCamera->GetActorLocation() - Pivot;
|
||
|
|
if (Away.Size() > LPCameraModeLocal::FollowMaxDistance)
|
||
|
|
{
|
||
|
|
SceneCamera->SetActorLocation(Pivot + Away.GetSafeNormal() * LPCameraModeLocal::FollowMaxDistance);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bSexC3Was = bC3;
|
||
|
|
LastSexView = View;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::RecenterSceneCamera(UGameInstance* GI, AActor* Room, AActor* SceneCamera, const TArray<AActor*>& Couple, const FVector& Pivot, const TCHAR* Reason)
|
||
|
|
{
|
||
|
|
const UWorld* World = GI->GetWorld();
|
||
|
|
if (!World || Couple.Num() == 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Cible : milieu du corps, au-dessus de la racine du maillage la plus basse du couple.
|
||
|
|
double LowestRoot = TNumericLimits<double>::Max();
|
||
|
|
for (const AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
const ACharacter* Character = Cast<ACharacter>(Person);
|
||
|
|
const USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr;
|
||
|
|
LowestRoot = FMath::Min(LowestRoot, Mesh ? Mesh->GetComponentLocation().Z : Person->GetActorLocation().Z);
|
||
|
|
}
|
||
|
|
const FVector Target(Pivot.X, Pivot.Y, LowestRoot + LPCameraModeLocal::RecenterTargetAboveRoot);
|
||
|
|
// Azimut actuel de la camera autour du couple (saut limite), sinon derriere le meneur.
|
||
|
|
FVector Flat = SceneCamera->GetActorLocation() - Target;
|
||
|
|
Flat.Z = 0.0;
|
||
|
|
if (!Flat.Normalize())
|
||
|
|
{
|
||
|
|
Flat = -Couple[0]->GetActorForwardVector();
|
||
|
|
Flat.Z = 0.0;
|
||
|
|
if (!Flat.Normalize())
|
||
|
|
{
|
||
|
|
Flat = FVector::ForwardVector;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
FVector Desired = Target + Flat * LPCameraModeLocal::RecenterDistance + FVector(0.0, 0.0, LPCameraModeLocal::RecenterHeight);
|
||
|
|
LPCameraModeLocal::FRoomInterior Interior;
|
||
|
|
if (!Room->IsHidden() && LPCameraModeLocal::ComputeRoomInterior(Room, Interior))
|
||
|
|
{
|
||
|
|
const FBox2D& Area = Interior.Area;
|
||
|
|
const double Margin = LPCameraModeLocal::RecenterWallMargin;
|
||
|
|
if (Area.Max.X - Area.Min.X > 2.0 * Margin && Area.Max.Y - Area.Min.Y > 2.0 * Margin)
|
||
|
|
{
|
||
|
|
Desired.X = FMath::Clamp(Desired.X, Area.Min.X + Margin, Area.Max.X - Margin);
|
||
|
|
Desired.Y = FMath::Clamp(Desired.Y, Area.Min.Y + Margin, Area.Max.Y - Margin);
|
||
|
|
}
|
||
|
|
Desired.Z = FMath::Clamp(Desired.Z, Interior.FloorTop + 30.0, FMath::Max(Interior.FloorTop + 30.0, Interior.Ceiling - Margin));
|
||
|
|
}
|
||
|
|
// Comme un bras de camera : devant le premier obstacle entre la cible et la camera (couple et cameras ignores).
|
||
|
|
FCollisionQueryParams Query(SCENE_QUERY_STAT(LPRecenter), true);
|
||
|
|
Query.AddIgnoredActors(Couple);
|
||
|
|
Query.AddIgnoredActor(SceneCamera);
|
||
|
|
if (AActor* Wall = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIWall)))
|
||
|
|
{
|
||
|
|
Query.AddIgnoredActor(Wall);
|
||
|
|
}
|
||
|
|
if (AActor* CamRoom = Cast<AActor>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomCamRoom)))
|
||
|
|
{
|
||
|
|
Query.AddIgnoredActor(CamRoom);
|
||
|
|
}
|
||
|
|
FHitResult Hit;
|
||
|
|
if (World->LineTraceSingleByChannel(Hit, Target, Desired, ECC_Visibility, Query))
|
||
|
|
{
|
||
|
|
// Devant l'obstacle, mais jamais a moins de RecenterMinDistance de la cible (sinon la camera passerait de l'autre cote).
|
||
|
|
// ObstacleReach, pas Reach : LPDirectControlSubsystem.cpp a un Reach dans son espace anonyme (C4459 en unite unifiee).
|
||
|
|
const FVector Axis = (Desired - Target).GetSafeNormal();
|
||
|
|
const double ObstacleReach = FMath::Max(FVector::Dist(Target, Hit.Location) - LPCameraModeLocal::RecenterWallMargin, static_cast<double>(LPCameraModeLocal::RecenterMinDistance));
|
||
|
|
Desired = Target + Axis * ObstacleReach;
|
||
|
|
}
|
||
|
|
SceneCamera->SetActorLocationAndRotation(Desired, (Target - Desired).Rotation());
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : vue recadree sur le couple (%s) a %.0f cm, cible (%.0f, %.0f, %.0f)%s"),
|
||
|
|
Reason, FVector::Dist(Desired, Target), Target.X, Target.Y, Target.Z, Hit.bBlockingHit ? TEXT(", rapprochee devant un obstacle") : TEXT(""));
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::TickSexCamera(float DeltaTime)
|
||
|
|
{
|
||
|
|
SexKeysCooldown = FMath::Max(0.f, SexKeysCooldown - DeltaTime);
|
||
|
|
// Pas de temps borne : un a-coup d'image (chargement) ne fait pas tourner ou avancer la camera d'un coup.
|
||
|
|
const float Step = FMath::Min(DeltaTime, LPCameraModeLocal::SexMaxStep);
|
||
|
|
UGameInstance* GI = GetGameInstance();
|
||
|
|
const bool bSexOn = GI && LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISexOn);
|
||
|
|
// CAM-B2p-I (COMMUN-12, point 3) : debut et fin de scene ; a la fin, chaque etat touche par CAM est rendu, une ligne.
|
||
|
|
if (bSexOn != bSexStateWas)
|
||
|
|
{
|
||
|
|
bSexStateWas = bSexOn;
|
||
|
|
if (bSexOn)
|
||
|
|
{
|
||
|
|
BeginSexScene();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
EndSexScene();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!bSexOn)
|
||
|
|
{
|
||
|
|
bSceneFocusChecked = false;
|
||
|
|
bFocusClickWasDown = false;
|
||
|
|
bMiddleWasDown = false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// H1 / H2 : un champ de texte (filtre du menu des positions) garde le focus clavier ; ZQSD partirait dedans.
|
||
|
|
// H1 : un clic dans la vue 3D (vu par le PlayerInput, donc hors interface) rend le focus au viewport.
|
||
|
|
// H2 : a la premiere image du sexe, un champ vide qui a le focus le rend une fois.
|
||
|
|
// CAM-B2p-H : le clic molette aussi, sinon le filtre du panneau (menu pour MenuOnScreen) le bloquerait.
|
||
|
|
const UWorld* FocusWorld = GI->GetWorld();
|
||
|
|
const APlayerController* FocusPC = FocusWorld ? FocusWorld->GetFirstPlayerController() : nullptr;
|
||
|
|
const bool bClick = FocusPC && FocusPC->PlayerInput
|
||
|
|
&& (FocusPC->PlayerInput->IsPressed(EKeys::LeftMouseButton) || FocusPC->PlayerInput->IsPressed(EKeys::RightMouseButton)
|
||
|
|
|| FocusPC->PlayerInput->IsPressed(EKeys::MiddleMouseButton));
|
||
|
|
const TSharedPtr<SWidget> Editable = LPCameraModeLocal::FocusedEditable();
|
||
|
|
bool bGiveBack = false;
|
||
|
|
const TCHAR* Why = TEXT("");
|
||
|
|
if (Editable.IsValid() && bClick && !bFocusClickWasDown)
|
||
|
|
{
|
||
|
|
bGiveBack = true;
|
||
|
|
Why = TEXT("clic dans la vue");
|
||
|
|
}
|
||
|
|
else if (Editable.IsValid() && !bSceneFocusChecked && Editable->GetTypeAsString() == TEXT("SEditableText")
|
||
|
|
&& StaticCastSharedPtr<SEditableText>(Editable)->GetText().IsEmpty())
|
||
|
|
{
|
||
|
|
bGiveBack = true;
|
||
|
|
Why = TEXT("champ vide a l'entree");
|
||
|
|
}
|
||
|
|
bSceneFocusChecked = true;
|
||
|
|
bFocusClickWasDown = bClick;
|
||
|
|
if (bGiveBack)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : focus clavier rendu a la vue (%s, champ %s)"), Why, *Editable->GetTypeAsString());
|
||
|
|
FSlateApplication::Get().SetAllUserFocusToGameViewport();
|
||
|
|
bMenuOnScreen = LPCameraModeLocal::MenuOnScreen(GI);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!bSexOn || bMenuOnScreen)
|
||
|
|
{
|
||
|
|
// Hors sexe ou menu : aucune prise en cours, les clics reviennent au jeu tels quels (clic molette compris).
|
||
|
|
ReleaseCoupleGrab();
|
||
|
|
bMiddleWasDown = false;
|
||
|
|
if (!GI || !LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISexOn))
|
||
|
|
{
|
||
|
|
bCursorWarned = false;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const UWorld* World = GI->GetWorld();
|
||
|
|
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
AActor* View = PC ? PC->GetViewTarget() : nullptr;
|
||
|
|
if (PC)
|
||
|
|
{
|
||
|
|
TickPanelClick(PC, DeltaTime);
|
||
|
|
KeepMouseFree(PC, DeltaTime);
|
||
|
|
}
|
||
|
|
if (!PC || !IsValid(View))
|
||
|
|
{
|
||
|
|
ReleaseCoupleGrab();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const float Forward = LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisMoveForward);
|
||
|
|
const float Right = LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisMoveRight);
|
||
|
|
const float Wheel = LPCameraModeLocal::bSexWheelZoom ? LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisScroll) : 0.f;
|
||
|
|
const UObject* RoomObject = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
|
||
|
|
const UObject* Room = IsValid(RoomObject) ? RoomObject : nullptr;
|
||
|
|
const TArray<AActor*> Couple = LPCameraModeLocal::ReadActors(GI, LPCameraModeLocal::GISexPersons);
|
||
|
|
FVector Pivot = FVector::ZeroVector;
|
||
|
|
for (const AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
Pivot += Person->GetActorLocation();
|
||
|
|
}
|
||
|
|
if (Couple.Num() > 0)
|
||
|
|
{
|
||
|
|
Pivot /= Couple.Num();
|
||
|
|
}
|
||
|
|
const bool bKeys = !FMath::IsNearlyZero(Forward) || !FMath::IsNearlyZero(Right) || !FMath::IsNearlyZero(Wheel);
|
||
|
|
// Prise du couple a la souris (B2p-D) ; pendant la prise, la camera est figee et ZQSD ne la deplace pas.
|
||
|
|
const bool bGrabbing = TickCoupleMouse(GI, PC, View, Room, Couple, Pivot);
|
||
|
|
const AActor* SceneCamera = Cast<AActor>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomSceneCamera));
|
||
|
|
if (!bGrabbing && bKeys && Couple.Num() > 0 && View == SceneCamera)
|
||
|
|
{
|
||
|
|
// Mode Scene : la camera tourne autour du couple du meme angle que son lacet (cadrage garde, comme B2n),
|
||
|
|
// puis avance ou recule vers le couple, bornee.
|
||
|
|
const float Angle = -Right * LPCameraModeLocal::SexOrbitSpeed * Step;
|
||
|
|
FVector Offset = View->GetActorLocation() - Pivot;
|
||
|
|
Offset = Offset.RotateAngleAxis(Angle, FVector::UpVector);
|
||
|
|
// Bornes seulement quand on avance ou recule : l'orbite seule ne deplace pas une camera posee plus loin par le jeu.
|
||
|
|
double Distance = Offset.Size();
|
||
|
|
if (!FMath::IsNearlyZero(Forward) || !FMath::IsNearlyZero(Wheel))
|
||
|
|
{
|
||
|
|
Distance = FMath::Clamp(Distance - Forward * LPCameraModeLocal::SexDollySpeed * Step - Wheel * LPCameraModeLocal::SexWheelStep,
|
||
|
|
static_cast<double>(LPCameraModeLocal::SexMinDistance), static_cast<double>(LPCameraModeLocal::SexMaxDistance));
|
||
|
|
}
|
||
|
|
const FVector Direction = Offset.GetSafeNormal();
|
||
|
|
if (!Direction.IsZero())
|
||
|
|
{
|
||
|
|
FRotator Rotation = View->GetActorRotation();
|
||
|
|
Rotation.Yaw += Angle;
|
||
|
|
View->SetActorLocationAndRotation(Pivot + Direction * Distance, Rotation);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (!bGrabbing && bKeys && Couple.Contains(View))
|
||
|
|
{
|
||
|
|
// POV : meme composant que la souris en POV (PC 204 et 316 : FirstPersonSM de SexPersons[i]).
|
||
|
|
USceneComponent* Look = Cast<USceneComponent>(LPCameraModeLocal::ReadObject(View, LPCameraModeLocal::PlayerFirstPersonSM));
|
||
|
|
if (IsValid(Look))
|
||
|
|
{
|
||
|
|
FRotator Rotation = Look->GetComponentRotation();
|
||
|
|
Rotation.Yaw += Right * LPCameraModeLocal::SexLookSpeed * Step;
|
||
|
|
Rotation.Pitch = FMath::Clamp(Rotation.Pitch + Forward * LPCameraModeLocal::SexLookSpeed * Step,
|
||
|
|
-static_cast<double>(LPCameraModeLocal::SexMaxPitch), static_cast<double>(LPCameraModeLocal::SexMaxPitch));
|
||
|
|
Look->SetWorldRotation(Rotation);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Temoin (KO Maj + souris, CAM-COMMUN6-DIAG § 6) : au plus une ligne par seconde tant qu'une touche est tenue.
|
||
|
|
const float Arrows = FMath::Abs(LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisLeftRight)) + FMath::Abs(LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisDownUp));
|
||
|
|
const bool bShift = PC->PlayerInput && (PC->PlayerInput->IsPressed(EKeys::LeftShift) || PC->PlayerInput->IsPressed(EKeys::RightShift));
|
||
|
|
const bool bButtons = bLeftWasDown || bRightWasDown;
|
||
|
|
if (SexKeysCooldown > 0.f || (!bKeys && FMath::IsNearlyZero(Arrows) && !bShift && !bButtons))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
SexKeysCooldown = LPCameraModeLocal::SexKeysLogInterval;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : touches avant=%+.0f cote=%+.0f fleches=%+.0f/%+.0f Maj=%d MoveObject=%d MoveCamera=%d ; boutons G=%d D=%d prise=%d ; vue %s ; index POV %d ; couple %d a (%.0f, %.0f, %.0f), lacet %.0f"),
|
||
|
|
Forward, Right, LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisLeftRight), LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisDownUp),
|
||
|
|
bShift ? 1 : 0, LPCameraModeLocal::ReadBool(Room, LPCameraModeLocal::RoomMoveObject) ? 1 : 0, LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCMoveCamera) ? 1 : 0,
|
||
|
|
bLeftWasDown ? 1 : 0, bRightWasDown ? 1 : 0, CoupleGrabMode,
|
||
|
|
*GetNameSafe(View), LPCameraModeLocal::ReadInt(GI, LPCameraModeLocal::GISexCameraIndex), Couple.Num(), Pivot.X, Pivot.Y, Pivot.Z,
|
||
|
|
Couple.Num() > 0 ? Couple[0]->GetActorRotation().Yaw : 0.0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::TickPanelClick(APlayerController* PC, float DeltaTime)
|
||
|
|
{
|
||
|
|
// CAM-B2p-I (COMMUN-12) : Alt aussi (Espace rend sa fonction au jeu). Alt et le clic molette sont deja l'action
|
||
|
|
// RotateCamera : un appui bref sans mouvement bascule le panneau, un appui tenu ou une glissee reste l'orbite.
|
||
|
|
const bool bMiddleButton = PC->PlayerInput && PC->PlayerInput->IsPressed(EKeys::MiddleMouseButton);
|
||
|
|
const bool bAltKey = PC->PlayerInput && (PC->PlayerInput->IsPressed(EKeys::LeftAlt) || PC->PlayerInput->IsPressed(EKeys::RightAlt));
|
||
|
|
const bool bMiddle = bMiddleButton || bAltKey;
|
||
|
|
float PanelMouseX = 0.f;
|
||
|
|
float PanelMouseY = 0.f;
|
||
|
|
const bool bPanelMouse = PC->GetMousePosition(PanelMouseX, PanelMouseY);
|
||
|
|
if (bMiddle && !bMiddleWasDown)
|
||
|
|
{
|
||
|
|
bPanelByAlt = bAltKey && !bMiddleButton;
|
||
|
|
MiddleHeld = 0.f;
|
||
|
|
MiddleStart = FVector2D(PanelMouseX, PanelMouseY);
|
||
|
|
bMiddleHadMouse = bPanelMouse;
|
||
|
|
bMiddleMoved = false;
|
||
|
|
}
|
||
|
|
else if (bMiddleWasDown)
|
||
|
|
{
|
||
|
|
// Duree reelle (DeltaTime non borne) ; mouvement mesure seulement si le curseur a une position au depart et ici.
|
||
|
|
MiddleHeld += DeltaTime;
|
||
|
|
bMiddleMoved |= bMiddleHadMouse && bPanelMouse
|
||
|
|
&& FVector2D::Distance(MiddleStart, FVector2D(PanelMouseX, PanelMouseY)) > LPCameraModeLocal::PanelClickMaxMove;
|
||
|
|
if (!bMiddle && MiddleHeld <= LPCameraModeLocal::PanelClickMaxHold && !bMiddleMoved)
|
||
|
|
{
|
||
|
|
UFunction* Toggle = PC->FindFunction(LPCameraModeLocal::PCLeftMouseSexOn);
|
||
|
|
if (Toggle && Toggle->NumParms == 0)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : panneau bascule (%s, %.2f s)"), bPanelByAlt ? TEXT("Alt") : TEXT("clic molette"), MiddleHeld);
|
||
|
|
PC->ProcessEvent(Toggle, nullptr);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : panneau non bascule, evenement LeftMouseSexOn absent du PC"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bMiddleWasDown = bMiddle;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::KeepMouseFree(APlayerController* PC, float DeltaTime)
|
||
|
|
{
|
||
|
|
// COMMUN-12 : a la fermeture du panneau, le PC repasse en GameOnly (CapturePermanently, LockOnCapture, mouvement
|
||
|
|
// haute precision) : le curseur est cache et sa position figee pendant l'appui, glisser et rotation valent 0. Avant
|
||
|
|
// B2p-G, un clic court ouvrait le panneau (GameAndUI, DoNotLock) et la souris restait libre. Pendant le sexe, hors
|
||
|
|
// menu et sans bouton tenu, CAM remet la souris dans cet etat ; le clavier reste au jeu (focus sur la vue).
|
||
|
|
MouseFreeCooldown = FMath::Max(0.f, MouseFreeCooldown - DeltaTime);
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
UGameViewportClient* ViewportClient = GI ? GI->GetGameViewportClient() : nullptr;
|
||
|
|
UPlayerInput* Input = PC->PlayerInput;
|
||
|
|
if (!ViewportClient || !Input || Input->IsPressed(EKeys::LeftMouseButton) || Input->IsPressed(EKeys::RightMouseButton)
|
||
|
|
|| Input->IsPressed(EKeys::MiddleMouseButton))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// VAL B2p-J D2 : verrou aussi quand la capture n'a lieu qu'a l'appui mais cache le curseur (haute precision a l'appui,
|
||
|
|
// SceneViewport.cpp:756-783), etat vu au journal apres le premier relacher.
|
||
|
|
const EMouseCaptureMode Capture = ViewportClient->GetMouseCaptureMode();
|
||
|
|
const bool bPermanent = Capture == EMouseCaptureMode::CapturePermanently || Capture == EMouseCaptureMode::CapturePermanently_IncludingInitialMouseDown;
|
||
|
|
const bool bHiddenOnPress = ViewportClient->HideCursorDuringCapture();
|
||
|
|
if (!bPermanent && !bHiddenOnPress)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
bMouseFreed = true;
|
||
|
|
++MouseFreeCount;
|
||
|
|
FInputModeGameAndUI FreeMouse;
|
||
|
|
FreeMouse.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
|
||
|
|
FreeMouse.SetHideCursorDuringCapture(false);
|
||
|
|
PC->SetInputMode(FreeMouse);
|
||
|
|
if (MouseFreeCooldown <= 0.f)
|
||
|
|
{
|
||
|
|
MouseFreeCooldown = 2.f;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : souris liberee pour le glisser (le PC l'avait verrouillee, capture %d, curseur cache a l'appui %d ; %d fois dans cette scene)"),
|
||
|
|
static_cast<int32>(Capture), bHiddenOnPress ? 1 : 0, MouseFreeCount);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::BeginSexScene()
|
||
|
|
{
|
||
|
|
MappingsAtSexStart = LPCameraModeLocal::MappingSignature();
|
||
|
|
bMouseFreed = false;
|
||
|
|
MouseFreeCount = 0;
|
||
|
|
MouseFreeCooldown = 0.f;
|
||
|
|
// VAL B2p-J D3/D4 : etat de la souris d'avant la scene, rendu tel quel a la fin (et non un GameOnly impose).
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
const UGameViewportClient* ViewportClient = GI ? GI->GetGameViewportClient() : nullptr;
|
||
|
|
bSavedMouseState = ViewportClient != nullptr;
|
||
|
|
if (ViewportClient)
|
||
|
|
{
|
||
|
|
SavedCaptureMode = static_cast<uint8>(ViewportClient->GetMouseCaptureMode());
|
||
|
|
SavedLockMode = static_cast<uint8>(ViewportClient->GetMouseLockMode());
|
||
|
|
bSavedHideCursor = ViewportClient->HideCursorDuringCapture();
|
||
|
|
}
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : debut de scene, touches %s ; souris capture %d verrou %d curseur cache a l'appui %d"),
|
||
|
|
*MappingsAtSexStart, static_cast<int32>(SavedCaptureMode), static_cast<int32>(SavedLockMode), bSavedHideCursor ? 1 : 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::EndSexScene()
|
||
|
|
{
|
||
|
|
// Chaque etat que CAM a pu changer pendant la scene est rendu ; une ligne de temoin le confirme.
|
||
|
|
ReleaseCoupleGrab();
|
||
|
|
bMiddleWasDown = false;
|
||
|
|
bHelpBoxOpen = false;
|
||
|
|
if (HelpRoot.IsValid())
|
||
|
|
{
|
||
|
|
HelpRoot->SetVisibility(EVisibility::Collapsed);
|
||
|
|
}
|
||
|
|
if (UButton* HelpIcon = HelpButton.Get())
|
||
|
|
{
|
||
|
|
HelpIcon->SetVisibility(ESlateVisibility::Collapsed);
|
||
|
|
}
|
||
|
|
// D3 (VAL B2p-I) : les 4 lignes retrouvent leur opacite.
|
||
|
|
FString LinesText = TEXT("non touche");
|
||
|
|
if (UTextBlock* Lines = FadedHelpLines.Get())
|
||
|
|
{
|
||
|
|
Lines->SetRenderOpacity(FadedLinesOpacity);
|
||
|
|
LinesText = FString::Printf(TEXT("opacite %.2f rendue"), FadedLinesOpacity);
|
||
|
|
}
|
||
|
|
FadedHelpLines.Reset();
|
||
|
|
UGameInstance* GI = GetGameInstance();
|
||
|
|
const UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
const bool bButtonHeld = PC && PC->PlayerInput && (PC->PlayerInput->IsPressed(EKeys::LeftMouseButton)
|
||
|
|
|| PC->PlayerInput->IsPressed(EKeys::RightMouseButton) || PC->PlayerInput->IsPressed(EKeys::MiddleMouseButton)
|
||
|
|
|| PC->PlayerInput->IsPressed(EKeys::LeftAlt) || PC->PlayerInput->IsPressed(EKeys::RightAlt));
|
||
|
|
// MoveCamera : CAM l'a mis a faux pendant les prises ; au repos il est faux (le PC le remet au relacher).
|
||
|
|
if (PC && !bButtonHeld && LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCMoveCamera))
|
||
|
|
{
|
||
|
|
LPCameraModeLocal::WriteBool(PC, LPCameraModeLocal::PCMoveCamera, false);
|
||
|
|
}
|
||
|
|
if (PC && LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCSexC3))
|
||
|
|
{
|
||
|
|
LPCameraModeLocal::WriteBool(PC, LPCameraModeLocal::PCSexC3, false);
|
||
|
|
}
|
||
|
|
// Souris : rendue a l'etat d'avant la scene (VAL B2p-J D3/D4) seulement si CAM l'avait liberee, que la vue est
|
||
|
|
// toujours dans l'etat pose par CAM et qu'aucun menu n'est a l'ecran (sinon le jeu a deja pose son propre mode).
|
||
|
|
FString InputText = TEXT("non touchee");
|
||
|
|
if (bMouseFreed && bSavedMouseState)
|
||
|
|
{
|
||
|
|
UGameViewportClient* ViewportClient = GI ? GI->GetGameViewportClient() : nullptr;
|
||
|
|
const bool bStillFreed = ViewportClient && ViewportClient->GetMouseCaptureMode() == EMouseCaptureMode::CaptureDuringMouseDown
|
||
|
|
&& ViewportClient->GetMouseLockMode() == EMouseLockMode::DoNotLock && !ViewportClient->HideCursorDuringCapture();
|
||
|
|
if (PC && bStillFreed && !LPCameraModeLocal::MenuOnScreen(GI))
|
||
|
|
{
|
||
|
|
const EMouseCaptureMode CaptureBefore = static_cast<EMouseCaptureMode>(SavedCaptureMode);
|
||
|
|
if (CaptureBefore == EMouseCaptureMode::CapturePermanently || CaptureBefore == EMouseCaptureMode::CapturePermanently_IncludingInitialMouseDown)
|
||
|
|
{
|
||
|
|
// Capture permanente : GameOnly (haute precision et verrou passent par les operations Slate de SetInputMode).
|
||
|
|
FInputModeGameOnly GameOnly;
|
||
|
|
GameOnly.SetConsumeCaptureMouseDown(CaptureBefore == EMouseCaptureMode::CapturePermanently);
|
||
|
|
PC->SetInputMode(GameOnly);
|
||
|
|
}
|
||
|
|
ViewportClient->SetMouseCaptureMode(CaptureBefore);
|
||
|
|
ViewportClient->SetMouseLockMode(static_cast<EMouseLockMode>(SavedLockMode));
|
||
|
|
ViewportClient->SetHideCursorDuringCapture(bSavedHideCursor);
|
||
|
|
InputText = FString::Printf(TEXT("rendue (capture %d verrou %d curseur cache %d)"),
|
||
|
|
static_cast<int32>(SavedCaptureMode), static_cast<int32>(SavedLockMode), bSavedHideCursor ? 1 : 0);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
InputText = bStillFreed ? TEXT("laissee au menu ouvert") : TEXT("deja reposee par le jeu");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bMouseFreed = false;
|
||
|
|
const FString MappingsNow = LPCameraModeLocal::MappingSignature();
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : fin de scene, etats rendus : prise=%d MoveCamera=%d SexC3=%d ; mode d'entree %s ; 4 lignes %s ; icone et aide repliees ; touches %s (%s)"),
|
||
|
|
CoupleGrabMode, PC && LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCMoveCamera) ? 1 : 0,
|
||
|
|
PC && LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCSexC3) ? 1 : 0, *InputText, *LinesText,
|
||
|
|
MappingsNow == MappingsAtSexStart ? TEXT("inchangees") : TEXT("CHANGEES"), *MappingsNow);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::ReleaseCoupleGrab()
|
||
|
|
{
|
||
|
|
CoupleGrabMode = 0;
|
||
|
|
GrabCamera.Reset();
|
||
|
|
bLeftWasDown = false;
|
||
|
|
bRightWasDown = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPCameraModeSubsystem::TickCoupleMouse(UGameInstance* GI, APlayerController* PC, AActor* View, const UObject* Room, const TArray<AActor*>& Couple, const FVector& Pivot)
|
||
|
|
{
|
||
|
|
UPlayerInput* Input = PC->PlayerInput;
|
||
|
|
const bool bLeft = Input && Input->IsPressed(EKeys::LeftMouseButton);
|
||
|
|
const bool bRight = Input && Input->IsPressed(EKeys::RightMouseButton);
|
||
|
|
const bool bLeftPressed = bLeft && !bLeftWasDown;
|
||
|
|
const bool bRightPressed = bRight && !bRightWasDown;
|
||
|
|
bLeftWasDown = bLeft;
|
||
|
|
bRightWasDown = bRight;
|
||
|
|
const UWorld* World = GI->GetWorld();
|
||
|
|
// Mode Scene seulement : la vue n'est pas une des personnes du couple (POV) et le PC n'est pas en POV.
|
||
|
|
const bool bScene = Couple.Num() > 0 && !Couple.Contains(View) && !LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCPOVOn);
|
||
|
|
if (!World || !bScene)
|
||
|
|
{
|
||
|
|
CoupleGrabMode = 0;
|
||
|
|
GrabCamera.Reset();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Traces : le couple, la camera en vue, CamRoom et le panneau Wall sont ignores.
|
||
|
|
FCollisionQueryParams Query(SCENE_QUERY_STAT(LPCoupleDrag), true);
|
||
|
|
Query.AddIgnoredActors(Couple);
|
||
|
|
Query.AddIgnoredActor(View);
|
||
|
|
if (AActor* Wall = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIWall)))
|
||
|
|
{
|
||
|
|
Query.AddIgnoredActor(Wall);
|
||
|
|
}
|
||
|
|
if (AActor* CamRoom = Cast<AActor>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomCamRoom)))
|
||
|
|
{
|
||
|
|
Query.AddIgnoredActor(CamRoom);
|
||
|
|
}
|
||
|
|
// Point de l'ecran vise (curseur de la prise, voir GrabCursor) : premier impact Visibility.
|
||
|
|
auto TraceScreen = [&](const FVector2D& Screen, FVector& Out) -> bool
|
||
|
|
{
|
||
|
|
FVector Origin;
|
||
|
|
FVector Direction;
|
||
|
|
FHitResult Hit;
|
||
|
|
if (!PC->DeprojectScreenPositionToWorld(static_cast<float>(Screen.X), static_cast<float>(Screen.Y), Origin, Direction)
|
||
|
|
|| !World->LineTraceSingleByChannel(Hit, Origin, Origin + Direction * LPCameraModeLocal::CursorTraceLength, ECC_Visibility, Query))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
Out = Hit.ImpactPoint;
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
// E2 (CAM-B2p-E) : le sol de Room.Box ne bloque pas toujours le trace (COMMUN-9 : surface trouvee 88 cm sous le
|
||
|
|
// plancher). Dans les limites XY du sol, la surface n'est jamais plus basse que le dessus du sol.
|
||
|
|
// G-e (CAM-B2p-H) : canal Pawn, celui que LPShowCouple met en Block sur les meubles pendant la scene (le canal
|
||
|
|
// Visibility ne voyait pas le lit, COMMUN-11). OutSurface : composant touche au-dessus du sol, nul pour le sol.
|
||
|
|
const UPrimitiveComponent* FloorComponent = Cast<UPrimitiveComponent>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomFloor));
|
||
|
|
const AActor* RoomActor = Cast<AActor>(Room);
|
||
|
|
const bool bFloorKnown = IsValid(FloorComponent) && !(RoomActor && RoomActor->IsHidden());
|
||
|
|
const FBox FloorBox = bFloorKnown ? FloorComponent->Bounds.GetBox() : FBox(ForceInit);
|
||
|
|
auto TraceDown = [&](const FVector& At, double& OutZ, const UPrimitiveComponent*& OutSurface) -> bool
|
||
|
|
{
|
||
|
|
FHitResult Hit;
|
||
|
|
const FVector Start(At.X, At.Y, Pivot.Z + LPCameraModeLocal::CoupleTraceUp);
|
||
|
|
const FVector End(At.X, At.Y, Pivot.Z - LPCameraModeLocal::CoupleTraceDown);
|
||
|
|
const bool bHit = World->LineTraceSingleByChannel(Hit, Start, End, ECC_Pawn, Query);
|
||
|
|
const bool bOverFloor = bFloorKnown && At.X >= FloorBox.Min.X && At.X <= FloorBox.Max.X && At.Y >= FloorBox.Min.Y && At.Y <= FloorBox.Max.Y;
|
||
|
|
if (!bHit && !bOverFloor)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
OutZ = bHit ? Hit.ImpactPoint.Z : FloorBox.Max.Z;
|
||
|
|
OutSurface = bHit ? Hit.GetComponent() : nullptr;
|
||
|
|
if (bOverFloor && OutZ <= FloorBox.Max.Z)
|
||
|
|
{
|
||
|
|
OutZ = FloorBox.Max.Z;
|
||
|
|
OutSurface = nullptr;
|
||
|
|
}
|
||
|
|
if (OutSurface == FloorComponent)
|
||
|
|
{
|
||
|
|
OutSurface = nullptr;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
float MouseX = 0.f;
|
||
|
|
float MouseY = 0.f;
|
||
|
|
const bool bMouse = PC->GetMousePosition(MouseX, MouseY);
|
||
|
|
|
||
|
|
if (CoupleGrabMode == 0)
|
||
|
|
{
|
||
|
|
// Maj tenu : la voie d'origine (Maj + bouton + souris, Room) garde la main, aucune prise.
|
||
|
|
const bool bShiftHeld = Input && (Input->IsPressed(EKeys::LeftShift) || Input->IsPressed(EKeys::RightShift));
|
||
|
|
if ((!bLeftPressed && !bRightPressed) || bShiftHeld)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (!bMouse)
|
||
|
|
{
|
||
|
|
if (!bCursorWarned)
|
||
|
|
{
|
||
|
|
bCursorWarned = true;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : curseur sans position, prise du couple indisponible (vue %s)"), *GetNameSafe(View));
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// Selection : curseur a moins de CoupleSelectRadius px du segment bassin-tete projete d'une des personnes.
|
||
|
|
const FVector MousePoint(MouseX, MouseY, 0.f);
|
||
|
|
bool bOnCouple = false;
|
||
|
|
double Nearest = -1.0;
|
||
|
|
for (const AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
const ACharacter* Character = Cast<ACharacter>(Person);
|
||
|
|
const USkeletalMeshComponent* Mesh = Character ? Character->GetMesh() : nullptr;
|
||
|
|
const FVector Low = Mesh ? Mesh->GetSocketLocation(LPCameraModeLocal::PelvisSocket) : Person->GetActorLocation();
|
||
|
|
const FVector High = Mesh ? Mesh->GetSocketLocation(LPCameraModeLocal::HeadSocket) : Person->GetActorLocation();
|
||
|
|
FVector2D LowScreen;
|
||
|
|
FVector2D HighScreen;
|
||
|
|
if (PC->ProjectWorldLocationToScreen(Low, LowScreen, false) && PC->ProjectWorldLocationToScreen(High, HighScreen, false))
|
||
|
|
{
|
||
|
|
const double ScreenGap = FMath::PointDistToSegment(MousePoint, FVector(LowScreen, 0.f), FVector(HighScreen, 0.f));
|
||
|
|
Nearest = Nearest < 0.0 ? ScreenGap : FMath::Min(Nearest, ScreenGap);
|
||
|
|
if (ScreenGap <= LPCameraModeLocal::CoupleSelectRadius)
|
||
|
|
{
|
||
|
|
bOnCouple = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!bOnCouple)
|
||
|
|
{
|
||
|
|
// COMMUN-12 : refus muet rendu visible (au plus une ligne toutes les 2 s) ; le clic reste a la camera.
|
||
|
|
const double Now = FPlatformTime::Seconds();
|
||
|
|
if (Now - SelectMissLogTime > 2.0)
|
||
|
|
{
|
||
|
|
SelectMissLogTime = Now;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : clic hors du couple, pas de prise (curseur (%.0f, %.0f), couple a %s px, seuil %.0f) ; la camera tourne"),
|
||
|
|
MouseX, MouseY, Nearest < 0.0 ? TEXT("?") : *FString::Printf(TEXT("%.0f"), Nearest), LPCameraModeLocal::CoupleSelectRadius);
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
CoupleGrabMode = bLeftPressed ? 1 : 2;
|
||
|
|
bGrabMoved = false;
|
||
|
|
LastSpotBlocker.Reset();
|
||
|
|
LastSurfaceRefused.Reset();
|
||
|
|
GrabMouseStart = FVector2D(MouseX, MouseY);
|
||
|
|
GrabCursor = GrabMouseStart;
|
||
|
|
GrabLastReal = GrabMouseStart;
|
||
|
|
GrabRawFrames = 0;
|
||
|
|
bGrabAimWarned = false;
|
||
|
|
GrabPivotAfterCam = Pivot;
|
||
|
|
GrabLastMouseX = MouseX;
|
||
|
|
GrabTurned = 0.f;
|
||
|
|
GrabCamera = View;
|
||
|
|
GrabCameraLocation = View->GetActorLocation();
|
||
|
|
GrabCameraRotation = View->GetActorRotation();
|
||
|
|
GrabLeadStart = Couple[0]->GetActorLocation();
|
||
|
|
// G-e : ecart reel entre la racine du maillage la plus basse et la surface sous le couple (journal seulement).
|
||
|
|
double SurfaceZ = 0.0;
|
||
|
|
const UPrimitiveComponent* GrabSurface = nullptr;
|
||
|
|
bGrabHasHeight = TraceDown(Pivot, SurfaceZ, GrabSurface);
|
||
|
|
GrabHeight = bGrabHasHeight ? LPCameraModeLocal::CoupleLowestRoot(Couple) - SurfaceZ : 0.0;
|
||
|
|
FVector Aim;
|
||
|
|
GrabOffset = TraceScreen(GrabMouseStart, Aim) ? FVector(Pivot.X - Aim.X, Pivot.Y - Aim.Y, 0.0) : FVector::ZeroVector;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : couple pris (%s) a (%.0f, %.0f, %.0f), racine a %s de la surface %s"),
|
||
|
|
CoupleGrabMode == 1 ? TEXT("deplacement") : TEXT("rotation"), Pivot.X, Pivot.Y, Pivot.Z,
|
||
|
|
bGrabHasHeight ? *FString::Printf(TEXT("%+.0f cm"), GrabHeight) : TEXT("? cm"),
|
||
|
|
!bGrabHasHeight ? TEXT("inconnue") : GrabSurface ? *LPCameraModeLocal::DescribeComponent(GrabSurface) : TEXT("sol"));
|
||
|
|
}
|
||
|
|
|
||
|
|
const bool bHeld = CoupleGrabMode == 1 ? bLeft : bRight;
|
||
|
|
if (!bHeld)
|
||
|
|
{
|
||
|
|
const FRotator Rotation = Couple[0]->GetActorRotation();
|
||
|
|
const double CursorTravel = FVector2D::Distance(GrabMouseStart, GrabCursor);
|
||
|
|
if (CoupleGrabMode == 1)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : couple pose a (%.0f, %.0f, %.0f), lacet %.0f"), Pivot.X, Pivot.Y, Pivot.Z, Rotation.Yaw);
|
||
|
|
if (!bGrabMoved)
|
||
|
|
{
|
||
|
|
// COMMUN-12 : glisser sans effet rendu visible (curseur reste sous le seuil, ou souris verrouillee).
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : glisser sans effet, curseur deplace de %.0f px (seuil %.0f), deltas bruts sur %d images, curseur %s"),
|
||
|
|
CursorTravel, LPCameraModeLocal::CoupleDragThreshold, GrabRawFrames, bMouse ? TEXT("avec position") : TEXT("sans position"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : couple tourne de %+.0f degres, lacet %.0f"), GrabTurned, Rotation.Yaw);
|
||
|
|
if (FMath::Abs(GrabTurned) < 0.5f)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : rotation sans effet, curseur deplace de %.0f px, deltas bruts sur %d images, curseur %s"),
|
||
|
|
CursorTravel, GrabRawFrames, bMouse ? TEXT("avec position") : TEXT("sans position"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// G3 (STE) : une seule fois par pose, si le meneur a ete deplace par la prise (glisser, ou rotation autour d'un pivot
|
||
|
|
// hors du meneur) : le resolveur de LPShowCouple reprend sa place sure ici au lieu de balayer depuis l'ancienne.
|
||
|
|
const double LeadMoved = FVector::Dist(Couple[0]->GetActorLocation(), GrabLeadStart);
|
||
|
|
if (LeadMoved > 1.0)
|
||
|
|
{
|
||
|
|
ULPShowCoupleSubsystem* ShowCouple = GI->GetSubsystem<ULPShowCoupleSubsystem>();
|
||
|
|
// Temoin cote appelant (une ligne par appel) : KeepTogether remet deja LastSafe a chaque image, la ligne
|
||
|
|
// « place reprise » de STE n'apparait donc que si la place a change depuis son dernier passage.
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : G3 NotifyCoupleTeleported %s (meneur deplace de %.0f cm par la prise)"),
|
||
|
|
ShowCouple ? TEXT("appele") : TEXT("non appele, sous-systeme absent"), LeadMoved);
|
||
|
|
if (ShowCouple)
|
||
|
|
{
|
||
|
|
ShowCouple->NotifyCoupleTeleported();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bGrabJustEnded = true;
|
||
|
|
CoupleGrabMode = 0;
|
||
|
|
GrabCamera.Reset();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// COMMUN-12 : un deplacement du couple pendant la prise qui ne vient pas de CAM (molette de Room, LPShowCouple, jeu)
|
||
|
|
// est ecrit (au plus une ligne par seconde) ; il etait muet (montee de +22 cm par prise a 12:38).
|
||
|
|
if (FVector::Dist(Pivot, GrabPivotAfterCam) > 1.0)
|
||
|
|
{
|
||
|
|
const double Now = FPlatformTime::Seconds();
|
||
|
|
if (Now - ExternalMoveLogTime > 1.0)
|
||
|
|
{
|
||
|
|
ExternalMoveLogTime = Now;
|
||
|
|
const FVector Outside = Pivot - GrabPivotAfterCam;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : couple deplace pendant la prise hors CAM de (%+.0f, %+.0f, %+.0f) ; molette=%+.1f Maj=%d"),
|
||
|
|
Outside.X, Outside.Y, Outside.Z, LPCameraModeLocal::AxisValue(PC, LPCameraModeLocal::AxisScroll),
|
||
|
|
Input && (Input->IsPressed(EKeys::LeftShift) || Input->IsPressed(EKeys::RightShift)) ? 1 : 0);
|
||
|
|
}
|
||
|
|
GrabPivotAfterCam = Pivot;
|
||
|
|
}
|
||
|
|
|
||
|
|
// COMMUN-12 : curseur de la prise. Position reelle quand elle bouge ; sinon (souris verrouillee par le PC, curseur
|
||
|
|
// fige) deltas bruts de la souris : glisser et rotation ne dependent plus du mode d'entree.
|
||
|
|
const FVector2D RealCursor(MouseX, MouseY);
|
||
|
|
// Valeur brute (VAL B2p-J D1) : GetKeyValue applique la sensibilite du projet (0,07), deltas 14 fois trop petits.
|
||
|
|
const FVector2D RawDelta = Input ? FVector2D(Input->GetRawKeyValue(EKeys::MouseX), -Input->GetRawKeyValue(EKeys::MouseY)) : FVector2D::ZeroVector;
|
||
|
|
if (bMouse && !RealCursor.Equals(GrabLastReal, 0.5))
|
||
|
|
{
|
||
|
|
GrabCursor = RealCursor;
|
||
|
|
}
|
||
|
|
else if (!RawDelta.IsNearlyZero())
|
||
|
|
{
|
||
|
|
GrabCursor += RawDelta;
|
||
|
|
++GrabRawFrames;
|
||
|
|
}
|
||
|
|
if (bMouse)
|
||
|
|
{
|
||
|
|
GrabLastReal = RealCursor;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (CoupleGrabMode == 1)
|
||
|
|
{
|
||
|
|
bGrabMoved |= FVector2D::Distance(GrabMouseStart, GrabCursor) >= LPCameraModeLocal::CoupleDragThreshold;
|
||
|
|
FVector Aim;
|
||
|
|
const bool bAim = bGrabMoved && TraceScreen(GrabCursor, Aim);
|
||
|
|
if (bGrabMoved && !bAim && !bGrabAimWarned)
|
||
|
|
{
|
||
|
|
bGrabAimWarned = true;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : glisser, le curseur (%.0f, %.0f) ne vise aucune surface"), GrabCursor.X, GrabCursor.Y);
|
||
|
|
}
|
||
|
|
if (bAim)
|
||
|
|
{
|
||
|
|
FVector Target(Aim.X + GrabOffset.X, Aim.Y + GrabOffset.Y, Pivot.Z);
|
||
|
|
// F3 : meme interieur que E1 (murs reels, marge = demi-boite du couple).
|
||
|
|
LPCameraModeLocal::FRoomInterior Interior;
|
||
|
|
if (bFloorKnown && LPCameraModeLocal::ComputeRoomInterior(RoomActor, Interior))
|
||
|
|
{
|
||
|
|
const FBox2D& Area = Interior.Area;
|
||
|
|
const double Margin = LPCameraModeLocal::CoupleWallMargin;
|
||
|
|
if (Area.Max.X - Area.Min.X > 2.0 * Margin && Area.Max.Y - Area.Min.Y > 2.0 * Margin)
|
||
|
|
{
|
||
|
|
Target.X = FMath::Clamp(Target.X, Area.Min.X + Margin, Area.Max.X - Margin);
|
||
|
|
Target.Y = FMath::Clamp(Target.Y, Area.Min.Y + Margin, Area.Max.Y - Margin);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// G-e : la racine du maillage la plus basse est posee sur la surface sous la nouvelle place (dessus du lit ou
|
||
|
|
// sol), plus 3 cm ; elle ne depend plus de l'ecart pris a la selection (un couple qui flottait retombe).
|
||
|
|
double SurfaceZ = 0.0;
|
||
|
|
const UPrimitiveComponent* Surface = nullptr;
|
||
|
|
const bool bSurface = TraceDown(Target, SurfaceZ, Surface);
|
||
|
|
if (bSurface)
|
||
|
|
{
|
||
|
|
Target.Z = Pivot.Z + (SurfaceZ + LPCameraModeLocal::CoupleRootAboveSurface - LPCameraModeLocal::CoupleLowestRoot(Couple));
|
||
|
|
}
|
||
|
|
// G1 : place libre seulement (boite du couple sans meuble ni mur, lits admis). Sinon le couple reste a la
|
||
|
|
// derniere place libre ; une prise qui part d'une place encombree peut aller vers toute place libre.
|
||
|
|
const ACharacter* LeadCharacter = Cast<ACharacter>(Couple[0]);
|
||
|
|
const USkeletalMeshComponent* LeadMesh = LeadCharacter ? LeadCharacter->GetMesh() : nullptr;
|
||
|
|
const double RootOffset = LeadMesh ? LeadMesh->GetComponentLocation().Z - Couple[0]->GetActorLocation().Z : 0.0;
|
||
|
|
FString Blocker;
|
||
|
|
if (bFloorKnown && bSurface && Surface && !LPCameraModeLocal::IsAdmittedBed(Surface))
|
||
|
|
{
|
||
|
|
// G-f (CAM-B2p-H) : dans une piece, seuls le sol et les lits portent le couple (commode, bureau, armoire,
|
||
|
|
// personne : refuses). Le couple reste a la derniere place admise ; une ligne par changement.
|
||
|
|
const FString Refused = LPCameraModeLocal::DescribeComponent(Surface);
|
||
|
|
if (Refused != LastSurfaceRefused)
|
||
|
|
{
|
||
|
|
LastSurfaceRefused = Refused;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : surface refusee %s"), *Refused);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (LPCameraModeLocal::CoupleSpotFree(World, RoomActor, bFloorKnown ? FloorComponent : nullptr, Couple,
|
||
|
|
FVector(Target.X, Target.Y, Target.Z + RootOffset), Couple[0]->GetActorRotation().Yaw, Blocker))
|
||
|
|
{
|
||
|
|
const FVector Delta = Target - Pivot;
|
||
|
|
for (AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
Person->SetActorLocation(Person->GetActorLocation() + Delta);
|
||
|
|
}
|
||
|
|
GrabPivotAfterCam = Pivot + Delta;
|
||
|
|
// VAL B2p-H N2 : place admise, un nouveau passage sur la meme surface refusee ecrit de nouveau sa ligne.
|
||
|
|
LastSurfaceRefused.Reset();
|
||
|
|
}
|
||
|
|
else if (Blocker != LastSpotBlocker)
|
||
|
|
{
|
||
|
|
LastSpotBlocker = Blocker;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Sexe : place refusee, recouvre %s"), *Blocker);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (CoupleGrabMode == 2)
|
||
|
|
{
|
||
|
|
const float Turn = static_cast<float>(GrabCursor.X - GrabLastMouseX) * LPCameraModeLocal::CoupleTurnPerPixel;
|
||
|
|
GrabLastMouseX = static_cast<float>(GrabCursor.X);
|
||
|
|
if (!FMath::IsNearlyZero(Turn))
|
||
|
|
{
|
||
|
|
GrabTurned += Turn;
|
||
|
|
for (AActor* Person : Couple)
|
||
|
|
{
|
||
|
|
const FVector Offset = (Person->GetActorLocation() - Pivot).RotateAngleAxis(Turn, FVector::UpVector);
|
||
|
|
FRotator Rotation = Person->GetActorRotation();
|
||
|
|
Rotation.Yaw += Turn;
|
||
|
|
Person->SetActorLocationAndRotation(Pivot + Offset, Rotation);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Camera figee pendant la prise : le meme clic lance StartMoveCamera (orbite), la molette passe a Room (zoom).
|
||
|
|
if (AActor* Camera = GrabCamera.Get())
|
||
|
|
{
|
||
|
|
Camera->SetActorLocationAndRotation(GrabCameraLocation, GrabCameraRotation);
|
||
|
|
}
|
||
|
|
LPCameraModeLocal::WriteBool(PC, LPCameraModeLocal::PCMoveCamera, false);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::LogSwitchKey(const APlayerController* PC) const
|
||
|
|
{
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
const UObject* RoomObject = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
|
||
|
|
const UObject* Room = IsValid(RoomObject) ? RoomObject : nullptr;
|
||
|
|
const AActor* View = PC ? PC->GetViewTarget() : nullptr;
|
||
|
|
UGameViewportClient* ViewportClient = GI ? GI->GetGameViewportClient() : nullptr; // IgnoreInput() n'est pas const
|
||
|
|
auto Flag = [](const UObject* Container, FName Name) { return LPCameraModeLocal::ReadBool(Container, Name) ? 1 : 0; };
|
||
|
|
// CAM-B2r : drapeaux qui coupent la souris (boutons ignores si JournalOn, ContactsOn ou MainMenuOn) et MoveCamera du PC.
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Touche C : BuilderOn=%d SpaceOn=%d SexOn=%d CamC1On=%d CamC3On=%d C1Hidden=%d ; entree PC=%d regard ignore=%d viewport ignore=%d ; vue %s (%s) ; Room %s ; PC %s ; indicateur '%s' ; MainMenuOn=%d JournalOn=%d ContactsOn=%d CharGenOn=%d MoveCamera=%d"),
|
||
|
|
Flag(GI, LPCameraModeLocal::GIBuilderOn), Flag(GI, LPCameraModeLocal::GISpaceOn), Flag(GI, LPCameraModeLocal::GISexOn),
|
||
|
|
Flag(Room, LPCameraModeLocal::RoomCamC1On), Flag(Room, LPCameraModeLocal::RoomCamC3On), Flag(Room, LPCameraModeLocal::RoomC1Hidden),
|
||
|
|
PC && PC->InputEnabled() ? 1 : 0, PC && PC->IsLookInputIgnored() ? 1 : 0, ViewportClient && ViewportClient->IgnoreInput() ? 1 : 0,
|
||
|
|
*GetNameSafe(View), View ? *View->GetClass()->GetName() : TEXT("-"), *GetNameSafe(Room), *GetNameSafe(PC), *ShownLabel,
|
||
|
|
Flag(GI, LPCameraModeLocal::GIMainMenuOn), Flag(GI, LPCameraModeLocal::GIJournalOn), Flag(GI, LPCameraModeLocal::GIContactsOn),
|
||
|
|
Flag(GI, LPCameraModeLocal::GICharGenOn), Flag(PC, LPCameraModeLocal::PCMoveCamera));
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::WitnessOcclusion(float DeltaTime)
|
||
|
|
{
|
||
|
|
OcclusionCooldown = FMath::Max(0.f, OcclusionCooldown - DeltaTime);
|
||
|
|
if (OcclusionCooldown > 0.f)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
OcclusionCooldown = LPCameraModeLocal::OcclusionInterval;
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
AActor* Room = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom));
|
||
|
|
ACharacter* Player = Cast<ACharacter>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter));
|
||
|
|
AActor* Cam = IsValid(Room) ? Cast<AActor>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomCamRoom)) : nullptr;
|
||
|
|
const USkeletalMeshComponent* Mesh = IsValid(Player) ? Player->GetMesh() : nullptr;
|
||
|
|
if (!World || !IsValid(Cam) || !Mesh)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const FVector Head = Mesh->GetSocketLocation(LPCameraModeLocal::HeadSocket);
|
||
|
|
const FVector CamLoc = Cam->GetActorLocation();
|
||
|
|
// Trace complexe (polygones, pas les boites englobantes), Room compris : les meubles comptent, le panneau Wall non.
|
||
|
|
FCollisionQueryParams Query(SCENE_QUERY_STAT(LPCameraOcclusion), true);
|
||
|
|
Query.AddIgnoredActor(Player);
|
||
|
|
Query.AddIgnoredActor(Cam);
|
||
|
|
if (AActor* Wall = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIWall)))
|
||
|
|
{
|
||
|
|
Query.AddIgnoredActor(Wall);
|
||
|
|
}
|
||
|
|
FHitResult Hit;
|
||
|
|
if (!World->LineTraceSingleByChannel(Hit, CamLoc, Head, ECC_Visibility, Query))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const UPrimitiveComponent* Comp = Hit.GetComponent();
|
||
|
|
const UStaticMeshComponent* MeshComp = Cast<UStaticMeshComponent>(Comp);
|
||
|
|
OcclusionCooldown = LPCameraModeLocal::OcclusionLogInterval;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Temoin C2 : joueur masque par %s (%s) de %s, visible=%d, a %.0f cm de la camera (camera a %.0f cm de la tete)"),
|
||
|
|
*GetNameSafe(Comp), MeshComp ? *GetNameSafe(MeshComp->GetStaticMesh()) : TEXT("-"), *GetNameSafe(Hit.GetActor()),
|
||
|
|
Comp && Comp->IsVisible() ? 1 : 0, Hit.Distance, FVector::Dist(CamLoc, Head));
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::WitnessC2(float DeltaTime)
|
||
|
|
{
|
||
|
|
WitnessCooldown = FMath::Max(0.f, WitnessCooldown - DeltaTime);
|
||
|
|
if (WitnessCooldown > 0.f)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
AActor* Room = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom));
|
||
|
|
ACharacter* Player = Cast<ACharacter>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter));
|
||
|
|
AActor* Cam = IsValid(Room) ? Cast<AActor>(LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomCamRoom)) : nullptr;
|
||
|
|
const USkeletalMeshComponent* Mesh = IsValid(Player) ? Player->GetMesh() : nullptr;
|
||
|
|
if (!World || !IsValid(Cam) || !Mesh)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const FVector Head = Mesh->GetSocketLocation(LPCameraModeLocal::HeadSocket);
|
||
|
|
const FVector CamLoc = Cam->GetActorLocation();
|
||
|
|
const float Distance = FVector::Dist(Head, CamLoc);
|
||
|
|
if (Distance >= LPCameraModeLocal::WitnessNearDistance)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
WitnessCooldown = LPCameraModeLocal::WitnessInterval;
|
||
|
|
|
||
|
|
// Balayage Camera a plat, de la tete vers la camera (ou vers le dos si elle est au-dessus de la tete).
|
||
|
|
FVector Dir = CamLoc - Head;
|
||
|
|
Dir.Z = 0.f;
|
||
|
|
if (!Dir.Normalize())
|
||
|
|
{
|
||
|
|
Dir = -Player->GetActorForwardVector();
|
||
|
|
}
|
||
|
|
FCollisionQueryParams Query(SCENE_QUERY_STAT(LPCameraWitness), false);
|
||
|
|
Query.AddIgnoredActor(Room);
|
||
|
|
Query.AddIgnoredActor(Player);
|
||
|
|
Query.AddIgnoredActor(Cam);
|
||
|
|
// Meme liste que MakeArray_5 de Room apres B2g.
|
||
|
|
if (AActor* Wall = Cast<AActor>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIWall)))
|
||
|
|
{
|
||
|
|
Query.AddIgnoredActor(Wall);
|
||
|
|
}
|
||
|
|
// Canal Camera (sonde de Room jusqu'a B2e) et Visibility (sonde de Room depuis B2f).
|
||
|
|
auto Probe = [&](ECollisionChannel Channel, const FVector& Direction) -> FString
|
||
|
|
{
|
||
|
|
FHitResult Hit;
|
||
|
|
if (!World->SweepSingleByChannel(Hit, Head, Head + Direction * LPCameraModeLocal::WitnessProbeLength, FQuat::Identity, Channel,
|
||
|
|
FCollisionShape::MakeSphere(LPCameraModeLocal::WitnessProbeRadius), Query))
|
||
|
|
{
|
||
|
|
return TEXT("rien");
|
||
|
|
}
|
||
|
|
const AActor* Owner = Hit.GetActor();
|
||
|
|
return FString::Printf(TEXT("%s de %s (cache=%d) a %.0f cm%s"), *GetNameSafe(Hit.GetComponent()), *GetNameSafe(Owner),
|
||
|
|
Owner && Owner->IsHidden() ? 1 : 0, Hit.Distance, Hit.bStartPenetrating ? TEXT(", depart deja dedans") : TEXT(""));
|
||
|
|
};
|
||
|
|
const FString CameraText = Probe(ECC_Camera, Dir);
|
||
|
|
const FString VisibilityText = Probe(ECC_Visibility, Dir);
|
||
|
|
// Axe de la sonde 459 de Room : a plat, derriere la rotation de controle du PC.
|
||
|
|
const APlayerController* PC = World->GetFirstPlayerController();
|
||
|
|
const FVector Back = PC ? -FRotationMatrix(FRotator(0.f, PC->GetControlRotation().Yaw, 0.f)).GetUnitAxis(EAxis::X) : -Player->GetActorForwardVector();
|
||
|
|
const FString BackText = Probe(ECC_Visibility, Back);
|
||
|
|
FVector Target;
|
||
|
|
const FString TargetText = LPCameraModeLocal::ReadVector(Room, LPCameraModeLocal::RoomC2Target, Target)
|
||
|
|
? FString::Printf(TEXT("%.0f cm"), FVector::Dist(Head, Target)) : FString(TEXT("illisible"));
|
||
|
|
const FVector Offset = Head - Player->GetActorLocation();
|
||
|
|
// Marge de la camera au bord le plus proche de la boite C2 (X et Y), pour separer boite et sonde.
|
||
|
|
FString BoxText(TEXT("illisible"));
|
||
|
|
FVector BoxMin;
|
||
|
|
FVector BoxMax;
|
||
|
|
if (LPCameraModeLocal::ReadVector(Room, LPCameraModeLocal::RoomC2BoxMin, BoxMin) && LPCameraModeLocal::ReadVector(Room, LPCameraModeLocal::RoomC2BoxMax, BoxMax))
|
||
|
|
{
|
||
|
|
const double Margin = FMath::Min(FMath::Min(CamLoc.X - BoxMin.X, BoxMax.X - CamLoc.X), FMath::Min(CamLoc.Y - BoxMin.Y, BoxMax.Y - CamLoc.Y));
|
||
|
|
BoxText = FString::Printf(TEXT("marge %.0f cm (largeur X %.0f, Y %.0f)"), Margin, BoxMax.X - BoxMin.X, BoxMax.Y - BoxMin.Y);
|
||
|
|
}
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Temoin C2 : camera a %.0f cm de la tete (hauteur %+.0f) ; cible C2 a %s ; tete (%.0f, %.0f), decalage capsule %.0f cm ; vers la camera, Camera : %s, Visibility : %s ; vers l'arriere, Visibility : %s ; boite C2 : %s"),
|
||
|
|
Distance, CamLoc.Z - Head.Z, *TargetText, Head.X, Head.Y, Offset.Size2D(), *CameraText, *VisibilityText, *BackText, *BoxText);
|
||
|
|
}
|
||
|
|
|
||
|
|
FString ULPCameraModeSubsystem::ComputeLabel() const
|
||
|
|
{
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
const UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
if (!PC)
|
||
|
|
{
|
||
|
|
PC = Cast<APlayerController>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPC));
|
||
|
|
}
|
||
|
|
const AActor* View = IsValid(PC) ? PC->GetViewTarget() : nullptr;
|
||
|
|
if (!IsValid(View))
|
||
|
|
{
|
||
|
|
return FString();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ordre du paragraphe 2 de la SPEC ; chaque comparaison exige une reference non nulle.
|
||
|
|
const UObject* Birdview = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIBirdview);
|
||
|
|
if ((Birdview && View == Birdview) || View->GetClass()->GetName().Contains(TEXT("Birdview")))
|
||
|
|
{
|
||
|
|
return TEXT("Camera: Overview");
|
||
|
|
}
|
||
|
|
if (LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISexOn))
|
||
|
|
{
|
||
|
|
if (LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCPOVOn))
|
||
|
|
{
|
||
|
|
return TEXT("Camera: POV");
|
||
|
|
}
|
||
|
|
return LPCameraModeLocal::ReadBool(PC, LPCameraModeLocal::PCSexC3) ? TEXT("Camera: C3 Couple") : TEXT("Camera: Scene");
|
||
|
|
}
|
||
|
|
|
||
|
|
const UObject* RoomObject = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIRoom);
|
||
|
|
const UObject* Room = IsValid(RoomObject) ? RoomObject : nullptr;
|
||
|
|
const UObject* PlayerCharacter = LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GIPlayerCharacter);
|
||
|
|
if (PlayerCharacter && View == PlayerCharacter && LPCameraModeLocal::ReadBool(Room, LPCameraModeLocal::RoomCamC1On))
|
||
|
|
{
|
||
|
|
return TEXT("Camera: C1 First person");
|
||
|
|
}
|
||
|
|
const bool bC3 = LPCameraModeLocal::ReadBool(Room, LPCameraModeLocal::RoomCamC3On);
|
||
|
|
const UObject* CamRoom = LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomCamRoom);
|
||
|
|
if (CamRoom && View == CamRoom)
|
||
|
|
{
|
||
|
|
return bC3 ? TEXT("Camera: C3 Ceiling") : TEXT("Camera: C2 Follow");
|
||
|
|
}
|
||
|
|
// SceneCamera de Room est ChargenCameras[0] (CAM-AUDIT § 1) : le constructeur et l'ancienne vue C3 se
|
||
|
|
// distinguent du studio par BuilderOn et CamC3On, testes avant le tableau des cameras du studio.
|
||
|
|
const UObject* SceneCamera = LPCameraModeLocal::ReadObject(Room, LPCameraModeLocal::RoomSceneCamera);
|
||
|
|
const bool bSceneCamera = SceneCamera && View == SceneCamera;
|
||
|
|
if (bSceneCamera && LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GIBuilderOn))
|
||
|
|
{
|
||
|
|
return TEXT("Camera: Builder");
|
||
|
|
}
|
||
|
|
if (bSceneCamera && bC3)
|
||
|
|
{
|
||
|
|
return TEXT("Camera: C3 Room (old)");
|
||
|
|
}
|
||
|
|
if (LPCameraModeLocal::ArrayContains(GI, LPCameraModeLocal::GIChargenCameras, View))
|
||
|
|
{
|
||
|
|
return TEXT("Camera: Studio");
|
||
|
|
}
|
||
|
|
return FString();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPCameraModeSubsystem::EnsureWidget()
|
||
|
|
{
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
UGameViewportClient* ViewportClient = GI ? GI->GetGameViewportClient() : nullptr;
|
||
|
|
if (!ViewportClient || !FSlateApplication::IsInitialized())
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (Root.IsValid() && Viewport.Get() == ViewportClient)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// Premier ajout, ou viewport remplace depuis : retirer de l'ancien s'il existe encore, puis ajouter.
|
||
|
|
RemoveWidget();
|
||
|
|
TSharedPtr<STextBlock> NewText;
|
||
|
|
TSharedRef<SWidget> NewRoot = SNew(SOverlay)
|
||
|
|
.Visibility(EVisibility::Collapsed)
|
||
|
|
+ SOverlay::Slot().HAlign(HAlign_Left).VAlign(VAlign_Bottom).Padding(FMargin(16.f))
|
||
|
|
[
|
||
|
|
SAssignNew(NewText, STextBlock)
|
||
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 12))
|
||
|
|
.ColorAndOpacity(FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.7f)))
|
||
|
|
.ShadowOffset(FVector2D(1.f, 1.f))
|
||
|
|
.ShadowColorAndOpacity(FLinearColor(0.f, 0.f, 0.f, 0.7f))
|
||
|
|
];
|
||
|
|
ViewportClient->AddViewportWidgetContent(NewRoot, LPCameraModeLocal::ViewportZOrder);
|
||
|
|
Root = NewRoot;
|
||
|
|
Text = NewText;
|
||
|
|
Viewport = ViewportClient;
|
||
|
|
ShownLabel.Reset();
|
||
|
|
bShown = false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::RemoveWidget()
|
||
|
|
{
|
||
|
|
if (Root.IsValid())
|
||
|
|
{
|
||
|
|
if (UGameViewportClient* ViewportClient = Viewport.Get())
|
||
|
|
{
|
||
|
|
ViewportClient->RemoveViewportWidgetContent(Root.ToSharedRef());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Root.Reset();
|
||
|
|
Text.Reset();
|
||
|
|
Viewport.Reset();
|
||
|
|
ShownLabel.Reset();
|
||
|
|
bShown = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::SetShown(bool bShow)
|
||
|
|
{
|
||
|
|
if (!Root.IsValid() || bShown == bShow)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Racine non cliquable quand elle est visible ; repliee sinon.
|
||
|
|
Root->SetVisibility(bShow ? EVisibility::HitTestInvisible : EVisibility::Collapsed);
|
||
|
|
bShown = bShow;
|
||
|
|
}
|
||
|
|
|
||
|
|
FString ULPCameraModeSubsystem::BuildControlsHelp() const
|
||
|
|
{
|
||
|
|
// Controles de B2p-C a B2p-I (CAM-B2p-I-SPEC § 1.6). Molette : CAM ne la lit pas (bSexWheelZoom faux), mais B2p-R
|
||
|
|
// (Room, O1 : MoveObject OU SexOn) la donne au deplacement du couple pendant le sexe (COMMUN-12, 12:37:57 :
|
||
|
|
// molette=+1.0, couple +9 cm) : elle est citee. Panneau : Alt ou clic molette brefs (COMMUN-12 : Espace rendu au jeu).
|
||
|
|
using LPCameraModeLocal::MappedKeys;
|
||
|
|
return FString::Printf(TEXT(
|
||
|
|
"CAMERA\n"
|
||
|
|
" %s: next view (Scene > C3 couple close-up > each partner's eyes)\n"
|
||
|
|
" %s / %s: move closer / away %s / %s: orbit around the couple (eyes view: look around)\n"
|
||
|
|
" Hold a mouse button away from the couple and move the mouse: rotate the camera\n"
|
||
|
|
"COUPLE\n"
|
||
|
|
" Left-drag the couple: move it (floor or bed only)\n"
|
||
|
|
" Right-drag the couple: turn it\n"
|
||
|
|
" %s, %s: move the couple Mouse wheel: raise / lower the couple\n"
|
||
|
|
"ACTIONS\n"
|
||
|
|
" Alt or middle click (short press): show / hide this panel"),
|
||
|
|
*MappedKeys(LPCameraModeLocal::SwitchCameraAction, 0),
|
||
|
|
*MappedKeys(LPCameraModeLocal::AxisMoveForward, 1), *MappedKeys(LPCameraModeLocal::AxisMoveForward, -1),
|
||
|
|
*MappedKeys(LPCameraModeLocal::AxisMoveRight, -1), *MappedKeys(LPCameraModeLocal::AxisMoveRight, 1),
|
||
|
|
*MappedKeys(LPCameraModeLocal::AxisLeftRight, 0), *MappedKeys(LPCameraModeLocal::AxisDownUp, 0));
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::TickControlsHelp()
|
||
|
|
{
|
||
|
|
UGameInstance* GI = GetGameInstance();
|
||
|
|
const bool bSexOn = GI && LPCameraModeLocal::ReadBool(GI, LPCameraModeLocal::GISexOn);
|
||
|
|
const UWorld* World = GI ? GI->GetWorld() : nullptr;
|
||
|
|
const APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||
|
|
|
||
|
|
// I-d : les 4 lignes d'origine sont rendues invisibles (opacite 0) seulement quand le bloc porte ce texte. La liaison
|
||
|
|
// Get_TextBlock_1_Text reste en place (SetText la retirerait) : un autre texte du meme bloc reste affiche.
|
||
|
|
const UUserWidget* SexUI = bSexOn ? Cast<UUserWidget>(LPCameraModeLocal::ReadObject(GI, LPCameraModeLocal::GISexInterface)) : nullptr;
|
||
|
|
if (UTextBlock* Lines = SexUI ? Cast<UTextBlock>(SexUI->GetWidgetFromName(LPCameraModeLocal::SexHelpLinesName)) : nullptr)
|
||
|
|
{
|
||
|
|
const bool bOldHelp = Lines->GetText().ToString().StartsWith(LPCameraModeLocal::SexHelpLinesStart, ESearchCase::CaseSensitive);
|
||
|
|
if (bOldHelp && Lines->GetRenderOpacity() > 0.f)
|
||
|
|
{
|
||
|
|
FadedLinesOpacity = Lines->GetRenderOpacity();
|
||
|
|
FadedHelpLines = Lines;
|
||
|
|
Lines->SetRenderOpacity(0.f);
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : 4 lignes d'origine masquees (%s)"), *GetPathNameSafe(Lines));
|
||
|
|
}
|
||
|
|
else if (!bOldHelp && FadedHelpLines.Get() == Lines)
|
||
|
|
{
|
||
|
|
// Le meme bloc porte un autre texte : il retrouve son opacite.
|
||
|
|
Lines->SetRenderOpacity(FadedLinesOpacity);
|
||
|
|
FadedHelpLines.Reset();
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : TextBlock_1 porte un autre texte, opacite rendue"));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// I-a : icone inseree une fois par instance du panneau, pendant le sexe ; repliee hors du sexe (le Cursor sert
|
||
|
|
// aussi aux autres menus d'action).
|
||
|
|
UUserWidget* ActionCursor = PC ? Cast<UUserWidget>(LPCameraModeLocal::ReadObject(PC, LPCameraModeLocal::PCCursorREF)) : nullptr;
|
||
|
|
if (bSexOn && ActionCursor && HelpCursor.Get() != ActionCursor)
|
||
|
|
{
|
||
|
|
HelpCursor = ActionCursor;
|
||
|
|
HelpButton = AttachHelpIcon(ActionCursor);
|
||
|
|
}
|
||
|
|
const FString HelpBody = bSexOn ? BuildControlsHelp() : FString();
|
||
|
|
if (UButton* HelpIcon = HelpButton.Get())
|
||
|
|
{
|
||
|
|
const ESlateVisibility IconVisibility = bSexOn ? ESlateVisibility::Visible : ESlateVisibility::Collapsed;
|
||
|
|
if (HelpIcon->GetVisibility() != IconVisibility)
|
||
|
|
{
|
||
|
|
HelpIcon->SetVisibility(IconVisibility);
|
||
|
|
}
|
||
|
|
if (bSexOn && HelpBody != HelpTextShown)
|
||
|
|
{
|
||
|
|
// Remappage (menu Keyboard) : info-bulle et boite suivent.
|
||
|
|
HelpIcon->SetToolTipText(FText::FromString(HelpBody));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// I-c : boite d'aide ouverte par le clic, seulement pendant le sexe et panneau ouvert (meme test que le PC :
|
||
|
|
// BelowCursor visible) ; fermee avec le panneau.
|
||
|
|
const UWidget* PanelArea = ActionCursor ? ActionCursor->GetWidgetFromName(LPCameraModeLocal::CursorBelowCursor) : nullptr;
|
||
|
|
const bool bPanelOpen = bSexOn && PanelArea && PanelArea->IsVisible();
|
||
|
|
if (!bPanelOpen)
|
||
|
|
{
|
||
|
|
bHelpBoxOpen = false;
|
||
|
|
}
|
||
|
|
if (bHelpBoxOpen && EnsureHelpBox())
|
||
|
|
{
|
||
|
|
if (!HelpText->GetText().ToString().Equals(HelpBody, ESearchCase::CaseSensitive))
|
||
|
|
{
|
||
|
|
HelpText->SetText(FText::FromString(HelpBody));
|
||
|
|
}
|
||
|
|
HelpRoot->SetVisibility(EVisibility::HitTestInvisible);
|
||
|
|
}
|
||
|
|
else if (HelpRoot.IsValid())
|
||
|
|
{
|
||
|
|
HelpRoot->SetVisibility(EVisibility::Collapsed);
|
||
|
|
}
|
||
|
|
if (bSexOn)
|
||
|
|
{
|
||
|
|
HelpTextShown = HelpBody;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
UButton* ULPCameraModeSubsystem::AttachHelpIcon(UUserWidget* ActionCursor)
|
||
|
|
{
|
||
|
|
if (UButton* Existing = Cast<UButton>(ActionCursor->GetWidgetFromName(LPCameraModeLocal::HelpButtonName)))
|
||
|
|
{
|
||
|
|
Existing->OnClicked.AddUniqueDynamic(this, &ULPCameraModeSubsystem::ToggleControlsHelp);
|
||
|
|
return Existing;
|
||
|
|
}
|
||
|
|
UWidget* SearchBox = ActionCursor->GetWidgetFromName(LPCameraModeLocal::CursorSearchAction);
|
||
|
|
UPanelWidget* SearchParent = SearchBox ? SearchBox->GetParent() : nullptr;
|
||
|
|
if (!SearchBox || !SearchParent || !ActionCursor->WidgetTree)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : icone non inseree, %s introuvable dans %s"),
|
||
|
|
!SearchBox ? TEXT("SearchAction") : TEXT("le parent de SearchAction"), *GetNameSafe(ActionCursor));
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
const UCanvasPanelSlot* SearchCanvasSlot = Cast<UCanvasPanelSlot>(SearchBox->Slot);
|
||
|
|
if (SearchCanvasSlot && (SearchCanvasSlot->GetAnchors().IsStretchedHorizontal() || SearchCanvasSlot->GetAnchors().IsStretchedVertical()))
|
||
|
|
{
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : icone non inseree, SearchAction est ancre en etirement dans %s (%s)"),
|
||
|
|
*SearchParent->GetName(), *SearchParent->GetClass()->GetName());
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
UButton* IconButton = ActionCursor->WidgetTree->ConstructWidget<UButton>(UButton::StaticClass(), LPCameraModeLocal::HelpButtonName);
|
||
|
|
IconButton->SetBackgroundColor(FLinearColor::Transparent);
|
||
|
|
UTexture2D* IconTexture = LoadObject<UTexture2D>(nullptr, LPCameraModeLocal::HelpIconPath);
|
||
|
|
if (IconTexture)
|
||
|
|
{
|
||
|
|
UImage* IconImage = ActionCursor->WidgetTree->ConstructWidget<UImage>(UImage::StaticClass(), TEXT("LPControlsHelpIcon"));
|
||
|
|
IconImage->SetBrushFromTexture(IconTexture);
|
||
|
|
IconImage->SetDesiredSizeOverride(FVector2D(LPCameraModeLocal::HelpIconSize, LPCameraModeLocal::HelpIconSize));
|
||
|
|
IconButton->SetContent(IconImage);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Texture absente : un « i » en texte.
|
||
|
|
UTextBlock* IconLetter = ActionCursor->WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("LPControlsHelpLetter"));
|
||
|
|
IconLetter->SetText(FText::FromString(TEXT("i")));
|
||
|
|
IconLetter->SetFont(FCoreStyle::GetDefaultFontStyle("Bold", 18));
|
||
|
|
IconLetter->SetColorAndOpacity(FSlateColor(FLinearColor::White));
|
||
|
|
IconButton->SetContent(IconLetter);
|
||
|
|
}
|
||
|
|
IconButton->SetToolTipText(FText::FromString(BuildControlsHelp()));
|
||
|
|
IconButton->OnClicked.AddDynamic(this, &ULPCameraModeSubsystem::ToggleControlsHelp);
|
||
|
|
// AddChild, pas InsertChildAt : l'ordre d'insertion ne passe pas a la vue Slate sans reconstruction. L'icone vient
|
||
|
|
// donc en fin de la rangee du filtre (apres ses boutons), ou a droite du filtre dans un CanvasPanel.
|
||
|
|
UPanelSlot* IconSlot = SearchParent->AddChild(IconButton);
|
||
|
|
if (!IconSlot)
|
||
|
|
{
|
||
|
|
// VAL B2p-I D1 : panneau a enfant unique deja occupe (PanelWidget.cpp:139-142) : rien n'est insere.
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : icone non inseree, %s (%s) refuse un enfant de plus"),
|
||
|
|
*SearchParent->GetName(), *SearchParent->GetClass()->GetName());
|
||
|
|
return nullptr;
|
||
|
|
}
|
||
|
|
if (UHorizontalBoxSlot* RowSlot = Cast<UHorizontalBoxSlot>(IconSlot))
|
||
|
|
{
|
||
|
|
RowSlot->SetVerticalAlignment(VAlign_Center);
|
||
|
|
RowSlot->SetPadding(FMargin(4.f, 0.f, 0.f, 0.f));
|
||
|
|
}
|
||
|
|
else if (UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(IconSlot))
|
||
|
|
{
|
||
|
|
if (SearchCanvasSlot)
|
||
|
|
{
|
||
|
|
CanvasSlot->SetAnchors(SearchCanvasSlot->GetAnchors());
|
||
|
|
CanvasSlot->SetAlignment(SearchCanvasSlot->GetAlignment());
|
||
|
|
CanvasSlot->SetPosition(SearchCanvasSlot->GetPosition() + FVector2D(SearchCanvasSlot->GetSize().X + 4.0, 0.0));
|
||
|
|
CanvasSlot->SetSize(FVector2D(LPCameraModeLocal::HelpIconSize + 8.0, LPCameraModeLocal::HelpIconSize + 8.0));
|
||
|
|
CanvasSlot->SetZOrder(SearchCanvasSlot->GetZOrder());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : icone inseree dans %s (%s, %d enfants), image %s"),
|
||
|
|
*SearchParent->GetName(), *SearchParent->GetClass()->GetName(), SearchParent->GetChildrenCount(),
|
||
|
|
IconTexture ? LPCameraModeLocal::HelpIconPath : TEXT("absente, lettre i"));
|
||
|
|
return IconButton;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::ToggleControlsHelp()
|
||
|
|
{
|
||
|
|
bHelpBoxOpen = !bHelpBoxOpen;
|
||
|
|
UE_LOG(LogLifePlay, Log, TEXT("Aide sexe : boite d'aide %s (clic sur l'icone)"), bHelpBoxOpen ? TEXT("ouverte") : TEXT("fermee"));
|
||
|
|
TickControlsHelp();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ULPCameraModeSubsystem::EnsureHelpBox()
|
||
|
|
{
|
||
|
|
const UGameInstance* GI = GetGameInstance();
|
||
|
|
UGameViewportClient* ViewportClient = GI ? GI->GetGameViewportClient() : nullptr;
|
||
|
|
if (!ViewportClient || !FSlateApplication::IsInitialized())
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (HelpRoot.IsValid() && HelpViewport.Get() == ViewportClient)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
RemoveHelpBox();
|
||
|
|
// En bas a droite, la ou etaient les 4 lignes : fond sombre, texte blanc ombre (lisible sans LPReadableText).
|
||
|
|
TSharedPtr<STextBlock> NewHelpText;
|
||
|
|
TSharedRef<SWidget> NewHelpRoot = SNew(SOverlay)
|
||
|
|
.Visibility(EVisibility::Collapsed)
|
||
|
|
+ SOverlay::Slot().HAlign(HAlign_Right).VAlign(VAlign_Bottom).Padding(FMargin(24.f, 24.f, 24.f, 96.f))
|
||
|
|
[
|
||
|
|
SNew(SBorder)
|
||
|
|
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||
|
|
.BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.75f))
|
||
|
|
.Padding(FMargin(12.f, 8.f))
|
||
|
|
[
|
||
|
|
SAssignNew(NewHelpText, STextBlock)
|
||
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 14))
|
||
|
|
.ColorAndOpacity(FSlateColor(FLinearColor::White))
|
||
|
|
.ShadowOffset(FVector2D(1.f, 1.f))
|
||
|
|
.ShadowColorAndOpacity(FLinearColor::Black)
|
||
|
|
]
|
||
|
|
];
|
||
|
|
ViewportClient->AddViewportWidgetContent(NewHelpRoot, LPCameraModeLocal::HelpZOrder);
|
||
|
|
HelpRoot = NewHelpRoot;
|
||
|
|
HelpText = NewHelpText;
|
||
|
|
HelpViewport = ViewportClient;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void ULPCameraModeSubsystem::RemoveHelpBox()
|
||
|
|
{
|
||
|
|
if (HelpRoot.IsValid())
|
||
|
|
{
|
||
|
|
if (UGameViewportClient* ViewportClient = HelpViewport.Get())
|
||
|
|
{
|
||
|
|
ViewportClient->RemoveViewportWidgetContent(HelpRoot.ToSharedRef());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
HelpRoot.Reset();
|
||
|
|
HelpText.Reset();
|
||
|
|
HelpViewport.Reset();
|
||
|
|
}
|