Data-oriented Construction System (#2152)
- Powerful - Data-oriented - Approved by PJB - Powered by node graphs and AI pathfinding - Coded by the same nerd who brought you atmos Co-authored-by: Exp <theexp111@gmail.com>
This commit is contained in:
committed by
GitHub
parent
a6647e8de1
commit
745401a41e
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public abstract class ArbitraryInsertConstructionGraphStep : EntityInsertConstructionGraphStep
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public SpriteSpecifier Icon { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Icon, "icon", SpriteSpecifier.Invalid);
|
||||
serializer.DataField(this, x => x.Name, "name", string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class ComponentConstructionGraphStep : ArbitraryInsertConstructionGraphStep
|
||||
{
|
||||
public string Component { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Component, "component", string.Empty);
|
||||
|
||||
}
|
||||
|
||||
public override bool EntityValid(IEntity entity)
|
||||
{
|
||||
foreach (var component in entity.GetAllComponents())
|
||||
{
|
||||
if (component.Name == Component)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(string.IsNullOrEmpty(Name)
|
||||
? Loc.GetString("Next, insert an entity with a {0} component.", Component) // Terrible.
|
||||
: Loc.GetString("Next, insert {0}", Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Construction.ConstructionConditions
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class LowWallInTile : IConstructionCondition
|
||||
{
|
||||
public void ExposeData(ObjectSerializer serializer) { }
|
||||
|
||||
public bool Condition(IEntity user, EntityCoordinates location, Direction direction)
|
||||
{
|
||||
var lowWall = false;
|
||||
|
||||
foreach (var entity in location.GetEntitiesInTile(true))
|
||||
{
|
||||
if (entity.HasComponent<SharedCanBuildWindowOnTopComponent>())
|
||||
lowWall = true;
|
||||
|
||||
// Already has a window.
|
||||
if (entity.HasComponent<SharedWindowComponent>())
|
||||
return false;
|
||||
}
|
||||
|
||||
return lowWall;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Construction.ConstructionConditions
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class NoWindowsInTile : IConstructionCondition
|
||||
{
|
||||
public void ExposeData(ObjectSerializer serializer) { }
|
||||
|
||||
public bool Condition(IEntity user, EntityCoordinates location, Direction direction)
|
||||
{
|
||||
foreach (var entity in location.GetEntitiesInTile(true))
|
||||
{
|
||||
if (entity.HasComponent<SharedWindowComponent>())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Content.Shared/Construction/ConstructionGraphEdge.cs
Normal file
101
Content.Shared/Construction/ConstructionGraphEdge.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Content.Shared.GameObjects.Components.Power;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
[Serializable]
|
||||
public class ConstructionGraphEdge : IExposeData
|
||||
{
|
||||
private List<ConstructionGraphStep> _steps = new List<ConstructionGraphStep>();
|
||||
private List<IEdgeCondition> _conditions;
|
||||
private List<IGraphAction> _completed;
|
||||
|
||||
[ViewVariables]
|
||||
public string Target { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<IEdgeCondition> Conditions => _conditions;
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<IGraphAction> Completed => _completed;
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<ConstructionGraphStep> Steps => _steps;
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
var moduleManager = IoCManager.Resolve<IModuleManager>();
|
||||
|
||||
serializer.DataField(this, x => x.Target, "to", string.Empty);
|
||||
if (!moduleManager.IsServerModule) return;
|
||||
serializer.DataField(ref _conditions, "conditions", new List<IEdgeCondition>());
|
||||
serializer.DataField(ref _completed, "completed", new List<IGraphAction>());
|
||||
}
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
var serializer = YamlObjectSerializer.NewReader(mapping);
|
||||
ExposeData(serializer);
|
||||
|
||||
if (!mapping.TryGetNode("steps", out YamlSequenceNode stepsMapping)) return;
|
||||
|
||||
foreach (var yamlNode in stepsMapping)
|
||||
{
|
||||
var stepMapping = (YamlMappingNode) yamlNode;
|
||||
_steps.Add(LoadStep(stepMapping));
|
||||
}
|
||||
}
|
||||
|
||||
public static ConstructionGraphStep LoadStep(YamlMappingNode mapping)
|
||||
{
|
||||
var stepSerializer = YamlObjectSerializer.NewReader(mapping);
|
||||
|
||||
if (mapping.TryGetNode("material", out _))
|
||||
{
|
||||
var material = new MaterialConstructionGraphStep();
|
||||
material.ExposeData(stepSerializer);
|
||||
return material;
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("tool", out _))
|
||||
{
|
||||
var tool = new ToolConstructionGraphStep();
|
||||
tool.ExposeData(stepSerializer);
|
||||
return tool;
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("prototype", out _))
|
||||
{
|
||||
var prototype = new PrototypeConstructionGraphStep();
|
||||
prototype.ExposeData(stepSerializer);
|
||||
return prototype;
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("component", out _))
|
||||
{
|
||||
var component = new ComponentConstructionGraphStep();
|
||||
component.ExposeData(stepSerializer);
|
||||
return component;
|
||||
}
|
||||
|
||||
if(mapping.TryGetNode("steps", out _))
|
||||
{
|
||||
var nested = new NestedConstructionGraphStep();
|
||||
nested.ExposeData(stepSerializer);
|
||||
nested.LoadFrom(mapping);
|
||||
return nested;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Tried to convert invalid YAML node mapping to ConstructionGraphStep!");
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Content.Shared/Construction/ConstructionGraphNode.cs
Normal file
68
Content.Shared/Construction/ConstructionGraphNode.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using ObjectSerializer = Robust.Shared.Serialization.ObjectSerializer;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
[Serializable]
|
||||
public class ConstructionGraphNode
|
||||
{
|
||||
private List<IGraphAction> _actions = new List<IGraphAction>();
|
||||
private List<ConstructionGraphEdge> _edges = new List<ConstructionGraphEdge>();
|
||||
|
||||
[ViewVariables]
|
||||
public string Name { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<ConstructionGraphEdge> Edges => _edges;
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<IGraphAction> Actions => _actions;
|
||||
|
||||
[ViewVariables]
|
||||
public string Entity { get; private set; }
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
var moduleManager = IoCManager.Resolve<IModuleManager>();
|
||||
|
||||
serializer.DataField(this, x => x.Name, "node", string.Empty);
|
||||
serializer.DataField(this, x => x.Entity, "entity",string.Empty);
|
||||
if (!moduleManager.IsServerModule) return;
|
||||
serializer.DataField(ref _actions, "actions", new List<IGraphAction>());
|
||||
}
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
var serializer = YamlObjectSerializer.NewReader(mapping);
|
||||
ExposeData(serializer);
|
||||
|
||||
if (!mapping.TryGetNode("edges", out YamlSequenceNode edgesMapping)) return;
|
||||
|
||||
foreach (var yamlNode in edgesMapping)
|
||||
{
|
||||
var edgeMapping = (YamlMappingNode) yamlNode;
|
||||
var edge = new ConstructionGraphEdge();
|
||||
edge.LoadFrom(edgeMapping);
|
||||
_edges.Add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
public ConstructionGraphEdge GetEdge(string target)
|
||||
{
|
||||
foreach (var edge in _edges)
|
||||
{
|
||||
if (edge.Target == target)
|
||||
return edge;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
118
Content.Shared/Construction/ConstructionGraphPrototype.cs
Normal file
118
Content.Shared/Construction/ConstructionGraphPrototype.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
[Prototype("constructionGraph")]
|
||||
public class ConstructionGraphPrototype : IPrototype, IIndexedPrototype
|
||||
{
|
||||
private readonly Dictionary<string, ConstructionGraphNode> _nodes = new Dictionary<string, ConstructionGraphNode>();
|
||||
private Dictionary<ValueTuple<string, string>, ConstructionGraphNode[]> _paths = new Dictionary<ValueTuple<string, string>, ConstructionGraphNode[]>();
|
||||
private Dictionary<ConstructionGraphNode, ConstructionGraphNode> _pathfinding = new Dictionary<ConstructionGraphNode, ConstructionGraphNode>();
|
||||
|
||||
[ViewVariables]
|
||||
public string ID { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public string Start { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, ConstructionGraphNode> Nodes => _nodes;
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
var serializer = YamlObjectSerializer.NewReader(mapping);
|
||||
|
||||
serializer.DataField(this, x => x.ID, "id", string.Empty);
|
||||
serializer.DataField(this, x => x.Start, "start", string.Empty);
|
||||
|
||||
if (!mapping.TryGetNode("graph", out YamlSequenceNode graphMapping)) return;
|
||||
|
||||
foreach (var yamlNode in graphMapping)
|
||||
{
|
||||
var childMapping = (YamlMappingNode) yamlNode;
|
||||
var node = new ConstructionGraphNode();
|
||||
node.LoadFrom(childMapping);
|
||||
_nodes[node.Name] = node;
|
||||
}
|
||||
|
||||
if(string.IsNullOrEmpty(Start) || !_nodes.ContainsKey(Start))
|
||||
throw new InvalidDataException($"Starting node for construction graph {ID} is null, empty or invalid!");
|
||||
|
||||
_pathfinding = Pathfind(Start);
|
||||
}
|
||||
|
||||
public ConstructionGraphEdge Edge(string startNode, string nextNode)
|
||||
{
|
||||
var start = _nodes[startNode];
|
||||
return start.GetEdge(nextNode);
|
||||
}
|
||||
|
||||
public ConstructionGraphNode[] Path(string startNode, string finishNode)
|
||||
{
|
||||
var tuple = new ValueTuple<string, string>(startNode, finishNode);
|
||||
|
||||
if (_paths.ContainsKey(tuple))
|
||||
return _paths[tuple];
|
||||
|
||||
var start = _nodes[startNode];
|
||||
var finish = _nodes[finishNode];
|
||||
|
||||
var current = finish;
|
||||
var path = new List<ConstructionGraphNode>();
|
||||
while (current != start)
|
||||
{
|
||||
path.Add(current);
|
||||
|
||||
// No path.
|
||||
if (current == null || !_pathfinding.ContainsKey(current))
|
||||
{
|
||||
// We remember this for next time.
|
||||
_paths[tuple] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
current = _pathfinding[current];
|
||||
}
|
||||
|
||||
path.Reverse();
|
||||
return _paths[tuple] = path.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses breadth first search for pathfinding.
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
private Dictionary<ConstructionGraphNode, ConstructionGraphNode> Pathfind(string start)
|
||||
{
|
||||
// TODO: Make this use A* or something, although it's not that important.
|
||||
var startNode = _nodes[start];
|
||||
|
||||
var frontier = new Queue<ConstructionGraphNode>();
|
||||
var cameFrom = new Dictionary<ConstructionGraphNode, ConstructionGraphNode>();
|
||||
|
||||
frontier.Enqueue(startNode);
|
||||
cameFrom[startNode] = null;
|
||||
|
||||
while (frontier.Count != 0)
|
||||
{
|
||||
var current = frontier.Dequeue();
|
||||
foreach (var edge in current.Edges)
|
||||
{
|
||||
var edgeNode = _nodes[edge.Target];
|
||||
if(cameFrom.ContainsKey(edgeNode)) continue;
|
||||
frontier.Enqueue(edgeNode);
|
||||
cameFrom[edgeNode] = current;
|
||||
}
|
||||
}
|
||||
|
||||
return cameFrom;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Content.Shared/Construction/ConstructionGraphStep.cs
Normal file
29
Content.Shared/Construction/ConstructionGraphStep.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class ConstructionGraphStep : IExposeData
|
||||
{
|
||||
private List<IGraphAction> _completed;
|
||||
public float DoAfter { get; private set; }
|
||||
public IReadOnlyList<IGraphAction> Completed => _completed;
|
||||
|
||||
public virtual void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
var moduleManager = IoCManager.Resolve<IModuleManager>();
|
||||
|
||||
serializer.DataField(this, x => x.DoAfter, "doAfter", 0f);
|
||||
if (!moduleManager.IsServerModule) return;
|
||||
serializer.DataField(ref _completed, "completed", new List<IGraphAction>());
|
||||
}
|
||||
|
||||
public abstract void DoExamine(FormattedMessage message, bool inDetailsRange);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -12,164 +9,69 @@ namespace Content.Shared.Construction
|
||||
[Prototype("construction")]
|
||||
public class ConstructionPrototype : IPrototype, IIndexedPrototype
|
||||
{
|
||||
private string _name;
|
||||
private string _description;
|
||||
private SpriteSpecifier _icon;
|
||||
private List<string> _keywords;
|
||||
private List<string> _categorySegments;
|
||||
private List<ConstructionStage> _stages = new List<ConstructionStage>();
|
||||
private ConstructionType _type;
|
||||
private string _id;
|
||||
private string _result;
|
||||
private string _placementMode;
|
||||
private bool _canBuildInImpassable;
|
||||
private List<IConstructionCondition> _conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Friendly name displayed in the construction GUI.
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Useful" description displayed in the construction GUI.
|
||||
/// </summary>
|
||||
public string Description => _description;
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ConstructionGraphPrototype"/> this construction will be using.
|
||||
/// </summary>
|
||||
public string Graph { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target <see cref="ConstructionGraphNode"/> this construction will guide the user to.
|
||||
/// </summary>
|
||||
public string TargetNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The starting <see cref="ConstructionGraphNode"/> this construction will start at.
|
||||
/// </summary>
|
||||
public string StartNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture path inside the construction GUI.
|
||||
/// </summary>
|
||||
public SpriteSpecifier Icon => _icon;
|
||||
public SpriteSpecifier Icon { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If you can start building or complete steps on impassable terrain.
|
||||
/// </summary>
|
||||
public bool CanBuildInImpassable => _canBuildInImpassable;
|
||||
public bool CanBuildInImpassable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of keywords that are used for searching.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Keywords => _keywords;
|
||||
public string Category { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The split up segments of the category.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> CategorySegments => _categorySegments;
|
||||
public ConstructionType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of stages of construction.
|
||||
/// Construction is separated into "stages" which is basically a state in a linear FSM.
|
||||
/// The stage has forward and optionally backwards "steps" which are the criteria to move around in the FSM.
|
||||
/// NOTE that the stages are mapped differently than they appear in the prototype.
|
||||
/// In the prototype, the forward step is displayed as "to move into this stage" and reverse "to move out of"
|
||||
/// Stage 0 is considered "construction not started" and last stage is considered "construction is finished".
|
||||
/// As such, neither last or 0 stage have actual stage DATA, only backward/forward steps respectively.
|
||||
/// This would be akward for a YAML prototype because it's always jagged.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ConstructionStage> Stages => _stages;
|
||||
public string ID { get; private set; }
|
||||
|
||||
public ConstructionType Type => _type;
|
||||
public string PlacementMode { get; private set; }
|
||||
|
||||
public string ID => _id;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype name of the entity prototype when construction is done.
|
||||
/// </summary>
|
||||
public string Result => _result;
|
||||
|
||||
public string PlacementMode => _placementMode;
|
||||
public IReadOnlyList<IConstructionCondition> Conditions => _conditions;
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
var ser = YamlObjectSerializer.NewReader(mapping);
|
||||
_name = ser.ReadDataField<string>("name");
|
||||
Name = ser.ReadDataField<string>("name");
|
||||
|
||||
ser.DataField(ref _id, "id", string.Empty);
|
||||
ser.DataField(ref _description, "description", string.Empty);
|
||||
ser.DataField(ref _icon, "icon", SpriteSpecifier.Invalid);
|
||||
ser.DataField(ref _type, "objectType", ConstructionType.Structure);
|
||||
ser.DataField(ref _result, "result", null);
|
||||
ser.DataField(ref _placementMode, "placementMode", "PlaceFree");
|
||||
ser.DataField(ref _canBuildInImpassable, "canBuildInImpassable", false);
|
||||
|
||||
_keywords = ser.ReadDataField<List<string>>("keywords", new List<string>());
|
||||
{
|
||||
var cat = ser.ReadDataField<string>("category");
|
||||
var split = cat.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
_categorySegments = split.ToList();
|
||||
}
|
||||
|
||||
{
|
||||
SpriteSpecifier nextIcon = null;
|
||||
ConstructionStep nextBackward = null;
|
||||
|
||||
foreach (var stepMap in mapping.GetNode<YamlSequenceNode>("steps").Cast<YamlMappingNode>())
|
||||
{
|
||||
var step = ReadStepPrototype(stepMap);
|
||||
_stages.Add(new ConstructionStage(step, nextIcon, nextBackward));
|
||||
if (stepMap.TryGetNode("icon", out var node))
|
||||
{
|
||||
nextIcon = SpriteSpecifier.FromYaml(node);
|
||||
}
|
||||
|
||||
if (stepMap.TryGetNode("reverse", out YamlMappingNode revMap))
|
||||
{
|
||||
nextBackward = ReadStepPrototype(revMap);
|
||||
}
|
||||
}
|
||||
|
||||
_stages.Add(new ConstructionStage(null, nextIcon, nextBackward));
|
||||
}
|
||||
}
|
||||
|
||||
ConstructionStep ReadStepPrototype(YamlMappingNode step)
|
||||
{
|
||||
int amount = 1;
|
||||
|
||||
if (step.TryGetNode("amount", out var node))
|
||||
{
|
||||
amount = node.AsInt();
|
||||
}
|
||||
if (step.TryGetNode("material", out node))
|
||||
{
|
||||
return new ConstructionStepMaterial(
|
||||
node.AsEnum<ConstructionStepMaterial.MaterialType>(),
|
||||
amount
|
||||
);
|
||||
}
|
||||
|
||||
if (step.TryGetNode("tool", out node))
|
||||
{
|
||||
return new ConstructionStepTool(
|
||||
node.AsEnum<ToolQuality>(),
|
||||
amount
|
||||
);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Not enough data specified to determine step.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConstructionStage
|
||||
{
|
||||
/// <summary>
|
||||
/// The icon of the construction frame at this stage.
|
||||
/// </summary>
|
||||
public readonly SpriteSpecifier Icon;
|
||||
|
||||
/// <summary>
|
||||
/// The step that should be completed to move away from this stage to the next one.
|
||||
/// </summary>
|
||||
public readonly ConstructionStep Forward;
|
||||
|
||||
/// <summary>
|
||||
/// The optional step that can be completed to move away from this stage to the previous one.
|
||||
/// </summary>
|
||||
public readonly ConstructionStep Backward;
|
||||
|
||||
public ConstructionStage(ConstructionStep forward, SpriteSpecifier icon = null, ConstructionStep backward = null)
|
||||
{
|
||||
Icon = icon;
|
||||
Forward = forward;
|
||||
Backward = backward;
|
||||
ser.DataField(this, x => x.ID, "id", string.Empty);
|
||||
ser.DataField(this, x => x.Graph, "graph", string.Empty);
|
||||
ser.DataField(this, x => x.TargetNode, "targetNode", string.Empty);
|
||||
ser.DataField(this, x => x.StartNode, "startNode", string.Empty);
|
||||
ser.DataField(this, x => x.Description, "description", string.Empty);
|
||||
ser.DataField(this, x => x.Icon, "icon", SpriteSpecifier.Invalid);
|
||||
ser.DataField(this, x => x.Type, "objectType", ConstructionType.Structure);
|
||||
ser.DataField(this, x => x.PlacementMode, "placementMode", "PlaceFree");
|
||||
ser.DataField(this, x => x.CanBuildInImpassable, "canBuildInImpassable", false);
|
||||
ser.DataField(this, x => x.Category, "category", string.Empty);
|
||||
ser.DataField(ref _conditions, "conditions", new List<IConstructionCondition>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,47 +80,5 @@ namespace Content.Shared.Construction
|
||||
Structure,
|
||||
Item,
|
||||
}
|
||||
|
||||
public abstract class ConstructionStep
|
||||
{
|
||||
public readonly int Amount;
|
||||
public readonly float DoAfterDelay;
|
||||
|
||||
protected ConstructionStep(int amount, float doAfterDelay = 0f)
|
||||
{
|
||||
Amount = amount;
|
||||
DoAfterDelay = doAfterDelay;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConstructionStepTool : ConstructionStep
|
||||
{
|
||||
public readonly ToolQuality ToolQuality;
|
||||
|
||||
public ConstructionStepTool(ToolQuality toolQuality, int amount) : base(amount)
|
||||
{
|
||||
ToolQuality = toolQuality;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConstructionStepMaterial : ConstructionStep
|
||||
{
|
||||
public readonly MaterialType Material;
|
||||
|
||||
public ConstructionStepMaterial(MaterialType material, int amount) : base(amount)
|
||||
{
|
||||
Material = material;
|
||||
}
|
||||
|
||||
|
||||
public enum MaterialType
|
||||
{
|
||||
Metal,
|
||||
Glass,
|
||||
Cable,
|
||||
Gold,
|
||||
Phoron,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public abstract class EntityInsertConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
public string Store { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Store, "store", string.Empty);
|
||||
}
|
||||
|
||||
public abstract bool EntityValid(IEntity entity);
|
||||
}
|
||||
}
|
||||
12
Content.Shared/Construction/IConstructionCondition.cs
Normal file
12
Content.Shared/Construction/IConstructionCondition.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public interface IConstructionCondition : IExposeData
|
||||
{
|
||||
bool Condition(IEntity user, EntityCoordinates location, Direction direction);
|
||||
}
|
||||
}
|
||||
13
Content.Shared/Construction/IEdgeCondition.cs
Normal file
13
Content.Shared/Construction/IEdgeCondition.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public interface IEdgeCondition : IExposeData
|
||||
{
|
||||
Task<bool> Condition(IEntity entity);
|
||||
void DoExamine(IEntity entity, FormattedMessage message, bool inExamineRange) { }
|
||||
}
|
||||
}
|
||||
13
Content.Shared/Construction/IGraphAction.cs
Normal file
13
Content.Shared/Construction/IGraphAction.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public interface IGraphAction : IExposeData
|
||||
{
|
||||
Task PerformAction(IEntity entity, IEntity? user);
|
||||
}
|
||||
}
|
||||
48
Content.Shared/Construction/MaterialConstructionGraphStep.cs
Normal file
48
Content.Shared/Construction/MaterialConstructionGraphStep.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Materials;
|
||||
using Content.Shared.Materials;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class MaterialConstructionGraphStep : EntityInsertConstructionGraphStep
|
||||
{
|
||||
// TODO: Make this use the material system.
|
||||
// TODO TODO: Make the material system not shit.
|
||||
public StackType Material { get; private set; }
|
||||
public int Amount { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Material, "material", StackType.Metal);
|
||||
serializer.DataField(this, x => x.Amount, "amount", 1);
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("Next, add [color=yellow]{0}x[/color] [color=cyan]{1}[/color].", Amount, Material));
|
||||
}
|
||||
|
||||
public override bool EntityValid(IEntity entity)
|
||||
{
|
||||
return entity.TryGetComponent(out SharedStackComponent? stack) && stack.StackType.Equals(Material);
|
||||
}
|
||||
|
||||
public bool EntityValid(IEntity entity, [NotNullWhen(true)] out SharedStackComponent? stack)
|
||||
{
|
||||
if(entity.TryGetComponent(out SharedStackComponent? otherStack) && otherStack.StackType.Equals(Material))
|
||||
stack = otherStack;
|
||||
else
|
||||
stack = null;
|
||||
|
||||
return stack != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Content.Shared/Construction/NestedConstructionGraphStep.cs
Normal file
41
Content.Shared/Construction/NestedConstructionGraphStep.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class NestedConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
public List<List<ConstructionGraphStep>> Steps { get; private set; } = new List<List<ConstructionGraphStep>>();
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
if (!mapping.TryGetNode("steps", out YamlSequenceNode steps)) return;
|
||||
|
||||
foreach (var node in steps)
|
||||
{
|
||||
var sequence = (YamlSequenceNode) node;
|
||||
var list = new List<ConstructionGraphStep>();
|
||||
|
||||
foreach (var innerNode in sequence)
|
||||
{
|
||||
var stepNode = (YamlMappingNode) innerNode;
|
||||
var step = ConstructionGraphEdge.LoadStep(stepNode);
|
||||
|
||||
if(step is NestedConstructionGraphStep)
|
||||
throw new InvalidDataException("Can't have nested construction steps inside nested construction steps!");
|
||||
|
||||
list.Add(step);
|
||||
}
|
||||
|
||||
Steps.Add(list);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class PrototypeConstructionGraphStep : ArbitraryInsertConstructionGraphStep
|
||||
{
|
||||
public string Prototype { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Prototype, "prototype", string.Empty);
|
||||
}
|
||||
|
||||
public override bool EntityValid(IEntity entity)
|
||||
{
|
||||
return entity.Prototype?.ID == Prototype;
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(string.IsNullOrEmpty(Name)
|
||||
? Loc.GetString("Next, insert {0}", Prototype) // Terrible.
|
||||
: Loc.GetString("Next, insert {0}", Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Content.Shared/Construction/ToolConstructionGraphStep.cs
Normal file
35
Content.Shared/Construction/ToolConstructionGraphStep.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class ToolConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
public ToolQuality Tool { get; private set; }
|
||||
public float Fuel { get; private set; }
|
||||
public string ExamineOverride { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Tool, "tool", ToolQuality.None);
|
||||
serializer.DataField(this, x => x.Fuel, "fuel", 10f); // Default fuel cost.
|
||||
serializer.DataField(this, x => x.ExamineOverride, "examine", string.Empty);
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ExamineOverride))
|
||||
{
|
||||
message.AddMarkup(Loc.GetString(ExamineOverride));
|
||||
return;
|
||||
}
|
||||
|
||||
message.AddMarkup(Loc.GetString($"Next, use a [color=cyan]{Tool.GetToolName()}[/color]."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.DamageContainer;
|
||||
using Content.Shared.Damage.ResistanceSet;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Mono.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -157,6 +158,9 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
var writeFlags = new List<DamageFlag>();
|
||||
|
||||
if (Flags == DamageFlag.None)
|
||||
return writeFlags;
|
||||
|
||||
foreach (var flag in (DamageFlag[]) Enum.GetValues(typeof(DamageFlag)))
|
||||
{
|
||||
if ((Flags & flag) == flag)
|
||||
|
||||
@@ -16,6 +16,23 @@ namespace Content.Shared.GameObjects.Components.Interactable
|
||||
Multitool = 1 << 5,
|
||||
}
|
||||
|
||||
public static class ToolQualityHelpers
|
||||
{
|
||||
public static string GetToolName(this ToolQuality quality)
|
||||
{
|
||||
return quality switch
|
||||
{
|
||||
ToolQuality.Anchoring => "Wrench",
|
||||
ToolQuality.Prying => "Crowbar",
|
||||
ToolQuality.Screwing => "Screwdriver",
|
||||
ToolQuality.Cutting => "Wirecutters",
|
||||
ToolQuality.Welding => "Welding tool",
|
||||
ToolQuality.Multitool => "Multitool",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class SharedToolComponent : Component
|
||||
{
|
||||
public override string Name => "Tool";
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum ReinforcedWallVisuals
|
||||
{
|
||||
DeconstructionStage,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class SharedCanBuildWindowOnTopComponent : Component
|
||||
{
|
||||
public override string Name => "CanBuildWindowOnTop";
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,7 @@ namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
Metal,
|
||||
Glass,
|
||||
Plasteel,
|
||||
Cable,
|
||||
Gold,
|
||||
Phoron,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
public class SharedWindowComponent : Component
|
||||
{
|
||||
public override string Name => "Window";
|
||||
}
|
||||
}
|
||||
@@ -76,28 +76,5 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
GhostId = ghostId;
|
||||
}
|
||||
}
|
||||
|
||||
public void DoExamine(FormattedMessage message, ConstructionPrototype prototype, int stage, bool inDetailRange)
|
||||
{
|
||||
var stages = prototype.Stages;
|
||||
if (stage >= 0 && stage < stages.Count)
|
||||
{
|
||||
var curStage = stages[stage];
|
||||
if (curStage.Backward != null && curStage.Backward is ConstructionStepTool)
|
||||
{
|
||||
var backward = (ConstructionStepTool) curStage.Backward;
|
||||
message.AddText(Loc.GetString("To deconstruct: {0}x {1} Tool", backward.Amount, backward.ToolQuality));
|
||||
}
|
||||
if (curStage.Forward != null && curStage.Forward is ConstructionStepMaterial)
|
||||
{
|
||||
if (curStage.Backward != null)
|
||||
{
|
||||
message.AddText("\n");
|
||||
}
|
||||
var forward = (ConstructionStepMaterial) curStage.Forward;
|
||||
message.AddText(Loc.GetString("To construct: {0}x {1}", forward.Amount, forward.Material));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
@@ -29,18 +28,18 @@ namespace Content.Shared.Maps
|
||||
/// <summary>
|
||||
/// Attempts to get the turf at map indices with grid id or null if no such turf is found.
|
||||
/// </summary>
|
||||
public static TileRef? GetTileRef(this MapIndices mapIndices, GridId gridId, IMapManager? mapManager = null)
|
||||
public static TileRef GetTileRef(this MapIndices mapIndices, GridId gridId, IMapManager? mapManager = null)
|
||||
{
|
||||
if (!gridId.IsValid())
|
||||
return null;
|
||||
return default;
|
||||
|
||||
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if (!mapManager.TryGetGrid(gridId, out var grid))
|
||||
return null;
|
||||
return default;
|
||||
|
||||
if (!grid.TryGetTileRef(mapIndices, out var tile))
|
||||
return null;
|
||||
return default;
|
||||
|
||||
return tile;
|
||||
}
|
||||
@@ -149,12 +148,7 @@ namespace Content.Shared.Maps
|
||||
/// </summary>
|
||||
public static IEnumerable<IEntity> GetEntitiesInTile(this MapIndices indices, GridId gridId, bool approximate = false, IEntityManager? entityManager = null)
|
||||
{
|
||||
var turf = indices.GetTileRef(gridId);
|
||||
|
||||
if (turf == null)
|
||||
return Enumerable.Empty<IEntity>();
|
||||
|
||||
return GetEntitiesInTile(turf.Value, approximate, entityManager);
|
||||
return GetEntitiesInTile(indices.GetTileRef(gridId), approximate, entityManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,7 +188,9 @@ namespace Content.Shared.Maps
|
||||
{
|
||||
var map = IoCManager.Resolve<IMapManager>();
|
||||
var tileGrid = map.GetGrid(turf.GridIndex);
|
||||
var tileBox = Box2.UnitCentered.Scale(tileGrid.TileSize);
|
||||
|
||||
// This is scaled to 90 % so it doesn't encompass walls on other tiles.
|
||||
var tileBox = Box2.UnitCentered.Scale(tileGrid.TileSize).Scale(0.9f);
|
||||
return tileBox.Translated(tileGrid.GridTileToWorldPos(turf.GridIndices));
|
||||
}
|
||||
|
||||
|
||||
@@ -101,13 +101,13 @@ namespace Content.Shared.Materials
|
||||
// All default material params are initialized to 1 because
|
||||
// I'm too lazy to figure out for which that's necessary to prevent divisions by zero in case left out.
|
||||
serializer.DataField(ref _density, "density", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _electricResistivity, "electricresistivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _thermalConductivity, "thermalconductivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _specificHeat, "specificheat", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _electricResistivity, "electricResistivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _thermalConductivity, "thermalConductivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _specificHeat, "specificHeat", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _durability, "durability", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _hardness, "hardness", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _sharpDamage, "sharpdamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _bluntDamage, "bluntdamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _sharpDamage, "sharpDamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _bluntDamage, "bluntDamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid, alwaysWrite: true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user