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:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user