InteractiveComputer/Display/PaneOSRtScreenBridge.cs
using System;
using System.Linq;
using Sandbox;
namespace PaneOS.InteractiveComputer.Display;
/// <summary>
/// Creates the PaneOS world-panel/camera side of an in-world computer display.
/// s&box's whitelist blocks reflection, so RT Screens package components should be assigned/configured in the editor.
/// </summary>
public sealed class PaneOSRtScreenBridge : Component
{
[Property] public InteractiveComputerComponent? Computer { get; set; }
[Property] public GameObject? DisplayObject { get; set; }
[Property] public bool CreatePaneOSScreenChild { get; set; } = true;
[Property] public string ScreenChildName { get; set; } = "PaneOS Screen";
[Property] public bool EnsureWorldPanel { get; set; } = true;
[Property] public string ScreenId { get; set; } = "";
[Property] public bool AutoGenerateScreenId { get; set; } = true;
[Property] public bool CreateCameraSource { get; set; } = true;
[Property] public string CameraChildName { get; set; } = "PaneOS RT Camera";
[Property] public bool ConfigureEveryStart { get; set; } = true;
[Property] public Component? RtScreenComponent { get; set; }
public GameObject? PaneOSScreenObject { get; private set; }
public CameraComponent? SourceCamera { get; private set; }
public Texture? RenderTarget { get; private set; }
private Vector2? lastResolvedResolution;
protected override void OnStart()
{
base.OnStart();
Setup();
}
protected override void OnUpdate()
{
base.OnUpdate();
SyncLiveResolution();
}
protected override void OnValidate()
{
base.OnValidate();
if ( ConfigureEveryStart )
{
try
{
Setup();
}
catch ( Exception ex )
{
Log.Warning( $"PaneOS RT screen bridge validation on {GameObject.Name} could not finish setup yet: {ex.Message}" );
}
}
}
public void Setup()
{
var display = DisplayObject ?? GameObject;
var computer = Computer ?? display.Components.Get<InteractiveComputerComponent>( FindMode.InSelf | FindMode.InAncestors | FindMode.InDescendants );
if ( computer is null )
{
Log.Warning( $"PaneOS RT screen bridge on {GameObject.Name} has no computer assigned." );
return;
}
Computer = computer;
var screenObject = ResolvePaneOSScreenObject( display, computer );
ResolveCameraSource( display, computer );
lastResolvedResolution = ResolveResolution( computer );
WarnIfRtScreenNeedsManualSetup( screenObject );
}
private GameObject ResolvePaneOSScreenObject( GameObject display, InteractiveComputerComponent computer )
{
if ( !CreatePaneOSScreenChild )
{
PaneOSScreenObject = display;
return display;
}
var existing = display.Children.FirstOrDefault( x => x.Name == ScreenChildName );
var screen = existing ?? new GameObject( ScreenChildName );
if ( existing is null )
screen.SetParent( display );
PaneOSScreenObject = screen;
ConfigurePaneOSWorldPanel( screen, computer );
var desktop = ResolvePaneOSDesktopComponent( screen );
if ( desktop is not null )
{
desktop.Computer = computer;
desktop.VisibleOnlyWhenInteracting = false;
}
return screen;
}
private void ConfigurePaneOSWorldPanel( GameObject screen, InteractiveComputerComponent computer )
{
if ( !EnsureWorldPanel )
return;
var worldPanel = screen.Components.Get<WorldPanel>( FindMode.InSelf );
if ( worldPanel is null )
worldPanel = screen.Components.Create<WorldPanel>();
worldPanel.PanelSize = ResolveResolution( computer );
worldPanel.RenderScale = 1.0f;
worldPanel.LookAtCamera = false;
worldPanel.InteractionRange = 0.0f;
}
private CameraComponent? ResolveCameraSource( GameObject display, InteractiveComputerComponent computer )
{
if ( !CreateCameraSource )
return null;
var cameraObject = display.Children.FirstOrDefault( x => x.Name == CameraChildName );
if ( cameraObject is null )
{
cameraObject = new GameObject( CameraChildName );
cameraObject.SetParent( display );
}
var camera = cameraObject.Components.Get<CameraComponent>( FindMode.InSelf );
if ( camera is null )
camera = cameraObject.Components.Create<CameraComponent>();
var targetSize = ResolveResolution( computer );
RenderTarget = Texture.CreateRenderTarget( ResolveScreenId( computer ), ImageFormat.RGBA8888, targetSize, camera.RenderTarget );
camera.RenderTarget = RenderTarget;
camera.CustomSize = targetSize;
SourceCamera = camera;
return camera;
}
private void SyncLiveResolution()
{
if ( Computer is null )
return;
var resolution = ResolveResolution( Computer );
if ( lastResolvedResolution.HasValue && lastResolvedResolution.Value == resolution )
return;
lastResolvedResolution = resolution;
if ( PaneOSScreenObject is not null && EnsureWorldPanel )
{
var worldPanel = PaneOSScreenObject.Components.Get<WorldPanel>( FindMode.InSelf );
if ( worldPanel is not null )
worldPanel.PanelSize = resolution;
}
if ( SourceCamera is not null )
{
RenderTarget = Texture.CreateRenderTarget( ResolveScreenId( Computer ), ImageFormat.RGBA8888, resolution, SourceCamera.RenderTarget );
SourceCamera.RenderTarget = RenderTarget;
SourceCamera.CustomSize = resolution;
}
}
private ComputerDesktop? ResolvePaneOSDesktopComponent( GameObject screen )
{
var desktop = screen.Components.Get<ComputerDesktop>( FindMode.InSelf );
if ( desktop is not null )
return desktop;
return screen.Components.Create<ComputerDesktop>();
}
private void WarnIfRtScreenNeedsManualSetup( GameObject screenObject )
{
if ( RtScreenComponent is null )
return;
Log.Info( $"PaneOS generated screen '{screenObject.Name}', camera '{SourceCamera?.GameObject.Name}', render target '{ResolveScreenId( Computer! )}'. Assign those values to the RT Screens component in the editor." );
}
private string ResolveScreenId( InteractiveComputerComponent computer )
{
if ( !string.IsNullOrWhiteSpace( ScreenId ) )
return ScreenId;
var generated = $"paneos-{computer.ComputerId}";
if ( AutoGenerateScreenId )
ScreenId = generated;
return generated;
}
private static Vector2 ResolveResolution( InteractiveComputerComponent computer )
{
if ( computer.Runtime is not null )
return new Vector2( computer.Runtime.State.ResolutionX, computer.Runtime.State.ResolutionY );
return new Vector2( computer.ResolutionX, computer.ResolutionY );
}
}