diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index c0915caf7b..5169e4a189 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -136,6 +136,8 @@ "TrashSpawner", "Pill", "RCD", + "RCDDeconstructWhitelist", + "RCDAmmo", "Pullable", "CursedEntityStorage", "Listening", diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs new file mode 100644 index 0000000000..16d2e5557e --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDAmmoComponent.cs @@ -0,0 +1,61 @@ +using System; +using Content.Server.Interfaces; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Items.RCD +{ + [RegisterComponent] + public class RCDAmmoComponent : Component, IAfterInteract, IExamine + { + +#pragma warning disable 649 + [Dependency] private IServerNotifyManager _serverNotifyManager; +#pragma warning restore 649 + public override string Name => "RCDAmmo"; + + //How much ammo we refill + [ViewVariables(VVAccess.ReadWrite)] private int refillAmmo = 5; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref refillAmmo, "refillAmmo", 5); + } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + message.AddMarkup(Loc.GetString("It holds {0} charges.", refillAmmo)); + } + + void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) + { + if (eventArgs.Target == null || !eventArgs.Target.TryGetComponent(out RCDComponent rcdComponent) || !eventArgs.User.TryGetComponent(out IHandsComponent hands)) + { + return; + } + + if (rcdComponent.maxAmmo - rcdComponent._ammo < refillAmmo) + { + _serverNotifyManager.PopupMessage(rcdComponent.Owner, eventArgs.User, "The RCD is full!"); + return; + } + + rcdComponent._ammo = Math.Min(rcdComponent.maxAmmo, rcdComponent._ammo + refillAmmo); + _serverNotifyManager.PopupMessage(rcdComponent.Owner, eventArgs.User, "You refill the RCD."); + + //Deleting a held item causes a lot of errors + hands.Drop(Owner, false); + Owner.Delete(); + + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs new file mode 100644 index 0000000000..d479c31d02 --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDComponent.cs @@ -0,0 +1,241 @@ +using System; +using System.Threading; +using Content.Server.GameObjects.EntitySystems.DoAfter; +using Content.Server.Interfaces; +using Content.Server.Utility; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Interfaces.GameObjects.Components; +using Content.Shared.Maps; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.Transform; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Robust.Shared.Map; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Items.RCD +{ + [RegisterComponent] + public class RCDComponent : Component, IAfterInteract, IUse, IExamine + { + +#pragma warning disable 649 + [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; + [Dependency] private readonly IEntitySystemManager _entitySystemManager; + [Dependency] private readonly IMapManager _mapManager; + [Dependency] private readonly IServerEntityManager _serverEntityManager; + [Dependency] private IServerNotifyManager _serverNotifyManager; +#pragma warning restore 649 + public override string Name => "RCD"; + private RcdMode _mode = 0; //What mode are we on? Can be floors, walls, deconstruct. + private readonly RcdMode[] _modes = (RcdMode[]) Enum.GetValues(typeof(RcdMode)); + [ViewVariables(VVAccess.ReadWrite)] public int maxAmmo; + public int _ammo; //How much "ammo" we have left. You can refill this with RCD ammo. + [ViewVariables(VVAccess.ReadWrite)] private float _delay; + private DoAfterSystem doAfterSystem; + + + ///Enum to store the different mode states for clarity. + private enum RcdMode + { + Floors, + Walls, + Airlock, + Deconstruct + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref maxAmmo, "maxAmmo", 5); + serializer.DataField(ref _delay, "delay", 2f); + } + + public override void Initialize() + { + base.Initialize(); + _ammo = maxAmmo; + doAfterSystem = EntitySystem.Get(); + } + + /// + /// Method called when the RCD is clicked in-hand, this will cycle the RCD mode. + /// + + public bool UseEntity(UseEntityEventArgs eventArgs) + { + SwapMode(eventArgs); + return true; + } + + /// + ///Method to allow the user to swap the mode of the RCD by clicking it in hand, the actual in hand clicking bit is done over on UseEntity() + ///@param UseEntityEventArgs = The entity which triggered this method call, used to know where to play the "click" sound. + /// + + public void SwapMode(UseEntityEventArgs eventArgs) + { + _entitySystemManager.GetEntitySystem().PlayFromEntity("/Audio/Items/genhit.ogg", Owner); + int mode = (int) _mode; //Firstly, cast our RCDmode mode to an int (enums are backed by ints anyway by default) + mode = (++mode) % _modes.Length; //Then, do a rollover on the value so it doesnt hit an invalid state + _mode = (RcdMode) mode; //Finally, cast the newly acquired int mode to an RCDmode so we can use it. + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"The RCD is now set to {this._mode} mode."); //Prints an overhead message above the RCD + } + + public void Examine(FormattedMessage message, bool inDetailsRange) + { + message.AddMarkup(Loc.GetString("It's currently on {0} mode, and holds {1} charges.",_mode.ToString(), this._ammo)); + } + + public async void AfterInteract(AfterInteractEventArgs eventArgs) + { + //No changing mode mid-RCD + var startingMode = _mode; + + var mapGrid = _mapManager.GetGrid(eventArgs.ClickLocation.GridID); + var tile = mapGrid.GetTileRef(eventArgs.ClickLocation); + var snapPos = mapGrid.SnapGridCellFor(eventArgs.ClickLocation, SnapGridOffset.Center); + + //Using an RCD isn't instantaneous + var cancelToken = new CancellationTokenSource(); + var doAfterEventArgs = new DoAfterEventArgs(eventArgs.User, _delay, cancelToken.Token, eventArgs.Target) + { + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = true, + ExtraCheck = () => IsRCDStillValid(eventArgs, mapGrid, tile, snapPos, startingMode) //All of the sanity checks are here + }; + + var result = await doAfterSystem.DoAfter(doAfterEventArgs); + if (result == DoAfterStatus.Cancelled) + { + return; + } + + switch (_mode) + { + //Floor mode just needs the tile to be a space tile (subFloor) + case RcdMode.Floors: + mapGrid.SetTile(eventArgs.ClickLocation, new Tile(_tileDefinitionManager["floor_steel"].TileId)); + break; + //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check. + case RcdMode.Deconstruct: + if (!tile.IsBlockedTurf(true)) //Delete the turf + { + mapGrid.SetTile(snapPos, Tile.Empty); + } + else //Delete what the user targeted + { + eventArgs.Target.Delete(); + } + break; + //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code. + case RcdMode.Walls: + var ent = _serverEntityManager.SpawnEntity("solid_wall", mapGrid.GridTileToLocal(snapPos)); + ent.Transform.LocalRotation = Owner.Transform.LocalRotation; //Now apply icon smoothing. + break; + case RcdMode.Airlock: + var airlock = _serverEntityManager.SpawnEntity("Airlock", mapGrid.GridTileToLocal(snapPos)); + airlock.Transform.LocalRotation = Owner.Transform.LocalRotation; //Now apply icon smoothing. + break; + default: + return; //I don't know why this would happen, but sure I guess. Get out of here invalid state! + } + + _entitySystemManager.GetEntitySystem().PlayFromEntity("/Audio/Items/deconstruct.ogg", Owner); + _ammo--; + + } + + private bool IsRCDStillValid(AfterInteractEventArgs eventArgs, IMapGrid mapGrid, TileRef tile, MapIndices snapPos, RcdMode startingMode) + { + //Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed. + if (_ammo <= 0) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"The RCD is out of ammo!"); + return false; + } + + if (_mode != startingMode) + { + return false; + } + + var coordinates = mapGrid.GridTileToLocal(tile.GridIndices); + if (coordinates == GridCoordinates.InvalidGrid || !InteractionChecks.InRangeUnobstructed(eventArgs)) + { + return false; + } + + switch (_mode) + { + //Floor mode just needs the tile to be a space tile (subFloor) + case RcdMode.Floors: + if (!tile.Tile.IsEmpty) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"You can only build a floor on space!"); + return false; + } + + return true; + //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check. + case RcdMode.Deconstruct: + if (tile.Tile.IsEmpty) + { + return false; + } + + //They tried to decon a turf but the turf is blocked + if (eventArgs.Target == null && tile.IsBlockedTurf(true)) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"That tile is obstructed!"); + return false; + } + //They tried to decon a non-turf but it's not in the whitelist + if (eventArgs.Target != null && !eventArgs.Target.TryGetComponent(out RCDDeconstructWhitelist rcd_decon)) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"You can't deconstruct that!"); + return false; + } + + return true; + //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code. + case RcdMode.Walls: + if (tile.Tile.IsEmpty) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"Cannot build a wall on space!"); + return false; + } + + if (tile.IsBlockedTurf(true)) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"That tile is obstructed!"); + return false; + } + return true; + case RcdMode.Airlock: + if (tile.Tile.IsEmpty) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"Cannot build an airlock on space!"); + return false; + } + if (tile.IsBlockedTurf(true)) + { + _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"That tile is obstructed!"); + return false; + } + return true; + default: + return false; //I don't know why this would happen, but sure I guess. Get out of here invalid state! + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Items/RCD/RCDDeconstructWhitelistComponent.cs b/Content.Server/GameObjects/Components/Items/RCD/RCDDeconstructWhitelistComponent.cs new file mode 100644 index 0000000000..7fb18a45cd --- /dev/null +++ b/Content.Server/GameObjects/Components/Items/RCD/RCDDeconstructWhitelistComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Items.RCD +{ + [RegisterComponent] + public class RCDDeconstructWhitelist : Component + { + public override string Name => "RCDDeconstructWhitelist"; + } +} diff --git a/Content.Server/GameObjects/Components/Items/RCDComponent.cs b/Content.Server/GameObjects/Components/Items/RCDComponent.cs deleted file mode 100644 index 382360ccd4..0000000000 --- a/Content.Server/GameObjects/Components/Items/RCDComponent.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using Content.Server.Interfaces; -using Content.Server.Utility; -using Content.Shared.GameObjects.EntitySystems; -using Content.Shared.Interfaces.GameObjects.Components; -using Content.Shared.Maps; -using Robust.Server.GameObjects.EntitySystems; -using Robust.Server.Interfaces.GameObjects; -using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Components.Transform; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Map; -using Robust.Shared.IoC; -using Robust.Shared.Localization; -using Robust.Shared.Map; -using Robust.Shared.Serialization; -using Robust.Shared.Utility; - -namespace Content.Server.GameObjects.Components.Items -{ - [RegisterComponent] - public class RCDComponent : Component, IAfterInteract, IUse, IExamine - { - -#pragma warning disable 649 - [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager; - [Dependency] private readonly IEntitySystemManager _entitySystemManager; - [Dependency] private readonly IMapManager _mapManager; - [Dependency] private readonly IServerEntityManager _serverEntityManager; - [Dependency] private IServerNotifyManager _serverNotifyManager; -#pragma warning restore 649 - public override string Name => "RCD"; - private string _outputTile = "floor_steel"; - private RcdMode _mode = 0; //What mode are we on? Can be floors, walls, deconstruct. - private readonly RcdMode[] _modes = (RcdMode[]) Enum.GetValues(typeof(RcdMode)); - private int _ammo = 5; //How much "ammo" we have left. You can refille this with RCD ammo. - - ///Enum to store the different mode states for clarity. - private enum RcdMode - { - Floors, - Walls, - Deconstruct - } - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - serializer.DataField(ref _outputTile, "output", "floor_steel"); - } - - - /// - /// Method called when the RCD is clicked in-hand, this will swap the RCD's mode from "floors" to "walls". - /// - - public bool UseEntity(UseEntityEventArgs eventArgs) - { - SwapMode(eventArgs); - return true; - } - - /// - ///Method to allow the user to swap the mode of the RCD by clicking it in hand, the actual in hand clicking bit is done over on UseEntity() - ///@param UseEntityEventArgs = The entity which triggered this method call, used to know where to play the "click" sound. - /// - - public void SwapMode(UseEntityEventArgs eventArgs) - { - _entitySystemManager.GetEntitySystem().PlayFromEntity("/Audio/Items/genhit.ogg", Owner); - int mode = (int) this._mode; //Firstly, cast our RCDmode mode to an int (enums are backed by ints anyway by default) - mode = (++mode) % _modes.Length; //Then, do a rollover on the value so it doesnt hit an invalid state - this._mode = (RcdMode) mode; //Finally, cast the newly acquired int mode to an RCDmode so we can use it. - switch (this._mode) - { - case RcdMode.Floors: - _outputTile = "floor_steel"; - break; - case RcdMode.Walls: - _outputTile = "base_wall"; - break; - case RcdMode.Deconstruct: - _outputTile = "space"; - break; - } - _serverNotifyManager.PopupMessage(Owner, eventArgs.User, $"The RCD is now set to {this._mode} mode."); //Prints an overhead message above the RCD - } - - /// - ///Method called when the user examines this object, it'll simply add the mode that it's in to the object's description - ///@params message = The original message from examining, like ..() in BYOND's examine - /// - - public void Examine(FormattedMessage message, bool inDetailsRange) - { - message.AddMarkup(Loc.GetString("It's currently on {0} mode, and holds {1} charges.",_mode.ToString(), this._ammo)); - } - - /// - /// Method to handle clicking on a tile to then appropriately RCD it. This can have several behaviours depending on mode. - /// @param eventAargs = An action event telling us what tile was clicked on. We use this to exrapolate where to place the new tile / remove the old one etc. - /// - - public void AfterInteract(AfterInteractEventArgs eventArgs) - { - var mapGrid = _mapManager.GetGrid(eventArgs.ClickLocation.GridID); - var tile = mapGrid.GetTileRef(eventArgs.ClickLocation); - var coordinates = mapGrid.GridTileToLocal(tile.GridIndices); - //Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed. - if (_ammo <= 0 || coordinates == GridCoordinates.InvalidGrid || !InteractionChecks.InRangeUnobstructed(eventArgs)) - { - return; - } - - var targetTile = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId]; - - var canPlaceTile = targetTile.IsSubFloor; //Boolean to check if we're able to build the desired tile. This defaults to checking for subfloors, but is overridden by "deconstruct" which sets it to the inverse. - - switch (this._mode) - { - //Floor mode just needs the tile to be a space tile (subFloor) - case RcdMode.Floors: - break; - //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check. - case RcdMode.Deconstruct: - canPlaceTile = !targetTile.IsSubFloor; - break; - //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code. - case RcdMode.Walls: - var snapPos = mapGrid.SnapGridCellFor(eventArgs.ClickLocation, SnapGridOffset.Center); - var ent = _serverEntityManager.SpawnEntity("solid_wall", mapGrid.GridTileToLocal(snapPos)); - ent.Transform.LocalRotation = Owner.Transform.LocalRotation; //Now apply icon smoothing. - _entitySystemManager.GetEntitySystem().PlayFromEntity("/Audio/Items/deconstruct.ogg", Owner); - _ammo--; - return; //Alright we're done here - default: - return; //I don't know why this would happen, but sure I guess. Get out of here invalid state! - } - - ITileDefinition desiredTile = null; - desiredTile = _tileDefinitionManager[_outputTile]; - if (canPlaceTile) //If desiredTile is null by this point, something has gone horribly wrong and you need to fix it. - { - mapGrid.SetTile(eventArgs.ClickLocation, new Tile(desiredTile.TileId)); - _entitySystemManager.GetEntitySystem().PlayFromEntity("/Audio/Items/deconstruct.ogg", Owner); - _ammo--; - } - } - } -} diff --git a/Content.Shared/Maps/TurfHelpers.cs b/Content.Shared/Maps/TurfHelpers.cs index aa60e7168c..b02472cfa5 100644 --- a/Content.Shared/Maps/TurfHelpers.cs +++ b/Content.Shared/Maps/TurfHelpers.cs @@ -83,13 +83,13 @@ namespace Content.Shared.Maps foreach (var body in query) { if (body.CanCollide && body.Hard && (body.CollisionLayer & (int) CollisionGroup.Impassable) != 0) - return false; + return true; if (filterMobs && (body.CollisionLayer & (int) CollisionGroup.MobMask) != 0) - return false; + return true; } - return true; + return false; } /// diff --git a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml index 6e8200cf27..ad88ad239f 100644 --- a/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Doors/airlock_base.yml @@ -4,6 +4,7 @@ description: It opens, it closes, and maybe crushes you. components: - type: Clickable + - type: RCDDeconstructWhitelist - type: InteractionOutline - type: Sprite netsync: false diff --git a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml index 36d945b8de..c381fae699 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/low_wall.yml @@ -8,6 +8,7 @@ - Wall components: - type: Clickable + - type: RCDDeconstructWhitelist - type: InteractionOutline - type: Sprite netsync: false diff --git a/Resources/Prototypes/Entities/Constructible/Walls/walls.yml b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml index 2ba3947118..7f82a01ce2 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/walls.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/walls.yml @@ -8,6 +8,7 @@ snap: - Wall components: + - type: RCDDeconstructWhitelist - type: Clickable - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml index dbb604d2ed..280139d3e6 100644 --- a/Resources/Prototypes/Entities/Constructible/Walls/windows.yml +++ b/Resources/Prototypes/Entities/Constructible/Walls/windows.yml @@ -8,6 +8,7 @@ - Window components: - type: Clickable + - type: RCDDeconstructWhitelist - type: InteractionOutline - type: Sprite color: "#DDDDDD" diff --git a/Resources/Prototypes/Entities/Objects/Tools/tools.yml b/Resources/Prototypes/Entities/Objects/Tools/tools.yml index ef5191d36c..51df8c6cac 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/tools.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/tools.yml @@ -249,7 +249,7 @@ name: RCD parent: BaseItem id: RCD - description: An advanced construction device which can place floors / walls down quickly. + description: An advanced construction device which can place/remove walls, floors, and airlocks quickly. components: - type: RCD - type: UseDelay @@ -262,3 +262,17 @@ state: rcd - type: Item sprite: Objects/Tools/rcd.rsi + +- type: entity + name: RCD Ammo + parent: BaseItem + id: RCDAmmo + description: Ammo cartridge for an RCD. + components: + - type: RCDAmmo + - type: Sprite + sprite: Objects/Tools/rcd.rsi + state: rcd_ammo + - type: Icon + sprite: Objects/Tools/rcd.rsi + state: rcd_ammo diff --git a/Resources/Textures/Objects/Tools/rcd.rsi/meta.json b/Resources/Textures/Objects/Tools/rcd.rsi/meta.json index 1cfaa7b48f..ace9e5c739 100644 --- a/Resources/Textures/Objects/Tools/rcd.rsi/meta.json +++ b/Resources/Textures/Objects/Tools/rcd.rsi/meta.json @@ -1 +1,64 @@ -{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "inhand-left", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "inhand-right", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "rcd", "directions": 1, "delays": [[1.0]]}]} \ No newline at end of file +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC BY-SA 3.0", + "states": [ + { + "name": "inhand-left", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "inhand-right", + "directions": 4, + "delays": [ + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ], + [ + 1.0 + ] + ] + }, + { + "name": "rcd", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + }, + { + "name": "rcd_ammo", + "directions": 1, + "delays": [ + [ + 1.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Objects/Tools/rcd.rsi/rcd.png b/Resources/Textures/Objects/Tools/rcd.rsi/rcd.png index fe1b33a4cf..cb62979010 100644 Binary files a/Resources/Textures/Objects/Tools/rcd.rsi/rcd.png and b/Resources/Textures/Objects/Tools/rcd.rsi/rcd.png differ diff --git a/Resources/Textures/Objects/Tools/rcd.rsi/rcd_ammo.png b/Resources/Textures/Objects/Tools/rcd.rsi/rcd_ammo.png new file mode 100644 index 0000000000..5422d5b976 Binary files /dev/null and b/Resources/Textures/Objects/Tools/rcd.rsi/rcd_ammo.png differ