diff --git a/Content.Client/Construction/ConstructionSystem.cs b/Content.Client/Construction/ConstructionSystem.cs index e77a809a70..6e99f38591 100644 --- a/Content.Client/Construction/ConstructionSystem.cs +++ b/Content.Client/Construction/ConstructionSystem.cs @@ -30,6 +30,7 @@ namespace Content.Client.Construction [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private readonly Dictionary _ghosts = new(); + private readonly Dictionary _guideCache = new(); private int _nextId; @@ -42,6 +43,7 @@ namespace Content.Client.Construction SubscribeLocalEvent(HandlePlayerAttached); SubscribeNetworkEvent(HandleAckStructure); + SubscribeNetworkEvent(OnConstructionGuideReceived); CommandBinds.Builder .Bind(ContentKeyFunctions.OpenCraftingMenu, @@ -53,6 +55,12 @@ namespace Content.Client.Construction SubscribeLocalEvent(HandleConstructionGhostExamined); } + private void OnConstructionGuideReceived(ResponseConstructionGuide ev) + { + _guideCache[ev.ConstructionId] = ev.Guide; + ConstructionGuideAvailable?.Invoke(this, ev.ConstructionId); + } + /// public override void Shutdown() { @@ -61,6 +69,15 @@ namespace Content.Client.Construction CommandBinds.Unregister(); } + public ConstructionGuide? GetGuide(ConstructionPrototype prototype) + { + if (_guideCache.TryGetValue(prototype.ID, out var guide)) + return guide; + + RaiseNetworkEvent(new RequestConstructionGuide(prototype.ID)); + return null; + } + private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args) { if (component.Prototype == null) return; @@ -84,6 +101,7 @@ namespace Content.Client.Construction } public event EventHandler? CraftingAvailabilityChanged; + public event EventHandler? ConstructionGuideAvailable; public event EventHandler? ToggleCraftingWindow; private void HandleAckStructure(AckStructureConstructionMessage msg) diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs index 684f89afd8..d0f9c24666 100644 --- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs +++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs @@ -65,6 +65,9 @@ namespace Content.Client.Construction.UI _constructionView.MoveToFront(); else _constructionView.OpenCentered(); + + if(_selected != null) + PopulateInfo(_selected); } else _constructionView.Close(); @@ -224,95 +227,23 @@ namespace Content.Client.Construction.UI private void GenerateStepList(ConstructionPrototype prototype, ItemList stepList) { - if (!_prototypeManager.TryIndex(prototype.Graph, out ConstructionGraphPrototype? graph)) + if (_constructionSystem?.GetGuide(prototype) is not { } guide) return; - var startNode = graph.Nodes[prototype.StartNode]; - var targetNode = graph.Nodes[prototype.TargetNode]; - - if (!graph.TryPath(startNode.Name, targetNode.Name, out var path)) + foreach (var entry in guide.Entries) { - return; + var text = entry.Arguments != null + ? Loc.GetString(entry.Localization, entry.Arguments) : Loc.GetString(entry.Localization); + + if (entry.EntryNumber is {} number) + text = Loc.GetString("construction-presenter-step-wrapper", + ("step-number", number), ("text", text)); + + // The padding needs to be applied regardless of text length... (See PadLeft documentation) + text = text.PadLeft(text.Length + entry.Padding); + + stepList.AddItem(text, entry.Icon?.Frame0(), false); } - - var current = startNode; - var stepNumber = 1; - - foreach (var node in path) - { - if (!current.TryGetEdge(node.Name, out var edge)) - { - continue; - } - - var firstNode = current == startNode; - - if (firstNode) - { - stepList.AddItem(prototype.Type == ConstructionType.Item - ? Loc.GetString($"construction-presenter-to-craft", ("step-number", stepNumber++)) - : Loc.GetString($"construction-presenter-to-build", ("step-number", stepNumber++))); - } - - foreach (var step in edge.Steps) - { - var icon = GetTextureForStep(_resourceCache, step); - - switch (step) - { - case MaterialConstructionGraphStep materialStep: - stepList.AddItem( - !firstNode - ? Loc.GetString( - "construction-presenter-material-step", - ("step-number", stepNumber++), - ("amount", materialStep.Amount), - ("material", materialStep.MaterialPrototype.Name)) - : Loc.GetString( - "construction-presenter-material-first-step", - ("amount", materialStep.Amount), - ("material", materialStep.MaterialPrototype.Name)), - icon); - - break; - - case ToolConstructionGraphStep toolStep: - stepList.AddItem(Loc.GetString( - "construction-presenter-tool-step", - ("step-number", stepNumber++), - ("tool", Loc.GetString(_prototypeManager.Index(toolStep.Tool).ToolName))), - icon); - break; - - case ArbitraryInsertConstructionGraphStep arbitraryStep: - stepList.AddItem(Loc.GetString( - "construction-presenter-arbitrary-step", - ("step-number", stepNumber++), - ("name", arbitraryStep.Name)), - icon); - break; - } - } - - current = node; - } - } - - private Texture? GetTextureForStep(IResourceCache resourceCache, ConstructionGraphStep step) - { - switch (step) - { - case MaterialConstructionGraphStep materialStep: - return materialStep.MaterialPrototype.Icon?.Frame0(); - - case ToolConstructionGraphStep toolStep: - return _prototypeManager.Index(toolStep.Tool).Icon?.Frame0(); - - case ArbitraryInsertConstructionGraphStep arbitraryStep: - return arbitraryStep.Icon?.Frame0(); - } - - return null; } private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList) @@ -413,6 +344,7 @@ namespace Content.Client.Construction.UI _constructionSystem = system; system.ToggleCraftingWindow += SystemOnToggleMenu; system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged; + system.ConstructionGuideAvailable += SystemGuideAvailable; CraftingAvailable = system.CraftingEnabled; } @@ -425,6 +357,7 @@ namespace Content.Client.Construction.UI system.ToggleCraftingWindow -= SystemOnToggleMenu; system.CraftingAvailabilityChanged -= SystemCraftingAvailabilityChanged; + system.ConstructionGuideAvailable -= SystemGuideAvailable; _constructionSystem = null; } @@ -454,5 +387,19 @@ namespace Content.Client.Construction.UI _gameHud.CraftingButtonDown = true; // This does not call CraftingButtonToggled } } + + private void SystemGuideAvailable(object? sender, string e) + { + if (!CraftingAvailable) + return; + + if (!WindowOpen) + return; + + if (_selected == null) + return; + + PopulateInfo(_selected); + } } } diff --git a/Content.Server/Construction/Conditions/AirlockBolted.cs b/Content.Server/Construction/Conditions/AirlockBolted.cs index 7db627ba75..0314d208b6 100644 --- a/Content.Server/Construction/Conditions/AirlockBolted.cs +++ b/Content.Server/Construction/Conditions/AirlockBolted.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Content.Shared.Construction; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -34,13 +35,21 @@ namespace Content.Server.Construction.Conditions if (airlock.BoltsDown != Value) { if (Value == true) - args.PushMarkup(Loc.GetString("construction-condition-airlock-bolt", ("entityName", entity.Name)) + "\n"); + args.PushMarkup(Loc.GetString("construction-examine-condition-airlock-bolt", ("entityName", entity.Name)) + "\n"); else - args.PushMarkup(Loc.GetString("construction-condition-airlock-unbolt", ("entityName", entity.Name)) + "\n"); + args.PushMarkup(Loc.GetString("construction-examine-condition-airlock-unbolt", ("entityName", entity.Name)) + "\n"); return true; } return false; } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = Value ? "construction-step-condition-airlock-bolt" : "construction-step-condition-airlock-unbolt" + }; + } } } diff --git a/Content.Server/Construction/Conditions/AllConditions.cs b/Content.Server/Construction/Conditions/AllConditions.cs index b9591fbe3d..5f785a9442 100644 --- a/Content.Server/Construction/Conditions/AllConditions.cs +++ b/Content.Server/Construction/Conditions/AllConditions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Content.Shared.Construction; using Content.Shared.Examine; @@ -37,5 +38,16 @@ namespace Content.Server.Construction.Conditions return ret; } + + public IEnumerable GenerateGuideEntry() + { + foreach (var condition in Conditions) + { + foreach (var entry in condition.GenerateGuideEntry()) + { + yield return entry; + } + } + } } } diff --git a/Content.Server/Construction/Conditions/AllWiresCut.cs b/Content.Server/Construction/Conditions/AllWiresCut.cs index d957382b29..b07a8e3e05 100644 --- a/Content.Server/Construction/Conditions/AllWiresCut.cs +++ b/Content.Server/Construction/Conditions/AllWiresCut.cs @@ -1,10 +1,10 @@ -using System.Threading.Tasks; -using Content.Server.GameObjects.Components; +using System.Collections.Generic; using Content.Server.WireHacking; using Content.Shared.Construction; using Content.Shared.Examine; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Construction.Conditions @@ -37,6 +37,21 @@ namespace Content.Server.Construction.Conditions return true; } - // TODO CONSTRUCTION: Examine for this condition. + public bool DoExamine(ExaminedEvent args) + { + args.PushMarkup(Loc.GetString(Value + ? "construction-examine-condition-all-wires-cut" + : "construction-examine-condition-all-wires-intact")); + return true; + } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = Value ? "construction-guide-condition-all-wires-cut" + : "construction-guide-condition-all-wires-intact" + }; + } } } diff --git a/Content.Server/Construction/Conditions/AnyConditions.cs b/Content.Server/Construction/Conditions/AnyConditions.cs index 31d7408b83..c1430eab99 100644 --- a/Content.Server/Construction/Conditions/AnyConditions.cs +++ b/Content.Server/Construction/Conditions/AnyConditions.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; using Content.Shared.Construction; +using Content.Shared.Examine; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; namespace Content.Server.Construction.Conditions @@ -24,6 +27,33 @@ namespace Content.Server.Construction.Conditions return false; } - // TODO CONSTRUCTION: Examine for this condition. + public bool DoExamine(ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("construction-examine-condition-any-conditions")); + + foreach (var condition in Conditions) + { + condition.DoExamine(args); + } + + return true; + } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = "construction-guide-condition-any-conditions", + }; + + foreach (var condition in Conditions) + { + foreach (var entry in condition.GenerateGuideEntry()) + { + entry.Padding += 4; + yield return entry; + } + } + } } } diff --git a/Content.Server/Construction/Conditions/ComponentInTile.cs b/Content.Server/Construction/Conditions/ComponentInTile.cs index 4c22e7b51e..e75a30b887 100644 --- a/Content.Server/Construction/Conditions/ComponentInTile.cs +++ b/Content.Server/Construction/Conditions/ComponentInTile.cs @@ -1,12 +1,17 @@ -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; using Content.Shared.Construction; +using Content.Shared.Examine; using Content.Shared.Maps; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; namespace Content.Server.Construction.Conditions { @@ -24,6 +29,15 @@ namespace Content.Server.Construction.Conditions [DataField("hasEntity")] public bool HasEntity { get; private set; } + [DataField("examineText")] + public string? ExamineText { get; } + + [DataField("guideText")] + public string? GuideText { get; } + + [DataField("guideIcon")] + public SpriteSpecifier? GuideIcon { get; } + /// /// The component name in question. /// @@ -49,6 +63,25 @@ namespace Content.Server.Construction.Conditions return !HasEntity; } - // TODO CONSTRUCTION: Custom examine for this condition. + public bool DoExamine(ExaminedEvent args) + { + if (string.IsNullOrEmpty(ExamineText)) + return false; + + args.PushMarkup(Loc.GetString(ExamineText)); + return true; + } + + public IEnumerable GenerateGuideEntry() + { + if (string.IsNullOrEmpty(GuideText)) + yield break; + + yield return new ConstructionGuideEntry() + { + Localization = GuideText, + Icon = GuideIcon, + }; + } } } diff --git a/Content.Server/Construction/Conditions/ContainerEmpty.cs b/Content.Server/Construction/Conditions/ContainerEmpty.cs index 091b0fa745..9eeb81aafa 100644 --- a/Content.Server/Construction/Conditions/ContainerEmpty.cs +++ b/Content.Server/Construction/Conditions/ContainerEmpty.cs @@ -1,10 +1,12 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Content.Shared.Construction; using Content.Shared.Examine; using JetBrains.Annotations; using Robust.Server.Containers; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; @@ -14,8 +16,17 @@ namespace Content.Server.Construction.Conditions [DataDefinition] public class ContainerEmpty : IGraphCondition { - [DataField("container")] public string Container { get; private set; } = string.Empty; - [DataField("text")] public string Text { get; private set; } = string.Empty; + [DataField("container")] + public string Container { get; } = string.Empty; + + [DataField("examineText")] + public string? ExamineText { get; } + + [DataField("guideStep")] + public string? GuideText { get; } + + [DataField("guideIcon")] + public SpriteSpecifier? GuideIcon { get; } public bool Condition(EntityUid uid, IEntityManager entityManager) { @@ -28,6 +39,9 @@ namespace Content.Server.Construction.Conditions public bool DoExamine(ExaminedEvent args) { + if (string.IsNullOrEmpty(ExamineText)) + return false; + var entity = args.Examined; if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) || @@ -36,9 +50,19 @@ namespace Content.Server.Construction.Conditions if (container.ContainedEntities.Count == 0) return false; - args.PushMarkup(Text); + args.PushMarkup(Loc.GetString(ExamineText)); return true; + } + public IEnumerable GenerateGuideEntry() + { + if (string.IsNullOrEmpty(GuideText)) + yield break; + yield return new ConstructionGuideEntry() + { + Localization = GuideText, + Icon = GuideIcon, + }; } } } diff --git a/Content.Server/Construction/Conditions/ContainerNotEmpty.cs b/Content.Server/Construction/Conditions/ContainerNotEmpty.cs index 7c50f65780..3c384b498c 100644 --- a/Content.Server/Construction/Conditions/ContainerNotEmpty.cs +++ b/Content.Server/Construction/Conditions/ContainerNotEmpty.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Content.Shared.Construction; using Content.Shared.Examine; @@ -5,6 +6,7 @@ using JetBrains.Annotations; using Robust.Server.Containers; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; @@ -15,7 +17,9 @@ namespace Content.Server.Construction.Conditions public class ContainerNotEmpty : IGraphCondition { [DataField("container")] public string Container { get; private set; } = string.Empty; - [DataField("text")] public string Text { get; private set; } = string.Empty; + [DataField("examineText")] public string? ExamineText { get; } + [DataField("guideText")] public string? GuideText { get; } + [DataField("guideIcon")] public SpriteSpecifier? GuideIcon { get; } public bool Condition(EntityUid uid, IEntityManager entityManager) { @@ -28,6 +32,9 @@ namespace Content.Server.Construction.Conditions public bool DoExamine(ExaminedEvent args) { + if (ExamineText == null) + return false; + var entity = args.Examined; if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) || @@ -36,9 +43,20 @@ namespace Content.Server.Construction.Conditions if (container.ContainedEntities.Count != 0) return false; - args.PushMarkup(Text); + args.PushMarkup(Loc.GetString(ExamineText)); return true; + } + public IEnumerable GenerateGuideEntry() + { + if (GuideText == null) + yield break; + + yield return new ConstructionGuideEntry() + { + Localization = GuideText, + Icon = GuideIcon, + }; } } } diff --git a/Content.Server/Construction/Conditions/DoorWelded.cs b/Content.Server/Construction/Conditions/DoorWelded.cs index 135742ad3c..147b3a63ad 100644 --- a/Content.Server/Construction/Conditions/DoorWelded.cs +++ b/Content.Server/Construction/Conditions/DoorWelded.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Content.Server.Doors.Components; using Content.Shared.Construction; using Content.Shared.Examine; @@ -32,13 +33,23 @@ namespace Content.Server.Construction.Conditions if (door.IsWeldedShut != Welded) { if (Welded == true) - args.PushMarkup(Loc.GetString("construction-condition-door-weld", ("entityName", entity.Name)) + "\n"); + args.PushMarkup(Loc.GetString("construction-examine-condition-door-weld", ("entityName", entity.Name)) + "\n"); else - args.PushMarkup(Loc.GetString("construction-condition-door-unweld", ("entityName", entity.Name)) + "\n"); + args.PushMarkup(Loc.GetString("construction-examine-condition-door-unweld", ("entityName", entity.Name)) + "\n"); return true; } return false; } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = Welded + ? "construction-guide-condition-door-weld" + : "construction-guide-condition-door-unweld", + }; + } } } diff --git a/Content.Server/Construction/Conditions/EntityAnchored.cs b/Content.Server/Construction/Conditions/EntityAnchored.cs index fd8ff50ecf..688976cb28 100644 --- a/Content.Server/Construction/Conditions/EntityAnchored.cs +++ b/Content.Server/Construction/Conditions/EntityAnchored.cs @@ -1,10 +1,13 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Content.Shared.Construction; using Content.Shared.Examine; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Localization; using Robust.Shared.Physics; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; namespace Content.Server.Construction.Conditions { @@ -27,14 +30,25 @@ namespace Content.Server.Construction.Conditions switch (Anchored) { case true when !entity.Transform.Anchored: - args.PushMarkup("First, anchor it."); + args.PushMarkup(Loc.GetString("construction-examine-condition-entity-anchored")); return true; case false when entity.Transform.Anchored: - args.PushMarkup("First, unanchor it."); + args.PushMarkup(Loc.GetString("construction-examine-condition-entity-unanchored")); return true; } return false; } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = Anchored + ? "construction-step-condition-entity-anchored" + : "construction-step-condition-entity-unanchored", + Icon = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Tools/wrench.rsi"), "icon"), + }; + } } } diff --git a/Content.Server/Construction/Conditions/MachineFrameComplete.cs b/Content.Server/Construction/Conditions/MachineFrameComplete.cs index 5d17274be8..9b598be314 100644 --- a/Content.Server/Construction/Conditions/MachineFrameComplete.cs +++ b/Content.Server/Construction/Conditions/MachineFrameComplete.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.Construction.Components; using Content.Shared.Construction; @@ -17,6 +18,13 @@ namespace Content.Server.Construction.Conditions [DataDefinition] public class MachineFrameComplete : IGraphCondition { + [DataField("guideIconBoard")] + public SpriteSpecifier? GuideIconBoard { get; } + + [DataField("guideIconParts")] + public SpriteSpecifier? GuideIconPart { get; } + + public bool Condition(EntityUid uid, IEntityManager entityManager) { if (!entityManager.TryGetComponent(uid, out MachineFrameComponent? machineFrame)) @@ -91,5 +99,22 @@ namespace Content.Server.Construction.Conditions return true; } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = "construction-step-condition-machine-frame-board", + Icon = GuideIconBoard, + EntryNumber = 0, // Set this to anything so the guide generation takes this as a numbered step. + }; + + yield return new ConstructionGuideEntry() + { + Localization = "construction-step-condition-machine-frame-parts", + Icon = GuideIconPart, + EntryNumber = 0, // Set this to anything so the guide generation takes this as a numbered step. + }; + } } } diff --git a/Content.Server/Construction/Conditions/ToiletLidClosed.cs b/Content.Server/Construction/Conditions/ToiletLidClosed.cs index 81384c3a3d..490113cb83 100644 --- a/Content.Server/Construction/Conditions/ToiletLidClosed.cs +++ b/Content.Server/Construction/Conditions/ToiletLidClosed.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Content.Server.Toilet; using Content.Shared.Construction; using Content.Shared.Examine; @@ -27,8 +28,16 @@ namespace Content.Server.Construction.Conditions if (!entity.TryGetComponent(out ToiletComponent? toilet)) return false; if (!toilet.LidOpen) return false; - args.PushMarkup(Loc.GetString("construction-condition-toilet-lid-closed") + "\n"); + args.PushMarkup(Loc.GetString("construction-examine-condition-toilet-lid-closed") + "\n"); return true; } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = "construction-step-condition-toilet-lid-closed" + }; + } } } diff --git a/Content.Server/Construction/Conditions/WirePanel.cs b/Content.Server/Construction/Conditions/WirePanel.cs index 5fb7813a60..7f9d12a9ae 100644 --- a/Content.Server/Construction/Conditions/WirePanel.cs +++ b/Content.Server/Construction/Conditions/WirePanel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Content.Server.GameObjects.Components; using Content.Server.WireHacking; @@ -34,14 +35,24 @@ namespace Content.Server.Construction.Conditions switch (Open) { case true when !wires.IsPanelOpen: - args.PushMarkup(Loc.GetString("construction-condition-wire-panel-open")); + args.PushMarkup(Loc.GetString("construction-examine-condition-wire-panel-open")); return true; case false when wires.IsPanelOpen: - args.PushMarkup(Loc.GetString("construction-condition-wire-panel-close")); + args.PushMarkup(Loc.GetString("construction-examine-condition-wire-panel-close")); return true; } return false; } + + public IEnumerable GenerateGuideEntry() + { + yield return new ConstructionGuideEntry() + { + Localization = Open + ? "construction-step-condition-wire-panel-open" + : "construction-step-condition-wire-panel-close" + }; + } } } diff --git a/Content.Server/Construction/ConstructionSystem.Guided.cs b/Content.Server/Construction/ConstructionSystem.Guided.cs new file mode 100644 index 0000000000..d0ba3c5909 --- /dev/null +++ b/Content.Server/Construction/ConstructionSystem.Guided.cs @@ -0,0 +1,228 @@ +using System.Collections.Generic; +using Content.Server.Construction.Components; +using Content.Shared.Construction; +using Content.Shared.Construction.Prototypes; +using Content.Shared.Construction.Steps; +using Content.Shared.Examine; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.Construction +{ + public partial class ConstructionSystem + { + private readonly Dictionary _guideCache = new(); + + private void InitializeGuided() + { + SubscribeNetworkEvent(OnGuideRequested); + SubscribeLocalEvent(AddDeconstructVerb); + SubscribeLocalEvent(HandleConstructionExamined); + } + + private void OnGuideRequested(RequestConstructionGuide msg, EntitySessionEventArgs args) + { + if (!_prototypeManager.TryIndex(msg.ConstructionId, out ConstructionPrototype? prototype)) + return; + + if(GetGuide(prototype) is {} guide) + RaiseNetworkEvent(new ResponseConstructionGuide(msg.ConstructionId, guide), args.SenderSession.ConnectedClient); + } + + private void AddDeconstructVerb(EntityUid uid, ConstructionComponent component, GetOtherVerbsEvent args) + { + if (!args.CanAccess) + return; + + if (component.TargetNode == component.DeconstructionNode || + component.Node == component.DeconstructionNode) + return; + + Verb verb = new(); + //verb.Category = VerbCategories.Construction; + //TODO VERBS add more construction verbs? Until then, removing construction category + verb.Text = Loc.GetString("deconstructible-verb-begin-deconstruct"); + verb.IconTexture = "/Textures/Interface/hammer_scaled.svg.192dpi.png"; + + verb.Act = () => + { + SetPathfindingTarget(uid, component.DeconstructionNode, component); + if (component.TargetNode == null) + { + // Maybe check, but on the flip-side a better solution might be to not make it undeconstructible in the first place, no? + component.Owner.PopupMessage(args.User, Loc.GetString("deconstructible-verb-activate-no-target-text")); + } + else + { + component.Owner.PopupMessage(args.User, Loc.GetString("deconstructible-verb-activate-text")); + } + }; + + args.Verbs.Add(verb); + } + + private void HandleConstructionExamined(EntityUid uid, ConstructionComponent component, ExaminedEvent args) + { + if (GetTargetNode(uid, component) is {} target) + { + args.PushMarkup(Loc.GetString( + "construction-component-to-create-header", + ("targetName", target.Name)) + "\n"); + } + + if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge) + { + var preventStepExamine = false; + + foreach (var condition in targetEdge.Conditions) + { + preventStepExamine |= condition.DoExamine(args); + } + + if (!preventStepExamine) + targetEdge.Steps[0].DoExamine(args); + return; + } + + if (GetCurrentEdge(uid, component) is {} edge) + { + var preventStepExamine = false; + + foreach (var condition in edge.Conditions) + { + preventStepExamine |= condition.DoExamine(args); + } + + if (preventStepExamine) return; + } + } + + + private ConstructionGuide? GetGuide(ConstructionPrototype construction) + { + // NOTE: This method might be allocate a fair bit, but do not worry! + // This method is specifically designed to generate guides once and cache the results, + // therefore we don't need to worry *too much* about the performance of this. + + // If we've generated and cached this guide before, return it. + if (_guideCache.TryGetValue(construction, out var guide)) + return guide; + + // If the graph doesn't actually exist, do nothing. + if (!_prototypeManager.TryIndex(construction.Graph, out ConstructionGraphPrototype? graph)) + return null; + + // If either the start node or the target node are missing, do nothing. + if (GetNodeFromGraph(graph, construction.StartNode) is not {} startNode + || GetNodeFromGraph(graph, construction.TargetNode) is not {} targetNode) + return null; + + // If there's no path from start to target, do nothing. + if (graph.Path(construction.StartNode, construction.TargetNode) is not {} path + || path.Length == 0) + return null; + + var step = 1; + + var entries = new List() + { + // Initial construction header. + new() + { + Localization = construction.Type == ConstructionType.Structure + ? "construction-presenter-to-build" : "construction-presenter-to-craft", + EntryNumber = step, + } + }; + + var conditions = new HashSet(); + + // Iterate until the penultimate node. + var node = startNode; + var index = 0; + while(node != targetNode) + { + // Can't find path, therefore can't generate guide... + if (!node.TryGetEdge(path[index].Name, out var edge)) + return null; + + // First steps are handled specially. + if (step == 1) + { + foreach (var graphStep in edge.Steps) + { + // This graph is invalid, we only allow insert steps as the initial construction steps. + if (graphStep is not EntityInsertConstructionGraphStep insertStep) + return null; + + entries.Add(insertStep.GenerateGuideEntry()); + } + + // Now actually list the construction conditions. + foreach (var condition in construction.Conditions) + { + if (condition.GenerateGuideEntry() is not {} conditionEntry) + continue; + + conditionEntry.Padding += 4; + entries.Add(conditionEntry); + } + + step++; + node = path[index++]; + + // Add a bit of padding if there will be more steps after this. + if(node != targetNode) + entries.Add(new ConstructionGuideEntry()); + + continue; + } + + var old = conditions; + conditions = new HashSet(); + + foreach (var condition in edge.Conditions) + { + foreach (var conditionEntry in condition.GenerateGuideEntry()) + { + conditions.Add(conditionEntry.Localization); + + // Okay so if the condition entry had a non-null value here, we take it as a numbered step. + // This is for cases where there is a lot of snowflake behavior, such as machine frames... + // So that the step of inserting a machine board and inserting all of its parts is numbered. + if (conditionEntry.EntryNumber != null) + conditionEntry.EntryNumber = step++; + + // To prevent spamming the same stuff over and over again. This is a bit naive, but..ye. + // Also we will only hide this condition *if* it isn't numbered. + else + { + if (old.Contains(conditionEntry.Localization)) + continue; + + // We only add padding for non-numbered entries. + conditionEntry.Padding += 4; + } + + entries.Add(conditionEntry); + } + } + + foreach (var graphStep in edge.Steps) + { + var entry = graphStep.GenerateGuideEntry(); + entry.EntryNumber = step++; + entries.Add(entry); + } + + node = path[index++]; + } + + guide = new ConstructionGuide(entries.ToArray()); + _guideCache[construction] = guide; + return guide; + } + } +} diff --git a/Content.Server/Construction/ConstructionSystem.Interactions.cs b/Content.Server/Construction/ConstructionSystem.Interactions.cs index 969d30ee5c..76c5aebf59 100644 --- a/Content.Server/Construction/ConstructionSystem.Interactions.cs +++ b/Content.Server/Construction/ConstructionSystem.Interactions.cs @@ -14,7 +14,7 @@ namespace Content.Server.Construction { private readonly HashSet _constructionUpdateQueue = new(); - private void InitializeSteps() + private void InitializeInteractions() { #region DoAfter Subscriptions diff --git a/Content.Server/Construction/ConstructionSystem.cs b/Content.Server/Construction/ConstructionSystem.cs index 01688362d2..7aee035026 100644 --- a/Content.Server/Construction/ConstructionSystem.cs +++ b/Content.Server/Construction/ConstructionSystem.cs @@ -39,13 +39,12 @@ namespace Content.Server.Construction _sawmill = _logManager.GetSawmill(SawmillName); InitializeGraphs(); - InitializeSteps(); + InitializeGuided(); + InitializeInteractions(); InitializeInitial(); SubscribeLocalEvent(OnConstructionInit); SubscribeLocalEvent(OnConstructionStartup); - SubscribeLocalEvent(AddDeconstructVerb); - SubscribeLocalEvent(HandleConstructionExamined); } private void OnConstructionInit(EntityUid uid, ConstructionComponent construction, ComponentInit args) @@ -94,74 +93,6 @@ namespace Content.Server.Construction PerformActions(uid, null, node.Actions); } - private void AddDeconstructVerb(EntityUid uid, ConstructionComponent component, GetOtherVerbsEvent args) - { - if (!args.CanAccess) - return; - - if (component.TargetNode == component.DeconstructionNode || - component.Node == component.DeconstructionNode) - return; - - Verb verb = new(); - //verb.Category = VerbCategories.Construction; - //TODO VERBS add more construction verbs? Until then, removing construction category - verb.Text = Loc.GetString("deconstructible-verb-begin-deconstruct"); - verb.IconTexture = "/Textures/Interface/hammer_scaled.svg.192dpi.png"; - - verb.Act = () => - { - SetPathfindingTarget(uid, component.DeconstructionNode, component); - if (component.TargetNode == null) - { - // Maybe check, but on the flip-side a better solution might be to not make it undeconstructible in the first place, no? - component.Owner.PopupMessage(args.User, Loc.GetString("deconstructible-verb-activate-no-target-text")); - } - else - { - component.Owner.PopupMessage(args.User, Loc.GetString("deconstructible-verb-activate-text")); - } - }; - - args.Verbs.Add(verb); - } - - private void HandleConstructionExamined(EntityUid uid, ConstructionComponent component, ExaminedEvent args) - { - if (GetTargetNode(uid, component) is {} target) - { - args.PushMarkup(Loc.GetString( - "construction-component-to-create-header", - ("targetName", target.Name)) + "\n"); - } - - if (component.EdgeIndex == null && GetTargetEdge(uid, component) is {} targetEdge) - { - var preventStepExamine = false; - - foreach (var condition in targetEdge.Conditions) - { - preventStepExamine |= condition.DoExamine(args); - } - - if (!preventStepExamine) - targetEdge.Steps[0].DoExamine(args); - return; - } - - if (GetCurrentEdge(uid, component) is {} edge) - { - var preventStepExamine = false; - - foreach (var condition in edge.Conditions) - { - preventStepExamine |= condition.DoExamine(args); - } - - if (preventStepExamine) return; - } - } - public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs b/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs index cfe564862e..cce825c205 100644 --- a/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs +++ b/Content.Shared/Construction/Conditions/EmptyOrWindowValidInTile.cs @@ -1,4 +1,5 @@ -using Content.Shared.Maps; +using System.Collections.Generic; +using Content.Shared.Maps; using Content.Shared.Window; using JetBrains.Annotations; using Robust.Shared.GameObjects; @@ -30,5 +31,13 @@ namespace Content.Shared.Construction.Conditions return result; } + + public ConstructionGuideEntry? GenerateGuideEntry() + { + return new ConstructionGuideEntry() + { + Localization = "construction-guide-condition-empty-or-window-valid-in-tile" + }; + } } } diff --git a/Content.Shared/Construction/Conditions/IConstructionCondition.cs b/Content.Shared/Construction/Conditions/IConstructionCondition.cs index 7b632dc1b1..44f9084587 100644 --- a/Content.Shared/Construction/Conditions/IConstructionCondition.cs +++ b/Content.Shared/Construction/Conditions/IConstructionCondition.cs @@ -6,6 +6,7 @@ namespace Content.Shared.Construction.Conditions { public interface IConstructionCondition { + ConstructionGuideEntry? GenerateGuideEntry(); bool Condition(IEntity user, EntityCoordinates location, Direction direction); } } diff --git a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs index 11805d7cb5..72a6e13981 100644 --- a/Content.Shared/Construction/Conditions/NoWindowsInTile.cs +++ b/Content.Shared/Construction/Conditions/NoWindowsInTile.cs @@ -22,5 +22,13 @@ namespace Content.Shared.Construction.Conditions return true; } + + public ConstructionGuideEntry? GenerateGuideEntry() + { + return new ConstructionGuideEntry() + { + Localization = "construction-step-condition-no-windows-in-tile" + }; + } } } diff --git a/Content.Shared/Construction/Conditions/TileNotBlocked.cs b/Content.Shared/Construction/Conditions/TileNotBlocked.cs index 5ad9b06461..190c6c7731 100644 --- a/Content.Shared/Construction/Conditions/TileNotBlocked.cs +++ b/Content.Shared/Construction/Conditions/TileNotBlocked.cs @@ -23,5 +23,13 @@ namespace Content.Shared.Construction.Conditions return !tileRef.Value.IsBlockedTurf(_filterMobs); } + + public ConstructionGuideEntry? GenerateGuideEntry() + { + return new ConstructionGuideEntry() + { + Localization = "construction-step-condition-tile-not-blocked", + }; + } } } diff --git a/Content.Shared/Construction/Conditions/TileType.cs b/Content.Shared/Construction/Conditions/TileType.cs index c57da7a87e..41b5621ce7 100644 --- a/Content.Shared/Construction/Conditions/TileType.cs +++ b/Content.Shared/Construction/Conditions/TileType.cs @@ -5,6 +5,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Utility; namespace Content.Shared.Construction.Conditions { @@ -12,7 +13,14 @@ namespace Content.Shared.Construction.Conditions [DataDefinition] public class TileType : IConstructionCondition { - [DataField("targets")] public List TargetTiles { get; private set; } = new(); + [DataField("targets")] + public List TargetTiles { get; } = new(); + + [DataField("guideText")] + public string? GuideText = null; + + [DataField("guideIcon")] + public SpriteSpecifier? GuideIcon = null; public bool Condition(IEntity user, EntityCoordinates location, Direction direction) { @@ -32,5 +40,17 @@ namespace Content.Shared.Construction.Conditions } return false; } + + public ConstructionGuideEntry? GenerateGuideEntry() + { + if (GuideText == null) + return null; + + return new ConstructionGuideEntry() + { + Localization = GuideText, + Icon = GuideIcon, + }; + } } } diff --git a/Content.Shared/Construction/Conditions/WallmountCondition.cs b/Content.Shared/Construction/Conditions/WallmountCondition.cs index 31169b462a..1a29d6a4bb 100644 --- a/Content.Shared/Construction/Conditions/WallmountCondition.cs +++ b/Content.Shared/Construction/Conditions/WallmountCondition.cs @@ -50,5 +50,13 @@ namespace Content.Shared.Construction.Conditions predicate: (e) => e == targetWall || !e.HasTag("Wall")); return !adjWallRaycastResults.Any(); } + + public ConstructionGuideEntry? GenerateGuideEntry() + { + return new ConstructionGuideEntry() + { + Localization = "construction-step-condition-wallmount", + }; + } } } diff --git a/Content.Shared/Construction/ConstructionGuide.cs b/Content.Shared/Construction/ConstructionGuide.cs new file mode 100644 index 0000000000..f01703925e --- /dev/null +++ b/Content.Shared/Construction/ConstructionGuide.cs @@ -0,0 +1,27 @@ +using System; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared.Construction +{ + [Serializable, NetSerializable] + public sealed class ConstructionGuide + { + public readonly ConstructionGuideEntry[] Entries; + + public ConstructionGuide(ConstructionGuideEntry[] entries) + { + Entries = entries; + } + } + + [Serializable, NetSerializable] + public sealed class ConstructionGuideEntry + { + public int? EntryNumber { get; set; } = null; + public int Padding { get; set; } = 0; + public string Localization { get; set; } = string.Empty; + public (string, object)[]? Arguments { get; set; } = null; + public SpriteSpecifier? Icon { get; set; } = null; + } +} diff --git a/Content.Shared/Construction/IGraphCondition.cs b/Content.Shared/Construction/IGraphCondition.cs index 2a7c11a358..1fec216194 100644 --- a/Content.Shared/Construction/IGraphCondition.cs +++ b/Content.Shared/Construction/IGraphCondition.cs @@ -1,4 +1,5 @@ -using Content.Shared.Examine; +using System.Collections.Generic; +using Content.Shared.Examine; using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; @@ -8,6 +9,7 @@ namespace Content.Shared.Construction public interface IGraphCondition { bool Condition(EntityUid uid, IEntityManager entityManager); - bool DoExamine(ExaminedEvent args) { return false; } + bool DoExamine(ExaminedEvent args); + IEnumerable GenerateGuideEntry(); } } diff --git a/Content.Shared/Construction/SharedConstructionSystem.cs b/Content.Shared/Construction/SharedConstructionSystem.cs index 6472e58265..6052518110 100644 --- a/Content.Shared/Construction/SharedConstructionSystem.cs +++ b/Content.Shared/Construction/SharedConstructionSystem.cs @@ -60,7 +60,7 @@ namespace Content.Shared.Construction } /// - /// Send server -> client to tell the client that a ghost has started to be constructed. + /// Sent server -> client to tell the client that a ghost has started to be constructed. /// [Serializable, NetSerializable] public class AckStructureConstructionMessage : EntityEventArgs @@ -72,5 +72,35 @@ namespace Content.Shared.Construction GhostId = ghostId; } } + + /// + /// Sent client -> server to request a specific construction guide. + /// + [Serializable, NetSerializable] + public class RequestConstructionGuide : EntityEventArgs + { + public readonly string ConstructionId; + + public RequestConstructionGuide(string constructionId) + { + ConstructionId = constructionId; + } + } + + /// + /// Sent server -> client as a response to a net message. + /// + [Serializable, NetSerializable] + public class ResponseConstructionGuide : EntityEventArgs + { + public readonly string ConstructionId; + public readonly ConstructionGuide Guide; + + public ResponseConstructionGuide(string constructionId, ConstructionGuide guide) + { + ConstructionId = constructionId; + Guide = guide; + } + } } } diff --git a/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs b/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs index e5341affbf..c7962c5eb9 100644 --- a/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/ArbitraryInsertConstructionGraphStep.cs @@ -9,7 +9,7 @@ namespace Content.Shared.Construction.Steps { [DataField("name")] public string Name { get; private set; } = string.Empty; - [DataField("icon")] public SpriteSpecifier Icon { get; private set; } = SpriteSpecifier.Invalid; + [DataField("icon")] public SpriteSpecifier? Icon { get; private set; } = null; public override void DoExamine(ExaminedEvent examinedEvent) { @@ -18,5 +18,15 @@ namespace Content.Shared.Construction.Steps examinedEvent.Message.AddMarkup(Loc.GetString("construction-insert-arbitrary-entity", ("stepName", Name))); } + + public override ConstructionGuideEntry GenerateGuideEntry() + { + return new ConstructionGuideEntry() + { + Localization = "construction-presenter-arbitrary-step", + Arguments = new (string, object)[]{("name", Name)}, + Icon = Icon, + }; + } } } diff --git a/Content.Shared/Construction/Steps/ConstructionGraphStep.cs b/Content.Shared/Construction/Steps/ConstructionGraphStep.cs index ca77db1522..40e83ebdf3 100644 --- a/Content.Shared/Construction/Steps/ConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/ConstructionGraphStep.cs @@ -16,5 +16,6 @@ namespace Content.Shared.Construction.Steps public IReadOnlyList Completed => _completed; public abstract void DoExamine(ExaminedEvent examinedEvent); + public abstract ConstructionGuideEntry GenerateGuideEntry(); } } diff --git a/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs b/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs index 175394bc78..e799bdc87e 100644 --- a/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/MaterialConstructionGraphStep.cs @@ -6,6 +6,7 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Manager.Attributes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Construction.Steps { @@ -14,16 +15,16 @@ namespace Content.Shared.Construction.Steps { // TODO: Make this use the material system. // TODO TODO: Make the material system not shit. - [DataField("material")] public string MaterialPrototypeId { get; } = "Steel"; + [DataField("material", required:true, customTypeSerializer:typeof(PrototypeIdSerializer))] + public string MaterialPrototypeId { get; } = "Steel"; [DataField("amount")] public int Amount { get; } = 1; - public StackPrototype MaterialPrototype => - IoCManager.Resolve().Index(MaterialPrototypeId); - public override void DoExamine(ExaminedEvent examinedEvent) { - examinedEvent.Message.AddMarkup(Loc.GetString("construction-insert-material-entity", ("amount", Amount), ("materialName", MaterialPrototype.Name))); + var material = IoCManager.Resolve().Index(MaterialPrototypeId); + + examinedEvent.Message.AddMarkup(Loc.GetString("construction-insert-material-entity", ("amount", Amount), ("materialName", material.Name))); } public override bool EntityValid(IEntity entity) @@ -40,5 +41,17 @@ namespace Content.Shared.Construction.Steps return stack != null; } + + public override ConstructionGuideEntry GenerateGuideEntry() + { + var material = IoCManager.Resolve().Index(MaterialPrototypeId); + + return new ConstructionGuideEntry() + { + Localization = "construction-presenter-material-step", + Arguments = new (string, object)[]{("amount", Amount), ("material", material.Name)}, + Icon = material.Icon, + }; + } } } diff --git a/Content.Shared/Construction/Steps/ToolConstructionGraphStep.cs b/Content.Shared/Construction/Steps/ToolConstructionGraphStep.cs index e4396d9a3a..c5bf4b8dad 100644 --- a/Content.Shared/Construction/Steps/ToolConstructionGraphStep.cs +++ b/Content.Shared/Construction/Steps/ToolConstructionGraphStep.cs @@ -35,5 +35,17 @@ namespace Content.Shared.Construction.Steps examinedEvent.PushMarkup(Loc.GetString("construction-use-tool-entity", ("toolName", Loc.GetString(quality.ToolName)))); } + + public override ConstructionGuideEntry GenerateGuideEntry() + { + var quality = IoCManager.Resolve().Index(Tool); + + return new ConstructionGuideEntry() + { + Localization = "construction-presenter-tool-step", + Arguments = new (string, object)[]{("tool", quality.ToolName)}, + Icon = quality.Icon, + }; + } } } diff --git a/Content.Shared/Localizations/Localization.cs b/Content.Shared/Localizations/Localization.cs index 6b60cf5f8d..c089f1d532 100644 --- a/Content.Shared/Localizations/Localization.cs +++ b/Content.Shared/Localizations/Localization.cs @@ -34,6 +34,14 @@ namespace Content.Shared.Localizations loc.AddFunction(culture, "POWERWATTS", FormatPowerWatts); loc.AddFunction(culture, "POWERJOULES", FormatPowerJoules); loc.AddFunction(culture, "TOSTRING", args => FormatToString(culture, args)); + loc.AddFunction(culture, "LOC", FormatLoc); + } + + private static ILocValue FormatLoc(LocArgs args) + { + var id = ((LocValueString)args.Args[0]).Value; + + return new LocValueString(Loc.GetString(id)); } private static ILocValue FormatToString(CultureInfo culture, LocArgs args) diff --git a/Resources/Locale/en-US/construction/conditions/airlock-bolted.ftl b/Resources/Locale/en-US/construction/conditions/airlock-bolted.ftl index 4ba7be4705..c4d37db439 100644 --- a/Resources/Locale/en-US/construction/conditions/airlock-bolted.ftl +++ b/Resources/Locale/en-US/construction/conditions/airlock-bolted.ftl @@ -1,3 +1,5 @@ # AirlockBolted -construction-condition-airlock-bolt = First, bolt the {$entityName}. -construction-condition-airlock-unbolt = First, unbolt the {$entityName}. \ No newline at end of file +construction-examine-condition-airlock-bolt = First, bolt the {$entityName}. +construction-examine-condition-airlock-unbolt = First, unbolt the {$entityName}. +construction-step-condition-airlock-bolt = It must be bolted. +construction-step-condition-airlock-unbolt = It must be unbolted. diff --git a/Resources/Locale/en-US/construction/conditions/all-wires-cut.ftl b/Resources/Locale/en-US/construction/conditions/all-wires-cut.ftl new file mode 100644 index 0000000000..1911e50340 --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/all-wires-cut.ftl @@ -0,0 +1,4 @@ +construction-examine-condition-all-wires-cut = All of its wires must be cut. +construction-examine-condition-all-wires-intact = All of its wires must be intact. +construction-guide-condition-all-wires-cut = All of its wires must be cut. +construction-guide-condition-all-wires-intact = All of its wires must be intact. diff --git a/Resources/Locale/en-US/construction/conditions/any-conditions.ftl b/Resources/Locale/en-US/construction/conditions/any-conditions.ftl new file mode 100644 index 0000000000..f4a4ff1519 --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/any-conditions.ftl @@ -0,0 +1,2 @@ +construction-examine-condition-any-conditions = Any of these conditions must be true: +construction-guide-condition-any-conditions = Any of the conditions below must be true diff --git a/Resources/Locale/en-US/construction/conditions/door-welded.ftl b/Resources/Locale/en-US/construction/conditions/door-welded.ftl index db0c561e5b..cd6806df8f 100644 --- a/Resources/Locale/en-US/construction/conditions/door-welded.ftl +++ b/Resources/Locale/en-US/construction/conditions/door-welded.ftl @@ -1,3 +1,5 @@ # DoorWelded -construction-condition-door-weld = First, weld the {$entityName}. -construction-condition-door-unweld = First, unweld the {$entityName}. \ No newline at end of file +construction-examine-condition-door-weld = First, weld the {$entityName}. +construction-examine-condition-door-unweld = First, unweld the {$entityName}. +construction-guide-condition-door-weld = Make sure it is welded. +construction-guide-condition-door-unweld = Make sure it is not welded. diff --git a/Resources/Locale/en-US/construction/conditions/empty-or-window-valid-in-tile.ftl b/Resources/Locale/en-US/construction/conditions/empty-or-window-valid-in-tile.ftl new file mode 100644 index 0000000000..f678dad94d --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/empty-or-window-valid-in-tile.ftl @@ -0,0 +1 @@ +construction-guide-condition-empty-or-window-valid-in-tile = You must place this on a valid tile. diff --git a/Resources/Locale/en-US/construction/conditions/entity-anchored.ftl b/Resources/Locale/en-US/construction/conditions/entity-anchored.ftl new file mode 100644 index 0000000000..3d924cd1c1 --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/entity-anchored.ftl @@ -0,0 +1,4 @@ +construction-examine-condition-entity-anchored = First, anchor it. +construction-examine-condition-entity-unanchored = First, unanchor it. +construction-step-condition-entity-anchored = It must be anchored. +construction-step-condition-entity-unanchored = It must be unanchored. diff --git a/Resources/Locale/en-US/construction/conditions/machine-frame-complete.ftl b/Resources/Locale/en-US/construction/conditions/machine-frame-complete.ftl index d251775be4..bc2b841535 100644 --- a/Resources/Locale/en-US/construction/conditions/machine-frame-complete.ftl +++ b/Resources/Locale/en-US/construction/conditions/machine-frame-complete.ftl @@ -1,4 +1,6 @@ # MachineFrameComplete construction-condition-machine-frame-requirement-label = Requires: construction-condition-machine-frame-insert-circuit-board-message = Insert [color=cyan]any machine circuit board[/color]. -construction-condition-machine-frame-required-element-entry = [color=yellow]{$amount}x[/color] [color=green]{$elementName}[/color] \ No newline at end of file +construction-condition-machine-frame-required-element-entry = [color=yellow]{$amount}x[/color] [color=green]{$elementName}[/color] +construction-step-condition-machine-frame-board = You will need to insert a machine board. +construction-step-condition-machine-frame-parts = Afterwards, insert all required parts. diff --git a/Resources/Locale/en-US/construction/conditions/no-windows-in-tile.ftl b/Resources/Locale/en-US/construction/conditions/no-windows-in-tile.ftl new file mode 100644 index 0000000000..f3abcd2500 --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/no-windows-in-tile.ftl @@ -0,0 +1 @@ +construction-step-condition-no-windows-in-tile = There can be no windows in that tile. diff --git a/Resources/Locale/en-US/construction/conditions/tile-not-blocked.ftl b/Resources/Locale/en-US/construction/conditions/tile-not-blocked.ftl new file mode 100644 index 0000000000..8dd3f6a0d8 --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/tile-not-blocked.ftl @@ -0,0 +1 @@ +construction-step-condition-tile-not-blocked = The tile must not be obstructed. diff --git a/Resources/Locale/en-US/construction/conditions/toilet-lid-closed.ftl b/Resources/Locale/en-US/construction/conditions/toilet-lid-closed.ftl index 17d24728cc..d5096aaff5 100644 --- a/Resources/Locale/en-US/construction/conditions/toilet-lid-closed.ftl +++ b/Resources/Locale/en-US/construction/conditions/toilet-lid-closed.ftl @@ -1,2 +1,3 @@ # ToiletLidClosed -construction-condition-toilet-lid-closed = Use a [color=yellow]crowbar[/color] to close the lid. \ No newline at end of file +construction-examine-condition-toilet-lid-closed = Use a [color=yellow]crowbar[/color] to close the lid. +construction-step-condition-toilet-lid-closed = Make sure the toilet lid is closed. diff --git a/Resources/Locale/en-US/construction/conditions/wallmount.ftl b/Resources/Locale/en-US/construction/conditions/wallmount.ftl new file mode 100644 index 0000000000..691e86636e --- /dev/null +++ b/Resources/Locale/en-US/construction/conditions/wallmount.ftl @@ -0,0 +1 @@ +construction-step-condition-wallmount = You must build it on a wall. diff --git a/Resources/Locale/en-US/construction/conditions/wire-panel.ftl b/Resources/Locale/en-US/construction/conditions/wire-panel.ftl index c9b9ecec1e..09081cec69 100644 --- a/Resources/Locale/en-US/construction/conditions/wire-panel.ftl +++ b/Resources/Locale/en-US/construction/conditions/wire-panel.ftl @@ -1,3 +1,5 @@ # WirePanel -construction-condition-wire-panel-open = First, open the maintenance panel. -construction-condition-wire-panel-close = First, close the maintenance panel. \ No newline at end of file +construction-examine-condition-wire-panel-open = First, open the maintenance panel. +construction-examine-condition-wire-panel-close = First, close the maintenance panel. +construction-step-condition-wire-panel-open = The maintenance panel must be open. +construction-step-condition-wire-panel-close = The maintenance panel must be closed. diff --git a/Resources/Locale/en-US/construction/ui/construction-menu-presenter.ftl b/Resources/Locale/en-US/construction/ui/construction-menu-presenter.ftl index 30115fd8df..0684638ca3 100644 --- a/Resources/Locale/en-US/construction/ui/construction-menu-presenter.ftl +++ b/Resources/Locale/en-US/construction/ui/construction-menu-presenter.ftl @@ -1,15 +1,9 @@ construction-presenter-category-all = All -construction-presenter-to-craft = {$step-number}. To craft this item, you need: -construction-presenter-to-build = {$step-number}. To build this, first you need: +construction-presenter-to-craft = To craft this item, you need to: +construction-presenter-to-build = To build this, first you need to: -construction-presenter-material-first-step = {$amount}x {$material} -construction-presenter-material-step = {$step-number}. Add {$amount}x {$material}. -construction-presenter-material-substep = {$step-number}.{$parallel-number}.{$substep-number}. Add {$amount}x {$material}. +construction-presenter-step-wrapper = {$step-number}. {$text} -construction-presenter-tool-step = {$step-number}. Use a {$tool}. -construction-presenter-tool-substep = {$step-number}.{$parallel-number}.{$substep-number}. Use a {$tool}. - -construction-presenter-arbitrary-step = {$step-number}. Add {$name}. -construction-presenter-arbitrary-substep = {$step-number}.{$parallel-number}.{$substep-number}. Add {$name}. - -construction-presenter-nested-step = {$step-number}. In parallel... +construction-presenter-tool-step = Use a {LOC($tool)}. +construction-presenter-material-step = Add {$amount}x {LOC($material)}. +construction-presenter-arbitrary-step = Add {LOC($name)}. diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/machine.yml b/Resources/Prototypes/Recipes/Construction/Graphs/machine.yml index 0c59465441..b5ad5d548f 100644 --- a/Resources/Prototypes/Recipes/Construction/Graphs/machine.yml +++ b/Resources/Prototypes/Recipes/Construction/Graphs/machine.yml @@ -51,6 +51,12 @@ conditions: - !type:EntityAnchored - !type:MachineFrameComplete + guideIconBoard: + sprite: Objects/Misc/module.rsi + state: id_mod + guideIconParts: + sprite: Objects/Misc/stock_parts.rsi + state: scan_module completed: # Yes, this is snowflaked so we don't have to make an unique graph per machine. You're welcome. - !type:BuildMachine