Construction System. (#87)

* Construction WiP

* Construction kinda works!

* Lots more construction work.

* It mostly works!
This commit is contained in:
Pieter-Jan Briers
2018-08-02 08:29:55 +02:00
committed by GitHub
parent f051078c79
commit d7074bf74f
72 changed files with 1925 additions and 245 deletions

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

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

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

View File

@@ -63,6 +63,9 @@
<Reference Include="YamlDotNet, Version=4.3.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)packages\YamlDotNet.4.3.1\lib\net45\YamlDotNet.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple">
<HintPath>$(SolutionDir)packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="EntryPoint.cs" />
@@ -107,5 +110,10 @@
<None Include="packages.config" />
<Compile Include="GameObjects\Components\Power\SmesVisualizer2D.cs" />
<Compile Include="GameObjects\Components\Power\ApcVisualizer2D.cs" />
<Compile Include="Construction\ConstructionMenu.cs" />
<Compile Include="Construction\ConstructionButton.cs" />
<Compile Include="GameObjects\Components\Construction\ConstructorComponent.cs" />
<Compile Include="GameObjects\Components\Construction\ConstructionGhostComponent.cs" />
<Compile Include="Construction\ConstructionPlacementHijack.cs" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,12 @@
using Content.Client.GameObjects;
using Content.Client.GameObjects.Components.Construction;
using Content.Client.GameObjects.Components.Power;
using Content.Client.GameObjects.Components.Storage;
using Content.Client.Interfaces.GameObjects;
using SS14.Shared.ContentPack;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.IoC;
using SS14.Shared.Prototypes;
namespace Content.Client
{
@@ -13,6 +15,7 @@ namespace Content.Client
public override void Init()
{
var factory = IoCManager.Resolve<IComponentFactory>();
var prototypes = IoCManager.Resolve<IPrototypeManager>();
factory.RegisterIgnore("Item");
factory.RegisterIgnore("Interactable");
@@ -40,11 +43,18 @@ namespace Content.Client
factory.RegisterIgnore("Storeable");
factory.RegisterIgnore("Clothing");
factory.RegisterIgnore("Material");
factory.RegisterIgnore("Stack");
factory.Register<HandsComponent>();
factory.RegisterReference<HandsComponent, IHandsComponent>();
factory.Register<ClientStorageComponent>();
factory.Register<ClientInventoryComponent>();
factory.Register<PowerDebugTool>();
factory.Register<ConstructorComponent>();
factory.Register<ConstructionGhostComponent>();
prototypes.RegisterIgnore("material");
}
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared.Construction;
using SS14.Shared.GameObjects;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.Network;
namespace Content.Client.GameObjects.Components.Construction
{
public class ConstructionGhostComponent : Component
{
public override string Name => "ConstructionGhost";
public ConstructionPrototype Prototype { get; set; }
public ConstructorComponent Master { get; set; }
public int GhostID { get; set; }
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
switch (message)
{
case ClientEntityClickMsg clickMsg:
Master.TryStartConstruction(GhostID);
break;
}
}
}
}

View File

@@ -0,0 +1,96 @@
using System.Collections.Generic;
using Content.Client.Construction;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components.Construction;
using SS14.Client.GameObjects;
using SS14.Client.Interfaces.GameObjects;
using SS14.Shared.GameObjects;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Interfaces.GameObjects.Components;
using SS14.Shared.Interfaces.Network;
using SS14.Shared.IoC;
using SS14.Shared.Log;
using SS14.Shared.Map;
using SS14.Shared.Maths;
namespace Content.Client.GameObjects.Components.Construction
{
public class ConstructorComponent : SharedConstructorComponent
{
int nextId;
readonly Dictionary<int, ConstructionGhostComponent> Ghosts = new Dictionary<int, ConstructionGhostComponent>();
ConstructionButton Button;
ITransformComponent Transform;
public override void Initialize()
{
base.Initialize();
Transform = Owner.GetComponent<ITransformComponent>();
}
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
switch (message)
{
case PlayerAttachedMsg _:
if (Button == null)
{
Button = new ConstructionButton();
Button.Owner = this;
}
Button.AddToScreen();
break;
case PlayerDetachedMsg _:
Button.RemoveFromScreen();
break;
case AckStructureConstructionMessage ackMsg:
ClearGhost(ackMsg.Ack);
break;
}
}
public override void OnRemove()
{
Button?.Dispose();
}
public void SpawnGhost(ConstructionPrototype prototype, GridLocalCoordinates loc, Direction dir)
{
var entMgr = IoCManager.Resolve<IClientEntityManager>();
var ghost = entMgr.ForceSpawnEntityAt("constructionghost", loc);
var comp = ghost.GetComponent<ConstructionGhostComponent>();
comp.Prototype = prototype;
comp.Master = this;
comp.GhostID = nextId++;
var transform = ghost.GetComponent<ITransformComponent>().LocalRotation = dir.ToAngle();
var sprite = ghost.GetComponent<SpriteComponent>();
sprite.LayerSetSprite(0, prototype.Icon);
sprite.LayerSetShader(0, "unshaded");
Ghosts.Add(comp.GhostID, comp);
}
public void TryStartConstruction(int ghostId)
{
var ghost = Ghosts[ghostId];
var transform = ghost.Owner.GetComponent<ITransformComponent>();
var msg = new TryStartStructureConstructionMessage(transform.LocalPosition, ghost.Prototype.ID, transform.LocalRotation, ghostId);
SendNetworkMessage(msg);
}
public void ClearGhost(int ghostId)
{
if (Ghosts.TryGetValue(ghostId, out var ghost))
{
ghost.Owner.Delete();
Ghosts.Remove(ghostId);
}
}
}
}

View File

@@ -60,7 +60,7 @@ namespace Content.Client.GameObjects
public void UseActiveHand()
{
if(GetEntity(ActiveIndex) != null)
if (GetEntity(ActiveIndex) != null)
{
SendNetworkMessage(new ActivateInhandMsg());
}

View File

@@ -2,4 +2,4 @@
<packages>
<package id="System.ValueTuple" version="4.4.0" targetFramework="net451" />
<package id="YamlDotNet" version="4.3.1" targetFramework="net451" />
</packages>
</packages>