]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add shortcut to flip for construction menu (#14152)
author08A <git@08a.re>
Mon, 15 May 2023 04:13:24 +0000 (06:13 +0200)
committerGitHub <noreply@github.com>
Mon, 15 May 2023 04:13:24 +0000 (14:13 +1000)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Content.Client/Construction/ConstructionSystem.cs
Content.Client/Construction/UI/ConstructionMenuPresenter.cs
Content.Client/Input/ContentContexts.cs
Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
Content.Shared/Construction/Prototypes/ConstructionPrototype.cs
Content.Shared/Input/ContentKeyFunctions.cs
Resources/Locale/en-US/escape-menu/ui/options-menu.ftl
Resources/Prototypes/Recipes/Construction/Graphs/utilities/atmos_trinary.yml
Resources/Prototypes/Recipes/Construction/Graphs/utilities/disposal_pipes.yml
Resources/Prototypes/Recipes/Construction/utilities.yml
Resources/keybinds.yml

index 06726d6caad163c13f704496f1f0230ecce724b8..8a7e0be06d829cd6e8151828767077ffd01a7177 100644 (file)
@@ -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<ConstructionSystem>();
 
             SubscribeLocalEvent<ConstructionGhostComponent, ExaminedEvent>(HandleConstructionGhostExamined);
@@ -99,6 +101,7 @@ namespace Content.Client.Construction
         public event EventHandler<CraftingAvailabilityChangedArgs>? CraftingAvailabilityChanged;
         public event EventHandler<string>? 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)
