diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 8fe2c5af68..99577a0199 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -601,6 +601,7 @@ namespace Content.Client.Actions fillEvent.Action.ClientExclusive = true; fillEvent.Action.Temporary = true; + fillEvent.Action.AutoPopulate = false; Ui.Component.Actions.Add(fillEvent.Action); Assignments.AssignSlot(hotbar, index, fillEvent.Action); @@ -608,11 +609,9 @@ namespace Content.Client.Actions Ui.UpdateUI(); } - public void SaveActionAssignments(string path) + /*public void SaveActionAssignments(string path) { - // Disabled until YamlMappingFix's sandbox issues are resolved. - - /* + // Currently only tested with temporary innate actions (i.e., mapping actions). No guarantee it works with // other actions. If its meant to be used for full game state saving/loading, the entity that provides // actions needs to keep the same uid. @@ -630,8 +629,7 @@ namespace Content.Client.Actions using var writer = _resourceManager.UserData.OpenWriteText(new ResourcePath(path).ToRootedPath()); var stream = new YamlStream { new(sequence.ToSequenceNode()) }; stream.Save(new YamlMappingFix(new Emitter(writer)), false); - */ - } + }*/ /// /// Load actions and their toolbar assignments from a file. diff --git a/Content.Client/Commands/ActionsCommands.cs b/Content.Client/Commands/ActionsCommands.cs new file mode 100644 index 0000000000..840d175ca9 --- /dev/null +++ b/Content.Client/Commands/ActionsCommands.cs @@ -0,0 +1,88 @@ +using Content.Client.Actions; +using Content.Client.Mapping; +using Content.Shared.Administration; +using Robust.Shared.Console; + +namespace Content.Client.Commands; + +// Disabled until sandoxing issues are resolved. In the meantime, if you want to create an acttions preset, just disable +// sandboxing and uncomment this code (and the SaveActionAssignments() function). +/* +[AnyCommand] +public sealed class SaveActionsCommand : IConsoleCommand +{ + public string Command => "saveacts"; + + public string Description => "Saves the current action toolbar assignments to a file"; + + public string Help => $"Usage: {Command} "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteLine(Help); + return; + } + + try + { + EntitySystem.Get().SaveActionAssignments(args[0]); + } + catch + { + shell.WriteLine("Failed to save action assignments"); + } + } +} +*/ + +[AnyCommand] +public sealed class LoadActionsCommand : IConsoleCommand +{ + public string Command => "loadacts"; + + public string Description => "Loads action toolbar assignments from a user-file."; + + public string Help => $"Usage: {Command} "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 1) + { + shell.WriteLine(Help); + return; + } + + try + { + EntitySystem.Get().LoadActionAssignments(args[0], true); + } + catch + { + shell.WriteLine("Failed to load action assignments"); + } + } +} + +[AnyCommand] +public sealed class LoadMappingActionsCommand : IConsoleCommand +{ + public string Command => "loadmapacts"; + + public string Description => "Loads the mapping preset action toolbar assignments."; + + public string Help => $"Usage: {Command}"; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + try + { + EntitySystem.Get().LoadMappingActions(); + } + catch + { + shell.WriteLine("Failed to load action assignments"); + } + } +} diff --git a/Content.Client/Decals/DecalPlacementSystem.cs b/Content.Client/Decals/DecalPlacementSystem.cs index ec04709fd0..daa40ec7f0 100644 --- a/Content.Client/Decals/DecalPlacementSystem.cs +++ b/Content.Client/Decals/DecalPlacementSystem.cs @@ -1,8 +1,14 @@ -using Content.Shared.Decals; +using Content.Client.Actions; +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Decals; using Robust.Client.GameObjects; using Robust.Client.Input; using Robust.Shared.Input; using Robust.Shared.Input.Binding; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Client.Decals; @@ -12,6 +18,8 @@ public sealed class DecalPlacementSystem : EntitySystem { [Dependency] private readonly IInputManager _inputManager = default!; [Dependency] private readonly InputSystem _inputSystem = default!; + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IMapManager _mapMan = default!; private string? _decalId; private Color _decalColor = Color.White; @@ -82,6 +90,70 @@ public sealed class DecalPlacementSystem : EntitySystem return true; }, true)).Register(); + + SubscribeLocalEvent(OnFillSlot); + SubscribeLocalEvent(OnPlaceDecalAction); + } + + private void OnPlaceDecalAction(PlaceDecalActionEvent args) + { + if (args.Handled) + return; + + if (!_mapMan.TryFindGridAt(args.Target, out var grid)) + return; + + args.Handled = true; + + var coords = EntityCoordinates.FromMap(grid.GridEntityId, args.Target, EntityManager); + + if (args.Snap) + { + var newPos = new Vector2( + (float) (MathF.Round(coords.X - 0.5f, MidpointRounding.AwayFromZero) + 0.5), + (float) (MathF.Round(coords.Y - 0.5f, MidpointRounding.AwayFromZero) + 0.5) + ); + coords = coords.WithPosition(newPos); + } + + coords = coords.Offset(new Vector2(-0.5f, -0.5f)); + + var decal = new Decal(coords.Position, args.DecalId, args.Color, Angle.FromDegrees(args.Rotation), args.ZIndex, args.Cleanable); + RaiseNetworkEvent(new RequestDecalPlacementEvent(decal, coords)); + } + + private void OnFillSlot(FillActionSlotEvent ev) + { + if (!_active || _placing) + return; + + if (ev.Action != null) + return; + + if (_decalId == null || !_protoMan.TryIndex(_decalId, out var decalProto)) + return; + + var actionEvent = new PlaceDecalActionEvent() + { + DecalId = _decalId, + Color = _decalColor, + Rotation = _decalAngle.Degrees, + Snap = _snap, + ZIndex = _zIndex, + Cleanable = _cleanable, + }; + + ev.Action = new WorldTargetAction() + { + Name = $"{_decalId} ({_decalColor.ToHex()}, {(int) _decalAngle.Degrees})", // non-unique actions may be considered duplicates when saving/loading. + Icon = decalProto.Sprite, + Repeat = true, + CheckCanAccess = false, + CheckCanInteract = false, + Range = -1, + Event = actionEvent, + IconColor = _decalColor, + }; } public override void Shutdown() @@ -110,3 +182,24 @@ public sealed class DecalPlacementSystem : EntitySystem _inputSystem.SetEntityContextActive(); } } + +public sealed class PlaceDecalActionEvent : PerformWorldTargetActionEvent +{ + [DataField("decalId", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string DecalId = string.Empty; + + [DataField("color")] + public Color Color; + + [DataField("rotation")] + public double Rotation; + + [DataField("snap")] + public bool Snap; + + [DataField("zIndex")] + public int ZIndex; + + [DataField("cleanable")] + public bool Cleanable; +} diff --git a/Content.Client/Mapping/MappingSystem.cs b/Content.Client/Mapping/MappingSystem.cs new file mode 100644 index 0000000000..82e93e29dd --- /dev/null +++ b/Content.Client/Mapping/MappingSystem.cs @@ -0,0 +1,154 @@ +using Robust.Shared.Utility; +using Robust.Client.Placement; +using Robust.Shared.Map; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Actions; +using Content.Client.Actions; +using Content.Shared.Maps; + +namespace Content.Client.Mapping; + +public sealed partial class MappingSystem : EntitySystem +{ + [Dependency] private readonly IPlacementManager _placementMan = default!; + [Dependency] private readonly ITileDefinitionManager _tileMan = default!; + [Dependency] private readonly ActionsSystem _actionsSystem = default!; + + /// + /// The icon to use for space tiles. + /// + private readonly SpriteSpecifier _spaceIcon = new SpriteSpecifier.Texture(new ResourcePath("Tiles/cropped_parallax.png")); + + /// + /// The icon to use for entity-eraser. + /// + private readonly SpriteSpecifier _deleteIcon = new SpriteSpecifier.Texture(new ResourcePath("Interface/VerbIcons/delete.svg.192dpi.png")); + + public string DefaultMappingActions = "/mapping_actions.yml"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnFillActionSlot); + SubscribeLocalEvent(OnStartPlacementAction); + } + + public void LoadMappingActions() + { + _actionsSystem.LoadActionAssignments(DefaultMappingActions, false); + } + + /// + /// This checks if the placement manager is currently active, and attempts to copy the placement information for + /// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd + /// prefer if it were to function more like DecalPlacementSystem. + /// + private void OnFillActionSlot(FillActionSlotEvent ev) + { + if (!_placementMan.IsActive) + return; + + if (ev.Action != null) + return; + + var actionEvent = new StartPlacementActionEvent(); + + if (_placementMan.CurrentPermission != null) + { + actionEvent.EntityType = _placementMan.CurrentPermission.EntityType; + actionEvent.IsTile = _placementMan.CurrentPermission.IsTile; + actionEvent.TileType = _placementMan.CurrentPermission.TileType; + actionEvent.PlacementOption = _placementMan.CurrentPermission.PlacementOption; + } + else if (_placementMan.Eraser) + { + actionEvent.Eraser = true; + } + else + return; + + if (actionEvent.IsTile) + { + var tileDef = _tileMan[actionEvent.TileType]; + + if (tileDef is not ContentTileDefinition contentTileDef) + return; + + var tileIcon = contentTileDef.IsSpace + ? _spaceIcon + : new SpriteSpecifier.Texture(new ResourcePath(tileDef.Path) / $"{tileDef.SpriteName}.png"); + + ev.Action = new InstantAction() + { + CheckCanInteract = false, + Event = actionEvent, + Name = tileDef.Name, + Icon = tileIcon + }; + + return; + } + + if (actionEvent.Eraser) + { + ev.Action = new InstantAction() + { + CheckCanInteract = false, + Event = actionEvent, + Name = "action-name-mapping-erase", + Icon = _deleteIcon, + }; + + return; + } + + if (string.IsNullOrWhiteSpace(actionEvent.EntityType)) + return; + + ev.Action = new InstantAction() + { + CheckCanInteract = false, + Event = actionEvent, + Name = actionEvent.EntityType, + Icon = new SpriteSpecifier.EntityPrototype(actionEvent.EntityType), + }; + } + + private void OnStartPlacementAction(StartPlacementActionEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + + _placementMan.BeginPlacing(new() + { + EntityType = args.EntityType, + IsTile = args.IsTile, + TileType = args.TileType, + PlacementOption = args.PlacementOption, + }); + + if (_placementMan.Eraser != args.Eraser) + _placementMan.ToggleEraser(); + } +} + +public sealed class StartPlacementActionEvent : PerformActionEvent +{ + [DataField("entityType")] + public string? EntityType; + + [DataField("isTile")] + public bool IsTile; + + [DataField("tileType")] + public ushort TileType; + + [DataField("placementOption")] + public string? PlacementOption; + + [DataField("eraser")] + public bool Eraser; +} diff --git a/Content.Server/GameTicking/Commands/MappingCommand.cs b/Content.Server/GameTicking/Commands/MappingCommand.cs index 426edb50b0..82b07bccee 100644 --- a/Content.Server/GameTicking/Commands/MappingCommand.cs +++ b/Content.Server/GameTicking/Commands/MappingCommand.cs @@ -88,6 +88,7 @@ namespace Content.Server.GameTicking.Commands shell.RemoteExecuteCommand("showmarkers"); shell.RemoteExecuteCommand("togglelight"); shell.RemoteExecuteCommand("showsubfloorforever"); + shell.RemoteExecuteCommand("loadmapacts"); mapManager.SetMapPaused(mapId, true); if (args.Length == 2) diff --git a/Resources/Textures/Tiles/cropped_parallax.png b/Resources/Textures/Tiles/cropped_parallax.png new file mode 100644 index 0000000000..6a3c1ad5f5 Binary files /dev/null and b/Resources/Textures/Tiles/cropped_parallax.png differ diff --git a/Resources/mapping_actions.yml b/Resources/mapping_actions.yml new file mode 100644 index 0000000000..239a6274cb --- /dev/null +++ b/Resources/mapping_actions.yml @@ -0,0 +1,994 @@ +# Default action-toolbar layout for the /mapping command. + +- action: !type:InstantAction + icon: Window + name: Window + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Window + assignments: + - 0: 2 +- action: !type:InstantAction + icon: WallReinforced + name: WallReinforced + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: WallReinforced + assignments: + - 0: 1 +- action: !type:InstantAction + icon: WallSolid + name: WallSolid + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: WallSolid + assignments: + - 0: 0 +- action: !type:InstantAction + icon: ReinforcedWindow + name: ReinforcedWindow + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: ReinforcedWindow + assignments: + - 0: 3 +- action: !type:InstantAction + icon: Firelock + name: Firelock + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Firelock + assignments: + - 0: 5 +- action: !type:InstantAction + icon: Grille + name: Grille + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Grille + assignments: + - 0: 4 +- action: !type:InstantAction + icon: Interface/VerbIcons/delete.svg.192dpi.png + name: action-name-mapping-erase + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + eraser: True + assignments: + - 0: 9 + - 1: 9 + - 2: 9 + - 4: 9 + - 5: 9 + - 6: 9 +- action: !type:InstantAction + icon: Tiles/cropped_parallax.png + name: space + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + isTile: True + assignments: + - 0: 8 +- action: !type:InstantAction + icon: /Textures/Tiles/steel.png + name: steel floor + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + tileType: 30 + isTile: True + assignments: + - 0: 7 +- action: !type:InstantAction + icon: /Textures/Tiles/plating.png + name: plating + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: AlignTileAny + tileType: 36 + isTile: True + assignments: + - 0: 6 + - 1: 8 + - 2: 8 +- action: !type:InstantAction + icon: GasPipeStraight + name: GasPipeStraight + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: GasPipeStraight + assignments: + - 1: 0 +- action: !type:InstantAction + icon: GasPipeBend + name: GasPipeBend + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: GasPipeBend + assignments: + - 1: 1 +- action: !type:InstantAction + icon: GasPipeTJunction + name: GasPipeTJunction + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: GasPipeTJunction + assignments: + - 1: 2 +- action: !type:InstantAction + icon: GasPipeFourway + name: GasPipeFourway + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: GasPipeFourway + assignments: + - 1: 3 +- action: !type:InstantAction + icon: GasVentScrubber + name: GasVentScrubber + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: GasVentScrubber + assignments: + - 1: 4 +- action: !type:InstantAction + icon: GasVentPump + name: GasVentPump + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: GasVentPump + assignments: + - 1: 5 +- action: !type:InstantAction + icon: AirAlarm + name: AirAlarm + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirAlarm + assignments: + - 1: 6 +- action: !type:InstantAction + icon: FireAlarm + name: FireAlarm + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: FireAlarm + assignments: + - 1: 7 +- action: !type:InstantAction + icon: SalternAPC + name: SalternAPC + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: SalternAPC + assignments: + - 2: 3 +- action: !type:InstantAction + icon: CableApcExtension + name: CableApcExtension + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: CableApcExtension + assignments: + - 2: 0 +- action: !type:InstantAction + icon: CableMV + name: CableMV + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: CableMV + assignments: + - 2: 1 +- action: !type:InstantAction + icon: CableHV + name: CableHV + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: CableHV + assignments: + - 2: 2 +- action: !type:InstantAction + icon: SalternSubstation + name: SalternSubstation + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: SalternSubstation + assignments: + - 2: 4 +- action: !type:InstantAction + icon: Poweredlight + name: Poweredlight + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Poweredlight + assignments: + - 2: 6 +- action: !type:InstantAction + icon: EmergencyLight + name: EmergencyLight + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: PlaceFree + entityType: EmergencyLight + assignments: + - 2: 7 +- action: !type:InstantAction + icon: SalternSMES + name: SalternSMES + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: SalternSMES + assignments: + - 2: 5 +- action: !type:InstantAction + icon: TableWood + name: TableWood + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: TableWood + assignments: + - 3: 0 +- action: !type:InstantAction + icon: Table + name: Table + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Table + assignments: + - 3: 1 +- action: !type:InstantAction + icon: ChairWood + name: ChairWood + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: ChairWood + assignments: + - 3: 2 +- action: !type:InstantAction + icon: Chair + name: Chair + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Chair + assignments: + - 3: 3 +- action: !type:InstantAction + icon: ChairOfficeLight + name: ChairOfficeLight + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: ChairOfficeLight + assignments: + - 3: 4 +- action: !type:InstantAction + icon: StoolBar + name: StoolBar + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: StoolBar + assignments: + - 3: 6 +- action: !type:InstantAction + icon: Stool + name: Stool + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Stool + assignments: + - 3: 7 +- action: !type:InstantAction + icon: Rack + name: Rack + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Rack + assignments: + - 3: 8 +- action: !type:InstantAction + icon: ChairOfficeDark + name: ChairOfficeDark + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: ChairOfficeDark + assignments: + - 3: 5 +- action: !type:InstantAction + icon: LampGold + name: LampGold + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: PlaceFree + entityType: LampGold + assignments: + - 3: 9 +- action: !type:InstantAction + icon: DisposalPipe + name: DisposalPipe + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalPipe + assignments: + - 4: 0 +- action: !type:InstantAction + icon: DisposalBend + name: DisposalBend + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalBend + assignments: + - 4: 1 +- action: !type:InstantAction + icon: DisposalJunction + name: DisposalJunction + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalJunction + assignments: + - 4: 2 +- action: !type:InstantAction + icon: DisposalJunctionFlipped + name: DisposalJunctionFlipped + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalJunctionFlipped + assignments: + - 4: 3 +- action: !type:InstantAction + icon: DisposalRouter + name: DisposalRouter + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalRouter + assignments: + - 4: 4 +- action: !type:InstantAction + icon: DisposalRouterFlipped + name: DisposalRouterFlipped + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalRouterFlipped + assignments: + - 4: 5 +- action: !type:InstantAction + icon: DisposalUnit + name: DisposalUnit + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalUnit + assignments: + - 4: 6 +- action: !type:InstantAction + icon: DisposalTrunk + name: DisposalTrunk + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: DisposalTrunk + assignments: + - 4: 7 +- action: !type:InstantAction + icon: SignDisposalSpace + name: SignDisposalSpace + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: SignDisposalSpace + assignments: + - 4: 8 +- action: !type:InstantAction + icon: Windoor + name: Windoor + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Windoor + assignments: + - 5: 0 +- action: !type:InstantAction + icon: WindowDirectional + name: WindowDirectional + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: WindowDirectional + assignments: + - 5: 1 +- action: !type:InstantAction + icon: WindowReinforcedDirectional + name: WindowReinforcedDirectional + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: WindowReinforcedDirectional + assignments: + - 5: 2 +- action: !type:InstantAction + icon: PlasmaWindowDirectional + name: PlasmaWindowDirectional + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: PlasmaWindowDirectional + assignments: + - 5: 3 +- action: !type:InstantAction + icon: Railing + name: Railing + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: Railing + assignments: + - 5: 6 +- action: !type:InstantAction + icon: RailingCorner + name: RailingCorner + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: RailingCorner + assignments: + - 5: 7 +- action: !type:InstantAction + icon: RailingCornerSmall + name: RailingCornerSmall + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: RailingCornerSmall + assignments: + - 5: 8 +- action: !type:InstantAction + icon: AirlockMaintLocked + name: AirlockMaintLocked + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockMaintLocked + assignments: + - 6: 0 +- action: !type:InstantAction + icon: AirlockGlass + name: AirlockGlass + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockGlass + assignments: + - 6: 1 +- action: !type:InstantAction + icon: AirlockServiceLocked + name: AirlockServiceLocked + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockServiceLocked + assignments: + - 6: 2 +- action: !type:InstantAction + icon: AirlockSecurityLocked + name: AirlockSecurityLocked + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockSecurityLocked + assignments: + - 6: 3 +- action: !type:InstantAction + icon: AirlockCommand + name: AirlockCommand + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockCommand + assignments: + - 6: 4 +- action: !type:InstantAction + icon: AirlockScience + name: AirlockScience + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockScience + assignments: + - 6: 5 +- action: !type:InstantAction + icon: AirlockMedical + name: AirlockMedical + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockMedical + assignments: + - 6: 6 +- action: !type:InstantAction + icon: AirlockEngineering + name: AirlockEngineering + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockEngineering + assignments: + - 6: 7 +- action: !type:InstantAction + icon: AirlockCargo + name: AirlockCargo + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:StartPlacementActionEvent + placementOption: SnapgridCenter + entityType: AirlockCargo + assignments: + - 6: 8 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/markings.rsi + state: bot_left + iconColor: '#EFB34196' + name: BotLeft + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#EFB34196' + decalId: BotLeft + assignments: + - 7: 0 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/markings.rsi + state: delivery + iconColor: '#EFB34196' + name: Delivery + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#EFB34196' + decalId: Delivery + assignments: + - 7: 1 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/markings.rsi + state: warn_full + iconColor: '#EFB34196' + name: WarnFull + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#EFB34196' + decalId: WarnFull + assignments: + - 7: 2 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#EFB34196' + name: HalfTileOverlayGreyscale + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#EFB34196' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 3 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#334E6DC8' + name: HalfTileOverlayGreyscale (#334E6DC8, 0) + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#334E6DC8' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 4 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#52B4E996' + name: HalfTileOverlayGreyscale (#52B4E996, 0) + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#52B4E996' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 5 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#9FED5896' + name: HalfTileOverlayGreyscale (#9FED5896, 0) + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#9FED5896' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 6 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#DE3A3A96' + name: HalfTileOverlayGreyscale (#DE3A3A96, 0) + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#DE3A3A96' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 7 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#D381C996' + name: HalfTileOverlayGreyscale (#D381C996, 0) + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#D381C996' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 8 +- action: !type:WorldTargetAction + repeat: True + checkCanAccess: False + icon: + sprite: Decals/Overlays/greyscale.rsi + state: halftile_overlay + iconColor: '#A4610696' + name: HalfTileOverlayGreyscale (#A4610696, 0) + keywords: [] + checkCanInteract: False + clientExclusive: True + temporary: True + autoPopulate: False + event: !type:PlaceDecalActionEvent + snap: True + color: '#A4610696' + decalId: HalfTileOverlayGreyscale + assignments: + - 7: 9 +...