RCD Refactor (#34781)

* fixed prediction (hopefully), removed caching of prototype, sealed the class, removed any and count

* erroneus using statement

* removed unused timing, removed obsolete method of getting gridUid

* nuked mapgriddata

* code cleanup

* cleanup

* this has to be a string without me rewriting more code than i want to in this moment

* kill

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
gus
2025-04-17 18:34:38 -07:00
committed by GitHub
parent 944aa57907
commit 2c60d6b27f
6 changed files with 129 additions and 175 deletions

View File

@@ -96,20 +96,22 @@ public sealed class AlignRCDConstruction : PlacementMode
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd)) if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
return false; return false;
// Retrieve the map grid data for the position var gridUid = _transformSystem.GetGrid(position);
if (!_rcdSystem.TryGetMapGridData(position, out var mapGridData)) if (!_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var mapGrid))
return false; 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 // Determine if the user is hovering over a target
var currentState = _stateManager.CurrentState; var currentState = _stateManager.CurrentState;
if (currentState is not GameplayStateBase screen) if (currentState is not GameplayStateBase screen)
return false; return false;
var target = screen.GetClickedEntity(_transformSystem.ToMapCoordinates(_unalignedMouseCoords)); var target = screen.GetClickedEntity(_transformSystem.ToMapCoordinates(_unalignedMouseCoords));
// Determine if the RCD operation is valid or not // 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 false;
return true; return true;

View File

@@ -6,6 +6,7 @@ using Content.Shared.RCD.Systems;
using Robust.Client.Placement; using Robust.Client.Placement;
using Robust.Client.Player; using Robust.Client.Player;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
namespace Content.Client.RCD; namespace Content.Client.RCD;
@@ -14,6 +15,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly RCDSystem _rcdSystem = default!; [Dependency] private readonly RCDSystem _rcdSystem = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private string _placementMode = typeof(AlignRCDConstruction).Name; private string _placementMode = typeof(AlignRCDConstruction).Name;
private Direction _placementDirection = default; private Direction _placementDirection = default;
@@ -47,6 +49,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
return; return;
} }
var prototype = _protoManager.Index(rcd.ProtoId);
// Update the direction the RCD prototype based on the placer direction // Update the direction the RCD prototype based on the placer direction
if (_placementDirection != _placementManager.Direction) if (_placementDirection != _placementManager.Direction)
@@ -56,9 +59,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
} }
// If the placer has not changed, exit // If the placer has not changed, exit
_rcdSystem.UpdateCachedPrototype(heldEntity.Value, rcd); if (heldEntity == placerEntity && prototype.Prototype == placerProto)
if (heldEntity == placerEntity && rcd.CachedPrototype.Prototype == placerProto)
return; return;
// Create a new placer // Create a new placer
@@ -66,9 +67,9 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
{ {
MobUid = heldEntity.Value, MobUid = heldEntity.Value,
PlacementOption = _placementMode, PlacementOption = _placementMode,
EntityType = rcd.CachedPrototype.Prototype, EntityType = prototype.Prototype,
Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange), Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange),
IsTile = (rcd.CachedPrototype.Mode == RcdMode.ConstructTile), IsTile = (prototype.Mode == RcdMode.ConstructTile),
UseEditorContext = false, UseEditorContext = false,
}; };

View File

