Construction System. (#87)
* Construction WiP * Construction kinda works! * Lots more construction work. * It mostly works!
This commit is contained in:
committed by
GitHub
parent
f051078c79
commit
d7074bf74f
56
Content.Client/Construction/ConstructionButton.cs
Normal file
56
Content.Client/Construction/ConstructionButton.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Content.Client.GameObjects.Components.Construction;
|
||||
using SS14.Client.UserInterface;
|
||||
using SS14.Client.UserInterface.Controls;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
public class ConstructionButton : Button
|
||||
{
|
||||
protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Construction/ConstructionButton.tscn");
|
||||
|
||||
public ConstructorComponent Owner
|
||||
{
|
||||
get => Menu.Owner;
|
||||
set => Menu.Owner = value;
|
||||
}
|
||||
ConstructionMenu Menu;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
OnPressed += IWasPressed;
|
||||
Menu = new ConstructionMenu();
|
||||
Menu.AddToScreen();
|
||||
}
|
||||
|
||||
void IWasPressed(ButtonEventArgs args)
|
||||
{
|
||||
Menu.Open();
|
||||
}
|
||||
|
||||
public void AddToScreen()
|
||||
{
|
||||
UserInterfaceManager.StateRoot.AddChild(this);
|
||||
}
|
||||
|
||||
public void RemoveFromScreen()
|
||||
{
|
||||
if (Parent != null)
|
||||
{
|
||||
Parent.RemoveChild(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Menu.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
344
Content.Client/Construction/ConstructionMenu.cs
Normal file
344
Content.Client/Construction/ConstructionMenu.cs
Normal file
@@ -0,0 +1,344 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components.Construction;
|
||||
using Content.Shared.Construction;
|
||||
using SS14.Client.GameObjects;
|
||||
using SS14.Client.Graphics;
|
||||
using SS14.Client.Interfaces.GameObjects;
|
||||
using SS14.Client.Interfaces.Placement;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Client.Placement;
|
||||
using SS14.Client.ResourceManagement;
|
||||
using SS14.Client.UserInterface;
|
||||
using SS14.Client.UserInterface.Controls;
|
||||
using SS14.Client.UserInterface.CustomControls;
|
||||
using SS14.Client.Utility;
|
||||
using SS14.Shared.Enums;
|
||||
using SS14.Shared.Interfaces.GameObjects.Components;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Maths;
|
||||
using SS14.Shared.Prototypes;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
public class ConstructionMenu : SS14Window
|
||||
{
|
||||
protected override ResourcePath ScenePath => new ResourcePath("/Scenes/Construction/ConstructionMenu.tscn");
|
||||
|
||||
#pragma warning disable CS0649
|
||||
[Dependency]
|
||||
readonly IPrototypeManager PrototypeManager;
|
||||
[Dependency]
|
||||
readonly IResourceCache ResourceCache;
|
||||
#pragma warning restore
|
||||
|
||||
public ConstructorComponent Owner { get; set; }
|
||||
Button BuildButton;
|
||||
Button EraseButton;
|
||||
LineEdit SearchBar;
|
||||
Tree RecipeList;
|
||||
TextureRect InfoIcon;
|
||||
Label InfoLabel;
|
||||
ItemList StepList;
|
||||
|
||||
CategoryNode RootCategory;
|
||||
// This list is flattened in such a way that the top most deepest category is first.
|
||||
List<CategoryNode> FlattenedCategories;
|
||||
PlacementManager Placement;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
IoCManager.InjectDependencies(this);
|
||||
Placement = (PlacementManager)IoCManager.Resolve<IPlacementManager>();
|
||||
Placement.PlacementCanceled += OnPlacementCanceled;
|
||||
|
||||
HideOnClose = true;
|
||||
Title = "Construction";
|
||||
Visible = false;
|
||||
var split = Contents.GetChild("HSplitContainer");
|
||||
var rightSide = split.GetChild("Guide");
|
||||
var info = rightSide.GetChild("Info");
|
||||
InfoIcon = info.GetChild<TextureRect>("TextureRect");
|
||||
InfoLabel = info.GetChild<Label>("Label");
|
||||
StepList = rightSide.GetChild<ItemList>("StepsList");
|
||||
var buttons = rightSide.GetChild("Buttons");
|
||||
BuildButton = buttons.GetChild<Button>("BuildButton");
|
||||
BuildButton.OnPressed += OnBuildPressed;
|
||||
EraseButton = buttons.GetChild<Button>("EraseButton");
|
||||
EraseButton.OnToggled += OnEraseToggled;
|
||||
|
||||
var leftSide = split.GetChild("Recipes");
|
||||
SearchBar = leftSide.GetChild<LineEdit>("Search");
|
||||
SearchBar.OnTextChanged += OnTextEntered;
|
||||
RecipeList = leftSide.GetChild<Tree>("Tree");
|
||||
RecipeList.OnItemSelected += OnItemSelected;
|
||||
|
||||
PopulatePrototypeList();
|
||||
PopulateTree();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Placement.PlacementCanceled -= OnPlacementCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
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/sheet_metal.png");
|
||||
text = $"Metal x{mat.Amount}";
|
||||
break;
|
||||
case ConstructionStepMaterial.MaterialType.Glass:
|
||||
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/sheet_glass.png");
|
||||
text = $"Glass x{mat.Amount}";
|
||||
break;
|
||||
case ConstructionStepMaterial.MaterialType.Cable:
|
||||
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/cable_coil.png");
|
||||
text = $"Cable Coil x{mat.Amount}";
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
break;
|
||||
case ConstructionStepTool tool:
|
||||
switch (tool.Tool)
|
||||
{
|
||||
case ConstructionStepTool.ToolType.Wrench:
|
||||
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/wrench.png");
|
||||
text = "Wrench";
|
||||
break;
|
||||
case ConstructionStepTool.ToolType.Crowbar:
|
||||
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/crowbar.png");
|
||||
text = "Crowbar";
|
||||
break;
|
||||
case ConstructionStepTool.ToolType.Screwdriver:
|
||||
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/screwdriver.png");
|
||||
text = "Screwdriver";
|
||||
break;
|
||||
case ConstructionStepTool.ToolType.Welder:
|
||||
icon = ResourceCache.GetResource<RSIResource>("/Textures/Objects/tools.rsi").RSI["welder"].Frame0;
|
||||
text = $"Welding tool ({tool.Amount} fuel)";
|
||||
break;
|
||||
case ConstructionStepTool.ToolType.Wirecutters:
|
||||
icon = ResourceCache.GetResource<TextureResource>("/Textures/Objects/wirecutter.png");
|
||||
text = "Wirecutters";
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
StepList.AddItem(text, icon, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnTextEntered(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
var str = args.Text;
|
||||
PopulateTree(string.IsNullOrWhiteSpace(str) ? null : str.ToLowerInvariant());
|
||||
}
|
||||
|
||||
void OnBuildPressed(Button.ButtonEventArgs args)
|
||||
{
|
||||
var prototype = (ConstructionPrototype)RecipeList.Selected.Metadata;
|
||||
if (prototype == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (prototype.Type != ConstructionType.Structure)
|
||||
{
|
||||
// In-hand attackby doesn't exist so this is the best alternative.
|
||||
var loc = Owner.Owner.GetComponent<ITransformComponent>().LocalPosition;
|
||||
Owner.SpawnGhost(prototype, loc, Direction.North);
|
||||
return;
|
||||
}
|
||||
|
||||
var hijack = new ConstructionPlacementHijack(prototype, Owner);
|
||||
var info = new PlacementInformation
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = prototype.PlacementMode,
|
||||
};
|
||||
|
||||
|
||||
Placement.BeginHijackedPlacing(info, hijack);
|
||||
}
|
||||
|
||||
private void OnEraseToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
var hijack = new ConstructionPlacementHijack(null, Owner);
|
||||
Placement.ToggleEraserHijacked(hijack);
|
||||
}
|
||||
|
||||
void PopulatePrototypeList()
|
||||
{
|
||||
RootCategory = new CategoryNode("", null);
|
||||
int 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);
|
||||
}
|
||||
|
||||
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.SetText(0, node.Name);
|
||||
item.SetSelectable(0, 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) != -1)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var keyw in prototype.Keywords.Concat(prototype.CategorySegments))
|
||||
{
|
||||
// TODO: don't run ToLowerInvariant() constantly.
|
||||
if (keyw.ToLowerInvariant().IndexOf(searchTerm) != -1)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var subItem = RecipeList.CreateItem(ItemForNode(node));
|
||||
subItem.SetText(0, prototype.Name);
|
||||
subItem.Metadata = prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlacementCanceled(object sender, EventArgs e)
|
||||
{
|
||||
EraseButton.Pressed = false;
|
||||
}
|
||||
|
||||
private static int ComparePrototype(ConstructionPrototype x, ConstructionPrototype y)
|
||||
{
|
||||
return x.Name.CompareTo(y.Name);
|
||||
}
|
||||
|
||||
class CategoryNode
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly CategoryNode Parent;
|
||||
public SortedDictionary<string, CategoryNode> ChildCategories = new SortedDictionary<string, CategoryNode>();
|
||||
public List<ConstructionPrototype> Prototypes = new List<ConstructionPrototype>();
|
||||
public int FlattenedIndex = -1;
|
||||
|
||||
public CategoryNode(string name, CategoryNode parent)
|
||||
{
|
||||
Name = name;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Content.Client/Construction/ConstructionPlacementHijack.cs
Normal file
62
Content.Client/Construction/ConstructionPlacementHijack.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Content.Client.GameObjects.Components.Construction;
|
||||
using Content.Shared.Construction;
|
||||
using SS14.Client.Graphics;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Client.Placement;
|
||||
using SS14.Client.ResourceManagement;
|
||||
using SS14.Client.Utility;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Map;
|
||||
using SS14.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
public class ConstructionPlacementHijack : PlacementHijack
|
||||
{
|
||||
private readonly ConstructionPrototype Prototype;
|
||||
private readonly ConstructorComponent Owner;
|
||||
|
||||
public ConstructionPlacementHijack(ConstructionPrototype prototype, ConstructorComponent owner)
|
||||
{
|
||||
Prototype = prototype;
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public override bool HijackPlacementRequest(GridLocalCoordinates coords)
|
||||
{
|
||||
if (Prototype != null)
|
||||
{
|
||||
// Stupid god damn Y AXIS.
|
||||
var dir = Manager.Direction;
|
||||
if (dir == Direction.South)
|
||||
{
|
||||
dir = Direction.North;
|
||||
}
|
||||
else if (dir == Direction.North)
|
||||
{
|
||||
dir = Direction.South;
|
||||
}
|
||||
Owner.SpawnGhost(Prototype, coords, dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool HijackDeletion(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out ConstructionGhostComponent ghost))
|
||||
{
|
||||
Owner.ClearGhost(ghost.GhostID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void StartHijack(PlacementManager manager)
|
||||
{
|
||||
base.StartHijack(manager);
|
||||
|
||||
var res = IoCManager.Resolve<IResourceCache>();
|
||||
manager.CurrentBaseSprite = Prototype.Icon.DirFrame0();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user