new PointerInputCmdHandler(HandleOpenCraftingMenu))
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUse, outsidePrediction: true))
+ .Bind(ContentKeyFunctions.EditorFlipObject,
+ new PointerInputCmdHandler(HandleFlip))
.Register<ConstructionSystem>();
SubscribeLocalEvent<ConstructionGhostComponent, ExaminedEvent>(HandleConstructionGhostExamined);
public event EventHandler<CraftingAvailabilityChangedArgs>? CraftingAvailabilityChanged;
public event EventHandler<string>? ConstructionGuideAvailable;
public event EventHandler? ToggleCraftingWindow;
+ public event EventHandler? FlipConstructionPrototype;
private void HandleAckStructure(AckStructureConstructionMessage msg)
{
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)
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
{
+ if (recipe.Hide)
+ continue;
+
if (!string.IsNullOrEmpty(search))
{
if (!recipe.Name.ToLowerInvariant().Contains(search.Trim().ToLowerInvariant()))
{
_constructionSystem = system;
system.ToggleCraftingWindow += SystemOnToggleMenu;
+ system.FlipConstructionPrototype += SystemFlipConstructionPrototype;
system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged;
system.ConstructionGuideAvailable += SystemGuideAvailable;
if (_uiManager.GetActiveUIWidgetOrNull<GameTopMenuBar>() != null)
throw new InvalidOperationException();
system.ToggleCraftingWindow -= SystemOnToggleMenu;
+ system.FlipConstructionPrototype -= SystemFlipConstructionPrototype;
system.CraftingAvailabilityChanged -= SystemCraftingAvailabilityChanged;
system.ConstructionGuideAvailable -= SystemGuideAvailable;
_constructionSystem = null;
}
}
+ 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)
// 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);
AddButton(EngineKeyFunctions.EditorGridPlace);
AddButton(EngineKeyFunctions.EditorLinePlace);
AddButton(EngineKeyFunctions.EditorRotateObject);
+ AddButton(ContentKeyFunctions.EditorFlipObject);
AddButton(ContentKeyFunctions.EditorCopyObject);
AddHeader("ui-options-header-dev");
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,
+}
public static readonly BoundKeyFunction Vote8 = "Vote8";
public static readonly BoundKeyFunction Vote9 = "Vote9";
public static readonly BoundKeyFunction EditorCopyObject = "EditorCopyObject";
+ public static readonly BoundKeyFunction EditorFlipObject = "EditorFlipObject";
}
}
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
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:
- 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
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:
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 {}
- function: EditorRotateObject
type: State
key: MouseMiddle
+- function: EditorFlipObject
+ type: State
+ key: MouseMiddle
+ mod1: Control
- function: EditorCopyObject
type: State
key: P