Per-scenario type field driven by project enum (Settings dropdown + JSON peek)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 19:04:59 +02:00
parent d9ed016297
commit c7ec3e9653
10 changed files with 339 additions and 8 deletions

View File

@@ -1,13 +1,19 @@
#include "PS_Editor_GameInstanceSubsystem.h" #include "PS_Editor_GameInstanceSubsystem.h"
#include "PS_Editor_SceneLoader.h"
#include "PS_Editor_SceneData.h"
#include "Kismet/GameplayStatics.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; PendingScenarioName = ScenarioName;
PendingBaseLevel = BaseLevel; PendingBaseLevel = BaseLevel;
PendingScenarioType = ScenarioType;
LastEditedScenarioName = ScenarioName; LastEditedScenarioName = ScenarioName;
LastEditedBaseLevel = BaseLevel; 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() FString UPS_Editor_GameInstanceSubsystem::ConsumePendingScenario()
@@ -45,3 +51,44 @@ void UPS_Editor_GameInstanceSubsystem::ReturnToEditor(const UObject* WorldContex
OpenEditor(WorldContextObject); 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;
}

View File

@@ -40,9 +40,36 @@ public:
UPROPERTY(BlueprintReadWrite, Category = "PS_Editor") UPROPERTY(BlueprintReadWrite, Category = "PS_Editor")
bool bIsPlayMode = false; 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") 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). */ /** Consume the pending scenario (reads and clears). */
UFUNCTION(BlueprintCallable, Category = "PS_Editor") UFUNCTION(BlueprintCallable, Category = "PS_Editor")

View File

@@ -68,4 +68,10 @@ struct FPS_Editor_SceneData
* cooked Static nav, which has zero runtime cost. Default: false. */ * cooked Static nav, which has zero runtime cost. Default: false. */
UPROPERTY() UPROPERTY()
bool bUseDynamicNavigation = false; 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;
}; };

View File

@@ -171,6 +171,7 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_
if (SpawnManager) if (SpawnManager)
{ {
SceneData.bUseDynamicNavigation = SpawnManager->bUseDynamicNavigation; SceneData.bUseDynamicNavigation = SpawnManager->bUseDynamicNavigation;
SceneData.ScenarioType = SpawnManager->ScenarioType;
} }
// Convert to JSON // Convert to JSON
@@ -450,6 +451,7 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
if (SpawnManager) if (SpawnManager)
{ {
SpawnManager->bUseDynamicNavigation = SceneData.bUseDynamicNavigation; SpawnManager->bUseDynamicNavigation = SceneData.bUseDynamicNavigation;
SpawnManager->ScenarioType = SceneData.ScenarioType;
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOuter())) if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOuter()))
{ {
PS_Editor_NavUtils::SetDynamicNavigationEnabled(EditorPC->GetWorld(), SpawnManager->bUseDynamicNavigation); PS_Editor_NavUtils::SetDynamicNavigationEnabled(EditorPC->GetWorld(), SpawnManager->bUseDynamicNavigation);

View File

@@ -118,4 +118,16 @@ public:
*/ */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS_Editor") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS_Editor")
TArray<FPS_Editor_BaseLevelEntry> BaseLevels; TArray<FPS_Editor_BaseLevelEntry> 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<UEnum> ScenarioTypeEnum;
}; };

View File

