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,9 +96,11 @@ public sealed class AlignRCDConstruction : PlacementMode
if (!_entityManager.TryGetComponent<RCDComponent>(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<MapGridComponent>(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;
@@ -109,7 +111,7 @@ public sealed class AlignRCDConstruction : PlacementMode
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;

View File

@@ -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,
};

View File

@@ -33,25 +33,13 @@ public sealed partial class RCDComponent : Component
[DataField, AutoNetworkedField]
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>
/// The direction constructed entities will face upon spawning
/// </summary>
[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
/// </remarks>
[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;
[Serializable, NetSerializable]
public sealed class RCDSystemMessage : BoundUserInterfaceMessage
public sealed class RCDSystemMessage(ProtoId<RCDPrototype> protoId) : BoundUserInterfaceMessage
{
public ProtoId<RCDPrototype> ProtoId;
public RCDSystemMessage(ProtoId<RCDPrototype> protoId)
{
ProtoId = protoId;
}
public ProtoId<RCDPrototype> 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]

View File

@@ -6,10 +6,10 @@ using Robust.Shared.Utility;
namespace Content.Shared.RCD;
/// <summary>
/// Contains the parameters for a RCD construction / operation
/// Contains the parameters for an RCD construction / operation
/// </summary>
[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
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public SpriteSpecifier? Sprite { get; private set; } = null;
public SpriteSpecifier? Sprite { get; private set; }
/// <summary>
/// The entity prototype that will be constructed (mode dependent)
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public string? Prototype { get; private set; } = string.Empty;
public string? Prototype { get; private set; }
/// <summary>
/// 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
/// </summary>
[DataField("fx"), ViewVariables(VVAccess.ReadOnly)]
public EntProtoId? Effect { get; private set; } = null;
public EntProtoId? Effect { get; private set; }
/// <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>
[DataField("rules"), ViewVariables(VVAccess.ReadOnly)]
public HashSet<RcdConstructionRule> 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;
/// <summary>
/// The polygon shape associated with the prototype CollisionBounds (if set)
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public PolygonShape? CollisionPolygon { get; private set; } = null;
public PolygonShape? CollisionPolygon { get; private set; }
/// <summary>
/// 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.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<MapGridComponent>(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<MapGridComponent>(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<MapGridComponent>(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<LimitedChargesComponent>(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<FixturesComponent>(ent, out var fixtures))
if (prototype.CollisionMask != CollisionGroup.None && TryComp<FixturesComponent>(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<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)
{
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<RCDPrototype> StartingProtoId { get; private set; } = default!;
public ProtoId<RCDPrototype> 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;
}
}