diff --git a/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs new file mode 100644 index 0000000000..07a8c04578 --- /dev/null +++ b/Content.Client/GameObjects/Components/Atmos/SiphonVisualizer.cs @@ -0,0 +1,68 @@ +using Content.Shared.GameObjects.Atmos; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.ResourceManagement; +using Robust.Shared.GameObjects.Components.Renderable; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Utility; +using System; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components.Atmos +{ + [UsedImplicitly] + public class SiphonVisualizer : AppearanceVisualizer + { + private RSI _siphonRSI; + + public override void LoadData(YamlMappingNode node) + { + base.LoadData(node); + + var rsiString = node.GetNode("siphonRSI").ToString(); + var rsiPath = SharedSpriteComponent.TextureRoot / rsiString; + try + { + var resourceCache = IoCManager.Resolve(); + var resource = resourceCache.GetResource(rsiPath); + _siphonRSI = resource.RSI; + } + catch (Exception e) + { + Logger.ErrorS("go.siphonvisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e); + } + } + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out ISpriteComponent sprite)) + { + return; + } + if (!component.TryGetData(SiphonVisuals.VisualState, out SiphonVisualState siphonVisualState)) + { + return; + } + + var siphonBaseState = "scrub"; + siphonBaseState += siphonVisualState.SiphonEnabled ? "On" : "Off"; + + sprite.LayerMapReserveBlank(Layer.SiphonBase); + var baseSiphonLayer = sprite.LayerMapGet(Layer.SiphonBase); + sprite.LayerSetRSI(baseSiphonLayer, _siphonRSI); + sprite.LayerSetState(baseSiphonLayer, siphonBaseState); + sprite.LayerSetVisible(baseSiphonLayer, true); + } + + private enum Layer + { + SiphonBase, + } + } +} diff --git a/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs b/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs new file mode 100644 index 0000000000..a30b2b9f9d --- /dev/null +++ b/Content.Client/GameObjects/Components/Atmos/VentVisualizer.cs @@ -0,0 +1,68 @@ +using Content.Shared.GameObjects.Atmos; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Client.Interfaces.ResourceManagement; +using Robust.Client.ResourceManagement; +using Robust.Shared.GameObjects.Components.Renderable; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Utility; +using System; +using YamlDotNet.RepresentationModel; + +namespace Content.Client.GameObjects.Components.Atmos +{ + [UsedImplicitly] + public class VentVisualizer : AppearanceVisualizer + { + private RSI _ventRSI; + + public override void LoadData(YamlMappingNode node) + { + base.LoadData(node); + + var rsiString = node.GetNode("ventRSI").ToString(); + var rsiPath = SharedSpriteComponent.TextureRoot / rsiString; + try + { + var resourceCache = IoCManager.Resolve(); + var resource = resourceCache.GetResource(rsiPath); + _ventRSI = resource.RSI; + } + catch (Exception e) + { + Logger.ErrorS("go.ventvisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e); + } + } + + public override void OnChangeData(AppearanceComponent component) + { + base.OnChangeData(component); + + if (!component.Owner.TryGetComponent(out ISpriteComponent sprite)) + { + return; + } + if (!component.TryGetData(VentVisuals.VisualState, out VentVisualState ventVisualState)) + { + return; + } + + var ventBaseState = "vent"; + ventBaseState += ventVisualState.VentEnabled ? "On" : "Off"; + + sprite.LayerMapReserveBlank(Layer.VentBase); + var baseVentLayer = sprite.LayerMapGet(Layer.VentBase); + sprite.LayerSetRSI(baseVentLayer, _ventRSI); + sprite.LayerSetState(baseVentLayer, ventBaseState); + sprite.LayerSetVisible(baseVentLayer, true); + } + + private enum Layer + { + VentBase, + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 783220aaf2..c0fd55dfb4 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -177,7 +177,8 @@ "ExtinguisherCabinetFilled", "FireExtinguisher", "Firelock", - "AtmosPlaque" + "AtmosPlaque", + "Spillable", }; } } diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index e29926951e..feddf591e5 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -11,10 +11,8 @@ namespace Content.IntegrationTests.Tests [TestFixture] public class PostMapInitTest : ContentIntegrationTest { - public readonly string[] SkippedMaps = - { - "/Maps/Pathfinding/simple.yml" - }; + public const bool SkipTestMaps = true; + public const string TestMapsPath = "/Maps/Test/"; [Test] public async Task NoSavedPostMapInitTest() @@ -34,7 +32,8 @@ namespace Content.IntegrationTests.Tests { var rootedPath = map.ToRootedPath(); - if (SkippedMaps.Contains(rootedPath.ToString())) + // ReSharper disable once RedundantLogicalConditionalExpressionOperand + if (SkipTestMaps && rootedPath.ToString().StartsWith(TestMapsPath)) { continue; } @@ -53,7 +52,7 @@ namespace Content.IntegrationTests.Tests var meta = root["meta"]; var postMapInit = meta["postmapinit"].AsBool(); - Assert.False(postMapInit); + Assert.False(postMapInit, $"Map {map.Filename} was saved postmapinit"); } } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs index da90efdad2..921a7c92be 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Scrubbers/BaseSiphonComponent.cs @@ -2,6 +2,8 @@ using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Atmos; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Log; using Robust.Shared.ViewVariables; @@ -23,6 +25,20 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping private AtmosphereSystem _atmosSystem; + [ViewVariables(VVAccess.ReadWrite)] + public bool SiphonEnabled + { + get => _siphonEnabled; + set + { + _siphonEnabled = value; + UpdateAppearance(); + } + } + private bool _siphonEnabled = true; + + private AppearanceComponent _appearance; + public override void Initialize() { base.Initialize(); @@ -40,10 +56,15 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } + Owner.TryGetComponent(out _appearance); + UpdateAppearance(); } public override void Update() { + if (!SiphonEnabled) + return; + var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere(_entityManager); if (tileAtmos == null) return; @@ -52,5 +73,10 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping } protected abstract void ScrubGas(GasMixture inletGas, GasMixture outletGas); + + private void UpdateAppearance() + { + _appearance?.SetData(SiphonVisuals.VisualState, new SiphonVisualState(SiphonEnabled)); + } } } diff --git a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs index a9240c3556..8176cdd315 100644 --- a/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs +++ b/Content.Server/GameObjects/Components/Atmos/Piping/Vents/BaseVentComponent.cs @@ -2,6 +2,8 @@ using Content.Server.GameObjects.Components.NodeContainer; using Content.Server.GameObjects.Components.NodeContainer.Nodes; using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Atmos; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Log; using Robust.Shared.ViewVariables; @@ -23,6 +25,20 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping private AtmosphereSystem _atmosSystem; + [ViewVariables(VVAccess.ReadWrite)] + public bool VentEnabled + { + get => _ventEnabled; + set + { + _ventEnabled = value; + UpdateAppearance(); + } + } + private bool _ventEnabled = true; + + private AppearanceComponent _appearance; + public override void Initialize() { base.Initialize(); @@ -40,10 +56,15 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}."); return; } + Owner.TryGetComponent(out _appearance); + UpdateAppearance(); } public override void Update() { + if (!VentEnabled) + return; + var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere(_entityManager); if (tileAtmos == null) return; @@ -52,5 +73,10 @@ namespace Content.Server.GameObjects.Components.Atmos.Piping } protected abstract void VentGas(GasMixture inletGas, GasMixture outletGas); + + private void UpdateAppearance() + { + _appearance?.SetData(VentVisuals.VisualState, new VentVisualState(VentEnabled)); + } } } diff --git a/Content.Server/Utility/NotifyExtensions.cs b/Content.Server/Utility/NotifyExtensions.cs index d4ae473f31..ef076ce1aa 100644 --- a/Content.Server/Utility/NotifyExtensions.cs +++ b/Content.Server/Utility/NotifyExtensions.cs @@ -13,12 +13,17 @@ namespace Content.Server.Utility /// /// The entity on which to popup the message. /// The message to show. + /// + /// The instance of player manager to use, will be resolved automatically + /// if null. + /// /// /// The range in which to search for players, defaulting to one screen. /// - public static void PopupMessageOtherClients(this IEntity source, string message, int range = 15) + public static void PopupMessageOtherClients(this IEntity source, string message, IPlayerManager playerManager = null, int range = 15) { - var playerManager = IoCManager.Resolve(); + playerManager ??= IoCManager.Resolve(); + var viewers = playerManager.GetPlayersInRange(source.Transform.Coordinates, range); foreach (var viewer in viewers) @@ -33,5 +38,24 @@ namespace Content.Server.Utility source.PopupMessage(viewer.AttachedEntity, message); } } + + /// + /// Pops up a message at the given entity's location for everyone, + /// including itself, to see. + /// + /// The entity above which to show the message. + /// The message to be seen. + /// + /// The instance of player manager to use, will be resolved automatically + /// if null. + /// + /// + /// The range in which to search for players, defaulting to one screen. + /// + public static void PopupMessageEveryone(this IEntity source, string message, IPlayerManager playerManager = null, int range = 15) + { + source.PopupMessage(message); + source.PopupMessageOtherClients(message, playerManager, range); + } } } diff --git a/Content.Shared/GameObjects/Atmos/SharedSiphonComponent.cs b/Content.Shared/GameObjects/Atmos/SharedSiphonComponent.cs new file mode 100644 index 0000000000..39be0e6ca7 --- /dev/null +++ b/Content.Shared/GameObjects/Atmos/SharedSiphonComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.GameObjects.Atmos +{ + [Serializable, NetSerializable] + public enum SiphonVisuals + { + VisualState + } + + [Serializable, NetSerializable] + public class SiphonVisualState + { + public readonly bool SiphonEnabled; + + public SiphonVisualState(bool siphonEnabled) + { + SiphonEnabled = siphonEnabled; + } + } +} diff --git a/Content.Shared/GameObjects/Atmos/SharedVentComponent.cs b/Content.Shared/GameObjects/Atmos/SharedVentComponent.cs new file mode 100644 index 0000000000..03c7d9d320 --- /dev/null +++ b/Content.Shared/GameObjects/Atmos/SharedVentComponent.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Serialization; +using System; + +namespace Content.Shared.GameObjects.Atmos +{ + [Serializable, NetSerializable] + public enum VentVisuals + { + VisualState + } + + [Serializable, NetSerializable] + public class VentVisualState + { + public readonly bool VentEnabled; + + public VentVisualState(bool ventEnabled) + { + VentEnabled = ventEnabled; + } + } +} diff --git a/Resources/Maps/Pathfinding/simple.yml b/Resources/Maps/Test/Pathfinding/simple.yml similarity index 100% rename from Resources/Maps/Pathfinding/simple.yml rename to Resources/Maps/Test/Pathfinding/simple.yml diff --git a/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml b/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml index 2372c3e65d..2dc4f1f3e9 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/scrubbers.yml @@ -12,7 +12,12 @@ - type: Icon texture: Constructible/Power/eightdirwire.png - type: Sprite - texture: Constructible/Power/eightdirwire.png + - type: Appearance + visuals: + - type: PipeVisualizer + pipeRSI: Constructible/Atmos/pipe.rsi + - type: SiphonVisualizer + siphonRSI: Constructible/Atmos/scrubber.rsi - type: Destructible thresholdvalue: 100 diff --git a/Resources/Prototypes/Entities/Constructible/Ground/vents.yml b/Resources/Prototypes/Entities/Constructible/Ground/vents.yml index 2940ec1b08..4c02001688 100644 --- a/Resources/Prototypes/Entities/Constructible/Ground/vents.yml +++ b/Resources/Prototypes/Entities/Constructible/Ground/vents.yml @@ -12,7 +12,12 @@ - type: Icon texture: Constructible/Power/eightdirwire.png - type: Sprite - texture: Constructible/Power/eightdirwire.png + - type: Appearance + visuals: + - type: PipeVisualizer + pipeRSI: Constructible/Atmos/pipe.rsi + - type: VentVisualizer + ventRSI: Constructible/Atmos/vent.rsi - type: Destructible thresholdvalue: 100 diff --git a/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml b/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml index a359afb625..85b1ad4e66 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/bar_sign.yml @@ -18,6 +18,7 @@ - type: entity id: LargeBarSign name: large bar sign + abstract: true components: - type: Clickable - type: InteractionOutline diff --git a/Resources/Textures/Constructible/Atmos/scrubber.rsi/meta.json b/Resources/Textures/Constructible/Atmos/scrubber.rsi/meta.json new file mode 100644 index 0000000000..2feeef5cfa --- /dev/null +++ b/Resources/Textures/Constructible/Atmos/scrubber.rsi/meta.json @@ -0,0 +1,48 @@ +{ + "version":1, + "size":{ + "x":32, + "y":32 + }, + "license":"CC-BY-SA-3.0", + "copyright":"Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da", + "states":[ + { + "name":"scrubOff", + "directions":1, + "delays":[ [ 1.0 ] ] + }, + { + "name":"scrubOn", + "directions":1, + "delays":[ + [ + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + } + ] +} diff --git a/Resources/Textures/Constructible/Atmos/scrubber.rsi/scrubOff.png b/Resources/Textures/Constructible/Atmos/scrubber.rsi/scrubOff.png new file mode 100644 index 0000000000..c4b16c7d0e Binary files /dev/null and b/Resources/Textures/Constructible/Atmos/scrubber.rsi/scrubOff.png differ diff --git a/Resources/Textures/Constructible/Atmos/scrubber.rsi/scrubOn.png b/Resources/Textures/Constructible/Atmos/scrubber.rsi/scrubOn.png new file mode 100644 index 0000000000..06a975cc5a Binary files /dev/null and b/Resources/Textures/Constructible/Atmos/scrubber.rsi/scrubOn.png differ diff --git a/Resources/Textures/Constructible/Atmos/vent.rsi/meta.json b/Resources/Textures/Constructible/Atmos/vent.rsi/meta.json new file mode 100644 index 0000000000..b5dbb04a19 --- /dev/null +++ b/Resources/Textures/Constructible/Atmos/vent.rsi/meta.json @@ -0,0 +1,28 @@ +{ + "version":1, + "size":{ + "x":32, + "y":32 + }, + "license":"CC-BY-SA-3.0", + "copyright":"Taken from https://github.com/tgstation/tgstation at commit 57cd1d59ca019dd0e7811ac451f295f818e573da", + "states":[ + { + "name":"ventOff", + "directions":1, + "delays":[ [ 1.0 ] ] + }, + { + "name":"ventOn", + "directions":1, + "delays":[ + [ + 0.08, + 0.08, + 0.08, + 0.08 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Constructible/Atmos/vent.rsi/ventOff.png b/Resources/Textures/Constructible/Atmos/vent.rsi/ventOff.png new file mode 100644 index 0000000000..e8e469dc4b Binary files /dev/null and b/Resources/Textures/Constructible/Atmos/vent.rsi/ventOff.png differ diff --git a/Resources/Textures/Constructible/Atmos/vent.rsi/ventOn.png b/Resources/Textures/Constructible/Atmos/vent.rsi/ventOn.png new file mode 100644 index 0000000000..33d42b8b10 Binary files /dev/null and b/Resources/Textures/Constructible/Atmos/vent.rsi/ventOn.png differ diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 311788d48c..be0b3eff72 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -102,6 +102,7 @@ True True True + True True True True