Upgradeable machines. (#2675)

* Start work on upgradeable machines.

* Upgradeable machines work

* Component requirements for upgradeable machines

* Better container handling

* Remember to not push submodule updates in your PRs, kids!

* Refresh parts after building a machine.

* NetSync false

* Address some reviews, fix some bugs

* Nullable stackhelpers dependencies

* Use container helper method to delete all entities in containers

* Nullable string in AddContainer

* Better examine for machine frame and construction in general

* Machine breakage

* Nullable node

* nullable GraphPrototype

* Re-save saltern for autolathe parts

* Fix SaveLoadSave
This commit is contained in:
Vera Aguilera Puerto
2020-12-03 22:49:00 +01:00
committed by GitHub
parent ba2bdec13b
commit c3341132c5
36 changed files with 5270 additions and 3703 deletions

View File

@@ -0,0 +1,23 @@
using Content.Shared.GameObjects.Components.Construction;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
namespace Content.Client.GameObjects.Components.Construction
{
[UsedImplicitly]
public class MachineFrameVisualizer : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
if (component.TryGetData<int>(MachineFrameVisuals.State, out var data))
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
sprite.LayerSetState(0, $"box_{data}");
}
}
}
}

View File

@@ -216,6 +216,10 @@
"ConveyorAssembly", "ConveyorAssembly",
"TwoWayLever", "TwoWayLever",
"FirelockElectronics", "FirelockElectronics",
"Machine",
"MachinePart",
"MachineFrame",
"MachineBoard"
}; };
} }
} }

View File

@@ -0,0 +1,30 @@
#nullable enable
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Construction;
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.Construction.Completions
{
[UsedImplicitly]
public class AddContainer : IGraphAction
{
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Container, "container", null);
}
public string? Container { get; private set; } = null;
public async Task PerformAction(IEntity entity, IEntity? user)
{
if (entity.Deleted || string.IsNullOrEmpty(Container))
return;
var construction = entity.GetComponent<ConstructionComponent>();
construction.AddContainer(Container);
}
}
}

View File

@@ -0,0 +1,115 @@
#nullable enable
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Construction;
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Content.Server.Construction.Completions
{
[UsedImplicitly]
public class BuildMachine : IGraphAction
{
public async Task PerformAction(IEntity entity, IEntity? user)
{
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager))
{
Logger.Warning($"Machine frame entity {entity} did not have a container manager! Aborting build machine action.");
return;
}
if (!entity.TryGetComponent(out MachineFrameComponent? machineFrame))
{
Logger.Warning($"Machine frame entity {entity} did not have a machine frame component! Aborting build machine action.");
return;
}
if (!machineFrame.IsComplete)
{
Logger.Warning($"Machine frame entity {entity} doesn't have all required parts to be built! Aborting build machine action.");
return;
}
if (!containerManager.TryGetContainer(MachineFrameComponent.BoardContainer, out var entBoardContainer))
{
Logger.Warning($"Machine frame entity {entity} did not have the '{MachineFrameComponent.BoardContainer}' container! Aborting build machine action.");
return;
}
if (!containerManager.TryGetContainer(MachineFrameComponent.PartContainer, out var entPartContainer))
{
Logger.Warning($"Machine frame entity {entity} did not have the '{MachineFrameComponent.PartContainer}' container! Aborting build machine action.");
return;
}
if (entBoardContainer.ContainedEntities.Count != 1)
{
Logger.Warning($"Machine frame entity {entity} did not have exactly one item in the '{MachineFrameComponent.BoardContainer}' container! Aborting build machine action.");
}
var board = entBoardContainer.ContainedEntities[0];
if (!board.TryGetComponent(out MachineBoardComponent? boardComponent))
{
Logger.Warning($"Machine frame entity {entity} had an invalid entity in container \"{MachineFrameComponent.BoardContainer}\"! Aborting build machine action.");
return;
}
var entityManager = entity.EntityManager;
entBoardContainer.Remove(board);
var machine = entityManager.SpawnEntity(boardComponent.Prototype, entity.Transform.Coordinates);
machine.Transform.LocalRotation = entity.Transform.LocalRotation;
var boardContainer = ContainerManagerComponent.Ensure<Container>(MachineFrameComponent.BoardContainer, machine, out var existed);
if (existed)
{
// Clean that up...
boardContainer.CleanContainer();
}
var partContainer = ContainerManagerComponent.Ensure<Container>(MachineFrameComponent.PartContainer, machine, out existed);
if (existed)
{
// Clean that up, too...
partContainer.CleanContainer();
}
boardContainer.Insert(board);
// Now we insert all parts.
foreach (var part in entPartContainer.ContainedEntities.ToArray())
{
entPartContainer.ForceRemove(part);
partContainer.Insert(part);
}
if (machine.TryGetComponent(out ConstructionComponent? construction))
{
// We only add these two container. If some construction needs to take other containers into account, fix this.
construction.AddContainer(MachineFrameComponent.BoardContainer);
construction.AddContainer(MachineFrameComponent.PartContainer);
}
if (machine.TryGetComponent(out MachineComponent? machineComp))
{
machineComp.RefreshParts();
}
entity.Delete();
}
public void ExposeData(ObjectSerializer serializer)
{
}
}
}

View File

@@ -0,0 +1,30 @@
#nullable enable
using System.Threading.Tasks;
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.Construction.Completions
{
[UsedImplicitly]
public class EmptyAllContainers : IGraphAction
{
public void ExposeData(ObjectSerializer serializer)
{
}
public async Task PerformAction(IEntity entity, IEntity? user)
{
if (entity.Deleted || !entity.TryGetComponent<ContainerManagerComponent>(out var containerManager))
return;
foreach (var container in containerManager.GetAllContainers())
{
container.EmptyContainer(true, entity.Transform.Coordinates);
}
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Content.Shared.Construction; using Content.Shared.Construction;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -26,12 +27,7 @@ namespace Content.Server.Construction.Completions
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) || if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) ||
!containerManager.TryGetContainer(Container, out var container)) return; !containerManager.TryGetContainer(Container, out var container)) return;
foreach (var ent in container.ContainedEntities.ToArray()) container.EmptyContainer(true, entity.Transform.Coordinates);
{
if (ent == null || ent.Deleted) continue;
container.ForceRemove(ent);
ent.Transform.Coordinates = entity.Transform.Coordinates;
}
} }
} }
} }

View File

