From 3f93b11e4acb9af9468bf1111cbbcade0a459f59 Mon Sep 17 00:00:00 2001 From: 08A Date: Mon, 15 May 2023 06:13:24 +0200 Subject: [PATCH] Add shortcut to flip for construction menu (#14152) Co-authored-by: metalgearsloth --- .../Construction/ConstructionSystem.cs | 10 ++ .../UI/ConstructionMenuPresenter.cs | 21 +++ Content.Client/Input/ContentContexts.cs | 3 + .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 1 + .../Prototypes/ConstructionPrototype.cs | 140 ++++++++++-------- Content.Shared/Input/ContentKeyFunctions.cs | 1 + .../en-US/escape-menu/ui/options-menu.ftl | 1 + .../Graphs/utilities/atmos_trinary.yml | 48 ++++++ .../Graphs/utilities/disposal_pipes.yml | 46 ++++++ .../Recipes/Construction/utilities.yml | 72 +++++++++ Resources/keybinds.yml | 4 + 11 files changed, 282 insertions(+), 65 deletions(-) diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs index 06726d6caa..8a7e0be06d 100644 --- a/Content.Client/Construction/ConstructionSystem.cs +++ b/Content.Client/Construction/ConstructionSystem.cs @@ -46,6 +46,8 @@ namespace Content.Client.Construction new PointerInputCmdHandler(HandleOpenCraftingMenu)) .Bind(EngineKeyFunctions.Use, new PointerInputCmdHandler(HandleUse, outsidePrediction: true)) + .Bind(ContentKeyFunctions.EditorFlipObject, + new PointerInputCmdHandler(HandleFlip)) .Register(); SubscribeLocalEvent(HandleConstructionGhostExamined); @@ -99,6 +101,7 @@ namespace Content.Client.Construction public event EventHandler? CraftingAvailabilityChanged; public event EventHandler? ConstructionGuideAvailable; public event EventHandler? ToggleCraftingWindow; + public event EventHandler? FlipConstructionPrototype; private void HandleAckStructure(AckStructureConstructionMessage msg) { @@ -118,6 +121,13 @@ namespace Content.Client.Construction return true; } + private bool HandleFlip(in PointerInputCmdHandler.PointerInputCmdArgs args) + { + if (args.State == BoundKeyState.Down) + FlipConstructionPrototype?.Invoke(this, EventArgs.Empty); + return true; + } + private void UpdateCraftingAvailability(bool available) { if (CraftingEnabled == available) diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs index 0fdf1c1dd0..5912b7a84f 100644 --- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs +++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs @@ -148,6 +148,9 @@ namespace Content.Client.Construction.UI foreach (var recipe in _prototypeManager.EnumeratePrototypes()) { + if (recipe.Hide) + continue; + if (!string.IsNullOrEmpty(search)) { if (!recipe.Name.ToLowerInvariant().Contains(search.Trim().ToLowerInvariant())) @@ -342,6 +345,7 @@ namespace Content.Client.Construction.UI { _constructionSystem = system; system.ToggleCraftingWindow += SystemOnToggleMenu; + system.FlipConstructionPrototype += SystemFlipConstructionPrototype; system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged; system.ConstructionGuideAvailable += SystemGuideAvailable; if (_uiManager.GetActiveUIWidgetOrNull() != null) @@ -358,6 +362,7 @@ namespace Content.Client.Construction.UI throw new InvalidOperationException(); system.ToggleCraftingWindow -= SystemOnToggleMenu; + system.FlipConstructionPrototype -= SystemFlipConstructionPrototype; system.CraftingAvailabilityChanged -= SystemCraftingAvailabilityChanged; system.ConstructionGuideAvailable -= SystemGuideAvailable; _constructionSystem = null; @@ -392,6 +397,22 @@ namespace Content.Client.Construction.UI } } + private void SystemFlipConstructionPrototype(object? sender, EventArgs eventArgs) + { + if (!_placementManager.IsActive || _placementManager.Eraser) + { + return; + } + + if (_selected == null || _selected.Mirror == String.Empty) + { + return; + } + + _selected = _prototypeManager.Index(_selected.Mirror); + UpdateGhostPlacement(); + } + private void SystemGuideAvailable(object? sender, string e) { if (!CraftingAvailable) diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 0684bb7c49..963571abe7 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -37,6 +37,9 @@ namespace Content.Client.Input // Not in engine, because engine cannot check for sanbox/admin status before starting placement. common.AddFunction(ContentKeyFunctions.EditorCopyObject); + // Not in engine because the engine doesn't understand what a flipped object is + common.AddFunction(ContentKeyFunctions.EditorFlipObject); + var human = contexts.GetContext("human"); human.AddFunction(EngineKeyFunctions.MoveUp); human.AddFunction(EngineKeyFunctions.MoveDown); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 93c34e9530..cbd462ea4f 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -184,6 +184,7 @@ namespace Content.Client.Options.UI.Tabs AddButton(EngineKeyFunctions.EditorGridPlace); AddButton(EngineKeyFunctions.EditorLinePlace); AddButton(EngineKeyFunctions.EditorRotateObject); + AddButton(ContentKeyFunctions.EditorFlipObject); AddButton(ContentKeyFunctions.EditorCopyObject); AddHeader("ui-options-header-dev"); diff --git a/Content.Shared/Construction/Prototypes/ConstructionPrototype.cs b/Content.Shared/Construction/Prototypes/ConstructionPrototype.cs index 84b0b8591a..7fde68b6d7 100644 --- a/Content.Shared/Construction/Prototypes/ConstructionPrototype.cs +++ b/Content.Shared/Construction/Prototypes/ConstructionPrototype.cs @@ -3,86 +3,96 @@ using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Utility; -namespace Content.Shared.Construction.Prototypes +namespace Content.Shared.Construction.Prototypes; + +[Prototype("construction")] +public sealed class ConstructionPrototype : IPrototype { - [Prototype("construction")] - public sealed class ConstructionPrototype : IPrototype - { - [DataField("conditions")] private List _conditions = new(); + [DataField("conditions")] private List _conditions = new(); - /// - /// Friendly name displayed in the construction GUI. - /// - [DataField("name")] - public string Name { get; } = string.Empty; + /// + /// Hide from the construction list + /// + [DataField("hide")] + public bool Hide = false; - /// - /// "Useful" description displayed in the construction GUI. - /// - [DataField("description")] - public string Description { get; } = string.Empty; + /// + /// Friendly name displayed in the construction GUI. + /// + [DataField("name")] + public string Name= string.Empty; - /// - /// The this construction will be using. - /// - [DataField("graph", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string Graph { get; } = string.Empty; + /// + /// "Useful" description displayed in the construction GUI. + /// + [DataField("description")] + public string Description = string.Empty; - /// - /// The target this construction will guide the user to. - /// - [DataField("targetNode")] - public string TargetNode { get; } = string.Empty; + /// + /// The this construction will be using. + /// + [DataField("graph", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string Graph = string.Empty; - /// - /// The starting this construction will start at. - /// - [DataField("startNode")] - public string StartNode { get; } = string.Empty; + /// + /// The target this construction will guide the user to. + /// + [DataField("targetNode")] + public string TargetNode = string.Empty; - /// - /// Texture path inside the construction GUI. - /// - [DataField("icon")] - public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid; + /// + /// The starting this construction will start at. + /// + [DataField("startNode")] + public string StartNode = string.Empty; - /// - /// Texture paths used for the construction ghost. - /// - [DataField("layers")] - private List? _layers; + /// + /// Texture path inside the construction GUI. + /// + [DataField("icon")] + public SpriteSpecifier Icon = SpriteSpecifier.Invalid; - /// - /// If you can start building or complete steps on impassable terrain. - /// - [DataField("canBuildInImpassable")] - public bool CanBuildInImpassable { get; private set; } + /// + /// Texture paths used for the construction ghost. + /// + [DataField("layers")] + private List? _layers; - [DataField("category")] public string Category { get; private set; } = ""; + /// + /// If you can start building or complete steps on impassable terrain. + /// + [DataField("canBuildInImpassable")] + public bool CanBuildInImpassable { get; private set; } - [DataField("objectType")] public ConstructionType Type { get; private set; } = ConstructionType.Structure; + [DataField("category")] public string Category { get; private set; } = ""; - [ViewVariables] - [IdDataField] - public string ID { get; } = default!; + [DataField("objectType")] public ConstructionType Type { get; private set; } = ConstructionType.Structure; - [DataField("placementMode")] - public string PlacementMode { get; } = "PlaceFree"; + [ViewVariables] + [IdDataField] + public string ID { get; } = default!; - /// - /// Whether this construction can be constructed rotated or not. - /// - [DataField("canRotate")] - public bool CanRotate { get; } = true; + [DataField("placementMode")] + public string PlacementMode = "PlaceFree"; - public IReadOnlyList Conditions => _conditions; - public IReadOnlyList Layers => _layers ?? new List{Icon}; - } + /// + /// Whether this construction can be constructed rotated or not. + /// + [DataField("canRotate")] + public bool CanRotate = true; - public enum ConstructionType - { - Structure, - Item, - } + /// + /// Construction to replace this construction with when the current one is 'flipped' + /// + [DataField("mirror", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string Mirror = string.Empty; + + public IReadOnlyList Conditions => _conditions; + public IReadOnlyList Layers => _layers ?? new List{Icon}; } +public enum ConstructionType +{ + Structure, + Item, +} diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index d9b3d090ea..57b0493eb9 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -111,5 +111,6 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction Vote8 = "Vote8"; public static readonly BoundKeyFunction Vote9 = "Vote9"; public static readonly BoundKeyFunction EditorCopyObject = "EditorCopyObject"; + public static readonly BoundKeyFunction EditorFlipObject = "EditorFlipObject"; } } diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index 0caa547301..1a50c932ff 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -154,6 +154,7 @@ ui-options-function-editor-cancel-place = Cancel placement ui-options-function-editor-grid-place = Place in grid ui-options-function-editor-line-place = Place line ui-options-function-editor-rotate-object = Rotate +ui-options-function-editor-flip-object = Flip ui-options-function-editor-copy-object = Copy ui-options-function-open-abilities-menu = Open action menu diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml index 5a77ea405b..73da987b37 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml @@ -4,24 +4,39 @@ graph: - node: start edges: + # Filter - to: filter steps: - material: Steel amount: 2 doAfter: 1 + - to: filterflipped + steps: + - material: Steel + amount: 2 + doAfter: 1 + + # Mixer - to: mixer steps: - material: Steel amount: 2 doAfter: 1 + - to: mixerflipped + steps: + - material: Steel + amount: 2 + doAfter: 1 + - to: pneumaticvalve steps: - material: Steel amount: 2 doAfter: 1 + # Filter - node: filter entity: GasFilter edges: @@ -38,6 +53,23 @@ - tool: Welding doAfter: 1 + - node: filterflipped + entity: GasFilterFlipped + edges: + - to: start + conditions: + - !type:EntityAnchored + anchored: false + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity + steps: + - tool: Welding + doAfter: 1 + + # Mixer - node: mixer entity: GasMixer edges: @@ -54,6 +86,22 @@ - tool: Welding doAfter: 1 + - node: mixerflipped + entity: GasMixerFlipped + edges: + - to: start + conditions: + - !type:EntityAnchored + anchored: false + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity + steps: + - tool: Welding + doAfter: 1 + - node: pneumaticvalve entity: PressureControlledValve edges: diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_pipes.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_pipes.yml index 42ca4e597e..b626105532 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_pipes.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_pipes.yml @@ -19,16 +19,28 @@ - material: Steel amount: 2 doAfter: 1 + # DisposalRouter - to: router steps: - material: Steel amount: 2 doAfter: 1 + - to: routerflipped + steps: + - material: Steel + amount: 2 + doAfter: 1 + # DisposalJunction - to: junction steps: - material: Steel amount: 2 doAfter: 1 + - to: junctionflipped + steps: + - material: Steel + amount: 2 + doAfter: 1 - to: yJunction steps: - material: Steel @@ -86,6 +98,7 @@ steps: - tool: Welding doAfter: 1 + # DisposalRouter - node: router entity: DisposalRouter edges: @@ -98,6 +111,19 @@ steps: - tool: Welding doAfter: 1 + - node: routerflipped + entity: DisposalRouterFlipped + edges: + - to: start + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity + steps: + - tool: Welding + doAfter: 1 + # DisposalJunction - node: junction entity: DisposalJunction edges: @@ -114,6 +140,22 @@ steps: - tool: Screwing doAfter: 1 + - node: junctionflipped + entity: DisposalJunctionFlipped + edges: + - to: start + completed: + - !type:SpawnPrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity + steps: + - tool: Welding + doAfter: 1 + - to: yJunction + steps: + - tool: Screwing + doAfter: 1 - node: yJunction entity: DisposalYJunction edges: @@ -130,6 +172,10 @@ steps: - tool: Screwing doAfter: 1 + - to: junctionflipped + steps: + - tool: Screwing + doAfter: 1 - node: bend entity: DisposalBend edges: diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index c85d426c39..9b285ff27d 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -211,6 +211,23 @@ icon: sprite: Structures/Piping/disposal.rsi state: conpipe-j1s + mirror: DisposalRouterFlipped + +- type: construction + hide: true + name: disposal router + description: A three-way router. Entities with matching tags get routed to the side. + id: DisposalRouterFlipped + graph: DisposalPipe + startNode: start + targetNode: routerflipped + category: construction-category-utilities + placementMode: SnapgridCenter + canBuildInImpassable: false + icon: + sprite: Structures/Piping/disposal.rsi + state: conpipe-j2s + mirror: DisposalRouter - type: construction name: disposal junction @@ -225,6 +242,23 @@ icon: sprite: Structures/Piping/disposal.rsi state: conpipe-j1 + mirror: DisposalJunctionFlipped + +- type: construction + hide: true + name: disposal junction + description: A three-way junction. The arrow indicates where items exit. + id: DisposalJunctionFlipped + graph: DisposalPipe + startNode: start + targetNode: junctionflipped + category: construction-category-utilities + placementMode: SnapgridCenter + canBuildInImpassable: false + icon: + sprite: Structures/Piping/disposal.rsi + state: conpipe-j2 + mirror: DisposalJunction - type: construction name: disposal Y junction @@ -624,6 +658,25 @@ icon: sprite: Structures/Piping/Atmospherics/gasfilter.rsi state: gasFilter + mirror: GasFilterFlipped + conditions: + - !type:TileNotBlocked {} + +- type: construction + id: GasFilterFlipped + hide: true + name: gas filter + description: Very useful for filtering gases. + graph: GasTrinary + startNode: start + targetNode: filterflipped + category: construction-category-utilities + placementMode: SnapgridCenter + canBuildInImpassable: false + icon: + sprite: Structures/Piping/Atmospherics/gasfilter.rsi + state: gasFilterF + mirror: GasFilter conditions: - !type:TileNotBlocked {} @@ -640,6 +693,25 @@ icon: sprite: Structures/Piping/Atmospherics/gasmixer.rsi state: gasMixer + mirror: GasMixerFlipped + conditions: + - !type:TileNotBlocked {} + +- type: construction + id: GasMixerFlipped + hide: true + name: gas mixer + description: Very useful for mixing gases. + graph: GasTrinary + startNode: start + targetNode: mixerflipped + category: construction-category-utilities + placementMode: SnapgridCenter + canBuildInImpassable: false + icon: + sprite: Structures/Piping/Atmospherics/gasmixer.rsi + state: gasMixerF + mirror: GasMixer conditions: - !type:TileNotBlocked {} diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 9c5bf8ae18..82e5baeb43 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -150,6 +150,10 @@ binds: - function: EditorRotateObject type: State key: MouseMiddle +- function: EditorFlipObject + type: State + key: MouseMiddle + mod1: Control - function: EditorCopyObject type: State key: P