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
@@ -1,20 +1,28 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.GameObjects.EntitySystems;
|
using Content.Client.GameObjects.EntitySystems;
|
||||||
|
using Content.Client.Utility;
|
||||||
using Content.Shared.Construction;
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
using Content.Shared.GameObjects.Components.Interactable;
|
||||||
|
using Content.Shared.Materials;
|
||||||
|
using Microsoft.CodeAnalysis.Options;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Interfaces.Placement;
|
using Robust.Client.Interfaces.Placement;
|
||||||
using Robust.Client.Interfaces.ResourceManagement;
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
using Robust.Client.Placement;
|
using Robust.Client.Placement;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.Utility;
|
using Robust.Client.Utility;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
@@ -25,376 +33,424 @@ namespace Content.Client.Construction
|
|||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||||
|
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||||
|
|
||||||
private readonly Button BuildButton;
|
protected override Vector2? CustomSize => (720, 320);
|
||||||
private readonly Button EraseButton;
|
|
||||||
private readonly LineEdit SearchBar;
|
|
||||||
private readonly Tree RecipeList;
|
|
||||||
private readonly TextureRect InfoIcon;
|
|
||||||
private readonly Label InfoLabel;
|
|
||||||
private readonly ItemList StepList;
|
|
||||||
|
|
||||||
private CategoryNode RootCategory;
|
private ConstructionPrototype? _selected;
|
||||||
|
private string[] _categories = Array.Empty<string>();
|
||||||
|
|
||||||
// This list is flattened in such a way that the top most deepest category is first.
|
private readonly ItemList _recipes;
|
||||||
private List<CategoryNode> FlattenedCategories;
|
private readonly ItemList _stepList;
|
||||||
private readonly PlacementManager Placement;
|
private readonly Button _buildButton;
|
||||||
|
private readonly Button _eraseButton;
|
||||||
protected override Vector2? CustomSize => (500, 350);
|
private readonly LineEdit _searchBar;
|
||||||
|
private readonly OptionButton _category;
|
||||||
|
private readonly TextureRect _targetTexture;
|
||||||
|
private readonly RichTextLabel _targetName;
|
||||||
|
private readonly RichTextLabel _targetDescription;
|
||||||
|
|
||||||
public ConstructionMenu()
|
public ConstructionMenu()
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
Placement = (PlacementManager) IoCManager.Resolve<IPlacementManager>();
|
|
||||||
Placement.PlacementChanged += OnPlacementChanged;
|
_placementManager.PlacementChanged += PlacementChanged;
|
||||||
|
|
||||||
Title = "Construction";
|
Title = "Construction";
|
||||||
|
|
||||||
var hSplitContainer = new HSplitContainer();
|
var hbox = new HBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand};
|
||||||
|
|
||||||
// Left side
|
var recipeContainer = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.45f};
|
||||||
var recipes = new VBoxContainer {CustomMinimumSize = new Vector2(150.0f, 0.0f)};
|
|
||||||
SearchBar = new LineEdit {PlaceHolder = "Search"};
|
|
||||||
RecipeList = new Tree {SizeFlagsVertical = SizeFlags.FillExpand, HideRoot = true};
|
|
||||||
recipes.AddChild(SearchBar);
|
|
||||||
recipes.AddChild(RecipeList);
|
|
||||||
hSplitContainer.AddChild(recipes);
|
|
||||||
|
|
||||||
// Right side
|
var searchContainer = new HBoxContainer() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.1f};
|
||||||
var guide = new VBoxContainer();
|
_searchBar = new LineEdit() {PlaceHolder = "Search", SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.6f};
|
||||||
var info = new HBoxContainer();
|
_category = new OptionButton() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.4f};
|
||||||
InfoIcon = new TextureRect();
|
|
||||||
InfoLabel = new Label
|
|
||||||
{
|
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.ShrinkCenter
|
|
||||||
};
|
|
||||||
info.AddChild(InfoIcon);
|
|
||||||
info.AddChild(InfoLabel);
|
|
||||||
guide.AddChild(info);
|
|
||||||
|
|
||||||
var stepsLabel = new Label
|
_recipes = new ItemList() {SelectMode = ItemList.ItemListSelectMode.Single, SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.9f};
|
||||||
{
|
|
||||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
|
||||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
|
||||||
Text = "Steps"
|
|
||||||
};
|
|
||||||
guide.AddChild(stepsLabel);
|
|
||||||
|
|
||||||
StepList = new ItemList
|
var spacer = new Control() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.05f};
|
||||||
{
|
|
||||||
SizeFlagsVertical = SizeFlags.FillExpand, SelectMode = ItemList.ItemListSelectMode.None
|
|
||||||
};
|
|
||||||
guide.AddChild(StepList);
|
|
||||||
|
|
||||||
var buttonsContainer = new HBoxContainer();
|
var stepsContainer = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.45f};
|
||||||
BuildButton = new Button
|
var targetContainer = new HBoxContainer() {Align = BoxContainer.AlignMode.Center, SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.25f};
|
||||||
{
|
_targetTexture = new TextureRect() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.15f, Stretch = TextureRect.StretchMode.KeepCentered};
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
var targetInfoContainer = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.85f};
|
||||||
TextAlign = Label.AlignMode.Center,
|
_targetName = new RichTextLabel() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.1f};
|
||||||
Text = "Build!",
|
_targetDescription = new RichTextLabel() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.9f};
|
||||||
Disabled = true,
|
|
||||||
ToggleMode = true
|
|
||||||
};
|
|
||||||
EraseButton = new Button
|
|
||||||
{
|
|
||||||
TextAlign = Label.AlignMode.Center, Text = "Clear Ghosts", ToggleMode = true
|
|
||||||
};
|
|
||||||
buttonsContainer.AddChild(BuildButton);
|
|
||||||
buttonsContainer.AddChild(EraseButton);
|
|
||||||
guide.AddChild(buttonsContainer);
|
|
||||||
|
|
||||||
hSplitContainer.AddChild(guide);
|
_stepList = new ItemList() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.75f, SelectMode = ItemList.ItemListSelectMode.None};
|
||||||
Contents.AddChild(hSplitContainer);
|
|
||||||
|
|
||||||
BuildButton.OnToggled += OnBuildToggled;
|
var buttonContainer = new VBoxContainer() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.1f};
|
||||||
EraseButton.OnToggled += OnEraseToggled;
|
_buildButton = new Button() {Disabled = true, ToggleMode = true, Text = Loc.GetString("Place construction ghost"), SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.5f};
|
||||||
SearchBar.OnTextChanged += OnTextEntered;
|
|
||||||
RecipeList.OnItemSelected += OnItemSelected;
|
|
||||||
|
|
||||||
PopulatePrototypeList();
|
var eraseContainer = new HBoxContainer() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.5f};
|
||||||
PopulateTree();
|
_eraseButton = new Button() {Text = Loc.GetString("Eraser Mode"), ToggleMode = true, SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.7f};
|
||||||
|
var clearButton = new Button() {Text = Loc.GetString("Clear All"), SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.3f};
|
||||||
|
|
||||||
|
recipeContainer.AddChild(searchContainer);
|
||||||
|
recipeContainer.AddChild(_recipes);
|
||||||
|
|
||||||
|
searchContainer.AddChild(_searchBar);
|
||||||
|
searchContainer.AddChild(_category);
|
||||||
|
|
||||||
|
targetInfoContainer.AddChild(_targetName);
|
||||||
|
targetInfoContainer.AddChild(_targetDescription);
|
||||||
|
|
||||||
|
targetContainer.AddChild(_targetTexture);
|
||||||
|
targetContainer.AddChild(targetInfoContainer);
|
||||||
|
|
||||||
|
stepsContainer.AddChild(targetContainer);
|
||||||
|
stepsContainer.AddChild(_stepList);
|
||||||
|
|
||||||
|
eraseContainer.AddChild(_eraseButton);
|
||||||
|
eraseContainer.AddChild(clearButton);
|
||||||
|
|
||||||
|
buttonContainer.AddChild(_buildButton);
|
||||||
|
buttonContainer.AddChild(eraseContainer);
|
||||||
|
|
||||||
|
stepsContainer.AddChild(buttonContainer);
|
||||||
|
|
||||||
|
hbox.AddChild(recipeContainer);
|
||||||
|
hbox.AddChild(spacer);
|
||||||
|
hbox.AddChild(stepsContainer);
|
||||||
|
Contents.AddChild(hbox);
|
||||||
|
|
||||||
|
_recipes.OnItemSelected += RecipeSelected;
|
||||||
|
_recipes.OnItemDeselected += RecipeDeselected;
|
||||||
|
|
||||||
|
_searchBar.OnTextChanged += SearchTextChanged;
|
||||||
|
_category.OnItemSelected += CategorySelected;
|
||||||
|
|
||||||
|
_buildButton.OnToggled += BuildButtonToggled;
|
||||||
|
clearButton.OnPressed += ClearAllButtonPressed;
|
||||||
|
_eraseButton.OnToggled += EraseButtonToggled;
|
||||||
|
|
||||||
|
PopulateCategories();
|
||||||
|
PopulateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlacementChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_buildButton.Pressed = false;
|
||||||
|
_eraseButton.Pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateAll()
|
||||||
|
{
|
||||||
|
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||||
|
{
|
||||||
|
_recipes.Add(GetItem(recipe, _recipes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
|
||||||
|
{
|
||||||
|
return new ItemList.Item(itemList)
|
||||||
|
{
|
||||||
|
Metadata = recipe,
|
||||||
|
Text = recipe.Name,
|
||||||
|
Icon = recipe.Icon.Frame0(),
|
||||||
|
TooltipEnabled = true,
|
||||||
|
TooltipText = recipe.Description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateBy(string search, string category)
|
||||||
|
{
|
||||||
|
_recipes.Clear();
|
||||||
|
|
||||||
|
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(search))
|
||||||
|
{
|
||||||
|
if (!recipe.Name.ToLowerInvariant().Contains(search.Trim().ToLowerInvariant()))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(category) && category != Loc.GetString("All"))
|
||||||
|
{
|
||||||
|
if (recipe.Category != category)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_recipes.Add(GetItem(recipe, _recipes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateCategories()
|
||||||
|
{
|
||||||
|
var uniqueCategories = new HashSet<string>();
|
||||||
|
|
||||||
|
// hard-coded to show all recipes
|
||||||
|
uniqueCategories.Add(Loc.GetString("All"));
|
||||||
|
|
||||||
|
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||||
|
{
|
||||||
|
var category = Loc.GetString(prototype.Category);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(category))
|
||||||
|
uniqueCategories.Add(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
_category.Clear();
|
||||||
|
|
||||||
|
var array = uniqueCategories.ToArray();
|
||||||
|
Array.Sort(array);
|
||||||
|
|
||||||
|
for (var i = 0; i < array.Length; i++)
|
||||||
|
{
|
||||||
|
var category = array[i];
|
||||||
|
_category.AddItem(category, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
_categories = array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateInfo(ConstructionPrototype prototype)
|
||||||
|
{
|
||||||
|
ClearInfo();
|
||||||
|
|
||||||
|
var isItem = prototype.Type == ConstructionType.Item;
|
||||||
|
|
||||||
|
_buildButton.Disabled = false;
|
||||||
|
_buildButton.Text = Loc.GetString(!isItem ? "Place construction ghost" : "Craft");
|
||||||
|
_targetName.SetMessage(prototype.Name);
|
||||||
|
_targetDescription.SetMessage(prototype.Description);
|
||||||
|
_targetTexture.Texture = prototype.Icon.Frame0();
|
||||||
|
|
||||||
|
if (!_prototypeManager.TryIndex(prototype.Graph, out ConstructionGraphPrototype graph))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var startNode = graph.Nodes[prototype.StartNode];
|
||||||
|
var targetNode = graph.Nodes[prototype.TargetNode];
|
||||||
|
|
||||||
|
var path = graph.Path(startNode.Name, targetNode.Name);
|
||||||
|
|
||||||
|
var current = startNode;
|
||||||
|
|
||||||
|
var stepNumber = 1;
|
||||||
|
|
||||||
|
Texture? GetTextureForStep(ConstructionGraphStep step)
|
||||||
|
{
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case MaterialConstructionGraphStep materialStep:
|
||||||
|
switch (materialStep.Material)
|
||||||
|
{
|
||||||
|
case StackType.Metal:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Materials/sheets.rsi/metal.png");
|
||||||
|
|
||||||
|
case StackType.Glass:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Materials/sheets.rsi/glass.png");
|
||||||
|
|
||||||
|
case StackType.Plasteel:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Materials/sheets.rsi/plasteel.png");
|
||||||
|
|
||||||
|
case StackType.Phoron:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Materials/sheets.rsi/phoron.png");
|
||||||
|
|
||||||
|
case StackType.Cable:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/cables.rsi/coil-30.png");
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ToolConstructionGraphStep toolStep:
|
||||||
|
switch (toolStep.Tool)
|
||||||
|
{
|
||||||
|
case ToolQuality.Anchoring:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/wrench.rsi/icon.png");
|
||||||
|
case ToolQuality.Prying:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/crowbar.rsi/icon.png");
|
||||||
|
case ToolQuality.Screwing:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/screwdriver.rsi/screwdriver-map.png");
|
||||||
|
case ToolQuality.Cutting:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/wirecutters.rsi/cutters-map.png");
|
||||||
|
case ToolQuality.Welding:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/welder.rsi/welder.png");
|
||||||
|
case ToolQuality.Multitool:
|
||||||
|
return _resourceCache.GetTexture("/Textures/Objects/Tools/multitool.rsi/multitool.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ComponentConstructionGraphStep componentStep:
|
||||||
|
return componentStep.Icon?.Frame0();
|
||||||
|
|
||||||
|
case PrototypeConstructionGraphStep prototypeStep:
|
||||||
|
return prototypeStep.Icon?.Frame0();
|
||||||
|
|
||||||
|
case NestedConstructionGraphStep _:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var node in path)
|
||||||
|
{
|
||||||
|
var edge = current.GetEdge(node.Name);
|
||||||
|
var firstNode = current == startNode;
|
||||||
|
|
||||||
|
if (firstNode)
|
||||||
|
{
|
||||||
|
_stepList.AddItem(isItem
|
||||||
|
? Loc.GetString($"{stepNumber++}. To craft this item, you need:")
|
||||||
|
: Loc.GetString($"{stepNumber++}. To build this, first you need:"));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var step in edge.Steps)
|
||||||
|
{
|
||||||
|
var icon = GetTextureForStep(step);
|
||||||
|
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case MaterialConstructionGraphStep materialStep:
|
||||||
|
_stepList.AddItem(
|
||||||
|
!firstNode
|
||||||
|
? Loc.GetString(
|
||||||
|
"{0}. Add {1}x {2}.", stepNumber++, materialStep.Amount, materialStep.Material)
|
||||||
|
: Loc.GetString(" {0}x {1}", materialStep.Amount, materialStep.Material), icon);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ToolConstructionGraphStep toolStep:
|
||||||
|
_stepList.AddItem(Loc.GetString("{0}. Use a {1}.", stepNumber++, toolStep.Tool.GetToolName()), icon);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PrototypeConstructionGraphStep prototypeStep:
|
||||||
|
_stepList.AddItem(Loc.GetString("{0}. Add {1}.", stepNumber++, prototypeStep.Name), icon);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ComponentConstructionGraphStep componentStep:
|
||||||
|
_stepList.AddItem(Loc.GetString("{0}. Add {1}.", stepNumber++, componentStep.Name), icon);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NestedConstructionGraphStep nestedStep:
|
||||||
|
var parallelNumber = 1;
|
||||||
|
_stepList.AddItem(Loc.GetString("{0}. In parallel...", stepNumber++));
|
||||||
|
|
||||||
|
foreach (var steps in nestedStep.Steps)
|
||||||
|
{
|
||||||
|
var subStepNumber = 1;
|
||||||
|
|
||||||
|
foreach (var subStep in steps)
|
||||||
|
{
|
||||||
|
icon = GetTextureForStep(subStep);
|
||||||
|
|
||||||
|
switch (subStep)
|
||||||
|
{
|
||||||
|
case MaterialConstructionGraphStep materialStep:
|
||||||
|
if (!isItem)
|
||||||
|
_stepList.AddItem(Loc.GetString(" {0}.{1}.{2}. Add {3}x {4}.", stepNumber, parallelNumber, subStepNumber++, materialStep.Amount, materialStep.Material), icon);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ToolConstructionGraphStep toolStep:
|
||||||
|
_stepList.AddItem(Loc.GetString(" {0}.{1}.{2}. Use a {3}.", stepNumber, parallelNumber, subStepNumber++, toolStep.Tool.GetToolName()), icon);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PrototypeConstructionGraphStep prototypeStep:
|
||||||
|
_stepList.AddItem(Loc.GetString(" {0}.{1}.{2}. Add {3}.", stepNumber, parallelNumber, subStepNumber++, prototypeStep.Name), icon);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ComponentConstructionGraphStep componentStep:
|
||||||
|
_stepList.AddItem(Loc.GetString(" {0}.{1}.{2}. Add {3}.", stepNumber, parallelNumber, subStepNumber++, componentStep.Name), icon);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parallelNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearInfo()
|
||||||
|
{
|
||||||
|
_buildButton.Disabled = true;
|
||||||
|
_targetName.SetMessage(string.Empty);
|
||||||
|
_targetDescription.SetMessage(string.Empty);
|
||||||
|
_targetTexture.Texture = null;
|
||||||
|
_stepList.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecipeSelected(ItemList.ItemListSelectedEventArgs obj)
|
||||||
|
{
|
||||||
|
_selected = (ConstructionPrototype) obj.ItemList[obj.ItemIndex].Metadata!;
|
||||||
|
PopulateInfo(_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecipeDeselected(ItemList.ItemListDeselectedEventArgs obj)
|
||||||
|
{
|
||||||
|
_selected = null;
|
||||||
|
ClearInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CategorySelected(OptionButton.ItemSelectedEventArgs obj)
|
||||||
|
{
|
||||||
|
_category.SelectId(obj.Id);
|
||||||
|
PopulateBy(_searchBar.Text, _categories[obj.Id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SearchTextChanged(LineEdit.LineEditEventArgs obj)
|
||||||
|
{
|
||||||
|
PopulateBy(_searchBar.Text, _categories[_category.SelectedId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Pressed)
|
||||||
|
{
|
||||||
|
if (_selected == null) return;
|
||||||
|
|
||||||
|
var constructSystem = EntitySystem.Get<ConstructionSystem>();
|
||||||
|
|
||||||
|
if (_selected.Type == ConstructionType.Item)
|
||||||
|
{
|
||||||
|
constructSystem.TryStartItemConstruction(_selected.ID);
|
||||||
|
_buildButton.Pressed = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_placementManager.BeginPlacing(new PlacementInformation()
|
||||||
|
{
|
||||||
|
IsTile = false,
|
||||||
|
PlacementOption = _selected.PlacementMode,
|
||||||
|
}, new ConstructionPlacementHijack(constructSystem, _selected));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_placementManager.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildButton.Pressed = args.Pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EraseButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Pressed) _placementManager.Clear();
|
||||||
|
_placementManager.ToggleEraserHijacked(new ConstructionPlacementHijack(_systemManager.GetEntitySystem<ConstructionSystem>(), null));
|
||||||
|
_eraseButton.Pressed = args.Pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearAllButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||||
|
{
|
||||||
|
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
|
||||||
|
|
||||||
|
constructionSystem.ClearAllGhosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
Placement.PlacementChanged -= OnPlacementChanged;
|
_placementManager.PlacementChanged -= PlacementChanged;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnItemSelected()
|
|
||||||
{
|
|
||||||
var prototype = (ConstructionPrototype) RecipeList.Selected.Metadata;
|
|
||||||
|
|
||||||
if (prototype == null)
|
|
||||||
{
|
|
||||||
InfoLabel.Text = "";
|
|
||||||
InfoIcon.Texture = null;
|
|
||||||
StepList.Clear();
|
|
||||||
BuildButton.Disabled = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BuildButton.Disabled = false;
|
|
||||||
InfoLabel.Text = prototype.Description;
|
|
||||||
InfoIcon.Texture = prototype.Icon.Frame0();
|
|
||||||
|
|
||||||
StepList.Clear();
|
|
||||||
|
|
||||||
foreach (var forward in prototype.Stages.Select(a => a.Forward))
|
|
||||||
{
|
|
||||||
if (forward == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture icon;
|
|
||||||
string text;
|
|
||||||
switch (forward)
|
|
||||||
{
|
|
||||||
case ConstructionStepMaterial mat:
|
|
||||||
switch (mat.Material)
|
|
||||||
{
|
|
||||||
case ConstructionStepMaterial.MaterialType.Metal:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>(
|
|
||||||
"/Textures/Objects/Materials/sheet_metal.png");
|
|
||||||
text = $"Metal x{mat.Amount}";
|
|
||||||
break;
|
|
||||||
case ConstructionStepMaterial.MaterialType.Glass:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>(
|
|
||||||
"/Textures/Objects/Materials/sheet_glass.png");
|
|
||||||
text = $"Glass x{mat.Amount}";
|
|
||||||
break;
|
|
||||||
case ConstructionStepMaterial.MaterialType.Cable:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>(
|
|
||||||
"/Textures/Objects/Tools/cable_coil.png");
|
|
||||||
text = $"Cable Coil x{mat.Amount}";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case ConstructionStepTool tool:
|
|
||||||
switch (tool.ToolQuality)
|
|
||||||
{
|
|
||||||
case ToolQuality.Anchoring:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>("/Textures/Objects/Tools/wrench.rsi/icon.png");
|
|
||||||
text = "Wrench";
|
|
||||||
break;
|
|
||||||
case ToolQuality.Prying:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>("/Textures/Objects/Tools/crowbar.rsi/icon.png");
|
|
||||||
text = "Crowbar";
|
|
||||||
break;
|
|
||||||
case ToolQuality.Screwing:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>(
|
|
||||||
"/Textures/Objects/Tools/screwdriver.rsi/screwdriver-map.png");
|
|
||||||
text = "Screwdriver";
|
|
||||||
break;
|
|
||||||
case ToolQuality.Welding:
|
|
||||||
icon = _resourceCache.GetResource<RSIResource>("/Textures/Objects/tools.rsi")
|
|
||||||
.RSI["welder"].Frame0;
|
|
||||||
text = $"Welding tool ({tool.Amount} fuel)";
|
|
||||||
break;
|
|
||||||
case ToolQuality.Cutting:
|
|
||||||
icon = _resourceCache.GetResource<TextureResource>(
|
|
||||||
"/Textures/Objects/Tools/wirecutters.rsi/cutters-map.png");
|
|
||||||
text = "Wirecutters";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
StepList.AddItem(text, icon, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTextEntered(LineEdit.LineEditEventArgs args)
|
|
||||||
{
|
|
||||||
var str = args.Text;
|
|
||||||
PopulateTree(string.IsNullOrWhiteSpace(str) ? null : str.ToLowerInvariant());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuildToggled(BaseButton.ButtonToggledEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.Pressed)
|
|
||||||
{
|
|
||||||
var prototype = (ConstructionPrototype) RecipeList.Selected.Metadata;
|
|
||||||
if (prototype == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prototype.Type == ConstructionType.Item)
|
|
||||||
{
|
|
||||||
var constructSystem = _systemManager.GetEntitySystem<ConstructionSystem>();
|
|
||||||
constructSystem.TryStartItemConstruction(prototype.ID);
|
|
||||||
BuildButton.Pressed = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Placement.BeginHijackedPlacing(
|
|
||||||
new PlacementInformation
|
|
||||||
{
|
|
||||||
IsTile = false,
|
|
||||||
PlacementOption = prototype.PlacementMode
|
|
||||||
},
|
|
||||||
new ConstructionPlacementHijack(_systemManager.GetEntitySystem<ConstructionSystem>(), prototype));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Placement.Clear();
|
|
||||||
}
|
|
||||||
BuildButton.Pressed = args.Pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEraseToggled(BaseButton.ButtonToggledEventArgs args)
|
|
||||||
{
|
|
||||||
if (args.Pressed) Placement.Clear();
|
|
||||||
Placement.ToggleEraserHijacked(new ConstructionPlacementHijack(_systemManager.GetEntitySystem<ConstructionSystem>(), null));
|
|
||||||
EraseButton.Pressed = args.Pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlacementChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
BuildButton.Pressed = false;
|
|
||||||
EraseButton.Pressed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulatePrototypeList()
|
|
||||||
{
|
|
||||||
RootCategory = new CategoryNode("", null);
|
|
||||||
var count = 1;
|
|
||||||
|
|
||||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
|
||||||
{
|
|
||||||
var currentNode = RootCategory;
|
|
||||||
|
|
||||||
foreach (var category in prototype.CategorySegments)
|
|
||||||
{
|
|
||||||
if (!currentNode.ChildCategories.TryGetValue(category, out var subNode))
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
subNode = new CategoryNode(category, currentNode);
|
|
||||||
currentNode.ChildCategories.Add(category, subNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentNode = subNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentNode.Prototypes.Add(prototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a pass to sort the prototype lists and flatten the hierarchy.
|
|
||||||
void Recurse(CategoryNode node)
|
|
||||||
{
|
|
||||||
// I give up we're using recursion to flatten this.
|
|
||||||
// There probably IS a way to do it.
|
|
||||||
// I'm too stupid to think of what that way is.
|
|
||||||
foreach (var child in node.ChildCategories.Values)
|
|
||||||
{
|
|
||||||
Recurse(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
node.Prototypes.Sort(ComparePrototype);
|
|
||||||
FlattenedCategories.Add(node);
|
|
||||||
node.FlattenedIndex = FlattenedCategories.Count - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlattenedCategories = new List<CategoryNode>(count);
|
|
||||||
Recurse(RootCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateTree(string searchTerm = null)
|
|
||||||
{
|
|
||||||
RecipeList.Clear();
|
|
||||||
|
|
||||||
var categoryItems = new Tree.Item[FlattenedCategories.Count];
|
|
||||||
categoryItems[RootCategory.FlattenedIndex] = RecipeList.CreateItem();
|
|
||||||
|
|
||||||
// Yay more recursion.
|
|
||||||
Tree.Item ItemForNode(CategoryNode node)
|
|
||||||
{
|
|
||||||
if (categoryItems[node.FlattenedIndex] != null)
|
|
||||||
{
|
|
||||||
return categoryItems[node.FlattenedIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = RecipeList.CreateItem(ItemForNode(node.Parent));
|
|
||||||
item.Text = node.Name;
|
|
||||||
item.Selectable = false;
|
|
||||||
categoryItems[node.FlattenedIndex] = item;
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var node in FlattenedCategories)
|
|
||||||
{
|
|
||||||
foreach (var prototype in node.Prototypes)
|
|
||||||
{
|
|
||||||
if (searchTerm != null)
|
|
||||||
{
|
|
||||||
var found = false;
|
|
||||||
// TODO: don't run ToLowerInvariant() constantly.
|
|
||||||
if (prototype.Name.ToLowerInvariant().IndexOf(searchTerm, StringComparison.Ordinal) != -1)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var keyw in prototype.Keywords.Concat(prototype.CategorySegments))
|
|
||||||
{
|
|
||||||
// TODO: don't run ToLowerInvariant() constantly.
|
|
||||||
if (keyw.ToLowerInvariant().IndexOf(searchTerm, StringComparison.Ordinal) != -1)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var subItem = RecipeList.CreateItem(ItemForNode(node));
|
|
||||||
subItem.Text = prototype.Name;
|
|
||||||
subItem.Metadata = prototype;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ComparePrototype(ConstructionPrototype x, ConstructionPrototype y)
|
|
||||||
{
|
|
||||||
return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CategoryNode
|
|
||||||
{
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly CategoryNode Parent;
|
|
||||||
|
|
||||||
public readonly SortedDictionary<string, CategoryNode>
|
|
||||||
ChildCategories = new SortedDictionary<string, CategoryNode>();
|
|
||||||
|
|
||||||
public readonly List<ConstructionPrototype> Prototypes = new List<ConstructionPrototype>();
|
|
||||||
public int FlattenedIndex = -1;
|
|
||||||
|
|
||||||
public CategoryNode(string name, CategoryNode parent)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Parent = parent;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using Content.Shared.Construction;
|
using Content.Shared.Construction;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ namespace Content.Client.GameObjects.Components.Construction
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ConstructionGhostComponent : Component, IExamine
|
public class ConstructionGhostComponent : Component, IExamine
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
public override string Name => "ConstructionGhost";
|
public override string Name => "ConstructionGhost";
|
||||||
|
|
||||||
[ViewVariables] public ConstructionPrototype Prototype { get; set; }
|
[ViewVariables] public ConstructionPrototype Prototype { get; set; }
|
||||||
@@ -18,8 +21,13 @@ namespace Content.Client.GameObjects.Components.Construction
|
|||||||
|
|
||||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||||
{
|
{
|
||||||
message.AddText(Loc.GetString("Building: {0}\n", Prototype.Name));
|
message.AddMarkup(Loc.GetString("Building: [color=cyan]{0}[/color]\n", Prototype.Name));
|
||||||
EntitySystem.Get<SharedConstructionSystem>().DoExamine(message, Prototype, 0, inDetailsRange);
|
|
||||||
|
if (!_prototypeManager.TryIndex(Prototype.Graph, out ConstructionGraphPrototype graph)) return;
|
||||||
|
var startNode = graph.Nodes[Prototype.StartNode];
|
||||||
|
var path = graph.Path(Prototype.StartNode, Prototype.TargetNode);
|
||||||
|
var edge = startNode.GetEdge(path[0].Name);
|
||||||
|
edge.Steps[0].DoExamine(message, inDetailsRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Client.GameObjects.Components.IconSmoothing;
|
using Content.Client.GameObjects.Components.IconSmoothing;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
@@ -35,6 +36,7 @@ namespace Content.Client.GameObjects.Components
|
|||||||
Sprite.LayerSetDirOffset(ReinforcedCornerLayers.NW, DirectionOffset.Flip);
|
Sprite.LayerSetDirOffset(ReinforcedCornerLayers.NW, DirectionOffset.Flip);
|
||||||
Sprite.LayerMapSet(ReinforcedCornerLayers.SW, Sprite.AddLayerState(state0));
|
Sprite.LayerMapSet(ReinforcedCornerLayers.SW, Sprite.AddLayerState(state0));
|
||||||
Sprite.LayerSetDirOffset(ReinforcedCornerLayers.SW, DirectionOffset.Clockwise);
|
Sprite.LayerSetDirOffset(ReinforcedCornerLayers.SW, DirectionOffset.Clockwise);
|
||||||
|
Sprite.LayerMapSet(ReinforcedWallVisualLayers.Deconstruction, Sprite.AddBlankLayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void CalculateNewSprite()
|
internal override void CalculateNewSprite()
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class ReinforcedWallVisualizer : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
if (component.TryGetData(ReinforcedWallVisuals.DeconstructionStage, out int stage))
|
||||||
|
{
|
||||||
|
SetDeconstructionStage(component, stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDeconstructionStage(AppearanceComponent component, int stage)
|
||||||
|
{
|
||||||
|
var entity = component.Owner;
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out ISpriteComponent sprite)) return;
|
||||||
|
|
||||||
|
if (stage < 0)
|
||||||
|
{
|
||||||
|
sprite.LayerSetVisible(ReinforcedWallVisualLayers.Deconstruction, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite.LayerSetVisible(ReinforcedWallVisualLayers.Deconstruction, true);
|
||||||
|
sprite.LayerSetState(ReinforcedWallVisualLayers.Deconstruction, $"reinf_construct-{stage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ReinforcedWallVisualLayers
|
||||||
|
{
|
||||||
|
Deconstruction,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ using Robust.Shared.ViewVariables;
|
|||||||
namespace Content.Client.GameObjects.Components
|
namespace Content.Client.GameObjects.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedStackComponent))]
|
||||||
public class StackComponent : SharedStackComponent, IItemStatus
|
public class StackComponent : SharedStackComponent, IItemStatus
|
||||||
{
|
{
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Content.Client.GameObjects.EntitySystems;
|
using Content.Client.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Interfaces.GameObjects.Components;
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -9,10 +10,9 @@ using static Content.Client.GameObjects.Components.IconSmoothing.IconSmoothCompo
|
|||||||
namespace Content.Client.GameObjects.Components
|
namespace Content.Client.GameObjects.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class WindowComponent : Component
|
[ComponentReference(typeof(SharedWindowComponent))]
|
||||||
|
public sealed class WindowComponent : SharedWindowComponent
|
||||||
{
|
{
|
||||||
public override string Name => "Window";
|
|
||||||
|
|
||||||
private string _stateBase;
|
private string _stateBase;
|
||||||
private ISpriteComponent _sprite;
|
private ISpriteComponent _sprite;
|
||||||
private SnapGridComponent _snapGrid;
|
private SnapGridComponent _snapGrid;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Client.UserInterface;
|
|||||||
using Content.Shared.Construction;
|
using Content.Shared.Construction;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
|
using Content.Shared.Utility;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.GameObjects.EntitySystems;
|
using Robust.Client.GameObjects.EntitySystems;
|
||||||
@@ -152,11 +153,20 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SpawnGhost(ConstructionPrototype prototype, EntityCoordinates loc, Direction dir)
|
public void SpawnGhost(ConstructionPrototype prototype, EntityCoordinates loc, Direction dir)
|
||||||
{
|
{
|
||||||
if (GhostPresent(loc))
|
var user = _playerManager.LocalPlayer?.ControlledEntity;
|
||||||
|
|
||||||
|
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
|
||||||
|
if (user == null || GhostPresent(loc) || !user.InRangeUnobstructed(loc, 20f, ignoreInsideBlocker:prototype.CanBuildInImpassable))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var condition in prototype.Conditions)
|
||||||
|
{
|
||||||
|
if (!condition.Condition(user, loc, dir))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var ghost = _entityManager.SpawnEntity("constructionghost", loc);
|
var ghost = _entityManager.SpawnEntity("constructionghost", loc);
|
||||||
var comp = ghost.GetComponent<ConstructionGhostComponent>();
|
var comp = ghost.GetComponent<ConstructionGhostComponent>();
|
||||||
comp.Prototype = prototype;
|
comp.Prototype = prototype;
|
||||||
@@ -214,5 +224,18 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
_ghosts.Remove(ghostId);
|
_ghosts.Remove(ghostId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes all construction ghosts.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearAllGhosts()
|
||||||
|
{
|
||||||
|
foreach (var (_, ghost) in _ghosts)
|
||||||
|
{
|
||||||
|
ghost.Owner.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ghosts.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,12 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
|||||||
|
|
||||||
if (doAfter.BreakOnTargetMove)
|
if (doAfter.BreakOnTargetMove)
|
||||||
{
|
{
|
||||||
var targetEntity = _entityManager.GetEntity(doAfter.TargetUid);
|
if (!_entityManager.TryGetEntity(doAfter.TargetUid, out var targetEntity))
|
||||||
|
{
|
||||||
|
// Cancel if the target entity doesn't exist.
|
||||||
|
doAfterComponent.Cancel(id, currentTime);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (targetEntity.Transform.Coordinates != doAfter.TargetGrid)
|
if (targetEntity.Transform.Coordinates != doAfter.TargetGrid)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -179,6 +179,8 @@
|
|||||||
"Butcherable",
|
"Butcherable",
|
||||||
"Rehydratable",
|
"Rehydratable",
|
||||||
"Headset",
|
"Headset",
|
||||||
|
"ComputerBoard",
|
||||||
|
"BreakableConstruction",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -819,9 +819,7 @@ namespace Content.Server.Atmos
|
|||||||
|
|
||||||
var tileRef = GridIndices.GetTileRef(GridIndex);
|
var tileRef = GridIndices.GetTileRef(GridIndex);
|
||||||
|
|
||||||
if (tileRef == null) return;
|
foreach (var entity in tileRef.GetEntitiesInTileFast(_gridTileLookupSystem))
|
||||||
|
|
||||||
foreach (var entity in tileRef?.GetEntitiesInTileFast(_gridTileLookupSystem))
|
|
||||||
{
|
{
|
||||||
foreach (var fireAct in entity.GetAllComponents<IFireAct>())
|
foreach (var fireAct in entity.GetAllComponents<IFireAct>())
|
||||||
{
|
{
|
||||||
|
|||||||
68
Content.Server/Construction/Completions/BuildComputer.cs
Normal file
68
Content.Server/Construction/Completions/BuildComputer.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.GameObjects.Components.Construction;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class BuildComputer : IGraphAction
|
||||||
|
{
|
||||||
|
public string Container { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Container, "container", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Computer entity {entity} did not have a container manager! Aborting build computer action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!containerManager.TryGetContainer(Container, out var container))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Computer entity {entity} did not have the specified '{Container}' container! Aborting build computer action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.ContainedEntities.Count != 1)
|
||||||
|
{
|
||||||
|
Logger.Warning($"Computer entity {entity} did not have exactly one item in the specified '{Container}' container! Aborting build computer action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var board = container.ContainedEntities[0];
|
||||||
|
|
||||||
|
if (!board.TryGetComponent(out ComputerBoardComponent? boardComponent))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Computer entity {entity} had an invalid entity in container \"{Container}\"! Aborting build computer action.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entityManager = entity.EntityManager;
|
||||||
|
container.Remove(board);
|
||||||
|
|
||||||
|
var computer = entityManager.SpawnEntity(boardComponent.Prototype, entity.Transform.Coordinates);
|
||||||
|
computer.Transform.LocalRotation = entity.Transform.LocalRotation;
|
||||||
|
|
||||||
|
var computerContainer = ContainerManagerComponent.Ensure<Container>(Container, computer);
|
||||||
|
computerContainer.Insert(board);
|
||||||
|
|
||||||
|
if (computer.TryGetComponent(out ConstructionComponent? construction))
|
||||||
|
{
|
||||||
|
// We only add this container. If some construction needs to take other containers into account, fix this.
|
||||||
|
construction.AddContainer(Container);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Content.Server/Construction/Completions/DeleteEntity.cs
Normal file
24
Content.Server/Construction/Completions/DeleteEntity.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class DeleteEntity : IGraphAction
|
||||||
|
{
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (entity.Deleted) return;
|
||||||
|
|
||||||
|
entity.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Content.Server/Construction/Completions/EmptyContainer.cs
Normal file
37
Content.Server/Construction/Completions/EmptyContainer.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class EmptyContainer : IGraphAction
|
||||||
|
{
|
||||||
|
public string Container { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Container, "container", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (entity.Deleted) return;
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) ||
|
||||||
|
!containerManager.TryGetContainer(Container, out var container)) return;
|
||||||
|
|
||||||
|
foreach (var ent in container.ContainedEntities.ToArray())
|
||||||
|
{
|
||||||
|
if (ent == null || ent.Deleted) continue;
|
||||||
|
container.ForceRemove(ent);
|
||||||
|
ent.Transform.Coordinates = entity.Transform.Coordinates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Server/Construction/Completions/PlaySound.cs
Normal file
39
Content.Server/Construction/Completions/PlaySound.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class PlaySound : IGraphAction
|
||||||
|
{
|
||||||
|
public string SoundCollection { get; private set; } = string.Empty;
|
||||||
|
public string Sound { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Sound, "sound", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.SoundCollection, "soundCollection", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
var sound = GetSound();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(sound)) return;
|
||||||
|
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayFromEntity(sound, entity, AudioHelpers.WithVariation(0.125f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSound()
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(SoundCollection) ? AudioHelpers.GetRandomFileFromSoundCollection(SoundCollection) : Sound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Content.Server/Construction/Completions/PopupUser.cs
Normal file
33
Content.Server/Construction/Completions/PopupUser.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class PopupUser : IGraphAction
|
||||||
|
{
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Text, "text", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.Cursor, "cursor", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Cursor { get; private set; } = false;
|
||||||
|
public string Text { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
if(Cursor)
|
||||||
|
user.PopupMessageCursor(Text);
|
||||||
|
else
|
||||||
|
entity.PopupMessage(user, Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/Construction/Completions/SetAnchor.cs
Normal file
28
Content.Server/Construction/Completions/SetAnchor.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class SetAnchor : IGraphAction
|
||||||
|
{
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Value, "value", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Value { get; private set; } = true;
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out CollidableComponent? collidable)) return;
|
||||||
|
|
||||||
|
collidable.Anchored = Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Content.Server/Construction/Completions/SnapToGrid.cs
Normal file
29
Content.Server/Construction/Completions/SnapToGrid.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Utility;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects.Components.Transform;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class SnapToGrid : IGraphAction
|
||||||
|
{
|
||||||
|
public SnapGridOffset Offset { get; private set; } = SnapGridOffset.Center;
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Offset, "offset", SnapGridOffset.Center);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (entity.Deleted) return;
|
||||||
|
|
||||||
|
entity.SnapToGrid(Offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Content.Server/Construction/Completions/SpawnPrototype.cs
Normal file
36
Content.Server/Construction/Completions/SpawnPrototype.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class SpawnPrototype : IGraphAction
|
||||||
|
{
|
||||||
|
public string Prototype { get; private set; } = string.Empty;
|
||||||
|
public int Amount { get; private set; } = 1;
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Prototype, "prototype", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.Amount, "amount", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (entity.Deleted || string.IsNullOrEmpty(Prototype)) return;
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var coordinates = entity.Transform.Coordinates;
|
||||||
|
|
||||||
|
for (var i = 0; i < Amount; i++)
|
||||||
|
{
|
||||||
|
entityManager.SpawnEntity(Prototype, coordinates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Content.Server/Construction/Completions/SpriteChange.cs
Normal file
33
Content.Server/Construction/Completions/SpriteChange.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class SpriteChange : IGraphAction
|
||||||
|
{
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.SpriteSpecifier, "specifier", SpriteSpecifier.Invalid);
|
||||||
|
serializer.DataField(this, x => x.Layer, "layer", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Layer { get; private set; } = 0;
|
||||||
|
public SpriteSpecifier? SpriteSpecifier { get; private set; } = SpriteSpecifier.Invalid;
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (entity.Deleted || SpriteSpecifier == null || SpriteSpecifier == SpriteSpecifier.Invalid) return;
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out SpriteComponent? sprite)) return;
|
||||||
|
|
||||||
|
sprite.LayerSetSprite(Layer, SpriteSpecifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Server/Construction/Completions/SpriteStateChange.cs
Normal file
39
Content.Server/Construction/Completions/SpriteStateChange.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class SpriteStateChange : IGraphAction
|
||||||
|
{
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.State, "state", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.Layer, "layer", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Layer { get; private set; } = 0;
|
||||||
|
public string? State { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public async Task StepCompleted(IEntity entity, IEntity user)
|
||||||
|
{
|
||||||
|
await PerformAction(entity, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (entity.Deleted || string.IsNullOrEmpty(State)) return;
|
||||||
|
|
||||||
|
if (!entity.TryGetComponent(out SpriteComponent? sprite)) return;
|
||||||
|
|
||||||
|
sprite.LayerSetState(Layer, State);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Content.Server/Construction/Completions/VisualizerDataInt.cs
Normal file
49
Content.Server/Construction/Completions/VisualizerDataInt.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Reflection;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Completions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class VisualizerDataInt : IGraphAction
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||||
|
|
||||||
|
public VisualizerDataInt()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Key, "key", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.Data, "data", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? Key { get; private set; } = string.Empty;
|
||||||
|
public int Data { get; private set; } = 0;
|
||||||
|
|
||||||
|
public async Task PerformAction(IEntity entity, IEntity? user)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Key)) return;
|
||||||
|
|
||||||
|
if (entity.TryGetComponent(out AppearanceComponent? appearance))
|
||||||
|
{
|
||||||
|
if(_reflectionManager.TryParseEnumReference(Key, out var @enum))
|
||||||
|
{
|
||||||
|
appearance.SetData(@enum, Data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appearance.SetData(Key, Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
Content.Server/Construction/Conditions/ComponentInTile.cs
Normal file
61
Content.Server/Construction/Conditions/ComponentInTile.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Conditions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Makes the condition fail if any entities on a tile have (or not) a component.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class ComponentInTile : IEdgeCondition
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
|
||||||
|
public ComponentInTile()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Component, "component", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.HasEntity, "hasEntity", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, any entity on the tile must have the component.
|
||||||
|
/// If false, no entity on the tile must have the component.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasEntity { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The component name in question.
|
||||||
|
/// </summary>
|
||||||
|
public string Component { get; private set; }
|
||||||
|
|
||||||
|
public async Task<bool> Condition(IEntity entity)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Component)) return false;
|
||||||
|
|
||||||
|
var type = _componentFactory.GetRegistration(Component).Type;
|
||||||
|
|
||||||
|
var indices = entity.Transform.Coordinates.ToMapIndices(entity.EntityManager, _mapManager);
|
||||||
|
var entities = indices.GetEntitiesInTile(entity.Transform.GridID, true, entity.EntityManager);
|
||||||
|
|
||||||
|
foreach (var ent in entities)
|
||||||
|
{
|
||||||
|
if (ent.HasComponent(type))
|
||||||
|
return HasEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !HasEntity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Content.Server/Construction/Conditions/ContainerEmpty.cs
Normal file
41
Content.Server/Construction/Conditions/ContainerEmpty.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Conditions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class ContainerEmpty : IEdgeCondition
|
||||||
|
{
|
||||||
|
public string Container { get; private set; } = string.Empty;
|
||||||
|
public string Text { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Container, "container", string.Empty);
|
||||||
|
serializer.DataField(this, x => x.Text, "text", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Condition(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) ||
|
||||||
|
!containerManager.TryGetContainer(Container, out var container)) return true;
|
||||||
|
|
||||||
|
return container.ContainedEntities.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ContainerManagerComponent? containerManager) ||
|
||||||
|
!containerManager.TryGetContainer(Container, out var container)) return;
|
||||||
|
|
||||||
|
if (container.ContainedEntities.Count != 0)
|
||||||
|
message.AddMarkup(Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Server/Construction/Conditions/EntityAnchored.cs
Normal file
39
Content.Server/Construction/Conditions/EntityAnchored.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Construction.Conditions
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class EntityAnchored : IEdgeCondition
|
||||||
|
{
|
||||||
|
public bool Anchored { get; private set; }
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Anchored, "anchored", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Condition(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ICollidableComponent collidable)) return false;
|
||||||
|
|
||||||
|
return collidable.Anchored == Anchored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out ICollidableComponent collidable)) return;
|
||||||
|
|
||||||
|
if(Anchored && !collidable.Anchored)
|
||||||
|
message.AddMarkup("First, anchor it.\n");
|
||||||
|
|
||||||
|
if(!Anchored && collidable.Anchored)
|
||||||
|
message.AddMarkup("First, unanchor it.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Content.Server/Construction/Conditions/WirePanel.cs
Normal file
40
Content.Server/Construction/Conditions/WirePanel.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.GameObjects.Components;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class WirePanel : IEdgeCondition
|
||||||
|
{
|
||||||
|
public bool Open { get; private set; }
|
||||||
|
|
||||||
|
public void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
serializer.DataField(this, x => x.Open, "open", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Condition(IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out WiresComponent wires)) return false;
|
||||||
|
|
||||||
|
return wires.IsPanelOpen == Open;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoExamine(IEntity entity, FormattedMessage message, bool inDetailsRange)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out WiresComponent wires)) return;
|
||||||
|
|
||||||
|
if(Open && !wires.IsPanelOpen)
|
||||||
|
message.AddMarkup(Loc.GetString("First, open the maintenance panel.\n"));
|
||||||
|
|
||||||
|
if(!Open && wires.IsPanelOpen)
|
||||||
|
message.AddMarkup(Loc.GetString("First, close the maintenance panel.\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ namespace Content.Server.GameObjects.Components.Access
|
|||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(IActivate))]
|
[ComponentReference(typeof(IActivate))]
|
||||||
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate, IInteractUsing
|
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate, IInteractUsing, IBreakAct
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
@@ -307,6 +307,15 @@ namespace Content.Server.GameObjects.Components.Access
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnBreak(BreakageEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
var privileged = _privilegedIdContainer.ContainedEntity;
|
||||||
|
if (privileged != null)
|
||||||
|
_privilegedIdContainer.Remove(privileged);
|
||||||
|
|
||||||
|
var target = _targetIdContainer.ContainedEntity;
|
||||||
|
if (target != null)
|
||||||
|
_targetIdContainer.Remove(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Content.Shared.Interfaces.GameObjects.Components;
|
|||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Components;
|
using Robust.Shared.GameObjects.Components;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components
|
namespace Content.Server.GameObjects.Components
|
||||||
@@ -16,6 +17,15 @@ namespace Content.Server.GameObjects.Components
|
|||||||
{
|
{
|
||||||
public override string Name => "Anchorable";
|
public override string Name => "Anchorable";
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(this, x => x.Tool, "tool", ToolQuality.Anchoring);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public ToolQuality Tool { get; private set; } = ToolQuality.Anchoring;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
int IInteractUsing.Priority => 1;
|
int IInteractUsing.Priority => 1;
|
||||||
|
|
||||||
@@ -37,7 +47,7 @@ namespace Content.Server.GameObjects.Components
|
|||||||
{
|
{
|
||||||
if (utilizing == null ||
|
if (utilizing == null ||
|
||||||
!utilizing.TryGetComponent(out ToolComponent? tool) ||
|
!utilizing.TryGetComponent(out ToolComponent? tool) ||
|
||||||
!(await tool.UseTool(user, Owner, 0.5f, ToolQuality.Anchoring)))
|
!(await tool.UseTool(user, Owner, 0.5f, Tool)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
|||||||
public override bool CanClose(IEntity user) => true;
|
public override bool CanClose(IEntity user) => true;
|
||||||
public override bool CanOpen(IEntity user) => CanOpen();
|
public override bool CanOpen(IEntity user) => CanOpen();
|
||||||
|
|
||||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public override async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
|
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Construction
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ComputerBoardComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "ComputerBoard";
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(this, x => x.Prototype, "prototype", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public string Prototype { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +1,545 @@
|
|||||||
using Content.Shared.Construction;
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.GameObjects.Components.Interactable;
|
||||||
|
using Content.Server.GameObjects.Components.Stack;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.GameObjects.Components.Interactable;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Construction
|
namespace Content.Server.GameObjects.Components.Construction
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Holds data about an entity that is in the process of being constructed or destructed.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ConstructionComponent : Component, IExamine
|
public class ConstructionComponent : Component, IExamine, IInteractUsing
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IComponentFactory _componentFactory = default!;
|
||||||
|
|
||||||
public override string Name => "Construction";
|
public override string Name => "Construction";
|
||||||
|
|
||||||
/// <summary>
|
private bool _handling = false;
|
||||||
/// The current construction recipe being used to build this entity.
|
|
||||||
/// </summary>
|
private TaskCompletionSource<object>? _handlingTask = null;
|
||||||
[ViewVariables]
|
private string _graphIdentifier = string.Empty;
|
||||||
public ConstructionPrototype Prototype { get; set; }
|
private string _startingNodeIdentifier = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current stage of construction.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public int Stage { get; set; }
|
private HashSet<string> _containers = new HashSet<string>();
|
||||||
|
[ViewVariables]
|
||||||
|
private List<List<ConstructionGraphStep>>? _edgeNestedStepProgress = null;
|
||||||
|
|
||||||
|
private ConstructionGraphNode? _target = null;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public ConstructionGraphPrototype GraphPrototype { get; private set; } = null!;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public ConstructionGraphNode Node { get; private set; } = null!;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public ConstructionGraphEdge? Edge { get; private set; } = null;
|
||||||
|
|
||||||
|
public IReadOnlyCollection<string> Containers => _containers;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public ConstructionGraphNode? Target
|
||||||
|
{
|
||||||
|
get => _target;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ClearTarget();
|
||||||
|
_target = value;
|
||||||
|
UpdateTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public ConstructionGraphEdge? TargetNextEdge { get; private set; } = null;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public Queue<ConstructionGraphNode>? TargetPathfinding { get; private set; } = null;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public int EdgeStep { get; private set; } = 0;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
serializer.DataReadWriteFunction("prototype", null,
|
serializer.DataField(ref _graphIdentifier, "graph", string.Empty);
|
||||||
value => Prototype = value, () => Prototype);
|
serializer.DataField(ref _startingNodeIdentifier, "node", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
serializer.DataReadWriteFunction("stage", 0,
|
public void ClearTarget()
|
||||||
value => Stage = value, () => Stage);
|
{
|
||||||
|
_target = null;
|
||||||
|
TargetNextEdge = null;
|
||||||
|
TargetPathfinding = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTarget()
|
||||||
|
{
|
||||||
|
// Can't pathfind without a target.
|
||||||
|
if (Target == null) return;
|
||||||
|
|
||||||
|
// If we're at our target, stop pathfinding.
|
||||||
|
if (Target == Node)
|
||||||
|
{
|
||||||
|
ClearTarget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have the path, set it!
|
||||||
|
if (TargetPathfinding == null)
|
||||||
|
{
|
||||||
|
var path = GraphPrototype.Path(Node.Name, Target.Name);
|
||||||
|
|
||||||
|
if (path == null)
|
||||||
|
{
|
||||||
|
ClearTarget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetPathfinding = new Queue<ConstructionGraphNode>(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dequeue the pathfinding queue if the next is the node we're at.
|
||||||
|
if (TargetPathfinding.Peek() == Node)
|
||||||
|
TargetPathfinding.Dequeue();
|
||||||
|
|
||||||
|
// If we went the wrong way, we stop pathfinding.
|
||||||
|
if (Edge != null && TargetNextEdge != Edge)
|
||||||
|
{
|
||||||
|
ClearTarget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's set the next target edge.
|
||||||
|
if (Edge == null && TargetNextEdge == null)
|
||||||
|
TargetNextEdge = Node.GetEdge(TargetPathfinding.Peek().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (_handling)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
_handlingTask = new TaskCompletionSource<object>();
|
||||||
|
_handling = true;
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
if (Edge == null)
|
||||||
|
result = await HandleNode(eventArgs);
|
||||||
|
else
|
||||||
|
result = await HandleEdge(eventArgs);
|
||||||
|
|
||||||
|
_handling = false;
|
||||||
|
_handlingTask.SetResult(null!);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> HandleNode(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
EdgeStep = 0;
|
||||||
|
|
||||||
|
foreach (var edge in Node.Edges)
|
||||||
|
{
|
||||||
|
if(edge.Steps.Count == 0)
|
||||||
|
throw new InvalidDataException($"Edge to \"{edge.Target}\" in node \"{Node.Name}\" of graph \"{GraphPrototype.ID}\" doesn't have any steps!");
|
||||||
|
|
||||||
|
var firstStep = edge.Steps[0];
|
||||||
|
switch (firstStep)
|
||||||
|
{
|
||||||
|
case MaterialConstructionGraphStep _:
|
||||||
|
case ToolConstructionGraphStep _:
|
||||||
|
case PrototypeConstructionGraphStep _:
|
||||||
|
case ComponentConstructionGraphStep _:
|
||||||
|
if (await HandleStep(eventArgs, edge, firstStep))
|
||||||
|
{
|
||||||
|
if(edge.Steps.Count > 1)
|
||||||
|
Edge = edge;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NestedConstructionGraphStep nestedStep:
|
||||||
|
throw new IndexOutOfRangeException($"Nested construction step not supported as the first step in an edge! Graph: {GraphPrototype.ID} Node: {Node.Name} Edge: {edge.Target}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> HandleStep(InteractUsingEventArgs eventArgs, ConstructionGraphEdge? edge = null, ConstructionGraphStep? step = null, bool nested = false)
|
||||||
|
{
|
||||||
|
edge ??= Edge;
|
||||||
|
step ??= edge?.Steps[EdgeStep];
|
||||||
|
|
||||||
|
if (edge == null || step == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var condition in edge.Conditions)
|
||||||
|
{
|
||||||
|
if (!await condition.Condition(Owner)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handled = false;
|
||||||
|
|
||||||
|
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||||
|
|
||||||
|
var doAfterArgs = new DoAfterEventArgs(eventArgs.User, step.DoAfter, default, eventArgs.Target)
|
||||||
|
{
|
||||||
|
BreakOnDamage = false,
|
||||||
|
BreakOnStun = true,
|
||||||
|
BreakOnTargetMove = true,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
NeedHand = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case ToolConstructionGraphStep toolStep:
|
||||||
|
// Gotta take welder fuel into consideration.
|
||||||
|
if (toolStep.Tool == ToolQuality.Welding)
|
||||||
|
{
|
||||||
|
if (eventArgs.Using.TryGetComponent(out WelderComponent? welder) &&
|
||||||
|
await welder.UseTool(eventArgs.User, Owner, step.DoAfter, toolStep.Tool, toolStep.Fuel))
|
||||||
|
{
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventArgs.Using.TryGetComponent(out ToolComponent? tool) &&
|
||||||
|
await tool.UseTool(eventArgs.User, Owner, step.DoAfter, toolStep.Tool))
|
||||||
|
{
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// To prevent too much code duplication.
|
||||||
|
case EntityInsertConstructionGraphStep insertStep:
|
||||||
|
var valid = false;
|
||||||
|
var entityUsing = eventArgs.Using;
|
||||||
|
|
||||||
|
switch (insertStep)
|
||||||
|
{
|
||||||
|
case PrototypeConstructionGraphStep prototypeStep:
|
||||||
|
if (prototypeStep.EntityValid(eventArgs.Using)
|
||||||
|
&& await doAfterSystem.DoAfter(doAfterArgs) == DoAfterStatus.Finished)
|
||||||
|
{
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ComponentConstructionGraphStep componentStep:
|
||||||
|
if (componentStep.EntityValid(eventArgs.Using)
|
||||||
|
&& await doAfterSystem.DoAfter(doAfterArgs) == DoAfterStatus.Finished)
|
||||||
|
{
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MaterialConstructionGraphStep materialStep:
|
||||||
|
if (materialStep.EntityValid(eventArgs.Using, out var sharedStack)
|
||||||
|
&& await doAfterSystem.DoAfter(doAfterArgs) == DoAfterStatus.Finished)
|
||||||
|
{
|
||||||
|
var stack = (StackComponent) sharedStack;
|
||||||
|
valid = stack.Split(materialStep.Amount, eventArgs.User.Transform.Coordinates, out entityUsing);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid || entityUsing == null) break;
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(insertStep.Store))
|
||||||
|
{
|
||||||
|
entityUsing.Delete();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_containers.Add(insertStep.Store);
|
||||||
|
var container = ContainerManagerComponent.Ensure<Container>(insertStep.Store, Owner);
|
||||||
|
container.Insert(entityUsing);
|
||||||
|
}
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NestedConstructionGraphStep nestedStep:
|
||||||
|
if(_edgeNestedStepProgress == null)
|
||||||
|
_edgeNestedStepProgress = new List<List<ConstructionGraphStep>>(nestedStep.Steps);
|
||||||
|
|
||||||
|
foreach (var list in _edgeNestedStepProgress.ToArray())
|
||||||
|
{
|
||||||
|
if (list.Count == 0)
|
||||||
|
{
|
||||||
|
_edgeNestedStepProgress.Remove(list);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await HandleStep(eventArgs, edge, list[0], true)) continue;
|
||||||
|
|
||||||
|
list.RemoveAt(0);
|
||||||
|
|
||||||
|
// We check again...
|
||||||
|
if (list.Count == 0)
|
||||||
|
_edgeNestedStepProgress.Remove(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_edgeNestedStepProgress.Count == 0)
|
||||||
|
handled = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled)
|
||||||
|
{
|
||||||
|
foreach (var completed in step.Completed)
|
||||||
|
{
|
||||||
|
await completed.PerformAction(Owner, eventArgs.User);
|
||||||
|
|
||||||
|
if (Owner.Deleted)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nested && handled) return true;
|
||||||
|
if (!handled) return false;
|
||||||
|
|
||||||
|
EdgeStep++;
|
||||||
|
|
||||||
|
if (edge.Steps.Count == EdgeStep)
|
||||||
|
{
|
||||||
|
await HandleCompletion(edge, eventArgs.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTarget();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> HandleCompletion(ConstructionGraphEdge edge, IEntity user)
|
||||||
|
{
|
||||||
|
if (edge.Steps.Count != EdgeStep)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Edge = edge;
|
||||||
|
|
||||||
|
UpdateTarget();
|
||||||
|
|
||||||
|
TargetNextEdge = null;
|
||||||
|
Edge = null;
|
||||||
|
Node = GraphPrototype.Nodes[edge.Target];
|
||||||
|
|
||||||
|
// Perform node actions!
|
||||||
|
foreach (var action in Node.Actions)
|
||||||
|
{
|
||||||
|
await action.PerformAction(Owner, user);
|
||||||
|
|
||||||
|
if (Owner.Deleted)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Target == Node)
|
||||||
|
ClearTarget();
|
||||||
|
|
||||||
|
foreach (var completed in edge.Completed)
|
||||||
|
{
|
||||||
|
await completed.PerformAction(Owner, user);
|
||||||
|
if (Owner.Deleted) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await HandleEntityChange(Node, user);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> HandleEdge(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (Edge == null || EdgeStep >= Edge.Steps.Count) return false;
|
||||||
|
|
||||||
|
return await HandleStep(eventArgs, Edge, Edge.Steps[EdgeStep]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> HandleEntityChange(ConstructionGraphNode node, IEntity? user = null)
|
||||||
|
{
|
||||||
|
if (node.Entity == Owner.Prototype?.ID || string.IsNullOrEmpty(node.Entity)) return false;
|
||||||
|
|
||||||
|
var entity = _entityManager.SpawnEntity(node.Entity, Owner.Transform.Coordinates);
|
||||||
|
|
||||||
|
entity.Transform.LocalRotation = Owner.Transform.LocalRotation;
|
||||||
|
|
||||||
|
if (entity.TryGetComponent(out ConstructionComponent? construction))
|
||||||
|
{
|
||||||
|
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}!");
|
||||||
|
|
||||||
|
construction.Node = node;
|
||||||
|
construction.Target = Target;
|
||||||
|
construction._containers = new HashSet<string>(_containers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ContainerManagerComponent? containerComp))
|
||||||
|
{
|
||||||
|
foreach (var container in _containers)
|
||||||
|
{
|
||||||
|
var otherContainer = ContainerManagerComponent.Ensure<Container>(container, entity);
|
||||||
|
var ourContainer = containerComp.GetContainer(container);
|
||||||
|
|
||||||
|
foreach (var ent in ourContainer.ContainedEntities.ToArray())
|
||||||
|
{
|
||||||
|
ourContainer.ForceRemove(ent);
|
||||||
|
otherContainer.Insert(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out CollidableComponent? collidable) &&
|
||||||
|
entity.TryGetComponent(out CollidableComponent? otherCollidable))
|
||||||
|
{
|
||||||
|
otherCollidable.Anchored = collidable.Anchored;
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.Delete();
|
||||||
|
|
||||||
|
foreach (var action in node.Actions)
|
||||||
|
{
|
||||||
|
await action.PerformAction(entity, user);
|
||||||
|
|
||||||
|
if (entity.Deleted)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddContainer(string id)
|
||||||
|
{
|
||||||
|
return _containers.Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_graphIdentifier))
|
||||||
|
{
|
||||||
|
Logger.Error($"Prototype {Owner.Prototype?.ID}'s construction component didn't have a graph identifier!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_prototypeManager.TryIndex(_graphIdentifier, out ConstructionGraphPrototype graph))
|
||||||
|
{
|
||||||
|
GraphPrototype = graph;
|
||||||
|
|
||||||
|
if (GraphPrototype.Nodes.TryGetValue(_startingNodeIdentifier, out var node))
|
||||||
|
{
|
||||||
|
Node = node;
|
||||||
|
|
||||||
|
foreach (var action in Node.Actions)
|
||||||
|
{
|
||||||
|
action.PerformAction(Owner, null);
|
||||||
|
|
||||||
|
if (Owner.Deleted)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error($"Couldn't find node {_startingNodeIdentifier} in graph {_graphIdentifier} in construction component!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Error($"Couldn't find prototype {_graphIdentifier} in construction component!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ChangeNode(string node)
|
||||||
|
{
|
||||||
|
var graphNode = GraphPrototype.Nodes[node];
|
||||||
|
|
||||||
|
if (_handling && _handlingTask?.Task != null)
|
||||||
|
await _handlingTask.Task;
|
||||||
|
|
||||||
|
Edge = null;
|
||||||
|
Node = graphNode;
|
||||||
|
|
||||||
|
foreach (var action in Node.Actions)
|
||||||
|
{
|
||||||
|
await action.PerformAction(Owner, null);
|
||||||
|
if (Owner.Deleted)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await HandleEntityChange(graphNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||||
{
|
{
|
||||||
EntitySystem.Get<SharedConstructionSystem>().DoExamine(message, Prototype, Stage, inDetailsRange);
|
if(Target != null)
|
||||||
|
message.AddMarkup(Loc.GetString("To create {0}...\n", Target.Name));
|
||||||
|
|
||||||
|
if (Edge == null && TargetNextEdge != null)
|
||||||
|
{
|
||||||
|
foreach (var condition in TargetNextEdge.Conditions)
|
||||||
|
{
|
||||||
|
condition.DoExamine(Owner, message, inDetailsRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetNextEdge.Steps[0].DoExamine(message, inDetailsRange);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Edge != null)
|
||||||
|
{
|
||||||
|
foreach (var condition in Edge.Conditions)
|
||||||
|
{
|
||||||
|
condition.DoExamine(Owner, message, inDetailsRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_edgeNestedStepProgress == null)
|
||||||
|
{
|
||||||
|
if(EdgeStep < Edge?.Steps.Count)
|
||||||
|
Edge.Steps[EdgeStep].DoExamine(message, inDetailsRange);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var list in _edgeNestedStepProgress)
|
||||||
|
{
|
||||||
|
if(list.Count == 0) continue;
|
||||||
|
|
||||||
|
list[0].DoExamine(message, inDetailsRange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Construction;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Damage
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IDamageableComponent))]
|
||||||
|
public class BreakableConstructionComponent : RuinableComponent
|
||||||
|
{
|
||||||
|
private ActSystem _actSystem = default!;
|
||||||
|
|
||||||
|
public override string Name => "BreakableConstruction";
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(this, x => x.Node, "node", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_actSystem = EntitySystem.Get<ActSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Node { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
protected override async void DestructionBehavior()
|
||||||
|
{
|
||||||
|
if (Owner.Deleted || !Owner.TryGetComponent(out ConstructionComponent? construction) || string.IsNullOrEmpty(Node)) return;
|
||||||
|
|
||||||
|
_actSystem.HandleBreakage(Owner);
|
||||||
|
|
||||||
|
await construction.ChangeNode(Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Entity spawned upon destruction.
|
/// Entity spawned upon destruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SpawnOnDestroy { get; set; }
|
public string SpawnOnDestroy { get; private set; }
|
||||||
|
|
||||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
|||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnondestroy", string.Empty);
|
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnOnDestroy", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ namespace Content.Server.GameObjects.Components.GUI
|
|||||||
base.OnRemove();
|
base.OnRemove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IEntity> GetAllHeldItems()
|
||||||
|
{
|
||||||
|
foreach (var (_, container) in _slotContainers)
|
||||||
|
{
|
||||||
|
foreach (var entity in container.ContainedEntities)
|
||||||
|
{
|
||||||
|
yield return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper to get container name for specified slot on this component
|
/// Helper to get container name for specified slot on this component
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Content.Server.GameObjects.Components.GUI;
|
|||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.MachineLinking;
|
using Content.Server.GameObjects.Components.MachineLinking;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Shared.Damage;
|
using Content.Shared.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
@@ -56,21 +55,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
// TODO CONSTRUCTION make this use a construction graph
|
||||||
{
|
|
||||||
base.HandleMessage(message, component);
|
|
||||||
|
|
||||||
switch (message)
|
|
||||||
{
|
|
||||||
case BeginDeconstructCompMsg msg:
|
|
||||||
if (!msg.BlockDeconstruct && !(_lightBulbContainer.ContainedEntity is null))
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(msg.User, Loc.GetString("Remove the bulb."));
|
|
||||||
msg.BlockDeconstruct = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -73,14 +73,7 @@ namespace Content.Server.GameObjects.Components.Recycling
|
|||||||
{
|
{
|
||||||
prototype = null;
|
prototype = null;
|
||||||
|
|
||||||
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
|
// TODO CONSTRUCTION fix this
|
||||||
var entityId = entity.MetaData.EntityPrototype?.ID;
|
|
||||||
|
|
||||||
if (entityId == null ||
|
|
||||||
!constructionSystem.CraftRecipes.TryGetValue(entityId, out prototype))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Powered;
|
return Powered;
|
||||||
}
|
}
|
||||||
@@ -105,17 +98,7 @@ namespace Content.Server.GameObjects.Components.Recycling
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
|
// TODO CONSTRUCTION fix this
|
||||||
var recyclerPosition = Owner.Transform.MapPosition;
|
|
||||||
foreach (var stage in prototype.Stages)
|
|
||||||
{
|
|
||||||
if (!(stage.Forward is ConstructionStepMaterial step))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructionSystem.SpawnIngredient(recyclerPosition, step);
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.Delete();
|
entity.Delete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Shared.GameObjects.Components;
|
using Content.Shared.GameObjects.Components;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Timers;
|
using Robust.Shared.Timers;
|
||||||
@@ -16,8 +20,11 @@ namespace Content.Server.GameObjects.Components.Stack
|
|||||||
|
|
||||||
// TODO: Naming and presentation and such could use some improvement.
|
// TODO: Naming and presentation and such could use some improvement.
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedStackComponent))]
|
||||||
public class StackComponent : SharedStackComponent, IInteractUsing, IExamine
|
public class StackComponent : SharedStackComponent, IInteractUsing, IExamine
|
||||||
{
|
{
|
||||||
|
[Dependency] private IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
private bool _throwIndividually = false;
|
private bool _throwIndividually = false;
|
||||||
|
|
||||||
public override int Count
|
public override int Count
|
||||||
@@ -57,6 +64,33 @@ namespace Content.Server.GameObjects.Components.Stack
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to split this stack in two.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount">amount the new stack will have</param>
|
||||||
|
/// <param name="spawnPosition">the position the new stack will spawn at</param>
|
||||||
|
/// <param name="stack">the new stack</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool Split(int amount, EntityCoordinates spawnPosition, [NotNullWhen(true)] out IEntity? stack)
|
||||||
|
{
|
||||||
|
if (Count >= amount)
|
||||||
|
{
|
||||||
|
Count -= amount;
|
||||||
|
|
||||||
|
stack = _entityManager.SpawnEntity(Owner.Prototype?.ID, spawnPosition);
|
||||||
|
|
||||||
|
if (stack.TryGetComponent(out StackComponent? stackComp))
|
||||||
|
{
|
||||||
|
stackComp.Count = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Using.TryGetComponent<StackComponent>(out var stack))
|
if (eventArgs.Using.TryGetComponent<StackComponent>(out var stack))
|
||||||
|
|||||||
11
Content.Server/GameObjects/Components/WindowComponent.cs
Normal file
11
Content.Server/GameObjects/Components/WindowComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(SharedWindowComponent))]
|
||||||
|
public class WindowComponent : SharedWindowComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Content.Server.GameObjects.Components.Atmos;
|
using Content.Server.GameObjects.Components.Atmos;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
@@ -286,7 +287,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
|
|||||||
gridAtmosComponents[gridId] = gam;
|
gridAtmosComponents[gridId] = gam;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var invalid in indices)
|
foreach (var invalid in indices.ToArray())
|
||||||
{
|
{
|
||||||
var chunk = GetOrCreateChunk(gridId, invalid);
|
var chunk = GetOrCreateChunk(gridId, invalid);
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameObjects.Components.Construction;
|
using Content.Server.GameObjects.Components.Construction;
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Interactable;
|
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Stack;
|
using Content.Server.GameObjects.Components.Stack;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||||
using Content.Shared.Construction;
|
using Content.Shared.Construction;
|
||||||
using Content.Shared.GameObjects.Components;
|
using Content.Shared.GameObjects.Components;
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces;
|
||||||
using Content.Shared.Utility;
|
using Content.Shared.Utility;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Server.Interfaces.Player;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
using Robust.Shared.Interfaces.Random;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Timers;
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -33,548 +34,425 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
internal class ConstructionSystem : SharedConstructionSystem
|
internal class ConstructionSystem : SharedConstructionSystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||||
|
|
||||||
private readonly Dictionary<string, ConstructionPrototype> _craftRecipes = new Dictionary<string, ConstructionPrototype>();
|
private readonly Dictionary<ICommonSession, HashSet<int>> _beingBuilt = new Dictionary<ICommonSession, HashSet<int>>();
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, ConstructionPrototype> CraftRecipes => _craftRecipes;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
|
||||||
{
|
|
||||||
_craftRecipes.Add(prototype.Result, prototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
|
SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
|
||||||
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
|
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
|
||||||
|
|
||||||
SubscribeLocalEvent<AfterInteractMessage>(HandleToolInteraction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleStartStructureConstruction(TryStartStructureConstructionMessage msg, EntitySessionEventArgs args)
|
private IEnumerable<IEntity> EnumerateNearby(IEntity user)
|
||||||
{
|
{
|
||||||
var placingEnt = args.SenderSession.AttachedEntity;
|
if (user.TryGetComponent(out HandsComponent? hands))
|
||||||
var result = TryStartStructureConstruction(placingEnt, msg.Location, msg.PrototypeName, msg.Angle);
|
|
||||||
if (!result) return;
|
|
||||||
var responseMsg = new AckStructureConstructionMessage(msg.Ack);
|
|
||||||
var channel = ((IPlayerSession) args.SenderSession).ConnectedClient;
|
|
||||||
RaiseNetworkEvent(responseMsg, channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleStartItemConstruction(TryStartItemConstructionMessage msg, EntitySessionEventArgs args)
|
|
||||||
{
|
{
|
||||||
var placingEnt = args.SenderSession.AttachedEntity;
|
foreach (var itemComponent in hands?.GetAllHeldItems()!)
|
||||||
TryStartItemConstruction(placingEnt, msg.PrototypeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void HandleToolInteraction(AfterInteractMessage msg)
|
|
||||||
{
|
{
|
||||||
if(msg.Handled)
|
if (itemComponent.Owner.TryGetComponent(out ServerStorageComponent? storage))
|
||||||
return;
|
|
||||||
|
|
||||||
// You can only construct/deconstruct things within reach
|
|
||||||
if(!msg.CanReach)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var targetEnt = msg.Attacked;
|
|
||||||
var handEnt = msg.ItemInHand;
|
|
||||||
|
|
||||||
// A tool has to interact with an entity.
|
|
||||||
if(targetEnt is null || handEnt is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!handEnt.InRangeUnobstructed(targetEnt, ignoreInsideBlocker: true))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Cannot deconstruct an entity with no prototype.
|
|
||||||
var targetPrototype = targetEnt.MetaData.EntityPrototype;
|
|
||||||
if (targetPrototype is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// the target entity is in the process of being constructed/deconstructed
|
|
||||||
if (msg.Attacked.TryGetComponent<ConstructionComponent>(out var constructComp))
|
|
||||||
{
|
{
|
||||||
var result = await TryConstructEntity(constructComp, handEnt, msg.User);
|
foreach (var storedEntity in storage.StoredEntities!)
|
||||||
|
|
||||||
// TryConstructEntity may delete the existing entity
|
|
||||||
|
|
||||||
msg.Handled = result;
|
|
||||||
}
|
|
||||||
else // try to start the deconstruction process
|
|
||||||
{
|
{
|
||||||
// A tool was not used on the entity.
|
yield return storedEntity;
|
||||||
if (!handEnt.TryGetComponent<IToolComponent>(out var toolComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// no known recipe for entity
|
|
||||||
if (!_craftRecipes.TryGetValue(targetPrototype.ID, out var prototype))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// there is a recipe, but it can't be deconstructed.
|
|
||||||
var lastStep = prototype.Stages[^1].Backward;
|
|
||||||
if (!(lastStep is ConstructionStepTool))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// wrong tool
|
|
||||||
var caps = ((ConstructionStepTool) lastStep).ToolQuality;
|
|
||||||
if ((toolComp.Qualities & caps) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// ask around and see if the deconstruction prerequisites are satisfied
|
|
||||||
// (remove bulbs, approved access, open panels, etc)
|
|
||||||
var deconCompMsg = new BeginDeconstructCompMsg(msg.User);
|
|
||||||
targetEnt.SendMessage(null, deconCompMsg);
|
|
||||||
if(deconCompMsg.BlockDeconstruct)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var deconEntMsg = new BeginDeconstructEntityMsg(msg.User, handEnt, targetEnt);
|
|
||||||
RaiseLocalEvent(deconEntMsg);
|
|
||||||
if(deconEntMsg.BlockDeconstruct)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// --- GOOD TO GO ---
|
|
||||||
msg.Handled = true;
|
|
||||||
|
|
||||||
// pop off the material and switch to frame
|
|
||||||
var targetEntPos = targetEnt.Transform.MapPosition;
|
|
||||||
if (prototype.Stages.Count <= 2) // there are no intermediate stages
|
|
||||||
{
|
|
||||||
targetEnt.Delete();
|
|
||||||
|
|
||||||
SpawnIngredient(targetEntPos, prototype.Stages[(prototype.Stages.Count - 2)].Forward as ConstructionStepMaterial);
|
|
||||||
}
|
|
||||||
else // replace ent with intermediate
|
|
||||||
{
|
|
||||||
// Spawn frame
|
|
||||||
var frame = SpawnCopyTransform("structureconstructionframe", targetEnt.Transform);
|
|
||||||
var construction = frame.GetComponent<ConstructionComponent>();
|
|
||||||
SetupComponent(construction, prototype);
|
|
||||||
construction.Stage = prototype.Stages.Count - 2;
|
|
||||||
SetupDeconIntermediateSprite(construction, prototype);
|
|
||||||
frame.Transform.LocalRotation = targetEnt.Transform.LocalRotation;
|
|
||||||
|
|
||||||
if (targetEnt.Prototype.Components.TryGetValue("Item", out var itemProtoComp))
|
|
||||||
{
|
|
||||||
if(frame.HasComponent<ItemComponent>())
|
|
||||||
frame.RemoveComponent<ItemComponent>();
|
|
||||||
|
|
||||||
var itemComp = frame.AddComponent<ItemComponent>();
|
|
||||||
|
|
||||||
var serializer = YamlObjectSerializer.NewReader(itemProtoComp);
|
|
||||||
itemComp.ExposeData(serializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplaceInContainerOrGround(targetEnt, frame);
|
|
||||||
|
|
||||||
// remove target
|
|
||||||
targetEnt.Delete();
|
|
||||||
|
|
||||||
// spawn material
|
|
||||||
SpawnIngredient(targetEntPos, prototype.Stages[(prototype.Stages.Count-2)].Forward as ConstructionStepMaterial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEntity SpawnCopyTransform(string prototypeId, ITransformComponent toReplace)
|
|
||||||
{
|
|
||||||
var frame = EntityManager.SpawnEntity(prototypeId, toReplace.MapPosition);
|
|
||||||
frame.Transform.WorldRotation = toReplace.WorldRotation;
|
|
||||||
frame.Transform.ParentUid = toReplace.ParentUid;
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetupDeconIntermediateSprite(ConstructionComponent constructionComponent, ConstructionPrototype prototype)
|
|
||||||
{
|
|
||||||
if(!constructionComponent.Owner.TryGetComponent<SpriteComponent>(out var spriteComp))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (var i = prototype.Stages.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
if (prototype.Stages[i].Icon != null)
|
|
||||||
{
|
|
||||||
spriteComp.AddLayerWithSprite(prototype.Stages[1].Icon);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spriteComp.AddLayerWithSprite(prototype.Icon);
|
yield return itemComponent.Owner;
|
||||||
}
|
|
||||||
|
|
||||||
public void SpawnIngredient(MapCoordinates position, ConstructionStepMaterial lastStep)
|
|
||||||
{
|
|
||||||
if(lastStep is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var material = lastStep.Material;
|
|
||||||
var quantity = lastStep.Amount;
|
|
||||||
|
|
||||||
var matEnt = EntityManager.SpawnEntity(MaterialPrototypes[material], position);
|
|
||||||
if (matEnt.TryGetComponent<StackComponent>(out var stackComp))
|
|
||||||
{
|
|
||||||
stackComp.Count = quantity;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
quantity--; // already spawned one above
|
|
||||||
while (quantity > 0)
|
|
||||||
{
|
|
||||||
EntityManager.SpawnEntity(MaterialPrototypes[material], position);
|
|
||||||
quantity--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Dictionary<ConstructionStepMaterial.MaterialType, string> MaterialPrototypes =
|
|
||||||
new Dictionary<ConstructionStepMaterial.MaterialType, string>
|
|
||||||
{
|
|
||||||
{ ConstructionStepMaterial.MaterialType.Cable, "CableStack1" },
|
|
||||||
{ ConstructionStepMaterial.MaterialType.Gold, "GoldStack1" },
|
|
||||||
{ ConstructionStepMaterial.MaterialType.Metal, "SteelSheet1" },
|
|
||||||
{ ConstructionStepMaterial.MaterialType.Glass, "GlassSheet1" }
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool TryStartStructureConstruction(IEntity placingEnt, EntityCoordinates loc, string prototypeName, Angle angle)
|
|
||||||
{
|
|
||||||
var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);
|
|
||||||
|
|
||||||
if (!placingEnt.InRangeUnobstructed(loc, ignoreInsideBlocker: prototype.CanBuildInImpassable, popup: true))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prototype.Stages.Count < 2)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Prototype '{prototypeName}' does not have enough stages.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var stage0 = prototype.Stages[0];
|
|
||||||
if (!(stage0.Forward is ConstructionStepMaterial matStep))
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find the stack with the material in the user's hand.
|
|
||||||
if(!placingEnt.TryGetComponent<HandsComponent>(out var hands))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
var activeHand = hands.GetActiveHand?.Owner;
|
|
||||||
if (activeHand == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeHand.TryGetComponent(out StackComponent stack) || !MaterialStackValidFor(matStep, stack))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stack.Use(matStep.Amount))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK WE'RE GOOD CONSTRUCTION STARTED.
|
|
||||||
Get<AudioSystem>().PlayAtCoords("/Audio/Items/deconstruct.ogg", loc);
|
|
||||||
if (prototype.Stages.Count == 2)
|
|
||||||
{
|
|
||||||
// Exactly 2 stages, so don't make an intermediate frame.
|
|
||||||
var ent = EntityManager.SpawnEntity(prototype.Result, loc);
|
|
||||||
ent.Transform.LocalRotation = angle;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var frame = EntityManager.SpawnEntity("structureconstructionframe", loc);
|
|
||||||
var construction = frame.GetComponent<ConstructionComponent>();
|
|
||||||
SetupComponent(construction, prototype);
|
|
||||||
frame.Transform.LocalRotation = angle;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryStartItemConstruction(IEntity placingEnt, string prototypeName)
|
|
||||||
{
|
|
||||||
if (!ActionBlockerSystem.CanInteract(placingEnt)) return;
|
|
||||||
|
|
||||||
var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);
|
|
||||||
|
|
||||||
if (prototype.Stages.Count < 2)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Prototype '{prototypeName}' does not have enough stages.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var stage0 = prototype.Stages[0];
|
|
||||||
if (!(stage0.Forward is ConstructionStepMaterial matStep))
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find the stack with the material in the user's hand.
|
|
||||||
if (!placingEnt.TryGetComponent(out HandsComponent hands)) return;
|
|
||||||
|
|
||||||
var activeHand = hands.GetActiveHand?.Owner;
|
|
||||||
if (activeHand == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeHand.TryGetComponent(out StackComponent stack) || !MaterialStackValidFor(matStep, stack))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stack.Use(matStep.Amount))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK WE'RE GOOD CONSTRUCTION STARTED.
|
|
||||||
Get<AudioSystem>().PlayFromEntity("/Audio/Items/deconstruct.ogg", placingEnt);
|
|
||||||
if (prototype.Stages.Count == 2)
|
|
||||||
{
|
|
||||||
// Exactly 2 stages, so don't make an intermediate frame.
|
|
||||||
var ent = SpawnCopyTransform(prototype.Result, placingEnt.Transform);
|
|
||||||
hands.PutInHandOrDrop(ent.GetComponent<ItemComponent>());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var frame = SpawnCopyTransform("structureconstructionframe", placingEnt.Transform);
|
|
||||||
var construction = frame.GetComponent<ConstructionComponent>();
|
|
||||||
SetupComponent(construction, prototype);
|
|
||||||
|
|
||||||
var finalPrototype = _prototypeManager.Index<EntityPrototype>(prototype.Result);
|
|
||||||
if (finalPrototype.Components.TryGetValue("Item", out var itemProtoComp))
|
|
||||||
{
|
|
||||||
if(frame.HasComponent<ItemComponent>())
|
|
||||||
frame.RemoveComponent<ItemComponent>();
|
|
||||||
|
|
||||||
var itemComp = frame.AddComponent<ItemComponent>();
|
|
||||||
|
|
||||||
var serializer = YamlObjectSerializer.NewReader(itemProtoComp);
|
|
||||||
itemComp.ExposeData(serializer);
|
|
||||||
|
|
||||||
hands.PutInHandOrDrop(itemComp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> TryConstructEntity(ConstructionComponent constructionComponent, IEntity handTool, IEntity user)
|
|
||||||
{
|
|
||||||
var constructEntity = constructionComponent.Owner;
|
|
||||||
var spriteComponent = constructEntity.GetComponent<SpriteComponent>();
|
|
||||||
var transformComponent = constructEntity.GetComponent<ITransformComponent>();
|
|
||||||
|
|
||||||
// default interaction check for AttackBy allows inside blockers, so we will check if its blocked if
|
|
||||||
// we're not allowed to build on impassable stuff
|
|
||||||
var constructPrototype = constructionComponent.Prototype;
|
|
||||||
if (constructPrototype.CanBuildInImpassable == false)
|
|
||||||
{
|
|
||||||
if (!user.InRangeUnobstructed(constructEntity, popup: true))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stage = constructPrototype.Stages[constructionComponent.Stage];
|
|
||||||
|
|
||||||
if (await TryProcessStep(constructEntity, stage.Forward, handTool, user, transformComponent.Coordinates))
|
|
||||||
{
|
|
||||||
constructionComponent.Stage++;
|
|
||||||
if (constructionComponent.Stage == constructPrototype.Stages.Count - 1)
|
|
||||||
{
|
|
||||||
// Oh boy we get to finish construction!
|
|
||||||
var ent = SpawnCopyTransform(constructPrototype.Result, transformComponent);
|
|
||||||
ent.Transform.LocalRotation = transformComponent.LocalRotation;
|
|
||||||
|
|
||||||
ReplaceInContainerOrGround(constructEntity, ent);
|
|
||||||
|
|
||||||
constructEntity.Delete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
stage = constructPrototype.Stages[constructionComponent.Stage];
|
|
||||||
if (stage.Icon != null)
|
|
||||||
{
|
|
||||||
spriteComponent.LayerSetSprite(0, stage.Icon);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (await TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.Coordinates))
|
if (user!.TryGetComponent(out InventoryComponent? inventory))
|
||||||
{
|
{
|
||||||
constructionComponent.Stage--;
|
foreach (var held in inventory.GetAllHeldItems())
|
||||||
stage = constructPrototype.Stages[constructionComponent.Stage];
|
|
||||||
|
|
||||||
// If forward needed a material, drop it
|
|
||||||
SpawnIngredient(constructEntity.Transform.MapPosition, stage.Forward as ConstructionStepMaterial);
|
|
||||||
|
|
||||||
if (constructionComponent.Stage == 0)
|
|
||||||
{
|
{
|
||||||
// Deconstruction complete.
|
if (held.TryGetComponent(out ServerStorageComponent? storage))
|
||||||
constructEntity.Delete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stage.Icon != null)
|
|
||||||
{
|
{
|
||||||
spriteComponent.LayerSetSprite(0, stage.Icon);
|
foreach (var storedEntity in storage.StoredEntities!)
|
||||||
|
{
|
||||||
|
yield return storedEntity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
yield return held;
|
||||||
}
|
|
||||||
|
|
||||||
private static void ReplaceInContainerOrGround(IEntity oldEntity, IEntity newEntity)
|
|
||||||
{
|
|
||||||
var parentEntity = oldEntity.Transform.Parent?.Owner;
|
|
||||||
if (!(parentEntity is null) && parentEntity.TryGetComponent<IContainerManager>(out var containerMan))
|
|
||||||
{
|
|
||||||
if (containerMan.TryGetContainer(oldEntity, out var container))
|
|
||||||
{
|
|
||||||
container.ForceRemove(oldEntity);
|
|
||||||
container.Insert(newEntity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> TryProcessStep(IEntity constructEntity, ConstructionStep step, IEntity slapped, IEntity user, EntityCoordinates gridCoords)
|
foreach (var near in _entityManager.GetEntitiesInRange(user!, 2f, true))
|
||||||
{
|
{
|
||||||
if (step == null)
|
yield return near;
|
||||||
{
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var sound = EntitySystemManager.GetEntitySystem<AudioSystem>();
|
private async Task<IEntity?> Construct(IEntity user, string materialContainer, ConstructionGraphPrototype graph, ConstructionGraphEdge edge, ConstructionGraphNode targetNode)
|
||||||
|
{
|
||||||
|
// We need a place to hold our construction items!
|
||||||
|
var container = ContainerManagerComponent.Ensure<Container>(materialContainer, user, out var existed);
|
||||||
|
|
||||||
|
if (existed)
|
||||||
|
{
|
||||||
|
user.PopupMessageCursor(Loc.GetString("You can't start another construction now!"));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var containers = new Dictionary<string, Container>();
|
||||||
|
|
||||||
|
var doAfterTime = 0f;
|
||||||
|
|
||||||
|
// HOLY SHIT THIS IS SOME HACKY CODE.
|
||||||
|
// But I'd rather do this shit than risk having collisions with other containers.
|
||||||
|
Container GetContainer(string name)
|
||||||
|
{
|
||||||
|
if (containers!.ContainsKey(name))
|
||||||
|
return containers[name];
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var random = _robustRandom.Next();
|
||||||
|
var c = ContainerManagerComponent.Ensure<Container>(random.ToString(), user!, out var existed);
|
||||||
|
|
||||||
|
if (existed) continue;
|
||||||
|
|
||||||
|
containers[name] = c;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FailCleanup()
|
||||||
|
{
|
||||||
|
foreach (var entity in container!.ContainedEntities.ToArray())
|
||||||
|
{
|
||||||
|
container.Remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cont in containers!.Values)
|
||||||
|
{
|
||||||
|
foreach (var entity in cont.ContainedEntities.ToArray())
|
||||||
|
{
|
||||||
|
cont.Remove(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't do this, items are invisible for some fucking reason. Nice.
|
||||||
|
Timer.Spawn(1, ShutdownContainers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownContainers()
|
||||||
|
{
|
||||||
|
container!.Shutdown();
|
||||||
|
foreach (var c in containers!.Values.ToArray())
|
||||||
|
{
|
||||||
|
c.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var failed = false;
|
||||||
|
|
||||||
|
var steps = new List<ConstructionGraphStep>();
|
||||||
|
|
||||||
|
foreach (var step in edge.Steps)
|
||||||
|
{
|
||||||
|
doAfterTime += step.DoAfter;
|
||||||
|
|
||||||
|
var handled = false;
|
||||||
|
|
||||||
switch (step)
|
switch (step)
|
||||||
{
|
{
|
||||||
case ConstructionStepMaterial matStep:
|
case MaterialConstructionGraphStep materialStep:
|
||||||
if (!slapped.TryGetComponent(out StackComponent stack)
|
foreach (var entity in EnumerateNearby(user))
|
||||||
|| !MaterialStackValidFor(matStep, stack)
|
|
||||||
|| !stack.Use(matStep.Amount))
|
|
||||||
{
|
{
|
||||||
return false;
|
if (!materialStep.EntityValid(entity, out var sharedStack))
|
||||||
}
|
continue;
|
||||||
if (matStep.Material == ConstructionStepMaterial.MaterialType.Cable)
|
|
||||||
sound.PlayAtCoords("/Audio/Items/zip.ogg", gridCoords);
|
|
||||||
else
|
|
||||||
sound.PlayAtCoords("/Audio/Items/deconstruct.ogg", gridCoords);
|
|
||||||
return true;
|
|
||||||
case ConstructionStepTool toolStep:
|
|
||||||
if (!slapped.TryGetComponent<ToolComponent>(out var tool))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Handle welder manually since tool steps specify fuel amount needed, for some reason.
|
var stack = (StackComponent) sharedStack;
|
||||||
if (toolStep.ToolQuality.HasFlag(ToolQuality.Welding))
|
|
||||||
return slapped.TryGetComponent<WelderComponent>(out var welder)
|
|
||||||
&& await welder.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality, toolStep.Amount);
|
|
||||||
|
|
||||||
return await tool.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality);
|
if (!stack.Split(materialStep.Amount, user.ToCoordinates(), out var newStack))
|
||||||
|
continue;
|
||||||
|
|
||||||
default:
|
if (string.IsNullOrEmpty(materialStep.Store))
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Really this should check the actual materials at play..
|
|
||||||
private static readonly Dictionary<StackType, ConstructionStepMaterial.MaterialType> StackTypeMap
|
|
||||||
= new Dictionary<StackType, ConstructionStepMaterial.MaterialType>
|
|
||||||
{
|
{
|
||||||
{ StackType.Cable, ConstructionStepMaterial.MaterialType.Cable },
|
if (!container.Insert(newStack))
|
||||||
{ StackType.Gold, ConstructionStepMaterial.MaterialType.Gold },
|
continue;
|
||||||
{ StackType.Glass, ConstructionStepMaterial.MaterialType.Glass },
|
}
|
||||||
{ StackType.Metal, ConstructionStepMaterial.MaterialType.Metal }
|
else if (!GetContainer(materialStep.Store).Insert(newStack))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ComponentConstructionGraphStep componentStep:
|
||||||
|
foreach (var entity in EnumerateNearby(user))
|
||||||
|
{
|
||||||
|
if (!componentStep.EntityValid(entity))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(componentStep.Store))
|
||||||
|
{
|
||||||
|
if (!container.Insert(entity))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!GetContainer(componentStep.Store).Insert(entity))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PrototypeConstructionGraphStep prototypeStep:
|
||||||
|
foreach (var entity in EnumerateNearby(user))
|
||||||
|
{
|
||||||
|
if (!prototypeStep.EntityValid(entity))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(prototypeStep.Store))
|
||||||
|
{
|
||||||
|
if (!container.Insert(entity))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!GetContainer(prototypeStep.Store).Insert(entity))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled == false)
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.Add(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
user.PopupMessageCursor(Loc.GetString("You don't have the materials to build that!"));
|
||||||
|
FailCleanup();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var doAfterSystem = Get<DoAfterSystem>();
|
||||||
|
|
||||||
|
var doAfterArgs = new DoAfterEventArgs(user, doAfterTime)
|
||||||
|
{
|
||||||
|
BreakOnDamage = true,
|
||||||
|
BreakOnStun = true,
|
||||||
|
BreakOnTargetMove = false,
|
||||||
|
BreakOnUserMove = true,
|
||||||
|
NeedHand = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static bool MaterialStackValidFor(ConstructionStepMaterial step, StackComponent stack)
|
if (await doAfterSystem.DoAfter(doAfterArgs) == DoAfterStatus.Cancelled)
|
||||||
{
|
{
|
||||||
return StackTypeMap.TryGetValue((StackType)stack.StackType, out var should) && should == step.Material;
|
FailCleanup();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupComponent(ConstructionComponent constructionComponent, ConstructionPrototype prototype)
|
var newEntity = _entityManager.SpawnEntity(graph.Nodes[edge.Target].Entity, user.Transform.Coordinates);
|
||||||
|
|
||||||
|
// Yes, this should throw if it's missing the component.
|
||||||
|
var construction = newEntity.GetComponent<ConstructionComponent>();
|
||||||
|
|
||||||
|
// We attempt to set the pathfinding target.
|
||||||
|
construction.Target = targetNode;
|
||||||
|
|
||||||
|
// We preserve the containers...
|
||||||
|
foreach (var (name, cont) in containers)
|
||||||
{
|
{
|
||||||
constructionComponent.Prototype = prototype;
|
var newCont = ContainerManagerComponent.Ensure<Container>(name, newEntity);
|
||||||
constructionComponent.Stage = 1;
|
|
||||||
var spriteComp = constructionComponent.Owner.GetComponent<SpriteComponent>();
|
foreach (var entity in cont.ContainedEntities.ToArray())
|
||||||
if(prototype.Stages[1].Icon != null)
|
|
||||||
{
|
{
|
||||||
spriteComp.AddLayerWithSprite(prototype.Stages[1].Icon);
|
cont.ForceRemove(entity);
|
||||||
|
newCont.Insert(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now get rid of all them.
|
||||||
|
ShutdownContainers();
|
||||||
|
|
||||||
|
// We have step completed steps!
|
||||||
|
foreach (var step in steps)
|
||||||
|
{
|
||||||
|
foreach (var completed in step.Completed)
|
||||||
|
{
|
||||||
|
await completed.PerformAction(newEntity, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And we also have edge completed effects!
|
||||||
|
foreach (var completed in edge.Completed)
|
||||||
|
{
|
||||||
|
await completed.PerformAction(newEntity, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleStartItemConstruction(TryStartItemConstructionMessage ev, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
var constructionPrototype = _prototypeManager.Index<ConstructionPrototype>(ev.PrototypeName);
|
||||||
|
var constructionGraph = _prototypeManager.Index<ConstructionGraphPrototype>(constructionPrototype.Graph);
|
||||||
|
var startNode = constructionGraph.Nodes[constructionPrototype.StartNode];
|
||||||
|
var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode];
|
||||||
|
var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name);
|
||||||
|
|
||||||
|
var user = args.SenderSession.AttachedEntity;
|
||||||
|
|
||||||
|
if (user == null || !ActionBlockerSystem.CanInteract(user)) return;
|
||||||
|
|
||||||
|
if (!user.TryGetComponent(out HandsComponent? hands)) return;
|
||||||
|
|
||||||
|
foreach (var condition in constructionPrototype.Conditions)
|
||||||
|
{
|
||||||
|
if (!condition.Condition(user, user.ToCoordinates(), Direction.South))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pathFind == null)
|
||||||
|
throw new InvalidDataException($"Can't find path from starting node to target node in construction! Recipe: {ev.PrototypeName}");
|
||||||
|
|
||||||
|
var edge = startNode.GetEdge(pathFind[0].Name);
|
||||||
|
|
||||||
|
if(edge == null)
|
||||||
|
throw new InvalidDataException($"Can't find edge from starting node to the next node in pathfinding! Recipe: {ev.PrototypeName}");
|
||||||
|
|
||||||
|
// No support for conditions here!
|
||||||
|
|
||||||
|
foreach (var step in edge.Steps)
|
||||||
|
{
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case ToolConstructionGraphStep _:
|
||||||
|
case NestedConstructionGraphStep _:
|
||||||
|
throw new InvalidDataException("Invalid first step for construction recipe!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = await Construct(user, "item_construction", constructionGraph, edge, targetNode);
|
||||||
|
|
||||||
|
if(item != null && item.TryGetComponent(out ItemComponent? itemComp))
|
||||||
|
hands.PutInHandOrDrop(itemComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HandleStartStructureConstruction(TryStartStructureConstructionMessage ev, EntitySessionEventArgs args)
|
||||||
|
{
|
||||||
|
var constructionPrototype = _prototypeManager.Index<ConstructionPrototype>(ev.PrototypeName);
|
||||||
|
var constructionGraph = _prototypeManager.Index<ConstructionGraphPrototype>(constructionPrototype.Graph);
|
||||||
|
var startNode = constructionGraph.Nodes[constructionPrototype.StartNode];
|
||||||
|
var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode];
|
||||||
|
var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name);
|
||||||
|
|
||||||
|
var user = args.SenderSession.AttachedEntity;
|
||||||
|
|
||||||
|
if (_beingBuilt.TryGetValue(args.SenderSession, out var set))
|
||||||
|
{
|
||||||
|
if (!set.Add(ev.Ack))
|
||||||
|
{
|
||||||
|
user.PopupMessageCursor(Loc.GetString("You are already building that!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spriteComp.AddLayerWithSprite(prototype.Icon);
|
var newSet = new HashSet<int> {ev.Ack};
|
||||||
|
_beingBuilt[args.SenderSession] = newSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
var frame = constructionComponent.Owner;
|
foreach (var condition in constructionPrototype.Conditions)
|
||||||
var finalPrototype = _prototypeManager.Index<EntityPrototype>(prototype.Result);
|
|
||||||
|
|
||||||
frame.Name = $"Unfinished {finalPrototype.Name}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A system message that is raised when an entity is trying to be deconstructed.
|
|
||||||
/// </summary>
|
|
||||||
public class BeginDeconstructEntityMsg : EntitySystemMessage
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
if (!condition.Condition(user, ev.Location, ev.Angle.GetCardinalDir()))
|
||||||
/// Entity that initiated the deconstruction.
|
|
||||||
/// </summary>
|
|
||||||
public IEntity User { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tool in the active hand of the user.
|
|
||||||
/// </summary>
|
|
||||||
public IEntity Hand { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Target entity that is trying to be deconstructed.
|
|
||||||
/// </summary>
|
|
||||||
public IEntity Target { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set this to true if you would like to block the deconstruction from happening.
|
|
||||||
/// </summary>
|
|
||||||
public bool BlockDeconstruct { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a new instance of <see cref="BeginDeconstructEntityMsg"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">Entity that initiated the deconstruction.</param>
|
|
||||||
/// <param name="hand">Tool in the active hand of the user.</param>
|
|
||||||
/// <param name="target">Target entity that is trying to be deconstructed.</param>
|
|
||||||
public BeginDeconstructEntityMsg(IEntity user, IEntity hand, IEntity target)
|
|
||||||
{
|
{
|
||||||
User = user;
|
Cleanup();
|
||||||
Hand = hand;
|
return;
|
||||||
Target = target;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
void Cleanup()
|
||||||
/// A component message that is raised when an entity is trying to be deconstructed.
|
|
||||||
/// </summary>
|
|
||||||
public class BeginDeconstructCompMsg : ComponentMessage
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
_beingBuilt[args.SenderSession].Remove(ev.Ack);
|
||||||
/// Entity that initiated the deconstruction.
|
}
|
||||||
/// </summary>
|
|
||||||
public IEntity User { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
if (user == null
|
||||||
/// Set this to true if you would like to block the deconstruction from happening.
|
|| !ActionBlockerSystem.CanInteract(user)
|
||||||
/// </summary>
|
|| !user.TryGetComponent(out HandsComponent? hands) || hands.GetActiveHand == null
|
||||||
public bool BlockDeconstruct { get; set; }
|
|| !user.InRangeUnobstructed(ev.Location, ignoreInsideBlocker:constructionPrototype.CanBuildInImpassable))
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a new instance of <see cref="BeginDeconstructCompMsg"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">Entity that initiated the deconstruction.</param>
|
|
||||||
public BeginDeconstructCompMsg(IEntity user)
|
|
||||||
{
|
{
|
||||||
User = user;
|
Cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pathFind == null)
|
||||||
|
throw new InvalidDataException($"Can't find path from starting node to target node in construction! Recipe: {ev.PrototypeName}");
|
||||||
|
|
||||||
|
var edge = startNode.GetEdge(pathFind[0].Name);
|
||||||
|
|
||||||
|
if(edge == null)
|
||||||
|
throw new InvalidDataException($"Can't find edge from starting node to the next node in pathfinding! Recipe: {ev.PrototypeName}");
|
||||||
|
|
||||||
|
var valid = false;
|
||||||
|
var holding = hands.GetActiveHand?.Owner;
|
||||||
|
|
||||||
|
if (holding == null)
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No support for conditions here!
|
||||||
|
|
||||||
|
foreach (var step in edge.Steps)
|
||||||
|
{
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case EntityInsertConstructionGraphStep entityInsert:
|
||||||
|
if (entityInsert.EntityValid(holding))
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
case ToolConstructionGraphStep _:
|
||||||
|
case NestedConstructionGraphStep _:
|
||||||
|
throw new InvalidDataException("Invalid first step for item recipe!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var structure = await Construct(user, (ev.Ack + constructionPrototype.GetHashCode()).ToString(), constructionGraph, edge, targetNode);
|
||||||
|
|
||||||
|
if (structure == null)
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
structure.Transform.Coordinates = ev.Location;
|
||||||
|
structure.Transform.LocalRotation = ev.Angle;
|
||||||
|
|
||||||
|
RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack));
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.GameObjects.Components;
|
using Content.Server.GameObjects.Components;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
@@ -27,7 +28,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
|||||||
var cancelled = new List<DoAfter>(0);
|
var cancelled = new List<DoAfter>(0);
|
||||||
var finished = new List<DoAfter>(0);
|
var finished = new List<DoAfter>(0);
|
||||||
|
|
||||||
foreach (var doAfter in comp.DoAfters)
|
foreach (var doAfter in comp.DoAfters.ToArray())
|
||||||
{
|
{
|
||||||
doAfter.Run(frameTime);
|
doAfter.Run(frameTime);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
"SubFloorHide",
|
"SubFloorHide",
|
||||||
"LowWall",
|
"LowWall",
|
||||||
"ReinforcedWall",
|
"ReinforcedWall",
|
||||||
"Window",
|
|
||||||
"CharacterInfo",
|
"CharacterInfo",
|
||||||
"InteractionOutline",
|
"InteractionOutline",
|
||||||
"MeleeWeaponArcAnimation",
|
"MeleeWeaponArcAnimation",
|
||||||
|
|||||||
43
Content.Server/Utility/SnapgridHelper.cs
Normal file
43
Content.Server/Utility/SnapgridHelper.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components.Transform;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Server.Utility
|
||||||
|
{
|
||||||
|
public static class SnapgridHelper
|
||||||
|
{
|
||||||
|
public static void SnapToGrid(this IEntity entity, SnapGridOffset offset = SnapGridOffset.Center, IEntityManager entityManager = null, IMapManager mapManager = null)
|
||||||
|
{
|
||||||
|
entity.Transform.Coordinates = entity.Transform.Coordinates.SnapToGrid(offset, entityManager, mapManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityCoordinates SnapToGrid(this EntityCoordinates coordinates,
|
||||||
|
SnapGridOffset offset = SnapGridOffset.Center, IEntityManager entityManager = null, IMapManager mapManager = null)
|
||||||
|
{
|
||||||
|
entityManager ??= IoCManager.Resolve<IEntityManager>();
|
||||||
|
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||||
|
|
||||||
|
var gridId = coordinates.GetGridId(entityManager);
|
||||||
|
|
||||||
|
var tileSize = 1f;
|
||||||
|
|
||||||
|
if (gridId.IsValid())
|
||||||
|
{
|
||||||
|
var grid = mapManager.GetGrid(gridId);
|
||||||
|
tileSize = grid.TileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
var localPos = coordinates.Position;
|
||||||
|
|
||||||
|
var x = (int)Math.Floor(localPos.X / tileSize) + tileSize / (offset == SnapGridOffset.Center ? 2f : 0f);
|
||||||
|
var y = (int)Math.Floor(localPos.Y / tileSize) + tileSize / (offset == SnapGridOffset.Center ? 2f : 0f);
|
||||||
|
|
||||||
|
return new EntityCoordinates(coordinates.EntityId, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -12,164 +9,69 @@ namespace Content.Shared.Construction
|
|||||||
[Prototype("construction")]
|
[Prototype("construction")]
|
||||||
public class ConstructionPrototype : IPrototype, IIndexedPrototype
|
public class ConstructionPrototype : IPrototype, IIndexedPrototype
|
||||||
{
|
{
|
||||||
private string _name;
|
private List<IConstructionCondition> _conditions;
|
||||||
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;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Friendly name displayed in the construction GUI.
|
/// Friendly name displayed in the construction GUI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name => _name;
|
public string Name { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Useful" description displayed in the construction GUI.
|
/// "Useful" description displayed in the construction GUI.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Texture path inside the construction GUI.
|
/// Texture path inside the construction GUI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SpriteSpecifier Icon => _icon;
|
public SpriteSpecifier Icon { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If you can start building or complete steps on impassable terrain.
|
/// If you can start building or complete steps on impassable terrain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanBuildInImpassable => _canBuildInImpassable;
|
public bool CanBuildInImpassable { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
public string Category { get; private set; }
|
||||||
/// A list of keywords that are used for searching.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<string> Keywords => _keywords;
|
|
||||||
|
|
||||||
/// <summary>
|
public ConstructionType Type { get; private set; }
|
||||||
/// The split up segments of the category.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<string> CategorySegments => _categorySegments;
|
|
||||||
|
|
||||||
/// <summary>
|
public string ID { get; private set; }
|
||||||
/// 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 ConstructionType Type => _type;
|
public string PlacementMode { get; private set; }
|
||||||
|
|
||||||
public string ID => _id;
|
public IReadOnlyList<IConstructionCondition> Conditions => _conditions;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The prototype name of the entity prototype when construction is done.
|
|
||||||
/// </summary>
|
|
||||||
public string Result => _result;
|
|
||||||
|
|
||||||
public string PlacementMode => _placementMode;
|
|
||||||
|
|
||||||
public void LoadFrom(YamlMappingNode mapping)
|
public void LoadFrom(YamlMappingNode mapping)
|
||||||
{
|
{
|
||||||
var ser = YamlObjectSerializer.NewReader(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(this, x => x.ID, "id", string.Empty);
|
||||||
ser.DataField(ref _description, "description", string.Empty);
|
ser.DataField(this, x => x.Graph, "graph", string.Empty);
|
||||||
ser.DataField(ref _icon, "icon", SpriteSpecifier.Invalid);
|
ser.DataField(this, x => x.TargetNode, "targetNode", string.Empty);
|
||||||
ser.DataField(ref _type, "objectType", ConstructionType.Structure);
|
ser.DataField(this, x => x.StartNode, "startNode", string.Empty);
|
||||||
ser.DataField(ref _result, "result", null);
|
ser.DataField(this, x => x.Description, "description", string.Empty);
|
||||||
ser.DataField(ref _placementMode, "placementMode", "PlaceFree");
|
ser.DataField(this, x => x.Icon, "icon", SpriteSpecifier.Invalid);
|
||||||
ser.DataField(ref _canBuildInImpassable, "canBuildInImpassable", false);
|
ser.DataField(this, x => x.Type, "objectType", ConstructionType.Structure);
|
||||||
|
ser.DataField(this, x => x.PlacementMode, "placementMode", "PlaceFree");
|
||||||
_keywords = ser.ReadDataField<List<string>>("keywords", new List<string>());
|
ser.DataField(this, x => x.CanBuildInImpassable, "canBuildInImpassable", false);
|
||||||
{
|
ser.DataField(this, x => x.Category, "category", string.Empty);
|
||||||
var cat = ser.ReadDataField<string>("category");
|
ser.DataField(ref _conditions, "conditions", new List<IConstructionCondition>());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,47 +80,5 @@ namespace Content.Shared.Construction
|
|||||||
Structure,
|
Structure,
|
||||||
Item,
|
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.DamageContainer;
|
||||||
using Content.Shared.Damage.ResistanceSet;
|
using Content.Shared.Damage.ResistanceSet;
|
||||||
using Content.Shared.Interfaces.GameObjects.Components;
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Mono.Collections.Generic;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -157,6 +158,9 @@ namespace Content.Shared.GameObjects.Components.Damage
|
|||||||
{
|
{
|
||||||
var writeFlags = new List<DamageFlag>();
|
var writeFlags = new List<DamageFlag>();
|
||||||
|
|
||||||
|
if (Flags == DamageFlag.None)
|
||||||
|
return writeFlags;
|
||||||
|
|
||||||
foreach (var flag in (DamageFlag[]) Enum.GetValues(typeof(DamageFlag)))
|
foreach (var flag in (DamageFlag[]) Enum.GetValues(typeof(DamageFlag)))
|
||||||
{
|
{
|
||||||
if ((Flags & flag) == flag)
|
if ((Flags & flag) == flag)
|
||||||
|
|||||||
@@ -16,6 +16,23 @@ namespace Content.Shared.GameObjects.Components.Interactable
|
|||||||
Multitool = 1 << 5,
|
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 class SharedToolComponent : Component
|
||||||
{
|
{
|
||||||
public override string Name => "Tool";
|
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,
|
Metal,
|
||||||
Glass,
|
Glass,
|
||||||
|
Plasteel,
|
||||||
Cable,
|
Cable,
|
||||||
Gold,
|
Gold,
|
||||||
Phoron,
|
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;
|
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 System.Diagnostics.CodeAnalysis;
|
||||||
using Content.Shared.Physics;
|
using Content.Shared.Physics;
|
||||||
using Content.Shared.Utility;
|
using Content.Shared.Utility;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Map;
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.Interfaces.Physics;
|
using Robust.Shared.Interfaces.Physics;
|
||||||
@@ -29,18 +28,18 @@ namespace Content.Shared.Maps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to get the turf at map indices with grid id or null if no such turf is found.
|
/// Attempts to get the turf at map indices with grid id or null if no such turf is found.
|
||||||
/// </summary>
|
/// </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())
|
if (!gridId.IsValid())
|
||||||
return null;
|
return default;
|
||||||
|
|
||||||
mapManager ??= IoCManager.Resolve<IMapManager>();
|
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||||
|
|
||||||
if (!mapManager.TryGetGrid(gridId, out var grid))
|
if (!mapManager.TryGetGrid(gridId, out var grid))
|
||||||
return null;
|
return default;
|
||||||
|
|
||||||
if (!grid.TryGetTileRef(mapIndices, out var tile))
|
if (!grid.TryGetTileRef(mapIndices, out var tile))
|
||||||
return null;
|
return default;
|
||||||
|
|
||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
@@ -149,12 +148,7 @@ namespace Content.Shared.Maps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static IEnumerable<IEntity> GetEntitiesInTile(this MapIndices indices, GridId gridId, bool approximate = false, IEntityManager? entityManager = null)
|
public static IEnumerable<IEntity> GetEntitiesInTile(this MapIndices indices, GridId gridId, bool approximate = false, IEntityManager? entityManager = null)
|
||||||
{
|
{
|
||||||
var turf = indices.GetTileRef(gridId);
|
return GetEntitiesInTile(indices.GetTileRef(gridId), approximate, entityManager);
|
||||||
|
|
||||||
if (turf == null)
|
|
||||||
return Enumerable.Empty<IEntity>();
|
|
||||||
|
|
||||||
return GetEntitiesInTile(turf.Value, approximate, entityManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -194,7 +188,9 @@ namespace Content.Shared.Maps
|
|||||||
{
|
{
|
||||||
var map = IoCManager.Resolve<IMapManager>();
|
var map = IoCManager.Resolve<IMapManager>();
|
||||||
var tileGrid = map.GetGrid(turf.GridIndex);
|
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));
|
return tileBox.Translated(tileGrid.GridTileToWorldPos(turf.GridIndices));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,13 +101,13 @@ namespace Content.Shared.Materials
|
|||||||
// All default material params are initialized to 1 because
|
// 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.
|
// 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 _density, "density", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _electricResistivity, "electricresistivity", 1, alwaysWrite: true);
|
serializer.DataField(ref _electricResistivity, "electricResistivity", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _thermalConductivity, "thermalconductivity", 1, alwaysWrite: true);
|
serializer.DataField(ref _thermalConductivity, "thermalConductivity", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _specificHeat, "specificheat", 1, alwaysWrite: true);
|
serializer.DataField(ref _specificHeat, "specificHeat", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _durability, "durability", 1, alwaysWrite: true);
|
serializer.DataField(ref _durability, "durability", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _hardness, "hardness", 1, alwaysWrite: true);
|
serializer.DataField(ref _hardness, "hardness", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _sharpDamage, "sharpdamage", 1, alwaysWrite: true);
|
serializer.DataField(ref _sharpDamage, "sharpDamage", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _bluntDamage, "bluntdamage", 1, alwaysWrite: true);
|
serializer.DataField(ref _bluntDamage, "bluntDamage", 1, alwaysWrite: true);
|
||||||
serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid, alwaysWrite: true);
|
serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid, alwaysWrite: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,9 +122,6 @@ entities:
|
|||||||
pos: -0.5,-0.5
|
pos: -0.5,-0.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 2
|
- uid: 2
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -132,9 +129,6 @@ entities:
|
|||||||
pos: -0.5,-1.5
|
pos: -0.5,-1.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 3
|
- uid: 3
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -142,9 +136,6 @@ entities:
|
|||||||
pos: -0.5,-2.5
|
pos: -0.5,-2.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 4
|
- uid: 4
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -152,9 +143,6 @@ entities:
|
|||||||
pos: 0.5,-2.5
|
pos: 0.5,-2.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 5
|
- uid: 5
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -162,9 +150,6 @@ entities:
|
|||||||
pos: 1.5,-2.5
|
pos: 1.5,-2.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 6
|
- uid: 6
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -172,9 +157,6 @@ entities:
|
|||||||
pos: 1.5,-1.5
|
pos: 1.5,-1.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 7
|
- uid: 7
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -182,9 +164,6 @@ entities:
|
|||||||
pos: 1.5,-0.5
|
pos: 1.5,-0.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
- uid: 8
|
- uid: 8
|
||||||
type: reinforced_wall
|
type: reinforced_wall
|
||||||
components:
|
components:
|
||||||
@@ -192,7 +171,4 @@ entities:
|
|||||||
pos: 0.5,-0.5
|
pos: 0.5,-0.5
|
||||||
rot: -1.5707963267948966 rad
|
rot: -1.5707963267948966 rad
|
||||||
type: Transform
|
type: Transform
|
||||||
- flags:
|
|
||||||
- None
|
|
||||||
type: Destructible
|
|
||||||
...
|
...
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -92,4 +92,4 @@
|
|||||||
components:
|
components:
|
||||||
- type: StorageFill
|
- type: StorageFill
|
||||||
contents:
|
contents:
|
||||||
- name: CableStack1
|
- name: ApcExtensionCableStack1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: MetalStack
|
id: MetalStack
|
||||||
icon: Objects/Materials/materials.rsi/sheet_metal.png
|
icon: Objects/Materials/sheets.rsi/metal.png
|
||||||
result: SteelSheet1
|
result: SteelSheet1
|
||||||
completetime: 500
|
completetime: 500
|
||||||
materials:
|
materials:
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: GlassStack
|
id: GlassStack
|
||||||
icon: Objects/Materials/materials.rsi/sheet_glass.png
|
icon: Objects/Materials/sheets.rsi/glass.png
|
||||||
result: GlassSheet1
|
result: GlassSheet1
|
||||||
completetime: 500
|
completetime: 500
|
||||||
materials:
|
materials:
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
- type: latheRecipe
|
- type: latheRecipe
|
||||||
id: CableStack
|
id: CableStack
|
||||||
name: cable coil
|
name: cable coil
|
||||||
icon: Objects/Tools/cable_coil.png
|
icon: /Textures/Objects/Tools/cables.rsi/coil-30.png
|
||||||
result: CableStack1
|
result: ApcExtensionCableStack1
|
||||||
completetime: 500
|
completetime: 500
|
||||||
materials:
|
materials:
|
||||||
steel: 50
|
steel: 50
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
animationDuration: 1.1
|
animationDuration: 1.1
|
||||||
spriteName: youtool
|
spriteName: youtool
|
||||||
startingInventory:
|
startingInventory:
|
||||||
CableStack1: 10
|
ApcExtensionCableStack1: 10
|
||||||
Crowbar: 5
|
Crowbar: 5
|
||||||
Welder: 3
|
Welder: 3
|
||||||
Wirecutter: 5
|
Wirecutter: 5
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
name: "glass crate"
|
name: "glass crate"
|
||||||
id: cargo.glass
|
id: cargo.glass
|
||||||
description: "50 sheets of glass."
|
description: "50 sheets of glass."
|
||||||
icon: Objects/Materials/sheet_glass.png
|
icon: Objects/Materials/sheets.rsi/glass.png
|
||||||
product: CrateGlass
|
product: CrateGlass
|
||||||
cost: 50
|
cost: 50
|
||||||
category: Engineering
|
category: Engineering
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
name: "cable crate"
|
name: "cable crate"
|
||||||
id: cargo.cable
|
id: cargo.cable
|
||||||
description: "50 coils of cable."
|
description: "50 coils of cable."
|
||||||
icon: Objects/Tools/cable_coil.png
|
icon: /Textures/Objects/Tools/cables.rsi/coil-30.png
|
||||||
product: CrateCable
|
product: CrateCable
|
||||||
cost: 50
|
cost: 50
|
||||||
category: Engineering
|
category: Engineering
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
- type: Destructible
|
- type: Destructible
|
||||||
deadThreshold: 30
|
deadThreshold: 30
|
||||||
destroySound: /Audio/Effects/metalbreak.ogg
|
destroySound: /Audio/Effects/metalbreak.ogg
|
||||||
spawnOnDestroy: MetalSheet1
|
spawnOnDestroy: SteelSheet1
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -47,5 +47,5 @@
|
|||||||
- type: Destructible
|
- type: Destructible
|
||||||
deadThreshold: 30
|
deadThreshold: 30
|
||||||
destroySound: /Audio/Effects/metalbreak.ogg
|
destroySound: /Audio/Effects/metalbreak.ogg
|
||||||
spawnOnDestroy: MetalSheet1
|
spawnOnDestroy: SteelSheet1
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
- type: Destructible
|
- type: Destructible
|
||||||
maxHP: 500
|
maxHP: 500
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
spawnondestroy: AMEPart
|
spawnOnDestroy: AMEPart
|
||||||
- type: SnapGrid
|
- type: SnapGrid
|
||||||
offset: Center
|
offset: Center
|
||||||
- type: Airtight
|
- type: Airtight
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: true
|
anchored: true
|
||||||
@@ -67,7 +66,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: true
|
anchored: true
|
||||||
@@ -75,7 +73,7 @@
|
|||||||
offset: Center
|
offset: Center
|
||||||
- type: Destructible
|
- type: Destructible
|
||||||
deadThreshold: 25
|
deadThreshold: 25
|
||||||
spawnOnDestroy: MetalSheet1
|
spawnOnDestroy: SteelSheet1
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
interfaces:
|
interfaces:
|
||||||
- key: enum.ChemMasterUiKey.Key
|
- key: enum.ChemMasterUiKey.Key
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: true
|
anchored: true
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
- type: entity
|
- type: entity
|
||||||
id: ComputerBase
|
id: ComputerFrame
|
||||||
name: computer
|
name: computer frame
|
||||||
abstract: true
|
|
||||||
components:
|
components:
|
||||||
- type: Physics
|
|
||||||
mass: 25
|
|
||||||
anchored: true
|
|
||||||
- type: Clickable
|
|
||||||
- type: InteractionOutline
|
|
||||||
- type: Collidable
|
- type: Collidable
|
||||||
IsScrapingFloor: true
|
mass: 25
|
||||||
|
anchored: false
|
||||||
shapes:
|
shapes:
|
||||||
- !type:PhysShapeAabb
|
- !type:PhysShapeAabb
|
||||||
bounds: "-0.5,-0.25,0.5,0.25"
|
bounds: "-0.5,-0.25,0.5,0.25"
|
||||||
@@ -22,10 +17,77 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Anchorable
|
||||||
|
- type: Construction
|
||||||
|
graph: computer
|
||||||
|
node: frameUnsecured
|
||||||
|
- type: Sprite
|
||||||
|
sprite: "Constructible/Misc/stock_parts.rsi"
|
||||||
|
state: "0"
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: ComputerBroken
|
||||||
|
name: broken computer
|
||||||
|
description: This computer has seen better days.
|
||||||
|
abstract: true # We don't want this to show up in the entity spawner menu.
|
||||||
|
components:
|
||||||
|
- type: Collidable
|
||||||
|
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: Construction
|
||||||
|
graph: computer
|
||||||
|
node: monitorBroken
|
||||||
|
- type: Sprite
|
||||||
|
sprite: "Constructible/Power/computers.rsi"
|
||||||
|
state: "broken"
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: ComputerBase
|
||||||
|
name: computer
|
||||||
|
abstract: true
|
||||||
|
components:
|
||||||
|
- type: Clickable
|
||||||
|
- type: InteractionOutline
|
||||||
|
- type: Construction
|
||||||
|
graph: computer
|
||||||
|
node: computer
|
||||||
|
- type: Collidable
|
||||||
|
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: Computer
|
- type: Computer
|
||||||
- type: PowerReceiver
|
- type: PowerReceiver
|
||||||
- type: Anchorable
|
- type: Anchorable
|
||||||
|
- type: BreakableConstruction
|
||||||
|
node: monitorBroken
|
||||||
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Constructible/Power/computers.rsi
|
sprite: Constructible/Power/computers.rsi
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: true
|
anchored: true
|
||||||
@@ -68,7 +67,7 @@
|
|||||||
- Screwdriver
|
- Screwdriver
|
||||||
- Welder
|
- Welder
|
||||||
- Wrench
|
- Wrench
|
||||||
- CableStack1
|
- CableStack
|
||||||
- Crowbar
|
- Crowbar
|
||||||
- Multitool
|
- Multitool
|
||||||
- type: Appearance
|
- type: Appearance
|
||||||
@@ -116,7 +115,7 @@
|
|||||||
- Screwdriver
|
- Screwdriver
|
||||||
- Welder
|
- Welder
|
||||||
- Wrench
|
- Wrench
|
||||||
- CableStack1
|
- CableStack
|
||||||
- Crowbar
|
- Crowbar
|
||||||
- Multitool
|
- Multitool
|
||||||
- type: UserInterface
|
- type: UserInterface
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: true
|
anchored: true
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
netsync: false
|
netsync: false
|
||||||
sprite: Constructible/Power/microwave.rsi
|
sprite: Constructible/Power/microwave.rsi
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
- Impassable
|
- Impassable
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: true
|
anchored: true
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
wireType: HighVoltage
|
wireType: HighVoltage
|
||||||
- type: Destructible
|
- type: Destructible
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
spawnondestroy: HVWireStack1
|
spawnOnDestroy: HVWireStack1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: WireBase
|
parent: WireBase
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
wireType: MediumVoltage
|
wireType: MediumVoltage
|
||||||
- type: Destructible
|
- type: Destructible
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
spawnondestroy: MVWireStack1
|
spawnOnDestroy: MVWireStack1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: WireBase
|
parent: WireBase
|
||||||
@@ -98,4 +98,4 @@
|
|||||||
wireType: Apc
|
wireType: Apc
|
||||||
- type: Destructible
|
- type: Destructible
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
spawnondestroy: ApcExtensionCableStack1
|
spawnOnDestroy: ApcExtensionCableStack1
|
||||||
|
|||||||
@@ -33,7 +33,6 @@
|
|||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
- SmallImpassable
|
- SmallImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: false
|
anchored: false
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
- SmallImpassable
|
- SmallImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 15
|
mass: 15
|
||||||
Anchored: false
|
Anchored: false
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
- Opaque
|
- Opaque
|
||||||
- MobImpassable
|
- MobImpassable
|
||||||
- SmallImpassable
|
- SmallImpassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 25
|
mass: 25
|
||||||
anchored: false
|
anchored: false
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
components:
|
components:
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
- type: InteractionOutline
|
- type: InteractionOutline
|
||||||
|
- type: Anchorable
|
||||||
|
- type: Construction
|
||||||
|
graph: girder
|
||||||
|
node: girder
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Constructible/Structures/Walls/solid.rsi
|
sprite: Constructible/Structures/Walls/solid.rsi
|
||||||
state: wall_girder
|
state: wall_girder
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
components:
|
components:
|
||||||
- type: Clickable
|
- type: Clickable
|
||||||
- type: RCDDeconstructWhitelist
|
- type: RCDDeconstructWhitelist
|
||||||
|
- type: CanBuildWindowOnTop
|
||||||
- type: InteractionOutline
|
- type: InteractionOutline
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
netsync: false
|
netsync: false
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
layer:
|
layer:
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
- SmallImpassable
|
- SmallImpassable
|
||||||
- type: Destructible
|
- type: BreakableConstruction
|
||||||
|
node: start
|
||||||
deadThreshold: 100
|
deadThreshold: 100
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
- type: SnapGrid
|
- type: SnapGrid
|
||||||
@@ -33,6 +35,9 @@
|
|||||||
- type: LowWall
|
- type: LowWall
|
||||||
key: walls
|
key: walls
|
||||||
base: metal_
|
base: metal_
|
||||||
|
- type: Construction
|
||||||
|
graph: lowWall
|
||||||
|
node: lowWall
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: LowWallOverlay
|
id: LowWallOverlay
|
||||||
|
|||||||
@@ -25,10 +25,6 @@
|
|||||||
- MobImpassable
|
- MobImpassable
|
||||||
- VaultImpassable
|
- VaultImpassable
|
||||||
- SmallImpassable
|
- SmallImpassable
|
||||||
- type: Destructible
|
|
||||||
deadThreshold: 500
|
|
||||||
spawnOnDestroy: Girder
|
|
||||||
resistances: metallicResistances
|
|
||||||
- type: Occluder
|
- type: Occluder
|
||||||
sizeX: 32
|
sizeX: 32
|
||||||
sizeY: 32
|
sizeY: 32
|
||||||
@@ -239,14 +235,20 @@
|
|||||||
- type: Icon
|
- type: Icon
|
||||||
sprite: Constructible/Structures/Walls/solid.rsi
|
sprite: Constructible/Structures/Walls/solid.rsi
|
||||||
state: rgeneric
|
state: rgeneric
|
||||||
- type: Destructible
|
- type: Construction
|
||||||
|
graph: girder
|
||||||
|
node: reinforcedWall
|
||||||
|
- type: BreakableConstruction
|
||||||
deadThreshold: 600
|
deadThreshold: 600
|
||||||
spawnOnDestroy: Girder
|
node: girder
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
- type: ReinforcedWall
|
- type: ReinforcedWall
|
||||||
key: walls
|
key: walls
|
||||||
base: solid
|
base: solid
|
||||||
reinforcedBase: reinf_over
|
reinforcedBase: reinf_over
|
||||||
|
- type: Appearance
|
||||||
|
visuals:
|
||||||
|
- type: ReinforcedWallVisualizer
|
||||||
|
|
||||||
# Riveting
|
# Riveting
|
||||||
- type: entity
|
- type: entity
|
||||||
@@ -308,11 +310,14 @@
|
|||||||
- type: Sprite
|
- type: Sprite
|
||||||
color: "#889192"
|
color: "#889192"
|
||||||
sprite: Constructible/Structures/Walls/solid.rsi
|
sprite: Constructible/Structures/Walls/solid.rsi
|
||||||
|
- type: Construction
|
||||||
|
graph: girder
|
||||||
|
node: wall
|
||||||
- type: Icon
|
- type: Icon
|
||||||
sprite: Constructible/Structures/Walls/solid.rsi
|
sprite: Constructible/Structures/Walls/solid.rsi
|
||||||
- type: Destructible
|
- type: BreakableConstruction
|
||||||
deadThreshold: 300
|
deadThreshold: 300
|
||||||
spawnOnDestroy: Girder
|
node: girder
|
||||||
destroySound: /Audio/Effects/metalbreak.ogg
|
destroySound: /Audio/Effects/metalbreak.ogg
|
||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
- type: IconSmooth
|
- type: IconSmooth
|
||||||
|
|||||||
@@ -34,6 +34,9 @@
|
|||||||
- type: Airtight
|
- type: Airtight
|
||||||
- type: Window
|
- type: Window
|
||||||
base: window
|
base: window
|
||||||
|
- type: Construction
|
||||||
|
graph: window
|
||||||
|
node: window
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: ReinforcedWindow
|
id: ReinforcedWindow
|
||||||
@@ -50,6 +53,9 @@
|
|||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
- type: Window
|
- type: Window
|
||||||
base: rwindow
|
base: rwindow
|
||||||
|
- type: Construction
|
||||||
|
graph: window
|
||||||
|
node: reinforcedWindow
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: PhoronWindow
|
id: PhoronWindow
|
||||||
@@ -66,3 +72,6 @@
|
|||||||
resistances: metallicResistances
|
resistances: metallicResistances
|
||||||
- type: Window
|
- type: Window
|
||||||
base: pwindow
|
base: pwindow
|
||||||
|
- type: Construction
|
||||||
|
graph: window
|
||||||
|
node: phoronWindow
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
- type: entity
|
||||||
|
id: BaseComputerCircuitboard
|
||||||
|
parent: BaseItem
|
||||||
|
name: circuit board
|
||||||
|
abstract: true
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Constructible/Misc/module.rsi
|
||||||
|
state: id_mod
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SupplyComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: supply computer circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerSupplyOrdering
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SupplyRequestComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: supply request computer circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerSupplyRequest
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: ResearchComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: R&D computer circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerResearchAndDevelopment
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: IDComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: ID card terminal circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerId
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: BodyScannerComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: body scanner computer circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: computerBodyScanner
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: CommsComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: communications computer circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerComms
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: SolarControlComputerCircuitboard
|
||||||
|
parent: BaseComputerCircuitboard
|
||||||
|
name: solar control computer circuit board
|
||||||
|
components:
|
||||||
|
- type: ComputerBoard
|
||||||
|
prototype: ComputerSolarControl
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
# ability to have applied colors yet in GUIs. -Swept
|
# ability to have applied colors yet in GUIs. -Swept
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
|
id: CableStack
|
||||||
abstract: true
|
abstract: true
|
||||||
parent: BaseItem
|
parent: BaseItem
|
||||||
id: CableStack1
|
name: cable stack
|
||||||
name: cable stack 1
|
|
||||||
suffix: Full
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
@@ -20,9 +20,10 @@
|
|||||||
- type: Clickable
|
- type: Clickable
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: CableStack1
|
|
||||||
id: HVWireStack
|
id: HVWireStack
|
||||||
|
parent: CableStack
|
||||||
name: HV Wire Coil
|
name: HV Wire Coil
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: coilhv-30
|
state: coilhv-30
|
||||||
@@ -33,34 +34,6 @@
|
|||||||
wirePrototypeID: HVWire
|
wirePrototypeID: HVWire
|
||||||
blockingWireType: HighVoltage
|
blockingWireType: HighVoltage
|
||||||
|
|
||||||
- type: entity
|
|
||||||
parent: CableStack1
|
|
||||||
id: MVWireStack
|
|
||||||
name: MV Wire Coil
|
|
||||||
components:
|
|
||||||
- type: Sprite
|
|
||||||
state: coilmv-30
|
|
||||||
- type: Item
|
|
||||||
size: 10
|
|
||||||
HeldPrefix: coilmv
|
|
||||||
- type: WirePlacer
|
|
||||||
wirePrototypeID: MVWire
|
|
||||||
blockingWireType: MediumVoltage
|
|
||||||
|
|
||||||
- type: entity
|
|
||||||
parent: CableStack1
|
|
||||||
id: ApcExtensionCableStack
|
|
||||||
name: Apc Extension Cable Coil
|
|
||||||
components:
|
|
||||||
- type: Sprite
|
|
||||||
state: coillv-30
|
|
||||||
- type: Item
|
|
||||||
size: 10
|
|
||||||
HeldPrefix: coillv
|
|
||||||
- type: WirePlacer
|
|
||||||
wirePrototypeID: ApcExtensionCable
|
|
||||||
blockingWireType: Apc
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: HVWireStack
|
parent: HVWireStack
|
||||||
id: HVWireStack1
|
id: HVWireStack1
|
||||||
@@ -74,16 +47,19 @@
|
|||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: MVWireStack
|
parent: CableStack
|
||||||
id: MVWireStack1
|
id: ApcExtensionCableStack
|
||||||
suffix: 1
|
name: Apc Extension Cable Coil
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
state: coilmv-10
|
state: coillv-30
|
||||||
- type: Item
|
- type: Item
|
||||||
size: 3
|
size: 10
|
||||||
- type: Stack
|
HeldPrefix: coillv
|
||||||
count: 1
|
- type: WirePlacer
|
||||||
|
wirePrototypeID: ApcExtensionCable
|
||||||
|
blockingWireType: Apc
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: ApcExtensionCableStack
|
parent: ApcExtensionCableStack
|
||||||
@@ -96,3 +72,30 @@
|
|||||||
size: 3
|
size: 3
|
||||||
- type: Stack
|
- type: Stack
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: CableStack
|
||||||
|
id: MVWireStack
|
||||||
|
name: MV Wire Coil
|
||||||
|
suffix: Full
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: coilmv-30
|
||||||
|
- type: Item
|
||||||
|
size: 10
|
||||||
|
HeldPrefix: coilmv
|
||||||
|
- type: WirePlacer
|
||||||
|
wirePrototypeID: MVWire
|
||||||
|
blockingWireType: MediumVoltage
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: MVWireStack
|
||||||
|
id: MVWireStack1
|
||||||
|
suffix: 1
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
state: coilmv-10
|
||||||
|
- type: Item
|
||||||
|
size: 3
|
||||||
|
- type: Stack
|
||||||
|
count: 1
|
||||||
|
|||||||
@@ -113,7 +113,6 @@
|
|||||||
bounds: "-0.25,-0.25,0.25,0.25"
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
mask: [Impassable]
|
mask: [Impassable]
|
||||||
layer: [Clickable]
|
layer: [Clickable]
|
||||||
IsScrapingFloor: true
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: recharger
|
name: recharger
|
||||||
@@ -143,7 +142,6 @@
|
|||||||
bounds: "-0.25,-0.25,0.25,0.25"
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
mask: [Impassable]
|
mask: [Impassable]
|
||||||
layer: [Clickable]
|
layer: [Clickable]
|
||||||
IsScrapingFloor: true
|
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: wall recharger
|
name: wall recharger
|
||||||
@@ -172,4 +170,3 @@
|
|||||||
bounds: "-0.25,-0.25,0.25,0.25"
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
mask: [Impassable]
|
mask: [Impassable]
|
||||||
layer: [Clickable]
|
layer: [Clickable]
|
||||||
IsScrapingFloor: true
|
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
- Opaque
|
- Opaque
|
||||||
layer:
|
layer:
|
||||||
- Opaque
|
- Opaque
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 5
|
mass: 5
|
||||||
anchored: false
|
anchored: false
|
||||||
@@ -74,7 +73,6 @@
|
|||||||
bounds: "-0.25,-0.25,0.25,0.25"
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
mask:
|
mask:
|
||||||
- Impassable
|
- Impassable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 5
|
mass: 5
|
||||||
anchored: false
|
anchored: false
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
sprite: Objects/Weapons/Melee/spear.rsi
|
sprite: Objects/Weapons/Melee/spear.rsi
|
||||||
state: spear
|
state: spear
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- type: MeleeWeapon
|
- type: MeleeWeapon
|
||||||
range: 1.5
|
range: 1.5
|
||||||
arcwidth: 0
|
arcwidth: 0
|
||||||
@@ -19,6 +17,10 @@
|
|||||||
sprite: Objects/Weapons/Melee/spear.rsi
|
sprite: Objects/Weapons/Melee/spear.rsi
|
||||||
prefix: inhand
|
prefix: inhand
|
||||||
|
|
||||||
|
- type: Construction
|
||||||
|
graph: spear
|
||||||
|
node: spear
|
||||||
|
|
||||||
- type: ItemCooldown
|
- type: ItemCooldown
|
||||||
|
|
||||||
- type: MeleeWeaponAnimation
|
- type: MeleeWeaponAnimation
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
name: steel sheet
|
name: steel sheet
|
||||||
id: MetalStack
|
id: MetalStack
|
||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
@@ -28,16 +29,19 @@
|
|||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: SteelSheet1
|
id: SteelSheet1
|
||||||
name: steel sheet 1
|
name: steel sheet
|
||||||
parent: MetalStack
|
parent: MetalStack
|
||||||
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
|
stacktype: enum.StackType.Metal
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: glass sheet
|
name: glass sheet
|
||||||
id: GlassStack
|
id: GlassStack
|
||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
@@ -54,16 +58,48 @@
|
|||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: GlassSheet1
|
id: GlassSheet1
|
||||||
name: glass sheet 1
|
name: glass sheet
|
||||||
parent: GlassStack
|
parent: GlassStack
|
||||||
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
|
stacktype: enum.StackType.Glass
|
||||||
|
count: 1
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
name: plasteel sheet
|
||||||
|
id: PlasteelStack
|
||||||
|
parent: MaterialStack
|
||||||
|
suffix: Full
|
||||||
|
components:
|
||||||
|
- type: Material
|
||||||
|
materials:
|
||||||
|
- key: enum.MaterialKeys.Stack
|
||||||
|
mat: plasteel
|
||||||
|
- type: Stack
|
||||||
|
stacktype: enum.StackType.Plasteel
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Materials/sheets.rsi
|
||||||
|
state: plasteel
|
||||||
|
- type: Item
|
||||||
|
sprite: Objects/Materials/sheets.rsi
|
||||||
|
HeldPrefix: plasteel
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: PlasteelSheet1
|
||||||
|
name: plasteel sheet
|
||||||
|
parent: PlasteelStack
|
||||||
|
suffix: 1
|
||||||
|
components:
|
||||||
|
- type: Stack
|
||||||
|
stacktype: enum.StackType.Plasteel
|
||||||
count: 1
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
name: gold bar
|
name: gold bar
|
||||||
id: GoldStack
|
id: GoldStack
|
||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
@@ -79,6 +115,7 @@
|
|||||||
id: GoldStack1
|
id: GoldStack1
|
||||||
name: gold bar 1
|
name: gold bar 1
|
||||||
parent: GoldStack
|
parent: GoldStack
|
||||||
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
sprite: Objects/Materials/materials.rsi
|
sprite: Objects/Materials/materials.rsi
|
||||||
@@ -99,6 +136,7 @@
|
|||||||
name: phoron sheet
|
name: phoron sheet
|
||||||
id: PhoronStack
|
id: PhoronStack
|
||||||
parent: MaterialStack
|
parent: MaterialStack
|
||||||
|
suffix: Full
|
||||||
components:
|
components:
|
||||||
- type: Material
|
- type: Material
|
||||||
materials:
|
materials:
|
||||||
@@ -115,8 +153,9 @@
|
|||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: PhoronStack1
|
id: PhoronStack1
|
||||||
name: phoron sheet 1
|
name: phoron sheet
|
||||||
parent: PhoronStack
|
parent: PhoronStack
|
||||||
|
suffix: 1
|
||||||
components:
|
components:
|
||||||
- type: Stack
|
- type: Stack
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
bounds: "-0.25,-0.25,0.25,0.25"
|
bounds: "-0.25,-0.25,0.25,0.25"
|
||||||
layer:
|
layer:
|
||||||
- Clickable
|
- Clickable
|
||||||
IsScrapingFloor: true
|
|
||||||
- type: Physics
|
- type: Physics
|
||||||
mass: 5
|
mass: 5
|
||||||
- type: Sprite
|
- type: Sprite
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user