@@ -0,0 +1,28 @@
#nullable enable
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Construction;
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.Construction.Completions
{
[UsedImplicitly]
public class MachineFrameRegenerateProgress : IGraphAction
{
public void ExposeData(ObjectSerializer serializer)
{ }
public async Task PerformAction(IEntity entity, IEntity? user)
{
if (entity.Deleted)
return;
if (entity.TryGetComponent<MachineFrameComponent>(out var machineFrame))
{
machineFrame.RegenerateProgress();
}
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components;
using Content.Server.GameObjects.Components.Power;
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.Construction.Conditions
{
/// <summary>
/// A condition that requires all wires to be cut (or intact)
/// Returns true if the entity doesn't have a wires component.
/// </summary>
[UsedImplicitly]
public class AllWiresCut : IEdgeCondition
{
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(this, x => x.Value, "value", true);
}
public bool Value { get; private set; } = true;
public async Task<bool> Condition(IEntity entity)
{
if (entity.Deleted)
return false;
if (!entity.TryGetComponent<WiresComponent>(out var wires))
return true;
foreach (var wire in wires.WiresList)
{
switch (Value)
{
case true when !wire.IsCut:
case false when wire.IsCut:
return false;
}
}
return true;
}
}
}

View File

@@ -29,13 +29,17 @@ namespace Content.Server.Construction.Conditions
return container.ContainedEntities.Count == 0; return container.ContainedEntities.Count == 0;
} }
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange) public bool DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
{ {
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) || if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) ||
!containerManager.TryGetContainer(Container, out var container)) return; !containerManager.TryGetContainer(Container, out var container)) return false;
if (container.ContainedEntities.Count == 0)
return false;
if (container.ContainedEntities.Count != 0)
message.AddMarkup(Text); message.AddMarkup(Text);
return true;
} }
} }
} }

View File

@@ -27,16 +27,21 @@ namespace Content.Server.Construction.Conditions
return doorComponent.IsWeldedShut == Welded; return doorComponent.IsWeldedShut == Welded;
} }
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange) public bool DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
{ {
if (!entity.TryGetComponent(out ServerDoorComponent doorComponent)) return; if (!entity.TryGetComponent(out ServerDoorComponent doorComponent)) return false;
if (doorComponent.State == ServerDoorComponent.DoorState.Closed && Welded) if (doorComponent.State == ServerDoorComponent.DoorState.Closed && Welded)
message.AddMarkup(Loc.GetString("First, weld the door.\n"));
else if (doorComponent.IsWeldedShut && !Welded)
{ {
message.AddMarkup(Loc.GetString("First, unweld the door.\n")); message.AddMarkup(Loc.GetString("First, weld the door.\n"));
return true;
} }
if (!doorComponent.IsWeldedShut || Welded) return false;
message.AddMarkup(Loc.GetString("First, unweld the door.\n"));
return true;
} }
} }
} }

View File

@@ -25,15 +25,21 @@ namespace Content.Server.Construction.Conditions
return physics.Anchored == Anchored; return physics.Anchored == Anchored;
} }
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange) public bool DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
{ {
if (!entity.TryGetComponent(out IPhysicsComponent physics)) return; if (!entity.TryGetComponent(out IPhysicsComponent physics)) return false;
if(Anchored && !physics.Anchored) switch (Anchored)
{
case true when !physics.Anchored:
message.AddMarkup("First, anchor it.\n"); message.AddMarkup("First, anchor it.\n");
return true;
if(!Anchored && physics.Anchored) case false when physics.Anchored:
message.AddMarkup("First, unanchor it.\n"); message.AddMarkup("First, unanchor it.\n");
return true;
}
return false;
} }
} }
} }

View File

@@ -0,0 +1,72 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Construction;
using Content.Shared.Construction;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Server.Construction.Conditions
{
/// <summary>
/// Checks that the entity has all parts needed in the machine frame component.
/// </summary>
[UsedImplicitly]
public class MachineFrameComplete : IEdgeCondition
{
public void ExposeData(ObjectSerializer serializer) { }
public async Task<bool> Condition(IEntity entity)
{
if (entity.Deleted || !entity.TryGetComponent<MachineFrameComponent>(out var machineFrame))
return false;
return machineFrame.IsComplete;
}
public bool DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
{
if (!entity.TryGetComponent<MachineFrameComponent>(out var machineFrame))
return false;
if (!machineFrame.HasBoard)
{
message.AddMarkup(Loc.GetString("Insert [color=cyan]any machine circuit board[/color]."));
return true;
}
if (machineFrame.IsComplete) return false;
message.AddMarkup(Loc.GetString("Requires:\n"));
foreach (var (part, required) in machineFrame.Requirements)
{
var amount = required - machineFrame.Progress[part];
if(amount == 0) continue;
message.AddMarkup(Loc.GetString("[color=yellow]{0}x[/color] [color=green]{1}[/color]\n", amount, Loc.GetString(part.ToString())));
}
foreach (var (material, required) in machineFrame.MaterialRequirements)
{
var amount = required - machineFrame.MaterialProgress[material];
if(amount == 0) continue;
message.AddMarkup(Loc.GetString("[color=yellow]{0}x[/color] [color=green]{1}[/color]\n", amount, Loc.GetString(material.ToString())));
}
foreach (var (compName, info) in machineFrame.ComponentRequirements)
{
var amount = info.Amount - machineFrame.ComponentProgress[compName];
if(amount == 0) continue;
message.AddMarkup(Loc.GetString("[color=yellow]{0}x[/color] [color=green]{1}[/color]\n", info.Amount, Loc.GetString(info.ExamineName)));
}
return true;
}
}
}

View File

@@ -26,15 +26,21 @@ namespace Content.Server.Construction.Conditions
return wires.IsPanelOpen == Open; return wires.IsPanelOpen == Open;
} }
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange) public bool DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
{ {
if (!entity.TryGetComponent(out WiresComponent wires)) return; if (!entity.TryGetComponent(out WiresComponent wires)) return false;
if(Open && !wires.IsPanelOpen) switch (Open)
{
case true when !wires.IsPanelOpen:
message.AddMarkup(Loc.GetString("First, open the maintenance panel.\n")); message.AddMarkup(Loc.GetString("First, open the maintenance panel.\n"));
return true;
if(!Open && wires.IsPanelOpen) case false when wires.IsPanelOpen:
message.AddMarkup(Loc.GetString("First, close the maintenance panel.\n")); message.AddMarkup(Loc.GetString("First, close the maintenance panel.\n"));
return true;
}
return false;
} }
} }
} }

View File

@@ -0,0 +1,20 @@
namespace Content.Server.Construction
{
public enum MachinePart : byte
{
Capacitor,
ScanningModule,
Manipulator,
Laser,
MatterBin,
// Subspace parts.
Ansible,
Filter,
Amplifier,
Treatment,
Analyzer,
Crystal,
Transmitter,
}
}

View File

