#pragma once #include "CoreMinimal.h" #include "Components/SceneComponent.h" #include "CarrierBlockout.generated.h" class UProceduralMeshComponent; class UPrimitiveComponent; class ULightComponent; class UPointLightComponent; class USpotLightComponent; /** What the blockout made, for the `carrier` state section and the tests. */ struct FCarrierBlockoutStats { int32 Rooms = 0; int32 MeshComponents = 0; int32 Sections = 0; int32 Triangles = 0; int32 ConvexHulls = 0; int32 InteriorPrimitives = 0; int32 InteriorWithStencil = 0; int32 InteriorOnChannel1Only = 0; int32 RoomLamps = 0; int32 ExteriorLights = 0; int32 Signs = 0; }; /** * Builds the CANTICLE from FCarrierLayout at BeginPlay (DESIGN_UE.md section 3.2): the hull, one procedural mesh per room with * convex collision, the doors, the moon pool, the lamps, the six exterior lights, the text signs. Interior primitives get the * custom-depth stencil 1 ("dry") and lighting channel 1; the exterior gets channel 0. A room whose layout entry names an * OverrideMesh gets a static mesh instead of the blockout. */ UCLASS(ClassGroup = (Descent), meta = (BlueprintSpawnableComponent)) class THEDESCENT_API UCarrierBlockoutComponent : public USceneComponent { GENERATED_BODY() public: UCarrierBlockoutComponent(); /** Builds everything. Safe to call once; a second call does nothing. */ void Build(); bool IsBuilt() const { return bBuilt; } /** Sets mesh/light flags of a primitive that belongs inside the hull (stencil 1, channel 1). Public so fixtures use the same rule. */ static void MakeInterior(UPrimitiveComponent* Prim); static void MakeExterior(UPrimitiveComponent* Prim); /** Collision profile of a walkable/solid blockout piece: blocks Pawn, Visibility and Placement, ignores the rest. */ static void MakeSolid(UPrimitiveComponent* Prim); /** Lamp pulse of the engine room, mast flash, stern pulse: cosmetics, driven from the actor's Tick. */ void UpdateCosmetics(double TimeSeconds); /** * The room the player stands in. Its lamps also light lighting channel 2, which is the only channel the first-person arm takes in * dry air: the room lamps cast no shadows and reach through walls, so on channel 1 the hand was lit by the warm lamps of the * quarters behind the bridge wall and looked tan in the blue bridge (fix list after M3). NAME_None keeps the last room. */ void SetViewerRoom(FName Room); FName GetViewerRoom() const { return ViewerRoom; } FCarrierBlockoutStats GatherStats() const; int32 NumExteriorLights() const { return ExteriorLights.Num(); } const TArray>& GetInteriorPrimitives() const { return InteriorPrims; } private: UProceduralMeshComponent* NewMesh(const FName& Name, bool bInterior, bool bSolid); void BuildLights(); bool bBuilt = false; UPROPERTY(Transient) TArray> InteriorPrims; UPROPERTY(Transient) TArray> MeshComps; UPROPERTY(Transient) TArray> RoomLamps; UPROPERTY(Transient) TArray> ExteriorLights; UPROPERTY(Transient) TObjectPtr MastLight; UPROPERTY(Transient) TObjectPtr SternLight; UPROPERTY(Transient) TObjectPtr MastBulb; struct FLampInfo { TWeakObjectPtr Light; float BaseBrightness = 0.f; bool bPulses = false; FName Room; }; FName ViewerRoom; TArray LampInfos; float MastBase = 0.f, SternBase = 0.f; int32 NumRooms = 0, NumSigns = 0; };