@@ -102,6 +102,12 @@ public:
* Persisted in the scenario JSON; default false. */ * Persisted in the scenario JSON; default false. */
bool bUseDynamicNavigation = 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) ---- // ---- Session-only UI preferences (NOT persisted) ----
/** If true, the navmesh is rendered at runtime (mimics the editor's P key). Personal /** If true, the navmesh is rendered at runtime (mimics the editor's P key). Personal

View File

@@ -591,6 +591,69 @@ TSharedRef<SWidget> 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<APS_Editor_PlayerController>(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<TSharedPtr<FString>>)
.OptionsSource(&ScenarioTypeOptions)
.OnComboBoxOpening_Lambda([this]() { RefreshScenarioTypeOptions(); })
.OnGenerateWidget_Lambda([](TSharedPtr<FString> Item) -> TSharedRef<SWidget>
{
return SNew(STextBlock).Text(FText::FromString(Item.IsValid() ? *Item : FString()));
})
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> 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) + SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f)
[ [
SNew(SButton) SNew(SButton)
@@ -1170,7 +1233,8 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
{ {
if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem<UPS_Editor_GameInstanceSubsystem>()) if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem<UPS_Editor_GameInstanceSubsystem>())
{ {
Sub->SetPendingScenario(SceneName, BaseLevel); const uint8 TypeToSend = SpawnManager.IsValid() ? SpawnManager->ScenarioType : 0;
Sub->SetPendingScenario(SceneName, BaseLevel, TypeToSend);
Sub->bIsPlayMode = true; Sub->bIsPlayMode = true;
if (Sub->EditorMapName.IsEmpty()) if (Sub->EditorMapName.IsEmpty())
{ {
@@ -3336,10 +3400,57 @@ void UPS_Editor_MainWidget::OpenSettingsPopup()
{ {
if (SettingsPopup.IsValid()) 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); SettingsPopup->SetVisibility(EVisibility::Visible);
} }
} }
void UPS_Editor_MainWidget::RefreshScenarioTypeOptions()
{
ScenarioTypeOptions.Empty();
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(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<FString>(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<APS_Editor_PlayerController>(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() void UPS_Editor_MainWidget::CloseSettingsPopup()
{ {
if (SettingsPopup.IsValid()) if (SettingsPopup.IsValid())

View File

@@ -134,13 +134,19 @@ private:
void ConfirmBaseLevelSelection(); void ConfirmBaseLevelSelection();
void CancelBaseLevelPopup(); 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<SWidget> SettingsPopup; TSharedPtr<SWidget> SettingsPopup;
void OpenSettingsPopup(); void OpenSettingsPopup();
void CloseSettingsPopup(); void CloseSettingsPopup();
void OnUseDynamicNavigationToggled(ECheckBoxState State); void OnUseDynamicNavigationToggled(ECheckBoxState State);
void OnShowNavMeshToggled(ECheckBoxState State); void OnShowNavMeshToggled(ECheckBoxState State);
// Scenario type dropdown — populated from MasterCatalog::ScenarioTypeEnum, hidden when null.
TArray<TSharedPtr<FString>> ScenarioTypeOptions;
TSharedPtr<SComboBox<TSharedPtr<FString>>> ScenarioTypeCombo;
void RefreshScenarioTypeOptions();
FString GetCurrentScenarioTypeDisplayName() const;
// Transform fields // Transform fields
TSharedPtr<SEditableTextBox> LocX, LocY, LocZ; TSharedPtr<SEditableTextBox> LocX, LocY, LocZ;
TSharedPtr<SEditableTextBox> RotX, RotY, RotZ; TSharedPtr<SEditableTextBox> RotX, RotY, RotZ;

View File

@@ -550,6 +550,69 @@ TSharedRef<SWidget> 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<APS_Editor_PlayerController>(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<TSharedPtr<FString>>)
.OptionsSource(&ScenarioTypeOptions)
.OnComboBoxOpening_Lambda([this]() { RefreshScenarioTypeOptions(); })
.OnGenerateWidget_Lambda([](TSharedPtr<FString> Item) -> TSharedRef<SWidget>
{
return SNew(STextBlock).Text(FText::FromString(Item.IsValid() ? *Item : FString()));
})
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> 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 // Close button
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f) + SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 16.0f, 0.0f, 0.0f)
[ [
@@ -1011,7 +1074,8 @@ TSharedRef<SWidget> UPS_Editor_MainWidget_Legacy::BuildSceneButtons()
{ {
if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem<UPS_Editor_GameInstanceSubsystem>()) if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem<UPS_Editor_GameInstanceSubsystem>())
{ {
Sub->SetPendingScenario(SceneName, BaseLevel); const uint8 TypeToSend = SpawnManager.IsValid() ? SpawnManager->ScenarioType : 0;
Sub->SetPendingScenario(SceneName, BaseLevel, TypeToSend);
Sub->bIsPlayMode = true; Sub->bIsPlayMode = true;
if (Sub->EditorMapName.IsEmpty()) if (Sub->EditorMapName.IsEmpty())
{ {
@@ -3010,10 +3074,54 @@ void UPS_Editor_MainWidget_Legacy::OpenSettingsPopup()
{ {
if (SettingsPopup.IsValid()) 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); SettingsPopup->SetVisibility(EVisibility::Visible);
} }
} }
void UPS_Editor_MainWidget_Legacy::RefreshScenarioTypeOptions()
{
ScenarioTypeOptions.Empty();
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(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<FString>(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<APS_Editor_PlayerController>(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() void UPS_Editor_MainWidget_Legacy::CloseSettingsPopup()
{ {
if (SettingsPopup.IsValid()) if (SettingsPopup.IsValid())

View File

@@ -107,7 +107,7 @@ private:
void ConfirmBaseLevelSelection(); void ConfirmBaseLevelSelection();
void CancelBaseLevelPopup(); 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<SWidget> SettingsPopup; TSharedPtr<SWidget> SettingsPopup;
void OpenSettingsPopup(); void OpenSettingsPopup();
void CloseSettingsPopup(); void CloseSettingsPopup();
@@ -116,6 +116,12 @@ private:
/** Toggle the navmesh visualization (P key handler). */ /** Toggle the navmesh visualization (P key handler). */
void ToggleShowNavMesh(); void ToggleShowNavMesh();
// Scenario type dropdown — populated from MasterCatalog::ScenarioTypeEnum, hidden when null.
TArray<TSharedPtr<FString>> ScenarioTypeOptions;
TSharedPtr<SComboBox<TSharedPtr<FString>>> ScenarioTypeCombo;
void RefreshScenarioTypeOptions();
FString GetCurrentScenarioTypeDisplayName() const;
// Transform fields // Transform fields
TSharedPtr<SEditableTextBox> LocX, LocY, LocZ; TSharedPtr<SEditableTextBox> LocX, LocY, LocZ;
TSharedPtr<SEditableTextBox> RotX, RotY, RotZ; TSharedPtr<SEditableTextBox> RotX, RotY, RotZ;