]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
"Fix RCD light spam, bypass of indestructible tiles and some plating fixes" - Tile...
authorVelken <8467292+Velken@users.noreply.github.com>
Tue, 20 Jan 2026 21:48:30 +0000 (18:48 -0300)
committerGitHub <noreply@github.com>
Tue, 20 Jan 2026 21:48:30 +0000 (21:48 +0000)
* No more light spam, and some plating fixes

* fixed test

Content.IntegrationTests/Tests/Construction/RCDTest.cs
Content.IntegrationTests/Tests/Interaction/InteractionTest.Constants.cs
Content.Shared/RCD/RCDPrototype.cs
Content.Shared/RCD/Systems/RCDSystem.cs
Resources/Locale/en-US/rcd/components/rcd-component.ftl
Resources/Prototypes/RCD/rcd.yml
Resources/Prototypes/Tiles/floors.yml
Resources/Prototypes/Tiles/plating.yml

index f20a0cb434c50fe6c2a83545168b255fca6ad132..770f004517dd72e41a1c14ae6c48edbbcc18f727 100644 (file)
@@ -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));
index 3cfb5a5dbaffa52d5956509053f73076ebc0a7f5..1aac18f3a4f3d0ac5e11e4b43069355d11af2c22 100644 (file)
@@ -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";
 
index 2be5e1c7767c11c755bb425fd14b2d34fd3372d3..c4ac7148f7afabff792a1e2a547dcf9676cbab7f 100644 (file)
@@ -44,6 +44,12 @@ public sealed partial class RCDPrototype : IPrototype
     [DataField, ViewVariables(VVAccess.ReadOnly)]
     public string? Prototype { get; private set; }
 
+    /// <summary>
+    /// If true, allows placing the entity once per direction (North, West, South and East)
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadOnly)]
+    public bool AllowMultiDirection { get; private set; }
+
     /// <summary>
     /// Number of charges consumed when the operation is completed
     /// </summary>
index 8b3ae16a1f5c5bd1f7e3e5cd347504692c0d7932..2f1f058a1b7fbe5a6f69c59c013f99e3f0a5152b 100644 (file)
@@ -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<SharedCanBuildWindowOnTopComponent>(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;
 
index 9741bde388cad7bfbdcf0a72f34739da98e7102a..17fda9111e42dd27c60022756813139ea8ee74a1 100644 (file)
@@ -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
index 5fb5356f91d32d56f02a86ad95f1645de837ed52..b173fa41571b94453390f7bf46b8761b3ffbbd01 100644 (file)
@@ -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
     - IsWindow
   rotation: User
   fx: EffectRCDConstruct1
+  allowMultiDirection: true
 
 - type: rcd
   id: ReinforcedWindow
     - IsWindow
   rotation: User
   fx: EffectRCDConstruct2
+  allowMultiDirection: true
 
 # Airlocks
 - type: rcd
   collisionBounds: "-0.23,-0.49,0.23,-0.36"
   rotation: User
   fx: EffectRCDConstruct1
+  allowMultiDirection: true
 
 - type: rcd
   id: BulbLight
   collisionBounds: "-0.23,-0.49,0.23,-0.36"
   rotation: User
   fx: EffectRCDConstruct1
+  allowMultiDirection: true
 
 # Electrical
 - type: rcd
index 52657990d1f128b3d9c02c1431cdef3634dfd0dd..2d2a9ff3ea9187ca2d8532a378f24aabdc97b396 100644 (file)
@@ -22,6 +22,8 @@
   - FloorPlanetGrass
   - FloorSnow
   - FloorDirt
+  - PlatingRCD
+  - FloorHullReinforced
 
 - type: tile
   id: FloorSteel
     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
index 910f941bee2cf8676ce22de971bab6ec8803f89a..a6f150959d97da6b5478a2c784a64661f71f85a7 100644 (file)
   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