From c7ec3e96531b692580713ca7b4fc86c6d10e15c6 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Tue, 5 May 2026 19:04:59 +0200 Subject: [PATCH] Per-scenario type field driven by project enum (Settings dropdown + JSON peek) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../PS_Editor_GameInstanceSubsystem.cpp | 51 +++++++- .../PS_Editor_GameInstanceSubsystem.h | 31 ++++- .../Serialization/PS_Editor_SceneData.h | 6 + .../PS_Editor_SceneSerializer.cpp | 2 + .../PS_Editor/Spawn/PS_Editor_SpawnCatalog.h | 12 ++ .../PS_Editor/Spawn/PS_Editor_SpawnManager.h | 6 + .../UI/Widgets/PS_Editor_MainWidget.cpp | 113 +++++++++++++++++- .../UI/Widgets/PS_Editor_MainWidget.h | 8 +- .../Widgets/PS_Editor_MainWidget_Legacy.cpp | 110 ++++++++++++++++- .../UI/Widgets/PS_Editor_MainWidget_Legacy.h | 8 +- 10 files changed, 339 insertions(+), 8 deletions(-) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp index f24d09b..062ac4f 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp @@ -1,13 +1,19 @@ #include "PS_Editor_GameInstanceSubsystem.h" +#include "PS_Editor_SceneLoader.h" +#include "PS_Editor_SceneData.h" #include "Kismet/GameplayStatics.h" +#include "Misc/FileHelper.h" +#include "JsonObjectConverter.h" -void UPS_Editor_GameInstanceSubsystem::SetPendingScenario(const FString& ScenarioName, const FString& BaseLevel) +void UPS_Editor_GameInstanceSubsystem::SetPendingScenario(const FString& ScenarioName, const FString& BaseLevel, uint8 ScenarioType) { PendingScenarioName = ScenarioName; PendingBaseLevel = BaseLevel; + PendingScenarioType = ScenarioType; LastEditedScenarioName = ScenarioName; LastEditedBaseLevel = BaseLevel; - UE_LOG(LogTemp, Log, TEXT("PS_Editor: Pending scenario set: '%s' (base level: '%s')"), *ScenarioName, *BaseLevel); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Pending scenario set: '%s' (base level: '%s', type: %u)"), + *ScenarioName, *BaseLevel, ScenarioType); } FString UPS_Editor_GameInstanceSubsystem::ConsumePendingScenario() @@ -45,3 +51,44 @@ void UPS_Editor_GameInstanceSubsystem::ReturnToEditor(const UObject* WorldContex OpenEditor(WorldContextObject); } + +bool UPS_Editor_GameInstanceSubsystem::ReadScenarioMetadata(const FString& ScenarioName, uint8& OutScenarioType, FString& OutBaseLevel) const +{ + OutScenarioType = 0; + OutBaseLevel.Empty(); + + if (ScenarioName.IsEmpty()) + { + return false; + } + + const FString FilePath = UPS_Editor_SceneLoader::GetSceneFilePath(ScenarioName); + FString JsonString; + if (!FFileHelper::LoadFileToString(JsonString, *FilePath)) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: ReadScenarioMetadata — file not found: %s"), *FilePath); + return false; + } + + // Parse the full SceneData. We could parse just the metadata fields with a custom + // reader, but FJsonObjectConverter is fast enough for the scenario sizes we deal with + // (tens of actors max), and reusing the same struct keeps version handling consistent. + FPS_Editor_SceneData SceneData; + if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &SceneData, 0, 0)) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: ReadScenarioMetadata — failed to parse JSON for '%s'"), *ScenarioName); + return false; + } + + OutScenarioType = SceneData.ScenarioType; + OutBaseLevel = SceneData.LevelName; + return true; +} + +uint8 UPS_Editor_GameInstanceSubsystem::GetScenarioTypeFromFile(const FString& ScenarioName) const +{ + uint8 Type = 0; + FString DummyLevel; + ReadScenarioMetadata(ScenarioName, Type, DummyLevel); + return Type; +} diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h index cce6fc0..7c00c4d 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h @@ -40,9 +40,36 @@ public: UPROPERTY(BlueprintReadWrite, Category = "PS_Editor") bool bIsPlayMode = false; - /** Set pending scenario + base level (for opening the editor or gameplay). */ + /** Pending scenario type as uint8 — index into UPS_Editor_MasterCatalog::ScenarioTypeEnum. + * Set by the Play button alongside PendingScenarioName. The gameplay GameMode reads it + * (Byte to Enum) and routes its scenario-type-specific logic. Default 0 = first enum entry. */ + UPROPERTY(BlueprintReadWrite, Category = "PS_Editor") + uint8 PendingScenarioType = 0; + + /** Set pending scenario + base level + type (for opening the editor or gameplay). */ UFUNCTION(BlueprintCallable, Category = "PS_Editor") - void SetPendingScenario(const FString& ScenarioName, const FString& BaseLevel); + void SetPendingScenario(const FString& ScenarioName, const FString& BaseLevel, uint8 ScenarioType = 0); + + /** + * Pre-parse a scenario file from disk (without spawning anything) to extract its + * persisted metadata: scenario type and base level. Use this from PROSERVE GameMode + * BeginPlay (or earlier) to know what type a scenario is BEFORE deciding to load it, + * when the standard Play-button flow hasn't populated PendingScenarioType. + * + * @param ScenarioName Name of the scenario file (without extension). + * @param OutScenarioType Set to the scenario's saved type (uint8 index into MasterCatalog::ScenarioTypeEnum). 0 on failure. + * @param OutBaseLevel Set to the scenario's saved base level name. Empty on failure. + * @return True if the file was found and parsed successfully. + */ + UFUNCTION(BlueprintCallable, Category = "PS_Editor") + bool ReadScenarioMetadata(const FString& ScenarioName, uint8& OutScenarioType, FString& OutBaseLevel) const; + + /** + * Convenience: read just the scenario type from a file. Returns 0 if the file isn't + * found / can't be parsed (which is also the default for a saved-but-untyped scenario). + */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PS_Editor") + uint8 GetScenarioTypeFromFile(const FString& ScenarioName) const; /** Consume the pending scenario (reads and clears). */ UFUNCTION(BlueprintCallable, Category = "PS_Editor") diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h index 6e8d3df..08f448b 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h @@ -68,4 +68,10 @@ struct FPS_Editor_SceneData * cooked Static nav, which has zero runtime cost. Default: false. */ UPROPERTY() bool bUseDynamicNavigation = false; + + /** Project-specific scenario type as a uint8 (index into UPS_Editor_MasterCatalog::ScenarioTypeEnum). + * Selected by the user via the Settings popup dropdown. Defaults to 0 (= first enum entry) for + * legacy scenarios that were saved before this field existed. */ + UPROPERTY() + uint8 ScenarioType = 0; }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp index fa4674f..afce980 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp @@ -171,6 +171,7 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_ if (SpawnManager) { SceneData.bUseDynamicNavigation = SpawnManager->bUseDynamicNavigation; + SceneData.ScenarioType = SpawnManager->ScenarioType; } // Convert to JSON @@ -450,6 +451,7 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_ if (SpawnManager) { SpawnManager->bUseDynamicNavigation = SceneData.bUseDynamicNavigation; + SpawnManager->ScenarioType = SceneData.ScenarioType; if (APS_Editor_PlayerController* EditorPC = Cast(GetOuter())) { PS_Editor_NavUtils::SetDynamicNavigationEnabled(EditorPC->GetWorld(), SpawnManager->bUseDynamicNavigation); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h index ff2770a..448144b 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h @@ -118,4 +118,16 @@ public: */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS_Editor") TArray BaseLevels; + + /** + * Optional project-specific enum used to categorize scenarios. The runtime editor's + * "Scenario Type" dropdown (Settings popup) reads this enum to populate its choices. + * The picked value is saved per-scenario as a uint8 (index into the enum) and exposed + * at runtime via UPS_Editor_GameInstanceSubsystem::PendingScenarioType — the gameplay + * GameMode reads it (Byte to Enum) and dispatches its scenario-type-specific logic. + * + * Leave null if your project doesn't use scenario typing — the dropdown is hidden. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS_Editor") + TObjectPtr ScenarioTypeEnum; }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h index 413bd7d..6149b58 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h @@ -102,6 +102,12 @@ public: * Persisted in the scenario JSON; default false. */ bool bUseDynamicNavigation = false; + /** Project-specific scenario type as uint8 (index into MasterCatalog::ScenarioTypeEnum). + * Set via the Settings popup dropdown, persisted in the scenario JSON, pushed into + * PS_Editor_GameInstanceSubsystem::PendingScenarioType at Play click for gameplay + * GameMode to consume. Default 0 (first enum entry). */ + uint8 ScenarioType = 0; + // ---- Session-only UI preferences (NOT persisted) ---- /** If true, the navmesh is rendered at runtime (mimics the editor's P key). Personal diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp index 82637ab..34d3949 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp @@ -591,6 +591,69 @@ TSharedRef UPS_Editor_MainWidget::RebuildWidget() ] ] ] + // Scenario Type dropdown — visible only when MasterCatalog::ScenarioTypeEnum is set. + + SVerticalBox::Slot().AutoHeight().Padding(0.0f, 8.0f, 0.0f, 4.0f) + [ + SNew(SHorizontalBox) + .Visibility_Lambda([this]() -> EVisibility + { + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->MasterCatalogAsset && PC->MasterCatalogAsset->ScenarioTypeEnum) + { + return EVisibility::Visible; + } + } + return EVisibility::Collapsed; + }) + + SHorizontalBox::Slot().FillWidth(0.45f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Scenario Type"))) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .ColorAndOpacity(FLinearColor::White) + ] + + SVerticalBox::Slot().AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Saved per-scenario; pushed to PendingScenarioType at Play."))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 9)) + .ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f)) + .AutoWrapText(true) + ] + ] + + SHorizontalBox::Slot().FillWidth(0.55f).VAlign(VAlign_Center).Padding(8.0f, 0.0f, 0.0f, 0.0f) + [ + SAssignNew(ScenarioTypeCombo, SComboBox>) + .OptionsSource(&ScenarioTypeOptions) + .OnComboBoxOpening_Lambda([this]() { RefreshScenarioTypeOptions(); }) + .OnGenerateWidget_Lambda([](TSharedPtr Item) -> TSharedRef + { + return SNew(STextBlock).Text(FText::FromString(Item.IsValid() ? *Item : FString())); + }) + .OnSelectionChanged_Lambda([this](TSharedPtr NewItem, ESelectInfo::Type SelectType) + { + if (!NewItem.IsValid()) return; + const int32 Idx = ScenarioTypeOptions.IndexOfByKey(NewItem); + if (Idx == INDEX_NONE) return; + if (UPS_Editor_SpawnManager* SM = SpawnManager.Get()) + { + SM->ScenarioType = (uint8)Idx; + bSceneDirty = true; + } + }) + [ + SNew(STextBlock) + .Text_Lambda([this]() -> FText + { + return FText::FromString(GetCurrentScenarioTypeDisplayName()); + }) + ] + ] + ] + SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f) [ SNew(SButton) @@ -1170,7 +1233,8 @@ TSharedRef UPS_Editor_MainWidget::BuildSceneButtons() { if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem()) { - Sub->SetPendingScenario(SceneName, BaseLevel); + const uint8 TypeToSend = SpawnManager.IsValid() ? SpawnManager->ScenarioType : 0; + Sub->SetPendingScenario(SceneName, BaseLevel, TypeToSend); Sub->bIsPlayMode = true; if (Sub->EditorMapName.IsEmpty()) { @@ -3336,10 +3400,57 @@ void UPS_Editor_MainWidget::OpenSettingsPopup() { if (SettingsPopup.IsValid()) { + // Refresh enum-driven options each time the popup opens — covers the case where the + // MasterCatalogAsset (or its ScenarioTypeEnum) wasn't set at widget Construct time. + RefreshScenarioTypeOptions(); SettingsPopup->SetVisibility(EVisibility::Visible); } } +void UPS_Editor_MainWidget::RefreshScenarioTypeOptions() +{ + ScenarioTypeOptions.Empty(); + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->MasterCatalogAsset && PC->MasterCatalogAsset->ScenarioTypeEnum) + { + UEnum* E = PC->MasterCatalogAsset->ScenarioTypeEnum; + // NumEnums()-1 to skip the auto-generated _MAX entry on regular UENUMs. + // User-defined enums (UUserDefinedEnum) don't have _MAX but the last "MAX" entry + // is hidden behind the `bIsHidden` meta — we drop the last one defensively. + const int32 Count = FMath::Max(0, E->NumEnums() - 1); + for (int32 i = 0; i < Count; ++i) + { + ScenarioTypeOptions.Add(MakeShared(E->GetDisplayNameTextByIndex(i).ToString())); + } + } + } + if (ScenarioTypeCombo.IsValid()) + { + ScenarioTypeCombo->RefreshOptions(); + } +} + +FString UPS_Editor_MainWidget::GetCurrentScenarioTypeDisplayName() const +{ + if (UPS_Editor_SpawnManager* SM = SpawnManager.Get()) + { + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->MasterCatalogAsset && PC->MasterCatalogAsset->ScenarioTypeEnum) + { + UEnum* E = PC->MasterCatalogAsset->ScenarioTypeEnum; + const int32 Count = FMath::Max(0, E->NumEnums() - 1); + if (SM->ScenarioType < Count) + { + return E->GetDisplayNameTextByIndex(SM->ScenarioType).ToString(); + } + } + } + } + return FString(); +} + void UPS_Editor_MainWidget::CloseSettingsPopup() { if (SettingsPopup.IsValid()) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h index 4e0aba8..0338ee6 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h @@ -134,13 +134,19 @@ private: void ConfirmBaseLevelSelection(); void CancelBaseLevelPopup(); - // Global Settings popup (per-scenario nav option + session-only navmesh visu). + // Global Settings popup (per-scenario nav option + session-only navmesh visu + scenario type). TSharedPtr SettingsPopup; void OpenSettingsPopup(); void CloseSettingsPopup(); void OnUseDynamicNavigationToggled(ECheckBoxState State); void OnShowNavMeshToggled(ECheckBoxState State); + // Scenario type dropdown — populated from MasterCatalog::ScenarioTypeEnum, hidden when null. + TArray> ScenarioTypeOptions; + TSharedPtr>> ScenarioTypeCombo; + void RefreshScenarioTypeOptions(); + FString GetCurrentScenarioTypeDisplayName() const; + // Transform fields TSharedPtr LocX, LocY, LocZ; TSharedPtr RotX, RotY, RotZ; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp index 6c29aef..298c8f5 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp @@ -550,6 +550,69 @@ TSharedRef UPS_Editor_MainWidget_Legacy::RebuildWidget() ] ] ] + // Scenario Type dropdown — visible only when MasterCatalog::ScenarioTypeEnum is set. + + SVerticalBox::Slot().AutoHeight().Padding(0.0f, 8.0f, 0.0f, 4.0f) + [ + SNew(SHorizontalBox) + .Visibility_Lambda([this]() -> EVisibility + { + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->MasterCatalogAsset && PC->MasterCatalogAsset->ScenarioTypeEnum) + { + return EVisibility::Visible; + } + } + return EVisibility::Collapsed; + }) + + SHorizontalBox::Slot().FillWidth(0.45f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Scenario Type"))) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 11)) + .ColorAndOpacity(FLinearColor::White) + ] + + SVerticalBox::Slot().AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Saved per-scenario; pushed to PendingScenarioType at Play."))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 9)) + .ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f)) + .AutoWrapText(true) + ] + ] + + SHorizontalBox::Slot().FillWidth(0.55f).VAlign(VAlign_Center).Padding(8.0f, 0.0f, 0.0f, 0.0f) + [ + SAssignNew(ScenarioTypeCombo, SComboBox>) + .OptionsSource(&ScenarioTypeOptions) + .OnComboBoxOpening_Lambda([this]() { RefreshScenarioTypeOptions(); }) + .OnGenerateWidget_Lambda([](TSharedPtr Item) -> TSharedRef + { + return SNew(STextBlock).Text(FText::FromString(Item.IsValid() ? *Item : FString())); + }) + .OnSelectionChanged_Lambda([this](TSharedPtr NewItem, ESelectInfo::Type SelectType) + { + if (!NewItem.IsValid()) return; + const int32 Idx = ScenarioTypeOptions.IndexOfByKey(NewItem); + if (Idx == INDEX_NONE) return; + if (UPS_Editor_SpawnManager* SM = SpawnManager.Get()) + { + SM->ScenarioType = (uint8)Idx; + bSceneDirty = true; + } + }) + [ + SNew(STextBlock) + .Text_Lambda([this]() -> FText + { + return FText::FromString(GetCurrentScenarioTypeDisplayName()); + }) + ] + ] + ] // Close button + SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f) [ @@ -1011,7 +1074,8 @@ TSharedRef UPS_Editor_MainWidget_Legacy::BuildSceneButtons() { if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem()) { - Sub->SetPendingScenario(SceneName, BaseLevel); + const uint8 TypeToSend = SpawnManager.IsValid() ? SpawnManager->ScenarioType : 0; + Sub->SetPendingScenario(SceneName, BaseLevel, TypeToSend); Sub->bIsPlayMode = true; if (Sub->EditorMapName.IsEmpty()) { @@ -3010,10 +3074,54 @@ void UPS_Editor_MainWidget_Legacy::OpenSettingsPopup() { if (SettingsPopup.IsValid()) { + // Refresh enum-driven options each time the popup opens — covers the case where the + // MasterCatalogAsset (or its ScenarioTypeEnum) wasn't set at widget Construct time. + RefreshScenarioTypeOptions(); SettingsPopup->SetVisibility(EVisibility::Visible); } } +void UPS_Editor_MainWidget_Legacy::RefreshScenarioTypeOptions() +{ + ScenarioTypeOptions.Empty(); + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->MasterCatalogAsset && PC->MasterCatalogAsset->ScenarioTypeEnum) + { + UEnum* E = PC->MasterCatalogAsset->ScenarioTypeEnum; + const int32 Count = FMath::Max(0, E->NumEnums() - 1); + for (int32 i = 0; i < Count; ++i) + { + ScenarioTypeOptions.Add(MakeShared(E->GetDisplayNameTextByIndex(i).ToString())); + } + } + } + if (ScenarioTypeCombo.IsValid()) + { + ScenarioTypeCombo->RefreshOptions(); + } +} + +FString UPS_Editor_MainWidget_Legacy::GetCurrentScenarioTypeDisplayName() const +{ + if (UPS_Editor_SpawnManager* SM = SpawnManager.Get()) + { + if (APS_Editor_PlayerController* PC = Cast(GetOwningPlayer())) + { + if (PC->MasterCatalogAsset && PC->MasterCatalogAsset->ScenarioTypeEnum) + { + UEnum* E = PC->MasterCatalogAsset->ScenarioTypeEnum; + const int32 Count = FMath::Max(0, E->NumEnums() - 1); + if (SM->ScenarioType < Count) + { + return E->GetDisplayNameTextByIndex(SM->ScenarioType).ToString(); + } + } + } + } + return FString(); +} + void UPS_Editor_MainWidget_Legacy::CloseSettingsPopup() { if (SettingsPopup.IsValid()) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h index 98bf7db..75cf610 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h @@ -107,7 +107,7 @@ private: void ConfirmBaseLevelSelection(); void CancelBaseLevelPopup(); - // Global Settings popup (per-scenario nav option + session-only navmesh visu). + // Global Settings popup (per-scenario nav option + session-only navmesh visu + scenario type). TSharedPtr SettingsPopup; void OpenSettingsPopup(); void CloseSettingsPopup(); @@ -116,6 +116,12 @@ private: /** Toggle the navmesh visualization (P key handler). */ void ToggleShowNavMesh(); + // Scenario type dropdown — populated from MasterCatalog::ScenarioTypeEnum, hidden when null. + TArray> ScenarioTypeOptions; + TSharedPtr>> ScenarioTypeCombo; + void RefreshScenarioTypeOptions(); + FString GetCurrentScenarioTypeDisplayName() const; + // Transform fields TSharedPtr LocX, LocY, LocZ; TSharedPtr RotX, RotY, RotZ;