From: Velken <8467292+Velken@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:22:54 +0000 (-0300) Subject: Fix RCD light spam, bypass of indestructible tiles and some plating fixes (#42432) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=7d58e42ade391a61183145d271cb4e76b683bc22;p=space-station-14.git Fix RCD light spam, bypass of indestructible tiles and some plating fixes (#42432) * No more light spam, and some plating fixes * fixed test --- diff --git a/Content.IntegrationTests/Tests/Construction/RCDTest.cs b/Content.IntegrationTests/Tests/Construction/RCDTest.cs index f20a0cb434..770f004517 100644 --- a/Content.IntegrationTests/Tests/Construction/RCDTest.cs +++ b/Content.IntegrationTests/Tests/Construction/RCDTest.cs @@ -38,9 +38,9 @@ public sealed class RCDTest : InteractionTest pEast = Transform.WithEntityId(pEast, MapData.Grid); pWest = Transform.WithEntityId(pWest, MapData.Grid); - await SetTile(Plating, SEntMan.GetNetCoordinates(pNorth), MapData.Grid); - await SetTile(Plating, SEntMan.GetNetCoordinates(pSouth), MapData.Grid); - await SetTile(Plating, SEntMan.GetNetCoordinates(pEast), MapData.Grid); + await SetTile(PlatingRCD, SEntMan.GetNetCoordinates(pNorth), MapData.Grid); + await SetTile(PlatingRCD, SEntMan.GetNetCoordinates(pSouth), MapData.Grid); + await SetTile(PlatingRCD, SEntMan.GetNetCoordinates(pEast), MapData.Grid); await SetTile(Lattice, SEntMan.GetNetCoordinates(pWest), MapData.Grid); Assert.That(ProtoMan.TryIndex(RCDSettingWall, out var settingWall), $"RCDPrototype not found: {RCDSettingWall}."); @@ -194,7 +194,7 @@ public sealed class RCDTest : InteractionTest // Deconstruct the steel tile. await Interact(null, pEast); await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish - await AssertTile(Plating, FromServer(pEast)); + await AssertTile(PlatingRCD, FromServer(pEast)); // Check that the cost of the deconstruction was subtracted from the current charges. newCharges = sCharges.GetCurrentCharges(ToServer(rcd)); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs index 3cfb5a5dba..1aac18f3a4 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs @@ -11,6 +11,7 @@ public abstract partial class InteractionTest protected const string Floor = "FloorSteel"; protected const string FloorItem = "FloorTileItemSteel"; protected const string Plating = "Plating"; + protected const string PlatingRCD = "PlatingRCD"; protected const string Lattice = "Lattice"; protected const string PlatingBrass = "PlatingBrass"; diff --git a/Content.Shared/RCD/RCDPrototype.cs b/Content.Shared/RCD/RCDPrototype.cs index 2be5e1c776..c4ac7148f7 100644 --- a/Content.Shared/RCD/RCDPrototype.cs +++ b/Content.Shared/RCD/RCDPrototype.cs @@ -44,6 +44,12 @@ public sealed partial class RCDPrototype : IPrototype [DataField, ViewVariables(VVAccess.ReadOnly)] public string? Prototype { get; private set; } + /// + /// If true, allows placing the entity once per direction (North, West, South and East) + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public bool AllowMultiDirection { get; private set; } + /// /// Number of charges consumed when the operation is completed /// diff --git a/Content.Shared/RCD/Systems/RCDSystem.cs b/Content.Shared/RCD/Systems/RCDSystem.cs index 8b3ae16a1f..2f1f058a1b 100644 --- a/Content.Shared/RCD/Systems/RCDSystem.cs +++ b/Content.Shared/RCD/Systems/RCDSystem.cs @@ -146,7 +146,7 @@ public sealed class RCDSystem : EntitySystem var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location); - if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Target, args.User)) + if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, component.ConstructionDirection, args.Target, args.User)) return; if (!_net.IsServer) @@ -254,7 +254,7 @@ public sealed class RCDSystem : EntitySystem var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location); - if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Event.Target, args.Event.User)) + if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Event.Direction, args.Event.Target, args.Event.User)) args.Cancel(); } @@ -284,7 +284,7 @@ public sealed class RCDSystem : EntitySystem var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location); // Ensure the RCD operation is still valid - if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Target, args.User)) + if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Direction, args.Target, args.User)) return; // Finalize the operation (this should handle prediction properly) @@ -319,6 +319,11 @@ public sealed class RCDSystem : EntitySystem #region Entity construction/deconstruction rule checks public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, EntityUid? target, EntityUid user, bool popMsgs = true) + { + return IsRCDOperationStillValid(uid, component, gridUid, mapGrid, tile, position, component.ConstructionDirection, target, user, popMsgs); + } + + public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid? target, EntityUid user, bool popMsgs = true) { var prototype = _protoManager.Index(component.ProtoId); @@ -355,7 +360,7 @@ public sealed class RCDSystem : EntitySystem { case RcdMode.ConstructTile: case RcdMode.ConstructObject: - return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, user, popMsgs); + return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, direction, user, popMsgs); case RcdMode.Deconstruct: return IsDeconstructionStillValid(uid, tile, target, user, popMsgs); } @@ -363,7 +368,7 @@ public sealed class RCDSystem : EntitySystem return false; } - private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, EntityUid user, bool popMsgs = true) + private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid user, bool popMsgs = true) { var prototype = _protoManager.Index(component.ProtoId); @@ -406,8 +411,24 @@ public sealed class RCDSystem : EntitySystem return false; } + var tileDef = _turf.GetContentTileDefinition(tile); + + // Check rule: Respect baseTurf and baseWhitelist + if (prototype.Prototype != null && _tileDefMan.TryGetDefinition(prototype.Prototype, out var replacementDef)) + { + var replacementContentDef = (ContentTileDefinition) replacementDef; + + if (replacementContentDef.BaseTurf != tileDef.ID && !replacementContentDef.BaseWhitelist.Contains(tileDef.ID)) + { + if (popMsgs) + _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-empty-tile-message"), uid, user); + + return false; + } + } + // Check rule: Tiles can't be identical - if (_turf.GetContentTileDefinition(tile).ID == prototype.Prototype) + if (tileDef.ID == prototype.Prototype) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user); @@ -430,6 +451,28 @@ public sealed class RCDSystem : EntitySystem foreach (var ent in _intersectingEntities) { + // If the entity is the exact same prototype as what we are trying to build, then block it. + // This is to prevent spamming objects on the same tile (e.g. lights) + if (prototype.Prototype != null && MetaData(ent).EntityPrototype?.ID == prototype.Prototype) + { + var isIdentical = true; + + if (prototype.AllowMultiDirection) + { + var entDirection = Transform(ent).LocalRotation.GetCardinalDir(); + if (entDirection != direction) + isIdentical = false; + } + + if (isIdentical) + { + if (popMsgs) + _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-entity"), uid, user); + + return false; + } + } + if (isWindow && HasComp(ent)) continue; @@ -534,7 +577,10 @@ public sealed class RCDSystem : EntitySystem switch (prototype.Mode) { case RcdMode.ConstructTile: - _mapSystem.SetTile(gridUid, mapGrid, position, new Tile(_tileDefMan[prototype.Prototype].TileId)); + if (!_tileDefMan.TryGetDefinition(prototype.Prototype, out var tileDef)) + return; + + _tile.ReplaceTile(tile, (ContentTileDefinition) tileDef, gridUid, mapGrid); _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} {position} to {prototype.Prototype}"); break; diff --git a/Resources/Locale/en-US/rcd/components/rcd-component.ftl b/Resources/Locale/en-US/rcd/components/rcd-component.ftl index 9741bde388..17fda9111e 100644 --- a/Resources/Locale/en-US/rcd/components/rcd-component.ftl +++ b/Resources/Locale/en-US/rcd/components/rcd-component.ftl @@ -29,6 +29,7 @@ rcd-component-must-build-on-subfloor-message = You can only build that on expose rcd-component-cannot-build-on-subfloor-message = You can't build that on exposed subfloor! rcd-component-cannot-build-on-occupied-tile-message = You can't build here, the space is already occupied! rcd-component-cannot-build-identical-tile = That tile already exists there! +rcd-component-cannot-build-identical-entity = That already exists there! ### Category names diff --git a/Resources/Prototypes/RCD/rcd.yml b/Resources/Prototypes/RCD/rcd.yml index 5fb5356f91..b173fa4157 100644 --- a/Resources/Prototypes/RCD/rcd.yml +++ b/Resources/Prototypes/RCD/rcd.yml @@ -37,7 +37,7 @@ category: WallsAndFlooring sprite: /Textures/Interface/Radial/RCD/plating.png mode: ConstructTile - prototype: Plating + prototype: PlatingRCD cost: 1 delay: 1 collisionMask: InteractImpassable @@ -128,6 +128,7 @@ - IsWindow rotation: User fx: EffectRCDConstruct1 + allowMultiDirection: true - type: rcd id: ReinforcedWindow @@ -157,6 +158,7 @@ - IsWindow rotation: User fx: EffectRCDConstruct2 + allowMultiDirection: true # Airlocks - type: rcd @@ -208,6 +210,7 @@ collisionBounds: "-0.23,-0.49,0.23,-0.36" rotation: User fx: EffectRCDConstruct1 + allowMultiDirection: true - type: rcd id: BulbLight @@ -221,6 +224,7 @@ collisionBounds: "-0.23,-0.49,0.23,-0.36" rotation: User fx: EffectRCDConstruct1 + allowMultiDirection: true # Electrical - type: rcd diff --git a/Resources/Prototypes/Tiles/floors.yml b/Resources/Prototypes/Tiles/floors.yml index 52657990d1..2d2a9ff3ea 100644 --- a/Resources/Prototypes/Tiles/floors.yml +++ b/Resources/Prototypes/Tiles/floors.yml @@ -22,6 +22,8 @@ - FloorPlanetGrass - FloorSnow - FloorDirt + - PlatingRCD + - FloorHullReinforced - type: tile id: FloorSteel @@ -1607,17 +1609,6 @@ collection: FootstepHull itemDrop: FloorTileItemSteel #probably should not be normally obtainable, but the game shits itself and dies when you try to put null here -- type: tile - id: FloorHullReinforced - parent: BaseStationTile - name: tiles-hull-reinforced - sprite: /Textures/Tiles/hull_reinforced.png - footstepSounds: - collection: FootstepHull - itemDrop: FloorTileItemSteel - heatCapacity: 100000 #/tg/ has this set as "INFINITY." I don't know if that exists here so I've just added an extra 0 - indestructible: true - - type: tile id: FloorReinforcedHardened parent: BaseStationTile diff --git a/Resources/Prototypes/Tiles/plating.yml b/Resources/Prototypes/Tiles/plating.yml index 910f941bee..a6f150959d 100644 --- a/Resources/Prototypes/Tiles/plating.yml +++ b/Resources/Prototypes/Tiles/plating.yml @@ -16,6 +16,34 @@ name: tiles-plating sprite: /Textures/Tiles/plating.png +- type: tile + id: PlatingRCD + parent: Plating + baseWhitelist: + - TrainLattice + - FloorPlanetDirt + - FloorDesert + - FloorLowDesert + - FloorPlanetGrass + - FloorSnow + - FloorDirt + - FloorAsteroidIronsand + - FloorAsteroidSand + - FloorAsteroidSandBorderless + - FloorAsteroidIronsandBorderless + - FloorAsteroidSandRedBorderless + +- type: tile + id: FloorHullReinforced + parent: BasePlating + name: tiles-hull-reinforced + sprite: /Textures/Tiles/hull_reinforced.png + footstepSounds: + collection: FootstepHull + itemDrop: FloorTileItemSteel + heatCapacity: 100000 #/tg/ has this set as "INFINITY." I don't know if that exists here so I've just added an extra 0 + indestructible: true + - type: tile id: PlatingDamaged parent: BasePlating