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.Linq;
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Materials;
|
||||
using Microsoft.CodeAnalysis.Options;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.Placement;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -25,376 +33,424 @@ namespace Content.Client.Construction
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
|
||||
private readonly Button BuildButton;
|
||||
private readonly Button EraseButton;
|
||||
private readonly LineEdit SearchBar;
|
||||
private readonly Tree RecipeList;
|
||||
private readonly TextureRect InfoIcon;
|
||||
private readonly Label InfoLabel;
|
||||
private readonly ItemList StepList;
|
||||
protected override Vector2? CustomSize => (720, 320);
|
||||
|
||||
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 List<CategoryNode> FlattenedCategories;
|
||||
private readonly PlacementManager Placement;
|
||||
|
||||
protected override Vector2? CustomSize => (500, 350);
|
||||
private readonly ItemList _recipes;
|
||||
private readonly ItemList _stepList;
|
||||
private readonly Button _buildButton;
|
||||
private readonly Button _eraseButton;
|
||||
private readonly LineEdit _searchBar;
|
||||
private readonly OptionButton _category;
|
||||
private readonly TextureRect _targetTexture;
|
||||
private readonly RichTextLabel _targetName;
|
||||
private readonly RichTextLabel _targetDescription;
|
||||
|
||||
public ConstructionMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
Placement = (PlacementManager) IoCManager.Resolve<IPlacementManager>();
|
||||
Placement.PlacementChanged += OnPlacementChanged;
|
||||
|
||||
_placementManager.PlacementChanged += PlacementChanged;
|
||||
|
||||
Title = "Construction";
|
||||
|
||||
var hSplitContainer = new HSplitContainer();
|
||||
var hbox = new HBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand};
|
||||
|
||||
// Left side
|
||||
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);
|
||||
var recipeContainer = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.45f};
|
||||
|
||||
// Right side
|
||||
var guide = new VBoxContainer();
|
||||
var info = new HBoxContainer();
|
||||
InfoIcon = new TextureRect();
|
||||
InfoLabel = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
};
|
||||
info.AddChild(InfoIcon);
|
||||
info.AddChild(InfoLabel);
|
||||
guide.AddChild(info);
|
||||
var searchContainer = new HBoxContainer() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.1f};
|
||||
_searchBar = new LineEdit() {PlaceHolder = "Search", SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.6f};
|
||||
_category = new OptionButton() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.4f};
|
||||
|
||||
var stepsLabel = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
Text = "Steps"
|
||||
};
|
||||
guide.AddChild(stepsLabel);
|
||||
_recipes = new ItemList() {SelectMode = ItemList.ItemListSelectMode.Single, SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.9f};
|
||||
|
||||
StepList = new ItemList
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand, SelectMode = ItemList.ItemListSelectMode.None
|
||||
};
|
||||
guide.AddChild(StepList);
|
||||
var spacer = new Control() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.05f};
|
||||
|
||||
var buttonsContainer = new HBoxContainer();
|
||||
BuildButton = new Button
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
TextAlign = Label.AlignMode.Center,
|
||||
Text = "Build!",
|
||||
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);
|
||||
var stepsContainer = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.45f};
|
||||
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};
|
||||
var targetInfoContainer = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.85f};
|
||||
_targetName = new RichTextLabel() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.1f};
|
||||
_targetDescription = new RichTextLabel() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.9f};
|
||||
|
||||
hSplitContainer.AddChild(guide);
|
||||
Contents.AddChild(hSplitContainer);
|
||||
_stepList = new ItemList() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.75f, SelectMode = ItemList.ItemListSelectMode.None};
|
||||
|
||||
BuildButton.OnToggled += OnBuildToggled;
|
||||
EraseButton.OnToggled += OnEraseToggled;
|
||||
SearchBar.OnTextChanged += OnTextEntered;
|
||||
RecipeList.OnItemSelected += OnItemSelected;
|
||||
var buttonContainer = new VBoxContainer() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.1f};
|
||||
_buildButton = new Button() {Disabled = true, ToggleMode = true, Text = Loc.GetString("Place construction ghost"), SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.5f};
|
||||
|
||||
PopulatePrototypeList();
|
||||
PopulateTree();
|
||||
var eraseContainer = new HBoxContainer() {SizeFlagsVertical = SizeFlags.FillExpand, SizeFlagsStretchRatio = 0.5f};
|
||||
_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)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Placement.PlacementChanged -= OnPlacementChanged;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
_placementManager.PlacementChanged -= PlacementChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -11,6 +12,8 @@ namespace Content.Client.GameObjects.Components.Construction
|
||||
[RegisterComponent]
|
||||
public class ConstructionGhostComponent : Component, IExamine
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public override string Name => "ConstructionGhost";
|
||||
|
||||
[ViewVariables] public ConstructionPrototype Prototype { get; set; }
|
||||
@@ -18,8 +21,13 @@ namespace Content.Client.GameObjects.Components.Construction
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddText(Loc.GetString("Building: {0}\n", Prototype.Name));
|
||||
EntitySystem.Get<SharedConstructionSystem>().DoExamine(message, Prototype, 0, inDetailsRange);
|
||||
message.AddMarkup(Loc.GetString("Building: [color=cyan]{0}[/color]\n", Prototype.Name));
|
||||
|
||||
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.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -35,6 +36,7 @@ namespace Content.Client.GameObjects.Components
|
||||
Sprite.LayerSetDirOffset(ReinforcedCornerLayers.NW, DirectionOffset.Flip);
|
||||
Sprite.LayerMapSet(ReinforcedCornerLayers.SW, Sprite.AddLayerState(state0));
|
||||
Sprite.LayerSetDirOffset(ReinforcedCornerLayers.SW, DirectionOffset.Clockwise);
|
||||
Sprite.LayerMapSet(ReinforcedWallVisualLayers.Deconstruction, Sprite.AddBlankLayer());
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedStackComponent))]
|
||||
public class StackComponent : SharedStackComponent, IItemStatus
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -9,10 +10,9 @@ using static Content.Client.GameObjects.Components.IconSmoothing.IconSmoothCompo
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class WindowComponent : Component
|
||||
[ComponentReference(typeof(SharedWindowComponent))]
|
||||
public sealed class WindowComponent : SharedWindowComponent
|
||||
{
|
||||
public override string Name => "Window";
|
||||
|
||||
private string _stateBase;
|
||||
private ISpriteComponent _sprite;
|
||||
private SnapGridComponent _snapGrid;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Client.UserInterface;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
@@ -152,11 +153,20 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
foreach (var condition in prototype.Conditions)
|
||||
{
|
||||
if (!condition.Condition(user, loc, dir))
|
||||
return;
|
||||
}
|
||||
|
||||
var ghost = _entityManager.SpawnEntity("constructionghost", loc);
|
||||
var comp = ghost.GetComponent<ConstructionGhostComponent>();
|
||||
comp.Prototype = prototype;
|
||||
@@ -214,5 +224,18 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
_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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -179,6 +179,8 @@
|
||||
"Butcherable",
|
||||
"Rehydratable",
|
||||
"Headset",
|
||||
"ComputerBoard",
|
||||
"BreakableConstruction",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,9 +819,7 @@ namespace Content.Server.Atmos
|
||||
|
||||
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>())
|
||||
{
|
||||
|
||||
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]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate, IInteractUsing
|
||||
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate, IInteractUsing, IBreakAct
|
||||
{
|
||||
[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.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
@@ -16,6 +17,15 @@ namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
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]
|
||||
int IInteractUsing.Priority => 1;
|
||||
|
||||
@@ -37,7 +47,7 @@ namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
if (utilizing == null ||
|
||||
!utilizing.TryGetComponent(out ToolComponent? tool) ||
|
||||
!(await tool.UseTool(user, Owner, 0.5f, ToolQuality.Anchoring)))
|
||||
!(await tool.UseTool(user, Owner, 0.5f, Tool)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
public override bool CanClose(IEntity user) => true;
|
||||
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))
|
||||
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.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
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.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Construction
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds data about an entity that is in the process of being constructed or destructed.
|
||||
/// </summary>
|
||||
[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";
|
||||
|
||||
/// <summary>
|
||||
/// The current construction recipe being used to build this entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ConstructionPrototype Prototype { get; set; }
|
||||
private bool _handling = false;
|
||||
|
||||
private TaskCompletionSource<object>? _handlingTask = null;
|
||||
private string _graphIdentifier = string.Empty;
|
||||
private string _startingNodeIdentifier = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The current stage of construction.
|
||||
/// </summary>
|
||||
[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 />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction("prototype", null,
|
||||
value => Prototype = value, () => Prototype);
|
||||
serializer.DataField(ref _graphIdentifier, "graph", string.Empty);
|
||||
serializer.DataField(ref _startingNodeIdentifier, "node", string.Empty);
|
||||
}
|
||||
|
||||
serializer.DataReadWriteFunction("stage", 0,
|
||||
value => Stage = value, () => Stage);
|
||||
public void ClearTarget()
|
||||
{
|
||||
_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)
|
||||
{
|
||||
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>
|
||||
/// Entity spawned upon destruction.
|
||||
/// </summary>
|
||||
public string SpawnOnDestroy { get; set; }
|
||||
public string SpawnOnDestroy { get; private set; }
|
||||
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
@@ -39,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
public override void ExposeData(ObjectSerializer 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()
|
||||
|
||||
@@ -126,6 +126,17 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public IEnumerable<IEntity> GetAllHeldItems()
|
||||
{
|
||||
foreach (var (_, container) in _slotContainers)
|
||||
{
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
yield return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get container name for specified slot on this component
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,6 @@ using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.MachineLinking;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -56,21 +55,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
// TODO CONSTRUCTION make this use a construction graph
|
||||
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
|
||||
@@ -73,14 +73,7 @@ namespace Content.Server.GameObjects.Components.Recycling
|
||||
{
|
||||
prototype = null;
|
||||
|
||||
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
|
||||
var entityId = entity.MetaData.EntityPrototype?.ID;
|
||||
|
||||
if (entityId == null ||
|
||||
!constructionSystem.CraftRecipes.TryGetValue(entityId, out prototype))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// TODO CONSTRUCTION fix this
|
||||
|
||||
return Powered;
|
||||
}
|
||||
@@ -105,17 +98,7 @@ namespace Content.Server.GameObjects.Components.Recycling
|
||||
return;
|
||||
}
|
||||
|
||||
var constructionSystem = EntitySystem.Get<ConstructionSystem>();
|
||||
var recyclerPosition = Owner.Transform.MapPosition;
|
||||
foreach (var stage in prototype.Stages)
|
||||
{
|
||||
if (!(stage.Forward is ConstructionStepMaterial step))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
constructionSystem.SpawnIngredient(recyclerPosition, step);
|
||||
}
|
||||
// TODO CONSTRUCTION fix this
|
||||
|
||||
entity.Delete();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timers;
|
||||
@@ -16,8 +20,11 @@ namespace Content.Server.GameObjects.Components.Stack
|
||||
|
||||
// TODO: Naming and presentation and such could use some improvement.
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedStackComponent))]
|
||||
public class StackComponent : SharedStackComponent, IInteractUsing, IExamine
|
||||
{
|
||||
[Dependency] private IEntityManager _entityManager = default!;
|
||||
|
||||
private bool _throwIndividually = false;
|
||||
|
||||
public override int Count
|
||||
@@ -57,6 +64,33 @@ namespace Content.Server.GameObjects.Components.Stack
|
||||
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)
|
||||
{
|
||||
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
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -286,7 +287,7 @@ namespace Content.Server.GameObjects.EntitySystems.Atmos
|
||||
gridAtmosComponents[gridId] = gam;
|
||||
}
|
||||
|
||||
foreach (var invalid in indices)
|
||||
foreach (var invalid in indices.ToArray())
|
||||
{
|
||||
var chunk = GetOrCreateChunk(gridId, invalid);
|
||||
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Construction;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timers;
|
||||
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems
|
||||
{
|
||||
@@ -33,548 +34,425 @@ namespace Content.Server.GameObjects.EntitySystems
|
||||
[UsedImplicitly]
|
||||
internal class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = 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()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
_craftRecipes.Add(prototype.Result, prototype);
|
||||
}
|
||||
|
||||
SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
|
||||
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
|
||||
|
||||
SubscribeLocalEvent<AfterInteractMessage>(HandleToolInteraction);
|
||||
}
|
||||
|
||||
private void HandleStartStructureConstruction(TryStartStructureConstructionMessage msg, EntitySessionEventArgs args)
|
||||
private IEnumerable<IEntity> EnumerateNearby(IEntity user)
|
||||
{
|
||||
var placingEnt = args.SenderSession.AttachedEntity;
|
||||
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)
|
||||
if (user.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
var placingEnt = args.SenderSession.AttachedEntity;
|
||||
TryStartItemConstruction(placingEnt, msg.PrototypeName);
|
||||
}
|
||||
|
||||
private async void HandleToolInteraction(AfterInteractMessage msg)
|
||||
foreach (var itemComponent in hands?.GetAllHeldItems()!)
|
||||
{
|
||||
if(msg.Handled)
|
||||
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))
|
||||
if (itemComponent.Owner.TryGetComponent(out ServerStorageComponent? storage))
|
||||
{
|
||||
var result = await TryConstructEntity(constructComp, handEnt, msg.User);
|
||||
|
||||
// TryConstructEntity may delete the existing entity
|
||||
|
||||
msg.Handled = result;
|
||||
}
|
||||
else // try to start the deconstruction process
|
||||
foreach (var storedEntity in storage.StoredEntities!)
|
||||
{
|
||||
// A tool was not used on the entity.
|
||||
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;
|
||||
yield return storedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
spriteComp.AddLayerWithSprite(prototype.Icon);
|
||||
}
|
||||
|
||||
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);
|
||||
yield return itemComponent.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
else if (await TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.Coordinates))
|
||||
if (user!.TryGetComponent(out InventoryComponent? inventory))
|
||||
{
|
||||
constructionComponent.Stage--;
|
||||
stage = constructPrototype.Stages[constructionComponent.Stage];
|
||||
|
||||
// If forward needed a material, drop it
|
||||
SpawnIngredient(constructEntity.Transform.MapPosition, stage.Forward as ConstructionStepMaterial);
|
||||
|
||||
if (constructionComponent.Stage == 0)
|
||||
foreach (var held in inventory.GetAllHeldItems())
|
||||
{
|
||||
// Deconstruction complete.
|
||||
constructEntity.Delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stage.Icon != null)
|
||||
if (held.TryGetComponent(out ServerStorageComponent? storage))
|
||||
{
|
||||
spriteComponent.LayerSetSprite(0, stage.Icon);
|
||||
foreach (var storedEntity in storage.StoredEntities!)
|
||||
{
|
||||
yield return storedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
yield return held;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
yield return near;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case ConstructionStepMaterial matStep:
|
||||
if (!slapped.TryGetComponent(out StackComponent stack)
|
||||
|| !MaterialStackValidFor(matStep, stack)
|
||||
|| !stack.Use(matStep.Amount))
|
||||
case MaterialConstructionGraphStep materialStep:
|
||||
foreach (var entity in EnumerateNearby(user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
if (!materialStep.EntityValid(entity, out var sharedStack))
|
||||
continue;
|
||||
|
||||
// Handle welder manually since tool steps specify fuel amount needed, for some reason.
|
||||
if (toolStep.ToolQuality.HasFlag(ToolQuality.Welding))
|
||||
return slapped.TryGetComponent<WelderComponent>(out var welder)
|
||||
&& await welder.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality, toolStep.Amount);
|
||||
var stack = (StackComponent) sharedStack;
|
||||
|
||||
return await tool.UseTool(user, constructEntity, toolStep.DoAfterDelay, toolStep.ToolQuality);
|
||||
if (!stack.Split(materialStep.Amount, user.ToCoordinates(), out var newStack))
|
||||
continue;
|
||||
|
||||
default:
|
||||
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>
|
||||
if (string.IsNullOrEmpty(materialStep.Store))
|
||||
{
|
||||
{ StackType.Cable, ConstructionStepMaterial.MaterialType.Cable },
|
||||
{ StackType.Gold, ConstructionStepMaterial.MaterialType.Gold },
|
||||
{ StackType.Glass, ConstructionStepMaterial.MaterialType.Glass },
|
||||
{ StackType.Metal, ConstructionStepMaterial.MaterialType.Metal }
|
||||
if (!container.Insert(newStack))
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
constructionComponent.Stage = 1;
|
||||
var spriteComp = constructionComponent.Owner.GetComponent<SpriteComponent>();
|
||||
if(prototype.Stages[1].Icon != null)
|
||||
var newCont = ContainerManagerComponent.Ensure<Container>(name, newEntity);
|
||||
|
||||
foreach (var entity in cont.ContainedEntities.ToArray())
|
||||
{
|
||||
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
|
||||
{
|
||||
spriteComp.AddLayerWithSprite(prototype.Icon);
|
||||
var newSet = new HashSet<int> {ev.Ack};
|
||||
_beingBuilt[args.SenderSession] = newSet;
|
||||
}
|
||||
|
||||
var frame = constructionComponent.Owner;
|
||||
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
|
||||
foreach (var condition in constructionPrototype.Conditions)
|
||||
{
|
||||
/// <summary>
|
||||
/// 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)
|
||||
if (!condition.Condition(user, ev.Location, ev.Angle.GetCardinalDir()))
|
||||
{
|
||||
User = user;
|
||||
Hand = hand;
|
||||
Target = target;
|
||||
Cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A component message that is raised when an entity is trying to be deconstructed.
|
||||
/// </summary>
|
||||
public class BeginDeconstructCompMsg : ComponentMessage
|
||||
void Cleanup()
|
||||
{
|
||||
/// <summary>
|
||||
/// Entity that initiated the deconstruction.
|
||||
/// </summary>
|
||||
public IEntity User { get; }
|
||||
_beingBuilt[args.SenderSession].Remove(ev.Ack);
|
||||
}
|
||||
|
||||
/// <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="BeginDeconstructCompMsg"/>.
|
||||
/// </summary>
|
||||
/// <param name="user">Entity that initiated the deconstruction.</param>
|
||||
public BeginDeconstructCompMsg(IEntity user)
|
||||
if (user == null
|
||||
|| !ActionBlockerSystem.CanInteract(user)
|
||||
|| !user.TryGetComponent(out HandsComponent? hands) || hands.GetActiveHand == null
|
||||
|| !user.InRangeUnobstructed(ev.Location, ignoreInsideBlocker:constructionPrototype.CanBuildInImpassable))
|
||||
{
|
||||
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
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
@@ -27,7 +28,7 @@ namespace Content.Server.GameObjects.EntitySystems.DoAfter
|
||||
var cancelled = 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);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"SubFloorHide",
|
||||
"LowWall",
|
||||
"ReinforcedWall",
|
||||
"Window",
|
||||
"CharacterInfo",
|
||||
"InteractionOutline",
|
||||
"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.Linq;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -12,164 +9,69 @@ namespace Content.Shared.Construction
|
||||
[Prototype("construction")]
|
||||
public class ConstructionPrototype : IPrototype, IIndexedPrototype
|
||||
{
|
||||
private string _name;
|
||||
private string _description;
|
||||
private SpriteSpecifier _icon;
|
||||
private List<string> _keywords;
|
||||
private List<string> _categorySegments;
|
||||
private List<ConstructionStage> _stages = new List<ConstructionStage>();
|
||||
private ConstructionType _type;
|
||||
private string _id;
|
||||
private string _result;
|
||||
private string _placementMode;
|
||||
private bool _canBuildInImpassable;
|
||||
private List<IConstructionCondition> _conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Friendly name displayed in the construction GUI.
|
||||
/// </summary>
|
||||
public string Name => _name;
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// "Useful" description displayed in the construction GUI.
|
||||
/// </summary>
|
||||
public string Description => _description;
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ConstructionGraphPrototype"/> this construction will be using.
|
||||
/// </summary>
|
||||
public string Graph { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The target <see cref="ConstructionGraphNode"/> this construction will guide the user to.
|
||||
/// </summary>
|
||||
public string TargetNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The starting <see cref="ConstructionGraphNode"/> this construction will start at.
|
||||
/// </summary>
|
||||
public string StartNode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture path inside the construction GUI.
|
||||
/// </summary>
|
||||
public SpriteSpecifier Icon => _icon;
|
||||
public SpriteSpecifier Icon { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If you can start building or complete steps on impassable terrain.
|
||||
/// </summary>
|
||||
public bool CanBuildInImpassable => _canBuildInImpassable;
|
||||
public bool CanBuildInImpassable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of keywords that are used for searching.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Keywords => _keywords;
|
||||
public string Category { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The split up segments of the category.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> CategorySegments => _categorySegments;
|
||||
public ConstructionType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of stages of construction.
|
||||
/// Construction is separated into "stages" which is basically a state in a linear FSM.
|
||||
/// The stage has forward and optionally backwards "steps" which are the criteria to move around in the FSM.
|
||||
/// NOTE that the stages are mapped differently than they appear in the prototype.
|
||||
/// In the prototype, the forward step is displayed as "to move into this stage" and reverse "to move out of"
|
||||
/// Stage 0 is considered "construction not started" and last stage is considered "construction is finished".
|
||||
/// As such, neither last or 0 stage have actual stage DATA, only backward/forward steps respectively.
|
||||
/// This would be akward for a YAML prototype because it's always jagged.
|
||||
/// </summary>
|
||||
public IReadOnlyList<ConstructionStage> Stages => _stages;
|
||||
public string ID { get; private set; }
|
||||
|
||||
public ConstructionType Type => _type;
|
||||
public string PlacementMode { get; private set; }
|
||||
|
||||
public string ID => _id;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype name of the entity prototype when construction is done.
|
||||
/// </summary>
|
||||
public string Result => _result;
|
||||
|
||||
public string PlacementMode => _placementMode;
|
||||
public IReadOnlyList<IConstructionCondition> Conditions => _conditions;
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
var ser = YamlObjectSerializer.NewReader(mapping);
|
||||
_name = ser.ReadDataField<string>("name");
|
||||
Name = ser.ReadDataField<string>("name");
|
||||
|
||||
ser.DataField(ref _id, "id", string.Empty);
|
||||
ser.DataField(ref _description, "description", string.Empty);
|
||||
ser.DataField(ref _icon, "icon", SpriteSpecifier.Invalid);
|
||||
ser.DataField(ref _type, "objectType", ConstructionType.Structure);
|
||||
ser.DataField(ref _result, "result", null);
|
||||
ser.DataField(ref _placementMode, "placementMode", "PlaceFree");
|
||||
ser.DataField(ref _canBuildInImpassable, "canBuildInImpassable", false);
|
||||
|
||||
_keywords = ser.ReadDataField<List<string>>("keywords", new List<string>());
|
||||
{
|
||||
var cat = ser.ReadDataField<string>("category");
|
||||
var split = cat.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
_categorySegments = split.ToList();
|
||||
}
|
||||
|
||||
{
|
||||
SpriteSpecifier nextIcon = null;
|
||||
ConstructionStep nextBackward = null;
|
||||
|
||||
foreach (var stepMap in mapping.GetNode<YamlSequenceNode>("steps").Cast<YamlMappingNode>())
|
||||
{
|
||||
var step = ReadStepPrototype(stepMap);
|
||||
_stages.Add(new ConstructionStage(step, nextIcon, nextBackward));
|
||||
if (stepMap.TryGetNode("icon", out var node))
|
||||
{
|
||||
nextIcon = SpriteSpecifier.FromYaml(node);
|
||||
}
|
||||
|
||||
if (stepMap.TryGetNode("reverse", out YamlMappingNode revMap))
|
||||
{
|
||||
nextBackward = ReadStepPrototype(revMap);
|
||||
}
|
||||
}
|
||||
|
||||
_stages.Add(new ConstructionStage(null, nextIcon, nextBackward));
|
||||
}
|
||||
}
|
||||
|
||||
ConstructionStep ReadStepPrototype(YamlMappingNode step)
|
||||
{
|
||||
int amount = 1;
|
||||
|
||||
if (step.TryGetNode("amount", out var node))
|
||||
{
|
||||
amount = node.AsInt();
|
||||
}
|
||||
if (step.TryGetNode("material", out node))
|
||||
{
|
||||
return new ConstructionStepMaterial(
|
||||
node.AsEnum<ConstructionStepMaterial.MaterialType>(),
|
||||
amount
|
||||
);
|
||||
}
|
||||
|
||||
if (step.TryGetNode("tool", out node))
|
||||
{
|
||||
return new ConstructionStepTool(
|
||||
node.AsEnum<ToolQuality>(),
|
||||
amount
|
||||
);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Not enough data specified to determine step.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConstructionStage
|
||||
{
|
||||
/// <summary>
|
||||
/// The icon of the construction frame at this stage.
|
||||
/// </summary>
|
||||
public readonly SpriteSpecifier Icon;
|
||||
|
||||
/// <summary>
|
||||
/// The step that should be completed to move away from this stage to the next one.
|
||||
/// </summary>
|
||||
public readonly ConstructionStep Forward;
|
||||
|
||||
/// <summary>
|
||||
/// The optional step that can be completed to move away from this stage to the previous one.
|
||||
/// </summary>
|
||||
public readonly ConstructionStep Backward;
|
||||
|
||||
public ConstructionStage(ConstructionStep forward, SpriteSpecifier icon = null, ConstructionStep backward = null)
|
||||
{
|
||||
Icon = icon;
|
||||
Forward = forward;
|
||||
Backward = backward;
|
||||
ser.DataField(this, x => x.ID, "id", string.Empty);
|
||||
ser.DataField(this, x => x.Graph, "graph", string.Empty);
|
||||
ser.DataField(this, x => x.TargetNode, "targetNode", string.Empty);
|
||||
ser.DataField(this, x => x.StartNode, "startNode", string.Empty);
|
||||
ser.DataField(this, x => x.Description, "description", string.Empty);
|
||||
ser.DataField(this, x => x.Icon, "icon", SpriteSpecifier.Invalid);
|
||||
ser.DataField(this, x => x.Type, "objectType", ConstructionType.Structure);
|
||||
ser.DataField(this, x => x.PlacementMode, "placementMode", "PlaceFree");
|
||||
ser.DataField(this, x => x.CanBuildInImpassable, "canBuildInImpassable", false);
|
||||
ser.DataField(this, x => x.Category, "category", string.Empty);
|
||||
ser.DataField(ref _conditions, "conditions", new List<IConstructionCondition>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,47 +80,5 @@ namespace Content.Shared.Construction
|
||||
Structure,
|
||||
Item,
|
||||
}
|
||||
|
||||
public abstract class ConstructionStep
|
||||
{
|
||||
public readonly int Amount;
|
||||
public readonly float DoAfterDelay;
|
||||
|
||||
protected ConstructionStep(int amount, float doAfterDelay = 0f)
|
||||
{
|
||||
Amount = amount;
|
||||
DoAfterDelay = doAfterDelay;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConstructionStepTool : ConstructionStep
|
||||
{
|
||||
public readonly ToolQuality ToolQuality;
|
||||
|
||||
public ConstructionStepTool(ToolQuality toolQuality, int amount) : base(amount)
|
||||
{
|
||||
ToolQuality = toolQuality;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConstructionStepMaterial : ConstructionStep
|
||||
{
|
||||
public readonly MaterialType Material;
|
||||
|
||||
public ConstructionStepMaterial(MaterialType material, int amount) : base(amount)
|
||||
{
|
||||
Material = material;
|
||||
}
|
||||
|
||||
|
||||
public enum MaterialType
|
||||
{
|
||||
Metal,
|
||||
Glass,
|
||||
Cable,
|
||||
Gold,
|
||||
Phoron,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public abstract class EntityInsertConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
public string Store { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Store, "store", string.Empty);
|
||||
}
|
||||
|
||||
public abstract bool EntityValid(IEntity entity);
|
||||
}
|
||||
}
|
||||
12
Content.Shared/Construction/IConstructionCondition.cs
Normal file
12
Content.Shared/Construction/IConstructionCondition.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public interface IConstructionCondition : IExposeData
|
||||
{
|
||||
bool Condition(IEntity user, EntityCoordinates location, Direction direction);
|
||||
}
|
||||
}
|
||||
13
Content.Shared/Construction/IEdgeCondition.cs
Normal file
13
Content.Shared/Construction/IEdgeCondition.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public interface IEdgeCondition : IExposeData
|
||||
{
|
||||
Task<bool> Condition(IEntity entity);
|
||||
void DoExamine(IEntity entity, FormattedMessage message, bool inExamineRange) { }
|
||||
}
|
||||
}
|
||||
13
Content.Shared/Construction/IGraphAction.cs
Normal file
13
Content.Shared/Construction/IGraphAction.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public interface IGraphAction : IExposeData
|
||||
{
|
||||
Task PerformAction(IEntity entity, IEntity? user);
|
||||
}
|
||||
}
|
||||
48
Content.Shared/Construction/MaterialConstructionGraphStep.cs
Normal file
48
Content.Shared/Construction/MaterialConstructionGraphStep.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Materials;
|
||||
using Content.Shared.Materials;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class MaterialConstructionGraphStep : EntityInsertConstructionGraphStep
|
||||
{
|
||||
// TODO: Make this use the material system.
|
||||
// TODO TODO: Make the material system not shit.
|
||||
public StackType Material { get; private set; }
|
||||
public int Amount { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Material, "material", StackType.Metal);
|
||||
serializer.DataField(this, x => x.Amount, "amount", 1);
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("Next, add [color=yellow]{0}x[/color] [color=cyan]{1}[/color].", Amount, Material));
|
||||
}
|
||||
|
||||
public override bool EntityValid(IEntity entity)
|
||||
{
|
||||
return entity.TryGetComponent(out SharedStackComponent? stack) && stack.StackType.Equals(Material);
|
||||
}
|
||||
|
||||
public bool EntityValid(IEntity entity, [NotNullWhen(true)] out SharedStackComponent? stack)
|
||||
{
|
||||
if(entity.TryGetComponent(out SharedStackComponent? otherStack) && otherStack.StackType.Equals(Material))
|
||||
stack = otherStack;
|
||||
else
|
||||
stack = null;
|
||||
|
||||
return stack != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Content.Shared/Construction/NestedConstructionGraphStep.cs
Normal file
41
Content.Shared/Construction/NestedConstructionGraphStep.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class NestedConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
public List<List<ConstructionGraphStep>> Steps { get; private set; } = new List<List<ConstructionGraphStep>>();
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
if (!mapping.TryGetNode("steps", out YamlSequenceNode steps)) return;
|
||||
|
||||
foreach (var node in steps)
|
||||
{
|
||||
var sequence = (YamlSequenceNode) node;
|
||||
var list = new List<ConstructionGraphStep>();
|
||||
|
||||
foreach (var innerNode in sequence)
|
||||
{
|
||||
var stepNode = (YamlMappingNode) innerNode;
|
||||
var step = ConstructionGraphEdge.LoadStep(stepNode);
|
||||
|
||||
if(step is NestedConstructionGraphStep)
|
||||
throw new InvalidDataException("Can't have nested construction steps inside nested construction steps!");
|
||||
|
||||
list.Add(step);
|
||||
}
|
||||
|
||||
Steps.Add(list);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class PrototypeConstructionGraphStep : ArbitraryInsertConstructionGraphStep
|
||||
{
|
||||
public string Prototype { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Prototype, "prototype", string.Empty);
|
||||
}
|
||||
|
||||
public override bool EntityValid(IEntity entity)
|
||||
{
|
||||
return entity.Prototype?.ID == Prototype;
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(string.IsNullOrEmpty(Name)
|
||||
? Loc.GetString("Next, insert {0}", Prototype) // Terrible.
|
||||
: Loc.GetString("Next, insert {0}", Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Content.Shared/Construction/ToolConstructionGraphStep.cs
Normal file
35
Content.Shared/Construction/ToolConstructionGraphStep.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Construction
|
||||
{
|
||||
public class ToolConstructionGraphStep : ConstructionGraphStep
|
||||
{
|
||||
public ToolQuality Tool { get; private set; }
|
||||
public float Fuel { get; private set; }
|
||||
public string ExamineOverride { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Tool, "tool", ToolQuality.None);
|
||||
serializer.DataField(this, x => x.Fuel, "fuel", 10f); // Default fuel cost.
|
||||
serializer.DataField(this, x => x.ExamineOverride, "examine", string.Empty);
|
||||
}
|
||||
|
||||
public override void DoExamine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ExamineOverride))
|
||||
{
|
||||
message.AddMarkup(Loc.GetString(ExamineOverride));
|
||||
return;
|
||||
}
|
||||
|
||||
message.AddMarkup(Loc.GetString($"Next, use a [color=cyan]{Tool.GetToolName()}[/color]."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.DamageContainer;
|
||||
using Content.Shared.Damage.ResistanceSet;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Mono.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -157,6 +158,9 @@ namespace Content.Shared.GameObjects.Components.Damage
|
||||
{
|
||||
var writeFlags = new List<DamageFlag>();
|
||||
|
||||
if (Flags == DamageFlag.None)
|
||||
return writeFlags;
|
||||
|
||||
foreach (var flag in (DamageFlag[]) Enum.GetValues(typeof(DamageFlag)))
|
||||
{
|
||||
if ((Flags & flag) == flag)
|
||||
|
||||
@@ -16,6 +16,23 @@ namespace Content.Shared.GameObjects.Components.Interactable
|
||||
Multitool = 1 << 5,
|
||||
}
|
||||
|
||||
public static class ToolQualityHelpers
|
||||
{
|
||||
public static string GetToolName(this ToolQuality quality)
|
||||
{
|
||||
return quality switch
|
||||
{
|
||||
ToolQuality.Anchoring => "Wrench",
|
||||
ToolQuality.Prying => "Crowbar",
|
||||
ToolQuality.Screwing => "Screwdriver",
|
||||
ToolQuality.Cutting => "Wirecutters",
|
||||
ToolQuality.Welding => "Welding tool",
|
||||
ToolQuality.Multitool => "Multitool",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class SharedToolComponent : Component
|
||||
{
|
||||
public override string Name => "Tool";
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum ReinforcedWallVisuals
|
||||
{
|
||||
DeconstructionStage,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class SharedCanBuildWindowOnTopComponent : Component
|
||||
{
|
||||
public override string Name => "CanBuildWindowOnTop";
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,7 @@ namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
Metal,
|
||||
Glass,
|
||||
Plasteel,
|
||||
Cable,
|
||||
Gold,
|
||||
Phoron,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components
|
||||
{
|
||||
public class SharedWindowComponent : Component
|
||||
{
|
||||
public override string Name => "Window";
|
||||
}
|
||||
}
|
||||
@@ -76,28 +76,5 @@ namespace Content.Shared.GameObjects.EntitySystems
|
||||
GhostId = ghostId;
|
||||
}
|
||||
}
|
||||
|
||||
public void DoExamine(FormattedMessage message, ConstructionPrototype prototype, int stage, bool inDetailRange)
|
||||
{
|
||||
var stages = prototype.Stages;
|
||||
if (stage >= 0 && stage < stages.Count)
|
||||
{
|
||||
var curStage = stages[stage];
|
||||
if (curStage.Backward != null && curStage.Backward is ConstructionStepTool)
|
||||
{
|
||||
var backward = (ConstructionStepTool) curStage.Backward;
|
||||
message.AddText(Loc.GetString("To deconstruct: {0}x {1} Tool", backward.Amount, backward.ToolQuality));
|
||||
}
|
||||
if (curStage.Forward != null && curStage.Forward is ConstructionStepMaterial)
|
||||
{
|
||||
if (curStage.Backward != null)
|
||||
{
|
||||
message.AddText("\n");
|
||||
}
|
||||
var forward = (ConstructionStepMaterial) curStage.Forward;
|
||||
message.AddText(Loc.GetString("To construct: {0}x {1}", forward.Amount, forward.Material));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
@@ -29,18 +28,18 @@ namespace Content.Shared.Maps
|
||||
/// <summary>
|
||||
/// Attempts to get the turf at map indices with grid id or null if no such turf is found.
|
||||
/// </summary>
|
||||
public static TileRef? GetTileRef(this MapIndices mapIndices, GridId gridId, IMapManager? mapManager = null)
|
||||
public static TileRef GetTileRef(this MapIndices mapIndices, GridId gridId, IMapManager? mapManager = null)
|
||||
{
|
||||
if (!gridId.IsValid())
|
||||
return null;
|
||||
return default;
|
||||
|
||||
mapManager ??= IoCManager.Resolve<IMapManager>();
|
||||
|
||||
if (!mapManager.TryGetGrid(gridId, out var grid))
|
||||
return null;
|
||||
return default;
|
||||
|
||||
if (!grid.TryGetTileRef(mapIndices, out var tile))
|
||||
return null;
|
||||
return default;
|
||||
|
||||
return tile;
|
||||
}
|
||||
@@ -149,12 +148,7 @@ namespace Content.Shared.Maps
|
||||
/// </summary>
|
||||
public static IEnumerable<IEntity> GetEntitiesInTile(this MapIndices indices, GridId gridId, bool approximate = false, IEntityManager? entityManager = null)
|
||||
{
|
||||
var turf = indices.GetTileRef(gridId);
|
||||
|
||||
if (turf == null)
|
||||
return Enumerable.Empty<IEntity>();
|
||||
|
||||
return GetEntitiesInTile(turf.Value, approximate, entityManager);
|
||||
return GetEntitiesInTile(indices.GetTileRef(gridId), approximate, entityManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,7 +188,9 @@ namespace Content.Shared.Maps
|
||||
{
|
||||
var map = IoCManager.Resolve<IMapManager>();
|
||||
var tileGrid = map.GetGrid(turf.GridIndex);
|
||||
var tileBox = Box2.UnitCentered.Scale(tileGrid.TileSize);
|
||||
|
||||
// This is scaled to 90 % so it doesn't encompass walls on other tiles.
|
||||
var tileBox = Box2.UnitCentered.Scale(tileGrid.TileSize).Scale(0.9f);
|
||||
return tileBox.Translated(tileGrid.GridTileToWorldPos(turf.GridIndices));
|
||||
}
|
||||
|
||||
|
||||
@@ -101,13 +101,13 @@ namespace Content.Shared.Materials
|
||||
// All default material params are initialized to 1 because
|
||||
// I'm too lazy to figure out for which that's necessary to prevent divisions by zero in case left out.
|
||||
serializer.DataField(ref _density, "density", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _electricResistivity, "electricresistivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _thermalConductivity, "thermalconductivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _specificHeat, "specificheat", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _electricResistivity, "electricResistivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _thermalConductivity, "thermalConductivity", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _specificHeat, "specificHeat", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _durability, "durability", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _hardness, "hardness", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _sharpDamage, "sharpdamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _bluntDamage, "bluntdamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _sharpDamage, "sharpDamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _bluntDamage, "bluntDamage", 1, alwaysWrite: true);
|
||||
serializer.DataField(ref _icon, "icon", SpriteSpecifier.Invalid, alwaysWrite: true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,9 +122,6 @@ entities:
|
||||
pos: -0.5,-0.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 2
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -132,9 +129,6 @@ entities:
|
||||
pos: -0.5,-1.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 3
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -142,9 +136,6 @@ entities:
|
||||
pos: -0.5,-2.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 4
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -152,9 +143,6 @@ entities:
|
||||
pos: 0.5,-2.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 5
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -162,9 +150,6 @@ entities:
|
||||
pos: 1.5,-2.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 6
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -172,9 +157,6 @@ entities:
|
||||
pos: 1.5,-1.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 7
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -182,9 +164,6 @@ entities:
|
||||
pos: 1.5,-0.5
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- flags:
|
||||
- None
|
||||
type: Destructible
|
||||
- uid: 8
|
||||
type: reinforced_wall
|
||||
components:
|
||||
@@ -192,7 +171,4 @@ entities:
|
||||
pos: 0.5,-0.5
|
||||
rot: -1.5707963267948966 rad
|
||||
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:
|
||||
- type: StorageFill
|
||||
contents:
|
||||
- name: CableStack1
|
||||
- name: ApcExtensionCableStack1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
- type: latheRecipe
|
||||
id: MetalStack
|
||||
icon: Objects/Materials/materials.rsi/sheet_metal.png
|
||||
icon: Objects/Materials/sheets.rsi/metal.png
|
||||
result: SteelSheet1
|
||||
completetime: 500
|
||||
materials:
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
- type: latheRecipe
|
||||
id: GlassStack
|
||||
icon: Objects/Materials/materials.rsi/sheet_glass.png
|
||||
icon: Objects/Materials/sheets.rsi/glass.png
|
||||
result: GlassSheet1
|
||||
completetime: 500
|
||||
materials:
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
- type: latheRecipe
|
||||
id: CableStack
|
||||
name: cable coil
|
||||
icon: Objects/Tools/cable_coil.png
|
||||
result: CableStack1
|
||||
icon: /Textures/Objects/Tools/cables.rsi/coil-30.png
|
||||
result: ApcExtensionCableStack1
|
||||
completetime: 500
|
||||
materials:
|
||||
steel: 50
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
animationDuration: 1.1
|
||||
spriteName: youtool
|
||||
startingInventory:
|
||||
CableStack1: 10
|
||||
ApcExtensionCableStack1: 10
|
||||
Crowbar: 5
|
||||
Welder: 3
|
||||
Wirecutter: 5
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
name: "glass crate"
|
||||
id: cargo.glass
|
||||
description: "50 sheets of glass."
|
||||
icon: Objects/Materials/sheet_glass.png
|
||||
icon: Objects/Materials/sheets.rsi/glass.png
|
||||
product: CrateGlass
|
||||
cost: 50
|
||||
category: Engineering
|
||||
@@ -126,7 +126,7 @@
|
||||
name: "cable crate"
|
||||
id: cargo.cable
|
||||
description: "50 coils of cable."
|
||||
icon: Objects/Tools/cable_coil.png
|
||||
icon: /Textures/Objects/Tools/cables.rsi/coil-30.png
|
||||
product: CrateCable
|
||||
cost: 50
|
||||
category: Engineering
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- type: Destructible
|
||||
deadThreshold: 30
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
spawnOnDestroy: MetalSheet1
|
||||
spawnOnDestroy: SteelSheet1
|
||||
resistances: metallicResistances
|
||||
|
||||
- type: entity
|
||||
@@ -47,5 +47,5 @@
|
||||
- type: Destructible
|
||||
deadThreshold: 30
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
spawnOnDestroy: MetalSheet1
|
||||
spawnOnDestroy: SteelSheet1
|
||||
resistances: metallicResistances
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
- type: Destructible
|
||||
maxHP: 500
|
||||
resistances: metallicResistances
|
||||
spawnondestroy: AMEPart
|
||||
spawnOnDestroy: AMEPart
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Airtight
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
@@ -67,7 +66,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
@@ -75,7 +73,7 @@
|
||||
offset: Center
|
||||
- type: Destructible
|
||||
deadThreshold: 25
|
||||
spawnOnDestroy: MetalSheet1
|
||||
spawnOnDestroy: SteelSheet1
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.ChemMasterUiKey.Key
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
- type: entity
|
||||
id: ComputerBase
|
||||
name: computer
|
||||
abstract: true
|
||||
id: ComputerFrame
|
||||
name: computer frame
|
||||
components:
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Collidable
|
||||
IsScrapingFloor: true
|
||||
mass: 25
|
||||
anchored: false
|
||||
shapes:
|
||||
- !type:PhysShapeAabb
|
||||
bounds: "-0.5,-0.25,0.5,0.25"
|
||||
@@ -22,10 +17,77 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- 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: PowerReceiver
|
||||
- type: Anchorable
|
||||
- type: BreakableConstruction
|
||||
node: monitorBroken
|
||||
|
||||
- type: Sprite
|
||||
sprite: Constructible/Power/computers.rsi
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
@@ -68,7 +67,7 @@
|
||||
- Screwdriver
|
||||
- Welder
|
||||
- Wrench
|
||||
- CableStack1
|
||||
- CableStack
|
||||
- Crowbar
|
||||
- Multitool
|
||||
- type: Appearance
|
||||
@@ -116,7 +115,7 @@
|
||||
- Screwdriver
|
||||
- Welder
|
||||
- Wrench
|
||||
- CableStack1
|
||||
- CableStack
|
||||
- Crowbar
|
||||
- Multitool
|
||||
- type: UserInterface
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
sprite: Constructible/Power/microwave.rsi
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
- Impassable
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: true
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
wireType: HighVoltage
|
||||
- type: Destructible
|
||||
resistances: metallicResistances
|
||||
spawnondestroy: HVWireStack1
|
||||
spawnOnDestroy: HVWireStack1
|
||||
|
||||
- type: entity
|
||||
parent: WireBase
|
||||
@@ -69,7 +69,7 @@
|
||||
wireType: MediumVoltage
|
||||
- type: Destructible
|
||||
resistances: metallicResistances
|
||||
spawnondestroy: MVWireStack1
|
||||
spawnOnDestroy: MVWireStack1
|
||||
|
||||
- type: entity
|
||||
parent: WireBase
|
||||
@@ -98,4 +98,4 @@
|
||||
wireType: Apc
|
||||
- type: Destructible
|
||||
resistances: metallicResistances
|
||||
spawnondestroy: ApcExtensionCableStack1
|
||||
spawnOnDestroy: ApcExtensionCableStack1
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
- SmallImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: false
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
- SmallImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 15
|
||||
Anchored: false
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
- Opaque
|
||||
- MobImpassable
|
||||
- SmallImpassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 25
|
||||
anchored: false
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Anchorable
|
||||
- type: Construction
|
||||
graph: girder
|
||||
node: girder
|
||||
- type: Sprite
|
||||
sprite: Constructible/Structures/Walls/solid.rsi
|
||||
state: wall_girder
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: RCDDeconstructWhitelist
|
||||
- type: CanBuildWindowOnTop
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
@@ -24,7 +25,8 @@
|
||||
layer:
|
||||
- VaultImpassable
|
||||
- SmallImpassable
|
||||
- type: Destructible
|
||||
- type: BreakableConstruction
|
||||
node: start
|
||||
deadThreshold: 100
|
||||
resistances: metallicResistances
|
||||
- type: SnapGrid
|
||||
@@ -33,6 +35,9 @@
|
||||
- type: LowWall
|
||||
key: walls
|
||||
base: metal_
|
||||
- type: Construction
|
||||
graph: lowWall
|
||||
node: lowWall
|
||||
|
||||
- type: entity
|
||||
id: LowWallOverlay
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
- MobImpassable
|
||||
- VaultImpassable
|
||||
- SmallImpassable
|
||||
- type: Destructible
|
||||
deadThreshold: 500
|
||||
spawnOnDestroy: Girder
|
||||
resistances: metallicResistances
|
||||
- type: Occluder
|
||||
sizeX: 32
|
||||
sizeY: 32
|
||||
@@ -239,14 +235,20 @@
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Walls/solid.rsi
|
||||
state: rgeneric
|
||||
- type: Destructible
|
||||
- type: Construction
|
||||
graph: girder
|
||||
node: reinforcedWall
|
||||
- type: BreakableConstruction
|
||||
deadThreshold: 600
|
||||
spawnOnDestroy: Girder
|
||||
node: girder
|
||||
resistances: metallicResistances
|
||||
- type: ReinforcedWall
|
||||
key: walls
|
||||
base: solid
|
||||
reinforcedBase: reinf_over
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: ReinforcedWallVisualizer
|
||||
|
||||
# Riveting
|
||||
- type: entity
|
||||
@@ -308,11 +310,14 @@
|
||||
- type: Sprite
|
||||
color: "#889192"
|
||||
sprite: Constructible/Structures/Walls/solid.rsi
|
||||
- type: Construction
|
||||
graph: girder
|
||||
node: wall
|
||||
- type: Icon
|
||||
sprite: Constructible/Structures/Walls/solid.rsi
|
||||
- type: Destructible
|
||||
- type: BreakableConstruction
|
||||
deadThreshold: 300
|
||||
spawnOnDestroy: Girder
|
||||
node: girder
|
||||
destroySound: /Audio/Effects/metalbreak.ogg
|
||||
resistances: metallicResistances
|
||||
- type: IconSmooth
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
- type: Airtight
|
||||
- type: Window
|
||||
base: window
|
||||
- type: Construction
|
||||
graph: window
|
||||
node: window
|
||||
|
||||
- type: entity
|
||||
id: ReinforcedWindow
|
||||
@@ -50,6 +53,9 @@
|
||||
resistances: metallicResistances
|
||||
- type: Window
|
||||
base: rwindow
|
||||
- type: Construction
|
||||
graph: window
|
||||
node: reinforcedWindow
|
||||
|
||||
- type: entity
|
||||
id: PhoronWindow
|
||||
@@ -66,3 +72,6 @@
|
||||
resistances: metallicResistances
|
||||
- type: Window
|
||||
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
|
||||
|
||||
- type: entity
|
||||
id: CableStack
|
||||
abstract: true
|
||||
parent: BaseItem
|
||||
id: CableStack1
|
||||
name: cable stack 1
|
||||
name: cable stack
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Stack
|
||||
@@ -20,9 +20,10 @@
|
||||
- type: Clickable
|
||||
|
||||
- type: entity
|
||||
parent: CableStack1
|
||||
id: HVWireStack
|
||||
parent: CableStack
|
||||
name: HV Wire Coil
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Sprite
|
||||
state: coilhv-30
|
||||
@@ -33,34 +34,6 @@
|
||||
wirePrototypeID: HVWire
|
||||
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
|
||||
parent: HVWireStack
|
||||
id: HVWireStack1
|
||||
@@ -74,16 +47,19 @@
|
||||
count: 1
|
||||
|
||||
- type: entity
|
||||
parent: MVWireStack
|
||||
id: MVWireStack1
|
||||
suffix: 1
|
||||
parent: CableStack
|
||||
id: ApcExtensionCableStack
|
||||
name: Apc Extension Cable Coil
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Sprite
|
||||
state: coilmv-10
|
||||
state: coillv-30
|
||||
- type: Item
|
||||
size: 3
|
||||
- type: Stack
|
||||
count: 1
|
||||
size: 10
|
||||
HeldPrefix: coillv
|
||||
- type: WirePlacer
|
||||
wirePrototypeID: ApcExtensionCable
|
||||
blockingWireType: Apc
|
||||
|
||||
- type: entity
|
||||
parent: ApcExtensionCableStack
|
||||
@@ -96,3 +72,30 @@
|
||||
size: 3
|
||||
- type: Stack
|
||||
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"
|
||||
mask: [Impassable]
|
||||
layer: [Clickable]
|
||||
IsScrapingFloor: true
|
||||
|
||||
- type: entity
|
||||
name: recharger
|
||||
@@ -143,7 +142,6 @@
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
mask: [Impassable]
|
||||
layer: [Clickable]
|
||||
IsScrapingFloor: true
|
||||
|
||||
- type: entity
|
||||
name: wall recharger
|
||||
@@ -172,4 +170,3 @@
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
mask: [Impassable]
|
||||
layer: [Clickable]
|
||||
IsScrapingFloor: true
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
- Opaque
|
||||
layer:
|
||||
- Opaque
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 5
|
||||
anchored: false
|
||||
@@ -74,7 +73,6 @@
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
mask:
|
||||
- Impassable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 5
|
||||
anchored: false
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
sprite: Objects/Weapons/Melee/spear.rsi
|
||||
state: spear
|
||||
|
||||
|
||||
|
||||
- type: MeleeWeapon
|
||||
range: 1.5
|
||||
arcwidth: 0
|
||||
@@ -19,6 +17,10 @@
|
||||
sprite: Objects/Weapons/Melee/spear.rsi
|
||||
prefix: inhand
|
||||
|
||||
- type: Construction
|
||||
graph: spear
|
||||
node: spear
|
||||
|
||||
- type: ItemCooldown
|
||||
|
||||
- type: MeleeWeaponAnimation
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
name: steel sheet
|
||||
id: MetalStack
|
||||
parent: MaterialStack
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Material
|
||||
materials:
|
||||
@@ -28,16 +29,19 @@
|
||||
|
||||
- type: entity
|
||||
id: SteelSheet1
|
||||
name: steel sheet 1
|
||||
name: steel sheet
|
||||
parent: MetalStack
|
||||
suffix: 1
|
||||
components:
|
||||
- type: Stack
|
||||
stacktype: enum.StackType.Metal
|
||||
count: 1
|
||||
|
||||
- type: entity
|
||||
name: glass sheet
|
||||
id: GlassStack
|
||||
parent: MaterialStack
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Material
|
||||
materials:
|
||||
@@ -54,16 +58,48 @@
|
||||
|
||||
- type: entity
|
||||
id: GlassSheet1
|
||||
name: glass sheet 1
|
||||
name: glass sheet
|
||||
parent: GlassStack
|
||||
suffix: 1
|
||||
components:
|
||||
- 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
|
||||
|
||||
- type: entity
|
||||
name: gold bar
|
||||
id: GoldStack
|
||||
parent: MaterialStack
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Material
|
||||
materials:
|
||||
@@ -79,6 +115,7 @@
|
||||
id: GoldStack1
|
||||
name: gold bar 1
|
||||
parent: GoldStack
|
||||
suffix: 1
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Materials/materials.rsi
|
||||
@@ -99,6 +136,7 @@
|
||||
name: phoron sheet
|
||||
id: PhoronStack
|
||||
parent: MaterialStack
|
||||
suffix: Full
|
||||
components:
|
||||
- type: Material
|
||||
materials:
|
||||
@@ -115,8 +153,9 @@
|
||||
|
||||
- type: entity
|
||||
id: PhoronStack1
|
||||
name: phoron sheet 1
|
||||
name: phoron sheet
|
||||
parent: PhoronStack
|
||||
suffix: 1
|
||||
components:
|
||||
- type: Stack
|
||||
count: 1
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
bounds: "-0.25,-0.25,0.25,0.25"
|
||||
layer:
|
||||
- Clickable
|
||||
IsScrapingFloor: true
|
||||
- type: Physics
|
||||
mass: 5
|
||||
- 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