@@ -0,0 +1,63 @@
#nullable enable
using System;
using Content.Server.GameObjects.Components.Stack;
using Content.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
namespace Content.Server.Construction
{
public static class StackHelpers
{
/// <summary>
/// Spawns a stack of a specified type given an amount.
/// </summary>
public static IEntity SpawnStack(StackType stack, int amount, EntityCoordinates coordinates, IEntityManager? entityManager = null)
{
entityManager ??= IoCManager.Resolve<IEntityManager>();
string prototype;
switch (stack)
{
case StackType.Metal:
prototype = "SteelSheet1";
break;
case StackType.Glass:
prototype = "GlassSheet1";
break;
case StackType.MetalRod:
prototype = "MetalRodStack1";
break;
case StackType.Phoron:
prototype = "PhoronStack1";
break;
case StackType.Plasteel:
prototype = "PlasteelSheet1";
break;
case StackType.Cable:
prototype = "ApcExtensionCableStack1";
break;
// TODO: Add more.
default:
throw new ArgumentOutOfRangeException(nameof(stack),"Stack type doesn't have a prototype specified yet!");
}
var ent = entityManager.SpawnEntity(prototype, coordinates);
var stackComponent = ent.GetComponent<StackComponent>();
stackComponent.Count = Math.Min(amount, stackComponent.MaxCount);
return ent;
}
}
}

View File

