Sloth's subfloor vismask adventure (#35347)
* Add a subfloor vismask Significantly cuts down on sent entity count. * More optimisations * Fix command * Fixes * namespace cleanup * Review * Vismasks * Content update * Bandaid * awewa * Revert these * reh * Update Content.Shared/SubFloor/TrayScannerComponent.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
|||||||
using Content.Shared.DrawDepth;
|
using Content.Client.UserInterface.Systems.Sandbox;
|
||||||
using Content.Shared.SubFloor;
|
using Content.Shared.SubFloor;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
namespace Content.Client.SubFloor;
|
namespace Content.Client.SubFloor;
|
||||||
|
|
||||||
public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||||
|
|
||||||
private bool _showAll;
|
private bool _showAll;
|
||||||
|
|
||||||
@@ -18,8 +21,13 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
|||||||
{
|
{
|
||||||
if (_showAll == value) return;
|
if (_showAll == value) return;
|
||||||
_showAll = value;
|
_showAll = value;
|
||||||
|
_ui.GetUIController<SandboxUIController>().SetToggleSubfloors(value);
|
||||||
|
|
||||||
UpdateAll();
|
var ev = new ShowSubfloorRequestEvent()
|
||||||
|
{
|
||||||
|
Value = value,
|
||||||
|
};
|
||||||
|
RaiseNetworkEvent(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +36,20 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<SubFloorHideComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
SubscribeLocalEvent<SubFloorHideComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||||
|
SubscribeNetworkEvent<ShowSubfloorRequestEvent>(OnRequestReceived);
|
||||||
|
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerDetached(LocalPlayerDetachedEvent ev)
|
||||||
|
{
|
||||||
|
// Vismask resets so need to reset this.
|
||||||
|
ShowAll = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRequestReceived(ShowSubfloorRequestEvent ev)
|
||||||
|
{
|
||||||
|
// When client receives request Queue an update on all vis.
|
||||||
|
UpdateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAppearanceChanged(EntityUid uid, SubFloorHideComponent component, ref AppearanceChangeEvent args)
|
private void OnAppearanceChanged(EntityUid uid, SubFloorHideComponent component, ref AppearanceChangeEvent args)
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
|||||||
[UISystemDependency] private readonly DebugPhysicsSystem _debugPhysics = default!;
|
[UISystemDependency] private readonly DebugPhysicsSystem _debugPhysics = default!;
|
||||||
[UISystemDependency] private readonly MarkerSystem _marker = default!;
|
[UISystemDependency] private readonly MarkerSystem _marker = default!;
|
||||||
[UISystemDependency] private readonly SandboxSystem _sandbox = default!;
|
[UISystemDependency] private readonly SandboxSystem _sandbox = default!;
|
||||||
[UISystemDependency] private readonly SubFloorHideSystem _subfloorHide = default!;
|
|
||||||
|
|
||||||
private SandboxWindow? _window;
|
private SandboxWindow? _window;
|
||||||
|
|
||||||
@@ -117,10 +116,11 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
|||||||
|
|
||||||
_window.OnOpen += () => { SandboxButton!.Pressed = true; };
|
_window.OnOpen += () => { SandboxButton!.Pressed = true; };
|
||||||
_window.OnClose += () => { SandboxButton!.Pressed = false; };
|
_window.OnClose += () => { SandboxButton!.Pressed = false; };
|
||||||
|
|
||||||
|
// TODO: These need moving to opened so at least if they're not synced properly on open they work.
|
||||||
_window.ToggleLightButton.Pressed = !_light.Enabled;
|
_window.ToggleLightButton.Pressed = !_light.Enabled;
|
||||||
_window.ToggleFovButton.Pressed = !_eye.CurrentEye.DrawFov;
|
_window.ToggleFovButton.Pressed = !_eye.CurrentEye.DrawFov;
|
||||||
_window.ToggleShadowsButton.Pressed = !_light.DrawShadows;
|
_window.ToggleShadowsButton.Pressed = !_light.DrawShadows;
|
||||||
_window.ToggleSubfloorButton.Pressed = _subfloorHide.ShowAll;
|
|
||||||
_window.ShowMarkersButton.Pressed = _marker.MarkersVisible;
|
_window.ShowMarkersButton.Pressed = _marker.MarkersVisible;
|
||||||
_window.ShowBbButton.Pressed = (_debugPhysics.Flags & PhysicsDebugFlags.Shapes) != 0x0;
|
_window.ShowBbButton.Pressed = (_debugPhysics.Flags & PhysicsDebugFlags.Shapes) != 0x0;
|
||||||
|
|
||||||
@@ -219,4 +219,16 @@ public sealed class SandboxUIController : UIController, IOnStateChanged<Gameplay
|
|||||||
_window.Close();
|
_window.Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Buttons
|
||||||
|
|
||||||
|
public void SetToggleSubfloors(bool value)
|
||||||
|
{
|
||||||
|
if (_window == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_window.ToggleSubfloorButton.Pressed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Robust.Client.AutoGenerated;
|
using Content.Client.SubFloor;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
@@ -7,8 +8,18 @@ namespace Content.Client.UserInterface.Systems.Sandbox.Windows;
|
|||||||
[GenerateTypedNameReferences]
|
[GenerateTypedNameReferences]
|
||||||
public sealed partial class SandboxWindow : DefaultWindow
|
public sealed partial class SandboxWindow : DefaultWindow
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = null!;
|
||||||
|
|
||||||
public SandboxWindow()
|
public SandboxWindow()
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Opened()
|
||||||
|
{
|
||||||
|
base.Opened();
|
||||||
|
// Make sure state is up to date.
|
||||||
|
ToggleSubfloorButton.Pressed = _entManager.System<SubFloorHideSystem>().ShowAll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ public sealed class PrototypeSaveTest
|
|||||||
await using var pair = await PoolManager.GetServerClient();
|
await using var pair = await PoolManager.GetServerClient();
|
||||||
var server = pair.Server;
|
var server = pair.Server;
|
||||||
|
|
||||||
var mapManager = server.ResolveDependency<IMapManager>();
|
|
||||||
var entityMan = server.ResolveDependency<IEntityManager>();
|
var entityMan = server.ResolveDependency<IEntityManager>();
|
||||||
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
|
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
|
||||||
var seriMan = server.ResolveDependency<ISerializationManager>();
|
var seriMan = server.ResolveDependency<ISerializationManager>();
|
||||||
|
|||||||
@@ -100,6 +100,17 @@ namespace Content.Server.Ghost
|
|||||||
|
|
||||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
|
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
|
||||||
SubscribeLocalEvent<ToggleGhostVisibilityToAllEvent>(OnToggleGhostVisibilityToAll);
|
SubscribeLocalEvent<ToggleGhostVisibilityToAllEvent>(OnToggleGhostVisibilityToAll);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<GhostComponent, GetVisMaskEvent>(OnGhostVis);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGhostVis(Entity<GhostComponent> ent, ref GetVisMaskEvent args)
|
||||||
|
{
|
||||||
|
// If component not deleting they can see ghosts.
|
||||||
|
if (ent.Comp.LifeStage <= ComponentLifeStage.Running)
|
||||||
|
{
|
||||||
|
args.VisibilityMask |= (int)VisibilityFlags.Ghost;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGhostHearingAction(EntityUid uid, GhostComponent component, ToggleGhostHearingActionEvent args)
|
private void OnGhostHearingAction(EntityUid uid, GhostComponent component, ToggleGhostHearingActionEvent args)
|
||||||
@@ -186,8 +197,7 @@ namespace Content.Server.Ghost
|
|||||||
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCanSeeGhosts(uid, true);
|
_eye.RefreshVisibilityMask(uid);
|
||||||
|
|
||||||
var time = _gameTiming.CurTime;
|
var time = _gameTiming.CurTime;
|
||||||
component.TimeOfDeath = time;
|
component.TimeOfDeath = time;
|
||||||
}
|
}
|
||||||
@@ -207,21 +217,10 @@ namespace Content.Server.Ghost
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Entity can't see ghosts anymore.
|
// Entity can't see ghosts anymore.
|
||||||
SetCanSeeGhosts(uid, false);
|
_eye.RefreshVisibilityMask(uid);
|
||||||
_actions.RemoveAction(uid, component.BooActionEntity);
|
_actions.RemoveAction(uid, component.BooActionEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetCanSeeGhosts(EntityUid uid, bool canSee, EyeComponent? eyeComponent = null)
|
|
||||||
{
|
|
||||||
if (!Resolve(uid, ref eyeComponent, false))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (canSee)
|
|
||||||
_eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask | (int) VisibilityFlags.Ghost, eyeComponent);
|
|
||||||
else
|
|
||||||
_eye.SetVisibilityMask(uid, eyeComponent.VisibilityMask & ~(int) VisibilityFlags.Ghost, eyeComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, GhostComponent component, MapInitEvent args)
|
private void OnMapInit(EntityUid uid, GhostComponent component, MapInitEvent args)
|
||||||
{
|
{
|
||||||
_actions.AddAction(uid, ref component.BooActionEntity, component.BooAction);
|
_actions.AddAction(uid, ref component.BooActionEntity, component.BooAction);
|
||||||
|
|||||||
@@ -63,9 +63,16 @@ public sealed partial class RevenantSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<RevenantComponent, StatusEffectEndedEvent>(OnStatusEnded);
|
SubscribeLocalEvent<RevenantComponent, StatusEffectEndedEvent>(OnStatusEnded);
|
||||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
|
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
|
||||||
|
|
||||||
|
SubscribeLocalEvent<RevenantComponent, GetVisMaskEvent>(OnRevenantGetVis);
|
||||||
|
|
||||||
InitializeAbilities();
|
InitializeAbilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnRevenantGetVis(Entity<RevenantComponent> ent, ref GetVisMaskEvent args)
|
||||||
|
{
|
||||||
|
args.VisibilityMask |= (int)VisibilityFlags.Ghost;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnStartup(EntityUid uid, RevenantComponent component, ComponentStartup args)
|
private void OnStartup(EntityUid uid, RevenantComponent component, ComponentStartup args)
|
||||||
{
|
{
|
||||||
//update the icon
|
//update the icon
|
||||||
@@ -84,10 +91,7 @@ public sealed partial class RevenantSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
//ghost vision
|
//ghost vision
|
||||||
if (TryComp(uid, out EyeComponent? eye))
|
_eye.RefreshVisibilityMask(uid);
|
||||||
{
|
|
||||||
_eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) (VisibilityFlags.Ghost), eye);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMapInit(EntityUid uid, RevenantComponent component, MapInitEvent args)
|
private void OnMapInit(EntityUid uid, RevenantComponent component, MapInitEvent args)
|
||||||
|
|||||||
@@ -1,16 +1,76 @@
|
|||||||
using Content.Shared.Construction.Components;
|
using Content.Shared.Construction.Components;
|
||||||
|
using Content.Shared.Eye;
|
||||||
using Content.Shared.SubFloor;
|
using Content.Shared.SubFloor;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
namespace Content.Server.SubFloor;
|
namespace Content.Server.SubFloor;
|
||||||
|
|
||||||
public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||||
|
|
||||||
|
private HashSet<ICommonSession> _showFloors = new();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<SubFloorHideComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
SubscribeLocalEvent<SubFloorHideComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
||||||
SubscribeLocalEvent<SubFloorHideComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
SubscribeLocalEvent<SubFloorHideComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||||
|
SubscribeNetworkEvent<ShowSubfloorRequestEvent>(OnShowSubfloor);
|
||||||
|
SubscribeLocalEvent<GetVisMaskEvent>(OnGetVisibility);
|
||||||
|
|
||||||
|
_player.PlayerStatusChanged += OnPlayerStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerStatus(object? sender, SessionStatusEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.NewStatus == SessionStatus.Connected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_showFloors.Remove(e.Session);
|
||||||
|
|
||||||
|
if (e.Session.AttachedEntity != null)
|
||||||
|
_eye.RefreshVisibilityMask(e.Session.AttachedEntity.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetVisibility(ref GetVisMaskEvent ev)
|
||||||
|
{
|
||||||
|
if (!TryComp(ev.Entity, out ActorComponent? actor))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_showFloors.Contains(actor.PlayerSession))
|
||||||
|
{
|
||||||
|
ev.VisibilityMask |= (int)VisibilityFlags.Subfloor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShowSubfloor(ShowSubfloorRequestEvent ev, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
// TODO: Commands are a bit of an eh? for client-only but checking shared perms
|
||||||
|
var ent = args.SenderSession.AttachedEntity;
|
||||||
|
|
||||||
|
if (!TryComp(ent, out EyeComponent? eyeComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ev.Value)
|
||||||
|
{
|
||||||
|
_showFloors.Add(args.SenderSession);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_showFloors.Remove(args.SenderSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
_eye.RefreshVisibilityMask((ent.Value, eyeComp));
|
||||||
|
|
||||||
|
RaiseNetworkEvent(new ShowSubfloorRequestEvent()
|
||||||
|
{
|
||||||
|
Value = ev.Value,
|
||||||
|
}, args.SenderSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAnchorAttempt(EntityUid uid, SubFloorHideComponent component, AnchorAttemptEvent args)
|
private void OnAnchorAttempt(EntityUid uid, SubFloorHideComponent component, AnchorAttemptEvent args)
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ namespace Content.Shared.Eye
|
|||||||
None = 0,
|
None = 0,
|
||||||
Normal = 1 << 0,
|
Normal = 1 << 0,
|
||||||
Ghost = 1 << 1,
|
Ghost = 1 << 1,
|
||||||
|
Subfloor = 1 << 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using Robust.Shared.Prototypes;
|
|||||||
namespace Content.Shared.Ghost;
|
namespace Content.Shared.Ghost;
|
||||||
|
|
||||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedGhostSystem))]
|
[RegisterComponent, NetworkedComponent, Access(typeof(SharedGhostSystem))]
|
||||||
[AutoGenerateComponentState(true)]
|
[AutoGenerateComponentState(true), AutoGenerateComponentPause]
|
||||||
public sealed partial class GhostComponent : Component
|
public sealed partial class GhostComponent : Component
|
||||||
{
|
{
|
||||||
// Actions
|
// Actions
|
||||||
@@ -41,7 +41,7 @@ public sealed partial class GhostComponent : Component
|
|||||||
|
|
||||||
// End actions
|
// End actions
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField]
|
[ViewVariables(VVAccess.ReadWrite), DataField, AutoPausedField]
|
||||||
public TimeSpan TimeOfDeath = TimeSpan.Zero;
|
public TimeSpan TimeOfDeath = TimeSpan.Zero;
|
||||||
|
|
||||||
[DataField("booRadius"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("booRadius"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
using Content.Shared.Explosion;
|
using Content.Shared.Explosion;
|
||||||
|
using Content.Shared.Eye;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@@ -19,11 +20,16 @@ namespace Content.Shared.SubFloor
|
|||||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||||
[Dependency] protected readonly SharedMapSystem Map = default!;
|
[Dependency] protected readonly SharedMapSystem Map = default!;
|
||||||
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
|
||||||
|
[Dependency] private readonly SharedVisibilitySystem _visibility = default!;
|
||||||
|
|
||||||
|
private EntityQuery<SubFloorHideComponent> _hideQuery;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
_hideQuery = GetEntityQuery<SubFloorHideComponent>();
|
||||||
|
|
||||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||||
SubscribeLocalEvent<SubFloorHideComponent, ComponentStartup>(OnSubFloorStarted);
|
SubscribeLocalEvent<SubFloorHideComponent, ComponentStartup>(OnSubFloorStarted);
|
||||||
SubscribeLocalEvent<SubFloorHideComponent, ComponentShutdown>(OnSubFloorTerminating);
|
SubscribeLocalEvent<SubFloorHideComponent, ComponentShutdown>(OnSubFloorTerminating);
|
||||||
@@ -67,7 +73,7 @@ namespace Content.Shared.SubFloor
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Regardless of whether we're on a subfloor or not, unhide.
|
// Regardless of whether we're on a subfloor or not, unhide.
|
||||||
component.IsUnderCover = false;
|
SetUnderCover((uid, component), false);
|
||||||
UpdateAppearance(uid, component);
|
UpdateAppearance(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +86,7 @@ namespace Content.Shared.SubFloor
|
|||||||
}
|
}
|
||||||
else if (component.IsUnderCover)
|
else if (component.IsUnderCover)
|
||||||
{
|
{
|
||||||
component.IsUnderCover = false;
|
SetUnderCover((uid, component), false);
|
||||||
UpdateAppearance(uid, component);
|
UpdateAppearance(uid, component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +99,7 @@ namespace Content.Shared.SubFloor
|
|||||||
if (args.NewTile.Tile.IsEmpty)
|
if (args.NewTile.Tile.IsEmpty)
|
||||||
return; // Anything that was here will be unanchored anyways.
|
return; // Anything that was here will be unanchored anyways.
|
||||||
|
|
||||||
UpdateTile(args.NewTile.GridUid, Comp<MapGridComponent>(args.NewTile.GridUid), args.NewTile.GridIndices);
|
UpdateTile(args.NewTile.GridUid, args.Entity.Comp, args.NewTile.GridIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -105,13 +111,24 @@ namespace Content.Shared.SubFloor
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (xform.Anchored && TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
if (xform.Anchored && TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
||||||
component.IsUnderCover = HasFloorCover(xform.GridUid.Value, grid, Map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates));
|
SetUnderCover((uid, component), HasFloorCover(xform.GridUid.Value, grid, Map.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates)));
|
||||||
else
|
else
|
||||||
component.IsUnderCover = false;
|
SetUnderCover((uid, component), false);
|
||||||
|
|
||||||
UpdateAppearance(uid, component);
|
UpdateAppearance(uid, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetUnderCover(Entity<SubFloorHideComponent> entity, bool value)
|
||||||
|
{
|
||||||
|
// If it's not undercover or it always has visible layers then normal visibility.
|
||||||
|
_visibility.SetLayer(entity.Owner, value && entity.Comp.VisibleLayers.Count == 0 ? (ushort) VisibilityFlags.Subfloor : (ushort) VisibilityFlags.Normal);
|
||||||
|
|
||||||
|
if (entity.Comp.IsUnderCover == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entity.Comp.IsUnderCover = value;
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasFloorCover(EntityUid gridUid, MapGridComponent grid, Vector2i position)
|
public bool HasFloorCover(EntityUid gridUid, MapGridComponent grid, Vector2i position)
|
||||||
{
|
{
|
||||||
// TODO Redo this function. Currently wires on an asteroid are always "below the floor"
|
// TODO Redo this function. Currently wires on an asteroid are always "below the floor"
|
||||||
@@ -125,13 +142,13 @@ namespace Content.Shared.SubFloor
|
|||||||
|
|
||||||
foreach (var uid in Map.GetAnchoredEntities(gridUid, grid, position))
|
foreach (var uid in Map.GetAnchoredEntities(gridUid, grid, position))
|
||||||
{
|
{
|
||||||
if (!TryComp(uid, out SubFloorHideComponent? hideComp))
|
if (!_hideQuery.TryComp(uid, out var hideComp))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (hideComp.IsUnderCover == covered)
|
if (hideComp.IsUnderCover == covered)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
hideComp.IsUnderCover = covered;
|
SetUnderCover((uid, hideComp), covered);
|
||||||
UpdateAppearance(uid, hideComp);
|
UpdateAppearance(uid, hideComp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,6 +171,12 @@ namespace Content.Shared.SubFloor
|
|||||||
Appearance.SetData(uid, SubFloorVisuals.Covered, hideComp.IsUnderCover, appearance);
|
Appearance.SetData(uid, SubFloorVisuals.Covered, hideComp.IsUnderCover, appearance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
protected sealed class ShowSubfloorRequestEvent : EntityEventArgs
|
||||||
|
{
|
||||||
|
public bool Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
|
using Content.Shared.Eye;
|
||||||
|
using Content.Shared.Hands;
|
||||||
using Content.Shared.Interaction;
|
using Content.Shared.Interaction;
|
||||||
using Robust.Shared.Containers;
|
using Content.Shared.Inventory.Events;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Timing;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Content.Shared.SubFloor;
|
namespace Content.Shared.SubFloor;
|
||||||
|
|
||||||
public abstract class SharedTrayScannerSystem : EntitySystem
|
public abstract class SharedTrayScannerSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||||
|
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||||
|
|
||||||
public const float SubfloorRevealAlpha = 0.8f;
|
public const float SubfloorRevealAlpha = 0.8f;
|
||||||
|
|
||||||
@@ -22,6 +21,50 @@ public abstract class SharedTrayScannerSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<TrayScannerComponent, ComponentGetState>(OnTrayScannerGetState);
|
SubscribeLocalEvent<TrayScannerComponent, ComponentGetState>(OnTrayScannerGetState);
|
||||||
SubscribeLocalEvent<TrayScannerComponent, ComponentHandleState>(OnTrayScannerHandleState);
|
SubscribeLocalEvent<TrayScannerComponent, ComponentHandleState>(OnTrayScannerHandleState);
|
||||||
SubscribeLocalEvent<TrayScannerComponent, ActivateInWorldEvent>(OnTrayScannerActivate);
|
SubscribeLocalEvent<TrayScannerComponent, ActivateInWorldEvent>(OnTrayScannerActivate);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<TrayScannerComponent, GotEquippedHandEvent>(OnTrayHandEquipped);
|
||||||
|
SubscribeLocalEvent<TrayScannerComponent, GotUnequippedHandEvent>(OnTrayHandUnequipped);
|
||||||
|
SubscribeLocalEvent<TrayScannerComponent, GotEquippedEvent>(OnTrayEquipped);
|
||||||
|
SubscribeLocalEvent<TrayScannerComponent, GotUnequippedEvent>(OnTrayUnequipped);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<TrayScannerUserComponent, GetVisMaskEvent>(OnUserGetVis);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUserGetVis(Entity<TrayScannerUserComponent> ent, ref GetVisMaskEvent args)
|
||||||
|
{
|
||||||
|
args.VisibilityMask |= (int)VisibilityFlags.Subfloor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEquip(EntityUid user)
|
||||||
|
{
|
||||||
|
EnsureComp<TrayScannerUserComponent>(user);
|
||||||
|
_eye.RefreshVisibilityMask(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnequip(EntityUid user)
|
||||||
|
{
|
||||||
|
RemComp<TrayScannerUserComponent>(user);
|
||||||
|
_eye.RefreshVisibilityMask(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTrayHandUnequipped(Entity<TrayScannerComponent> ent, ref GotUnequippedHandEvent args)
|
||||||
|
{
|
||||||
|
OnUnequip(args.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTrayHandEquipped(Entity<TrayScannerComponent> ent, ref GotEquippedHandEvent args)
|
||||||
|
{
|
||||||
|
OnEquip(args.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTrayUnequipped(Entity<TrayScannerComponent> ent, ref GotUnequippedEvent args)
|
||||||
|
{
|
||||||
|
OnUnequip(args.Equipee);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTrayEquipped(Entity<TrayScannerComponent> ent, ref GotEquippedEvent args)
|
||||||
|
{
|
||||||
|
OnEquip(args.Equipee);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args)
|
private void OnTrayScannerActivate(EntityUid uid, TrayScannerComponent scanner, ActivateInWorldEvent args)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
|
|
||||||
|
|
||||||
namespace Content.Shared.SubFloor
|
namespace Content.Shared.SubFloor
|
||||||
{
|
{
|
||||||
@@ -27,7 +25,7 @@ namespace Content.Shared.SubFloor
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Useful for entities like vents, which are only partially hidden. Anchor attempts will still be blocked.
|
/// Useful for entities like vents, which are only partially hidden. Anchor attempts will still be blocked.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("blockInteractions")]
|
[DataField]
|
||||||
public bool BlockInteractions { get; set; } = true;
|
public bool BlockInteractions { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,15 +34,15 @@ namespace Content.Shared.SubFloor
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Useful for cables and piping, gives maint it's distinct noise.
|
/// Useful for cables and piping, gives maint it's distinct noise.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("blockAmbience")]
|
[DataField]
|
||||||
public bool BlockAmbience { get; set; } = true;
|
public bool BlockAmbience { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sprite layer keys for the layers that are always visible, even if the entity is below a floor tile. E.g.,
|
/// Sprite layer keys for the layers that are always visible, even if the entity is below a floor tile. E.g.,
|
||||||
/// the vent part of a vent is always visible, even though the piping is hidden.
|
/// the vent part of a vent is always visible, even though the piping is hidden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("visibleLayers")]
|
[DataField]
|
||||||
public HashSet<Enum> VisibleLayers = new() { SubfloorLayers.FirstLayer };
|
public HashSet<Enum> VisibleLayers = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is used for storing the original draw depth of a t-ray revealed entity.
|
/// This is used for storing the original draw depth of a t-ray revealed entity.
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ public sealed partial class TrayScannerComponent : Component
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the scanner is currently on.
|
/// Whether the scanner is currently on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables, DataField("enabled")] public bool Enabled;
|
[DataField]
|
||||||
|
public bool Enabled;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Radius in which the scanner will reveal entities. Centered on the <see cref="LastLocation"/>.
|
/// Radius in which the scanner will reveal entities. Centered on the <see cref="LastLocation"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables(VVAccess.ReadWrite), DataField("range")]
|
[DataField]
|
||||||
public float Range = 4f;
|
public float Range = 4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
Content.Shared/SubFloor/TrayScannerUserComponent.cs
Normal file
8
Content.Shared/SubFloor/TrayScannerUserComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Content.Shared.SubFloor;
|
||||||
|
|
||||||
|
// Don't need to network
|
||||||
|
/// <summary>
|
||||||
|
/// Added to anyone using <see cref="TrayScannerComponent"/> to handle the vismask changes.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class TrayScannerUserComponent : Component;
|
||||||
@@ -34,6 +34,8 @@
|
|||||||
type: NavMapBeaconBoundUserInterface
|
type: NavMapBeaconBoundUserInterface
|
||||||
- type: Item
|
- type: Item
|
||||||
size: Small
|
size: Small
|
||||||
|
- type: Visibility
|
||||||
|
layer: 1
|
||||||
- type: SubFloorHide
|
- type: SubFloorHide
|
||||||
- type: Anchorable
|
- type: Anchorable
|
||||||
- type: Construction
|
- type: Construction
|
||||||
|
|||||||
@@ -39,6 +39,9 @@
|
|||||||
- type: Rotatable
|
- type: Rotatable
|
||||||
- type: Transform
|
- type: Transform
|
||||||
noRot: false
|
noRot: false
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/pump.rsi
|
sprite: Structures/Piping/Atmospherics/pump.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -95,6 +98,9 @@
|
|||||||
- type: Rotatable
|
- type: Rotatable
|
||||||
- type: Transform
|
- type: Transform
|
||||||
noRot: false
|
noRot: false
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/pump.rsi
|
sprite: Structures/Piping/Atmospherics/pump.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -149,6 +155,9 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/pump.rsi
|
sprite: Structures/Piping/Atmospherics/pump.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -184,6 +193,9 @@
|
|||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
# TODO ATMOS: Give unique sprite.
|
# TODO ATMOS: Give unique sprite.
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/pump.rsi
|
sprite: Structures/Piping/Atmospherics/pump.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -237,6 +249,9 @@
|
|||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
- type: StationAiWhitelist
|
- type: StationAiWhitelist
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/pump.rsi
|
sprite: Structures/Piping/Atmospherics/pump.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -300,6 +315,9 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/gascanisterport.rsi
|
sprite: Structures/Piping/Atmospherics/gascanisterport.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -334,6 +352,9 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
drawdepth: FloorObjects
|
drawdepth: FloorObjects
|
||||||
sprite: Structures/Piping/Atmospherics/vent.rsi
|
sprite: Structures/Piping/Atmospherics/vent.rsi
|
||||||
@@ -501,6 +522,9 @@
|
|||||||
id: HeatExchangerBend
|
id: HeatExchangerBend
|
||||||
suffix: Bend
|
suffix: Bend
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
layers:
|
layers:
|
||||||
- sprite: Structures/Piping/Atmospherics/pipe.rsi
|
- sprite: Structures/Piping/Atmospherics/pipe.rsi
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: Visibility
|
||||||
|
layer: 1
|
||||||
- type: Item
|
- type: Item
|
||||||
size: Normal
|
size: Normal
|
||||||
- type: Transform
|
- type: Transform
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/gasfilter.rsi
|
sprite: Structures/Piping/Atmospherics/gasfilter.rsi
|
||||||
layers:
|
layers:
|
||||||
@@ -84,6 +87,9 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Structures/Piping/Atmospherics/gasfilter.rsi
|
sprite: Structures/Piping/Atmospherics/gasfilter.rsi
|
||||||
layers:
|
layers:
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
tags:
|
tags:
|
||||||
- GasVent
|
- GasVent
|
||||||
- Unstackable
|
- Unstackable
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
drawdepth: FloorObjects
|
drawdepth: FloorObjects
|
||||||
sprite: Structures/Piping/Atmospherics/vent.rsi
|
sprite: Structures/Piping/Atmospherics/vent.rsi
|
||||||
@@ -83,6 +86,9 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
drawdepth: FloorObjects
|
drawdepth: FloorObjects
|
||||||
sprite: Structures/Piping/Atmospherics/vent.rsi
|
sprite: Structures/Piping/Atmospherics/vent.rsi
|
||||||
@@ -120,6 +126,9 @@
|
|||||||
tags:
|
tags:
|
||||||
- GasScrubber
|
- GasScrubber
|
||||||
- Unstackable
|
- Unstackable
|
||||||
|
- type: SubFloorHide
|
||||||
|
visibleLayers:
|
||||||
|
- enum.SubfloorLayers.FirstLayer
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
drawdepth: FloorObjects
|
drawdepth: FloorObjects
|
||||||
sprite: Structures/Piping/Atmospherics/scrubber.rsi
|
sprite: Structures/Piping/Atmospherics/scrubber.rsi
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
sprite: Structures/Piping/disposal.rsi
|
sprite: Structures/Piping/disposal.rsi
|
||||||
visible: false
|
visible: false
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
|
- type: Visibility
|
||||||
|
layer: 1
|
||||||
- type: SubFloorHide
|
- type: SubFloorHide
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
- type: InteractionOutline
|
- type: InteractionOutline
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
behaviors:
|
behaviors:
|
||||||
- !type:DoActsBehavior
|
- !type:DoActsBehavior
|
||||||
acts: ["Destruction"]
|
acts: ["Destruction"]
|
||||||
|
- type: Visibility
|
||||||
|
layer: 1
|
||||||
- type: SubFloorHide
|
- type: SubFloorHide
|
||||||
blockAmbience: false
|
blockAmbience: false
|
||||||
blockInteractions: false
|
blockInteractions: false
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
placement:
|
placement:
|
||||||
mode: SnapgridCenter
|
mode: SnapgridCenter
|
||||||
components:
|
components:
|
||||||
|
- type: Visibility
|
||||||
|
layer: 1
|
||||||
- type: Cable
|
- type: Cable
|
||||||
cuttingDelay: 1
|
cuttingDelay: 1
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
|
|||||||
Reference in New Issue
Block a user