diff --git a/Content.Client/RCD/AlignRCDConstruction.cs b/Content.Client/RCD/AlignRCDConstruction.cs index ef99b01855..fbaf62c83d 100644 --- a/Content.Client/RCD/AlignRCDConstruction.cs +++ b/Content.Client/RCD/AlignRCDConstruction.cs @@ -96,20 +96,22 @@ public sealed class AlignRCDConstruction : PlacementMode if (!_entityManager.TryGetComponent(heldEntity, out var rcd)) return false; - // Retrieve the map grid data for the position - if (!_rcdSystem.TryGetMapGridData(position, out var mapGridData)) + var gridUid = _transformSystem.GetGrid(position); + if (!_entityManager.TryGetComponent(gridUid, out var mapGrid)) return false; + var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, position); + var posVector = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, position); // Determine if the user is hovering over a target var currentState = _stateManager.CurrentState; if (currentState is not GameplayStateBase screen) return false; - + var target = screen.GetClickedEntity(_transformSystem.ToMapCoordinates(_unalignedMouseCoords)); // Determine if the RCD operation is valid or not - if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, mapGridData.Value, target, player.Value, false)) + if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, gridUid.Value, mapGrid, tile, posVector, target, player.Value, false)) return false; return true; diff --git a/Content.Client/RCD/RCDConstructionGhostSystem.cs b/Content.Client/RCD/RCDConstructionGhostSystem.cs index 792916b892..0b6876005a 100644 --- a/Content.Client/RCD/RCDConstructionGhostSystem.cs +++ b/Content.Client/RCD/RCDConstructionGhostSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.RCD.Systems; using Robust.Client.Placement; using Robust.Client.Player; using Robust.Shared.Enums; +using Robust.Shared.Prototypes; namespace Content.Client.RCD; @@ -14,6 +15,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly RCDSystem _rcdSystem = default!; [Dependency] private readonly IPlacementManager _placementManager = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; private string _placementMode = typeof(AlignRCDConstruction).Name; private Direction _placementDirection = default; @@ -47,6 +49,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem return; } + var prototype = _protoManager.Index(rcd.ProtoId); // Update the direction the RCD prototype based on the placer direction if (_placementDirection != _placementManager.Direction) @@ -56,9 +59,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem } // If the placer has not changed, exit - _rcdSystem.UpdateCachedPrototype(heldEntity.Value, rcd); - - if (heldEntity == placerEntity && rcd.CachedPrototype.Prototype == placerProto) + if (heldEntity == placerEntity && prototype.Prototype == placerProto) return; // Create a new placer @@ -66,9 +67,9 @@ public sealed class RCDConstructionGhostSystem : EntitySystem { MobUid = heldEntity.Value, PlacementOption = _placementMode, - EntityType = rcd.CachedPrototype.Prototype, + EntityType = prototype.Prototype, Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange), - IsTile = (rcd.CachedPrototype.Mode == RcdMode.ConstructTile), + IsTile = (prototype.Mode == RcdMode.ConstructTile), UseEditorContext = false, }; diff --git a/Content.Shared/RCD/Components/RCDComponent.cs b/Content.Shared/RCD/Components/RCDComponent.cs index 39bb6fd3e9..1ea3166531 100644 --- a/Content.Shared/RCD/Components/RCDComponent.cs +++ b/Content.Shared/RCD/Components/RCDComponent.cs @@ -33,25 +33,13 @@ public sealed partial class RCDComponent : Component [DataField, AutoNetworkedField] public ProtoId ProtoId { get; set; } = "Invalid"; - /// - /// A cached copy of currently selected RCD prototype - /// - /// - /// If the ProtoId is changed, make sure to update the CachedPrototype as well - /// - [ViewVariables(VVAccess.ReadOnly)] - public RCDPrototype CachedPrototype { get; set; } = default!; - /// /// The direction constructed entities will face upon spawning /// [DataField, AutoNetworkedField] public Direction ConstructionDirection { - get - { - return _constructionDirection; - } + get => _constructionDirection; set { _constructionDirection = value; @@ -68,5 +56,5 @@ public sealed partial class RCDComponent : Component /// Contains no position data /// [ViewVariables(VVAccess.ReadOnly)] - public Transform ConstructionTransform { get; private set; } = default!; + public Transform ConstructionTransform { get; private set; } } diff --git a/Content.Shared/RCD/RCDEvents.cs b/Content.Shared/RCD/RCDEvents.cs index a15a010277..6871ec178e 100644 --- a/Content.Shared/RCD/RCDEvents.cs +++ b/Content.Shared/RCD/RCDEvents.cs @@ -4,27 +4,16 @@ using Robust.Shared.Serialization; namespace Content.Shared.RCD; [Serializable, NetSerializable] -public sealed class RCDSystemMessage : BoundUserInterfaceMessage +public sealed class RCDSystemMessage(ProtoId protoId) : BoundUserInterfaceMessage { - public ProtoId ProtoId; - - public RCDSystemMessage(ProtoId protoId) - { - ProtoId = protoId; - } + public ProtoId ProtoId = protoId; } [Serializable, NetSerializable] -public sealed class RCDConstructionGhostRotationEvent : EntityEventArgs +public sealed class RCDConstructionGhostRotationEvent(NetEntity netEntity, Direction direction) : EntityEventArgs { - public readonly NetEntity NetEntity; - public readonly Direction Direction; - - public RCDConstructionGhostRotationEvent(NetEntity netEntity, Direction direction) - { - NetEntity = netEntity; - Direction = direction; - } + public readonly NetEntity NetEntity = netEntity; + public readonly Direction Direction = direction; } [Serializable, NetSerializable] diff --git a/Content.Shared/RCD/RCDPrototype.cs b/Content.Shared/RCD/RCDPrototype.cs index 58093bbe87..b00f59f049 100644 --- a/Content.Shared/RCD/RCDPrototype.cs +++ b/Content.Shared/RCD/RCDPrototype.cs @@ -6,10 +6,10 @@ using Robust.Shared.Utility; namespace Content.Shared.RCD; /// -/// Contains the parameters for a RCD construction / operation +/// Contains the parameters for an RCD construction / operation /// [Prototype("rcd")] -public sealed partial class RCDPrototype : IPrototype +public sealed class RCDPrototype : IPrototype { [IdDataField] public string ID { get; private set; } = default!; @@ -36,13 +36,13 @@ public sealed partial class RCDPrototype : IPrototype /// Texture path for this prototypes menu icon /// [DataField, ViewVariables(VVAccess.ReadOnly)] - public SpriteSpecifier? Sprite { get; private set; } = null; + public SpriteSpecifier? Sprite { get; private set; } /// /// The entity prototype that will be constructed (mode dependent) /// [DataField, ViewVariables(VVAccess.ReadOnly)] - public string? Prototype { get; private set; } = string.Empty; + public string? Prototype { get; private set; } /// /// Number of charges consumed when the operation is completed @@ -60,10 +60,10 @@ public sealed partial class RCDPrototype : IPrototype /// The visual effect that plays during this operation /// [DataField("fx"), ViewVariables(VVAccess.ReadOnly)] - public EntProtoId? Effect { get; private set; } = null; + public EntProtoId? Effect { get; private set; } /// - /// A list of rules that govern where the entity prototype can be contructed + /// A list of rules that govern where the entity prototype can be constructed /// [DataField("rules"), ViewVariables(VVAccess.ReadOnly)] public HashSet ConstructionRules { get; private set; } = new(); @@ -84,10 +84,7 @@ public sealed partial class RCDPrototype : IPrototype [DataField, ViewVariables(VVAccess.ReadOnly)] public Box2? CollisionBounds { - get - { - return _collisionBounds; - } + get => _collisionBounds; private set { @@ -103,13 +100,13 @@ public sealed partial class RCDPrototype : IPrototype } } - private Box2? _collisionBounds = null; + private Box2? _collisionBounds; /// /// The polygon shape associated with the prototype CollisionBounds (if set) /// [ViewVariables(VVAccess.ReadOnly)] - public PolygonShape? CollisionPolygon { get; private set; } = null; + public PolygonShape? CollisionPolygon { get; private set; } /// /// Governs how the local rotation of the constructed entity will be set diff --git a/Content.Shared/RCD/Systems/RCDSystem.cs b/Content.Shared/RCD/Systems/RCDSystem.cs index a5c2af39ae..83d6660e8e 100644 --- a/Content.Shared/RCD/Systems/RCDSystem.cs +++ b/Content.Shared/RCD/Systems/RCDSystem.cs @@ -22,16 +22,12 @@ using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Timing; -using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Content.Shared.RCD.Systems; -[Virtual] -public class RCDSystem : EntitySystem +public sealed class RCDSystem : EntitySystem { - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; @@ -47,6 +43,7 @@ public class RCDSystem : EntitySystem [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly TagSystem _tags = default!; + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; private readonly int _instantConstructionDelay = 0; private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0"; @@ -74,10 +71,9 @@ public class RCDSystem : EntitySystem private void OnMapInit(EntityUid uid, RCDComponent component, MapInitEvent args) { // On init, set the RCD to its first available recipe - if (component.AvailablePrototypes.Any()) + if (component.AvailablePrototypes.Count > 0) { - component.ProtoId = component.AvailablePrototypes.First(); - UpdateCachedPrototype(uid, component); + component.ProtoId = component.AvailablePrototypes.ElementAt(0); Dirty(uid, component); return; @@ -98,7 +94,6 @@ public class RCDSystem : EntitySystem // Set the current RCD prototype to the one supplied component.ProtoId = args.ProtoId; - UpdateCachedPrototype(uid, component); Dirty(uid, component); } @@ -107,17 +102,16 @@ public class RCDSystem : EntitySystem if (!args.IsInDetailsRange) return; - // Update cached prototype if required - UpdateCachedPrototype(uid, component); + var prototype = _protoManager.Index(component.ProtoId); - var msg = Loc.GetString("rcd-component-examine-mode-details", ("mode", Loc.GetString(component.CachedPrototype.SetName))); + var msg = Loc.GetString("rcd-component-examine-mode-details", ("mode", Loc.GetString(prototype.SetName))); - if (component.CachedPrototype.Mode == RcdMode.ConstructTile || component.CachedPrototype.Mode == RcdMode.ConstructObject) + if (prototype.Mode == RcdMode.ConstructTile || prototype.Mode == RcdMode.ConstructObject) { - var name = Loc.GetString(component.CachedPrototype.SetName); + var name = Loc.GetString(prototype.SetName); - if (component.CachedPrototype.Prototype != null && - _protoManager.TryIndex(component.CachedPrototype.Prototype, out var proto)) + if (prototype.Prototype != null && + _protoManager.TryIndex(prototype.Prototype, out var proto)) name = proto.Name; msg = Loc.GetString("rcd-component-examine-build-details", ("name", name)); @@ -133,32 +127,37 @@ public class RCDSystem : EntitySystem var user = args.User; var location = args.ClickLocation; + var prototype = _protoManager.Index(component.ProtoId); // Initial validity checks if (!location.IsValid(EntityManager)) return; - if (!TryGetMapGridData(location, out var mapGridData)) + var gridUid = _transformSystem.GetGrid(location); + + if (!TryComp(gridUid, out var mapGrid)) { _popup.PopupClient(Loc.GetString("rcd-component-no-valid-grid"), uid, user); return; } + var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); + var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location); - if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Target, args.User)) + if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Target, args.User)) return; if (!_net.IsServer) return; // Get the starting cost, delay, and effect from the prototype - var cost = component.CachedPrototype.Cost; - var delay = component.CachedPrototype.Delay; - var effectPrototype = component.CachedPrototype.Effect; + var cost = prototype.Cost; + var delay = prototype.Delay; + var effectPrototype = prototype.Effect; #region: Operation modifiers // Deconstruction modifiers - switch (component.CachedPrototype.Mode) + switch (prototype.Mode) { case RcdMode.Deconstruct: @@ -176,7 +175,7 @@ public class RCDSystem : EntitySystem // Deconstructing a tile else { - var deconstructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location); + var deconstructedTile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); var protoName = !deconstructedTile.IsSpace() ? _deconstructTileProto : _deconstructLatticeProto; if (_protoManager.TryIndex(protoName, out var deconProto)) @@ -192,7 +191,7 @@ public class RCDSystem : EntitySystem case RcdMode.ConstructTile: // If replacing a tile, make the construction instant - var contructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location); + var contructedTile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); if (!contructedTile.Tile.IsEmpty) { @@ -206,8 +205,8 @@ public class RCDSystem : EntitySystem #endregion // Try to start the do after - var effect = Spawn(effectPrototype, mapGridData.Value.Location); - var ev = new RCDDoAfterEvent(GetNetCoordinates(mapGridData.Value.Location), component.ConstructionDirection, component.ProtoId, cost, EntityManager.GetNetEntity(effect)); + var effect = Spawn(effectPrototype, location); + var ev = new RCDDoAfterEvent(GetNetCoordinates(location), component.ConstructionDirection, component.ProtoId, cost, EntityManager.GetNetEntity(effect)); var doAfterArgs = new DoAfterArgs(EntityManager, user, delay, ev, uid, target: args.Target, used: uid) { @@ -240,37 +239,53 @@ public class RCDSystem : EntitySystem // Ensure the RCD operation is still valid var location = GetCoordinates(args.Event.Location); - if (!TryGetMapGridData(location, out var mapGridData)) + var gridUid = _transformSystem.GetGrid(location); + + if (!TryComp(gridUid, out var mapGrid)) { args.Cancel(); return; } - if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Event.Target, args.Event.User)) + + 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)) args.Cancel(); } private void OnDoAfter(EntityUid uid, RCDComponent component, RCDDoAfterEvent args) { - if (args.Cancelled && _net.IsServer) - QueueDel(EntityManager.GetEntity(args.Effect)); + if (args.Cancelled) + { + // Delete the effect entity if the do-after was cancelled (server-side only) + if (_net.IsServer) + QueueDel(EntityManager.GetEntity(args.Effect)); + return; + } - if (args.Handled || args.Cancelled || !_timing.IsFirstTimePredicted) + if (args.Handled) return; args.Handled = true; var location = GetCoordinates(args.Location); - if (!TryGetMapGridData(location, out var mapGridData)) + var gridUid = _transformSystem.GetGrid(location); + + if (!TryComp(gridUid, out var mapGrid)) return; + var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); + var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location); + // Ensure the RCD operation is still valid - if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Target, args.User)) + if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, args.Target, args.User)) return; - // Finalize the operation - FinalizeRCDOperation(uid, component, mapGridData.Value, args.Direction, args.Target, args.User); + // Finalize the operation (this should handle prediction properly) + FinalizeRCDOperation(uid, component, gridUid.Value, mapGrid, tile, position, args.Direction, args.Target, args.User); // Play audio and consume charges _audio.PlayPredicted(component.SuccessSound, uid, args.User); @@ -301,10 +316,9 @@ public class RCDSystem : EntitySystem #region Entity construction/deconstruction rule checks - public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user, bool popMsgs = true) + public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, EntityUid? target, EntityUid user, bool popMsgs = true) { - // Update cached prototype if required - UpdateCachedPrototype(uid, component); + var prototype = _protoManager.Index(component.ProtoId); // Check that the RCD has enough ammo to get the job done TryComp(uid, out var charges); @@ -318,7 +332,7 @@ public class RCDSystem : EntitySystem return false; } - if (_charges.HasInsufficientCharges(uid, component.CachedPrototype.Cost, charges)) + if (_charges.HasInsufficientCharges(uid, prototype.Cost, charges)) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-insufficient-ammo-message"), uid, user); @@ -328,27 +342,31 @@ public class RCDSystem : EntitySystem // Exit if the target / target location is obstructed var unobstructed = (target == null) - ? _interaction.InRangeUnobstructed(user, _mapSystem.GridTileToWorld(mapGridData.GridUid, mapGridData.Component, mapGridData.Position), popup: popMsgs) + ? _interaction.InRangeUnobstructed(user, _mapSystem.GridTileToWorld(gridUid, mapGrid, position), popup: popMsgs) : _interaction.InRangeUnobstructed(user, target.Value, popup: popMsgs); if (!unobstructed) return false; // Return whether the operation location is valid - switch (component.CachedPrototype.Mode) + switch (prototype.Mode) { - case RcdMode.ConstructTile: return IsConstructionLocationValid(uid, component, mapGridData, user, popMsgs); - case RcdMode.ConstructObject: return IsConstructionLocationValid(uid, component, mapGridData, user, popMsgs); - case RcdMode.Deconstruct: return IsDeconstructionStillValid(uid, component, mapGridData, target, user, popMsgs); + case RcdMode.ConstructTile: + case RcdMode.ConstructObject: + return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, user, popMsgs); + case RcdMode.Deconstruct: + return IsDeconstructionStillValid(uid, tile, target, user, popMsgs); } return false; } - private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid user, bool popMsgs = true) + private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, EntityUid user, bool popMsgs = true) { + var prototype = _protoManager.Index(component.ProtoId); + // Check rule: Must build on empty tile - if (component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnEmptyTile) && !mapGridData.Tile.Tile.IsEmpty) + if (prototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnEmptyTile) && !tile.Tile.IsEmpty) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-must-build-on-empty-tile-message"), uid, user); @@ -357,7 +375,7 @@ public class RCDSystem : EntitySystem } // Check rule: Must build on non-empty tile - if (!component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.CanBuildOnEmptyTile) && mapGridData.Tile.Tile.IsEmpty) + if (!prototype.ConstructionRules.Contains(RcdConstructionRule.CanBuildOnEmptyTile) && tile.Tile.IsEmpty) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-empty-tile-message"), uid, user); @@ -366,7 +384,7 @@ public class RCDSystem : EntitySystem } // Check rule: Must place on subfloor - if (component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnSubfloor) && !mapGridData.Tile.Tile.GetContentTileDefinition().IsSubFloor) + if (prototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnSubfloor) && !tile.Tile.GetContentTileDefinition().IsSubFloor) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-must-build-on-subfloor-message"), uid, user); @@ -375,10 +393,10 @@ public class RCDSystem : EntitySystem } // Tile specific rules - if (component.CachedPrototype.Mode == RcdMode.ConstructTile) + if (prototype.Mode == RcdMode.ConstructTile) { // Check rule: Tile placement is valid - if (!_floors.CanPlaceTile(mapGridData.GridUid, mapGridData.Component, out var reason)) + if (!_floors.CanPlaceTile(gridUid, mapGrid, out var reason)) { if (popMsgs) _popup.PopupClient(reason, uid, user); @@ -387,7 +405,7 @@ public class RCDSystem : EntitySystem } // Check rule: Tiles can't be identical - if (mapGridData.Tile.Tile.GetContentTileDefinition().ID == component.CachedPrototype.Prototype) + if (tile.Tile.GetContentTileDefinition().ID == prototype.Prototype) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user); @@ -402,11 +420,11 @@ public class RCDSystem : EntitySystem // Entity specific rules // Check rule: The tile is unoccupied - var isWindow = component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.IsWindow); - var isCatwalk = component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.IsCatwalk); + var isWindow = prototype.ConstructionRules.Contains(RcdConstructionRule.IsWindow); + var isCatwalk = prototype.ConstructionRules.Contains(RcdConstructionRule.IsCatwalk); _intersectingEntities.Clear(); - _lookup.GetLocalEntitiesIntersecting(mapGridData.GridUid, mapGridData.Position, _intersectingEntities, -0.05f, LookupFlags.Uncontained); + _lookup.GetLocalEntitiesIntersecting(gridUid, position, _intersectingEntities, -0.05f, LookupFlags.Uncontained); foreach (var ent in _intersectingEntities) { @@ -421,17 +439,17 @@ public class RCDSystem : EntitySystem return false; } - if (component.CachedPrototype.CollisionMask != CollisionGroup.None && TryComp(ent, out var fixtures)) + if (prototype.CollisionMask != CollisionGroup.None && TryComp(ent, out var fixtures)) { foreach (var fixture in fixtures.Fixtures.Values) { // Continue if no collision is possible - if (!fixture.Hard || fixture.CollisionLayer <= 0 || (fixture.CollisionLayer & (int) component.CachedPrototype.CollisionMask) == 0) + if (!fixture.Hard || fixture.CollisionLayer <= 0 || (fixture.CollisionLayer & (int) prototype.CollisionMask) == 0) continue; // Continue if our custom collision bounds are not intersected - if (component.CachedPrototype.CollisionPolygon != null && - !DoesCustomBoundsIntersectWithFixture(component.CachedPrototype.CollisionPolygon, component.ConstructionTransform, ent, fixture)) + if (prototype.CollisionPolygon != null && + !DoesCustomBoundsIntersectWithFixture(prototype.CollisionPolygon, component.ConstructionTransform, ent, fixture)) continue; // Collision was detected @@ -446,13 +464,13 @@ public class RCDSystem : EntitySystem return true; } - private bool IsDeconstructionStillValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user, bool popMsgs = true) + private bool IsDeconstructionStillValid(EntityUid uid, TileRef tile, EntityUid? target, EntityUid user, bool popMsgs = true) { // Attempt to deconstruct a floor tile if (target == null) { // The tile is empty - if (mapGridData.Tile.Tile.IsEmpty) + if (tile.Tile.IsEmpty) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-nothing-to-deconstruct-message"), uid, user); @@ -461,7 +479,7 @@ public class RCDSystem : EntitySystem } // The tile has a structure sitting on it - if (_turf.IsTileBlocked(mapGridData.Tile, CollisionGroup.MobMask)) + if (_turf.IsTileBlocked(tile, CollisionGroup.MobMask)) { if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user); @@ -470,7 +488,7 @@ public class RCDSystem : EntitySystem } // The tile cannot be destroyed - var tileDef = (ContentTileDefinition) _tileDefMan[mapGridData.Tile.Tile.TypeId]; + var tileDef = (ContentTileDefinition) _tileDefMan[tile.Tile.TypeId]; if (tileDef.Indestructible) { @@ -501,25 +519,27 @@ public class RCDSystem : EntitySystem #region Entity construction/deconstruction - private void FinalizeRCDOperation(EntityUid uid, RCDComponent component, MapGridData mapGridData, Direction direction, EntityUid? target, EntityUid user) + private void FinalizeRCDOperation(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid? target, EntityUid user) { if (!_net.IsServer) return; - if (component.CachedPrototype.Prototype == null) + var prototype = _protoManager.Index(component.ProtoId); + + if (prototype.Prototype == null) return; - switch (component.CachedPrototype.Mode) + switch (prototype.Mode) { case RcdMode.ConstructTile: - _mapSystem.SetTile(mapGridData.GridUid, mapGridData.Component, mapGridData.Position, new Tile(_tileDefMan[component.CachedPrototype.Prototype].TileId)); - _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} {mapGridData.Position} to {component.CachedPrototype.Prototype}"); + _mapSystem.SetTile(gridUid, mapGrid, position, new Tile(_tileDefMan[prototype.Prototype].TileId)); + _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} {position} to {prototype.Prototype}"); break; case RcdMode.ConstructObject: - var ent = Spawn(component.CachedPrototype.Prototype, _mapSystem.GridTileToLocal(mapGridData.GridUid, mapGridData.Component, mapGridData.Position)); + var ent = Spawn(prototype.Prototype, _mapSystem.GridTileToLocal(gridUid, mapGrid, position)); - switch (component.CachedPrototype.Rotation) + switch (prototype.Rotation) { case RcdRotation.Fixed: Transform(ent).LocalRotation = Angle.Zero; @@ -532,7 +552,7 @@ public class RCDSystem : EntitySystem break; } - _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to spawn {ToPrettyString(ent)} at {mapGridData.Position} on grid {mapGridData.GridUid}"); + _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to spawn {ToPrettyString(ent)} at {position} on grid {gridUid}"); break; case RcdMode.Deconstruct: @@ -540,9 +560,9 @@ public class RCDSystem : EntitySystem if (target == null) { // Deconstruct tile (either converts the tile to lattice, or removes lattice) - var tile = (mapGridData.Tile.Tile.GetContentTileDefinition().ID != "Lattice") ? new Tile(_tileDefMan["Lattice"].TileId) : Tile.Empty; - _mapSystem.SetTile(mapGridData.GridUid, mapGridData.Component, mapGridData.Position, tile); - _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} tile: {mapGridData.Position} open to space"); + var tileDef = (tile.Tile.GetContentTileDefinition().ID != "Lattice") ? new Tile(_tileDefMan["Lattice"].TileId) : Tile.Empty; + _mapSystem.SetTile(gridUid, mapGrid, position, tileDef); + _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} tile: {position} open to space"); } else { @@ -559,28 +579,6 @@ public class RCDSystem : EntitySystem #region Utility functions - public bool TryGetMapGridData(EntityCoordinates location, [NotNullWhen(true)] out MapGridData? mapGridData) - { - mapGridData = null; - var gridUid = _transform.GetGrid(location); - - if (!TryComp(gridUid, out var mapGrid)) - { - location = location.AlignWithClosestGridTile(1.75f, EntityManager); - gridUid = _transform.GetGrid(location); - - // Check if we got a grid ID the second time round - if (!TryComp(gridUid, out mapGrid)) - return false; - } - - var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location); - var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location); - mapGridData = new MapGridData(gridUid.Value, mapGrid, location, tile, position); - - return true; - } - private bool DoesCustomBoundsIntersectWithFixture(PolygonShape boundingPolygon, Transform boundingTransform, EntityUid fixtureOwner, Fixture fixture) { var entXformComp = Transform(fixtureOwner); @@ -589,50 +587,26 @@ public class RCDSystem : EntitySystem return boundingPolygon.ComputeAABB(boundingTransform, 0).Intersects(fixture.Shape.ComputeAABB(entXform, 0)); } - public void UpdateCachedPrototype(EntityUid uid, RCDComponent component) - { - if (component.ProtoId.Id != component.CachedPrototype?.Prototype) - component.CachedPrototype = _protoManager.Index(component.ProtoId); - } - #endregion } -public struct MapGridData -{ - public EntityUid GridUid; - public MapGridComponent Component; - public EntityCoordinates Location; - public TileRef Tile; - public Vector2i Position; - - public MapGridData(EntityUid gridUid, MapGridComponent component, EntityCoordinates location, TileRef tile, Vector2i position) - { - GridUid = gridUid; - Component = component; - Location = location; - Tile = tile; - Position = position; - } -} - [Serializable, NetSerializable] public sealed partial class RCDDoAfterEvent : DoAfterEvent { [DataField(required: true)] - public NetCoordinates Location { get; private set; } = default!; + public NetCoordinates Location { get; private set; } [DataField] - public Direction Direction { get; private set; } = default!; + public Direction Direction { get; private set; } [DataField] - public ProtoId StartingProtoId { get; private set; } = default!; + public ProtoId StartingProtoId { get; private set; } [DataField] public int Cost { get; private set; } = 1; [DataField("fx")] - public NetEntity? Effect { get; private set; } = null; + public NetEntity? Effect { get; private set; } private RCDDoAfterEvent() { } @@ -645,5 +619,8 @@ public sealed partial class RCDDoAfterEvent : DoAfterEvent Effect = effect; } - public override DoAfterEvent Clone() => this; + public override DoAfterEvent Clone() + { + return this; + } }