@@ -48,10 +48,10 @@ namespace Content.Server.GameObjects.Components.Construction
private ConstructionGraphNode? _target = null; private ConstructionGraphNode? _target = null;
[ViewVariables] [ViewVariables]
public ConstructionGraphPrototype GraphPrototype { get; private set; } = null!; public ConstructionGraphPrototype? GraphPrototype { get; private set; }
[ViewVariables] [ViewVariables]
public ConstructionGraphNode Node { get; private set; } = null!; public ConstructionGraphNode? Node { get; private set; } = null;
[ViewVariables] [ViewVariables]
public ConstructionGraphEdge? Edge { get; private set; } = null; public ConstructionGraphEdge? Edge { get; private set; } = null;
@@ -88,6 +88,17 @@ namespace Content.Server.GameObjects.Components.Construction
serializer.DataField(ref _startingNodeIdentifier, "node", string.Empty); serializer.DataField(ref _startingNodeIdentifier, "node", string.Empty);
} }
/// <summary>
/// Attempts to set a new pathfinding target.
/// </summary>
public void SetNewTarget(string node)
{
if (GraphPrototype != null && GraphPrototype.Nodes.TryGetValue(node, out var target))
{
Target = target;
}
}
public void ClearTarget() public void ClearTarget()
{ {
_target = null; _target = null;
@@ -97,8 +108,8 @@ namespace Content.Server.GameObjects.Components.Construction
public void UpdateTarget() public void UpdateTarget()
{ {
// Can't pathfind without a target. // Can't pathfind without a target or no node.
if (Target == null) return; if (Target == null || Node == null || GraphPrototype == null) return;
// If we're at our target, stop pathfinding. // If we're at our target, stop pathfinding.
if (Target == Node) if (Target == Node)
@@ -133,7 +144,7 @@ namespace Content.Server.GameObjects.Components.Construction
} }
// Let's set the next target edge. // Let's set the next target edge.
if (Edge == null && TargetNextEdge == null) if (Edge == null && TargetNextEdge == null && TargetPathfinding != null)
TargetNextEdge = Node.GetEdge(TargetPathfinding.Peek().Name); TargetNextEdge = Node.GetEdge(TargetPathfinding.Peek().Name);
} }
@@ -161,6 +172,8 @@ namespace Content.Server.GameObjects.Components.Construction
{ {
EdgeStep = 0; EdgeStep = 0;
if (Node == null || GraphPrototype == null) return false;
foreach (var edge in Node.Edges) foreach (var edge in Node.Edges)
{ {
if(edge.Steps.Count == 0) if(edge.Steps.Count == 0)
@@ -344,7 +357,7 @@ namespace Content.Server.GameObjects.Components.Construction
private async Task<bool> HandleCompletion(ConstructionGraphEdge edge, IEntity user) private async Task<bool> HandleCompletion(ConstructionGraphEdge edge, IEntity user)
{ {
if (edge.Steps.Count != EdgeStep) if (edge.Steps.Count != EdgeStep || GraphPrototype == null)
{ {
return false; return false;
} }
@@ -380,6 +393,16 @@ namespace Content.Server.GameObjects.Components.Construction
return true; return true;
} }
public void ResetEdge()
{
_edgeNestedStepProgress = null;
TargetNextEdge = null;
Edge = null;
EdgeStep = 0;
UpdateTarget();
}
private async Task<bool> HandleEdge(InteractUsingEventArgs eventArgs) private async Task<bool> HandleEdge(InteractUsingEventArgs eventArgs)
{ {
if (Edge == null || EdgeStep >= Edge.Steps.Count) return false; if (Edge == null || EdgeStep >= Edge.Steps.Count) return false;
@@ -389,7 +412,8 @@ namespace Content.Server.GameObjects.Components.Construction
private async Task<bool> HandleEntityChange(ConstructionGraphNode node, IEntity? user = null) private async Task<bool> HandleEntityChange(ConstructionGraphNode node, IEntity? user = null)
{ {
if (node.Entity == Owner.Prototype?.ID || string.IsNullOrEmpty(node.Entity)) return false; if (node.Entity == Owner.Prototype?.ID || string.IsNullOrEmpty(node.Entity)
|| GraphPrototype == null) return false;
var entity = Owner.EntityManager.SpawnEntity(node.Entity, Owner.Transform.Coordinates); var entity = Owner.EntityManager.SpawnEntity(node.Entity, Owner.Transform.Coordinates);
@@ -398,7 +422,7 @@ namespace Content.Server.GameObjects.Components.Construction
if (entity.TryGetComponent(out ConstructionComponent? construction)) if (entity.TryGetComponent(out ConstructionComponent? construction))
{ {
if(construction.GraphPrototype != GraphPrototype) if(construction.GraphPrototype != GraphPrototype)
throw new Exception($"New entity {node.Entity}'s graph {construction.GraphPrototype.ID} isn't the same as our graph {GraphPrototype.ID} on node {node.Name}!"); throw new Exception($"New entity {node.Entity}'s graph {construction.GraphPrototype?.ID ?? null} isn't the same as our graph {GraphPrototype.ID} on node {node.Name}!");
construction.Node = node; construction.Node = node;
construction.Target = Target; construction.Target = Target;
@@ -461,14 +485,6 @@ namespace Content.Server.GameObjects.Components.Construction
if (GraphPrototype.Nodes.TryGetValue(_startingNodeIdentifier, out var node)) if (GraphPrototype.Nodes.TryGetValue(_startingNodeIdentifier, out var node))
{ {
Node = node; Node = node;
foreach (var action in Node.Actions)
{
action.PerformAction(Owner, null);
if (Owner.Deleted)
return;
}
} }
else else
{ {
@@ -481,8 +497,25 @@ namespace Content.Server.GameObjects.Components.Construction
} }
} }
protected override void Startup()
{
base.Startup();
if (Node == null) return;
foreach (var action in Node.Actions)
{
action.PerformAction(Owner, null);
if (Owner.Deleted)
return;
}
}
public async Task ChangeNode(string node) public async Task ChangeNode(string node)
{ {
if (GraphPrototype == null) return;
var graphNode = GraphPrototype.Nodes[node]; var graphNode = GraphPrototype.Nodes[node];
if (_handling && _handlingTask?.Task != null) if (_handling && _handlingTask?.Task != null)
@@ -508,21 +541,28 @@ namespace Content.Server.GameObjects.Components.Construction
if (Edge == null && TargetNextEdge != null) if (Edge == null && TargetNextEdge != null)
{ {
var preventStepExamine = false;
foreach (var condition in TargetNextEdge.Conditions) foreach (var condition in TargetNextEdge.Conditions)
{ {
condition.DoExamine(Owner, message, inDetailsRange); preventStepExamine |= condition.DoExamine(Owner, message, inDetailsRange);
} }
if(!preventStepExamine)
TargetNextEdge.Steps[0].DoExamine(message, inDetailsRange); TargetNextEdge.Steps[0].DoExamine(message, inDetailsRange);
return; return;
} }
if (Edge != null) if (Edge != null)
{ {
var preventStepExamine = false;
foreach (var condition in Edge.Conditions) foreach (var condition in Edge.Conditions)
{ {
condition.DoExamine(Owner, message, inDetailsRange); preventStepExamine |= condition.DoExamine(Owner, message, inDetailsRange);
} }
if (preventStepExamine) return;
} }
if (_edgeNestedStepProgress == null) if (_edgeNestedStepProgress == null)

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using Content.Server.Construction;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Construction
{
[RegisterComponent]
public class MachineBoardComponent : Component, IExamine
{
public override string Name => "MachineBoard";
[ViewVariables]
private Dictionary<MachinePart, int> _requirements;
[ViewVariables]
private Dictionary<StackType, int> _materialRequirements;
[ViewVariables]
private Dictionary<string, ComponentPartInfo> _componentRequirements;
/// <summary>
/// So, what happens if you spawn a machine from the entity spawning menu?
/// It should probably have all parts, including the component parts...
/// This is where this fancy little dictionary comes in!
/// This maps component name types to entity prototype IDs to be used as defaults.
/// </summary>
[ViewVariables]
private Dictionary<string, string> _componentDefaults;
[ViewVariables(VVAccess.ReadWrite)]
public string Prototype { get; private set; }
public IReadOnlyDictionary<MachinePart, int> Requirements => _requirements;
public IReadOnlyDictionary<StackType, int> MaterialRequirements => _materialRequirements;
public IReadOnlyDictionary<string, ComponentPartInfo> ComponentRequirements => _componentRequirements;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.Prototype, "prototype", null);
serializer.DataField(ref _requirements, "requirements", new Dictionary<MachinePart, int>());
serializer.DataField(ref _materialRequirements, "materialRequirements", new Dictionary<StackType, int>());
serializer.DataField(ref _componentRequirements, "componentRequirements", new Dictionary<string, ComponentPartInfo>());
}
public void Examine(FormattedMessage message, bool inDetailsRange)
{
message.AddMarkup(Loc.GetString("Requires:\n"));
foreach (var (part, amount) in Requirements)
{
message.AddMarkup(Loc.GetString("[color=yellow]{0}x[/color] [color=green]{1}[/color]\n", amount, Loc.GetString(part.ToString())));
}
foreach (var (material, amount) in MaterialRequirements)
{
message.AddMarkup(Loc.GetString("[color=yellow]{0}x[/color] [color=green]{1}[/color]\n", amount, Loc.GetString(material.ToString())));
}
foreach (var (_, info) in ComponentRequirements)
{
message.AddMarkup(Loc.GetString("[color=yellow]{0}x[/color] [color=green]{1}[/color]\n", info.Amount, Loc.GetString(info.ExamineName)));
}
}
}
[Serializable]
public struct ComponentPartInfo
{
public int Amount;
public string ExamineName;
public string DefaultPrototype;
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Construction;
using Content.Server.Interfaces.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Construction
{
[RegisterComponent]
public class MachineComponent : Component, IMapInit
{
public override string Name => "Machine";
public string BoardPrototype { get; private set; }
private Container _boardContainer;
private Container _partContainer;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.BoardPrototype, "board", null);
}
public override void Initialize()
{
base.Initialize();
_boardContainer = ContainerManagerComponent.Ensure<Container>(MachineFrameComponent.BoardContainer, Owner);
_partContainer = ContainerManagerComponent.Ensure<Container>(MachineFrameComponent.PartContainer, Owner);
}
protected override void Startup()
{
base.Startup();
CreateBoardAndStockParts();
}
public IEnumerable<MachinePartComponent> GetAllParts()
{
foreach (var entity in _partContainer.ContainedEntities)
{
if (entity.TryGetComponent<MachinePartComponent>(out var machinePart))
yield return machinePart;
}
}
public void RefreshParts()
{
foreach (var refreshable in Owner.GetAllComponents<IRefreshParts>())
{
refreshable.RefreshParts(GetAllParts());
}
}
public void CreateBoardAndStockParts()
{
// Entity might not be initialized yet.
var boardContainer = ContainerManagerComponent.Ensure<Container>(MachineFrameComponent.BoardContainer, Owner, out var existedBoard);
var partContainer = ContainerManagerComponent.Ensure<Container>(MachineFrameComponent.PartContainer, Owner, out var existedParts);
if (string.IsNullOrEmpty(BoardPrototype))
return;
var entityManager = Owner.EntityManager;
if (existedBoard || existedParts)
{
// We're done here, let's suppose all containers are correct just so we don't screw SaveLoadSave.
if (boardContainer.ContainedEntities.Count > 0)
return;
}
var board = entityManager.SpawnEntity(BoardPrototype, Owner.Transform.Coordinates);
if (!_boardContainer.Insert(board))
{
throw new Exception($"Couldn't insert board with prototype {BoardPrototype} to machine with prototype {Owner.Prototype?.ID ?? "N/A"}!");
}
if (!board.TryGetComponent<MachineBoardComponent>(out var machineBoard))
{
throw new Exception($"Entity with prototype {BoardPrototype} doesn't have a {nameof(MachineBoardComponent)}!");
}
foreach (var (part, amount) in machineBoard.Requirements)
{
for (var i = 0; i < amount; i++)
{
var p = entityManager.SpawnEntity(MachinePartComponent.Prototypes[part], Owner.Transform.Coordinates);
if (!partContainer.Insert(p))
throw new Exception($"Couldn't insert machine part of type {part} to machine with prototype {Owner.Prototype?.ID ?? "N/A"}!");
}
}
foreach (var (stackType, amount) in machineBoard.MaterialRequirements)
{
var s = StackHelpers.SpawnStack(stackType, amount, Owner.Transform.Coordinates);
if (!partContainer.Insert(s))
throw new Exception($"Couldn't insert machine material of type {stackType} to machine with prototype {Owner.Prototype?.ID ?? "N/A"}");
}
foreach (var (compName, info) in machineBoard.ComponentRequirements)
{
for (var i = 0; i < info.Amount; i++)
{
var c = entityManager.SpawnEntity(info.DefaultPrototype, Owner.Transform.Coordinates);
if(!partContainer.Insert(c))
throw new Exception($"Couldn't insert machine component part with default prototype '{compName}' to machine with prototype {Owner.Prototype?.ID ?? "N/A"}");
}
}
}
public void MapInit()
{
CreateBoardAndStockParts();
}
}
}

View File

@@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.Construction;
using Content.Server.GameObjects.Components.Stack;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Construction;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Construction
{
[RegisterComponent]
public class MachineFrameComponent : Component, IInteractUsing
{
[Dependency] private IComponentFactory _componentFactory = default!;
public const string PartContainer = "machine_parts";
public const string BoardContainer = "machine_board";
public override string Name => "MachineFrame";
[ViewVariables]
public bool IsComplete
{
get
{
if (!HasBoard || Requirements == null || MaterialRequirements == null)
return false;
foreach (var (part, amount) in Requirements)
{
if (_progress[part] < amount)
return false;
}
foreach (var (type, amount) in MaterialRequirements)
{
if (_materialProgress[type] < amount)
return false;
}
foreach (var (compName, info) in ComponentRequirements)
{
if (_componentProgress[compName] < info.Amount)
return false;
}
return true;
}
}
[ViewVariables]
public bool HasBoard => _boardContainer?.ContainedEntities.Count != 0;
[ViewVariables]
private Dictionary<MachinePart, int> _progress;
[ViewVariables]
private Dictionary<StackType, int> _materialProgress;
[ViewVariables]
private Dictionary<string, int> _componentProgress;
[ViewVariables]
private Container _boardContainer;
[ViewVariables]
private Container _partContainer;
[ViewVariables]
public IReadOnlyDictionary<MachinePart, int> Requirements { get; private set; }
[ViewVariables]
public IReadOnlyDictionary<StackType, int> MaterialRequirements { get; private set; }
[ViewVariables]
public IReadOnlyDictionary<string, ComponentPartInfo> ComponentRequirements { get; private set; }
public IReadOnlyDictionary<MachinePart, int> Progress => _progress;
public IReadOnlyDictionary<StackType, int> MaterialProgress => _materialProgress;
public IReadOnlyDictionary<string, int> ComponentProgress => _componentProgress;
public override void Initialize()
{
base.Initialize();
_boardContainer = ContainerManagerComponent.Ensure<Container>(BoardContainer, Owner);
_partContainer = ContainerManagerComponent.Ensure<Container>(PartContainer, Owner);
}
protected override void Startup()
{
base.Startup();
RegenerateProgress();
if (Owner.TryGetComponent<ConstructionComponent>(out var construction))
{
// Attempt to set pathfinding to the machine node...
construction.SetNewTarget("machine");
}
}
private void ResetProgressAndRequirements(MachineBoardComponent machineBoard)
{
Requirements = machineBoard.Requirements;
MaterialRequirements = machineBoard.MaterialRequirements;
ComponentRequirements = machineBoard.ComponentRequirements;
_progress = new Dictionary<MachinePart, int>();
_materialProgress = new Dictionary<StackType, int>();
_componentProgress = new Dictionary<string, int>();
foreach (var (machinePart, _) in Requirements)
{
_progress[machinePart] = 0;
}
foreach (var (stackType, _) in MaterialRequirements)
{
_materialProgress[stackType] = 0;
}
foreach (var (compName, _) in ComponentRequirements)
{
_componentProgress[compName] = 0;
}
}
public void RegenerateProgress()
{
AppearanceComponent appearance;
if (!HasBoard)
{
if (Owner.TryGetComponent(out appearance))
{
appearance.SetData(MachineFrameVisuals.State, 1);
}
Requirements = null;
MaterialRequirements = null;
ComponentRequirements = null;
_progress = null;
_materialProgress = null;
_componentProgress = null;
return;
}
var board = _boardContainer.ContainedEntities[0];
if (!board.TryGetComponent<MachineBoardComponent>(out var machineBoard))
return;
if (Owner.TryGetComponent(out appearance))
{
appearance.SetData(MachineFrameVisuals.State, 2);
}
ResetProgressAndRequirements(machineBoard);
foreach (var part in _partContainer.ContainedEntities)
{
if (part.TryGetComponent<MachinePartComponent>(out var machinePart))
{
// Check this is part of the requirements...
if (!Requirements.ContainsKey(machinePart.PartType))
continue;
if (!_progress.ContainsKey(machinePart.PartType))
_progress[machinePart.PartType] = 1;
else
_progress[machinePart.PartType]++;
}
if (part.TryGetComponent<StackComponent>(out var stack))
{
var type = (StackType) stack.StackType;
// Check this is part of the requirements...
if (!MaterialRequirements.ContainsKey(type))
continue;
if (!_materialProgress.ContainsKey(type))
_materialProgress[type] = 1;
else
_materialProgress[type]++;
}
// I have many regrets.
foreach (var (compName, amount) in ComponentRequirements)
{
var registration = _componentFactory.GetRegistration(compName);
if (!part.HasComponent(registration.Type))
continue;
if (!_componentProgress.ContainsKey(compName))
_componentProgress[compName] = 1;
else
_componentProgress[compName]++;
}
}
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!HasBoard && eventArgs.Using.TryGetComponent<MachineBoardComponent>(out var machineBoard))
{
if (eventArgs.Using.TryRemoveFromContainer())
{
// Valid board!
_boardContainer.Insert(eventArgs.Using);
// Setup requirements and progress...
ResetProgressAndRequirements(machineBoard);
if (Owner.TryGetComponent<AppearanceComponent>(out var appearance))
{
appearance.SetData(MachineFrameVisuals.State, 2);
}
if (Owner.TryGetComponent(out ConstructionComponent construction))
{
// So prying the components off works correctly.
construction.ResetEdge();
}
return true;
}
}
else if (HasBoard)
{
if (eventArgs.Using.TryGetComponent<MachinePartComponent>(out var machinePart))
{
if (!Requirements.ContainsKey(machinePart.PartType))
return false;
if (_progress[machinePart.PartType] != Requirements[machinePart.PartType]
&& eventArgs.Using.TryRemoveFromContainer() && _partContainer.Insert(eventArgs.Using))
{
_progress[machinePart.PartType]++;
return true;
}
}
if (eventArgs.Using.TryGetComponent<StackComponent>(out var stack))
{
var type = (StackType) stack.StackType;
if (!MaterialRequirements.ContainsKey(type))
return false;
if (_materialProgress[type] == MaterialRequirements[type])
return false;
var needed = MaterialRequirements[type] - _materialProgress[type];
var count = stack.Count;
if (count < needed && stack.Split(count, Owner.Transform.Coordinates, out var newStack))
{
_materialProgress[type] += count;
return true;
}
if (!stack.Split(needed, Owner.Transform.Coordinates, out newStack))
return false;
if(!_partContainer.Insert(newStack))
return false;
_materialProgress[type] += needed;
return true;
}
foreach (var (compName, info) in ComponentRequirements)
{
if (_componentProgress[compName] >= info.Amount)
continue;
var registration = _componentFactory.GetRegistration(compName);
if (!eventArgs.Using.HasComponent(registration.Type))
continue;
if (!eventArgs.Using.TryRemoveFromContainer() || !_partContainer.Insert(eventArgs.Using)) continue;
_componentProgress[compName]++;
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,54 @@
using System.Collections.Generic;
using Content.Server.Construction;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Construction
{
[RegisterComponent]
public class MachinePartComponent : Component, IExamine
{
// I'm so sorry for hard-coding this. But trust me, it should make things less painful.
public static IReadOnlyDictionary<MachinePart, string> Prototypes { get; } = new Dictionary<MachinePart, string>()
{
{MachinePart.Capacitor, "CapacitorStockPart"},
{MachinePart.ScanningModule, "ScanningModuleStockPart"},
{MachinePart.Manipulator, "MicroManipulatorStockPart"},
{MachinePart.Laser, "MicroLaserStockPart"},
{MachinePart.MatterBin, "MatterBinStockPart"},
{MachinePart.Ansible, "AnsibleSubspaceStockPart"},
{MachinePart.Filter, "FilterSubspaceStockPart"},
{MachinePart.Amplifier, "AmplifierSubspaceStockPart"},
{MachinePart.Treatment, "TreatmentSubspaceStockPart"},
{MachinePart.Analyzer, "AnalyzerSubspaceStockPart"},
{MachinePart.Crystal, "CrystalSubspaceStockPart"},
{MachinePart.Transmitter, "TransmitterSubspaceStockPart"}
};
public override string Name => "MachinePart";
[ViewVariables]
public MachinePart PartType { get; private set; }
[ViewVariables(VVAccess.ReadWrite)]
public int Rating { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.PartType, "part", MachinePart.Capacitor);
serializer.DataField(this, x => x.Rating, "rating", 1);
}
public void Examine(FormattedMessage message, bool inDetailsRange)
{
message.AddMarkup(Loc.GetString("[color=white]Rating:[/color] [color=cyan]{0}[/color]\n", Rating));
message.AddMarkup(Loc.GetString("[color=white]Type:[/color] [color=cyan]{0}[/color]\n", PartType));
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Construction;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.Interfaces.GameObjects
{
public interface IRefreshParts
{
void RefreshParts(IEnumerable<MachinePartComponent> parts);
}
}

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization; using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -8,6 +9,6 @@ namespace Content.Shared.Construction
public interface IEdgeCondition : IExposeData public interface IEdgeCondition : IExposeData
{ {
Task<bool> Condition(IEntity entity); Task<bool> Condition(IEntity entity);
void DoExamine(IEntity entity, FormattedMessage message, bool inExamineRange) { } bool DoExamine(IEntity entity, FormattedMessage message, bool inExamineRange) { return false; }
} }
} }

View File

@@ -0,0 +1,10 @@
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Construction
{
[NetSerializable]
public enum MachineFrameVisuals
{
State,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,8 @@
- type: latheRecipe - type: latheRecipe
id: Welder id: Welder
icon: icon:
sprite: Objects/Tools/welder.rsi sprite: /Textures/Objects/Tools/welder.rsi
state: welder state: icon
result: Welder result: Welder
completetime: 500 completetime: 500
materials: materials:

View File

@@ -49,6 +49,18 @@
- state: autolathe_unlit - state: autolathe_unlit
shader: unshaded shader: unshaded
map: ["enum.AutolatheVisualLayers.BaseUnlit"] map: ["enum.AutolatheVisualLayers.BaseUnlit"]
- state: autolathe_panel
map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
- type: Construction
graph: machine
node: machine
- type: BreakableConstruction
node: machineFrame
- type: Machine
board: AutolatheMachineCircuitboard
- type: Wires
BoardName: "Autolathe"
LayoutId: Autolathe
- type: LatheDatabase - type: LatheDatabase
static: true static: true
recipes: recipes:
@@ -68,6 +80,7 @@
- type: Appearance - type: Appearance
visuals: visuals:
- type: AutolatheVisualizer - type: AutolatheVisualizer
- type: WiresVisualizer
- type: entity - type: entity
parent: BaseLathe parent: BaseLathe
@@ -85,7 +98,12 @@
map: ["enum.ProtolatheVisualLayers.BaseUnlit"] map: ["enum.ProtolatheVisualLayers.BaseUnlit"]
- state: protolathe - state: protolathe
map: ["enum.ProtolatheVisualLayers.AnimationLayer"] map: ["enum.ProtolatheVisualLayers.AnimationLayer"]
- state: protolathe_panel
map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
- type: ResearchClient - type: ResearchClient
- type: Wires
BoardName: "Protolathe"
LayoutId: Protolathe
- type: TechnologyDatabase - type: TechnologyDatabase
- type: ProtolatheDatabase - type: ProtolatheDatabase
protolatherecipes: protolatherecipes:
@@ -112,3 +130,4 @@
- type: Appearance - type: Appearance
visuals: visuals:
- type: ProtolatheVisualizer - type: ProtolatheVisualizer
- type: WiresVisualizer

View File

@@ -0,0 +1,76 @@
- type: entity
id: UnfinishedMachineFrame
name: machine frame
suffix: Unfinished
placement:
mode: SnapgridCenter
components:
- type: Physics
mass: 25
anchored: true
shapes:
- !type:PhysShapeAabb
bounds: "-0.5,-0.25,0.5,0.25"
layer:
- Impassable
- MobImpassable
- VaultImpassable
- Opaque
mask:
- Impassable
- MobImpassable
- VaultImpassable
- type: Clickable
- type: InteractionOutline
- type: Anchorable
- type: Rotatable
- type: Pullable
- type: Construction
graph: machine
node: missingWires
- type: BreakableConstruction
node: start
- type: Sprite
sprite: "Constructible/Misc/stock_parts.rsi"
state: "box_0"
- type: entity
id: MachineFrame
name: machine frame
suffix: Ready
placement:
mode: SnapgridCenter
components:
- type: Physics
mass: 25
anchored: true
shapes:
- !type:PhysShapeAabb
bounds: "-0.5,-0.25,0.5,0.25"
layer:
- Impassable
- MobImpassable
- VaultImpassable
- Opaque
mask:
- Impassable
- MobImpassable
- VaultImpassable
- type: Clickable
- type: InteractionOutline
- type: Anchorable
- type: Rotatable
- type: Pullable
- type: Construction
graph: machine
node: machineFrame
- type: BreakableConstruction
node: missingWires
- type: MachineFrame
- type: Sprite
netsync: false
sprite: "Constructible/Misc/stock_parts.rsi"
state: "box_1"
- type: Appearance
visuals:
- type: MachineFrameVisualizer

View File

@@ -0,0 +1,324 @@
- type: entity
id: BaseStockPart
name: stock part
parent: BaseItem
description: What?
abstract: true
components:
- type: Sprite
netsync: false
sprite: Constructible/Misc/stock_parts.rsi
- type: MachinePart
rating: 1
# Rating 1
- type: entity
id: CapacitorStockPart
name: capacitor
parent: BaseStockPart
description: A basic capacitor used in the construction of a variety of devices.
components:
- type: Sprite
state: capacitor
- type: MachinePart
part: Capacitor
rating: 1
- type: entity
id: ScanningModuleStockPart
name: scanning module
parent: BaseStockPart
description: A compact, high resolution scanning module used in the construction of certain devices.
components:
- type: Sprite
state: scan_module
- type: MachinePart
part: ScanningModule
rating: 1
- type: entity
id: MicroManipulatorStockPart
name: micro-manipulator
parent: BaseStockPart
description: A tiny little manipulator used in the construction of certain devices.
components:
- type: Sprite
state: micro_mani
- type: MachinePart
part: Manipulator
rating: 1
- type: entity
id: MicroLaserStockPart
name: micro-laser
parent: BaseStockPart
description: A tiny laser used in certain devices.
components:
- type: Sprite
state: micro_laser
- type: MachinePart
part: Laser
rating: 1
- type: entity
id: MatterBinStockPart
name: matter bin
parent: BaseStockPart
description: A container designed to hold compressed matter awaiting reconstruction.
components:
- type: Sprite
state: matter_bin
- type: MachinePart
part: MatterBin
rating: 1
# Rating 2
- type: entity
id: AdvancedCapacitorStockPart
name: advanced capacitor
parent: CapacitorStockPart
description: An advanced capacitor used in the construction of a variety of devices.
components:
- type: Sprite
state: adv_capacitor
- type: MachinePart
rating: 2
- type: entity
id: AdvancedScanningModuleStockPart
name: advanced scanning module
parent: ScanningModuleStockPart
description: A compact, high resolution scanning module used in the construction of certain devices.
components:
- type: Sprite
state: adv_scan_module
- type: MachinePart
rating: 2
- type: entity
id: NanoManipulatorStockPart
name: nano-manipulator
parent: MicroManipulatorStockPart
description: A tiny little manipulator used in the construction of certain devices.
components:
- type: Sprite
state: nano_mani
- type: MachinePart
rating: 2
- type: entity
id: HighPowerMicroLaserStockPart
name: high-power micro-laser
parent: MicroLaserStockPart
description: A tiny laser used in certain devices.
components:
- type: Sprite
state: high_micro_laser
- type: MachinePart
rating: 2
- type: entity
id: AdvancedMatterBinStockPart
name: advanced matter bin
parent: MatterBinStockPart
description: A container designed to hold compressed matter awaiting reconstruction.
components:
- type: Sprite
state: advanced_matter_bin
- type: MachinePart
rating: 2
# Rating 3
- type: entity
id: SuperCapacitorStockPart
name: super capacitor
parent: CapacitorStockPart
description: A super-high capacity capacitor used in the construction of a variety of devices.
components:
- type: Sprite
state: super_capacitor
- type: MachinePart
rating: 3
- type: entity
id: PhasicScanningModuleStockPart
name: phasic scanning module
parent: ScanningModuleStockPart
description: A compact, high resolution phasic scanning module used in the construction of certain devices.
components:
- type: Sprite
state: super_scan_module
- type: MachinePart
rating: 3
- type: entity
id: PicoManipulatorStockPart
name: pico-manipulator
parent: MicroManipulatorStockPart
description: A tiny little manipulator used in the construction of certain devices.
components:
- type: Sprite
state: pico_mani
- type: MachinePart
rating: 3
- type: entity
id: UltraHighPowerMicroLaserStockPart
name: ultra-high-power micro-laser
parent: MicroLaserStockPart
description: A tiny laser used in certain devices.
components:
- type: Sprite
state: ultra_high_micro_laser
- type: MachinePart
rating: 3
- type: entity
id: SuperMatterBinStockPart
name: super matter bin
parent: MatterBinStockPart
description: A container designed to hold compressed matter awaiting reconstruction.
components:
- type: Sprite
state: super_matter_bin
- type: MachinePart
rating: 3
# Rating 4
- type: entity
id: QuadraticCapacitorStockPart
name: quadratic capacitor
parent: CapacitorStockPart
description: A quadratic capacity capacitor used in the construction of a variety of devices.
components:
- type: Sprite
state: quadratic_capacitor
- type: MachinePart
rating: 4
- type: entity
id: TriphasicScanningModuleStockPart
name: triphasic scanning module
parent: ScanningModuleStockPart
description: A compact, ultra resolution triphasic scanning module used in the construction of certain devices.
components:
- type: Sprite
state: triphasic_scan_module
- type: MachinePart
rating: 4
- type: entity
id: FemtoManipulatorStockPart
name: femto-manipulator
parent: MicroManipulatorStockPart
description: A tiny little manipulator used in the construction of certain devices.
components:
- type: Sprite
state: femto_mani
- type: MachinePart
rating: 4
- type: entity
id: QuadUltraMicroLaserStockPart
name: quad-ultra micro-laser
parent: MicroLaserStockPart
description: A tiny laser used in certain devices.
components:
- type: Sprite
state: quadultra_micro_laser
- type: MachinePart
rating: 4
- type: entity
id: BluespaceMatterBinStockPart
name: bluespace matter bin
parent: MatterBinStockPart
description: A container designed to hold compressed matter in bluespace awaiting reconstruction.
components:
- type: Sprite
state: bluespace_matter_bin
- type: MachinePart
rating: 4
# Subspace stock parts
- type: entity
id: AnsibleSubspaceStockPart
name: subspace ansible
parent: BaseStockPart
description: A compact module capable of sensing extradimensional activity.
components:
- type: Sprite
state: subspace_ansible
- type: MachinePart
part: Ansible
- type: entity
id: FilterSubspaceStockPart
name: hyperwave filter
parent: BaseStockPart
description: A tiny device capable of filtering and converting super-intense radiowaves.
components:
- type: Sprite
state: hyperwave_filter
- type: MachinePart
part: Filter
- type: entity
id: AmplifierSubspaceStockPart
name: subspace amplifier
parent: BaseStockPart
description: A compact micro-machine capable of amplifying weak subspace transmissions.
components:
- type: Sprite
state: subspace_amplifier
- type: MachinePart
part: Amplifier
- type: entity
id: TreatmentSubspaceStockPart
name: subspace treatment disk
parent: BaseStockPart
description: A compact micro-machine capable of stretching out hyper-compressed radio waves.
components:
- type: Sprite
state: treatment_disk
- type: MachinePart
part: Treatment
- type: entity
id: AnalyzerSubspaceStockPart
name: subspace wavelength analyzer
parent: BaseStockPart
description: A sophisticated analyzer capable of analyzing cryptic subspace wavelengths.
components:
- type: Sprite
state: wavelength_analyzer
- type: MachinePart
part: Analyzer
- type: entity
id: CrystalSubspaceStockPart
name: ansible crystal
parent: BaseStockPart
description: A crystal made from pure glass used to transmit laser databursts to subspace.
components:
- type: Sprite
state: ansible_crystal
- type: MachinePart
part: Crystal
- type: entity
id: TransmitterSubspaceStockPart
name: subspace transmitter
parent: BaseStockPart
description: A large piece of equipment used to open a window into the subspace dimension.
components:
- type: Sprite
state: subspace_transmitter
- type: MachinePart
part: Transmitter

View File

@@ -0,0 +1,23 @@
- type: entity
id: BaseMachineCircuitboard
parent: BaseItem
name: machine board
abstract: true
components:
- type: MachineBoard
- type: Sprite
sprite: Constructible/Misc/module.rsi
state: id_mod
- type: entity
id: AutolatheMachineCircuitboard
parent: BaseMachineCircuitboard
name: Autolathe (Machine Board)
components:
- type: MachineBoard
prototype: Autolathe
requirements:
MatterBin: 3
Manipulator: 1
materialRequirements:
Glass: 1

View File

@@ -24,7 +24,7 @@
steps: steps:
- component: ComputerBoard - component: ComputerBoard
store: board store: board
name: a computer circuit board name: any computer circuit board
icon: icon:
sprite: "Constructible/Misc/module.rsi" sprite: "Constructible/Misc/module.rsi"
state: "id_mod" state: "id_mod"
@@ -57,8 +57,7 @@
conditions: conditions:
- !type:EntityAnchored { } - !type:EntityAnchored { }
completed: completed:
- !type:EmptyContainer - !type:EmptyAllContainers {}
container: board
- !type:SpriteStateChange - !type:SpriteStateChange
state: 0 state: 0
steps: steps:
@@ -99,6 +98,10 @@
- to: missingWires - to: missingWires
conditions: conditions:
- !type:EntityAnchored { } - !type:EntityAnchored { }
completed:
- !type:SpawnPrototype
prototype: ApcExtensionCableStack1
amount: 5
steps: steps:
- tool: Cutting - tool: Cutting

View File

@@ -0,0 +1,89 @@
- type: constructionGraph
id: machine
start: start
graph:
- node: start
actions:
- !type:SpawnPrototype
prototype: SteelSheet1
- !type:DeleteEntity {}
edges:
- to: missingWires
completed:
- !type:SetAnchor
value: false
steps:
- material: Metal
amount: 5
doAfter: 2.5
- node: missingWires
entity: UnfinishedMachineFrame
actions:
- !type:SpriteStateChange
state: "box_0"
- !type:EmptyAllContainers {}
edges:
- to: machineFrame
conditions:
- !type:EntityAnchored {}
steps:
- material: Cable
- to: start
conditions:
- !type:EntityAnchored
anchored: false
completed:
- !type:SpawnPrototype
prototype: SteelSheet1
amount: 5
- !type:DeleteEntity {}
steps:
- tool: Screwing
doAfter: 2
- node: machineFrame
entity: MachineFrame
actions:
- !type:MachineFrameRegenerateProgress {}
edges:
- to: machine
conditions:
- !type:EntityAnchored {}
- !type:MachineFrameComplete {}
completed:
# Yes, this is snowflaked so we don't have to make an unique graph per machine. You're welcome.
- !type:BuildMachine {}
steps:
- tool: Screwing
doAfter: 0.5
- to: missingWires
conditions:
- !type:EntityAnchored {}
completed:
- !type:SpawnPrototype
prototype: ApcExtensionCableStack1
steps:
- tool: Prying
doAfter: 2
completed:
- !type:EmptyAllContainers {}
- !type:MachineFrameRegenerateProgress {}
- tool: Cutting
doAfter: 0.25
- node: machine
actions:
- !type:AddContainer
container: machine_parts
- !type:AddContainer
container: machine_board
edges:
- to: machineFrame
conditions:
- !type:EntityAnchored {}
- !type:WirePanel {}
steps:
- tool: Prying
doAfter: 0.25

View File

@@ -10,3 +10,16 @@
icon: icon:
sprite: Constructible/Misc/stock_parts.rsi sprite: Constructible/Misc/stock_parts.rsi
state: 4 state: 4
- type: construction
name: machine frame
id: MachineFrame
graph: machine
startNode: start
targetNode: machine
category: Machines
placementMode: SnapgridCenter
canBuildInImpassable: false
icon:
sprite: Constructible/Misc/stock_parts.rsi
state: "box_0"

View File

@@ -200,7 +200,7 @@
] ]
}, },
{ {
"name": "protolathe_open", "name": "protolathe_panel",
"directions": 1, "directions": 1,
"delays": [ "delays": [
[ [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -162,6 +162,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unequip/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Unequip/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unlockable/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=unlockable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unoccluded/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Unoccluded/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unweld/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=uplink/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=uplink/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Wirecutter/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Wirecutter/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xeno/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Xeno/@EntryIndexedValue">True</s:Boolean>