Improved RCDs (#22799)
* Initial radial menu prototyping for the RCD * Radial UI buttons can send messages to the server * Beginning to update RCDSystem * RCD building system in progress * Further updates * Added extra effects, RCDSystem now reads RCD prototype data * Replacing tiles is instant, multiple constructions are allowed, deconstruction is broken * Added extra functionality to RadialContainers plus documentation * Fixed localization of RCD UI strings * Menu opens near cursor, added basic RCD * Avoiding merge conflict * Implemented atomized construction / deconstruction rules * Increased RCD ammo base charges * Moved input context definition to content * Removed obsoleted code * Updates to system * Switch machine and computer frames for electrical cabling * Added construction ghosts * Fixed issue with keybind detection code * Fixed RCD construction ghost mispredications * Code clean up * Updated deconstruction effects * RCDs effects don't rotate * Code clean up * Balancing for ammo counts * Code clean up * Added missing localized strings * More clean up * Made directional window handling more robust * Added documentation to radial menus and made them no longer dependent on Content * Made radial containers more robust * Further robustness to the radial menu * The RCD submenu buttons are only shown when the destination layer has at least one children * Expanded upon deconstructing plus construction balance * Fixed line endings * Updated list of RCD deconstructable entities. Now needs a component to deconstruct instead of a tag * Bug fixes * Revert unnecessary change * Updated RCD strings * Fixed bug * More fixes * Deconstructed tiles/subflooring convert to lattice instead * Fixed failed tests (Linux doesn't like invalid spritespecifer paths) * Fixing merge conflict * Updated airlock assembly * Fixing merge conflict * Fixing merge conflict * More fixing... * Removed erroneous project file change * Fixed string handling issue * Trying to fix merge conflict * Still fixing merge conflicts * Balancing * Hidden RCD construction ghosts when in 'build' mode * Fixing merge conflict * Implemented requested changes (Part 1) * Added more requested changes * Fix for failed test. Removed sussy null suppression * Made requested changes - custom construction ghost system was replaced * Fixing merge conflict * Fixed merge conflict * Fixed bug in RCD construction ghost validation * Fixing merge conflict * Merge conflict fixed * Made required update * Removed lingering RCD deconstruct tag * Fixing merge conflict * Merge conflict fixed * Made requested changes * Bug fixes and balancing * Made string names more consistent * Can no longer stack catwalks
@@ -45,6 +45,9 @@ namespace Content.Client.Input
|
||||
// Not in engine because the engine doesn't understand what a flipped object is
|
||||
common.AddFunction(ContentKeyFunctions.EditorFlipObject);
|
||||
|
||||
// Not in engine so that the RCD can rotate objects
|
||||
common.AddFunction(EngineKeyFunctions.EditorRotateObject);
|
||||
|
||||
var human = contexts.GetContext("human");
|
||||
human.AddFunction(EngineKeyFunctions.MoveUp);
|
||||
human.AddFunction(EngineKeyFunctions.MoveDown);
|
||||
|
||||
122
Content.Client/RCD/AlignRCDConstruction.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.RCD;
|
||||
|
||||
public sealed class AlignRCDConstruction : PlacementMode
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly RCDSystem _rcdSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
|
||||
private const float SearchBoxSize = 2f;
|
||||
private const float PlaceColorBaseAlpha = 0.5f;
|
||||
|
||||
private EntityCoordinates _unalignedMouseCoords = default;
|
||||
|
||||
/// <summary>
|
||||
/// This placement mode is not on the engine because it is content specific (i.e., for the RCD)
|
||||
/// </summary>
|
||||
public AlignRCDConstruction(PlacementManager pMan) : base(pMan)
|
||||
{
|
||||
var dependencies = IoCManager.Instance!;
|
||||
_entityManager = dependencies.Resolve<IEntityManager>();
|
||||
_mapManager = dependencies.Resolve<IMapManager>();
|
||||
_playerManager = dependencies.Resolve<IPlayerManager>();
|
||||
_stateManager = dependencies.Resolve<IStateManager>();
|
||||
|
||||
_mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
_rcdSystem = _entityManager.System<RCDSystem>();
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
ValidPlaceColor = ValidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
|
||||
}
|
||||
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
{
|
||||
_unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
|
||||
MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
|
||||
|
||||
var gridId = MouseCoords.GetGridUid(_entityManager);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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 (!xform.Coordinates.InRange(_entityManager, _transformSystem, 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 (!_entityManager.TryGetComponent<HandsComponent>(player, out var hands))
|
||||
return false;
|
||||
|
||||
var heldEntity = hands.ActiveHand?.HeldEntity;
|
||||
|
||||
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))
|
||||
return false;
|
||||
|
||||
// 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(_unalignedMouseCoords.ToMap(_entityManager, _transformSystem));
|
||||
|
||||
// Determine if the RCD operation is valid or not
|
||||
if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, mapGridData.Value, target, player.Value, false))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
78
Content.Client/RCD/RCDConstructionGhostSystem.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
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.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.RCD;
|
||||
|
||||
public sealed class RCDConstructionGhostSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly RCDSystem _rcdSystem = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
|
||||
private string _placementMode = typeof(AlignRCDConstruction).Name;
|
||||
private Direction _placementDirection = default;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
// Get current placer data
|
||||
var placerEntity = _placementManager.CurrentPermission?.MobUid;
|
||||
var placerProto = _placementManager.CurrentPermission?.EntityType;
|
||||
var placerIsRCD = HasComp<RCDComponent>(placerEntity);
|
||||
|
||||
// Exit if erasing or the current placer is not an RCD (build mode is active)
|
||||
if (_placementManager.Eraser || (placerEntity != null && !placerIsRCD))
|
||||
return;
|
||||
|
||||
// Determine if player is carrying an RCD in their active hand
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
|
||||
if (!TryComp<HandsComponent>(player, out var hands))
|
||||
return;
|
||||
|
||||
var heldEntity = hands.ActiveHand?.HeldEntity;
|
||||
|
||||
if (!TryComp<RCDComponent>(heldEntity, out var rcd))
|
||||
{
|
||||
// If the player was holding an RCD, but is no longer, cancel placement
|
||||
if (placerIsRCD)
|
||||
_placementManager.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the direction the RCD prototype based on the placer direction
|
||||
if (_placementDirection != _placementManager.Direction)
|
||||
{
|
||||
_placementDirection = _placementManager.Direction;
|
||||
RaiseNetworkEvent(new RCDConstructionGhostRotationEvent(GetNetEntity(heldEntity.Value), _placementDirection));
|
||||
}
|
||||
|
||||
// If the placer has not changed, exit
|
||||
_rcdSystem.UpdateCachedPrototype(heldEntity.Value, rcd);
|
||||
|
||||
if (heldEntity == placerEntity && rcd.CachedPrototype.Prototype == placerProto)
|
||||
return;
|
||||
|
||||
// Create a new placer
|
||||
var newObjInfo = new PlacementInformation
|
||||
{
|
||||
MobUid = heldEntity.Value,
|
||||
PlacementOption = _placementMode,
|
||||
EntityType = rcd.CachedPrototype.Prototype,
|
||||
Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange),
|
||||
IsTile = (rcd.CachedPrototype.Mode == RcdMode.ConstructTile),
|
||||
UseEditorContext = false,
|
||||
};
|
||||
|
||||
_placementManager.Clear();
|
||||
_placementManager.BeginPlacing(newObjInfo);
|
||||
}
|
||||
}
|
||||
47
Content.Client/RCD/RCDMenu.xaml
Normal file
@@ -0,0 +1,47 @@
|
||||
<ui:RadialMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:rcd="clr-namespace:Content.Client.RCD"
|
||||
BackButtonStyleClass="RadialMenuBackButton"
|
||||
CloseButtonStyleClass="RadialMenuCloseButton"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="450 450">
|
||||
|
||||
<!-- Note: The min size of the window just determine how close to the edge of the screen the center of the radial menu can be placed -->
|
||||
<!-- The radial menu will try to open so that its center is located where the player's cursor is currently -->
|
||||
|
||||
<!-- Entry layer (shows main categories) -->
|
||||
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-walls-and-flooring'}" TargetLayer="WallsAndFlooring" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/walls_and_flooring.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-windows-and-grilles'}" TargetLayer="WindowsAndGrilles" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/windows_and_grilles.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-airlocks'}" TargetLayer="Airlocks" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/airlocks.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-electrical'}" TargetLayer="Electrical" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/multicoil.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'rcd-component-lighting'}" TargetLayer="Lighting" Visible="False">
|
||||
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/lighting.png"/>
|
||||
</ui:RadialMenuTextureButton>
|
||||
</ui:RadialContainer>
|
||||
|
||||
<!-- Walls and flooring -->
|
||||
<ui:RadialContainer Name="WallsAndFlooring" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
<!-- Windows and grilles -->
|
||||
<ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
<!-- Airlocks -->
|
||||
<ui:RadialContainer Name="Airlocks" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
<!-- Computer and machine frames -->
|
||||
<ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
<!-- Lighting -->
|
||||
<ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
|
||||
|
||||
</ui:RadialMenu>
|
||||
137
Content.Client/RCD/RCDMenu.xaml.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.RCD;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.RCD;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class RCDMenu : RadialMenu
|
||||
{
|
||||
[Dependency] private readonly EntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public event Action<ProtoId<RCDPrototype>>? SendRCDSystemMessageAction;
|
||||
|
||||
public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
|
||||
// Find the main radial container
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
|
||||
if (main == null)
|
||||
return;
|
||||
|
||||
// Populate secondary radial containers
|
||||
if (!_entManager.TryGetComponent<RCDComponent>(owner, out var rcd))
|
||||
return;
|
||||
|
||||
foreach (var protoId in rcd.AvailablePrototypes)
|
||||
{
|
||||
if (!_protoManager.TryIndex(protoId, out var proto))
|
||||
continue;
|
||||
|
||||
if (proto.Mode == RcdMode.Invalid)
|
||||
continue;
|
||||
|
||||
var parent = FindControl<RadialContainer>(proto.Category);
|
||||
|
||||
if (parent == null)
|
||||
continue;
|
||||
|
||||
var name = Loc.GetString(proto.SetName);
|
||||
name = char.ToUpper(name[0]) + name.Remove(0, 1);
|
||||
|
||||
var button = new RCDMenuButton()
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = name,
|
||||
ProtoId = protoId,
|
||||
};
|
||||
|
||||
if (proto.Sprite != null)
|
||||
{
|
||||
var tex = new TextureRect()
|
||||
{
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Texture = _spriteSystem.Frame0(proto.Sprite),
|
||||
TextureScale = new Vector2(2f, 2f),
|
||||
};
|
||||
|
||||
button.AddChild(tex);
|
||||
}
|
||||
|
||||
parent.AddChild(button);
|
||||
|
||||
// Ensure that the button that transitions the menu to the associated category layer
|
||||
// is visible in the main radial container (as these all start with Visible = false)
|
||||
foreach (var child in main.Children)
|
||||
{
|
||||
var castChild = child as RadialMenuTextureButton;
|
||||
|
||||
if (castChild is not RadialMenuTextureButton)
|
||||
continue;
|
||||
|
||||
if (castChild.TargetLayer == proto.Category)
|
||||
{
|
||||
castChild.Visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up menu actions
|
||||
foreach (var child in Children)
|
||||
AddRCDMenuButtonOnClickActions(child);
|
||||
|
||||
OnChildAdded += AddRCDMenuButtonOnClickActions;
|
||||
|
||||
SendRCDSystemMessageAction += bui.SendRCDSystemMessage;
|
||||
}
|
||||
|
||||
private void AddRCDMenuButtonOnClickActions(Control control)
|
||||
{
|
||||
var radialContainer = control as RadialContainer;
|
||||
|
||||
if (radialContainer == null)
|
||||
return;
|
||||
|
||||
foreach (var child in radialContainer.Children)
|
||||
{
|
||||
var castChild = child as RCDMenuButton;
|
||||
|
||||
if (castChild == null)
|
||||
continue;
|
||||
|
||||
castChild.OnButtonUp += _ =>
|
||||
{
|
||||
SendRCDSystemMessageAction?.Invoke(castChild.ProtoId);
|
||||
Close();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RCDMenuButton : RadialMenuTextureButton
|
||||
{
|
||||
public ProtoId<RCDPrototype> ProtoId { get; set; }
|
||||
|
||||
public RCDMenuButton()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
49
Content.Client/RCD/RCDMenuBoundUserInterface.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Content.Shared.RCD;
|
||||
using Content.Shared.RCD.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.RCD;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class RCDMenuBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
private RCDMenu? _menu;
|
||||
|
||||
public RCDMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new(Owner, this);
|
||||
_menu.OnClose += Close;
|
||||
|
||||
// Open the menu, centered on the mouse
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
|
||||
}
|
||||
|
||||
public void SendRCDSystemMessage(ProtoId<RCDPrototype> protoId)
|
||||
{
|
||||
// A predicted message cannot be used here as the RCD UI is closed immediately
|
||||
// after this message is sent, which will stop the server from receiving it
|
||||
SendMessage(new RCDSystemMessage(protoId));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing) return;
|
||||
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -368,9 +368,9 @@ namespace Content.Client.Stylesheets
|
||||
};
|
||||
tabContainerPanel.SetPatchMargin(StyleBox.Margin.All, 2);
|
||||
|
||||
var tabContainerBoxActive = new StyleBoxFlat {BackgroundColor = new Color(64, 64, 64)};
|
||||
var tabContainerBoxActive = new StyleBoxFlat { BackgroundColor = new Color(64, 64, 64) };
|
||||
tabContainerBoxActive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
|
||||
var tabContainerBoxInactive = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 32)};
|
||||
var tabContainerBoxInactive = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 32) };
|
||||
tabContainerBoxInactive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
|
||||
|
||||
var progressBarBackground = new StyleBoxFlat
|
||||
@@ -409,21 +409,21 @@ namespace Content.Client.Stylesheets
|
||||
|
||||
// Placeholder
|
||||
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
|
||||
var placeholder = new StyleBoxTexture {Texture = placeholderTexture};
|
||||
var placeholder = new StyleBoxTexture { Texture = placeholderTexture };
|
||||
placeholder.SetPatchMargin(StyleBox.Margin.All, 19);
|
||||
placeholder.SetExpandMargin(StyleBox.Margin.All, -5);
|
||||
placeholder.Mode = StyleBoxTexture.StretchMode.Tile;
|
||||
|
||||
var itemListBackgroundSelected = new StyleBoxFlat {BackgroundColor = new Color(75, 75, 86)};
|
||||
var itemListBackgroundSelected = new StyleBoxFlat { BackgroundColor = new Color(75, 75, 86) };
|
||||
itemListBackgroundSelected.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||
itemListBackgroundSelected.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
||||
var itemListItemBackgroundDisabled = new StyleBoxFlat {BackgroundColor = new Color(10, 10, 12)};
|
||||
var itemListItemBackgroundDisabled = new StyleBoxFlat { BackgroundColor = new Color(10, 10, 12) };
|
||||
itemListItemBackgroundDisabled.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||
itemListItemBackgroundDisabled.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
||||
var itemListItemBackground = new StyleBoxFlat {BackgroundColor = new Color(55, 55, 68)};
|
||||
var itemListItemBackground = new StyleBoxFlat { BackgroundColor = new Color(55, 55, 68) };
|
||||
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
||||
var itemListItemBackgroundTransparent = new StyleBoxFlat {BackgroundColor = Color.Transparent};
|
||||
var itemListItemBackgroundTransparent = new StyleBoxFlat { BackgroundColor = Color.Transparent };
|
||||
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
|
||||
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
|
||||
|
||||
@@ -489,9 +489,9 @@ namespace Content.Client.Stylesheets
|
||||
sliderForeBox.SetPatchMargin(StyleBox.Margin.All, 12);
|
||||
sliderGrabBox.SetPatchMargin(StyleBox.Margin.All, 12);
|
||||
|
||||
var sliderFillGreen = new StyleBoxTexture(sliderFillBox) {Modulate = Color.LimeGreen};
|
||||
var sliderFillRed = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Red};
|
||||
var sliderFillBlue = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Blue};
|
||||
var sliderFillGreen = new StyleBoxTexture(sliderFillBox) { Modulate = Color.LimeGreen };
|
||||
var sliderFillRed = new StyleBoxTexture(sliderFillBox) { Modulate = Color.Red };
|
||||
var sliderFillBlue = new StyleBoxTexture(sliderFillBox) { Modulate = Color.Blue };
|
||||
var sliderFillWhite = new StyleBoxTexture(sliderFillBox) { Modulate = Color.White };
|
||||
|
||||
var boxFont13 = resCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||
@@ -1468,6 +1468,25 @@ namespace Content.Client.Stylesheets
|
||||
Element<Label>().Class("Disabled")
|
||||
.Prop("font-color", DisabledFore),
|
||||
|
||||
// Radial menu buttons
|
||||
Element<TextureButton>().Class("RadialMenuButton")
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/button_normal.png")),
|
||||
Element<TextureButton>().Class("RadialMenuButton")
|
||||
.Pseudo(TextureButton.StylePseudoClassHover)
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/button_hover.png")),
|
||||
|
||||
Element<TextureButton>().Class("RadialMenuCloseButton")
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/close_normal.png")),
|
||||
Element<TextureButton>().Class("RadialMenuCloseButton")
|
||||
.Pseudo(TextureButton.StylePseudoClassHover)
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/close_hover.png")),
|
||||
|
||||
Element<TextureButton>().Class("RadialMenuBackButton")
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/back_normal.png")),
|
||||
Element<TextureButton>().Class("RadialMenuBackButton")
|
||||
.Pseudo(TextureButton.StylePseudoClassHover)
|
||||
.Prop(TextureButton.StylePropertyTexture, resCache.GetTexture("/Textures/Interface/Radial/back_hover.png")),
|
||||
|
||||
//PDA - Backgrounds
|
||||
Element<PanelContainer>().Class("PdaContentBackground")
|
||||
.Prop(PanelContainer.StylePropertyPanel, BaseButtonOpenBoth)
|
||||
|
||||
105
Content.Client/UserInterface/Controls/RadialContainer.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
[Virtual]
|
||||
public class RadialContainer : LayoutContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the anglular range, in radians, in which child elements will be placed.
|
||||
/// The first value denotes the angle at which the first element is to be placed, and
|
||||
/// the second value denotes the angle at which the last element is to be placed.
|
||||
/// Both values must be between 0 and 2 PI radians
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The top of the screen is at 0 radians, and the bottom of the screen is at PI radians
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 AngularRange
|
||||
{
|
||||
get
|
||||
{
|
||||
return _angularRange;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
var x = value.X;
|
||||
var y = value.Y;
|
||||
|
||||
x = x > MathF.Tau ? x % MathF.Tau : x;
|
||||
y = y > MathF.Tau ? y % MathF.Tau : y;
|
||||
|
||||
x = x < 0 ? MathF.Tau + x : x;
|
||||
y = y < 0 ? MathF.Tau + y : y;
|
||||
|
||||
_angularRange = new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _angularRange = new Vector2(0f, MathF.Tau - float.Epsilon);
|
||||
|
||||
/// <summary>
|
||||
/// Determines the direction in which child elements will be arranged
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how far from the radial container's center that its child elements will be placed
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Radius { get; set; } = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the container should reserve a space on the layout for child which are not currently visible
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool ReserveSpaceForHiddenChildren { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// This container arranges its children, evenly separated, in a radial pattern
|
||||
/// </summary>
|
||||
public RadialContainer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible);
|
||||
var childCount = children.Count();
|
||||
|
||||
// Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements
|
||||
var arc = AngularRange.Y - AngularRange.X;
|
||||
arc = (arc < 0) ? MathF.Tau + arc : arc;
|
||||
arc = (RadialAlignment == RAlignment.AntiClockwise) ? MathF.Tau - arc : arc;
|
||||
|
||||
// Account for both circular arrangements and arc-based arrangements
|
||||
var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f) ? 0 : 1;
|
||||
|
||||
// Determine the separation between child elements
|
||||
var sepAngle = arc / (childCount - childMod);
|
||||
sepAngle *= (RadialAlignment == RAlignment.AntiClockwise) ? -1f : 1f;
|
||||
|
||||
// Adjust the positions of all the child elements
|
||||
foreach (var (i, child) in children.Select((x, i) => (i, x)))
|
||||
{
|
||||
var position = new Vector2(Radius * MathF.Sin(AngularRange.X + sepAngle * i) + Width / 2f - child.Width / 2f, -Radius * MathF.Cos(AngularRange.X + sepAngle * i) + Height / 2f - child.Height / 2f);
|
||||
SetPosition(child, position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the different radial alignment modes
|
||||
/// </summary>
|
||||
/// <seealso cref="RadialAlignment"/>
|
||||
public enum RAlignment : byte
|
||||
{
|
||||
Clockwise,
|
||||
AntiClockwise,
|
||||
}
|
||||
}
|
||||
255
Content.Client/UserInterface/Controls/RadialMenu.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls;
|
||||
|
||||
[Virtual]
|
||||
public class RadialMenu : BaseWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Contextual button used to traverse through previous layers of the radial menu
|
||||
/// </summary>
|
||||
public TextureButton? ContextualButton { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set a style class to be applied to the contextual button when it is set to move the user back through previous layers of the radial menu
|
||||
/// </summary>
|
||||
public string? BackButtonStyleClass
|
||||
{
|
||||
get
|
||||
{
|
||||
return _backButtonStyleClass;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_backButtonStyleClass = value;
|
||||
|
||||
if (_path.Count > 0 && ContextualButton != null && _backButtonStyleClass != null)
|
||||
ContextualButton.SetOnlyStyleClass(_backButtonStyleClass);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a style class to be applied to the contextual button when it will close the radial menu
|
||||
/// </summary>
|
||||
public string? CloseButtonStyleClass
|
||||
{
|
||||
get
|
||||
{
|
||||
return _closeButtonStyleClass;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_closeButtonStyleClass = value;
|
||||
|
||||
if (_path.Count == 0 && ContextualButton != null && _closeButtonStyleClass != null)
|
||||
ContextualButton.SetOnlyStyleClass(_closeButtonStyleClass);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Control> _path = new();
|
||||
private string? _backButtonStyleClass;
|
||||
private string? _closeButtonStyleClass;
|
||||
|
||||
/// <summary>
|
||||
/// A free floating menu which enables the quick display of one or more radial containers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only one radial container is visible at a time (each container forming a separate 'layer' within
|
||||
/// the menu), along with a contextual button at the menu center, which will either return the user
|
||||
/// to the previous layer or close the menu if there are no previous layers left to traverse.
|
||||
/// To create a functional radial menu, simply parent one or more named radial containers to it,
|
||||
/// and populate the radial containers with RadialMenuButtons. Setting the TargetLayer field of these
|
||||
/// buttons to the name of a radial conatiner will display the container in question to the user
|
||||
/// whenever it is clicked in additon to any other actions assigned to the button
|
||||
/// </remarks>
|
||||
public RadialMenu()
|
||||
{
|
||||
// Hide all starting children (if any) except the first (this is the active layer)
|
||||
if (ChildCount > 1)
|
||||
{
|
||||
for (int i = 1; i < ChildCount; i++)
|
||||
GetChild(i).Visible = false;
|
||||
}
|
||||
|
||||
// Auto generate a contextual button for moving back through visited layers
|
||||
ContextualButton = new TextureButton()
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
};
|
||||
|
||||
ContextualButton.OnButtonUp += _ => ReturnToPreviousLayer();
|
||||
AddChild(ContextualButton);
|
||||
|
||||
// Hide any further add children, unless its promoted to the active layer
|
||||
OnChildAdded += child => child.Visible = (GetCurrentActiveLayer() == child);
|
||||
}
|
||||
|
||||
private Control? GetCurrentActiveLayer()
|
||||
{
|
||||
var children = Children.Where(x => x != ContextualButton);
|
||||
|
||||
if (!children.Any())
|
||||
return null;
|
||||
|
||||
return children.First(x => x.Visible);
|
||||
}
|
||||
|
||||
public bool TryToMoveToNewLayer(string newLayer)
|
||||
{
|
||||
if (newLayer == string.Empty)
|
||||
return false;
|
||||
|
||||
var currentLayer = GetCurrentActiveLayer();
|
||||
|
||||
if (currentLayer == null)
|
||||
return false;
|
||||
|
||||
var result = false;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child == ContextualButton)
|
||||
continue;
|
||||
|
||||
// Hide layers which are not of interest
|
||||
if (result == true || child.Name != newLayer)
|
||||
{
|
||||
child.Visible = false;
|
||||
}
|
||||
|
||||
// Show the layer of interest
|
||||
else
|
||||
{
|
||||
child.Visible = true;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the traversal path
|
||||
if (result)
|
||||
_path.Add(currentLayer);
|
||||
|
||||
// Set the style class of the button
|
||||
if (_path.Count > 0 && ContextualButton != null && BackButtonStyleClass != null)
|
||||
ContextualButton.SetOnlyStyleClass(BackButtonStyleClass);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void ReturnToPreviousLayer()
|
||||
{
|
||||
// Close the menu if the traversal path is empty
|
||||
if (_path.Count == 0)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var lastChild = _path[^1];
|
||||
|
||||
// Hide all children except the contextual button
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child != ContextualButton)
|
||||
child.Visible = false;
|
||||
}
|
||||
|
||||
// Make the last visited layer visible, update the path list
|
||||
lastChild.Visible = true;
|
||||
_path.RemoveAt(_path.Count - 1);
|
||||
|
||||
// Set the style class of the button
|
||||
if (_path.Count == 0 && ContextualButton != null && CloseButtonStyleClass != null)
|
||||
ContextualButton.SetOnlyStyleClass(CloseButtonStyleClass);
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class RadialMenuButton : Button
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon clicking this button the radial menu will transition to the named layer
|
||||
/// </summary>
|
||||
public string? TargetLayer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A simple button that can move the user to a different layer within a radial menu
|
||||
/// </summary>
|
||||
public RadialMenuButton()
|
||||
{
|
||||
OnButtonUp += OnClicked;
|
||||
}
|
||||
|
||||
private void OnClicked(ButtonEventArgs args)
|
||||
{
|
||||
if (TargetLayer == null || TargetLayer == string.Empty)
|
||||
return;
|
||||
|
||||
var parent = FindParentMultiLayerContainer(this);
|
||||
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
parent.TryToMoveToNewLayer(TargetLayer);
|
||||
}
|
||||
|
||||
private RadialMenu? FindParentMultiLayerContainer(Control control)
|
||||
{
|
||||
foreach (var ancestor in control.GetSelfAndLogicalAncestors())
|
||||
{
|
||||
if (ancestor is RadialMenu)
|
||||
return ancestor as RadialMenu;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[Virtual]
|
||||
public class RadialMenuTextureButton : TextureButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Upon clicking this button the radial menu will be moved to the named layer
|
||||
/// </summary>
|
||||
public string TargetLayer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// A simple texture button that can move the user to a different layer within a radial menu
|
||||
/// </summary>
|
||||
public RadialMenuTextureButton()
|
||||
{
|
||||
OnButtonUp += OnClicked;
|
||||
}
|
||||
|
||||
private void OnClicked(ButtonEventArgs args)
|
||||
{
|
||||
if (TargetLayer == string.Empty)
|
||||
return;
|
||||
|
||||
var parent = FindParentMultiLayerContainer(this);
|
||||
|
||||
if (parent == null)
|
||||
return;
|
||||
|
||||
parent.TryToMoveToNewLayer(TargetLayer);
|
||||
}
|
||||
|
||||
private RadialMenu? FindParentMultiLayerContainer(Control control)
|
||||
{
|
||||
foreach (var ancestor in control.GetSelfAndLogicalAncestors())
|
||||
{
|
||||
if (ancestor is RadialMenu)
|
||||
return ancestor as RadialMenu;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -61,4 +61,26 @@ public abstract class SharedChargesSystem : EntitySystem
|
||||
if (Resolve(uid, ref comp, false))
|
||||
AddCharges(uid, -1, comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the limited charges component and returns true if the number of charges remaining is less than the specified value.
|
||||
/// Will return false if there is no limited charges component.
|
||||
/// </summary>
|
||||
public bool HasInsufficientCharges(EntityUid uid, int requiredCharges, LimitedChargesComponent? comp = null)
|
||||
{
|
||||
// can't be empty if there are no limited charges
|
||||
if (!Resolve(uid, ref comp, false))
|
||||
return false;
|
||||
|
||||
return comp.Charges < requiredCharges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses up a specified number of charges. Must check HasInsufficentCharges beforehand to prevent using with insufficient remaining charges.
|
||||
/// </summary>
|
||||
public virtual void UseCharges(EntityUid uid, int chargesUsed, LimitedChargesComponent? comp = null)
|
||||
{
|
||||
if (Resolve(uid, ref comp, false))
|
||||
AddCharges(uid, -chargesUsed, comp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Shared.Construction.Components
|
||||
@@ -9,7 +9,7 @@ namespace Content.Shared.Construction.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class ComputerBoardComponent : Component
|
||||
{
|
||||
[DataField("prototype", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
[DataField("prototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? Prototype { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,5 @@ public sealed partial class RCDAmmoComponent : Component
|
||||
/// Can be partially transferred into an RCD, until it is empty then it gets deleted.
|
||||
/// </summary>
|
||||
[DataField("charges"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public int Charges = 5;
|
||||
public int Charges = 30;
|
||||
}
|
||||
|
||||
// TODO: state??? check if it desyncs
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.RCD.Components;
|
||||
|
||||
public enum RcdMode : byte
|
||||
{
|
||||
Floors,
|
||||
Walls,
|
||||
Airlock,
|
||||
Deconstruct
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main component for the RCD
|
||||
/// Optionally uses LimitedChargesComponent.
|
||||
@@ -25,27 +16,57 @@ public enum RcdMode : byte
|
||||
public sealed partial class RCDComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Time taken to do an action like placing a wall
|
||||
/// List of RCD prototypes that the device comes loaded with
|
||||
/// </summary>
|
||||
[DataField("delay"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public float Delay = 2f;
|
||||
|
||||
[DataField("swapModeSound")]
|
||||
public SoundSpecifier SwapModeSound = new SoundPathSpecifier("/Audio/Items/genhit.ogg");
|
||||
|
||||
[DataField("successSound")]
|
||||
public SoundSpecifier SuccessSound = new SoundPathSpecifier("/Audio/Items/deconstruct.ogg");
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<ProtoId<RCDPrototype>> AvailablePrototypes { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// What mode are we on? Can be floors, walls, airlock, deconstruct.
|
||||
/// Sound that plays when a RCD operation successfully completes
|
||||
/// </summary>
|
||||
[DataField("mode"), AutoNetworkedField]
|
||||
public RcdMode Mode = RcdMode.Floors;
|
||||
[DataField]
|
||||
public SoundSpecifier SuccessSound { get; set; } = new SoundPathSpecifier("/Audio/Items/deconstruct.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// ID of the floor to create when using the floor mode.
|
||||
/// The ProtoId of the currently selected RCD prototype
|
||||
/// </summary>
|
||||
[DataField("floor", customTypeSerializer: typeof(PrototypeIdSerializer<ContentTileDefinition>))]
|
||||
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public string Floor = "FloorSteel";
|
||||
[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;
|
||||
}
|
||||
set
|
||||
{
|
||||
_constructionDirection = value;
|
||||
ConstructionTransform = new Transform(new(), _constructionDirection.ToAngle());
|
||||
}
|
||||
}
|
||||
|
||||
private Direction _constructionDirection = Direction.South;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a rotated transform based on the specified ConstructionDirection
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contains no position data
|
||||
/// </remarks>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public Transform ConstructionTransform { get; private set; } = default!;
|
||||
}
|
||||
|
||||
34
Content.Shared/RCD/Components/RCDDeconstructibleComponent.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Shared.RCD.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.RCD.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(RCDSystem))]
|
||||
public sealed partial class RCDDeconstructableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of charges consumed when the deconstruction is completed
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Cost = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the deconstruction
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Delay = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// The visual effect that plays during deconstruction
|
||||
/// </summary>
|
||||
[DataField("fx"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntProtoId? Effect = null;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles whether this entity is deconstructable or not
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Deconstructable = true;
|
||||
}
|
||||
34
Content.Shared/RCD/RCDEvents.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.RCD;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RCDSystemMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public ProtoId<RCDPrototype> ProtoId;
|
||||
|
||||
public RCDSystemMessage(ProtoId<RCDPrototype> protoId)
|
||||
{
|
||||
ProtoId = protoId;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class RCDConstructionGhostRotationEvent : EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity NetEntity;
|
||||
public readonly Direction Direction;
|
||||
|
||||
public RCDConstructionGhostRotationEvent(NetEntity netEntity, Direction direction)
|
||||
{
|
||||
NetEntity = netEntity;
|
||||
Direction = direction;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum RcdUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
144
Content.Shared/RCD/RCDPrototype.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.RCD;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the parameters for a RCD construction / operation
|
||||
/// </summary>
|
||||
[Prototype("rcd")]
|
||||
public sealed class RCDPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The RCD mode associated with the operation
|
||||
/// </summary>
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadOnly)]
|
||||
public RcdMode Mode { get; private set; } = RcdMode.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// The name associated with the prototype
|
||||
/// </summary>
|
||||
[DataField("name"), ViewVariables(VVAccess.ReadOnly)]
|
||||
public string SetName { get; private set; } = "Unknown";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the radial container that this prototype will be listed under on the RCD menu
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public string Category { get; private set; } = "Undefined";
|
||||
|
||||
/// <summary>
|
||||
/// Texture path for this prototypes menu icon
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public SpriteSpecifier? Sprite { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// The entity prototype that will be constructed (mode dependent)
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public string? Prototype { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Number of charges consumed when the operation is completed
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public int Cost { get; private set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the operation
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public float Delay { get; private set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// The visual effect that plays during this operation
|
||||
/// </summary>
|
||||
[DataField("fx"), ViewVariables(VVAccess.ReadOnly)]
|
||||
public EntProtoId? Effect { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// A list of rules that govern where the entity prototype can be contructed
|
||||
/// </summary>
|
||||
[DataField("rules"), ViewVariables(VVAccess.ReadOnly)]
|
||||
public HashSet<RcdConstructionRule> ConstructionRules { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The collision mask used for determining whether the entity prototype will fit into a target tile
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public CollisionGroup CollisionMask { get; private set; } = CollisionGroup.None;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a set of custom collision bounds for determining whether the entity prototype will fit into a target tile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should be set assuming that the entity faces south.
|
||||
/// Make sure that Rotation is set to RcdRotation.User if the entity is to be rotated by the user
|
||||
/// </remarks>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public Box2? CollisionBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _collisionBounds;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
_collisionBounds = value;
|
||||
|
||||
if (_collisionBounds != null)
|
||||
{
|
||||
var poly = new PolygonShape();
|
||||
poly.SetAsBox(_collisionBounds.Value);
|
||||
|
||||
CollisionPolygon = poly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Box2? _collisionBounds = null;
|
||||
|
||||
/// <summary>
|
||||
/// The polygon shape associated with the prototype CollisionBounds (if set)
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public PolygonShape? CollisionPolygon { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Governs how the local rotation of the constructed entity will be set
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadOnly)]
|
||||
public RcdRotation Rotation { get; private set; } = RcdRotation.User;
|
||||
}
|
||||
|
||||
public enum RcdMode : byte
|
||||
{
|
||||
Invalid,
|
||||
Deconstruct,
|
||||
ConstructTile,
|
||||
ConstructObject,
|
||||
}
|
||||
|
||||
// These are to be replaced with more flexible 'RulesRule' at a later time
|
||||
public enum RcdConstructionRule : byte
|
||||
{
|
||||
MustBuildOnEmptyTile, // Can only be built on empty space (e.g. lattice)
|
||||
CanBuildOnEmptyTile, // Can be built on empty space or replace an existing tile (e.g. hull plating)
|
||||
MustBuildOnSubfloor, // Can only be built on exposed subfloor (e.g. catwalks on lattice or hull plating)
|
||||
IsWindow, // The entity is a window and can be built on grilles
|
||||
IsCatwalk, // The entity is a catwalk
|
||||
}
|
||||
|
||||
public enum RcdRotation : byte
|
||||
{
|
||||
Fixed, // The entity has a local rotation of zero
|
||||
Camera, // The rotation of the entity matches the local player camera
|
||||
User, // The entity can be rotated by the local player prior to placement
|
||||
}
|
||||
@@ -1,28 +1,35 @@
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Tiles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
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;
|
||||
|
||||
public sealed class RCDSystem : EntitySystem
|
||||
[Virtual]
|
||||
public class RCDSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
@@ -34,312 +41,599 @@ public sealed class RCDSystem : EntitySystem
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
|
||||
private readonly int _rcdModeCount = Enum.GetValues(typeof(RcdMode)).Length;
|
||||
private readonly int _instantConstructionDelay = 0;
|
||||
private readonly EntProtoId _instantConstructionFx = "EffectRCDConstruct0";
|
||||
private readonly ProtoId<RCDPrototype> _deconstructTileProto = "DeconstructTile";
|
||||
private readonly ProtoId<RCDPrototype> _deconstructLatticeProto = "DeconstructLattice";
|
||||
|
||||
private HashSet<EntityUid> _intersectingEntities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RCDComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<RCDComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<RCDComponent, UseInHandEvent>(OnUseInHand);
|
||||
SubscribeLocalEvent<RCDComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<RCDComponent, RCDDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<RCDComponent, DoAfterAttemptEvent<RCDDoAfterEvent>>(OnDoAfterAttempt);
|
||||
SubscribeLocalEvent<RCDComponent, RCDSystemMessage>(OnRCDSystemMessage);
|
||||
SubscribeNetworkEvent<RCDConstructionGhostRotationEvent>(OnRCDconstructionGhostRotationEvent);
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, RCDComponent comp, ExaminedEvent args)
|
||||
#region Event handling
|
||||
|
||||
private void OnMapInit(EntityUid uid, RCDComponent component, MapInitEvent args)
|
||||
{
|
||||
// On init, set the RCD to its first available recipe
|
||||
if (component.AvailablePrototypes.Any())
|
||||
{
|
||||
component.ProtoId = component.AvailablePrototypes.First();
|
||||
UpdateCachedPrototype(uid, component);
|
||||
Dirty(uid, component);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The RCD has no valid recipes somehow? Get rid of it
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
private void OnRCDSystemMessage(EntityUid uid, RCDComponent component, RCDSystemMessage args)
|
||||
{
|
||||
// Exit if the RCD doesn't actually know the supplied prototype
|
||||
if (!component.AvailablePrototypes.Contains(args.ProtoId))
|
||||
return;
|
||||
|
||||
if (!_protoManager.HasIndex(args.ProtoId))
|
||||
return;
|
||||
|
||||
// Set the current RCD prototype to the one supplied
|
||||
component.ProtoId = args.ProtoId;
|
||||
UpdateCachedPrototype(uid, component);
|
||||
Dirty(uid, component);
|
||||
|
||||
if (args.Session.AttachedEntity != null)
|
||||
{
|
||||
// Popup message
|
||||
var msg = (component.CachedPrototype.Prototype != null) ?
|
||||
Loc.GetString("rcd-component-change-build-mode", ("name", Loc.GetString(component.CachedPrototype.SetName))) :
|
||||
Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(component.CachedPrototype.SetName)));
|
||||
|
||||
_popup.PopupClient(msg, uid, args.Session.AttachedEntity.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, RCDComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
var msg = Loc.GetString("rcd-component-examine-detail", ("mode", comp.Mode));
|
||||
// Update cached prototype if required
|
||||
UpdateCachedPrototype(uid, component);
|
||||
|
||||
var msg = (component.CachedPrototype.Prototype != null) ?
|
||||
Loc.GetString("rcd-component-examine-build-details", ("name", Loc.GetString(component.CachedPrototype.SetName))) :
|
||||
Loc.GetString("rcd-component-examine-mode-details", ("mode", Loc.GetString(component.CachedPrototype.SetName)));
|
||||
|
||||
args.PushMarkup(msg);
|
||||
}
|
||||
|
||||
private void OnUseInHand(EntityUid uid, RCDComponent comp, UseInHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
NextMode(uid, comp, args.User);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, RCDComponent comp, AfterInteractEvent args)
|
||||
private void OnAfterInteract(EntityUid uid, RCDComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Handled || !args.CanReach)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
|
||||
TryComp<LimitedChargesComponent>(uid, out var charges);
|
||||
if (_charges.IsEmpty(uid, charges))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-no-ammo-message"), uid, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var location = args.ClickLocation;
|
||||
// Initial validity check
|
||||
|
||||
// Initial validity checks
|
||||
if (!location.IsValid(EntityManager))
|
||||
return;
|
||||
|
||||
var gridId = location.GetGridUid(EntityManager);
|
||||
if (!HasComp<MapGridComponent>(gridId))
|
||||
if (!TryGetMapGridData(location, out var mapGridData))
|
||||
{
|
||||
location = location.AlignWithClosestGridTile();
|
||||
gridId = location.GetGridUid(EntityManager);
|
||||
// Check if fixing it failed / get final grid ID
|
||||
if (!HasComp<MapGridComponent>(gridId))
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-no-valid-grid"), uid, user);
|
||||
return;
|
||||
}
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, user, comp.Delay, new RCDDoAfterEvent(GetNetCoordinates(location), comp.Mode), uid, target: args.Target, used: uid)
|
||||
if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, 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;
|
||||
|
||||
#region: Operation modifiers
|
||||
|
||||
// Deconstruction modifiers
|
||||
switch (component.CachedPrototype.Mode)
|
||||
{
|
||||
case RcdMode.Deconstruct:
|
||||
|
||||
// Deconstructing an object
|
||||
if (args.Target != null)
|
||||
{
|
||||
if (TryComp<RCDDeconstructableComponent>(args.Target, out var destructible))
|
||||
{
|
||||
cost = destructible.Cost;
|
||||
delay = destructible.Delay;
|
||||
effectPrototype = destructible.Effect;
|
||||
}
|
||||
}
|
||||
|
||||
// Deconstructing a tile
|
||||
else
|
||||
{
|
||||
var deconstructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location);
|
||||
var protoName = deconstructedTile.IsSpace() ? _deconstructTileProto : _deconstructLatticeProto;
|
||||
|
||||
if (_protoManager.TryIndex(protoName, out var deconProto))
|
||||
{
|
||||
cost = deconProto.Cost;
|
||||
delay = deconProto.Delay;
|
||||
effectPrototype = deconProto.Effect;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RcdMode.ConstructTile:
|
||||
|
||||
// If replacing a tile, make the construction instant
|
||||
var contructedTile = _mapSystem.GetTileRef(mapGridData.Value.GridUid, mapGridData.Value.Component, mapGridData.Value.Location);
|
||||
|
||||
if (!contructedTile.Tile.IsEmpty)
|
||||
{
|
||||
delay = _instantConstructionDelay;
|
||||
effectPrototype = _instantConstructionFx;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Try to start the do after
|
||||
var effect = Spawn(effectPrototype, mapGridData.Value.Location);
|
||||
var ev = new RCDDoAfterEvent(GetNetCoordinates(mapGridData.Value.Location), component.ProtoId, cost, EntityManager.GetNetEntity(effect));
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, user, delay, ev, uid, target: args.Target, used: uid)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
NeedHand = true,
|
||||
BreakOnHandChange = true,
|
||||
BreakOnMove = true,
|
||||
AttemptFrequency = AttemptFrequency.EveryTick
|
||||
AttemptFrequency = AttemptFrequency.EveryTick,
|
||||
CancelDuplicate = false,
|
||||
BlockDuplicate = false
|
||||
};
|
||||
|
||||
args.Handled = true;
|
||||
|
||||
if (_doAfter.TryStartDoAfter(doAfterArgs) && _gameTiming.IsFirstTimePredicted)
|
||||
Spawn("EffectRCDConstruction", location);
|
||||
if (!_doAfter.TryStartDoAfter(doAfterArgs))
|
||||
QueueDel(effect);
|
||||
}
|
||||
|
||||
private void OnDoAfterAttempt(EntityUid uid, RCDComponent comp, DoAfterAttemptEvent<RCDDoAfterEvent> args)
|
||||
private void OnDoAfterAttempt(EntityUid uid, RCDComponent component, DoAfterAttemptEvent<RCDDoAfterEvent> args)
|
||||
{
|
||||
// sus client crash why
|
||||
if (args.Event?.DoAfter?.Args == null)
|
||||
return;
|
||||
|
||||
// Exit if the RCD prototype has changed
|
||||
if (component.ProtoId != args.Event.StartingProtoId)
|
||||
return;
|
||||
|
||||
// Ensure the RCD operation is still valid
|
||||
var location = GetCoordinates(args.Event.Location);
|
||||
|
||||
var gridId = location.GetGridUid(EntityManager);
|
||||
if (!HasComp<MapGridComponent>(gridId))
|
||||
{
|
||||
location = location.AlignWithClosestGridTile();
|
||||
gridId = location.GetGridUid(EntityManager);
|
||||
// Check if fixing it failed / get final grid ID
|
||||
if (!HasComp<MapGridComponent>(gridId))
|
||||
if (!TryGetMapGridData(location, out var mapGridData))
|
||||
return;
|
||||
}
|
||||
|
||||
var mapGrid = Comp<MapGridComponent>(gridId.Value);
|
||||
var tile = mapGrid.GetTileRef(location);
|
||||
|
||||
if (!IsRCDStillValid(uid, comp, args.Event.User, args.Event.Target, mapGrid, tile, args.Event.StartingMode))
|
||||
if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Event.Target, args.Event.User))
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, RCDComponent comp, RCDDoAfterEvent args)
|
||||
private void OnDoAfter(EntityUid uid, RCDComponent component, RCDDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled && _net.IsServer)
|
||||
QueueDel(EntityManager.GetEntity(args.Effect));
|
||||
|
||||
if (args.Handled || args.Cancelled || !_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
args.Handled = true;
|
||||
|
||||
var location = GetCoordinates(args.Location);
|
||||
|
||||
var gridId = location.GetGridUid(EntityManager);
|
||||
if (!HasComp<MapGridComponent>(gridId))
|
||||
{
|
||||
location = location.AlignWithClosestGridTile();
|
||||
gridId = location.GetGridUid(EntityManager);
|
||||
// Check if fixing it failed / get final grid ID
|
||||
if (!HasComp<MapGridComponent>(gridId))
|
||||
if (!TryGetMapGridData(location, out var mapGridData))
|
||||
return;
|
||||
}
|
||||
|
||||
var mapGrid = Comp<MapGridComponent>(gridId.Value);
|
||||
var tile = mapGrid.GetTileRef(location);
|
||||
var snapPos = mapGrid.TileIndicesFor(location);
|
||||
|
||||
// I love that this uses entirely separate code to construction and tile placement!!!
|
||||
|
||||
switch (comp.Mode)
|
||||
{
|
||||
//Floor mode just needs the tile to be a space tile (subFloor)
|
||||
case RcdMode.Floors:
|
||||
if (!_floors.CanPlaceTile(gridId.Value, mapGrid, out var reason))
|
||||
{
|
||||
_popup.PopupClient(reason, user, user);
|
||||
// Ensure the RCD operation is still valid
|
||||
if (!IsRCDOperationStillValid(uid, component, mapGridData.Value, args.Target, args.User))
|
||||
return;
|
||||
|
||||
// Finalize the operation
|
||||
FinalizeRCDOperation(uid, component, mapGridData.Value, args.Target, args.User);
|
||||
|
||||
// Play audio and consume charges
|
||||
_audio.PlayPredicted(component.SuccessSound, uid, args.User);
|
||||
_charges.UseCharges(uid, args.Cost);
|
||||
}
|
||||
|
||||
mapGrid.SetTile(snapPos, new Tile(_tileDefMan[comp.Floor].TileId));
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridUid} {snapPos} to {comp.Floor}");
|
||||
break;
|
||||
//We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
|
||||
case RcdMode.Deconstruct:
|
||||
if (!IsTileBlocked(tile)) // Delete the turf
|
||||
private void OnRCDconstructionGhostRotationEvent(RCDConstructionGhostRotationEvent ev, EntitySessionEventArgs session)
|
||||
{
|
||||
mapGrid.SetTile(snapPos, Tile.Empty);
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to set grid: {tile.GridUid} tile: {snapPos} to space");
|
||||
}
|
||||
else // Delete the targeted thing
|
||||
{
|
||||
var target = args.Target!.Value;
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to delete {ToPrettyString(target):target}");
|
||||
QueueDel(target);
|
||||
}
|
||||
break;
|
||||
//Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile,
|
||||
// thus we early return to avoid the tile set code.
|
||||
case RcdMode.Walls:
|
||||
// only spawn on the server
|
||||
if (_net.IsServer)
|
||||
{
|
||||
var ent = Spawn("WallSolid", mapGrid.GridTileToLocal(snapPos));
|
||||
Transform(ent).LocalRotation = Angle.Zero; // Walls always need to point south.
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to spawn {ToPrettyString(ent)} at {snapPos} on grid {tile.GridUid}");
|
||||
}
|
||||
break;
|
||||
case RcdMode.Airlock:
|
||||
// only spawn on the server
|
||||
if (_net.IsServer)
|
||||
{
|
||||
var airlock = Spawn("Airlock", mapGrid.GridTileToLocal(snapPos));
|
||||
Transform(airlock).LocalRotation = Transform(uid).LocalRotation; //Now apply icon smoothing.
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(args.User):user} used RCD to spawn {ToPrettyString(airlock)} at {snapPos} on grid {tile.GridUid}");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
args.Handled = true;
|
||||
return; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
|
||||
var uid = GetEntity(ev.NetEntity);
|
||||
|
||||
// Determine if player that send the message is carrying the specified RCD in their active hand
|
||||
if (session.SenderSession.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(session.SenderSession.AttachedEntity, out var hands) ||
|
||||
uid != hands.ActiveHand?.HeldEntity)
|
||||
return;
|
||||
|
||||
if (!TryComp<RCDComponent>(uid, out var rcd))
|
||||
return;
|
||||
|
||||
// Update the construction direction
|
||||
rcd.ConstructionDirection = ev.Direction;
|
||||
Dirty(uid, rcd);
|
||||
}
|
||||
|
||||
_audio.PlayPredicted(comp.SuccessSound, uid, user);
|
||||
_charges.UseCharge(uid);
|
||||
args.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private bool IsRCDStillValid(EntityUid uid, RCDComponent comp, EntityUid user, EntityUid? target, MapGridComponent mapGrid, TileRef tile, RcdMode startingMode)
|
||||
#region Entity construction/deconstruction rule checks
|
||||
|
||||
public bool IsRCDOperationStillValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
//Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed.
|
||||
if (comp.Mode != startingMode)
|
||||
// Update cached prototype if required
|
||||
UpdateCachedPrototype(uid, component);
|
||||
|
||||
// Check that the RCD has enough ammo to get the job done
|
||||
TryComp<LimitedChargesComponent>(uid, out var charges);
|
||||
|
||||
// Both of these were messages were suppose to be predicted, but HasInsufficientCharges wasn't being checked on the client for some reason?
|
||||
if (_charges.IsEmpty(uid, charges))
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-no-ammo-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var unobstructed = target == null
|
||||
? _interaction.InRangeUnobstructed(user, mapGrid.GridTileToWorld(tile.GridIndices), popup: true)
|
||||
: _interaction.InRangeUnobstructed(user, target.Value, popup: true);
|
||||
if (_charges.HasInsufficientCharges(uid, component.CachedPrototype.Cost, charges))
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-insufficient-ammo-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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, target.Value, popup: popMsgs);
|
||||
|
||||
if (!unobstructed)
|
||||
return false;
|
||||
|
||||
switch (comp.Mode)
|
||||
// Return whether the operation location is valid
|
||||
switch (component.CachedPrototype.Mode)
|
||||
{
|
||||
//Floor mode just needs the tile to be a space tile (subFloor)
|
||||
case RcdMode.Floors:
|
||||
if (!tile.Tile.IsEmpty)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-floor-tile-not-empty-message"), uid, user);
|
||||
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);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
//We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check.
|
||||
case RcdMode.Deconstruct:
|
||||
if (tile.Tile.IsEmpty)
|
||||
return false;
|
||||
private bool IsConstructionLocationValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
// Check rule: Must build on empty tile
|
||||
if (component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnEmptyTile) && !mapGridData.Tile.Tile.IsEmpty)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-must-build-on-empty-tile-message"), uid, user);
|
||||
|
||||
//They tried to decon a turf but...
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check rule: Must build on non-empty tile
|
||||
if (!component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.CanBuildOnEmptyTile) && mapGridData.Tile.Tile.IsEmpty)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-empty-tile-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check rule: Must place on subfloor
|
||||
if (component.CachedPrototype.ConstructionRules.Contains(RcdConstructionRule.MustBuildOnSubfloor) && !mapGridData.Tile.Tile.GetContentTileDefinition().IsSubFloor)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-must-build-on-subfloor-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tile specific rules
|
||||
if (component.CachedPrototype.Mode == RcdMode.ConstructTile)
|
||||
{
|
||||
// Check rule: Tile placement is valid
|
||||
if (!_floors.CanPlaceTile(mapGridData.GridUid, mapGridData.Component, out var reason))
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(reason, uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check rule: Tiles can't be identical
|
||||
if (mapGridData.Tile.Tile.GetContentTileDefinition().ID == component.CachedPrototype.Prototype)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-identical-tile"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure that all construction rules shared between tiles and object are checked before exiting here
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
_intersectingEntities.Clear();
|
||||
_lookup.GetLocalEntitiesIntersecting(mapGridData.GridUid, mapGridData.Position, _intersectingEntities, -0.05f, LookupFlags.Uncontained);
|
||||
|
||||
foreach (var ent in _intersectingEntities)
|
||||
{
|
||||
if (isWindow && HasComp<SharedCanBuildWindowOnTopComponent>(ent))
|
||||
continue;
|
||||
|
||||
if (isCatwalk && _tags.HasTag(ent, "Catwalk"))
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-occupied-tile-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (component.CachedPrototype.CollisionMask != CollisionGroup.None && TryComp<FixturesComponent>(ent, out var fixtures))
|
||||
{
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
{
|
||||
// Continue if no collision is possible
|
||||
if (fixture.CollisionLayer <= 0 || (fixture.CollisionLayer & (int) component.CachedPrototype.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))
|
||||
continue;
|
||||
|
||||
// Collision was detected
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-on-occupied-tile-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsDeconstructionStillValid(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user, bool popMsgs = true)
|
||||
{
|
||||
// Attempt to deconstruct a floor tile
|
||||
if (target == null)
|
||||
{
|
||||
// the turf is blocked
|
||||
if (IsTileBlocked(tile))
|
||||
// The tile is empty
|
||||
if (mapGridData.Tile.Tile.IsEmpty)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user);
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-nothing-to-deconstruct-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
// the turf can't be destroyed (planet probably)
|
||||
var tileDef = (ContentTileDefinition) _tileDefMan[tile.Tile.TypeId];
|
||||
|
||||
// The tile has a structure sitting on it
|
||||
if (_turf.IsTileBlocked(mapGridData.Tile, CollisionGroup.MobMask))
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The tile cannot be destroyed
|
||||
var tileDef = (ContentTileDefinition) _tileDefMan[mapGridData.Tile.Tile.TypeId];
|
||||
|
||||
if (tileDef.Indestructible)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-tile-indestructible-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//They tried to decon a non-turf but it's not in the whitelist
|
||||
else if (!_tag.HasTag(target.Value, "RCDDeconstructWhitelist"))
|
||||
|
||||
// Attempt to deconstruct an object
|
||||
else
|
||||
{
|
||||
// The object is not in the whitelist
|
||||
if (!TryComp<RCDDeconstructableComponent>(target, out var deconstructible) || !deconstructible.Deconstructable)
|
||||
{
|
||||
if (popMsgs)
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), uid, user);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
//Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code.
|
||||
case RcdMode.Walls:
|
||||
if (tile.Tile.IsEmpty)
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entity construction/deconstruction
|
||||
|
||||
private void FinalizeRCDOperation(EntityUid uid, RCDComponent component, MapGridData mapGridData, EntityUid? target, EntityUid user)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-wall-tile-not-empty-message"), uid, user);
|
||||
if (!_net.IsServer)
|
||||
return;
|
||||
|
||||
if (component.CachedPrototype.Prototype == null)
|
||||
return;
|
||||
|
||||
switch (component.CachedPrototype.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}");
|
||||
break;
|
||||
|
||||
case RcdMode.ConstructObject:
|
||||
var ent = Spawn(component.CachedPrototype.Prototype, _mapSystem.GridTileToLocal(mapGridData.GridUid, mapGridData.Component, mapGridData.Position));
|
||||
|
||||
switch (component.CachedPrototype.Rotation)
|
||||
{
|
||||
case RcdRotation.Fixed:
|
||||
Transform(ent).LocalRotation = Angle.Zero;
|
||||
break;
|
||||
case RcdRotation.Camera:
|
||||
Transform(ent).LocalRotation = Transform(uid).LocalRotation;
|
||||
break;
|
||||
case RcdRotation.User:
|
||||
Transform(ent).LocalRotation = component.ConstructionDirection.ToAngle();
|
||||
break;
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to spawn {ToPrettyString(ent)} at {mapGridData.Position} on grid {mapGridData.GridUid}");
|
||||
break;
|
||||
|
||||
case RcdMode.Deconstruct:
|
||||
|
||||
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");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Deconstruct object
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to delete {ToPrettyString(target):target}");
|
||||
QueueDel(target);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility functions
|
||||
|
||||
public bool TryGetMapGridData(EntityCoordinates location, [NotNullWhen(true)] out MapGridData? mapGridData)
|
||||
{
|
||||
mapGridData = null;
|
||||
var gridUid = location.GetGridUid(EntityManager);
|
||||
|
||||
if (!TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
||||
{
|
||||
location = location.AlignWithClosestGridTile(1.75f, EntityManager);
|
||||
gridUid = location.GetGridUid(EntityManager);
|
||||
|
||||
// Check if we got a grid ID the second time round
|
||||
if (!TryComp(gridUid, out mapGrid))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsTileBlocked(tile))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user);
|
||||
return false;
|
||||
}
|
||||
gridUid = mapGrid.Owner;
|
||||
|
||||
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;
|
||||
case RcdMode.Airlock:
|
||||
if (tile.Tile.IsEmpty)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-cannot-build-airlock-tile-not-empty-message"), uid, user);
|
||||
return false;
|
||||
}
|
||||
if (IsTileBlocked(tile))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("rcd-component-tile-obstructed-message"), uid, user);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
|
||||
}
|
||||
}
|
||||
|
||||
private void NextMode(EntityUid uid, RCDComponent comp, EntityUid user)
|
||||
private bool DoesCustomBoundsIntersectWithFixture(PolygonShape boundingPolygon, Transform boundingTransform, EntityUid fixtureOwner, Fixture fixture)
|
||||
{
|
||||
_audio.PlayPredicted(comp.SwapModeSound, uid, user);
|
||||
var entXformComp = Transform(fixtureOwner);
|
||||
var entXform = new Transform(new(), entXformComp.LocalRotation);
|
||||
|
||||
var mode = (int) comp.Mode;
|
||||
mode = ++mode % _rcdModeCount;
|
||||
comp.Mode = (RcdMode) mode;
|
||||
Dirty(uid, comp);
|
||||
|
||||
var msg = Loc.GetString("rcd-component-change-mode", ("mode", comp.Mode.ToString()));
|
||||
_popup.PopupClient(msg, uid, user);
|
||||
return boundingPolygon.ComputeAABB(boundingTransform, 0).Intersects(fixture.Shape.ComputeAABB(entXform, 0));
|
||||
}
|
||||
|
||||
private bool IsTileBlocked(TileRef tile)
|
||||
public void UpdateCachedPrototype(EntityUid uid, RCDComponent component)
|
||||
{
|
||||
return _turf.IsTileBlocked(tile, CollisionGroup.MobMask);
|
||||
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("location", required: true)]
|
||||
public NetCoordinates Location = default!;
|
||||
[DataField(required: true)]
|
||||
public NetCoordinates Location { get; private set; } = default!;
|
||||
|
||||
[DataField("startingMode", required: true)]
|
||||
public RcdMode StartingMode = default!;
|
||||
[DataField]
|
||||
public ProtoId<RCDPrototype> StartingProtoId { get; private set; } = default!;
|
||||
|
||||
private RCDDoAfterEvent()
|
||||
{
|
||||
}
|
||||
[DataField]
|
||||
public int Cost { get; private set; } = 1;
|
||||
|
||||
public RCDDoAfterEvent(NetCoordinates location, RcdMode startingMode)
|
||||
[DataField("fx")]
|
||||
public NetEntity? Effect { get; private set; } = null;
|
||||
|
||||
private RCDDoAfterEvent() { }
|
||||
|
||||
public RCDDoAfterEvent(NetCoordinates location, ProtoId<RCDPrototype> startingProtoId, int cost, NetEntity? effect = null)
|
||||
{
|
||||
Location = location;
|
||||
StartingMode = startingMode;
|
||||
StartingProtoId = startingProtoId;
|
||||
Cost = cost;
|
||||
Effect = effect;
|
||||
}
|
||||
|
||||
public override DoAfterEvent Clone() => this;
|
||||
|
||||
@@ -1,18 +1,66 @@
|
||||
|
||||
### UI
|
||||
|
||||
# Shown when an RCD is examined in details range
|
||||
rcd-component-examine-detail = It's currently on {$mode} mode.
|
||||
rcd-component-examine-mode-details = It's currently set to '{$mode}' mode.
|
||||
rcd-component-examine-build-details = It's currently set to build {MAKEPLURAL($name)}.
|
||||
|
||||
|
||||
### Interaction Messages
|
||||
|
||||
# Shown when changing RCD Mode
|
||||
rcd-component-change-mode = The RCD is now set to {$mode} mode.
|
||||
# Mode change
|
||||
rcd-component-change-mode = The RCD is now set to '{$mode}' mode.
|
||||
rcd-component-change-build-mode = The RCD is now set to build {MAKEPLURAL($name)}.
|
||||
|
||||
rcd-component-no-ammo-message = The RCD is out of ammo!
|
||||
rcd-component-tile-obstructed-message = That tile is obstructed!
|
||||
rcd-component-tile-indestructible-message = That tile can't be destroyed!
|
||||
# Ammo count
|
||||
rcd-component-no-ammo-message = The RCD has run out of charges!
|
||||
rcd-component-insufficient-ammo-message = The RCD doesn't have enough charges left!
|
||||
|
||||
# Deconstruction
|
||||
rcd-component-tile-indestructible-message = That tile can't be destructed!
|
||||
rcd-component-deconstruct-target-not-on-whitelist-message = You can't deconstruct that!
|
||||
rcd-component-cannot-build-floor-tile-not-empty-message = You can only build a floor on space!
|
||||
rcd-component-cannot-build-wall-tile-not-empty-message = You cannot build a wall on space!
|
||||
rcd-component-cannot-build-airlock-tile-not-empty-message = Cannot build an airlock on space!
|
||||
rcd-component-nothing-to-deconstruct-message = There's nothing to deconstruct!
|
||||
rcd-component-tile-obstructed-message = You can't deconstruct tiles when there's something on top of them!
|
||||
|
||||
# Construction
|
||||
rcd-component-no-valid-grid = You're too far into open space to build here!
|
||||
rcd-component-must-build-on-empty-tile-message = A foundation already exists here!
|
||||
rcd-component-cannot-build-on-empty-tile-message = You can't build that without a foundation!
|
||||
rcd-component-must-build-on-subfloor-message = You can only build that on exposed subfloor!
|
||||
rcd-component-cannot-build-on-subfloor-message = You can't build that on exposed subfloor!
|
||||
rcd-component-cannot-build-on-occupied-tile-message = You can't build here, the space is already occupied!
|
||||
rcd-component-cannot-build-identical-tile = That tile already exists there!
|
||||
|
||||
|
||||
### Category names
|
||||
|
||||
rcd-component-walls-and-flooring = Walls and flooring
|
||||
rcd-component-windows-and-grilles = Windows and grilles
|
||||
rcd-component-airlocks = Airlocks
|
||||
rcd-component-electrical = Electrical
|
||||
rcd-component-lighting = Lighting
|
||||
|
||||
|
||||
### Prototype names (note: constructable items will be puralized)
|
||||
|
||||
rcd-component-deconstruct = deconstruct
|
||||
rcd-component-wall-solid = solid wall
|
||||
rcd-component-floor-steel = steel tile
|
||||
rcd-component-plating = hull plate
|
||||
rcd-component-catwalk = catwalk
|
||||
rcd-component-wall-reinforced = reinforced wall
|
||||
rcd-component-grille = grille
|
||||
rcd-component-window = window
|
||||
rcd-component-window-directional = directional window
|
||||
rcd-component-window-reinforced-directional = directional reinforced window
|
||||
rcd-component-reinforced-window = reinforced window
|
||||
rcd-component-airlock = standard airlock
|
||||
rcd-component-airlock-glass = glass airlock
|
||||
rcd-component-firelock = firelock
|
||||
rcd-component-computer-frame = computer frame
|
||||
rcd-component-machine-frame = machine frame
|
||||
rcd-component-tube-light = light
|
||||
rcd-component-window-bulb-light = small light
|
||||
rcd-component-window-lv-cable = LV cable
|
||||
rcd-component-window-mv-cable = MV cable
|
||||
rcd-component-window-hv-cable = HV cable
|
||||
rcd-component-window-cable-terminal = cable terminal
|
||||
|
||||
3
Resources/Locale/en-US/ui/general.ftl
Normal file
@@ -0,0 +1,3 @@
|
||||
### Loc for the various UI-related verbs
|
||||
ui-verb-toggle-open = Toggle UI
|
||||
verb-instrument-openui = Play Music
|
||||
@@ -166,8 +166,8 @@
|
||||
- type: entity
|
||||
id: CrateRCDAmmo
|
||||
parent: CrateEngineering
|
||||
name: RCD ammo crate
|
||||
description: 3 RCD ammo, each restoring 5 charges.
|
||||
name: compressed matter crate
|
||||
description: Contains three compressed matter cartridges.
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
@@ -178,7 +178,7 @@
|
||||
id: CrateRCD
|
||||
parent: CrateEngineeringSecure
|
||||
name: RCD crate
|
||||
description: A crate containing a single Rapid Construction Device.
|
||||
description: A crate containing a single rapid construction device.
|
||||
components:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
|
||||
@@ -128,9 +128,10 @@
|
||||
snap:
|
||||
- Wall
|
||||
components:
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
|
||||
@@ -1,16 +1,115 @@
|
||||
- type: entity
|
||||
id: EffectRCDConstruction
|
||||
id: EffectRCDBase
|
||||
abstract: true
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Transform
|
||||
anchored: True
|
||||
- type: Sprite
|
||||
snapCardinals: true
|
||||
noRot: true
|
||||
drawdepth: Effects
|
||||
sprite: /Textures/Effects/rcd.rsi
|
||||
state: construct
|
||||
- type: TimedDespawn
|
||||
lifetime: 3.2
|
||||
state: construct0
|
||||
- type: Tag
|
||||
tags:
|
||||
- HideContextMenu
|
||||
- type: AnimationPlayer
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDDeconstructPreview
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: deconstructPreview
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDConstruct0
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: construct0
|
||||
- type: TimedDespawn
|
||||
lifetime: 1.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDConstruct1
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: construct1
|
||||
- type: TimedDespawn
|
||||
lifetime: 2.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDConstruct2
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: construct2
|
||||
- type: TimedDespawn
|
||||
lifetime: 3.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDConstruct3
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: construct3
|
||||
- type: TimedDespawn
|
||||
lifetime: 4.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDConstruct4
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: construct4
|
||||
- type: TimedDespawn
|
||||
lifetime: 5.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDDeconstruct2
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: deconstruct2
|
||||
- type: TimedDespawn
|
||||
lifetime: 3.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDDeconstruct4
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: deconstruct4
|
||||
- type: TimedDespawn
|
||||
lifetime: 5.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDDeconstruct6
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: deconstruct6
|
||||
- type: TimedDespawn
|
||||
lifetime: 7.2
|
||||
|
||||
- type: entity
|
||||
parent: EffectRCDBase
|
||||
id: EffectRCDDeconstruct8
|
||||
noSpawn: true
|
||||
components:
|
||||
- type: Sprite
|
||||
state: deconstruct8
|
||||
- type: TimedDespawn
|
||||
lifetime: 9.2
|
||||
@@ -337,15 +337,35 @@
|
||||
path: "/Audio/Items/drill_hit.ogg"
|
||||
|
||||
- type: entity
|
||||
name: RCD
|
||||
parent: BaseItem
|
||||
id: RCD
|
||||
description: An advanced construction device which can place/remove walls, floors, and airlocks quickly.
|
||||
parent: BaseItem
|
||||
name: RCD
|
||||
description: The rapid construction device can be used to quickly place and remove various station structures and fixtures. Requires compressed matter to function.
|
||||
components:
|
||||
- type: RCD
|
||||
availablePrototypes:
|
||||
- WallSolid
|
||||
- FloorSteel
|
||||
- Plating
|
||||
- Catwalk
|
||||
- Grille
|
||||
- Window
|
||||
- WindowDirectional
|
||||
- WindowReinforcedDirectional
|
||||
- ReinforcedWindow
|
||||
- Airlock
|
||||
- AirlockGlass
|
||||
- Firelock
|
||||
- TubeLight
|
||||
- BulbLight
|
||||
- LVCable
|
||||
- MVCable
|
||||
- HVCable
|
||||
- CableTerminal
|
||||
- Deconstruct
|
||||
- type: LimitedCharges
|
||||
maxCharges: 5
|
||||
charges: 5
|
||||
maxCharges: 30
|
||||
charges: 30
|
||||
- type: UseDelay
|
||||
- type: Sprite
|
||||
sprite: Objects/Tools/rcd.rsi
|
||||
@@ -363,6 +383,12 @@
|
||||
Plastic: 100
|
||||
- type: StaticPrice
|
||||
price: 100
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.RcdUiKey.Key
|
||||
type: RCDMenuBoundUserInterface
|
||||
- type: ActivatableUI
|
||||
key: enum.RcdUiKey.Key
|
||||
|
||||
- type: entity
|
||||
id: RCDEmpty
|
||||
@@ -370,37 +396,50 @@
|
||||
suffix: Empty
|
||||
components:
|
||||
- type: LimitedCharges
|
||||
maxCharges: 5
|
||||
charges: 0
|
||||
- type: RCD
|
||||
availablePrototypes:
|
||||
- WallSolid
|
||||
- FloorSteel
|
||||
- Plating
|
||||
- Catwalk
|
||||
- Grille
|
||||
- Window
|
||||
- WindowDirectional
|
||||
- WindowReinforcedDirectional
|
||||
- ReinforcedWindow
|
||||
- Airlock
|
||||
- AirlockGlass
|
||||
- Firelock
|
||||
|
||||
- type: entity
|
||||
id: RCDRecharging
|
||||
parent: RCD
|
||||
name: experimental rcd
|
||||
description: A bluespace-enhanced RCD that regenerates charges passively.
|
||||
name: experimental RCD
|
||||
description: A bluespace-enhanced rapid construction device that passively generates its own compressed matter.
|
||||
suffix: AutoRecharge
|
||||
components:
|
||||
- type: LimitedCharges
|
||||
maxCharges: 3
|
||||
charges: 3
|
||||
maxCharges: 20
|
||||
charges: 20
|
||||
- type: AutoRecharge
|
||||
rechargeDuration: 30
|
||||
rechargeDuration: 10
|
||||
|
||||
- type: entity
|
||||
id: RCDExperimental
|
||||
parent: RCD
|
||||
suffix: Admeme
|
||||
name: experimental rcd
|
||||
description: A bluespace-enhanced RCD that regenerates charges passively.
|
||||
name: experimental RCD
|
||||
description: A bluespace-enhanced rapid construction device that passively generates its own compressed matter.
|
||||
components:
|
||||
- type: AutoRecharge
|
||||
rechargeDuration: 5
|
||||
rechargeDuration: 1
|
||||
|
||||
- type: entity
|
||||
name: RCD Ammo
|
||||
name: compressed matter
|
||||
parent: BaseItem
|
||||
id: RCDAmmo
|
||||
description: Ammo cartridge for an RCD.
|
||||
description: A cartridge of raw matter compacted by bluespace technology. Used in rapid construction devices.
|
||||
components:
|
||||
- type: RCDAmmo
|
||||
- type: Sprite
|
||||
|
||||
@@ -138,48 +138,6 @@
|
||||
sprite: Structures/Doors/Airlocks/Standard/hatch_maint.rsi
|
||||
|
||||
# Glass
|
||||
|
||||
- type: entity
|
||||
id: AirlockGlass
|
||||
parent: Airlock
|
||||
name: glass airlock
|
||||
components:
|
||||
- type: MeleeSound
|
||||
soundGroups:
|
||||
Brute:
|
||||
collection: GlassSmack
|
||||
- type: Door
|
||||
occludes: false
|
||||
- type: Occluder
|
||||
enabled: false
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/glass.rsi
|
||||
- type: AnimationPlayer
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close
|
||||
density: 100
|
||||
mask:
|
||||
- FullTileMask
|
||||
layer: #removed opaque from the layer, allowing lasers to pass through glass airlocks
|
||||
- GlassAirlockLayer
|
||||
- type: LayerChangeOnWeld
|
||||
unWeldedLayer: GlassAirlockLayer
|
||||
weldedLayer: GlassLayer
|
||||
- type: Construction
|
||||
graph: Airlock
|
||||
node: glassAirlock
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
- type: RadiationBlocker
|
||||
resistance: 2
|
||||
- type: Tag
|
||||
tags:
|
||||
- GlassAirlock
|
||||
# This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor
|
||||
- type: entity
|
||||
parent: AirlockGlass
|
||||
id: AirlockEngineeringGlass
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -111,6 +111,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: StructuralInorganic
|
||||
damageModifierSet: StrongMetallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -150,4 +154,52 @@
|
||||
placement:
|
||||
mode: SnapgridCenter
|
||||
|
||||
- type: entity
|
||||
id: AirlockRCDResistant
|
||||
parent: Airlock
|
||||
abstract: true
|
||||
components:
|
||||
- type: RCDDeconstructable
|
||||
deconstructable: false
|
||||
|
||||
- type: entity
|
||||
id: AirlockGlass
|
||||
parent: Airlock
|
||||
name: glass airlock
|
||||
components:
|
||||
- type: MeleeSound
|
||||
soundGroups:
|
||||
Brute:
|
||||
collection: GlassSmack
|
||||
- type: Door
|
||||
occludes: false
|
||||
- type: Occluder
|
||||
enabled: false
|
||||
- type: Sprite
|
||||
sprite: Structures/Doors/Airlocks/Glass/glass.rsi
|
||||
- type: AnimationPlayer
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.49,-0.49,0.49,0.49" # don't want this colliding with walls or they won't close
|
||||
density: 100
|
||||
mask:
|
||||
- FullTileMask
|
||||
layer: #removed opaque from the layer, allowing lasers to pass through glass airlocks
|
||||
- GlassAirlockLayer
|
||||
- type: LayerChangeOnWeld
|
||||
unWeldedLayer: GlassAirlockLayer
|
||||
weldedLayer: GlassLayer
|
||||
- type: Construction
|
||||
graph: Airlock
|
||||
node: glassAirlock
|
||||
- type: PaintableAirlock
|
||||
group: Glass
|
||||
- type: RadiationBlocker
|
||||
resistance: 2
|
||||
- type: Tag
|
||||
tags:
|
||||
- GlassAirlock
|
||||
# This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor
|
||||
@@ -1,5 +1,5 @@
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockRCDResistant
|
||||
id: AirlockExternal
|
||||
suffix: External
|
||||
description: It opens, it closes, it might crush you, and there might be only space behind it.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- type: entity
|
||||
parent: Airlock
|
||||
parent: AirlockRCDResistant
|
||||
id: AirlockShuttle
|
||||
suffix: Docking
|
||||
name: external airlock
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 4
|
||||
delay: 6
|
||||
fx: EffectRCDDeconstruct6
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 4
|
||||
delay: 6
|
||||
fx: EffectRCDDeconstruct6
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 6
|
||||
fx: EffectRCDDeconstruct6
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -97,6 +101,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
damageModifierSet: Glass
|
||||
- type: ExaminableDamage
|
||||
messages: WindowMessages
|
||||
- type: RCDDeconstructable
|
||||
cost: 8
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 4
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Metallic
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
lowVoltageNode: power
|
||||
- type: CableVis
|
||||
node: power
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
- type: entity
|
||||
parent: CableBase
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
Brute:
|
||||
path:
|
||||
"/Audio/Weapons/grille_hit.ogg"
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/fence.rsi
|
||||
drawdepth: WallTops
|
||||
@@ -78,6 +75,10 @@
|
||||
True: { visible: True }
|
||||
False: { visible: False }
|
||||
- type: AnimationPlayer
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
- type: entity
|
||||
parent: BaseFenceMetal
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"/Audio/Weapons/boxingpunch1.ogg"
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- Wooden
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/wooden_fence.rsi
|
||||
@@ -24,6 +23,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: Inorganic
|
||||
damageModifierSet: Wood
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
Brute:
|
||||
path:
|
||||
"/Audio/Weapons/grille_hit.ogg"
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 4
|
||||
fx: EffectRCDDeconstruct4
|
||||
- type: CanBuildWindowOnTop
|
||||
- type: Sprite
|
||||
drawdepth: Walls
|
||||
@@ -120,9 +121,10 @@
|
||||
- type: Icon
|
||||
sprite: Structures/Walls/grille.rsi
|
||||
state: grille_broken
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 4
|
||||
fx: EffectRCDDeconstruct4
|
||||
- type: Construction
|
||||
graph: Grille
|
||||
node: grilleBroken
|
||||
|
||||
@@ -57,6 +57,10 @@
|
||||
- type: Construction
|
||||
graph: Railing
|
||||
node: railing
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
- type: entity
|
||||
parent: BaseStructure
|
||||
@@ -126,6 +130,10 @@
|
||||
- type: Construction
|
||||
graph: Railing
|
||||
node: railingCorner
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
- type: entity
|
||||
parent: BaseStructure
|
||||
@@ -186,6 +194,10 @@
|
||||
- type: Construction
|
||||
graph: Railing
|
||||
node: railingCornerSmall
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
- type: entity
|
||||
parent: BaseStructure
|
||||
@@ -261,3 +273,7 @@
|
||||
- type: Construction
|
||||
graph: Railing
|
||||
node: railingRound
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
@@ -59,11 +59,14 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/brick.rsi
|
||||
- type: Icon
|
||||
sprite: Structures/Walls/brick.rsi
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -90,7 +93,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/clock.rsi
|
||||
- type: Icon
|
||||
@@ -123,7 +125,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/clown.rsi
|
||||
- type: Icon
|
||||
@@ -131,6 +132,10 @@
|
||||
- type: Construction
|
||||
graph: Girder
|
||||
node: bananiumWall
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -156,7 +161,6 @@
|
||||
components:
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- Wall
|
||||
- Structure
|
||||
- type: Sprite
|
||||
@@ -190,7 +194,6 @@
|
||||
components:
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- Wall
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/cult.rsi
|
||||
@@ -223,7 +226,6 @@
|
||||
tags:
|
||||
- Wall
|
||||
- Debug
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/debug.rsi
|
||||
- type: Icon
|
||||
@@ -253,11 +255,14 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/diamond.rsi
|
||||
- type: Icon
|
||||
sprite: Structures/Walls/diamond.rsi
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -283,7 +288,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/gold.rsi
|
||||
- type: Icon
|
||||
@@ -291,6 +295,10 @@
|
||||
- type: Construction
|
||||
graph: Girder
|
||||
node: goldWall
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -325,7 +333,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/ice.rsi
|
||||
- type: Icon
|
||||
@@ -355,7 +362,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/plasma.rsi
|
||||
- type: Icon
|
||||
@@ -363,6 +369,10 @@
|
||||
- type: Construction
|
||||
graph: Girder
|
||||
node: plasmaWall
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -399,7 +409,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/plastic.rsi
|
||||
- type: Icon
|
||||
@@ -407,6 +416,10 @@
|
||||
- type: Construction
|
||||
graph: Girder
|
||||
node: plasticWall
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -459,7 +472,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -604,7 +616,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/riveted.rsi
|
||||
- type: Icon
|
||||
@@ -639,11 +650,14 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/sandstone.rsi
|
||||
- type: Icon
|
||||
sprite: Structures/Walls/sandstone.rsi
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -669,7 +683,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/silver.rsi
|
||||
- type: Icon
|
||||
@@ -677,6 +690,10 @@
|
||||
- type: Construction
|
||||
graph: Girder
|
||||
node: silverWall
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -842,7 +859,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/solid.rsi
|
||||
- type: WallReplacementMarker
|
||||
@@ -851,6 +867,10 @@
|
||||
node: wall
|
||||
- type: Icon
|
||||
sprite: Structures/Walls/solid.rsi
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -1012,7 +1032,6 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/web.rsi
|
||||
- type: Icon
|
||||
@@ -1224,11 +1243,14 @@
|
||||
- type: Tag
|
||||
tags:
|
||||
- Wall
|
||||
- RCDDeconstructWhitelist
|
||||
- type: Sprite
|
||||
sprite: Structures/Walls/cobblebrick.rsi
|
||||
- type: Icon
|
||||
sprite: Structures/Walls/cobblebrick.rsi
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 8
|
||||
fx: EffectRCDDeconstruct8
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: MiningWindow
|
||||
name: mining window
|
||||
parent: Window
|
||||
parent: WindowRCDResistant
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: WallTops
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: PlasmaWindow
|
||||
name: plasma window
|
||||
parent: Window
|
||||
parent: WindowRCDResistant
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: WallTops
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
- type: entity
|
||||
id: PlasmaWindowDirectional
|
||||
parent: WindowDirectional
|
||||
parent: WindowDirectionalRCDResistant
|
||||
name: directional plasma window
|
||||
description: Don't smudge up the glass down there.
|
||||
placement:
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
- type: Damageable
|
||||
damageContainer: StructuralInorganic
|
||||
damageModifierSet: RGlass
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 6
|
||||
fx: EffectRCDDeconstruct6
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -99,6 +103,10 @@
|
||||
sprite: Structures/Windows/cracks_directional.rsi
|
||||
- type: Damageable
|
||||
damageModifierSet: RGlass
|
||||
- type: RCDDeconstructable
|
||||
cost: 4
|
||||
delay: 4
|
||||
fx: EffectRCDDeconstruct4
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: ReinforcedPlasmaWindow
|
||||
name: reinforced plasma window
|
||||
parent: Window
|
||||
parent: WindowRCDResistant
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: WallTops
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
- type: entity
|
||||
id: PlasmaReinforcedWindowDirectional
|
||||
parent: WindowDirectional
|
||||
parent: WindowDirectionalRCDResistant
|
||||
name: directional reinforced plasma window
|
||||
description: Don't smudge up the glass down there.
|
||||
placement:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: ReinforcedUraniumWindow
|
||||
name: reinforced uranium window
|
||||
parent: Window
|
||||
parent: WindowRCDResistant
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: WallTops
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: ShuttleWindow
|
||||
name: shuttle window
|
||||
parent: Window
|
||||
parent: WindowRCDResistant
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: WallTops
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: UraniumWindow
|
||||
name: uranium window
|
||||
parent: Window
|
||||
parent: WindowRCDResistant
|
||||
components:
|
||||
- type: Sprite
|
||||
drawdepth: WallTops
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
arc: 360 # interact despite grilles
|
||||
- type: Tag
|
||||
tags:
|
||||
- RCDDeconstructWhitelist
|
||||
- ForceFixRotations
|
||||
- Window
|
||||
- type: Sprite
|
||||
@@ -42,6 +41,10 @@
|
||||
- type: ExaminableDamage
|
||||
messages: WindowMessages
|
||||
- type: Repairable
|
||||
- type: RCDDeconstructable
|
||||
cost: 6
|
||||
delay: 4
|
||||
fx: EffectRCDDeconstruct4
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -90,6 +93,14 @@
|
||||
price: 100
|
||||
- type: BlockWeather
|
||||
|
||||
- type: entity
|
||||
id: WindowRCDResistant
|
||||
parent: Window
|
||||
abstract: true
|
||||
components:
|
||||
- type: RCDDeconstructable
|
||||
deconstructable: false
|
||||
|
||||
- type: entity
|
||||
id: WindowDirectional
|
||||
parent: BaseStructure
|
||||
@@ -139,6 +150,10 @@
|
||||
damageModifierSet: Glass
|
||||
- type: ExaminableDamage
|
||||
messages: WindowMessages
|
||||
- type: RCDDeconstructable
|
||||
cost: 4
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
@@ -181,6 +196,14 @@
|
||||
- type: StaticPrice
|
||||
price: 10
|
||||
|
||||
- type: entity
|
||||
id: WindowDirectionalRCDResistant
|
||||
parent: WindowDirectional
|
||||
abstract: true
|
||||
components:
|
||||
- type: RCDDeconstructable
|
||||
deconstructable: false
|
||||
|
||||
- type: entity
|
||||
id: WindowFrostedDirectional
|
||||
parent: WindowDirectional
|
||||
|
||||
@@ -51,3 +51,7 @@
|
||||
max: 1
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Destruction" ]
|
||||
- type: RCDDeconstructable
|
||||
cost: 2
|
||||
delay: 2
|
||||
fx: EffectRCDDeconstruct2
|
||||
294
Resources/Prototypes/RCD/rcd.yml
Normal file
@@ -0,0 +1,294 @@
|
||||
# Operations
|
||||
- type: rcd
|
||||
id: Invalid # Hidden prototype - do not add to RCDs
|
||||
mode: Invalid
|
||||
|
||||
- type: rcd
|
||||
id: Deconstruct
|
||||
name: rcd-component-deconstruct
|
||||
category: Main
|
||||
sprite: /Textures/Interface/Radial/RCD/deconstruct.png
|
||||
mode: Deconstruct
|
||||
prototype: EffectRCDDeconstructPreview
|
||||
rotation: Camera
|
||||
|
||||
- type: rcd
|
||||
id: DeconstructLattice # Hidden prototype - do not add to RCDs
|
||||
mode: Deconstruct
|
||||
cost: 2
|
||||
delay: 1
|
||||
rotation: Camera
|
||||
fx: EffectRCDDeconstruct2
|
||||
|
||||
- type: rcd
|
||||
id: DeconstructTile # Hidden prototype - do not add to RCDs
|
||||
mode: Deconstruct
|
||||
cost: 4
|
||||
delay: 4
|
||||
rotation: Camera
|
||||
fx: EffectRCDDeconstruct4
|
||||
|
||||
# Flooring
|
||||
- type: rcd
|
||||
id: Plating
|
||||
name: rcd-component-plating
|
||||
category: WallsAndFlooring
|
||||
sprite: /Textures/Interface/Radial/RCD/plating.png
|
||||
mode: ConstructTile
|
||||
prototype: Plating
|
||||
cost: 1
|
||||
delay: 1
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- CanBuildOnEmptyTile
|
||||
fx: EffectRCDConstruct1
|
||||
|
||||
- type: rcd
|
||||
id: FloorSteel
|
||||
name: rcd-component-floor-steel
|
||||
category: WallsAndFlooring
|
||||
sprite: /Textures/Interface/Radial/RCD/metal_tile.png
|
||||
mode: ConstructTile
|
||||
prototype: FloorSteel
|
||||
cost: 1
|
||||
delay: 1
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- CanBuildOnEmptyTile
|
||||
fx: EffectRCDConstruct1
|
||||
|
||||
- type: rcd
|
||||
id: Catwalk
|
||||
name: rcd-component-catwalk
|
||||
category: WallsAndFlooring
|
||||
sprite: /Textures/Interface/Radial/RCD/catwalk.png
|
||||
mode: ConstructObject
|
||||
prototype: Catwalk
|
||||
cost: 1
|
||||
delay: 1
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- MustBuildOnSubfloor
|
||||
- IsCatwalk
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct1
|
||||
|
||||
# Walls
|
||||
- type: rcd
|
||||
id: WallSolid
|
||||
name: rcd-component-wall-solid
|
||||
category: WallsAndFlooring
|
||||
sprite: /Textures/Interface/Radial/RCD/solid_wall.png
|
||||
mode: ConstructObject
|
||||
prototype: WallSolid
|
||||
cost: 4
|
||||
delay: 2
|
||||
collisionMask: FullTileMask
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct2
|
||||
|
||||
- type: rcd
|
||||
id: Grille
|
||||
name: rcd-component-grille
|
||||
category: WindowsAndGrilles
|
||||
sprite: /Textures/Interface/Radial/RCD/grille.png
|
||||
mode: ConstructObject
|
||||
prototype: Grille
|
||||
cost: 4
|
||||
delay: 2
|
||||
collisionMask: FullTileMask
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct2
|
||||
|
||||
# Windows
|
||||
- type: rcd
|
||||
id: Window
|
||||
name: rcd-component-window
|
||||
category: WindowsAndGrilles
|
||||
sprite: /Textures/Interface/Radial/RCD/window.png
|
||||
mode: ConstructObject
|
||||
prototype: Window
|
||||
cost: 3
|
||||
delay: 2
|
||||
collisionMask: FullTileMask
|
||||
rules:
|
||||
- IsWindow
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct2
|
||||
|
||||
- type: rcd
|
||||
id: WindowDirectional
|
||||
name: rcd-component-window-directional
|
||||
category: WindowsAndGrilles
|
||||
sprite: /Textures/Interface/Radial/RCD/directional.png
|
||||
mode: ConstructObject
|
||||
prototype: WindowDirectional
|
||||
cost: 2
|
||||
delay: 1
|
||||
collisionMask: FullTileMask
|
||||
collisionBounds: "-0.23,-0.49,0.23,-0.36"
|
||||
rules:
|
||||
- IsWindow
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct1
|
||||
|
||||
- type: rcd
|
||||
id: ReinforcedWindow
|
||||
name: rcd-component-reinforced-window
|
||||
category: WindowsAndGrilles
|
||||
sprite: /Textures/Interface/Radial/RCD/window_reinforced.png
|
||||
mode: ConstructObject
|
||||
prototype: ReinforcedWindow
|
||||
cost: 4
|
||||
delay: 3
|
||||
collisionMask: FullTileMask
|
||||
rules:
|
||||
- IsWindow
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct3
|
||||
|
||||
- type: rcd
|
||||
id: WindowReinforcedDirectional
|
||||
name: rcd-component-window-reinforced-directional
|
||||
category: WindowsAndGrilles
|
||||
sprite: /Textures/Interface/Radial/RCD/directional_reinforced.png
|
||||
mode: ConstructObject
|
||||
prototype: WindowReinforcedDirectional
|
||||
cost: 3
|
||||
delay: 2
|
||||
collisionMask: FullTileMask
|
||||
collisionBounds: "-0.23,-0.49,0.23,-0.36"
|
||||
rules:
|
||||
- IsWindow
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct2
|
||||
|
||||
# Airlocks
|
||||
- type: rcd
|
||||
id: Airlock
|
||||
name: rcd-component-airlock
|
||||
category: Airlocks
|
||||
sprite: /Textures/Interface/Radial/RCD/airlock.png
|
||||
mode: ConstructObject
|
||||
prototype: Airlock
|
||||
cost: 4
|
||||
delay: 4
|
||||
collisionMask: FullTileMask
|
||||
rotation: Camera
|
||||
fx: EffectRCDConstruct4
|
||||
|
||||
- type: rcd
|
||||
id: AirlockGlass
|
||||
name: rcd-component-airlock-glass
|
||||
category: Airlocks
|
||||
sprite: /Textures/Interface/Radial/RCD/glass_airlock.png
|
||||
mode: ConstructObject
|
||||
prototype: AirlockGlass
|
||||
cost: 4
|
||||
delay: 4
|
||||
collisionMask: FullTileMask
|
||||
rotation: Camera
|
||||
fx: EffectRCDConstruct4
|
||||
|
||||
- type: rcd
|
||||
id: Firelock
|
||||
name: rcd-component-firelock
|
||||
category: Airlocks
|
||||
sprite: /Textures/Interface/Radial/RCD/firelock.png
|
||||
mode: ConstructObject
|
||||
prototype: Firelock
|
||||
cost: 4
|
||||
delay: 3
|
||||
collisionMask: FullTileMask
|
||||
rotation: Camera
|
||||
fx: EffectRCDConstruct3
|
||||
|
||||
# Lighting
|
||||
- type: rcd
|
||||
id: TubeLight
|
||||
name: rcd-component-tube-light
|
||||
category: Lighting
|
||||
sprite: /Textures/Interface/Radial/RCD/tube_light.png
|
||||
mode: ConstructObject
|
||||
prototype: Poweredlight
|
||||
cost: 2
|
||||
delay: 1
|
||||
collisionMask: TabletopMachineMask
|
||||
collisionBounds: "-0.23,-0.49,0.23,-0.36"
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct1
|
||||
|
||||
- type: rcd
|
||||
id: BulbLight
|
||||
name: rcd-component-window-bulb-light
|
||||
category: Lighting
|
||||
sprite: /Textures/Interface/Radial/RCD/bulb_light.png
|
||||
mode: ConstructObject
|
||||
prototype: PoweredSmallLight
|
||||
cost: 2
|
||||
delay: 1
|
||||
collisionMask: TabletopMachineMask
|
||||
collisionBounds: "-0.23,-0.49,0.23,-0.36"
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct1
|
||||
|
||||
# Electrical
|
||||
- type: rcd
|
||||
id: LVCable
|
||||
name: rcd-component-window-lv-cable
|
||||
category: Electrical
|
||||
sprite: /Textures/Interface/Radial/RCD/lv_coil.png
|
||||
mode: ConstructObject
|
||||
prototype: CableApcExtension
|
||||
cost: 1
|
||||
delay: 0
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- MustBuildOnSubfloor
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: MVCable
|
||||
name: rcd-component-window-mv-cable
|
||||
category: Electrical
|
||||
sprite: /Textures/Interface/Radial/RCD/mv_coil.png
|
||||
mode: ConstructObject
|
||||
prototype: CableMV
|
||||
cost: 1
|
||||
delay: 0
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- MustBuildOnSubfloor
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: HVCable
|
||||
name: rcd-component-window-hv-cable
|
||||
category: Electrical
|
||||
sprite: /Textures/Interface/Radial/RCD/hv_coil.png
|
||||
mode: ConstructObject
|
||||
prototype: CableHV
|
||||
cost: 1
|
||||
delay: 0
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- MustBuildOnSubfloor
|
||||
rotation: Fixed
|
||||
fx: EffectRCDConstruct0
|
||||
|
||||
- type: rcd
|
||||
id: CableTerminal
|
||||
name: rcd-component-window-cable-terminal
|
||||
category: Electrical
|
||||
sprite: /Textures/Interface/Radial/RCD/cable_terminal.png
|
||||
mode: ConstructObject
|
||||
prototype: CableTerminal
|
||||
cost: 1
|
||||
delay: 0
|
||||
collisionMask: InteractImpassable
|
||||
rules:
|
||||
- MustBuildOnSubfloor
|
||||
rotation: User
|
||||
fx: EffectRCDConstruct0
|
||||
@@ -1034,9 +1034,6 @@
|
||||
- type: Tag
|
||||
id: RawMaterial
|
||||
|
||||
- type: Tag
|
||||
id: RCDDeconstructWhitelist
|
||||
|
||||
# Give this to something that doesn't need any special recycler behavior and just needs deleting.
|
||||
- type: Tag
|
||||
id: Recyclable
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/construct0.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/construct1.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/construct2.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/construct3.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/construct4.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/deconstruct2.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/deconstruct4.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/deconstruct6.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/deconstruct8.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Resources/Textures/Effects/rcd.rsi/deconstructPreview.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
@@ -8,7 +8,65 @@
|
||||
"copyright": "Taken from tgStation at commit https://github.com/tgstation/tgstation/commit/d75cbd0a2900fdec4c12cd5ba986b52ccff03713/icons/effects/effects_rcd.dmi",
|
||||
"states": [
|
||||
{
|
||||
"name": "construct",
|
||||
"name": "construct0",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "construct1",
|
||||
"delays": [
|
||||
[
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "construct2",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
@@ -45,6 +103,357 @@
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "construct3",
|
||||
"delays": [
|
||||
[
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "construct4",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deconstruct2",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deconstruct4",
|
||||
"delays": [
|
||||
[
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deconstruct6",
|
||||
"delays": [
|
||||
[
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.15,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deconstruct8",
|
||||
"delays": [
|
||||
[
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.2,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1,
|
||||
0.1
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deconstructPreview",
|
||||
"delays": [
|
||||
[
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05,
|
||||
0.05
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
Resources/Textures/Interface/Radial/RCD/airlock.png
Normal file
|
After Width: | Height: | Size: 710 B |
BIN
Resources/Textures/Interface/Radial/RCD/airlocks.png
Normal file
|
After Width: | Height: | Size: 776 B |
BIN
Resources/Textures/Interface/Radial/RCD/bulb_light.png
Normal file
|
After Width: | Height: | Size: 283 B |
BIN
Resources/Textures/Interface/Radial/RCD/cable_terminal.png
Normal file
|
After Width: | Height: | Size: 646 B |
BIN
Resources/Textures/Interface/Radial/RCD/catwalk.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
Resources/Textures/Interface/Radial/RCD/computer_frame.png
Normal file
|
After Width: | Height: | Size: 467 B |
BIN
Resources/Textures/Interface/Radial/RCD/computers_and_frames.png
Normal file
|
After Width: | Height: | Size: 775 B |
BIN
Resources/Textures/Interface/Radial/RCD/deconstruct.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Resources/Textures/Interface/Radial/RCD/directional.png
Normal file
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 296 B |
BIN
Resources/Textures/Interface/Radial/RCD/firelock.png
Normal file
|
After Width: | Height: | Size: 637 B |
BIN
Resources/Textures/Interface/Radial/RCD/glass_airlock.png
Normal file
|
After Width: | Height: | Size: 698 B |
BIN
Resources/Textures/Interface/Radial/RCD/grille.png
Normal file
|
After Width: | Height: | Size: 223 B |
BIN
Resources/Textures/Interface/Radial/RCD/hv_coil.png
Normal file
|
After Width: | Height: | Size: 856 B |
BIN
Resources/Textures/Interface/Radial/RCD/lighting.png
Normal file
|
After Width: | Height: | Size: 901 B |
BIN
Resources/Textures/Interface/Radial/RCD/lv_coil.png
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
Resources/Textures/Interface/Radial/RCD/machine_frame.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
Resources/Textures/Interface/Radial/RCD/metal_tile.png
Normal file
|
After Width: | Height: | Size: 297 B |
BIN
Resources/Textures/Interface/Radial/RCD/multicoil.png
Normal file
|
After Width: | Height: | Size: 976 B |
BIN
Resources/Textures/Interface/Radial/RCD/mv_coil.png
Normal file
|
After Width: | Height: | Size: 833 B |
BIN
Resources/Textures/Interface/Radial/RCD/plating.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
Resources/Textures/Interface/Radial/RCD/reinforced_wall.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Resources/Textures/Interface/Radial/RCD/solid_wall.png
Normal file
|
After Width: | Height: | Size: 448 B |
BIN
Resources/Textures/Interface/Radial/RCD/tube_light.png
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
Resources/Textures/Interface/Radial/RCD/walls_and_flooring.png
Normal file
|
After Width: | Height: | Size: 431 B |
BIN
Resources/Textures/Interface/Radial/RCD/window.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
Resources/Textures/Interface/Radial/RCD/window_reinforced.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Resources/Textures/Interface/Radial/RCD/windows_and_grilles.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Resources/Textures/Interface/Radial/back_hover.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
Resources/Textures/Interface/Radial/back_normal.png
Normal file
|
After Width: | Height: | Size: 525 B |
BIN
Resources/Textures/Interface/Radial/button_hover.png
Normal file
|
After Width: | Height: | Size: 211 B |
BIN
Resources/Textures/Interface/Radial/button_normal.png
Normal file
|
After Width: | Height: | Size: 186 B |
BIN
Resources/Textures/Interface/Radial/close_hover.png
Normal file
|
After Width: | Height: | Size: 389 B |
BIN
Resources/Textures/Interface/Radial/close_normal.png
Normal file
|
After Width: | Height: | Size: 391 B |