Files
UnrealPrototyping/Source/Salty/Core/SaltyCheats.cpp
T
2026-09-25 17:02:24 +03:00

202 lines
7.0 KiB
C++

// 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<UTelemetrySubsystem>() : 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<UWorld> World;
TSharedPtr<SWorldMap> Map;
TSharedPtr<SWidget> Container;
};
TArray<FWorldMapOverlay> 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<UWorldMapSubsystem>() : 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<UWorldMapDefinition*>(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<float>(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 <id>: 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<FString>& 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());
}));
}