index 0fdf1c1dd065e76e5f2016fb3b2e3696eb632018..5912b7a84f0c8755019fc9376cbc565d8b738646 100644 (file)
@@ -148,6 +148,9 @@ namespace Content.Client.Construction.UI
 
             foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
             {
+                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<GameTopMenuBar>() != 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<ConstructionPrototype>(_selected.Mirror);
+            UpdateGhostPlacement();
+        }
+
         private void SystemGuideAvailable(object? sender, string e)
         {
             if (!CraftingAvailable)
index 0684bb7c49365919e05c70a6f8d87ad486fe43e4..963571abe76fc7fb6d8fc8eb0f638c09bd64de16 100644 (file)
@@ -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);
index 93c34e953041e79cbf6070519cc406dbacc209e9..cbd462ea4ff5808a71519e0b568f6be3075990af 100644 (file)
@@ -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");
index 84b0b8591a44abd4dad2932101b138a8216838ce..7fde68b6d7c632ed51cf2e1c6a75fe3d5c5ff154 100644 (file)
@@ -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<IConstructionCondition> _conditions = new();
-
-        /// <summary>
-        ///     Friendly name displayed in the construction GUI.
-        /// </summary>
-        [DataField("name")]
-        public string Name { get; } = string.Empty;
-
-        /// <summary>
-        ///     "Useful" description displayed in the construction GUI.
-        /// </summary>
-        [DataField("description")]
-        public string Description { get; } = string.Empty;
-
-        /// <summary>
-        ///     The <see cref="ConstructionGraphPrototype"/> this construction will be using.
-        /// </summary>
-        [DataField("graph", customTypeSerializer:typeof(PrototypeIdSerializer<ConstructionGraphPrototype>))]
-        public string Graph { get; } = string.Empty;
-
-        /// <summary>
-        ///     The target <see cref="ConstructionGraphNode"/> this construction will guide the user to.
-        /// </summary>
-        [DataField("targetNode")]
-        public string TargetNode { get; } = string.Empty;
-
-        /// <summary>
-        ///     The starting <see cref="ConstructionGraphNode"/> this construction will start at.
-        /// </summary>
-        [DataField("startNode")]
-        public string StartNode { get; } = string.Empty;
-
-        /// <summary>
-        ///     Texture path inside the construction GUI.
-        /// </summary>
-        [DataField("icon")]
-        public SpriteSpecifier Icon { get; } = SpriteSpecifier.Invalid;
-
-        /// <summary>
-        ///     Texture paths used for the construction ghost.
-        /// </summary>
-        [DataField("layers")]
-        private List<SpriteSpecifier>? _layers;
-
-        /// <summary>
-        ///     If you can start building or complete steps on impassable terrain.
-        /// </summary>
-        [DataField("canBuildInImpassable")]
-        public bool CanBuildInImpassable { get; private set; }
-
-        [DataField("category")] public string Category { get; private set; } = "";
-
-        [DataField("objectType")] public ConstructionType Type { get; private set; } = ConstructionType.Structure;
-
-        [ViewVariables]
-        [IdDataField]
-        public string ID { get; } = default!;
-
-        [DataField("placementMode")]
-        public string PlacementMode { get; } = "PlaceFree";
-
-        /// <summary>
-        ///     Whether this construction can be constructed rotated or not.
-        /// </summary>
-        [DataField("canRotate")]
-        public bool CanRotate { get; } = true;
-
-        public IReadOnlyList<IConstructionCondition> Conditions => _conditions;
-        public IReadOnlyList<SpriteSpecifier> Layers => _layers ?? new List<SpriteSpecifier>{Icon};
-    }
-
-    public enum ConstructionType
-    {
-        Structure,
-        Item,
-    }
+    [DataField("conditions")] private List<IConstructionCondition> _conditions = new();
+
+    /// <summary>
+    ///     Hide from the construction list
+    /// </summary>
+    [DataField("hide")]
+    public bool Hide = false;
+
+    /// <summary>
+    ///     Friendly name displayed in the construction GUI.
+    /// </summary>
+    [DataField("name")]
+    public string Name= string.Empty;
+
+    /// <summary>
+    ///     "Useful" description displayed in the construction GUI.
+    /// </summary>
+    [DataField("description")]
+    public string Description = string.Empty;
+
+    /// <summary>
+    ///     The <see cref="ConstructionGraphPrototype"/> this construction will be using.
+    /// </summary>
+    [DataField("graph", customTypeSerializer:typeof(PrototypeIdSerializer<ConstructionGraphPrototype>))]
+    public string Graph = string.Empty;
+
+    /// <summary>
+    ///     The target <see cref="ConstructionGraphNode"/> this construction will guide the user to.
+    /// </summary>
+    [DataField("targetNode")]
+    public string TargetNode = string.Empty;
+
+    /// <summary>
+    ///     The starting <see cref="ConstructionGraphNode"/> this construction will start at.
+    /// </summary>
+    [DataField("startNode")]
+    public string StartNode = string.Empty;
+
+    /// <summary>
+    ///     Texture path inside the construction GUI.
+    /// </summary>
+    [DataField("icon")]
+    public SpriteSpecifier Icon = SpriteSpecifier.Invalid;
+
+    /// <summary>
+    ///     Texture paths used for the construction ghost.
+    /// </summary>
+    [DataField("layers")]
+    private List<SpriteSpecifier>? _layers;
+
+    /// <summary>
+    ///     If you can start building or complete steps on impassable terrain.
+    /// </summary>
+    [DataField("canBuildInImpassable")]
+    public bool CanBuildInImpassable { get; private set; }
+
+    [DataField("category")] public string Category { get; private set; } = "";
+
+    [DataField("objectType")] public ConstructionType Type { get; private set; } = ConstructionType.Structure;
+
+    [ViewVariables]
+    [IdDataField]
+    public string ID { get; } = default!;
+
+    [DataField("placementMode")]
+    public string PlacementMode = "PlaceFree";
+
+    /// <summary>
+    ///     Whether this construction can be constructed rotated or not.
+    /// </summary>
+    [DataField("canRotate")]
+    public bool CanRotate = true;
+
+    /// <summary>
+    ///     Construction to replace this construction with when the current one is 'flipped'
+    /// </summary>
+    [DataField("mirror", customTypeSerializer:typeof(PrototypeIdSerializer<ConstructionPrototype>))]
+    public string Mirror = string.Empty;
+
+    public IReadOnlyList<IConstructionCondition> Conditions => _conditions;
+    public IReadOnlyList<SpriteSpecifier> Layers => _layers ?? new List<SpriteSpecifier>{Icon};
 }
 
+public enum ConstructionType
+{
+    Structure,
+    Item,
+}
index d9b3d090eabb2116773d1d1514c3b5f58b0776a9..57b0493eb9a6acf1d5fe1934193408d0846f2266 100644 (file)
@@ -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";
     }
 }
index 0caa547301018572bb4c82a1e0490de62806bf7d..1a50c932ff4ee7157a64ef917d005980d374d61f 100644 (file)
@@ -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
index 5a77ea405bc2608dfde74d7d578eb27ba9f8c870..73da987b37c869c49eab637ff016b5e5e92ebbe5 100644 (file)
@@ -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:
       - 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:
       - 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:
index 42ca4e597e5b9dd9f9d5346d00463751afcf25b7..b626105532ffca4b69f98d57ad9a829c2d4fca52 100644 (file)
       - 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:
       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:
       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:
       steps:
       - tool: Screwing
         doAfter: 1
+    - to: junctionflipped
+      steps:
+      - tool: Screwing
+        doAfter: 1
   - node: bend
     entity: DisposalBend
     edges:
index c85d426c396879d761de9b0a96b9762dc6475580..9b285ff27def5ff80232f874689493d6c01fd21d 100644 (file)
   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
   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
   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 {}
 
   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 {}
 
index 9c5bf8ae18df0ef44550c37471453a0c4838b17c..82e5baeb4318c64e5a824fafd563e6c92f937d43 100644 (file)
@@ -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