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:
Víctor Aguilera Puerto
2020-10-08 17:41:23 +02:00
committed by GitHub
parent a6647e8de1
commit 745401a41e
261 changed files with 3886 additions and 11986 deletions

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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!");
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}

View File

@@ -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,
}
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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) { }
}
}

View 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);
}
}

View 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;
}
}
}

View 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)
{
}
}
}

View File

@@ -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));
}
}
}

View 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]."));
}
}
}

View File

@@ -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)

View File

@@ -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";

View File

@@ -0,0 +1,11 @@
using System;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components
{
[Serializable, NetSerializable]
public enum ReinforcedWallVisuals
{
DeconstructionStage,
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.GameObjects.Components
{
[RegisterComponent]
public class SharedCanBuildWindowOnTopComponent : Component
{
public override string Name => "CanBuildWindowOnTop";
}
}

View File

@@ -125,6 +125,7 @@ namespace Content.Shared.GameObjects.Components
{
Metal,
Glass,
Plasteel,
Cable,
Gold,
Phoron,

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameObjects;
namespace Content.Shared.GameObjects.Components
{
public class SharedWindowComponent : Component
{
public override string Name => "Window";
}
}

View File

@@ -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));
}
}
}
}
}

View File

@@ -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));
}

View File

@@ -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);
}
}