// The bs.* console commands. Every one marks the session as cheated through the telemetry subsystem, so a // playtest file that had a cheat in it can be filtered out later. Each step adds its own commands here. #include "Core/TelemetrySubsystem.h" #include "Engine/GameInstance.h" #include "Engine/GameViewportClient.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" #include "HAL/IConsoleManager.h" #include "Salty.h" #include "Styling/CoreStyle.h" #include "UI/SWorldMap.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" #include "World/WorldMapDefinition.h" #include "World/WorldMapSubsystem.h" namespace { UTelemetrySubsystem* TelemetryFor(UWorld* World) { const UGameInstance* GameInstance = World ? World->GetGameInstance() : nullptr; return GameInstance ? GameInstance->GetSubsystem() : nullptr; } // The world map overlay, per world. Per world rather than one static, because PIE with two clients is two // worlds in one process and a single pointer would hand the second client the first one's widget. struct FWorldMapOverlay { TWeakObjectPtr World; TSharedPtr Map; TSharedPtr Container; }; TArray WorldMapOverlays; FWorldMapOverlay* FindOverlay(const UWorld* World) { return WorldMapOverlays.FindByPredicate([World](const FWorldMapOverlay& Entry) { return Entry.World.Get() == World; }); } void CloseWorldMap(UWorld* World) { const int32 Index = WorldMapOverlays.IndexOfByPredicate( [World](const FWorldMapOverlay& Entry) { return Entry.World.Get() == World; }); if (Index == INDEX_NONE) { return; } if (UGameViewportClient* Viewport = World ? World->GetGameViewport() : nullptr) { Viewport->RemoveViewportWidgetContent(WorldMapOverlays[Index].Container.ToSharedRef()); } if (APlayerController* Controller = World ? World->GetFirstPlayerController() : nullptr) { Controller->SetInputMode(FInputModeGameOnly()); Controller->bShowMouseCursor = false; } WorldMapOverlays.RemoveAt(Index); } void OpenWorldMap(UWorld* World) { UGameViewportClient* Viewport = World ? World->GetGameViewport() : nullptr; UWorldMapSubsystem* Subsystem = World ? World->GetSubsystem() : nullptr; if (!Viewport || !Subsystem) { return; } // const_cast for the same reason UWorldMapWidget does it: the subsystem hands out a const view of // shared content and the widget holds a strong pointer it cannot make const. Nothing writes to it. UWorldMapDefinition* Definition = const_cast(Subsystem->GetDefinition()); FWorldMapOverlay Entry; Entry.World = World; // Slate straight into the viewport rather than a UMG asset, so opening the map needs no content at // all. When there is a HUD this becomes a key on it and WBP_WorldMap owns the frame instead. Entry.Container = SNew(SBox) .Padding(48.f) [ SNew(SBorder) .BorderImage(FCoreStyle::Get().GetBrush("GenericWhiteBox")) .BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.85f)) .Padding(2.f) [ SAssignNew(Entry.Map, SWorldMap) .Definition(Definition) .MarkerSource(Subsystem) ] ]; Entry.Map->SetFollowLocalPlayer(true); Viewport->AddViewportWidgetContent(Entry.Container.ToSharedRef(), 100); if (APlayerController* Controller = World->GetFirstPlayerController()) { // GameAndUI, not UIOnly: this is a map you glance at, and the game does not stop while it is open. FInputModeGameAndUI InputMode; InputMode.SetWidgetToFocus(Entry.Container); InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); Controller->SetInputMode(InputMode); Controller->bShowMouseCursor = true; } Subsystem->NoteMapOpened(Entry.Map->GetLayer(), static_cast(Entry.Map->GetMetresPerPixel()), TEXT("bs.WorldMap")); WorldMapOverlays.Add(MoveTemp(Entry)); } // bs.TelemetryTest: proves the seam end to end from the console. Emits cheat_used and taints the session. FAutoConsoleCommandWithWorld CmdTelemetryTest( TEXT("bs.TelemetryTest"), TEXT("Emits a cheat_used telemetry event and marks the session as cheated."), FConsoleCommandWithWorldDelegate::CreateLambda([](UWorld* World) { if (UTelemetrySubsystem* Telemetry = TelemetryFor(World)) { Telemetry->MarkCheatUsed(TEXT("bs.TelemetryTest")); } })); // bs.WorldMap: shows the world map over the viewport, or takes it away again. The only way in until there // is a HUD with a key on it; the widget it opens is the one the HUD will open. FAutoConsoleCommandWithWorld CmdWorldMap( TEXT("bs.WorldMap"), TEXT("Toggles the world map. Drag to pan, wheel to zoom, right-double-click to fit."), FConsoleCommandWithWorldDelegate::CreateLambda([](UWorld* World) { if (!World) { return; } if (UTelemetrySubsystem* Telemetry = TelemetryFor(World)) { Telemetry->MarkCheatUsed(TEXT("bs.WorldMap")); } if (FindOverlay(World)) { CloseWorldMap(World); } else { OpenWorldMap(World); } })); // bs.WorldMapFollow: put the view back on the player and keep it there. Panning drops the follow on purpose // - a map that snaps back as you let go cannot be read - so this is the way back to it. Left-double-click // on the map does the same thing without a console. FAutoConsoleCommandWithWorld CmdWorldMapFollow( TEXT("bs.WorldMapFollow"), TEXT("Centres the open world map on the local player and follows them again."), FConsoleCommandWithWorldDelegate::CreateLambda([](UWorld* World) { if (UTelemetrySubsystem* Telemetry = TelemetryFor(World)) { Telemetry->MarkCheatUsed(TEXT("bs.WorldMapFollow")); } FWorldMapOverlay* Overlay = FindOverlay(World); if (!Overlay || !Overlay->Map.IsValid()) { UE_LOG(LogSalty, Display, TEXT("bs.WorldMapFollow: the map is not open. bs.WorldMap first.")); return; } if (!Overlay->Map->FocusOnLocalPlayer()) { UE_LOG(LogSalty, Display, TEXT("bs.WorldMapFollow: there is no local body to follow.")); } })); // bs.WorldMapLayer : relief, colour, satellite, climate - whatever RawContent/World/MapArt/layers.json // says. With no argument it cycles, which is what you want while looking at one place. FAutoConsoleCommandWithWorldAndArgs CmdWorldMapLayer( TEXT("bs.WorldMapLayer"), TEXT("Switches the open world map to a layer by id, or cycles when given none."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& Args, UWorld* World) { if (UTelemetrySubsystem* Telemetry = TelemetryFor(World)) { Telemetry->MarkCheatUsed(TEXT("bs.WorldMapLayer")); } FWorldMapOverlay* Overlay = FindOverlay(World); if (!Overlay || !Overlay->Map.IsValid()) { UE_LOG(LogSalty, Display, TEXT("bs.WorldMapLayer: the map is not open. bs.WorldMap first.")); return; } if (Args.Num() > 0) { Overlay->Map->SetLayer(FName(*Args[0])); } else { Overlay->Map->NextLayer(); } UE_LOG(LogSalty, Display, TEXT("World map layer: %s"), *Overlay->Map->GetLayer().ToString()); })); }