From 880d4b7249ef4f1d60c6241f02855f74904d9b53 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Thu, 28 Mar 2019 11:14:03 +0100 Subject: [PATCH] Improved icon smoothing to use ECS properly, cables smooth now. --- Content.Client/Content.Client.csproj | 1 + Content.Client/EntryPoint.cs | 2 +- .../IconSmoothing/IconSmoothComponent.cs | 243 ++++---------- .../EntitySystems/IconSmoothSystem.cs | 304 ++++++++++++++++++ Resources/Prototypes/Entities/Power.yml | 10 +- .../Objects/power_cable.rsi/cable_0.png | Bin 0 -> 174 bytes .../Objects/power_cable.rsi/cable_1.png | Bin 0 -> 182 bytes .../Objects/power_cable.rsi/cable_10.png | Bin 0 -> 249 bytes .../Objects/power_cable.rsi/cable_11.png | Bin 0 -> 298 bytes .../Objects/power_cable.rsi/cable_12.png | Bin 0 -> 119 bytes .../Objects/power_cable.rsi/cable_13.png | Bin 0 -> 271 bytes .../Objects/power_cable.rsi/cable_14.png | Bin 0 -> 268 bytes .../Objects/power_cable.rsi/cable_15.png | Bin 0 -> 339 bytes .../Objects/power_cable.rsi/cable_2.png | Bin 0 -> 186 bytes .../Objects/power_cable.rsi/cable_3.png | Bin 0 -> 131 bytes .../Objects/power_cable.rsi/cable_4.png | Bin 0 -> 179 bytes .../Objects/power_cable.rsi/cable_5.png | Bin 0 -> 235 bytes .../Objects/power_cable.rsi/cable_6.png | Bin 0 -> 197 bytes .../Objects/power_cable.rsi/cable_7.png | Bin 0 -> 283 bytes .../Objects/power_cable.rsi/cable_8.png | Bin 0 -> 174 bytes .../Objects/power_cable.rsi/cable_9.png | Bin 0 -> 202 bytes .../Objects/power_cable.rsi/meta.json | 73 +++++ 22 files changed, 459 insertions(+), 174 deletions(-) create mode 100644 Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_0.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_1.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_10.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_11.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_12.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_13.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_14.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_15.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_2.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_3.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_4.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_5.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_6.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_7.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_8.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/cable_9.png create mode 100644 Resources/Textures/Objects/power_cable.rsi/meta.json diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 24f52e8351..7e484e6eee 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -90,6 +90,7 @@ + diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 6b9bcbdcd4..ac22770bb2 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -3,7 +3,7 @@ using Content.Client.GameObjects.Components.Actor; using Content.Client.GameObjects.Components.Clothing; using Content.Client.GameObjects.Components.Construction; using Content.Client.GameObjects.Components.Power; -using Content.Client.GameObjects.Components.SmoothWalling; +using Content.Client.GameObjects.Components.IconSmoothing; using Content.Client.GameObjects.Components.Storage; using Content.Client.GameObjects.Components.Weapons.Ranged; using Content.Client.GameTicking; diff --git a/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs b/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs index 18db2fb3e2..e416f4d69c 100644 --- a/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs +++ b/Content.Client/GameObjects/Components/IconSmoothing/IconSmoothComponent.cs @@ -1,18 +1,14 @@ -using System; -using System.Collections.Generic; -using Content.Shared.GameObjects; +using System.Diagnostics.CodeAnalysis; +using Content.Client.GameObjects.EntitySystems; +using JetBrains.Annotations; using SS14.Client.Interfaces.GameObjects.Components; using SS14.Shared.GameObjects; using SS14.Shared.GameObjects.Components.Transform; -using SS14.Shared.Interfaces.GameObjects; -using SS14.Shared.Interfaces.Map; -using SS14.Shared.IoC; using SS14.Shared.Map; -using SS14.Shared.Maths; using SS14.Shared.Serialization; using static SS14.Client.GameObjects.SpriteComponent; -namespace Content.Client.GameObjects.Components.SmoothWalling +namespace Content.Client.GameObjects.Components.IconSmoothing { // TODO: Potential improvements: // Defer updating of these. @@ -25,212 +21,115 @@ namespace Content.Client.GameObjects.Components.SmoothWalling /// To use, set base equal to the prefix of the corner states in the sprite base RSI. /// Any objects with the same key will connect. /// - public class IconSmoothComponent : Component, IComponentDebug + public sealed class IconSmoothComponent : Component { + private string _smoothKey; + private string _stateBase; + private IconSmoothingMode _mode; + public override string Name => "IconSmooth"; - ISpriteComponent Sprite; - SnapGridComponent SnapGrid; - - /// - /// Prepended to the RSI state. - /// - string StateBase; + internal ISpriteComponent Sprite { get; private set; } + internal SnapGridComponent SnapGrid { get; private set; } + private (GridId, MapIndices) _lastPosition; /// /// We will smooth with other objects with the same key. /// - string SmoothKey; + public string SmoothKey => _smoothKey; - IconSmoothComponent[] Neighbors = new IconSmoothComponent[8]; - // "Use an array". - // Nah. I'm too lazy. This is easy to understand compared to the enum value fuckery if I used an array. - // Deal with it. - CornerFill CornerSE; - CornerFill CornerNE; - CornerFill CornerNW; - CornerFill CornerSW; + /// + /// Prepended to the RSI state. + /// + public string StateBase => _stateBase; + + /// + /// Mode that controls how the icon should be selected. + /// + public IconSmoothingMode Mode => _mode; + + /// + /// Used by to reduce redundant updates. + /// + internal int UpdateGeneration { get; set; } public override void Initialize() { base.Initialize(); - var state0 = $"{StateBase}0"; SnapGrid = Owner.GetComponent(); Sprite = Owner.GetComponent(); - Sprite.LayerMapSet(CornerLayers.SE, Sprite.AddLayerState(state0)); - Sprite.LayerSetDirOffset(CornerLayers.SE, DirectionOffset.None); - Sprite.LayerMapSet(CornerLayers.NE, Sprite.AddLayerState(state0)); - Sprite.LayerSetDirOffset(CornerLayers.NE, DirectionOffset.CounterClockwise); - Sprite.LayerMapSet(CornerLayers.NW, Sprite.AddLayerState(state0)); - Sprite.LayerSetDirOffset(CornerLayers.NW, DirectionOffset.Flip); - Sprite.LayerMapSet(CornerLayers.SW, Sprite.AddLayerState(state0)); - Sprite.LayerSetDirOffset(CornerLayers.SW, DirectionOffset.Clockwise); } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataFieldCached(ref StateBase, "base", ""); - serializer.DataFieldCached(ref SmoothKey, "key", null); + + serializer.DataFieldCached(ref _stateBase, "base", ""); + serializer.DataFieldCached(ref _smoothKey, "key", null); + serializer.DataFieldCached(ref _mode, "mode", IconSmoothingMode.Corners); } public override void Startup() { base.Startup(); - SnapGrid.OnPositionChanged += SnapGridPositionChanged; - - UpdateConnections(true); - UpdateIcon(); + SnapGrid.OnPositionChanged += SnapGridOnPositionChanged; + Owner.EntityManager.RaiseEvent(Owner, new IconSmoothDirtyEvent(null, SnapGrid.Offset, Mode)); + var state0 = $"{StateBase}0"; + if (Mode == IconSmoothingMode.Corners) + { + Sprite.LayerMapSet(CornerLayers.SE, Sprite.AddLayerState(state0)); + Sprite.LayerSetDirOffset(CornerLayers.SE, DirectionOffset.None); + Sprite.LayerMapSet(CornerLayers.NE, Sprite.AddLayerState(state0)); + Sprite.LayerSetDirOffset(CornerLayers.NE, DirectionOffset.CounterClockwise); + Sprite.LayerMapSet(CornerLayers.NW, Sprite.AddLayerState(state0)); + Sprite.LayerSetDirOffset(CornerLayers.NW, DirectionOffset.Flip); + Sprite.LayerMapSet(CornerLayers.SW, Sprite.AddLayerState(state0)); + Sprite.LayerSetDirOffset(CornerLayers.SW, DirectionOffset.Clockwise); + } } public override void Shutdown() { + SnapGrid.OnPositionChanged -= SnapGridOnPositionChanged; + Owner.EntityManager.RaiseEvent(Owner, new IconSmoothDirtyEvent(_lastPosition, SnapGrid.Offset, Mode)); + base.Shutdown(); - - SnapGrid.OnPositionChanged -= SnapGridPositionChanged; - SayGoodbyes(); } - void SayGoodbyes() + private void SnapGridOnPositionChanged() { - foreach (var neighbor in Neighbors) - { - // Goodbye neighbor. - neighbor?.UpdateConnections(false); - neighbor?.UpdateIcon(); - } + Owner.EntityManager.RaiseEvent(Owner, new IconSmoothDirtyEvent(_lastPosition, SnapGrid.Offset, Mode)); + _lastPosition = (Owner.Transform.GridID, SnapGrid.Position); } - void SnapGridPositionChanged() - { - SayGoodbyes(); - UpdateConnections(true); - UpdateIcon(); - } - - void UpdateIcon() - { - // Try to turn this into a loop without hard to understand bit fuckery or 20 lines of helper functions. - // Challenge: do it in less lines. - // I dare you. - // No cheating like putting everything on a single line. Proper code conventions. - // This comment does not count, btw. - - Sprite.LayerSetState(CornerLayers.NE, $"{StateBase}{(int)CornerNE}"); - Sprite.LayerSetState(CornerLayers.SE, $"{StateBase}{(int)CornerSE}"); - Sprite.LayerSetState(CornerLayers.SW, $"{StateBase}{(int)CornerSW}"); - Sprite.LayerSetState(CornerLayers.NW, $"{StateBase}{(int)CornerNW}"); - } - - void UpdateConnections(bool propagate) - { - for (int i = 0; i < Neighbors.Length; i++) - { - var found = false; - var dir = (Direction)i; - foreach (var entity in SnapGrid.GetInDir(dir)) - { - if (entity.TryGetComponent(out IconSmoothComponent smooth) && smooth.SmoothKey == SmoothKey) - { - Neighbors[i] = smooth; - if (propagate) - { - smooth.UpdateConnections(false); - smooth.UpdateIcon(); - } - // Temptation to use goto: 10. - found = true; - break; - } - } - - if (!found) - { - Neighbors[i] = null; - } - } - - CornerNE = CornerSE = CornerNW = CornerSW = CornerFill.None; - - // "Use a loop". - // Well screw that I did the exact same thing while writing lighting corner population code in BYOND. - // This is 10x easier to understand and write than a complex loop working with the internet mapping of direction flags. - if (Neighbors[(int)Direction.North] != null) - { - CornerNE |= CornerFill.CounterClockwise; - CornerNW |= CornerFill.Clockwise; - } - if (Neighbors[(int)Direction.NorthEast] != null) - { - CornerNE |= CornerFill.Diagonal; - } - if (Neighbors[(int)Direction.East] != null) - { - CornerNE |= CornerFill.Clockwise; - CornerSE |= CornerFill.CounterClockwise; - } - if (Neighbors[(int)Direction.SouthEast] != null) - { - CornerSE |= CornerFill.Diagonal; - } - if (Neighbors[(int)Direction.South] != null) - { - CornerSE |= CornerFill.Clockwise; - CornerSW |= CornerFill.CounterClockwise; - } - if (Neighbors[(int)Direction.SouthWest] != null) - { - CornerSW |= CornerFill.Diagonal; - } - if (Neighbors[(int)Direction.West] != null) - { - CornerSW |= CornerFill.Clockwise; - CornerNW |= CornerFill.CounterClockwise; - } - if (Neighbors[(int)Direction.NorthWest] != null) - { - CornerNW |= CornerFill.Diagonal; - } - } - - public string GetDebugString() - { - return string.Format( - "N/NE/E/SE/S/SW/W/NW: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{7} cfill NE/SE/SW/NW: {8}/{9}/{10}/{11}", - Neighbors[(int)Direction.North]?.Owner?.Uid, - Neighbors[(int)Direction.NorthEast]?.Owner?.Uid, - Neighbors[(int)Direction.East]?.Owner?.Uid, - Neighbors[(int)Direction.SouthEast]?.Owner?.Uid, - Neighbors[(int)Direction.South]?.Owner?.Uid, - Neighbors[(int)Direction.SouthWest]?.Owner?.Uid, - Neighbors[(int)Direction.West]?.Owner?.Uid, - Neighbors[(int)Direction.NorthWest]?.Owner?.Uid, - CornerNE, CornerSE, CornerSW, CornerNW - ); - } - - enum CornerLayers + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum CornerLayers { SE, NE, NW, SW, } + } - [Flags] - enum CornerFill : byte - { - // These values are pulled from Baystation12. - // I'm too lazy to convert the state names. - None = 0, - // The cardinal tile counter-clockwise of this corner is filled. - CounterClockwise = 1, - // The diagonal tile in the direction of this corner. - Diagonal = 2, - // The cardinal tile clockwise of this corner is filled. - Clockwise = 4, - } + /// + /// Controls the mode with which icon smoothing is calculated. + /// + [PublicAPI] + public enum IconSmoothingMode + { + /// + /// Each icon is made up of 4 corners, each of which can get a different state depending on + /// adjacent entities clockwise, counter-clockwise and diagonal with the corner. + /// + Corners, + + /// + /// There are 16 icons, only one of which is used at once. + /// The icon selected is a bit field made up of the cardinal direction flags that have adjacent entities. + /// + CardinalFlags, } } diff --git a/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs b/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs new file mode 100644 index 0000000000..77aa222196 --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/IconSmoothSystem.cs @@ -0,0 +1,304 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Client.GameObjects.Components.IconSmoothing; +using JetBrains.Annotations; +using SS14.Client.Interfaces.GameObjects.Components; +using SS14.Shared.GameObjects; +using SS14.Shared.GameObjects.Components.Transform; +using SS14.Shared.GameObjects.Systems; +using SS14.Shared.Interfaces.GameObjects; +using SS14.Shared.Interfaces.Map; +using SS14.Shared.IoC; +using SS14.Shared.Map; +using SS14.Shared.Maths; + +namespace Content.Client.GameObjects.EntitySystems +{ + /// + /// Entity system implementing the logic for + /// + [UsedImplicitly] + internal sealed class IconSmoothSystem : EntitySystem + { +#pragma warning disable 649 + [Dependency] private readonly IMapManager _mapManager; +#pragma warning restore 649 + + private readonly Queue _dirtyEntities = new Queue(); + + private int _generation; + + public override void SubscribeEvents() + { + base.SubscribeEvents(); + + SubscribeEvent(HandleDirtyEvent); + } + + public override void Initialize() + { + base.Initialize(); + + IoCManager.InjectDependencies(this); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + + if (_dirtyEntities.Count == 0) + { + return; + } + + _generation += 1; + + // Performance: This could be spread over multiple updates, or made parallel. + while (_dirtyEntities.Count > 0) + { + CalculateNewSprite(_dirtyEntities.Dequeue()); + } + } + + private void HandleDirtyEvent(object sender, IconSmoothDirtyEvent ev) + { + // Yes, we updates ALL smoothing entities surrounding us even if they would never smooth with us. + // This is simpler to implement. If you want to optimize it be my guest. + if (sender is IEntity senderEnt && senderEnt.IsValid() && + senderEnt.HasComponent()) + { + var snapGrid = senderEnt.GetComponent(); + + _dirtyEntities.Enqueue(senderEnt); + AddValidEntities(snapGrid.GetInDir(Direction.North)); + AddValidEntities(snapGrid.GetInDir(Direction.South)); + AddValidEntities(snapGrid.GetInDir(Direction.East)); + AddValidEntities(snapGrid.GetInDir(Direction.West)); + if (ev.Mode == IconSmoothingMode.Corners) + { + + AddValidEntities(snapGrid.GetInDir(Direction.NorthEast)); + AddValidEntities(snapGrid.GetInDir(Direction.SouthEast)); + AddValidEntities(snapGrid.GetInDir(Direction.SouthWest)); + AddValidEntities(snapGrid.GetInDir(Direction.NorthWest)); + } + } + else if (ev.LastPosition.HasValue) + { + // Entity is no longer valid, update around the last position it was at. + var grid = _mapManager.GetGrid(ev.LastPosition.Value.grid); + var pos = ev.LastPosition.Value.pos; + + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(1, 0), ev.Offset)); + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(-1, 0), ev.Offset)); + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(0, 1), ev.Offset)); + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(0, -1), ev.Offset)); + if (ev.Mode == IconSmoothingMode.Corners) + { + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(1, 1), ev.Offset)); + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(-1, -1), ev.Offset)); + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(-1, 1), ev.Offset)); + AddValidEntities(grid.GetSnapGridCell(pos + new MapIndices(1, -1), ev.Offset)); + } + } + } + + private void AddValidEntities(IEnumerable candidates) + { + foreach (var entity in candidates) + { + if (entity.HasComponent()) + { + _dirtyEntities.Enqueue(entity); + } + } + } + + private void AddValidEntities(IEnumerable candidates) + { + AddValidEntities(candidates.Select(c => c.Owner)); + } + + private void CalculateNewSprite(IEntity entity) + { + // The generation check prevents updating an entity multiple times per tick. + // As it stands now, it's totally possible for something to get queued twice. + // Generation on the component is set after an update so we can cull updates that happened this generation. + if (!entity.IsValid() + || !entity.TryGetComponent(out IconSmoothComponent smoothing) + || smoothing.UpdateGeneration == _generation) + { + return; + } + + var sprite = smoothing.Sprite; + var snapGrid = smoothing.SnapGrid; + + switch (smoothing.Mode) + { + case IconSmoothingMode.Corners: + _calculateNewSpriteCorers(smoothing, snapGrid, sprite); + break; + + case IconSmoothingMode.CardinalFlags: + _calculateNewSpriteCardinal(smoothing, snapGrid, sprite); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + smoothing.UpdateGeneration = _generation; + } + + private static void _calculateNewSpriteCardinal(IconSmoothComponent smoothing, SnapGridComponent snapGrid, + ISpriteComponent sprite) + { + var dirs = CardinalConnectDirs.None; + + if (MatchingEntity(smoothing, snapGrid.GetInDir(Direction.North))) + dirs |= CardinalConnectDirs.North; + if (MatchingEntity(smoothing, snapGrid.GetInDir(Direction.South))) + dirs |= CardinalConnectDirs.South; + if (MatchingEntity(smoothing, snapGrid.GetInDir(Direction.East))) + dirs |= CardinalConnectDirs.East; + if (MatchingEntity(smoothing, snapGrid.GetInDir(Direction.West))) + dirs |= CardinalConnectDirs.West; + + sprite.LayerSetState(0, $"{smoothing.StateBase}{(int) dirs}"); + } + + private static void _calculateNewSpriteCorers(IconSmoothComponent smoothing, SnapGridComponent snapGrid, + ISpriteComponent sprite) + { + var n = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.North)); + var ne = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.NorthEast)); + var e = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.East)); + var se = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.SouthEast)); + var s = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.South)); + var sw = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.SouthWest)); + var w = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.West)); + var nw = MatchingEntity(smoothing, snapGrid.GetInDir(Direction.NorthWest)); + + // ReSharper disable InconsistentNaming + var cornerNE = CornerFill.None; + var cornerSE = CornerFill.None; + var cornerSW = CornerFill.None; + var cornerNW = CornerFill.None; + // ReSharper restore InconsistentNaming + + if (n) + { + cornerNE |= CornerFill.CounterClockwise; + cornerNW |= CornerFill.Clockwise; + } + + if (ne) + { + cornerNE |= CornerFill.Diagonal; + } + + if (e) + { + cornerNE |= CornerFill.Clockwise; + cornerSE |= CornerFill.CounterClockwise; + } + + if (se) + { + cornerSE |= CornerFill.Diagonal; + } + + if (s) + { + cornerSE |= CornerFill.Clockwise; + cornerSW |= CornerFill.CounterClockwise; + } + + if (sw) + { + cornerSW |= CornerFill.Diagonal; + } + + if (w) + { + cornerSW |= CornerFill.Clockwise; + cornerNW |= CornerFill.CounterClockwise; + } + + if (nw) + { + cornerNW |= CornerFill.Diagonal; + } + + sprite.LayerSetState(IconSmoothComponent.CornerLayers.NE, $"{smoothing.StateBase}{(int) cornerNE}"); + sprite.LayerSetState(IconSmoothComponent.CornerLayers.SE, $"{smoothing.StateBase}{(int) cornerSE}"); + sprite.LayerSetState(IconSmoothComponent.CornerLayers.SW, $"{smoothing.StateBase}{(int) cornerSW}"); + sprite.LayerSetState(IconSmoothComponent.CornerLayers.NW, $"{smoothing.StateBase}{(int) cornerNW}"); + } + + [System.Diagnostics.Contracts.Pure] + private static bool MatchingEntity(IconSmoothComponent source, IEnumerable candidates) + { + foreach (var entity in candidates) + { + if (!entity.TryGetComponent(out IconSmoothComponent other)) + { + return false; + } + + if (other.SmoothKey == source.SmoothKey) + { + return true; + } + } + + return false; + } + + [Flags] + private enum CardinalConnectDirs : byte + { + None = 0, + North = 1, + South = 2, + East = 4, + West = 8 + } + + [Flags] + private enum CornerFill : byte + { + // These values are pulled from Baystation12. + // I'm too lazy to convert the state names. + None = 0, + + // The cardinal tile counter-clockwise of this corner is filled. + CounterClockwise = 1, + + // The diagonal tile in the direction of this corner. + Diagonal = 2, + + // The cardinal tile clockwise of this corner is filled. + Clockwise = 4, + } + } + + /// + /// Event raised by a when it needs to be recalculated. + /// + public sealed class IconSmoothDirtyEvent : EntitySystemMessage + { + public IconSmoothDirtyEvent((GridId grid, MapIndices pos)? lastPosition, SnapGridOffset offset, IconSmoothingMode mode) + { + LastPosition = lastPosition; + Offset = offset; + Mode = mode; + } + + public (GridId grid, MapIndices pos)? LastPosition { get; } + public SnapGridOffset Offset { get; } + public IconSmoothingMode Mode { get; } + } +} diff --git a/Resources/Prototypes/Entities/Power.yml b/Resources/Prototypes/Entities/Power.yml index e9a96b26f7..7487ddadd2 100644 --- a/Resources/Prototypes/Entities/Power.yml +++ b/Resources/Prototypes/Entities/Power.yml @@ -6,12 +6,20 @@ - type: Clickable - type: BoundingBox - type: Sprite + netsync: false drawdepth: BelowFloor color: Red - texture: Objects/eightdirwire.png + sprite: Objects/power_cable.rsi + state: cable_0 - type: Icon texture: Objects/eightdirwire.png - type: PowerTransfer + - type: SnapGrid + offset: Center + - type: IconSmooth + base: cable_ + key: power_cables + mode: CardinalFlags snap: - Wire diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_0.png b/Resources/Textures/Objects/power_cable.rsi/cable_0.png new file mode 100644 index 0000000000000000000000000000000000000000..580528df530f5dd3f6c9d881241dd602b99f762e GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6io1R zaSVw#{Pxm8-UA98t%cPEw{Y^!iVqpHA8h!%&rh;h$c;=$32S z%9lmdKI;Vst0L?x^RR910 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_1.png b/Resources/Textures/Objects/power_cable.rsi/cable_1.png new file mode 100644 index 0000000000000000000000000000000000000000..fdebed5fa92767023dc731a89eb894ad0c4755fa GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6wL5+ zaSVw#{C3(w-UA98t_xqr?P9ey5U}3C>XY{P;9|2({w*i#)rF?mSkx|)zEgWsdud6i zs^G(`v!h;@{c}9&z`!KXz`&yLp`OwBw4g*}!nL(q=an@IdfY3p`zs-6V}4&%324iM Z&oRalqDpggd?x{U44$rjF6*2UngC|_IsO0u literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_10.png b/Resources/Textures/Objects/power_cable.rsi/cable_10.png new file mode 100644 index 0000000000000000000000000000000000000000..17c40b2276177a68bf427a437a72c8baf6e68445 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6kP4; z;usQf`0bR7oQD+z+P0<$hZitx|8Un__Jr&mC-%Z=+3Sx433&@8E4BW1sQGZE|9`+S zjwS_;CJCN)@vW^>gpNmQmac8sx=riB-uRPOEVL~$*RF22e*VClZJWz1;Tu{VhMNQq zMLT$%Y!TR#%~ajxRxzU)1x*Xw7yWX+ zp?vKYvjc~t#q<2)Yo%}FSq;7J{bqP9{3yRxL5lgv20sn~ua#4#RnKb+;0;(N5jaC! t=$h3>ALblHvv N44$rjF6*2UngD8DA_xEg literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_13.png b/Resources/Textures/Objects/power_cable.rsi/cable_13.png new file mode 100644 index 0000000000000000000000000000000000000000..6f03458fd3a1f72580b6cecd9c0cc85db25360d1 GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6g=qZ z;usQf`0dn#yoVKdTz#kdMl@RI9ON+&u-?GRb4X`PUsh+Bz@%k?{9kvg6zTjseuV7- z%h$%@b@L)0v)Q-tZfH!MHX-QMv8~sxR`V;iY*k#dbxWwuM`G1gmB-Wj?Uuu=Dt_e|NB? zGDnjF$D^c*%HZ1`nuQTtueCExOZ~aVYh@N+`MFSYCk}g~#cQ1Re*en1;NdihATyHH!C^b7N29?H;tuj`>M4cC1z@}H6M|D zc$i5eaqV9#<;@R+nbM6_8MZU#v6VdZ`1<|rf%^>SD_*NE6`XJ&mnH7rbP0l+XkKfY)oP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_15.png b/Resources/Textures/Objects/power_cable.rsi/cable_15.png new file mode 100644 index 0000000000000000000000000000000000000000..d45a7333240a151e6cf2c30eadef01cfc96e75f5 GIT binary patch literal 339 zcmV-Z0j&OsP)*k<|(CW z2(AYp_rvY;JadF$nkM@n9&mS0WAK6yf-TEpZw2s)leKNTjva+)a)ogZiMtR0p!x7j zYHc2?b9#m_EGF(6CFJ{zU@;humg;*!qDO>7AbXe%K&YSR(C3D3zoD!U_~>&%+?+{y zF}WZB2?nJ`*`BHt5>54jtSTUSWH_!6M666NIR|33en$M%NeGC(?>9MeR{VRrq1ypV lc3(e8imm<`Re+xWcmf|&p}nP@=Cc3*002ovPDHLkV1gW6i8ufN literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_2.png b/Resources/Textures/Objects/power_cable.rsi/cable_2.png new file mode 100644 index 0000000000000000000000000000000000000000..ce3ffea8a4abb7171dd26267b65f5052097dcf9d GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6wL8- zaSVw#{C4t2UIs-D*QhrAiP>}w_Ly!Jt>xk6+$?yWIK5+kJJbTsq z(|e;{f4F*PT5U(h?fr9Roj)xbD!aVa%fv~L^Q!&wR=MAE=FI#&l_e_sp?gH$e-?(dsvCQggdg?#c_n|KAH86qr9th=HMgyRGS# zYs;#Y9tBpvzihdx%DO(@*N%^WYnb`w({| Y5T78${9Q^)9%vzhr>mdKI;Vst0InW9!~g&Q literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_5.png b/Resources/Textures/Objects/power_cable.rsi/cable_5.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0ffe9db5c5472e508c3a8a4c9ad6f71edfa53c GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6rAhn z;usQf`0doQybTIGt=)yfVGsDCH!w4AWR5s%VXC+s&X~)hd4F#b=QlQIP?^j z`)9|_DNb1{TAkiwc=iW#Ef0%BLxt3WkSMM#tCiw(4=Odai>x;;Kej#llu}mw4Ego*C*M92wM?&fh3UHO=Mt8e2o{7rh@H&@ cblL)I;p{rAh{ir^1t5>X)78&qol`;+0C-DL-~a#s literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_6.png b/Resources/Textures/Objects/power_cable.rsi/cable_6.png new file mode 100644 index 0000000000000000000000000000000000000000..2d855fe035ebba6d3358b753d65aa93597a4e8f4 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6fF02 zaSVw#{C46--UA9eEQ}k)a}M&&n{?Jg=A;no5(SICMf=;>O8)l)H8L<59Lwg;T6=wF zP?i>>i@~DlLKi>yUCGdW6c^?7alOugBEiij&uf0$y~{iIDe|`9!JvJ*2UgX*W?Id1 p`rH4A111jEl}7ga7#QsYA22*))|$l;c=IdBSWj0!mvv4FO#rW`MJE6N literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_7.png b/Resources/Textures/Objects/power_cable.rsi/cable_7.png new file mode 100644 index 0000000000000000000000000000000000000000..e382deeda1165e9c97293f517623e9c64909c505 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6g=zc z;usQf_-)ul!6O0!Pu1pT9JG&p;pkX(fwgtT(VJ&Z{BYFsc=oO=y@vJu&FR~BPboQM z8U9zTr6VeP(Ue77ZG%od`>X${K!H)k!Ck*YGs@3FTu1%E-sQ(6lGgVAf6a7)>G_-% zfk(-b2|aK1eoQ$M!fC{r5wzjloSSLcTq;4aHx6Dt&zbsYLZ+u;*4EdTBp*zPHVSa# zEj^uzrZtjg<;H>S*R$d_~B eoNypqpn};yKC)?-BBvJ6V+@|IelF{r5}E+9qjM+# literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_8.png b/Resources/Textures/Objects/power_cable.rsi/cable_8.png new file mode 100644 index 0000000000000000000000000000000000000000..408fdf51ea09924ba4726c454ce9eb616e7b80bb GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6io1R zaSVw#{PyBO-UA98t%<&Oytfyyvn8=_KX5YVWvp*b{@?xF?2MnCI2aiI#NIKTar2Ar zbDKTi?k%tO(mee)^Ul3x8Ebo^vTbJiDiwWMxytTq$-l|M6-Db;f7va}z|g=Qyim%O TElwjHXcvR0tDnm{r-UW|5^g>O literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/cable_9.png b/Resources/Textures/Objects/power_cable.rsi/cable_9.png new file mode 100644 index 0000000000000000000000000000000000000000..2b988d3ffe696ac0afff4d99cfd6ad7ed5307284 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?31We{epSZZGe6s+-d zaSVw#{C4U=&I1ZOu9GkAE#XeT(7LH__ErrRn~+;=Q=ER^Kib^>y{E%R{YupGYQ>hd zQ=F6zrKBvJ?#RG&hEYzRAv}Ta!pG%Pe$)$pu=)FzA-^(tuU6jb?R(hI)LBnmwd0}g ud&b2XE=&D8MbBBvv#l^W<^i;r;fINJ!wW6-WUu#uAg-sYpUXO@geCyKTS*iE literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/power_cable.rsi/meta.json b/Resources/Textures/Objects/power_cable.rsi/meta.json new file mode 100644 index 0000000000..7e3b1a9d68 --- /dev/null +++ b/Resources/Textures/Objects/power_cable.rsi/meta.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "cable_0", + "directions": 1 + }, + { + "name": "cable_1", + "directions": 1 + }, + { + "name": "cable_2", + "directions": 1 + }, + { + "name": "cable_3", + "directions": 1 + }, + { + "name": "cable_4", + "directions": 1 + }, + { + "name": "cable_5", + "directions": 1 + }, + { + "name": "cable_6", + "directions": 1 + }, + { + "name": "cable_7", + "directions": 1 + }, + { + "name": "cable_8", + "directions": 1 + }, + { + "name": "cable_9", + "directions": 1 + }, + { + "name": "cable_10", + "directions": 1 + }, + { + "name": "cable_11", + "directions": 1 + }, + { + "name": "cable_12", + "directions": 1 + }, + { + "name": "cable_13", + "directions": 1 + }, + { + "name": "cable_14", + "directions": 1 + }, + { + "name": "cable_15", + "directions": 1 + }, + ] +}