-using Content.Shared.DrawDepth;
+using Content.Client.UserInterface.Systems.Sandbox;
using Content.Shared.SubFloor;
using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Shared.Player;
namespace Content.Client.SubFloor;
public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IUserInterfaceManager _ui = default!;
private bool _showAll;
{
if (_showAll == value) return;
_showAll = value;
+ _ui.GetUIController<SandboxUIController>().SetToggleSubfloors(value);
- UpdateAll();
+ var ev = new ShowSubfloorRequestEvent()
+ {
+ Value = value,
+ };
+ RaiseNetworkEvent(ev);
}
}
base.Initialize();
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)
[UISystemDependency] private readonly DebugPhysicsSystem _debugPhysics = default!;
[UISystemDependency] private readonly MarkerSystem _marker = default!;
[UISystemDependency] private readonly SandboxSystem _sandbox = default!;
- [UISystemDependency] private readonly SubFloorHideSystem _subfloorHide = default!;
private SandboxWindow? _window;
_window.OnOpen += () => { SandboxButton!.Pressed = true; };
_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.ToggleFovButton.Pressed = !_eye.CurrentEye.DrawFov;
_window.ToggleShadowsButton.Pressed = !_light.DrawShadows;
- _window.ToggleSubfloorButton.Pressed = _subfloorHide.ShowAll;
_window.ShowMarkersButton.Pressed = _marker.MarkersVisible;
_window.ShowBbButton.Pressed = (_debugPhysics.Flags & PhysicsDebugFlags.Shapes) != 0x0;
_window.Close();
}
}
+
+ #region Buttons
+
+ public void SetToggleSubfloors(bool value)
+ {
+ if (_window == null)
+ return;
+
+ _window.ToggleSubfloorButton.Pressed = value;
+ }
+
+ #endregion
}
-using Robust.Client.AutoGenerated;
+using Content.Client.SubFloor;
+using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
[GenerateTypedNameReferences]
public sealed partial class SandboxWindow : DefaultWindow
{
+ [Dependency] private readonly IEntityManager _entManager = null!;
+
public SandboxWindow()
{
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;
}
}
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
- var mapManager = server.ResolveDependency<IMapManager>();
var entityMan = server.ResolveDependency<IEntityManager>();
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
var seriMan = server.ResolveDependency<ISerializationManager>();
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
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)
_visibilitySystem.RefreshVisibility(uid, visibilityComponent: visibility);
}
- SetCanSeeGhosts(uid, true);
-
+ _eye.RefreshVisibilityMask(uid);
var time = _gameTiming.CurTime;
component.TimeOfDeath = time;
}
}
// Entity can't see ghosts anymore.
- SetCanSeeGhosts(uid, false);
+ _eye.RefreshVisibilityMask(uid);
_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)
{
_actions.AddAction(uid, ref component.BooActionEntity, component.BooAction);
SubscribeLocalEvent<RevenantComponent, StatusEffectEndedEvent>(OnStatusEnded);
SubscribeLocalEvent<RoundEndTextAppendEvent>(_ => MakeVisible(true));
+ SubscribeLocalEvent<RevenantComponent, GetVisMaskEvent>(OnRevenantGetVis);
+
InitializeAbilities();
}
+ private void OnRevenantGetVis(Entity<RevenantComponent> ent, ref GetVisMaskEvent args)
+ {
+ args.VisibilityMask |= (int)VisibilityFlags.Ghost;
+ }
+
private void OnStartup(EntityUid uid, RevenantComponent component, ComponentStartup args)
{
//update the icon
}
//ghost vision
- if (TryComp(uid, out EyeComponent? eye))
- {
- _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) (VisibilityFlags.Ghost), eye);
- }
+ _eye.RefreshVisibilityMask(uid);
}
private void OnMapInit(EntityUid uid, RevenantComponent component, MapInitEvent args)
using Content.Shared.Construction.Components;
+using Content.Shared.Eye;
using Content.Shared.SubFloor;
+using Robust.Server.Player;
+using Robust.Shared.Enums;
using Robust.Shared.Map.Components;
+using Robust.Shared.Player;
namespace Content.Server.SubFloor;
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()
{
base.Initialize();
SubscribeLocalEvent<SubFloorHideComponent, AnchorAttemptEvent>(OnAnchorAttempt);
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)
None = 0,
Normal = 1 << 0,
Ghost = 1 << 1,
+ Subfloor = 1 << 2,
}
}
namespace Content.Shared.Ghost;
[RegisterComponent, NetworkedComponent, Access(typeof(SharedGhostSystem))]
-[AutoGenerateComponentState(true)]
+[AutoGenerateComponentState(true), AutoGenerateComponentPause]
public sealed partial class GhostComponent : Component
{
// Actions
// End actions
- [ViewVariables(VVAccess.ReadWrite), DataField]
+ [ViewVariables(VVAccess.ReadWrite), DataField, AutoPausedField]
public TimeSpan TimeOfDeath = TimeSpan.Zero;
[DataField("booRadius"), ViewVariables(VVAccess.ReadWrite)]
using Content.Shared.Audio;
using Content.Shared.Explosion;
+using Content.Shared.Eye;
using Content.Shared.Interaction.Events;
using Content.Shared.Maps;
using JetBrains.Annotations;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] protected readonly SharedMapSystem Map = default!;
[Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+ [Dependency] private readonly SharedVisibilitySystem _visibility = default!;
+
+ private EntityQuery<SubFloorHideComponent> _hideQuery;
public override void Initialize()
{
base.Initialize();
+ _hideQuery = GetEntityQuery<SubFloorHideComponent>();
+
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
SubscribeLocalEvent<SubFloorHideComponent, ComponentStartup>(OnSubFloorStarted);
SubscribeLocalEvent<SubFloorHideComponent, ComponentShutdown>(OnSubFloorTerminating);
return;
// Regardless of whether we're on a subfloor or not, unhide.
- component.IsUnderCover = false;
+ SetUnderCover((uid, component), false);
UpdateAppearance(uid, component);
}
}
else if (component.IsUnderCover)
{
- component.IsUnderCover = false;
+ SetUnderCover((uid, component), false);
UpdateAppearance(uid, component);
}
}
if (args.NewTile.Tile.IsEmpty)
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>
return;
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
- component.IsUnderCover = false;
+ SetUnderCover((uid, component), false);
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)
{
// TODO Redo this function. Currently wires on an asteroid are always "below the floor"
foreach (var uid in Map.GetAnchoredEntities(gridUid, grid, position))
{
- if (!TryComp(uid, out SubFloorHideComponent? hideComp))
+ if (!_hideQuery.TryComp(uid, out var hideComp))
continue;
if (hideComp.IsUnderCover == covered)
continue;
- hideComp.IsUnderCover = covered;
+ SetUnderCover((uid, hideComp), covered);
UpdateAppearance(uid, hideComp);
}
}
Appearance.SetData(uid, SubFloorVisuals.Covered, hideComp.IsUnderCover, appearance);
}
}
+
+ [Serializable, NetSerializable]
+ protected sealed class ShowSubfloorRequestEvent : EntityEventArgs
+ {
+ public bool Value;
+ }
}
[Serializable, NetSerializable]
+using Content.Shared.Eye;
+using Content.Shared.Hands;
using Content.Shared.Interaction;
-using Robust.Shared.Containers;
+using Content.Shared.Inventory.Events;
using Robust.Shared.GameStates;
-using Robust.Shared.Map;
using Robust.Shared.Serialization;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-using System.Linq;
namespace Content.Shared.SubFloor;
public abstract class SharedTrayScannerSystem : EntitySystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly SharedEyeSystem _eye = default!;
public const float SubfloorRevealAlpha = 0.8f;
SubscribeLocalEvent<TrayScannerComponent, ComponentGetState>(OnTrayScannerGetState);
SubscribeLocalEvent<TrayScannerComponent, ComponentHandleState>(OnTrayScannerHandleState);
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)
using Robust.Shared.GameStates;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic;
namespace Content.Shared.SubFloor
{
/// <remarks>
/// Useful for entities like vents, which are only partially hidden. Anchor attempts will still be blocked.
/// </remarks>
- [DataField("blockInteractions")]
+ [DataField]
public bool BlockInteractions { get; set; } = true;
/// <summary>
/// <remarks>
/// Useful for cables and piping, gives maint it's distinct noise.
/// </remarks>
- [DataField("blockAmbience")]
+ [DataField]
public bool BlockAmbience { get; set; } = true;
/// <summary>
/// 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.
/// </summary>
- [DataField("visibleLayers")]
- public HashSet<Enum> VisibleLayers = new() { SubfloorLayers.FirstLayer };
+ [DataField]
+ public HashSet<Enum> VisibleLayers = new();
/// <summary>
/// This is used for storing the original draw depth of a t-ray revealed entity.
/// <summary>
/// Whether the scanner is currently on.
/// </summary>
- [ViewVariables, DataField("enabled")] public bool Enabled;
+ [DataField]
+ public bool Enabled;
/// <summary>
/// Radius in which the scanner will reveal entities. Centered on the <see cref="LastLocation"/>.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField("range")]
+ [DataField]
public float Range = 4f;
}
--- /dev/null
+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;
type: NavMapBeaconBoundUserInterface
- type: Item
size: Small
+ - type: Visibility
+ layer: 1
- type: SubFloorHide
- type: Anchorable
- type: Construction
- type: Rotatable
- type: Transform
noRot: false
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/pump.rsi
layers:
- type: Rotatable
- type: Transform
noRot: false
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/pump.rsi
layers:
placement:
mode: SnapgridCenter
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/pump.rsi
layers:
mode: SnapgridCenter
components:
# TODO ATMOS: Give unique sprite.
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/pump.rsi
layers:
mode: SnapgridCenter
components:
- type: StationAiWhitelist
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/pump.rsi
layers:
placement:
mode: SnapgridCenter
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/gascanisterport.rsi
layers:
placement:
mode: SnapgridCenter
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
drawdepth: FloorObjects
sprite: Structures/Piping/Atmospherics/vent.rsi
id: HeatExchangerBend
suffix: Bend
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
layers:
- sprite: Structures/Piping/Atmospherics/pipe.rsi
placement:
mode: SnapgridCenter
components:
+ - type: Visibility
+ layer: 1
- type: Item
size: Normal
- type: Transform
placement:
mode: SnapgridCenter
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/gasfilter.rsi
layers:
placement:
mode: SnapgridCenter
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
sprite: Structures/Piping/Atmospherics/gasfilter.rsi
layers:
tags:
- GasVent
- Unstackable
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
drawdepth: FloorObjects
sprite: Structures/Piping/Atmospherics/vent.rsi
placement:
mode: SnapgridCenter
components:
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
drawdepth: FloorObjects
sprite: Structures/Piping/Atmospherics/vent.rsi
tags:
- GasScrubber
- Unstackable
+ - type: SubFloorHide
+ visibleLayers:
+ - enum.SubfloorLayers.FirstLayer
- type: Sprite
drawdepth: FloorObjects
sprite: Structures/Piping/Atmospherics/scrubber.rsi
sprite: Structures/Piping/disposal.rsi
visible: false
- type: Appearance
+ - type: Visibility
+ layer: 1
- type: SubFloorHide
- type: Clickable
- type: InteractionOutline
behaviors:
- !type:DoActsBehavior
acts: ["Destruction"]
+ - type: Visibility
+ layer: 1
- type: SubFloorHide
blockAmbience: false
blockInteractions: false
suffix: uncuttable
components:
- type: Cable
- cuttingQuality: null
\ No newline at end of file
+ cuttingQuality: null
placement:
mode: SnapgridCenter
components:
+ - type: Visibility
+ layer: 1
- type: Cable
cuttingDelay: 1
- type: Clickable