diff --git a/Content.Client/Parallax/ParallaxOverlay.cs b/Content.Client/Parallax/ParallaxOverlay.cs index 642cdec896..fe16b25a99 100644 --- a/Content.Client/Parallax/ParallaxOverlay.cs +++ b/Content.Client/Parallax/ParallaxOverlay.cs @@ -1,4 +1,5 @@ using Content.Client.Parallax.Managers; +using Content.Shared._Afterlight.ThirdDimension; using Content.Shared.CCVar; using Content.Shared.Parallax.Biomes; using Robust.Client.Graphics; @@ -19,6 +20,7 @@ public sealed class ParallaxOverlay : Overlay [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IParallaxManager _manager = default!; private readonly ParallaxSystem _parallax; + private readonly SharedZLevelSystem _zlevel = default!; public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld; @@ -27,11 +29,12 @@ public sealed class ParallaxOverlay : Overlay ZIndex = ParallaxSystem.ParallaxZIndex; IoCManager.InjectDependencies(this); _parallax = _entManager.System(); + _zlevel = _entManager.System(); } protected override bool BeforeDraw(in OverlayDrawArgs args) { - if (args.MapId == MapId.Nullspace || _entManager.HasComponent(_mapManager.GetMapEntityId(args.MapId))) + if (args.MapId == MapId.Nullspace || _entManager.HasComponent(_mapManager.GetMapEntityId(args.MapId)) || _zlevel.MapBelow[(int)args.MapId] != null) return false; return true; diff --git a/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs b/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs index d16b61317d..e628ce03a6 100644 --- a/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs +++ b/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs @@ -1,5 +1,7 @@ +using System.Linq; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Systems.Gameplay; +using Content.Shared._Afterlight.ThirdDimension; using Content.Shared.CCVar; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -77,16 +79,28 @@ public sealed class ViewportUIController : UIController base.FrameUpdate(e); + Viewport.Viewport.Eye = _eyeManager.CurrentEye; // verify that the current eye is not "null". Fuck IEyeManager. var ent = _playerMan.LocalPlayer?.ControlledEntity; + if (_entMan.TryGetComponent(ent, out ZViewComponent? view)) + { + Viewport.Viewport.LowerEyes = view.DownViewEnts.Select(x => + { + var eye = _entMan.GetComponent(x); + eye.Rotation = _eyeManager.CurrentEye.Rotation; + eye.DrawFov = false; // We're z leveling, no FoV. + return eye.Eye!; + }).ToArray(); + } if (_eyeManager.CurrentEye.Position != default || ent == null) return; _entMan.TryGetComponent(ent, out EyeComponent? eye); + if (eye?.Eye == _eyeManager.CurrentEye && _entMan.GetComponent(ent.Value).WorldPosition == default) return; // nothing to worry about, the player is just in null space... actually that is probably a problem? diff --git a/Content.Client/Viewport/ScalingViewport.cs b/Content.Client/Viewport/ScalingViewport.cs index 64a8957b5c..e5199b4f0d 100644 --- a/Content.Client/Viewport/ScalingViewport.cs +++ b/Content.Client/Viewport/ScalingViewport.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.UserInterface; @@ -24,7 +25,9 @@ namespace Content.Client.Viewport // Internal viewport creation is deferred. private IClydeViewport? _viewport; + private List _lowerPorts = new(); private IEye? _eye; + private IEye[] _lowerEyes = new IEye[] {}; private Vector2i _viewportSize; private int _curRenderScale; private ScalingViewportStretchMode _stretchMode = ScalingViewportStretchMode.Bilinear; @@ -50,6 +53,27 @@ namespace Content.Client.Viewport } } + public IEye[] LowerEyes + { + get => _lowerEyes; + set + { + var old = value; + _lowerEyes = value; + if (old.Length != value.Length) + { + InvalidateViewport(); + Logger.Debug("Eyes updated.."); + } + + + foreach (var (eye, port) in _lowerEyes.Zip(_lowerPorts)) + { + port.Eye = eye; + } + } + } + /// /// The size, in unscaled pixels, of the internal viewport. /// @@ -137,6 +161,11 @@ namespace Content.Client.Viewport _viewport!.Render(); + foreach (var viewport in _lowerPorts) + { + viewport.Render(); + } + if (_queuedScreenshots.Count != 0) { var callbacks = _queuedScreenshots.ToArray(); @@ -155,6 +184,10 @@ namespace Content.Client.Viewport var drawBox = GetDrawBox(); var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition); _viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal); + foreach (var viewport in _lowerPorts.AsEnumerable().Reverse()) + { + handle.DrawTextureRect(viewport.RenderTarget.Texture, drawBox); + } handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox); _viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal); } @@ -224,6 +257,23 @@ namespace Content.Client.Viewport { Filter = StretchMode == ScalingViewportStretchMode.Bilinear, }); + _viewport.ClearColor = Color.Blue.WithAlpha(0.02f); + + _lowerPorts.Clear(); + for (var i = 0; i < _lowerEyes.Length; i++) + { + _lowerPorts.Add(_clyde.CreateViewport( + ViewportSize * renderScale, + new TextureSampleParameters + { + Filter = StretchMode == ScalingViewportStretchMode.Bilinear, + })); + _lowerPorts[i].RenderScale = (renderScale, renderScale); + _lowerPorts[i].ClearColor = Color.Blue.WithAlpha(0.02f); + + _lowerPorts[i].Eye = _lowerEyes[i]; + _lowerPorts[i].Eye!.Zoom = _lowerPorts[i].Eye!.Zoom * (1.02f + i * 0.02f); + } _viewport.RenderScale = (renderScale, renderScale); @@ -241,6 +291,11 @@ namespace Content.Client.Viewport { _viewport?.Dispose(); _viewport = null; + foreach (var port in _lowerPorts) + { + port.Dispose(); + } + _lowerPorts = new(); } public MapCoordinates ScreenToMap(Vector2 coords) @@ -291,7 +346,7 @@ namespace Content.Client.Viewport private void EnsureViewportCreated() { - if (_viewport == null) + if (_viewport == null || _lowerPorts.Count != _lowerEyes.Length) { RegenerateViewport(); } diff --git a/Content.Client/zlevels/ZViewSystem.cs b/Content.Client/zlevels/ZViewSystem.cs new file mode 100644 index 0000000000..62a8803551 --- /dev/null +++ b/Content.Client/zlevels/ZViewSystem.cs @@ -0,0 +1,17 @@ +using Content.Shared._Afterlight.ThirdDimension; +using Robust.Shared.Map; + +namespace Content.Client.zlevels; + +public sealed class ZViewSystem : SharedZViewSystem +{ + public override EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc) + { + throw new NotImplementedException(); + } + + public override bool CanSetup(EntityUid source) + { + return false; + } +} diff --git a/Content.Server/zlevels/ThirdDimension/LadderComponent.cs b/Content.Server/zlevels/ThirdDimension/LadderComponent.cs new file mode 100644 index 0000000000..e677367230 --- /dev/null +++ b/Content.Server/zlevels/ThirdDimension/LadderComponent.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server._Afterlight.ThirdDimension; + +/// +/// This is used for ladders and traversing them. +/// +[RegisterComponent] +public sealed class LadderComponent : Component +{ + [DataField("primary")] + public bool Primary = false; + + [DataField("otherHalf")] + public EntityUid? OtherHalf = EntityUid.Invalid; + + [DataField("otherHalfProto", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string OtherHalfProto = "LadderLower"; +} diff --git a/Content.Server/zlevels/ThirdDimension/LadderSystem.cs b/Content.Server/zlevels/ThirdDimension/LadderSystem.cs new file mode 100644 index 0000000000..41bb8e3c67 --- /dev/null +++ b/Content.Server/zlevels/ThirdDimension/LadderSystem.cs @@ -0,0 +1,56 @@ +using Content.Shared._Afterlight.ThirdDimension; +using Content.Shared.Interaction; +using Robust.Server.GameObjects; +using Robust.Shared.Map; + +namespace Content.Server._Afterlight.ThirdDimension; + +/// +/// This handles... +/// +public sealed class LadderSystem : EntitySystem +{ + [Dependency] private readonly SharedZLevelSystem _zLevel = default!; + [Dependency] private readonly TransformSystem _transform = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnInteractHand); + } + + public override void Update(float frameTime) + { + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out _, out var ladder, out var xform)) + { + if (!ladder.Primary || Deleted(ladder.OtherHalf)) + return; + + // Track it, it "hangs" from above. + _transform.SetWorldPosition(ladder.OtherHalf.Value, _transform.GetWorldPosition(xform)); + } + } + + private void OnInteractHand(EntityUid uid, LadderComponent component, InteractHandEvent args) + { + EnsureOpposing(uid, component); + _zLevel.TryTraverse(!component.Primary, args.User); + } + + private void EnsureOpposing(EntityUid uid, LadderComponent ladder) + { + if (!ladder.Primary || !Deleted(ladder.OtherHalf)) + return; + + var parentXform = Transform(uid); + var maybeBelow = _zLevel.MapBelow[(int) parentXform.MapID]; + + if (maybeBelow is not {} below) + return; + + var newCoords = new MapCoordinates(_transform.GetWorldPosition(parentXform), below); + ladder.OtherHalf = Spawn(ladder.OtherHalfProto, newCoords); + } +} diff --git a/Content.Server/zlevels/ThirdDimension/ZViewSystem.cs b/Content.Server/zlevels/ThirdDimension/ZViewSystem.cs new file mode 100644 index 0000000000..9f7f3867d4 --- /dev/null +++ b/Content.Server/zlevels/ThirdDimension/ZViewSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared._Afterlight.ThirdDimension; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Map; +using Robust.Shared.Network; + +namespace Content.Server._Afterlight.ThirdDimension; + +public sealed class ZViewSystem : SharedZViewSystem +{ + [Dependency] private readonly ViewSubscriberSystem _view = default!; + [Dependency] private readonly SharedZLevelSystem _zLevel = default!; + [Dependency] private readonly IServerNetManager _serverNet = default!; + + public override void Initialize() + { + base.Initialize(); + _serverNet.Connected += (sender, args) => _zLevel.UpdateMapList(); + } + + public override EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc) + { + var ent = Spawn(null, loc); + EnsureComp(ent); + var actor = Comp(source); + _view.AddViewSubscriber(ent, actor.PlayerSession); + return ent; + } + + public override bool CanSetup(EntityUid source) + { + return TryComp(source, out var actor) && actor.PlayerSession.AttachedEntity == source; + } +} diff --git a/Content.Shared/zlevels/ThirdDimension/SharedZLevelSystem.cs b/Content.Shared/zlevels/ThirdDimension/SharedZLevelSystem.cs new file mode 100644 index 0000000000..6240d05e63 --- /dev/null +++ b/Content.Shared/zlevels/ThirdDimension/SharedZLevelSystem.cs @@ -0,0 +1,239 @@ +using System.Linq; +using Content.Shared.Administration; +using Content.Shared.Administration.Managers; +using Content.Shared.Damage; +using Content.Shared.Damage.Prototypes; +using Content.Shared.FixedPoint; +using Content.Shared.Ghost; +using Content.Shared.Gravity; +using Content.Shared.Movement.Components; +using Robust.Shared.Console; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Network; +using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared._Afterlight.ThirdDimension; + +/// +/// This handles Z levels. I'm sorry to everyone who has to witness this. +/// +public sealed class SharedZLevelSystem : EntitySystem +{ + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly DamageableSystem _damageableSystem = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IConsoleHost _conHost = default!; + [Dependency] private readonly ISharedAdminManager _admin = default!; + + [ViewVariables] + private List _mapAbove = new(); + [ViewVariables] + private List _mapBelow = new(); + + public IReadOnlyList MapAbove => _mapAbove; + public IReadOnlyList MapBelow => _mapBelow; + + private bool DontDrop = false; //HACK: oh god + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnMapChanged); + SubscribeNetworkEvent(OnMapListChanged); + if (_net.IsServer) + { + SubscribeLocalEvent(OnMove); // Sloth forgive me. + _conHost.RegisterCommand("ztool", ZTool); + } + } + + private void OnMove(ref MoveEvent ev) + { + if (DontDrop || _mapBelow[(int) ev.Component.MapID] == null || HasComp(ev.Sender) || _gravity.IsWeightless(ev.Sender) || HasComp(ev.Sender) || !HasComp(ev.Sender)) + return; // get out! + + var mapEid = _map.GetMapEntityId(ev.Component.MapID); + + if (ev.Component.Coordinates.EntityId != mapEid) + return; // Can't fall through the map if we're not on it! + + if (TryComp(mapEid, out var grid)) + { + if (grid.TryGetTileRef(_xformSystem.GetWorldPosition(ev.Component), out var tile) && !tile.Tile.IsEmpty) + { + return; // Can't fall through grids. + } + } + + var phys = Comp(ev.Sender); + if (phys.Momentum.Length < 0.2 || HasComp(ev.Sender) || HasComp(ev.Sender)) + { + TryTraverse(false, ev.Sender); + // THWACK + _damageableSystem.TryChangeDamage(ev.Sender, + new DamageSpecifier(_prototype.Index("Blunt"), FixedPoint2.New(69))); + } + } + + [AnyCommand] + private void ZTool(IConsoleShell shell, string argstr, string[] args) + { + if (shell.Player?.AttachedEntity is not {} ent) + return; + + if (!_admin.IsAdmin(ent)) + { + shell.WriteLine("This code may be bad but it's not gonna let you break things."); + return; + } + + switch (args[0].ToLowerInvariant()) + { + case "above": + { + var xform = Transform(ent); + var map = MapAbove[(int) xform.MapID]; + shell.WriteLine($"The map above you is {(map is not null ? ToPrettyString(_map.GetMapEntityId(map.Value)) : "none")}"); + break; + } + case "below": + { + var xform = Transform(ent); + var map = MapBelow[(int) xform.MapID]; + shell.WriteLine($"The map below you is {(map is not null ? ToPrettyString(_map.GetMapEntityId(map.Value)) : "none")}"); + break; + } + case "link": + { + var dir = args[2].ToLowerInvariant(); + var baseMap = new MapId(int.Parse(args[1])); + var linkedMap = new MapId(int.Parse(args[3])); + + if (dir != "above" && dir != "below") + return; + + if (dir != "above") + { + LinkMaps(baseMap, linkedMap); + } + else + { + LinkMaps(linkedMap, baseMap); + } + + break; + } + case "traverse": + { + var dir = args[1].ToLowerInvariant(); + + if (dir != "above" && dir != "below") + return; + + TryTraverse(dir == "above", ent); + + break; + } + } + } + + public int AllMapsBelow(MapId map, ref MapId[] maps) + { + var curr = map; + var idx = 0; + + while (MapBelow[(int)curr] is { } below && idx < maps.Length) + { + maps[idx++] = below; + curr = below; + } + + return idx; + } + + public bool TryTraverse(bool direction, EntityUid traverser, TransformComponent? xform = default!) + { + if (!Resolve(traverser, ref xform)) + return false; + + var worldPosition = _xformSystem.GetWorldPosition(xform); + MapId? newMap; + if (direction) // Going up! + { + newMap = MapAbove[(int)xform.MapID]; + } + else + { + newMap = MapBelow[(int)xform.MapID]; + } + Logger.Debug($"Traversing to {newMap}.."); + + if (newMap is null) + return false; + + var coords = EntityCoordinates.FromMap(_map, new MapCoordinates(worldPosition, newMap.Value)); + DontDrop = true; + _xformSystem.SetCoordinates(traverser, coords); + DontDrop = false; + + return true; + } + + public void LinkMaps(MapId below, MapId above) + { + _mapBelow[(int)above] = below; + _mapAbove[(int)below] = above; + UpdateMapList(); + } + + private void OnMapListChanged(MapListChangedEvent ev) + { + if (!_net.IsClient) + return; + + //yoink + _mapAbove = ev.MapAbove.ToList(); + _mapBelow = ev.MapBelow.ToList(); + } + + private void OnMapChanged(MapChangedEvent ev) + { + if (!ev.Created) + return; + + while ((int) ev.Map + 1 > _mapAbove.Count) + { + // Resize time. + _mapAbove.Add(null); + _mapBelow.Add(null); + } + UpdateMapList(); + } + + public void UpdateMapList() + { + if (!_net.IsServer) + return; + + RaiseNetworkEvent(new MapListChangedEvent(_mapAbove.ToArray(), _mapBelow.ToArray())); + } + + [Serializable, NetSerializable] + public sealed class MapListChangedEvent : EntityEventArgs + { + public MapId?[] MapAbove; + public MapId?[] MapBelow; + + public MapListChangedEvent(MapId?[] mapAbove, MapId?[] mapBelow) + { + MapAbove = mapAbove; + MapBelow = mapBelow; + } + } +} diff --git a/Content.Shared/zlevels/ThirdDimension/ZViewComponent.cs b/Content.Shared/zlevels/ThirdDimension/ZViewComponent.cs new file mode 100644 index 0000000000..039af5998e --- /dev/null +++ b/Content.Shared/zlevels/ThirdDimension/ZViewComponent.cs @@ -0,0 +1,25 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared._Afterlight.ThirdDimension; + +/// +/// This is used for... +/// +[RegisterComponent, NetworkedComponent] +public sealed class ZViewComponent : Component +{ + [ViewVariables] + public List DownViewEnts = new(); +} + +[Serializable, NetSerializable] +public sealed class ZViewComponentState : ComponentState +{ + public List DownViewEnts; + + public ZViewComponentState(List downViewEnts) + { + DownViewEnts = downViewEnts; + } +} diff --git a/Content.Shared/zlevels/ThirdDimension/ZViewSystem.cs b/Content.Shared/zlevels/ThirdDimension/ZViewSystem.cs new file mode 100644 index 0000000000..ecd050968c --- /dev/null +++ b/Content.Shared/zlevels/ThirdDimension/ZViewSystem.cs @@ -0,0 +1,113 @@ +using System.Linq; +using Content.Shared.Body.Components; +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Players; + +namespace Content.Shared._Afterlight.ThirdDimension; + +/// +/// This handles view between z levels +/// +public abstract class SharedZViewSystem : EntitySystem +{ + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IMapManager _map = default!; + [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly SharedTransformSystem _xformSystem = default!; + [Dependency] private readonly SharedZLevelSystem _zLevel = default!; + + private const int ViewDepth = 3; + + public override void Initialize() + { + SubscribeLocalEvent(ZViewComponentHandleState); + SubscribeLocalEvent(ZViewComponentGetState); + + } + + private void ZViewComponentGetState(EntityUid uid, ZViewComponent component, ref ComponentGetState args) + { + args.State = new ZViewComponentState(component.DownViewEnts); + } + + private void ZViewComponentHandleState(EntityUid uid, ZViewComponent component, ref ComponentHandleState args) + { + if (args.Current is not ZViewComponentState state) + return; + + component.DownViewEnts = state.DownViewEnts; + } + + public override void Update(float frameTime) + { + if (_net.IsServer) + FrameUpdate(frameTime); + } + + /// + public override void FrameUpdate(float frameTime) + { + var query = EntityQueryEnumerator(); + var toUpdate = new List(); + while (query.MoveNext(out var uid, out _)) + { + var view = EnsureComp(uid); + var xform = Transform(uid); + var maps = new MapId[ViewDepth]; + var amt = _zLevel.AllMapsBelow(xform.MapID, ref maps); + if (amt == 0) + continue; + + var currPos = _xformSystem.GetWorldPosition(xform); + + if (view.DownViewEnts.Count != amt) + { + if (_net.IsClient || !CanSetup(uid)) + continue; + toUpdate.Add(uid); + Logger.Debug("Queued Z view update."); + continue; + } + + foreach (var (ent, map) in view.DownViewEnts.Zip(maps)) + { + if (map == MapId.Nullspace) + continue; + + var coords = EntityCoordinates.FromMap(_map, new MapCoordinates(currPos, map)); + _xformSystem.SetCoordinates(ent, coords); + } + } + + foreach (var uid in toUpdate) + { + Logger.Debug("Did z view update."); + var view = EnsureComp(uid); + var xform = Transform(uid); + foreach (var e in view.DownViewEnts) + { + QueueDel(e); + } + view.DownViewEnts.Clear(); + var maps = new MapId[ViewDepth]; + var amt = _zLevel.AllMapsBelow(xform.MapID, ref maps); + if (amt == 0) + continue; + var currPos = _xformSystem.GetWorldPosition(xform); + foreach (var map in maps) + { + if (map == MapId.Nullspace) + continue; + view.DownViewEnts.Add(SpawnViewEnt(uid, new MapCoordinates(currPos, map))); + } + + Dirty(view); + } + } + + public abstract EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc); + public abstract bool CanSetup(EntityUid source); + +} diff --git a/Resources/Prototypes/Entities/Structures/ladder.yml b/Resources/Prototypes/Entities/Structures/ladder.yml new file mode 100644 index 0000000000..dbd834defc --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/ladder.yml @@ -0,0 +1,39 @@ +- type: entity + id: BaseLadder + parent: BaseStructure + abstract: true + placement: + mode: SnapgridCenter + components: + - type: Ladder + - type: Sprite + netsync: false + sprite: Structures/ladder.rsi + - type: Transform + noRot: true + - type: InteractionOutline + +- type: entity + id: Ladder + parent: BaseLadder + name: ladder + description: This doesn't look very safe. + components: + - type: Ladder + primary: true + - type: Sprite + sprite: Structures/ladder.rsi + state: top + +- type: entity + id: LadderLower + parent: BaseLadder + name: ladder + description: This doesn't look very safe. + noSpawn: true + components: + - type: Ladder + primary: false + - type: Sprite + sprite: Structures/ladder.rsi + state: bottom diff --git a/Resources/Textures/Structures/ladder.rsi/bottom.png b/Resources/Textures/Structures/ladder.rsi/bottom.png new file mode 100644 index 0000000000..f416677dac Binary files /dev/null and b/Resources/Textures/Structures/ladder.rsi/bottom.png differ diff --git a/Resources/Textures/Structures/ladder.rsi/icon.png b/Resources/Textures/Structures/ladder.rsi/icon.png new file mode 100644 index 0000000000..0020e28ec6 Binary files /dev/null and b/Resources/Textures/Structures/ladder.rsi/icon.png differ diff --git a/Resources/Textures/Structures/ladder.rsi/meta.json b/Resources/Textures/Structures/ladder.rsi/meta.json new file mode 100644 index 0000000000..1b53c4e1ff --- /dev/null +++ b/Resources/Textures/Structures/ladder.rsi/meta.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station at commit https://github.com/tgstation/tgstation/commit/a66d78ef5b3339bbaa13a8d3167af266485d4589", + "size": { + "x": 32, + "y": 32 + }, + "states": + [ + { + "name": "bottom" + }, + { + "name": "icon" + }, + { + "name": "middle" + }, + { + "name": "top" + } + ] +} diff --git a/Resources/Textures/Structures/ladder.rsi/meta.json.bak b/Resources/Textures/Structures/ladder.rsi/meta.json.bak new file mode 100644 index 0000000000..807eaf43b7 --- /dev/null +++ b/Resources/Textures/Structures/ladder.rsi/meta.json.bak @@ -0,0 +1,24 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station at commit https://github.com/tgstation/tgstation/commit/a66d78ef5b3339bbaa13a8d3167af266485d4589", + "size": { + "x": 32, + "y": 32 + }, + "states": + [ + { + "name": "bottom" + }, + { + "name": "icon", + }, + { + "name": "middle", + }, + { + "name": "top" + } + ] +} diff --git a/Resources/Textures/Structures/ladder.rsi/middle.png b/Resources/Textures/Structures/ladder.rsi/middle.png new file mode 100644 index 0000000000..12b952d8e4 Binary files /dev/null and b/Resources/Textures/Structures/ladder.rsi/middle.png differ diff --git a/Resources/Textures/Structures/ladder.rsi/top.png b/Resources/Textures/Structures/ladder.rsi/top.png new file mode 100644 index 0000000000..0c1f038d66 Binary files /dev/null and b/Resources/Textures/Structures/ladder.rsi/top.png differ