@@ -33,25 +33,13 @@ public sealed partial class RCDComponent : Component
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public ProtoId<RCDPrototype> ProtoId { get; set; } = "Invalid"; public ProtoId<RCDPrototype> ProtoId { get; set; } = "Invalid";
/// <summary>
/// A cached copy of currently selected RCD prototype
/// </summary>
/// <remarks>
/// If the ProtoId is changed, make sure to update the CachedPrototype as well
/// </remarks>
[ViewVariables(VVAccess.ReadOnly)]
public RCDPrototype CachedPrototype { get; set; } = default!;
/// <summary> /// <summary>
/// The direction constructed entities will face upon spawning /// The direction constructed entities will face upon spawning
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public Direction ConstructionDirection public Direction ConstructionDirection
{ {
get get => _constructionDirection;
{
return _constructionDirection;
}
set set
{ {
_constructionDirection = value; _constructionDirection = value;
@@ -68,5 +56,5 @@ public sealed partial class RCDComponent : Component
/// Contains no position data /// Contains no position data
/// </remarks> /// </remarks>
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
public Transform ConstructionTransform { get; private set; } = default!; public Transform ConstructionTransform { get; private set; }
} }

View File

@@ -4,27 +4,16 @@ using Robust.Shared.Serialization;
namespace Content.Shared.RCD; namespace Content.Shared.RCD;
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class RCDSystemMessage : BoundUserInterfaceMessage public sealed class RCDSystemMessage(ProtoId<RCDPrototype> protoId) : BoundUserInterfaceMessage
{ {
public ProtoId<RCDPrototype> ProtoId; public ProtoId<RCDPrototype> ProtoId = protoId;
public RCDSystemMessage(ProtoId<RCDPrototype> protoId)
{
ProtoId = protoId;
}
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class RCDConstructionGhostRotationEvent : EntityEventArgs public sealed class RCDConstructionGhostRotationEvent(NetEntity netEntity, Direction direction) : EntityEventArgs
{ {
public readonly NetEntity NetEntity; public readonly NetEntity NetEntity = netEntity;
public readonly Direction Direction; public readonly Direction Direction = direction;
public RCDConstructionGhostRotationEvent(NetEntity netEntity, Direction direction)
{
NetEntity = netEntity;
Direction = direction;
}
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]

View File

@@ -6,10 +6,10 @@ using Robust.Shared.Utility;
namespace Content.Shared.RCD; namespace Content.Shared.RCD;
/// <summary> /// <summary>
/// Contains the parameters for a RCD construction / operation /// Contains the parameters for an RCD construction / operation
/// </summary> /// </summary>
[Prototype("rcd")] [Prototype("rcd")]
public sealed partial class RCDPrototype : IPrototype public sealed class RCDPrototype : IPrototype
{ {
[IdDataField] [IdDataField]
public string ID { get; private set; } = default!; public string ID { get; private set; } = default!;
@@ -36,13 +36,13 @@ public sealed partial class RCDPrototype : IPrototype
/// Texture path for this prototypes menu icon /// Texture path for this prototypes menu icon
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)] [DataField, ViewVariables(VVAccess.ReadOnly)]
public SpriteSpecifier? Sprite { get; private set; } = null; public SpriteSpecifier? Sprite { get; private set; }
/// <summary> /// <summary>
/// The entity prototype that will be constructed (mode dependent) /// The entity prototype that will be constructed (mode dependent)
/// </summary> /// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)] [DataField, ViewVariables(VVAccess.ReadOnly)]
public string? Prototype { get; private set; } = string.Empty; public string? Prototype { get; private set; }
/// <summary> /// <summary>
/// Number of charges consumed when the operation is completed /// 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 /// The visual effect that plays during this operation
/// </summary> /// </summary>
[DataField("fx"), ViewVariables(VVAccess.ReadOnly)] [DataField("fx"), ViewVariables(VVAccess.ReadOnly)]
public EntProtoId? Effect { get; private set; } = null; public EntProtoId? Effect { get; private set; }
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
[DataField("rules"), ViewVariables(VVAccess.ReadOnly)] [DataField("rules"), ViewVariables(VVAccess.ReadOnly)]
public HashSet<RcdConstructionRule> ConstructionRules { get; private set; } = new(); public HashSet<RcdConstructionRule> ConstructionRules { get; private set; } = new();
@@ -84,10 +84,7 @@ public sealed partial class RCDPrototype : IPrototype
[DataField, ViewVariables(VVAccess.ReadOnly)] [DataField, ViewVariables(VVAccess.ReadOnly)]
public Box2? CollisionBounds public Box2? CollisionBounds
{ {
get get => _collisionBounds;
{
return _collisionBounds;
}
private set private set
{ {
@@ -103,13 +100,13 @@ public sealed partial class RCDPrototype : IPrototype
} }
} }
private Box2? _collisionBounds = null; private Box2? _collisionBounds;
/// <summary> /// <summary>
/// The polygon shape associated with the prototype CollisionBounds (if set) /// The polygon shape associated with the prototype CollisionBounds (if set)
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadOnly)] [ViewVariables(VVAccess.ReadOnly)]
public PolygonShape? CollisionPolygon { get; private set; } = null; public PolygonShape? CollisionPolygon { get; private set; }
/// <summary> /// <summary>
/// Governs how the local rotation of the constructed entity will be set /// Governs how the local rotation of the constructed entity will be set

View File

@@ -22,16 +22,12 @@ using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace Content.Shared.RCD.Systems; namespace Content.Shared.RCD.Systems;
[Virtual] public sealed class RCDSystem : EntitySystem
public class RCDSystem : EntitySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly INetManager _net = default!; [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default!; [Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
@@ -47,6 +43,7 @@ public class RCDSystem : EntitySystem
[Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly TagSystem _tags = default!; [Dependency] private readonly TagSystem _tags = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
private readonly int _instantConstructionDelay = 0; private readonly int _instantConstructionDelay = 0;
private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0"; private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0";
@@ -74,10 +71,9 @@ public class RCDSystem : EntitySystem
private void OnMapInit(EntityUid uid, RCDComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, RCDComponent component, MapInitEvent args)
{ {
// On init, set the RCD to its first available recipe // On init, set the RCD to its first available recipe
if (component.AvailablePrototypes.Any()) if (component.AvailablePrototypes.Count > 0)
{ {
component.ProtoId = component.AvailablePrototypes.First(); component.ProtoId = component.AvailablePrototypes.ElementAt(0);
UpdateCachedPrototype(uid, component);
Dirty(uid, component); Dirty(uid, component);
return; return;
@@ -98,7 +94,6 @@ public class RCDSystem : EntitySystem
// Set the current RCD prototype to the one supplied // Set the current RCD prototype to the one supplied
component.ProtoId = args.ProtoId; component.ProtoId = args.ProtoId;
UpdateCachedPrototype(uid, component);
Dirty(uid, component); Dirty(uid, component);
} }
@@ -107,17 +102,16 @@ public class RCDSystem : EntitySystem
if (!args.IsInDetailsRange) if (!args.IsInDetailsRange)
return; return;
// Update cached prototype if required var prototype = _protoManager.Index(component.ProtoId);
UpdateCachedPrototype(uid, component);
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 && if (prototype.Prototype != null &&
_protoManager.TryIndex(component.CachedPrototype.Prototype, out var proto)) _protoManager.TryIndex(prototype.Prototype, out var proto))
name = proto.Name; name = proto.Name;
msg = Loc.GetString("rcd-component-examine-build-details", ("name", name)); msg = Loc.GetString("rcd-component-examine-build-details", ("name", name));
@@ -133,32 +127,37 @@ public class RCDSystem : EntitySystem
var user = args.User; var user = args.User;
var location = args.ClickLocation; var location = args.ClickLocation;
var prototype = _protoManager.Index(component.ProtoId);
// Initial validity checks // Initial validity checks
if (!location.IsValid(EntityManager)) if (!location.IsValid(EntityManager))
return; return;
if (!TryGetMapGridData(location, out var mapGridData)) var gridUid = _transformSystem.GetGrid(location);
if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
{ {
_popup.PopupClient(Loc.GetString("rcd-component-no-valid-grid"), uid, user); _popup.PopupClient(Loc.GetString("rcd-component-no-valid-grid"), uid, user);
return; 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; return;
if (!_net.IsServer) if (!_net.IsServer)
return; return;
// Get the starting cost, delay, and effect from the prototype // Get the starting cost, delay, and effect from the prototype
var cost = component.CachedPrototype.Cost; var cost = prototype.Cost;
var delay = component.CachedPrototype.Delay; var delay = prototype.Delay;
var effectPrototype = component.CachedPrototype.Effect; var effectPrototype = prototype.Effect;
#region: Operation modifiers #region: Operation modifiers
// Deconstruction modifiers // Deconstruction modifiers
switch (component.CachedPrototype.Mode) switch (prototype.Mode)
{ {
case RcdMode.Deconstruct: case RcdMode.Deconstruct:
@@ -176,7 +175,7 @@ public class RCDSystem : EntitySystem
// Deconstructing a tile // Deconstructing a tile
else 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; var protoName = !deconstructedTile.IsSpace() ? _deconstructTileProto : _deconstructLatticeProto;
if (_protoManager.TryIndex(protoName, out var deconProto)) if (_protoManager.TryIndex(protoName, out var deconProto))
@@ -192,7 +191,7 @@ public class RCDSystem : EntitySystem
case RcdMode.ConstructTile: case RcdMode.ConstructTile:
// If replacing a tile, make the construction instant // 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) if (!contructedTile.Tile.IsEmpty)
{ {
@@ -206,8 +205,8 @@ public class RCDSystem : EntitySystem
#endregion #endregion
// Try to start the do after // Try to start the do after
var effect = Spawn(effectPrototype, mapGridData.Value.Location); var effect = Spawn(effectPrototype, location);
var ev = new RCDDoAfterEvent(GetNetCoordinates(mapGridData.Value.Location), component.ConstructionDirection, component.ProtoId, cost, EntityManager.GetNetEntity(effect)); 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) 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 // Ensure the RCD operation is still valid
var location = GetCoordinates(args.Event.Location); var location = GetCoordinates(args.Event.Location);
if (!TryGetMapGridData(location, out var mapGridData)) var gridUid = _transformSystem.GetGrid(location);
if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
{ {
args.Cancel(); args.Cancel();
return; 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(); args.Cancel();
} }
private void OnDoAfter(EntityUid uid, RCDComponent component, RCDDoAfterEvent args) private void OnDoAfter(EntityUid uid, RCDComponent component, RCDDoAfterEvent args)
{ {
if (args.Cancelled && _net.IsServer) if (args.Cancelled)
QueueDel(EntityManager.GetEntity(args.Effect)); {
// 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; return;
args.Handled = true; args.Handled = true;
var location = GetCoordinates(args.Location); var location = GetCoordinates(args.Location);
if (!TryGetMapGridData(location, out var mapGridData)) var gridUid = _transformSystem.GetGrid(location);
if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
return; return;
var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location);
var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location);
// Ensure the RCD operation is still valid // 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; return;
// Finalize the operation // Finalize the operation (this should handle prediction properly)
FinalizeRCDOperation(uid, component, mapGridData.Value, args.Direction, args.Target, args.User); FinalizeRCDOperation(uid, component, gridUid.Value, mapGrid, tile, position, args.Direction, args.Target, args.User);
// Play audio and consume charges // Play audio and consume charges
_audio.PlayPredicted(component.SuccessSound, uid, args.User); _audio.PlayPredicted(component.SuccessSound, uid, args.User);
@@ -301,10 +316,9 @@ public class RCDSystem : EntitySystem
#region Entity construction/deconstruction rule checks #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 var prototype = _protoManager.Index(component.ProtoId);
UpdateCachedPrototype(uid, component);
// Check that the RCD has enough ammo to get the job done // Check that the RCD has enough ammo to get the job done
TryComp<LimitedChargesComponent>(uid, out var charges); TryComp<LimitedChargesComponent>(uid, out var charges);
@@ -318,7 +332,7 @@ public class RCDSystem : EntitySystem
return false; return false;
} }
if (_charges.HasInsufficientCharges(uid, component.CachedPrototype.Cost, charges)) if (_charges.HasInsufficientCharges(uid, prototype.Cost, charges))
{ {
if (popMsgs) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-insufficient-ammo-message"), uid, user); _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 // Exit if the target / target location is obstructed
var unobstructed = (target == null) 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); : _interaction.InRangeUnobstructed(user, target.Value, popup: popMsgs);
if (!unobstructed) if (!unobstructed)
return false; return false;
// Return whether the operation location is valid // 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.ConstructTile:
case RcdMode.ConstructObject: return IsConstructionLocationValid(uid, component, mapGridData, user, popMsgs); case RcdMode.ConstructObject:
case RcdMode.Deconstruct: return IsDeconstructionStillValid(uid, component, mapGridData, target, user, popMsgs); return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, user, popMsgs);
case RcdMode.Deconstruct:
return IsDeconstructionStillValid(uid, tile, target, user, popMsgs);
} }
return false; 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 // 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) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-must-build-on-empty-tile-message"), uid, user); _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 // 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) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-empty-tile-message"), uid, user); _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 // 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) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-must-build-on-subfloor-message"), uid, user); _popup.PopupClient(Loc.GetString("rcd-component-must-build-on-subfloor-message"), uid, user);
@@ -375,10 +393,10 @@ public class RCDSystem : EntitySystem
} }
// Tile specific rules // Tile specific rules
if (component.CachedPrototype.Mode == RcdMode.ConstructTile) if (prototype.Mode == RcdMode.ConstructTile)
{ {
// Check rule: Tile placement is valid // 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) if (popMsgs)
_popup.PopupClient(reason, uid, user); _popup.PopupClient(reason, uid, user);
@@ -387,7 +405,7 @@ public class RCDSystem : EntitySystem
} }
// Check rule: Tiles can't be identical // 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) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user); _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user);
@@ -402,11 +420,11 @@ public class RCDSystem : EntitySystem
// Entity specific rules // Entity specific rules
// Check rule: The tile is unoccupied // Check rule: The tile is unoccupied
var isWindow = component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.IsWindow); var isWindow = prototype.ConstructionRules.Contains(RcdConstructionRule.IsWindow);
var isCatwalk = component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.IsCatwalk); var isCatwalk = prototype.ConstructionRules.Contains(RcdConstructionRule.IsCatwalk);
_intersectingEntities.Clear(); _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) foreach (var ent in _intersectingEntities)
{ {
@@ -421,17 +439,17 @@ public class RCDSystem : EntitySystem
return false; return false;
} }
if (component.CachedPrototype.CollisionMask != CollisionGroup.None && TryComp<FixturesComponent>(ent, out var fixtures)) if (prototype.CollisionMask != CollisionGroup.None && TryComp<FixturesComponent>(ent, out var fixtures))
{ {
foreach (var fixture in fixtures.Fixtures.Values) foreach (var fixture in fixtures.Fixtures.Values)
{ {
// Continue if no collision is possible // 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;
// Continue if our custom collision bounds are not intersected // Continue if our custom collision bounds are not intersected
if (component.CachedPrototype.CollisionPolygon != null && if (prototype.CollisionPolygon != null &&
!DoesCustomBoundsIntersectWithFixture(component.CachedPrototype.CollisionPolygon, component.ConstructionTransform, ent, fixture)) !DoesCustomBoundsIntersectWithFixture(prototype.CollisionPolygon, component.ConstructionTransform, ent, fixture))
continue; continue;
// Collision was detected // Collision was detected
@@ -446,13 +464,13 @@ public class RCDSystem : EntitySystem
return true; 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 // Attempt to deconstruct a floor tile
if (target == null) if (target == null)
{ {
// The tile is empty // The tile is empty
if (mapGridData.Tile.Tile.IsEmpty) if (tile.Tile.IsEmpty)
{ {
if (popMsgs) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-nothing-to-deconstruct-message"), uid, user); _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 // The tile has a structure sitting on it
if (_turf.IsTileBlocked(mapGridData.Tile, CollisionGroup.MobMask)) if (_turf.IsTileBlocked(tile, CollisionGroup.MobMask))
{ {
if (popMsgs) if (popMsgs)
_popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user); _popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user);
@@ -470,7 +488,7 @@ public class RCDSystem : EntitySystem
} }
// The tile cannot be destroyed // The tile cannot be destroyed
var tileDef = (ContentTileDefinition) _tileDefMan[mapGridData.Tile.Tile.TypeId]; var tileDef = (ContentTileDefinition) _tileDefMan[tile.Tile.TypeId];
if (tileDef.Indestructible) if (tileDef.Indestructible)
{ {
@@ -501,25 +519,27 @@ public class RCDSystem : EntitySystem
#region Entity construction/deconstruction #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) if (!_net.IsServer)
return; return;
if (component.CachedPrototype.Prototype == null) var prototype = _protoManager.Index(component.ProtoId);
if (prototype.Prototype == null)
return; return;
switch (component.CachedPrototype.Mode) switch (prototype.Mode)
{ {
case RcdMode.ConstructTile: case RcdMode.ConstructTile:
_mapSystem.SetTile(mapGridData.GridUid, mapGridData.Component, mapGridData.Position, new Tile(_tileDefMan[component.CachedPrototype.Prototype].TileId)); _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: {mapGridData.GridUid} {mapGridData.Position} to {component.CachedPrototype.Prototype}"); _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} {position} to {prototype.Prototype}");
break; break;
case RcdMode.ConstructObject: 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: case RcdRotation.Fixed:
Transform(ent).LocalRotation = Angle.Zero; Transform(ent).LocalRotation = Angle.Zero;
@@ -532,7 +552,7 @@ public class RCDSystem : EntitySystem
break; 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; break;
case RcdMode.Deconstruct: case RcdMode.Deconstruct:
@@ -540,9 +560,9 @@ public class RCDSystem : EntitySystem
if (target == null) if (target == null)
{ {
// Deconstruct tile (either converts the tile to lattice, or removes lattice) // 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; var tileDef = (tile.Tile.GetContentTileDefinition().ID != "Lattice") ? new Tile(_tileDefMan["Lattice"].TileId) : Tile.Empty;
_mapSystem.SetTile(mapGridData.GridUid, mapGridData.Component, mapGridData.Position, tile); _mapSystem.SetTile(gridUid, mapGrid, position, tileDef);
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {mapGridData.GridUid} tile: {mapGridData.Position} open to space"); _adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} tile: {position} open to space");
} }
else else
{ {
@@ -559,28 +579,6 @@ public class RCDSystem : EntitySystem
#region Utility functions #region Utility functions
public bool TryGetMapGridData(EntityCoordinates location, [NotNullWhen(true)] out MapGridData? mapGridData)
{
mapGridData = null;
var gridUid = _transform.GetGrid(location);
if (!TryComp<MapGridComponent>(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) private bool DoesCustomBoundsIntersectWithFixture(PolygonShape boundingPolygon, Transform boundingTransform, EntityUid fixtureOwner, Fixture fixture)
{ {
var entXformComp = Transform(fixtureOwner); var entXformComp = Transform(fixtureOwner);
@@ -589,50 +587,26 @@ public class RCDSystem : EntitySystem
return boundingPolygon.ComputeAABB(boundingTransform, 0).Intersects(fixture.Shape.ComputeAABB(entXform, 0)); 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 #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] [Serializable, NetSerializable]
public sealed partial class RCDDoAfterEvent : DoAfterEvent public sealed partial class RCDDoAfterEvent : DoAfterEvent
{ {
[DataField(required: true)] [DataField(required: true)]
public NetCoordinates Location { get; private set; } = default!; public NetCoordinates Location { get; private set; }
[DataField] [DataField]
public Direction Direction { get; private set; } = default!; public Direction Direction { get; private set; }
[DataField] [DataField]
public ProtoId<RCDPrototype> StartingProtoId { get; private set; } = default!; public ProtoId<RCDPrototype> StartingProtoId { get; private set; }
[DataField] [DataField]
public int Cost { get; private set; } = 1; public int Cost { get; private set; } = 1;
[DataField("fx")] [DataField("fx")]
public NetEntity? Effect { get; private set; } = null; public NetEntity? Effect { get; private set; }
private RCDDoAfterEvent() { } private RCDDoAfterEvent() { }
@@ -645,5 +619,8 @@ public sealed partial class RCDDoAfterEvent : DoAfterEvent
Effect = effect; Effect = effect;
} }
public override DoAfterEvent Clone() => this; public override DoAfterEvent Clone()
{
return this;
}
} }