Merge cbb358f521 into c3c6a6abd9
|
|
@ -1,11 +1,15 @@
|
|||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared.Input; //DeltaV - RPD
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.RCD;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Input; //DeltaV - RPD
|
||||
using Robust.Shared.Input.Binding; //DeltaV - RPD
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Client.Atmos; //DeltaV - RPD
|
||||
|
||||
namespace Content.Client.RCD;
|
||||
|
||||
|
|
@ -15,6 +19,7 @@ namespace Content.Client.RCD;
|
|||
public sealed class RCDConstructionGhostSystem : EntitySystem
|
||||
{
|
||||
private const string PlacementMode = nameof(AlignRCDConstruction);
|
||||
private const string RpdPlacementMode = nameof(AlignRPDAtmosPipeLayers); //DeltaV - RPD
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
|
|
@ -22,6 +27,50 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
|
|||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
|
||||
private Direction _placementDirection = default;
|
||||
//DeltaV - RPD Begin
|
||||
private bool _useMirrorPrototype = false;
|
||||
public event EventHandler? FlipConstructionPrototype;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// bind key
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.EditorFlipObject,
|
||||
new PointerInputCmdHandler(HandleFlip, outsidePrediction: true))
|
||||
.Register<RCDConstructionGhostSystem>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
CommandBinds.Unregister<RCDConstructionGhostSystem>();
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private bool HandleFlip(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.State == BoundKeyState.Down)
|
||||
{
|
||||
if (!_placementManager.IsActive || _placementManager.Eraser)
|
||||
return false;
|
||||
|
||||
var placerEntity = _placementManager.CurrentPermission?.MobUid;
|
||||
|
||||
if (!TryComp<RCDComponent>(placerEntity, out var rcd) ||
|
||||
string.IsNullOrEmpty(rcd.CachedPrototype.MirrorPrototype))
|
||||
return false;
|
||||
|
||||
_useMirrorPrototype = !rcd.UseMirrorPrototype;
|
||||
|
||||
// tell the server
|
||||
|
||||
RaiseNetworkEvent(new RCDConstructionGhostFlipEvent(GetNetEntity(placerEntity.Value), _useMirrorPrototype));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
|
|
@ -55,7 +104,19 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
|
|||
|
||||
return;
|
||||
}
|
||||
var prototype = _protoManager.Index(rcd.ProtoId);
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
// Determine if mirrored
|
||||
var cachedProto = rcd.CachedPrototype;
|
||||
var wantMirror = _useMirrorPrototype && !string.IsNullOrEmpty(cachedProto.MirrorPrototype);
|
||||
var prototype = wantMirror ? cachedProto.MirrorPrototype : cachedProto.Prototype;
|
||||
|
||||
bool isLayered = rcd.IsRpd
|
||||
&& _protoManager.TryIndex<RCDPrototype>(cachedProto.ID, out var rcdProto)
|
||||
&& rcdProto.HasLayers;
|
||||
|
||||
var desiredMode = isLayered ? RpdPlacementMode : PlacementMode;
|
||||
//DeltaV - RPD End
|
||||
|
||||
// Update the direction the RCD prototype based on the placer direction
|
||||
if (_placementDirection != _placementManager.Direction)
|
||||
|
|
@ -64,18 +125,22 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
|
|||
RaiseNetworkEvent(new RCDConstructionGhostRotationEvent(GetNetEntity(heldEntity.Value), _placementDirection));
|
||||
}
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
// If the placer has not changed, exit
|
||||
if (heldEntity == placerEntity && prototype.Prototype == placerProto)
|
||||
if (heldEntity == placerEntity &&
|
||||
prototype == placerProto &&
|
||||
_placementManager.CurrentPermission?.PlacementOption == desiredMode)
|
||||
//DeltaV - RPD End
|
||||
return;
|
||||
|
||||
// Create a new placer
|
||||
var newObjInfo = new PlacementInformation
|
||||
{
|
||||
MobUid = heldEntity.Value,
|
||||
PlacementOption = PlacementMode,
|
||||
EntityType = prototype.Prototype,
|
||||
PlacementOption = desiredMode, //DeltaV - RPD
|
||||
EntityType = prototype,
|
||||
Range = (int)Math.Ceiling(SharedInteractionSystem.InteractionRange),
|
||||
IsTile = (prototype.Mode == RcdMode.ConstructTile),
|
||||
IsTile = (cachedProto.Mode == RcdMode.ConstructTile), //DeltaV - RPD
|
||||
UseEditorContext = false,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,15 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
|
|||
["Airlocks"] = ("rcd-component-airlocks", new SpriteSpecifier.Texture(new ResPath("/Textures/_DV/Interface/Radial/RCD/airlocks.png"))), // DeltaV - Path changed to new sprites reflecting Delta-V resprites
|
||||
["Electrical"] = ("rcd-component-electrical", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/multicoil.png"))),
|
||||
["Lighting"] = ("rcd-component-lighting", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/lighting.png"))),
|
||||
["Piping"] = ("rcd-component-piping", new SpriteSpecifier.Texture(new ResPath("/Textures/_DV/Interface/Radial/RPD/fourway.png"))), //DeltaV - RPD
|
||||
["AtmosphericUtility"] = ("rcd-component-atmospheric-utility", new SpriteSpecifier.Texture(new ResPath("/Textures/_DV/Interface/Radial/RPD/port.png"))), //DeltaV - RPD
|
||||
["PumpsValves"] = ("rcd-component-pumps-valves", new SpriteSpecifier.Texture(new ResPath("/Textures/_DV/Interface/Radial/RPD/pump_volume.png"))), //DeltaV - RPD
|
||||
["Vents"] = ("rcd-component-vents", new SpriteSpecifier.Texture(new ResPath("/Textures/_DV/Interface/Radial/RPD/vent_passive.png"))), //DeltaV - RPD
|
||||
["SensorsMonitors"] = ("rcd-component-sensors-monitors", new SpriteSpecifier.Texture(new ResPath("/Textures/_DV/Interface/Radial/RPD/alarm.png"))), //DeltaV - RPD
|
||||
};
|
||||
|
||||
private bool IsRpd => EntMan.TryGetComponent<RCDComponent>(Owner, out var rcd) && rcd.IsRpd; //DeltaV - RPD
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,286 @@
|
|||
using Content.Client.Gameplay;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.RCD;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Numerics;
|
||||
using static Robust.Client.Placement.PlacementManager;
|
||||
|
||||
namespace Content.Client.RCD;
|
||||
|
||||
/// <summary>
|
||||
/// Funkystation
|
||||
/// Allows users to place RCD prototypes with atmos pipe layers on different layers depending on how the mouse cursor is positioned within a grid tile.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This placement mode is not on the engine because it is content specific.
|
||||
/// </remarks>
|
||||
public sealed class AlignRPDAtmosPipeLayers : PlacementMode
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entityNetwork = default!;
|
||||
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private readonly SharedAtmosPipeLayersSystem _pipeLayersSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly RCDSystem _rcdSystem;
|
||||
private readonly HandsSystem _handsSystem;
|
||||
|
||||
private const float SearchBoxSize = 2f;
|
||||
private const float MouseDeadzoneRadius = 0.25f;
|
||||
private const float PlaceColorBaseAlpha = 0.5f;
|
||||
private const float GuideRadius = 0.1f;
|
||||
private const float GuideOffset = 0.21875f;
|
||||
|
||||
private EntityCoordinates _mouseCoordsRaw = default;
|
||||
private static AtmosPipeLayer _currentLayer = AtmosPipeLayer.Primary;
|
||||
private static float? _currentEyeRotation = null;
|
||||
private Color _guideColor = new(0, 0, 0.5785f);
|
||||
|
||||
public AlignRPDAtmosPipeLayers(PlacementManager pMan) : base(pMan)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||||
_rcdSystem = _entityManager.System<RCDSystem>();
|
||||
_pipeLayersSystem = _entityManager.System<SharedAtmosPipeLayersSystem>();
|
||||
_handsSystem = _entityManager.System<HandsSystem>();
|
||||
ValidPlaceColor = ValidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
|
||||
}
|
||||
|
||||
public override void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
// Early exit if mouse is out of interaction range
|
||||
if (_playerManager.LocalSession?.AttachedEntity is not { } player ||
|
||||
!_entityManager.TryGetComponent<TransformComponent>(player, out var xform) ||
|
||||
!_transformSystem.InRange(xform.Coordinates, MouseCoords, SharedInteractionSystem.InteractionRange))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var gridUid = _transformSystem.GetGrid(MouseCoords);
|
||||
|
||||
if (gridUid == null || !_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
return;
|
||||
|
||||
if (!_handsSystem.TryGetActiveItem(player, out var heldEntity) ||
|
||||
!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
|
||||
return;
|
||||
|
||||
// Draw guide circles for each pipe layer if we are not in line/grid placing mode
|
||||
if (rcd.CurrentMode == RpdMode.Free && pManager.PlacementType == PlacementTypes.None )
|
||||
{
|
||||
var gridRotation = _transformSystem.GetWorldRotation(gridUid.Value);
|
||||
var worldPosition = _mapSystem.LocalToWorld(gridUid.Value, grid, MouseCoords.Position);
|
||||
var direction = (_eyeManager.CurrentEye.Rotation + gridRotation + Math.PI / 2).GetCardinalDir();
|
||||
var multi = (direction == Direction.North || direction == Direction.South) ? -1f : 1f;
|
||||
|
||||
args.WorldHandle.DrawCircle(worldPosition, GuideRadius, _guideColor);
|
||||
args.WorldHandle.DrawCircle(worldPosition + gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
|
||||
args.WorldHandle.DrawCircle(worldPosition - gridRotation.RotateVec(new Vector2(multi * GuideOffset, GuideOffset)), GuideRadius, _guideColor);
|
||||
}
|
||||
|
||||
base.Render(args);
|
||||
}
|
||||
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
_mouseCoordsRaw = ScreenToCursorGrid(mouseScreen);
|
||||
MouseCoords = _mouseCoordsRaw.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
|
||||
|
||||
var gridId = _transformSystem.GetGrid(MouseCoords);
|
||||
|
||||
if (!_entityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
|
||||
return;
|
||||
|
||||
CurrentTile = _mapSystem.GetTileRef(gridId.Value, mapGrid, MouseCoords);
|
||||
|
||||
float tileSize = mapGrid.TileSize;
|
||||
GridDistancing = tileSize;
|
||||
|
||||
if (pManager.CurrentPermission!.IsTile)
|
||||
{
|
||||
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2,
|
||||
CurrentTile.Y + tileSize / 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2 + pManager.PlacementOffset.X,
|
||||
CurrentTile.Y + tileSize / 2 + pManager.PlacementOffset.Y));
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity))
|
||||
return;
|
||||
|
||||
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd) || !rcd.IsRpd)
|
||||
return;
|
||||
|
||||
if (!_entityManager.TryGetComponent<TransformComponent>(player.Value, out var playerXform))
|
||||
return;
|
||||
|
||||
if (!_transformSystem.InRange(playerXform.Coordinates, MouseCoords, SharedInteractionSystem.InteractionRange))
|
||||
return;
|
||||
|
||||
var mouseCoordsDiff = _mouseCoordsRaw.Position - MouseCoords.Position;
|
||||
var newLayer = AtmosPipeLayer.Primary; // fallback
|
||||
|
||||
// Get held RCD to check CurrentMode
|
||||
switch (rcd.CurrentMode)
|
||||
{
|
||||
case RpdMode.Primary:
|
||||
newLayer = AtmosPipeLayer.Primary;
|
||||
break;
|
||||
|
||||
case RpdMode.Secondary:
|
||||
newLayer = AtmosPipeLayer.Secondary;
|
||||
break;
|
||||
|
||||
case RpdMode.Tertiary:
|
||||
newLayer = AtmosPipeLayer.Tertiary;
|
||||
break;
|
||||
|
||||
case RpdMode.Free:
|
||||
// Only in Free mode do we use mouse direction
|
||||
if (mouseCoordsDiff.Length() > MouseDeadzoneRadius)
|
||||
{
|
||||
var gridRotation = _transformSystem.GetWorldRotation(gridId.Value);
|
||||
var rawAngle = new Angle(mouseCoordsDiff);
|
||||
var eyeRotation = _eyeManager.CurrentEye.Rotation;
|
||||
var direction = (rawAngle + eyeRotation + gridRotation + Math.PI / 2).GetCardinalDir();
|
||||
newLayer = (direction == Direction.North || direction == Direction.East)
|
||||
? AtmosPipeLayer.Secondary
|
||||
: AtmosPipeLayer.Tertiary;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Update layer if changed
|
||||
if (newLayer != _currentLayer)
|
||||
_currentLayer = newLayer;
|
||||
|
||||
if (rcd.CurrentMode == RpdMode.Free)
|
||||
UpdateEyeRotation(heldEntity.Value, _eyeManager.CurrentEye.Rotation);
|
||||
|
||||
UpdatePlacer(_currentLayer);
|
||||
}
|
||||
|
||||
// Since player eye rotation isn't networked and there is a comment warning against doing so,
|
||||
// we need a way of sending current eye rotation to the rpd for correct layer placement.
|
||||
// I'm sure there's a better solution for this but I haven't found it
|
||||
private void UpdateEyeRotation(EntityUid heldEntity, Angle eyeRotation)
|
||||
{
|
||||
if (_currentEyeRotation != eyeRotation.Theta)
|
||||
{
|
||||
_currentEyeRotation = (float) eyeRotation.Theta;
|
||||
_entityNetwork.SendSystemNetworkMessage(new RPDEyeRotationEvent(_entityManager.GetNetEntity(heldEntity), _currentEyeRotation));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlacer(AtmosPipeLayer layer)
|
||||
{
|
||||
// Try to get alternative prototypes from the entity atmos pipe layer component
|
||||
if (pManager.CurrentPermission?.EntityType == null)
|
||||
return;
|
||||
|
||||
if (!_protoManager.TryIndex<EntityPrototype>(pManager.CurrentPermission.EntityType, out var currentProto))
|
||||
return;
|
||||
|
||||
if (!currentProto.TryGetComponent<AtmosPipeLayersComponent>(out var atmosPipeLayers, _entityManager.ComponentFactory))
|
||||
return;
|
||||
|
||||
if (!_pipeLayersSystem.TryGetAlternativePrototype(atmosPipeLayers, layer, out var newProtoId))
|
||||
return;
|
||||
|
||||
if (_protoManager.TryIndex<EntityPrototype>(newProtoId, out var newProto))
|
||||
{
|
||||
// Update the placed prototype
|
||||
pManager.CurrentPermission.EntityType = newProtoId;
|
||||
|
||||
// Update the appearance of the ghost sprite
|
||||
if (newProto.TryGetComponent<SpriteComponent>(out var sprite, _entityManager.ComponentFactory))
|
||||
{
|
||||
var textures = new List<IDirectionalTextureProvider>();
|
||||
|
||||
foreach (var spriteLayer in sprite.AllLayers)
|
||||
{
|
||||
if (spriteLayer.ActualRsi?.Path != null && spriteLayer.RsiState.Name != null)
|
||||
textures.Add(_spriteSystem.RsiStateLike(new SpriteSpecifier.Rsi(spriteLayer.ActualRsi.Path, spriteLayer.RsiState.Name)));
|
||||
}
|
||||
|
||||
pManager.CurrentTextures = textures;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsValidPosition(EntityCoordinates position)
|
||||
{
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
|
||||
// If the destination is out of interaction range, set the placer alpha to zero
|
||||
if (!_entityManager.TryGetComponent<TransformComponent>(player, out var xform))
|
||||
return false;
|
||||
|
||||
if (!_transformSystem.InRange(xform.Coordinates, position, SharedInteractionSystem.InteractionRange))
|
||||
{
|
||||
InvalidPlaceColor = InvalidPlaceColor.WithAlpha(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise restore the alpha value
|
||||
else
|
||||
{
|
||||
InvalidPlaceColor = InvalidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
|
||||
}
|
||||
|
||||
// Determine if player is carrying an RCD in their active hand
|
||||
if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity))
|
||||
return false;
|
||||
|
||||
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
|
||||
return false;
|
||||
|
||||
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;
|
||||
|
||||
if (currentState is not GameplayStateBase screen)
|
||||
return false;
|
||||
|
||||
var target = screen.GetClickedEntity(_transformSystem.ToMapCoordinates(_mouseCoordsRaw));
|
||||
|
||||
// Determine if the RCD operation is valid or not
|
||||
if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, gridUid.Value, mapGrid, tile, posVector, target, player.Value, false))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using Content.Client.Items;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.RCD.Systems;
|
||||
|
||||
public sealed class RPDSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Subs.ItemStatus<RCDComponent>(OnItemStatus);
|
||||
}
|
||||
|
||||
private Control OnItemStatus(Entity<RCDComponent> entity)
|
||||
{
|
||||
return new RPDModeStatusControl(entity);
|
||||
}
|
||||
|
||||
private sealed class RPDModeStatusControl : Control
|
||||
{
|
||||
private readonly RichTextLabel _label = new()
|
||||
{
|
||||
StyleClasses = { "ItemStatus" }
|
||||
};
|
||||
|
||||
private readonly EntityUid _uid;
|
||||
private readonly bool _isRpd;
|
||||
private readonly RCDSystem _rcdSystem;
|
||||
|
||||
public RPDModeStatusControl(Entity<RCDComponent> entity)
|
||||
{
|
||||
_uid = entity.Owner;
|
||||
_isRpd = entity.Comp.IsRpd;
|
||||
_rcdSystem = EntitySystem.Get<RCDSystem>();
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (!_isRpd) return;
|
||||
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var currentMode = _rcdSystem.GetCurrentRpdMode(_uid);
|
||||
|
||||
var modeKey = $"rcd-rpd-mode-{currentMode.ToString().ToLowerInvariant()}";
|
||||
var modeName = Robust.Shared.Localization.Loc.GetString(modeKey);
|
||||
|
||||
_label.SetMarkup(Robust.Shared.Localization.Loc.GetString("rcd-item-status-mode",
|
||||
("mode", $"[color=cyan]{modeName}[/color]")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ namespace Content.Server.NodeContainer.Nodes
|
|||
/// </summary>
|
||||
[DataDefinition]
|
||||
[Virtual]
|
||||
public partial class PipeNode : Node, IGasMixtureHolder, IRotatableNode
|
||||
public partial class PipeNode : Node, IGasMixtureHolder, IRotatableNode, IPipeNode //DeltaV - RPD
|
||||
{
|
||||
/// <summary>
|
||||
/// The directions in which this pipe can connect to other pipes around it.
|
||||
|
|
@ -236,5 +236,8 @@ namespace Content.Server.NodeContainer.Nodes
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
PipeDirection IPipeNode.Direction => OriginalPipeDirection; //DeltaV - RPD
|
||||
AtmosPipeLayer IPipeNode.Layer => CurrentPipeLayer; //DeltaV - RPD
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
|
||||
namespace Content.Server.Atmos.Components;
|
||||
namespace Content.Shared.Atmos.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for restricting anchoring pipes so that they do not overlap.
|
||||
|
|
@ -1,30 +1,33 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Construction.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
namespace Content.Shared.Atmos.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles restricting pipe-based entities from overlapping outlets/inlets with other entities.
|
||||
/// </summary>
|
||||
public sealed class PipeRestrictOverlapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly TransformSystem _xform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
|
||||
private readonly List<EntityUid> _anchoredEntities = new();
|
||||
private EntityQuery<NodeContainerComponent> _nodeContainerQuery;
|
||||
|
||||
public readonly record struct ProposedPipe(
|
||||
|
||||
PipeDirection Direction,
|
||||
|
||||
AtmosPipeLayer Layer,
|
||||
|
||||
Angle Rotation = default);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
|
|
@ -116,10 +119,62 @@ public sealed class PipeRestrictOverlapSystem : EntitySystem
|
|||
{
|
||||
foreach (var node in pipe.Comp1.Nodes.Values)
|
||||
{
|
||||
// we need to rotate the pipe manually like this because the rotation doesn't update for pipes that are unanchored.
|
||||
if (node is PipeNode pipeNode)
|
||||
yield return (pipeNode.OriginalPipeDirection.RotatePipeDirection(pipe.Comp2.LocalRotation), pipeNode.CurrentPipeLayer);
|
||||
if (node is IPipeNode pipeNode)
|
||||
yield return (pipeNode.Direction.RotatePipeDirection(pipe.Comp2.LocalRotation), pipeNode.Layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if placing a new pipe with the given direction and layer on the specified tile would conflict
|
||||
/// with any existing anchored pipe on the same tile, same layer and overlapping direction.
|
||||
/// Returns the EntityUid of the first conflicting pipe found, or null if no conflict.
|
||||
/// </summary>
|
||||
public EntityUid? CheckIfWouldConflict(EntityUid gridUid,
|
||||
Vector2i tileIndices,
|
||||
ProposedPipe proposed,
|
||||
EntityUid? ignoreEntity = null)
|
||||
{
|
||||
if (!TryComp<MapGridComponent>(gridUid, out var gridComp))
|
||||
return null;
|
||||
|
||||
// Pre-calculate the absolute direction of the proposed pipe
|
||||
var proposedDirAbs = proposed.Direction.RotatePipeDirection(proposed.Rotation);
|
||||
|
||||
_anchoredEntities.Clear();
|
||||
_map.GetAnchoredEntities((gridUid, gridComp), tileIndices, _anchoredEntities);
|
||||
|
||||
foreach (var otherEnt in _anchoredEntities)
|
||||
{
|
||||
if (otherEnt == ignoreEntity)
|
||||
continue;
|
||||
|
||||
if (!_nodeContainerQuery.TryComp(otherEnt, out var otherNodeComp))
|
||||
continue;
|
||||
|
||||
var otherXform = Transform(otherEnt);
|
||||
|
||||
// Compare against the existing pipe's actual rotated nodes
|
||||
foreach (var (existingDir, existingLayer) in GetPipeNodeData((otherEnt, otherNodeComp, otherXform)))
|
||||
{
|
||||
// Conflict occurs if they share a layer AND any directional bit
|
||||
if (proposed.Layer == existingLayer && (proposedDirAbs & existingDir) != 0)
|
||||
return otherEnt;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IEnumerable<(PipeDirection RotatedDirection, AtmosPipeLayer Layer)> GetPipeNodeData(
|
||||
Entity<NodeContainerComponent, TransformComponent> pipe)
|
||||
{
|
||||
var rotation = pipe.Comp2.LocalRotation;
|
||||
|
||||
foreach (var node in pipe.Comp1.Nodes.Values)
|
||||
{
|
||||
if (node is IPipeNode pipeNode)
|
||||
yield return (pipeNode.Direction.RotatePipeDirection(rotation), pipeNode.Layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.FixedPoint; //DeltaV - RPD
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Content.Shared.FixedPoint; //DeltaV - RPD
|
||||
|
||||
namespace Content.Shared.RCD.Components;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Robust.Shared.Audio;
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization; //DeltaV - RPD
|
||||
|
||||
namespace Content.Shared.RCD.Components;
|
||||
|
||||
|
|
@ -33,6 +34,29 @@ public sealed partial class RCDComponent : Component
|
|||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<RCDPrototype> ProtoId { get; set; } = "Invalid";
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
/// <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>
|
||||
/// Indicates if a mirrored version of the construction prototype should be used (if available)
|
||||
/// </summary>
|
||||
[AutoNetworkedField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool UseMirrorPrototype = false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this is an RCD or an RPD
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsRpd { get; set; } = false;
|
||||
//DeltaV - RPD End
|
||||
|
||||
/// <summary>
|
||||
/// The direction constructed entities will face upon spawning
|
||||
/// </summary>
|
||||
|
|
@ -57,4 +81,32 @@ public sealed partial class RCDComponent : Component
|
|||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Transform ConstructionTransform { get; private set; }
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
/// <summary>
|
||||
/// Stores player rotation
|
||||
/// This is a workaround to the fact eye rotation is not currently networked and required for pipe layering
|
||||
/// Sent only when needed
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public float? LastKnownEyeRotation { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Current pipe layer / build mode for RPD
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public RpdMode CurrentMode { get; set; } = RpdMode.Free;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier SoundSwitchMode { get; set; } = new SoundPathSpecifier("/Audio/Machines/quickbeep.ogg");
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum RpdMode : byte
|
||||
{
|
||||
Primary = 0,
|
||||
Secondary = 1,
|
||||
Tertiary = 2,
|
||||
Free = 3,
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.FixedPoint; //DeltaV - RPD
|
||||
|
||||
namespace Content.Shared.RCD.Components;
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ public sealed partial class RCDDeconstructableComponent : Component
|
|||
public int Cost = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the deconstruction
|
||||
/// The length of the deconstruction
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Delay = 1f;
|
||||
|
|
@ -31,4 +32,12 @@ public sealed partial class RCDDeconstructableComponent : Component
|
|||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Deconstructable = true;
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
/// <summary>
|
||||
/// Toggles whether this entity is deconstructable by the RPD or not
|
||||
/// </summary>
|
||||
[DataField("rpd"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool RpdDeconstructable = false;
|
||||
//DeltaV - RPD End
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,32 @@ public sealed class RCDConstructionGhostRotationEvent(NetEntity netEntity, Direc
|
|||
public readonly NetEntity NetEntity = netEntity;
|
||||
public readonly Direction Direction = direction;
|
||||
}
|
||||
//DeltaV - RPD Begin
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RCDConstructionGhostFlipEvent : EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity NetEntity;
|
||||
public readonly bool UseMirrorPrototype;
|
||||
public RCDConstructionGhostFlipEvent(NetEntity netEntity, bool useMirrorPrototype)
|
||||
{
|
||||
NetEntity = netEntity;
|
||||
UseMirrorPrototype = useMirrorPrototype;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RPDEyeRotationEvent : EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity NetEntity;
|
||||
public float? EyeRotation;
|
||||
|
||||
public RPDEyeRotationEvent(NetEntity netEntity, float? eyeRotation)
|
||||
{
|
||||
NetEntity = netEntity;
|
||||
EyeRotation = eyeRotation;
|
||||
}
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
[Serializable, NetSerializable]
|
||||
public enum RcdUiKey : byte
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Content.Shared.Physics;
|
|||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.FixedPoint; //DeltaV - RPD
|
||||
|
||||
namespace Content.Shared.RCD;
|
||||
|
||||
|
|
@ -50,6 +51,14 @@ public sealed partial class RCDPrototype : IPrototype
|
|||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool AllowMultiDirection { get; private set; }
|
||||
|
||||
// DeltaV - RPD
|
||||
/// <summary>
|
||||
/// If the entity can be flipped, this prototype is available as an alternate (mode dependent)
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public string? MirrorPrototype { get; private set; } = string.Empty;
|
||||
// End DeltaV - RPD
|
||||
|
||||
/// <summary>
|
||||
/// Number of charges consumed when the operation is completed
|
||||
/// </summary>
|
||||
|
|
@ -119,6 +128,14 @@ public sealed partial class RCDPrototype : IPrototype
|
|||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public RcdRotation Rotation { get; private set; } = RcdRotation.User;
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
/// <summary>
|
||||
/// Determines whether this prototype uses layered placement (true for traditional placement, false for layered). Only applies to RPD.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool HasLayers { get; private set; } = false;
|
||||
//DeltaV - RPD End
|
||||
}
|
||||
|
||||
public enum RcdMode : byte
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.FixedPoint; //DeltaV - RPD
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.RCD.Components;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@ using Robust.Shared.Physics.Dynamics;
|
|||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using System.Linq;
|
||||
using Content.Shared.Atmos.EntitySystems; //DeltaV - RPD
|
||||
using Content.Shared.Atmos.Components; //DeltaV - RPD
|
||||
using Content.Shared.Hands.Components; //DeltaV - RPD
|
||||
using System.Numerics; //DeltaV - RPD
|
||||
using Content.Shared.Verbs; //DeltaV - RPD
|
||||
using Robust.Shared.Utility; //DeltaV - RPD
|
||||
using Content.Shared.NodeContainer; //DeltaV - RPD
|
||||
using Content.Shared.Atmos; //DeltaV - RPD
|
||||
|
||||
namespace Content.Shared.RCD.Systems;
|
||||
|
||||
|
|
@ -44,6 +52,9 @@ public sealed class RCDSystem : EntitySystem
|
|||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
[Dependency] private readonly SharedAtmosPipeLayersSystem _pipeLayersSystem = default!; //DeltaV - RPD
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!; //DeltaV - RPD
|
||||
[Dependency] private readonly PipeRestrictOverlapSystem _pipeOverlap = default!; //DeltaV - RPD
|
||||
|
||||
private readonly int _instantConstructionDelay = 0;
|
||||
private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0";
|
||||
|
|
@ -52,28 +63,37 @@ public sealed class RCDSystem : EntitySystem
|
|||
private static readonly ProtoId<TagPrototype> CatwalkTag = "Catwalk";
|
||||
|
||||
private HashSet<EntityUid> _intersectingEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RCDComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<RCDComponent, ComponentStartup>(OnStartup); //DeltaV - RPD
|
||||
SubscribeLocalEvent<RCDComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<RCDComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<RCDComponent, RCDDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<RCDComponent, DoAfterAttemptEvent<RCDDoAfterEvent>>(OnDoAfterAttempt);
|
||||
SubscribeLocalEvent<RCDComponent, RCDSystemMessage>(OnRCDSystemMessage);
|
||||
SubscribeNetworkEvent<RCDConstructionGhostRotationEvent>(OnRCDconstructionGhostRotationEvent);
|
||||
SubscribeNetworkEvent<RCDConstructionGhostFlipEvent>(OnRCDConstructionGhostFlipEvent); //DeltaV - RPD
|
||||
SubscribeNetworkEvent<RPDEyeRotationEvent>(OnRPDEyeRotationEvent); //DeltaV - RPD
|
||||
SubscribeLocalEvent<RCDComponent, GetVerbsEvent<UtilityVerb>>(OnGetUtilityVerb); //DeltaV - RPD
|
||||
SubscribeLocalEvent<RCDComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerb); //DeltaV - RPD
|
||||
}
|
||||
|
||||
#region Event handling
|
||||
|
||||
private void OnMapInit(EntityUid uid, RCDComponent component, MapInitEvent args)
|
||||
{
|
||||
// On init, set the RCD to its first available recipe
|
||||
// On map startup, set the RCD to its first available recipe
|
||||
if (component.AvailablePrototypes.Count > 0)
|
||||
{
|
||||
component.ProtoId = component.AvailablePrototypes.ElementAt(0);
|
||||
//DeltaV - RPD Begin
|
||||
if (component.IsRpd)
|
||||
component.ProtoId = "PipeStraight";
|
||||
else
|
||||
component.ProtoId = component.AvailablePrototypes.ElementAt(0);
|
||||
//DeltaV - RPD End
|
||||
Dirty(uid, component);
|
||||
|
||||
return;
|
||||
|
|
@ -83,6 +103,16 @@ public sealed class RCDSystem : EntitySystem
|
|||
QueueDel(uid);
|
||||
}
|
||||
|
||||
//DeltaV - RPD Begin
|
||||
private void OnStartup(EntityUid uid, RCDComponent component, ComponentStartup args)
|
||||
{
|
||||
UpdateCachedPrototype(uid, component);
|
||||
Dirty(uid, component);
|
||||
|
||||
return;
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
|
||||
private void OnRCDSystemMessage(EntityUid uid, RCDComponent component, RCDSystemMessage args)
|
||||
{
|
||||
// Exit if the RCD doesn't actually know the supplied prototype
|
||||
|
|
@ -94,6 +124,7 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
// Set the current RCD prototype to the one supplied
|
||||
component.ProtoId = args.ProtoId;
|
||||
UpdateCachedPrototype(uid, component); //DeltaV - RPD
|
||||
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.Low, $"{args.Actor} set RCD mode to: {prototype.Mode} : {prototype.Prototype}");
|
||||
|
||||
|
|
@ -105,7 +136,8 @@ public sealed class RCDSystem : EntitySystem
|
|||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
UpdateCachedPrototype(uid, component); //DeltaV - RPD
|
||||
var prototype = component.CachedPrototype; //DeltaV - RPD
|
||||
|
||||
var msg = Loc.GetString("rcd-component-examine-mode-details", ("mode", Loc.GetString(prototype.SetName)));
|
||||
|
||||
|
|
@ -121,8 +153,70 @@ public sealed class RCDSystem : EntitySystem
|
|||
}
|
||||
|
||||
args.PushMarkup(msg);
|
||||
//DeltaV - RPD Begin
|
||||
if (component.IsRpd)
|
||||
{
|
||||
var modeLoc = $"rcd-rpd-mode-{component.CurrentMode.ToString().ToLowerInvariant()}";
|
||||
args.PushMarkup(Loc.GetString("rcd-component-examine-rpd-mode", ("mode", Loc.GetString(modeLoc))));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRPDEyeRotationEvent(RPDEyeRotationEvent ev, EntitySessionEventArgs session)
|
||||
{
|
||||
var uid = GetEntity(ev.NetEntity);
|
||||
|
||||
if (session.SenderSession.AttachedEntity is not { } player)
|
||||
return;
|
||||
|
||||
if (_hands.GetActiveItem(player) != uid)
|
||||
return;
|
||||
|
||||
if (!TryComp<RCDComponent>(uid, out var rcd))
|
||||
return;
|
||||
|
||||
// Update the layer if different
|
||||
if (rcd.LastKnownEyeRotation != ev.EyeRotation)
|
||||
{
|
||||
rcd.LastKnownEyeRotation = ev.EyeRotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetUtilityVerb(EntityUid uid, RCDComponent component, GetVerbsEvent<UtilityVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !component.IsRpd)
|
||||
return;
|
||||
|
||||
var verb = new UtilityVerb
|
||||
{
|
||||
Act = () => SwitchPipeMode(uid, component, args.User),
|
||||
Text = Loc.GetString("rcd-verb-switch-mode"),
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
Impact = LogImpact.Low
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnGetAlternativeVerb(EntityUid uid, RCDComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !component.IsRpd || !args.Using.HasValue)
|
||||
return;
|
||||
|
||||
// Only show when alt-clicking the RPD itself (args.Using is the held item)
|
||||
if (args.Using.Value != uid)
|
||||
return;
|
||||
|
||||
var verb = new AlternativeVerb
|
||||
{
|
||||
Act = () => SwitchPipeMode(uid, component, args.User),
|
||||
Text = Loc.GetString("rcd-verb-switch-mode"),
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
Impact = LogImpact.Low
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
private void OnAfterInteract(EntityUid uid, RCDComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach)
|
||||
|
|
@ -130,7 +224,8 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
var user = args.User;
|
||||
var location = args.ClickLocation;
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
|
||||
var prototype = component.CachedPrototype; //DeltaV - RPD
|
||||
|
||||
// Initial validity checks
|
||||
if (!location.IsValid(EntityManager))
|
||||
|
|
@ -145,6 +240,46 @@ public sealed class RCDSystem : EntitySystem
|
|||
}
|
||||
var tile = _mapSystem.GetTileRef(gridUid.Value, mapGrid, location);
|
||||
var position = _mapSystem.TileIndicesFor(gridUid.Value, mapGrid, location);
|
||||
//DeltaV - RPD Begin
|
||||
var layer = AtmosPipeLayer.Primary;
|
||||
if (component.IsRpd && prototype.HasLayers)
|
||||
{
|
||||
var tileSize = mapGrid.TileSize;
|
||||
var tileCenter = new Vector2(tile.X + tileSize / 2, tile.Y + tileSize / 2);
|
||||
var mouseCoordsDiff = args.ClickLocation.Position - tileCenter - new Vector2(0.5f, 0.5f);
|
||||
var mouseDeadzoneRadius = 0.25f;
|
||||
|
||||
switch (component.CurrentMode)
|
||||
{
|
||||
case RpdMode.Primary:
|
||||
layer = AtmosPipeLayer.Primary;
|
||||
break;
|
||||
|
||||
case RpdMode.Secondary:
|
||||
layer = AtmosPipeLayer.Secondary;
|
||||
break;
|
||||
|
||||
case RpdMode.Tertiary:
|
||||
layer = AtmosPipeLayer.Tertiary;
|
||||
break;
|
||||
|
||||
case RpdMode.Free:
|
||||
// Only use mouse direction in Free mode
|
||||
if (mouseCoordsDiff.Length() > mouseDeadzoneRadius && component.LastKnownEyeRotation.HasValue)
|
||||
{
|
||||
var gridRotation = _transform.GetWorldRotation(gridUid.Value);
|
||||
var angle = new Angle(mouseCoordsDiff);
|
||||
var eyeRotation = new Angle(component.LastKnownEyeRotation.Value);
|
||||
var direction = (angle + eyeRotation + gridRotation + Math.PI / 2).GetCardinalDir();
|
||||
|
||||
layer = (direction == Direction.North || direction == Direction.East)
|
||||
? AtmosPipeLayer.Secondary
|
||||
: AtmosPipeLayer.Tertiary;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
|
||||
if (!IsRCDOperationStillValid(uid, component, gridUid.Value, mapGrid, tile, position, component.ConstructionDirection, args.Target, args.User))
|
||||
return;
|
||||
|
|
@ -209,7 +344,7 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
// Try to start the do after
|
||||
var effect = Spawn(effectPrototype, location);
|
||||
var ev = new RCDDoAfterEvent(GetNetCoordinates(location), component.ConstructionDirection, component.ProtoId, cost, GetNetEntity(effect));
|
||||
var ev = new RCDDoAfterEvent(GetNetCoordinates(location), component.ConstructionDirection, component.ProtoId, cost, layer, GetNetEntity(effect)); //DeltaV - RPD
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, user, delay, ev, uid, target: args.Target, used: uid)
|
||||
{
|
||||
|
|
@ -288,7 +423,7 @@ public sealed class RCDSystem : EntitySystem
|
|||
return;
|
||||
|
||||
// Finalize the operation (this should handle prediction properly)
|
||||
FinalizeRCDOperation(uid, component, gridUid.Value, mapGrid, tile, position, args.Direction, args.Target, args.User);
|
||||
FinalizeRCDOperation(uid, component, gridUid.Value, mapGrid, tile, position, args.Direction, args.Target, args.User, args.Layer);
|
||||
|
||||
// Play audio and consume charges
|
||||
_audio.PlayPredicted(component.SuccessSound, uid, args.User);
|
||||
|
|
@ -313,7 +448,45 @@ public sealed class RCDSystem : EntitySystem
|
|||
rcd.ConstructionDirection = ev.Direction;
|
||||
Dirty(uid, rcd);
|
||||
}
|
||||
//DeltaV - RPD Begin
|
||||
private void OnRCDConstructionGhostFlipEvent(RCDConstructionGhostFlipEvent ev, EntitySessionEventArgs session)
|
||||
{
|
||||
var uid = GetEntity(ev.NetEntity);
|
||||
|
||||
if (session.SenderSession.AttachedEntity is not { } player)
|
||||
return;
|
||||
|
||||
if (_hands.GetActiveItem(player) != uid)
|
||||
return;
|
||||
|
||||
if (!TryComp<RCDComponent>(uid, out var rcd))
|
||||
return;
|
||||
|
||||
rcd.UseMirrorPrototype = ev.UseMirrorPrototype;
|
||||
Dirty(uid, rcd);
|
||||
}
|
||||
|
||||
private void SwitchPipeMode(EntityUid uid, RCDComponent component, EntityUid? user = null)
|
||||
{
|
||||
if (!component.IsRpd)
|
||||
return;
|
||||
|
||||
// Cycle through modes
|
||||
component.CurrentMode = component.CurrentMode switch
|
||||
{
|
||||
RpdMode.Primary => RpdMode.Secondary,
|
||||
RpdMode.Secondary => RpdMode.Tertiary,
|
||||
RpdMode.Tertiary => RpdMode.Free,
|
||||
RpdMode.Free => RpdMode.Primary,
|
||||
_ => RpdMode.Free
|
||||
};
|
||||
|
||||
Dirty(uid, component);
|
||||
|
||||
if (user != null)
|
||||
_audio.PlayPredicted(component.SoundSwitchMode, uid, user.Value);
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
#endregion
|
||||
|
||||
#region Entity construction/deconstruction rule checks
|
||||
|
|
@ -325,7 +498,10 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid? target, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
// Update cached prototype if required
|
||||
UpdateCachedPrototype(uid, component); //DeltaV - RPD
|
||||
|
||||
var prototype = component.CachedPrototype; //DeltaV - RPD
|
||||
|
||||
// Check that the RCD has enough ammo to get the job done
|
||||
var charges = _sharedCharges.GetCurrentCharges(uid);
|
||||
|
|
@ -347,30 +523,86 @@ public sealed class RCDSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
// Exit if the target / target location is obstructed
|
||||
var unobstructed = (target == null)
|
||||
? _interaction.InRangeUnobstructed(user, _mapSystem.GridTileToWorld(gridUid, mapGrid, position), popup: popMsgs)
|
||||
: _interaction.InRangeUnobstructed(user, target.Value, popup: popMsgs);
|
||||
// DeltaV - RPD Begin
|
||||
bool unobstructed;
|
||||
var worldPos = _mapSystem.GridTileToWorld(gridUid, mapGrid, position);
|
||||
|
||||
if (component.IsRpd)
|
||||
{
|
||||
unobstructed = _interaction.InRangeUnobstructed(user, worldPos,
|
||||
predicate: (entity) => !HasComp<PipeRestrictOverlapComponent>(entity),
|
||||
popup: popMsgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
unobstructed = (target == null)
|
||||
? _interaction.InRangeUnobstructed(user, worldPos, popup: popMsgs)
|
||||
: _interaction.InRangeUnobstructed(user, target.Value, popup: popMsgs);
|
||||
}
|
||||
// DeltaV - RPD End
|
||||
|
||||
if (!unobstructed)
|
||||
return false;
|
||||
|
||||
// DeltaV - RPD Begin
|
||||
var layer = AtmosPipeLayer.Primary;
|
||||
if (component.IsRpd)
|
||||
{
|
||||
layer = GetLayerFromState(component, gridUid, mapGrid, tile, worldPos.Position);
|
||||
}
|
||||
// DeltaV - RPD End
|
||||
|
||||
// Return whether the operation location is valid
|
||||
switch (prototype.Mode)
|
||||
{
|
||||
case RcdMode.ConstructTile:
|
||||
case RcdMode.ConstructObject:
|
||||
return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, direction, user, popMsgs);
|
||||
return IsConstructionLocationValid(uid, component, gridUid, mapGrid, tile, position, direction, user, layer, popMsgs); //DeltaV - RPD
|
||||
case RcdMode.Deconstruct:
|
||||
return IsDeconstructionStillValid(uid, tile, target, user, popMsgs);
|
||||
return IsDeconstructionStillValid(uid, component, tile, target, user, popMsgs); //DeltaV - RPD
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid user, bool popMsgs = true)
|
||||
// DeltaV - RPD Begin
|
||||
private AtmosPipeLayer GetLayerFromState(RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2 clickPos)
|
||||
{
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
if (!component.IsRpd || component.CachedPrototype == null || !component.CachedPrototype.HasLayers)
|
||||
return AtmosPipeLayer.Primary;
|
||||
|
||||
var tileSize = mapGrid.TileSize;
|
||||
var tileCenter = new Vector2(tile.X + tileSize / 2f, tile.Y + tileSize / 2f);
|
||||
var mouseCoordsDiff = clickPos - tileCenter;
|
||||
var mouseDeadzoneRadius = 0.25f;
|
||||
|
||||
switch (component.CurrentMode)
|
||||
{
|
||||
case RpdMode.Primary: return AtmosPipeLayer.Primary;
|
||||
case RpdMode.Secondary: return AtmosPipeLayer.Secondary;
|
||||
case RpdMode.Tertiary: return AtmosPipeLayer.Tertiary;
|
||||
case RpdMode.Free:
|
||||
if (mouseCoordsDiff.Length() > mouseDeadzoneRadius && component.LastKnownEyeRotation.HasValue)
|
||||
{
|
||||
var gridRotation = _transform.GetWorldRotation(gridUid);
|
||||
var angle = new Angle(mouseCoordsDiff);
|
||||
var eyeRotation = new Angle(component.LastKnownEyeRotation.Value);
|
||||
var direction = (angle + eyeRotation + gridRotation + Math.PI / 2).GetCardinalDir();
|
||||
return (direction == Direction.North || direction == Direction.East) ? AtmosPipeLayer.Secondary : AtmosPipeLayer.Tertiary;
|
||||
}
|
||||
return AtmosPipeLayer.Primary;
|
||||
default: return AtmosPipeLayer.Primary;
|
||||
}
|
||||
}
|
||||
|
||||
// DeltaV - RPD End
|
||||
|
||||
private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, Direction direction, EntityUid user, AtmosPipeLayer layer, bool popMsgs = true)
|
||||
{
|
||||
// Update cached prototype if required
|
||||
UpdateCachedPrototype(uid, component); //DeltaV - RPD
|
||||
|
||||
var prototype = component.CachedPrototype; //DeltaV - RPD
|
||||
|
||||
// Check rule: Must build on empty tile
|
||||
if (prototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnEmptyTile) && !tile.Tile.IsEmpty)
|
||||
|
|
@ -455,9 +687,32 @@ public sealed class RCDSystem : EntitySystem
|
|||
// This is to prevent spamming objects on the same tile (e.g. lights)
|
||||
if (prototype.Prototype != null && MetaData(ent).EntityPrototype?.ID == prototype.Prototype)
|
||||
{
|
||||
// DeltaV - RPD Begin
|
||||
var isIdentical = true;
|
||||
|
||||
if (prototype.AllowMultiDirection)
|
||||
if (component.IsRpd)
|
||||
{
|
||||
// A pipe is only "identical" if it exists on the same layer we are trying to build on.
|
||||
// We check the nodes of the existing entity to find its layer.
|
||||
if (TryComp<NodeContainerComponent>(ent, out var nodeContainer))
|
||||
{
|
||||
var existingLayerMatch = false;
|
||||
foreach (var node in nodeContainer.Nodes.Values)
|
||||
{
|
||||
if (node is IPipeNode pipeNode && pipeNode.Layer == layer)
|
||||
{
|
||||
existingLayerMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no nodes match the target layer, it's not identical (we can build here!)
|
||||
if (!existingLayerMatch)
|
||||
isIdentical = false;
|
||||
}
|
||||
}
|
||||
|
||||
else if (prototype.AllowMultiDirection)
|
||||
{
|
||||
var entDirection = Transform(ent).LocalRotation.GetCardinalDir();
|
||||
if (entDirection != direction)
|
||||
|
|
@ -466,11 +721,10 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
if (isIdentical)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-entity"), uid, user);
|
||||
|
||||
if (popMsgs) _popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-entity"), uid, user);
|
||||
return false;
|
||||
}
|
||||
// DeltaV - RPD End
|
||||
}
|
||||
|
||||
if (isWindow && HasComp<SharedCanBuildWindowOnTopComponent>(ent))
|
||||
|
|
@ -484,12 +738,19 @@ public sealed class RCDSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
// DeltaV - RPD Begin
|
||||
if (component.IsRpd && HasComp<PipeRestrictOverlapComponent>(ent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// DeltaV - RPD End
|
||||
|
||||
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) prototype.CollisionMask) == 0)
|
||||
if (!fixture.Hard || fixture.CollisionLayer <= 0 || (fixture.CollisionLayer & (int) prototype.CollisionMask) == 0)
|
||||
continue;
|
||||
|
||||
// Continue if our custom collision bounds are not intersected
|
||||
|
|
@ -509,11 +770,20 @@ public sealed class RCDSystem : EntitySystem
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool IsDeconstructionStillValid(EntityUid uid, TileRef tile, EntityUid? target, EntityUid user, bool popMsgs = true)
|
||||
private bool IsDeconstructionStillValid(EntityUid uid, RCDComponent component, TileRef tile, EntityUid? target, EntityUid user, bool popMsgs = true) //DeltaV - RPD
|
||||
{
|
||||
// Attempt to deconstruct a floor tile
|
||||
if (target == null)
|
||||
{
|
||||
//DeltaV - RPD Begin
|
||||
if (component.IsRpd)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
// The tile is empty
|
||||
if (tile.Tile.IsEmpty)
|
||||
{
|
||||
|
|
@ -547,14 +817,26 @@ public sealed class RCDSystem : EntitySystem
|
|||
// Attempt to deconstruct an object
|
||||
else
|
||||
{
|
||||
// The object is not in the whitelist
|
||||
if (!TryComp<RCDDeconstructableComponent>(target, out var deconstructible) || !deconstructible.Deconstructable)
|
||||
//DeltaV - RPD Begin
|
||||
// The object is not in the RPD whitelist
|
||||
if (!TryComp<RCDDeconstructableComponent>(target, out var deconstructible) || !deconstructible.RpdDeconstructable && component.IsRpd)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
|
||||
// The object is not in the whitelist
|
||||
if (!deconstructible.Deconstructable) //DeltaV - RPD
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -564,12 +846,12 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
#region Entity construction/deconstruction
|
||||
|
||||
private void FinalizeRCDOperation(EntityUid uid, RCDComponent component, EntityUid gridUid, MapGridComponent mapGrid, TileRef tile, Vector2i position, 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, AtmosPipeLayer layer) // DeltaV - RPD
|
||||
{
|
||||
if (!_net.IsServer)
|
||||
return;
|
||||
|
||||
var prototype = _protoManager.Index(component.ProtoId);
|
||||
var prototype = component.CachedPrototype; //DeltaV - RPD
|
||||
|
||||
if (prototype.Prototype == null)
|
||||
return;
|
||||
|
|
@ -585,8 +867,67 @@ public sealed class RCDSystem : EntitySystem
|
|||
break;
|
||||
|
||||
case RcdMode.ConstructObject:
|
||||
var ent = Spawn(prototype.Prototype, _mapSystem.GridTileToLocal(gridUid, mapGrid, position));
|
||||
//DeltaV - RPD Begin
|
||||
var proto = (component.UseMirrorPrototype && !string.IsNullOrEmpty(prototype.MirrorPrototype))
|
||||
? prototype.MirrorPrototype
|
||||
: prototype.Prototype;
|
||||
|
||||
if (component.IsRpd && prototype.HasLayers)
|
||||
{
|
||||
if (_protoManager.TryIndex<EntityPrototype>(proto, out var entityProto) &&
|
||||
entityProto.TryGetComponent<AtmosPipeLayersComponent>(out var atmosPipeLayers, _entityManager.ComponentFactory) &&
|
||||
_pipeLayersSystem.TryGetAlternativePrototype(atmosPipeLayers, layer, out var newProtoId))
|
||||
{
|
||||
proto = newProtoId;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate rotation before spawn
|
||||
var rotation = prototype.Rotation switch
|
||||
{
|
||||
RcdRotation.Fixed => Angle.Zero,
|
||||
RcdRotation.Camera => Transform(uid).LocalRotation,
|
||||
RcdRotation.User => direction.ToAngle(),
|
||||
_ => Angle.Zero // Fallback
|
||||
};
|
||||
|
||||
// For RPD's, if overlapping existing pipe, replace the pipe
|
||||
if (component.IsRpd)
|
||||
{
|
||||
// We need to know what the pipe *would* look like to check for overlaps
|
||||
if (_protoManager.TryIndex<EntityPrototype>(proto, out var pipeProto) &&
|
||||
pipeProto.TryGetComponent<NodeContainerComponent>(out var nodeContainer, _entityManager.ComponentFactory))
|
||||
{
|
||||
// Check every node in the prototype to see if it overlaps something on the grid
|
||||
foreach (var node in nodeContainer.Nodes.Values)
|
||||
{
|
||||
if (node is IPipeNode pipeNode)
|
||||
{
|
||||
var proposed = new PipeRestrictOverlapSystem.ProposedPipe(
|
||||
pipeNode.Direction,
|
||||
layer,
|
||||
rotation
|
||||
);
|
||||
|
||||
// If there is a conflict, delete the old pipe first
|
||||
var conflict = _pipeOverlap.CheckIfWouldConflict(gridUid, position, proposed);
|
||||
if (Exists(conflict) && HasComp<RCDDeconstructableComponent>(conflict))
|
||||
{
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.Medium,
|
||||
$"{ToPrettyString(user):user} RPD replaced {ToPrettyString(conflict.Value)} at {position}");
|
||||
Del(conflict.Value);
|
||||
_audio.PlayPvs(component.SuccessSound, uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var entityCoords = _mapSystem.GridTileToLocal(gridUid, mapGrid, position);
|
||||
var mapCoords = new MapCoordinates(entityCoords.ToMapPos(EntityManager, _transform), entityCoords.GetMapId(EntityManager));
|
||||
|
||||
var ent = Spawn(proto, mapCoords, rotation: rotation);
|
||||
//DeltaV - RPD End
|
||||
switch (prototype.Rotation)
|
||||
{
|
||||
case RcdRotation.Fixed:
|
||||
|
|
@ -633,7 +974,25 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
return boundingPolygon.ComputeAABB(boundingTransform, 0).Intersects(fixture.Shape.ComputeAABB(entXform, 0));
|
||||
}
|
||||
//DeltaV - RPD Begin
|
||||
public void UpdateCachedPrototype(EntityUid uid, RCDComponent component)
|
||||
{
|
||||
if (component.ProtoId.Id != component.CachedPrototype?.Prototype ||
|
||||
(component.CachedPrototype?.MirrorPrototype != null &&
|
||||
component.ProtoId.Id != component.CachedPrototype?.MirrorPrototype))
|
||||
{
|
||||
component.CachedPrototype = _protoManager.Index(component.ProtoId);
|
||||
}
|
||||
}
|
||||
|
||||
public RpdMode GetCurrentRpdMode(EntityUid uid, RCDComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return RpdMode.Free; // default to Free mode
|
||||
|
||||
return component.CurrentMode;
|
||||
}
|
||||
//DeltaV - RPD End
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
|
@ -655,14 +1014,18 @@ public sealed partial class RCDDoAfterEvent : DoAfterEvent
|
|||
[DataField("fx")]
|
||||
public NetEntity? Effect { get; private set; }
|
||||
|
||||
[DataField] //DeltaV - RPD
|
||||
public AtmosPipeLayer Layer { get; private set; } = AtmosPipeLayer.Primary; //DeltaV - RPD
|
||||
|
||||
private RCDDoAfterEvent() { }
|
||||
|
||||
public RCDDoAfterEvent(NetCoordinates location, Direction direction, ProtoId<RCDPrototype> startingProtoId, int cost, NetEntity? effect = null)
|
||||
public RCDDoAfterEvent(NetCoordinates location, Direction direction, ProtoId<RCDPrototype> startingProtoId, int cost, AtmosPipeLayer layer, NetEntity? effect = null) //DeltaV - RPD
|
||||
{
|
||||
Location = location;
|
||||
Direction = direction;
|
||||
StartingProtoId = startingProtoId;
|
||||
Cost = cost;
|
||||
Layer = layer; //DeltaV - RPD
|
||||
Effect = effect;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
|
||||
namespace Content.Shared.Atmos;
|
||||
|
||||
public interface IPipeNode
|
||||
{
|
||||
PipeDirection Direction { get; }
|
||||
AtmosPipeLayer Layer { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# DeltaV - RPD mode change
|
||||
rcd-rpd-mode-primary = Primary
|
||||
rcd-rpd-mode-secondary = Secondary
|
||||
rcd-rpd-mode-tertiary = Tertiary
|
||||
rcd-rpd-mode-free = Free
|
||||
rcd-component-examine-rpd-mode = Current mode: [color=cyan]{$mode}[/color]
|
||||
rcd-verb-switch-mode = Switch mode
|
||||
rcd-item-status-mode = Mode: {$mode}
|
||||
|
||||
rcd-component-piping = Piping
|
||||
rcd-component-pumps-valves = Pumps & Valves
|
||||
rcd-component-vents = Vents
|
||||
rcd-component-atmospheric-utility = Atmospheric Utility
|
||||
rcd-component-sensors-monitors = Sensors & Monitors
|
||||
|
|
@ -160,6 +160,7 @@
|
|||
- id: DoorRemoteFirefight # DeltaV - Re-added fire-fighting remote.
|
||||
- id: RCD
|
||||
- id: RCDAmmo
|
||||
- id: RPD # DeltaV - RPD!
|
||||
- id: LunchboxEngineeringFilledRandom # DeltaV - Lunchboxes!
|
||||
prob: 0.3
|
||||
- id: AirGrenade
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@
|
|||
- id: AirGrenade # DeltaV
|
||||
- id: HolotapeProjector # DeltaV - engineering tape
|
||||
- id: BoxFolderCeClipboard # DeltaV - Power Digi-Board
|
||||
- id: RPD # DeltaV - RPD!
|
||||
|
||||
# Hardsuit table, used for suit storage as well
|
||||
- type: entityTable
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@
|
|||
- CrateEngineeringTeslaGroundingRod
|
||||
- CrateEngineeringParticleAccelerator
|
||||
- CrateRCD
|
||||
- CrateRPD #DeltaV - RPD
|
||||
- CrateEngineeringGear
|
||||
rareChance: 0.2
|
||||
prototypes:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@
|
|||
- type: CollideOnAnchor
|
||||
- type: Anchorable
|
||||
- type: Rotatable
|
||||
- type: RCDDeconstructable #DeltaV - RPD
|
||||
cost: 1 #DeltaV - RPD
|
||||
delay: 2 #DeltaV - RPD
|
||||
fx: EffectRCDDeconstruct2 #DeltaV - RPD
|
||||
rpd: true #DeltaV - RPD
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger: # for nukes
|
||||
|
|
@ -410,6 +415,3 @@
|
|||
navMapBlip: GasPipeManifold
|
||||
showAbsentConnections: false
|
||||
- type: AtmosDevice
|
||||
- type: Tag
|
||||
tags:
|
||||
- Unstackable
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@
|
|||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable #DeltaV - RPD
|
||||
cost: 1 #DeltaV - RPD
|
||||
delay: 2 #DeltaV - RPD
|
||||
fx: EffectRCDDeconstruct2 #DeltaV - RPD
|
||||
rpd: true #DeltaV - RPD
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
|
|
|||
|
|
@ -69,6 +69,11 @@
|
|||
- type: ContainerContainer
|
||||
containers:
|
||||
board: !type:Container
|
||||
- type: RCDDeconstructable #DeltaV - RPD
|
||||
cost: 10 #DeltaV - RPD
|
||||
delay: 2 #DeltaV - RPD
|
||||
fx: EffectRCDDeconstruct2 #DeltaV - RPD
|
||||
rpd: true #DeltaV - RPD
|
||||
- type: Appearance
|
||||
- type: WiresVisuals
|
||||
- type: Sprite
|
||||
|
|
|
|||
|
|
@ -7,3 +7,13 @@
|
|||
cost: 1100
|
||||
category: cargoproduct-category-name-atmospherics
|
||||
group: market
|
||||
|
||||
- type: cargoProduct
|
||||
id: EngineeringRPD
|
||||
icon:
|
||||
sprite: _DV/Objects/Tools/rpd.rsi
|
||||
state: icon
|
||||
product: CrateRPD
|
||||
cost: 800
|
||||
category: cargoproduct-category-name-atmospherics
|
||||
group: market
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
- type: entity
|
||||
parent: CrateEngineeringSecure
|
||||
id: CrateRPD
|
||||
name: RPD crate
|
||||
description: A crate containing a single rapid piping device.
|
||||
components:
|
||||
- type: EntityTableContainerFill
|
||||
containers:
|
||||
entity_storage:
|
||||
id: RPD
|
||||
|
|
@ -10,3 +10,43 @@
|
|||
sprite: _DV/Objects/Tools/RCD_combat.rsi
|
||||
- type: Clothing
|
||||
sprite: _DV/Objects/Tools/RCD_combat.rsi
|
||||
|
||||
- type: entity
|
||||
id: RPD
|
||||
parent: RCD
|
||||
name: RPD
|
||||
description: A device used to "rapidly" pipe things, and an atmosian's best friend.
|
||||
components:
|
||||
- type: RCD
|
||||
isRpd: true
|
||||
availablePrototypes:
|
||||
- PipeFourway
|
||||
- PipeStraight
|
||||
- PipeBend
|
||||
- PipeTJunction
|
||||
- PipeHalf
|
||||
- ManifoldGas
|
||||
- OutletInjector
|
||||
- ManualValve
|
||||
- VolumetricPump
|
||||
- PressurePump
|
||||
- VentScrubber
|
||||
- PressureValve
|
||||
- PressureRegulator
|
||||
- DualPortVent
|
||||
- VentGas
|
||||
- VentPassive
|
||||
- MixerGas
|
||||
- Radiator
|
||||
- RadiatorBend
|
||||
- SignalValve
|
||||
- CanisterPort
|
||||
- FilterGas
|
||||
- Alarm
|
||||
- Sensor
|
||||
- SensorPipe
|
||||
- Deconstruct
|
||||
- type: LimitedCharges
|
||||
maxCharges: 45
|
||||
- type: Sprite
|
||||
sprite: _DV/Objects/Tools/rpd.rsi
|
||||
|
|
|
|||
|
|
@ -0,0 +1,308 @@
|
|||
- type: rcd
|
||||
id: PipeFourway
|
||||
category: Piping
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/fourway.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeFourway
|
||||
cost: 1
|
||||
delay: 0
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PipeStraight
|
||||
category: Piping
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/straight.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeStraight
|
||||
cost: 1
|
||||
delay: 0
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PipeBend
|
||||
category: Piping
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/bend.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeBend
|
||||
cost: 1
|
||||
delay: 0
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PipeTJunction
|
||||
category: Piping
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/tjunction.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeTJunction
|
||||
cost: 1
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PipeHalf
|
||||
category: Piping
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pipe_half.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeHalf
|
||||
cost: 1
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: ManifoldGas
|
||||
category: Piping
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/manifold.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeManifold
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PressurePump
|
||||
category: PumpsValves
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pump_pressure.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPressurePump
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: VolumetricPump
|
||||
category: PumpsValves
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pump_volume.png
|
||||
mode: ConstructObject
|
||||
prototype: GasVolumePump
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: ManualValve
|
||||
category: PumpsValves
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pump_manual_valve.png
|
||||
mode: ConstructObject
|
||||
prototype: GasValve
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: SignalValve
|
||||
category: PumpsValves
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pump_signal_valve.png
|
||||
mode: ConstructObject
|
||||
prototype: SignalControlledValve
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PressureValve
|
||||
category: PumpsValves
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pneumatic_valve.png
|
||||
mode: ConstructObject
|
||||
prototype: PressureControlledValve
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: PressureRegulator
|
||||
category: PumpsValves
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/pump_pressure_regulator.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPressureRegulator
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
hasLayers: true
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
|
||||
- type: rcd
|
||||
id: OutletInjector
|
||||
category: Vents
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/injector.png
|
||||
mode: ConstructObject
|
||||
prototype: GasOutletInjector
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: VentScrubber
|
||||
category: Vents
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/scrub_off.png
|
||||
mode: ConstructObject
|
||||
prototype: GasVentScrubber
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: VentGas
|
||||
category: Vents
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/vent_off.png
|
||||
mode: ConstructObject
|
||||
prototype: GasVentPump
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: DualPortVent
|
||||
category: Vents
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/dual_port.png
|
||||
mode: ConstructObject
|
||||
prototype: GasDualPortVentPump
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: VentPassive
|
||||
category: Vents
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/vent_passive.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPassiveVent
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: Radiator
|
||||
category: AtmosphericUtility
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/radiator.png
|
||||
mode: ConstructObject
|
||||
prototype: HeatExchanger
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: RadiatorBend
|
||||
category: AtmosphericUtility
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/he_bend.png
|
||||
mode: ConstructObject
|
||||
prototype: HeatExchangerBend
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: MixerGas
|
||||
category: AtmosphericUtility
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/gas_mixer.png
|
||||
mode: ConstructObject
|
||||
prototype: GasMixer
|
||||
mirrorPrototype: GasMixerFlipped
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: FilterGas
|
||||
category: AtmosphericUtility
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/gas_filter.png
|
||||
mode: ConstructObject
|
||||
prototype: GasFilter
|
||||
mirrorPrototype: GasFilterFlipped
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: CanisterPort
|
||||
category: AtmosphericUtility
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/port.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPort
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: Alarm
|
||||
category: SensorsMonitors
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/alarm.png
|
||||
mode: ConstructObject
|
||||
prototype: AirAlarm
|
||||
cost: 10
|
||||
delay: 1
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: Sensor
|
||||
category: SensorsMonitors
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/sensor.png
|
||||
mode: ConstructObject
|
||||
prototype: AirSensor
|
||||
cost: 2
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: SensorPipe
|
||||
category: SensorsMonitors
|
||||
sprite: /Textures/_DV/Interface/Radial/RPD/sensorpipe.png
|
||||
mode: ConstructObject
|
||||
prototype: GasPipeSensor
|
||||
cost: 20
|
||||
delay: 0
|
||||
collisionMask: Impassable
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 330 B |
|
After Width: | Height: | Size: 777 B |
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 391 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 368 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 691 B |
|
After Width: | Height: | Size: 308 B |
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 334 B |
|
After Width: | Height: | Size: 332 B |
|
After Width: | Height: | Size: 707 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 274 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 820 B |
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 791 B |
|
After Width: | Height: | Size: 337 B |
|
After Width: | Height: | Size: 359 B |
|
After Width: | Height: | Size: 612 B |
|
After Width: | Height: | Size: 388 B |
|
After Width: | Height: | Size: 872 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from CEV-Eris at https://github.com/discordia-space/CEV-Eris/blob/8009c1d3db5ca5244f14899ffafaae93be0e318d/icons/obj/items.dmi,https://github.com/discordia-space/CEV-Eris/blob/df81fe2d0ba5f541bafffbb73554f3f44289dd76/icons/mob/items/righthand.dmi,https://github.com/discordia-space/CEV-Eris/blob/df81fe2d0ba5f541bafffbb73554f3f44289dd76/icons/mob/items/righthand.dmi",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
},
|
||||
{
|
||||
"name": "inhand-right",
|
||||
"directions": 4
|
||||
}
|
||||
]
|
||||
}
|
||||