Deconstruction Features (#1242)
* ConstructionSystem now detects when a tool is used on an arbitrary entity. * Refactored building logic from ConstructionComponent to ConstructionSystem. * Add OnDeconstruct events so that deconstruction can be blocked if prerequisites are not met. * Multi-step deconstruction works. Windows can be deconstructed using a screwdriver. * In-hand construction and deconstruction works. * Intermediate entities now have a better name assigned to them when created. * Removed a question mark to appease Jenkins. * Instead of running ExposeData on the existing ItemComponent of an intermediateFrame, the system will now replace the existing ItemComponent with a new one, and run ExposeData on the new one.
This commit is contained in:
@@ -1,168 +1,41 @@
|
|||||||
using System;
|
using Content.Shared.Construction;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.GameObjects.Components.Interactable;
|
|
||||||
using Content.Server.GameObjects.Components.Stack;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Interfaces;
|
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.Construction;
|
|
||||||
using Content.Shared.GameObjects.Components;
|
|
||||||
using Content.Shared.GameObjects.Components.Interactable;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects.Components;
|
|
||||||
using Robust.Shared.Interfaces.Random;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
using static Content.Shared.Construction.ConstructionStepMaterial;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Construction
|
namespace Content.Server.GameObjects.Components.Construction
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds data about an entity that is in the process of being constructed or destructed.
|
||||||
|
/// </summary>
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ConstructionComponent : Component, IInteractUsing
|
public class ConstructionComponent : Component
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public override string Name => "Construction";
|
public override string Name => "Construction";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current construction recipe being used to build this entity.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public ConstructionPrototype Prototype { get; private set; }
|
public ConstructionPrototype Prototype { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current stage of construction.
|
||||||
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public int Stage { get; private set; }
|
public int Stage { get; set; }
|
||||||
|
|
||||||
SpriteComponent Sprite;
|
/// <inheritdoc />
|
||||||
ITransformComponent Transform;
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
Sprite = Owner.GetComponent<SpriteComponent>();
|
serializer.DataReadWriteFunction("prototype", null,
|
||||||
Transform = Owner.GetComponent<ITransformComponent>();
|
value => Prototype = value, () => Prototype);
|
||||||
}
|
|
||||||
|
|
||||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
serializer.DataReadWriteFunction("stage", 0,
|
||||||
{
|
value => Stage = value, () => Stage);
|
||||||
// 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
|
|
||||||
if (Prototype.CanBuildInImpassable == false)
|
|
||||||
{
|
|
||||||
if (!InteractionChecks.InRangeUnobstructed(eventArgs, false))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var stage = Prototype.Stages[Stage];
|
|
||||||
|
|
||||||
if (TryProcessStep(stage.Forward, eventArgs.Using, eventArgs.User))
|
|
||||||
{
|
|
||||||
Stage++;
|
|
||||||
if (Stage == Prototype.Stages.Count - 1)
|
|
||||||
{
|
|
||||||
// Oh boy we get to finish construction!
|
|
||||||
var entMgr = IoCManager.Resolve<IServerEntityManager>();
|
|
||||||
var ent = entMgr.SpawnEntity(Prototype.Result, Transform.GridPosition);
|
|
||||||
ent.GetComponent<ITransformComponent>().LocalRotation = Transform.LocalRotation;
|
|
||||||
Owner.Delete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
stage = Prototype.Stages[Stage];
|
|
||||||
if (stage.Icon != null)
|
|
||||||
{
|
|
||||||
Sprite.LayerSetSprite(0, stage.Icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (TryProcessStep(stage.Backward, eventArgs.Using, eventArgs.User))
|
|
||||||
{
|
|
||||||
Stage--;
|
|
||||||
if (Stage == 0)
|
|
||||||
{
|
|
||||||
// Deconstruction complete.
|
|
||||||
Owner.Delete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
stage = Prototype.Stages[Stage];
|
|
||||||
if (stage.Icon != null)
|
|
||||||
{
|
|
||||||
Sprite.LayerSetSprite(0, stage.Icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init(ConstructionPrototype prototype)
|
|
||||||
{
|
|
||||||
Prototype = prototype;
|
|
||||||
Stage = 1;
|
|
||||||
if(prototype.Stages[1].Icon != null)
|
|
||||||
{
|
|
||||||
Sprite.AddLayerWithSprite(prototype.Stages[1].Icon);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Sprite.AddLayerWithSprite(prototype.Icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TryProcessStep(ConstructionStep step, IEntity slapped, IEntity user)
|
|
||||||
{
|
|
||||||
if (step == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var sound = EntitySystem.Get<AudioSystem>();
|
|
||||||
|
|
||||||
switch (step)
|
|
||||||
{
|
|
||||||
case ConstructionStepMaterial matStep:
|
|
||||||
if (!slapped.TryGetComponent(out StackComponent stack)
|
|
||||||
|| !MaterialStackValidFor(matStep, stack)
|
|
||||||
|| !stack.Use(matStep.Amount))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (matStep.Material == MaterialType.Cable)
|
|
||||||
sound.PlayAtCoords("/Audio/items/zip.ogg", Transform.GridPosition);
|
|
||||||
else
|
|
||||||
sound.PlayAtCoords("/Audio/items/deconstruct.ogg", Transform.GridPosition);
|
|
||||||
return true;
|
|
||||||
case ConstructionStepTool toolStep:
|
|
||||||
if (!slapped.TryGetComponent<ToolComponent>(out var tool))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Handle welder manually since tool steps specify fuel amount needed, for some reason.
|
|
||||||
if (toolStep.ToolQuality.HasFlag(ToolQuality.Welding))
|
|
||||||
return slapped.TryGetComponent<WelderComponent>(out var welder)
|
|
||||||
&& welder.UseTool(user, Owner, toolStep.ToolQuality, toolStep.Amount);
|
|
||||||
|
|
||||||
return tool.UseTool(user, Owner, toolStep.ToolQuality);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<StackType, MaterialType> StackTypeMap
|
|
||||||
= new Dictionary<StackType, MaterialType>
|
|
||||||
{
|
|
||||||
{ StackType.Cable, MaterialType.Cable },
|
|
||||||
{ StackType.Gold, MaterialType.Gold },
|
|
||||||
{ StackType.Glass, MaterialType.Glass },
|
|
||||||
{ StackType.Metal, MaterialType.Metal }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Really this should check the actual materials at play..
|
|
||||||
public static bool MaterialStackValidFor(ConstructionStepMaterial step, StackComponent stack)
|
|
||||||
{
|
|
||||||
return StackTypeMap.TryGetValue((StackType)stack.StackType, out var should) && should == step.Material;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,14 @@ using Robust.Shared.ViewVariables;
|
|||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Interactable
|
namespace Content.Server.GameObjects.Components.Interactable
|
||||||
{
|
{
|
||||||
|
public interface IToolComponent
|
||||||
|
{
|
||||||
|
ToolQuality Qualities { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public class ToolComponent : SharedToolComponent
|
[ComponentReference(typeof(IToolComponent))]
|
||||||
|
public class ToolComponent : SharedToolComponent, IToolComponent
|
||||||
{
|
{
|
||||||
protected ToolQuality _qualities = ToolQuality.None;
|
protected ToolQuality _qualities = ToolQuality.None;
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
|||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
[ComponentReference(typeof(ToolComponent))]
|
[ComponentReference(typeof(ToolComponent))]
|
||||||
|
[ComponentReference(typeof(IToolComponent))]
|
||||||
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct
|
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct
|
||||||
{
|
{
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||||
using Content.Server.GameObjects.Components.Sound;
|
using Content.Server.GameObjects.Components.Sound;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
@@ -48,6 +49,23 @@ namespace Content.Server.GameObjects.Components.Power
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||||
|
{
|
||||||
|
base.HandleMessage(message, component);
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case BeginDeconstructCompMsg msg:
|
||||||
|
if (!msg.BlockDeconstruct && !(_lightBulbContainer.ContainedEntity is null))
|
||||||
|
{
|
||||||
|
var notifyManager = IoCManager.Resolve<IServerNotifyManager>();
|
||||||
|
notifyManager.PopupMessage(Owner, msg.User, "Remove the bulb.");
|
||||||
|
msg.BlockDeconstruct = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
return InsertBulb(eventArgs.Using);
|
return InsertBulb(eventArgs.Using);
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Construction;
|
using Content.Server.GameObjects.Components.Construction;
|
||||||
|
using Content.Server.GameObjects.Components.Interactable;
|
||||||
using Content.Server.GameObjects.Components.Stack;
|
using Content.Server.GameObjects.Components.Stack;
|
||||||
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Utility;
|
using Content.Server.Utility;
|
||||||
using Content.Shared.Construction;
|
using Content.Shared.Construction;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Content.Shared.GameObjects.Components.Interactable;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Systems;
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects.Components;
|
||||||
using Robust.Shared.Interfaces.Map;
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.EntitySystems
|
namespace Content.Server.GameObjects.EntitySystems
|
||||||
{
|
{
|
||||||
@@ -27,16 +34,25 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||||
[Dependency] private readonly IMapManager _mapManager;
|
[Dependency] private readonly IMapManager _mapManager;
|
||||||
[Dependency] private readonly IServerEntityManager _serverEntityManager;
|
[Dependency] private readonly IServerNotifyManager _notifyManager;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private readonly Dictionary<string, ConstructionPrototype> _craftRecipes = new Dictionary<string, ConstructionPrototype>();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
|
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||||
|
{
|
||||||
|
_craftRecipes.Add(prototype.Result, prototype);
|
||||||
|
}
|
||||||
|
|
||||||
SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
|
SubscribeNetworkEvent<TryStartStructureConstructionMessage>(HandleStartStructureConstruction);
|
||||||
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
|
SubscribeNetworkEvent<TryStartItemConstructionMessage>(HandleStartItemConstruction);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<AfterAttackMessage>(HandleToolInteraction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleStartStructureConstruction(TryStartStructureConstructionMessage msg, EntitySessionEventArgs args)
|
private void HandleStartStructureConstruction(TryStartStructureConstructionMessage msg, EntitySessionEventArgs args)
|
||||||
@@ -55,6 +71,168 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
TryStartItemConstruction(placingEnt, msg.PrototypeName);
|
TryStartItemConstruction(placingEnt, msg.PrototypeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleToolInteraction(AfterAttackMessage msg)
|
||||||
|
{
|
||||||
|
if(msg.Handled)
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Cannot deconstruct an entity with no prototype.
|
||||||
|
var targetPrototype = targetEnt.MetaData.EntityPrototype;
|
||||||
|
if (targetPrototype is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// the target entity is in the process of being constructed/deconstructed
|
||||||
|
if (msg.Attacked.TryGetComponent<ConstructionComponent>(out var constructComp))
|
||||||
|
{
|
||||||
|
var result = TryConstructEntity(constructComp, handEnt, msg.User);
|
||||||
|
|
||||||
|
// TryConstructEntity may delete the existing entity
|
||||||
|
|
||||||
|
msg.Handled = result;
|
||||||
|
}
|
||||||
|
else // try to start the deconstruction process
|
||||||
|
{
|
||||||
|
// 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))
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(msg.Attacked, msg.User,
|
||||||
|
"Cannot be deconstructed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is a recipe, but it can't be deconstructed.
|
||||||
|
var lastStep = prototype.Stages[^1].Backward;
|
||||||
|
if (!(lastStep is ConstructionStepTool))
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(msg.Attacked, msg.User,
|
||||||
|
"Cannot be deconstructed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrong tool
|
||||||
|
var caps = ((ConstructionStepTool) lastStep).ToolQuality;
|
||||||
|
if ((toolComp.Qualities & caps) == 0)
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(msg.Attacked, msg.User,
|
||||||
|
"Wrong tool to start deconstruct.");
|
||||||
|
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 ---
|
||||||
|
|
||||||
|
// 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 = EntityManager.SpawnEntity("structureconstructionframe", targetEntPos);
|
||||||
|
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 static void SetupDeconIntermediateSprite(ConstructionComponent constructionComponent, ConstructionPrototype prototype)
|
||||||
|
{
|
||||||
|
if(!constructionComponent.Owner.TryGetComponent<SpriteComponent>(out var spriteComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = prototype.Stages.Count - 1; i < 0; i--)
|
||||||
|
{
|
||||||
|
if (prototype.Stages[i].Icon != null)
|
||||||
|
{
|
||||||
|
spriteComp.AddLayerWithSprite(prototype.Stages[1].Icon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spriteComp.AddLayerWithSprite(prototype.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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, GridCoordinates loc, string prototypeName, Angle angle)
|
private bool TryStartStructureConstruction(IEntity placingEnt, GridCoordinates loc, string prototypeName, Angle angle)
|
||||||
{
|
{
|
||||||
var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);
|
var prototype = _prototypeManager.Index<ConstructionPrototype>(prototypeName);
|
||||||
@@ -84,7 +262,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeHand.TryGetComponent(out StackComponent stack) || !ConstructionComponent.MaterialStackValidFor(matStep, stack))
|
if (!activeHand.TryGetComponent(out StackComponent stack) || !MaterialStackValidFor(matStep, stack))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -99,14 +277,14 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
if (prototype.Stages.Count == 2)
|
if (prototype.Stages.Count == 2)
|
||||||
{
|
{
|
||||||
// Exactly 2 stages, so don't make an intermediate frame.
|
// Exactly 2 stages, so don't make an intermediate frame.
|
||||||
var ent = _serverEntityManager.SpawnEntity(prototype.Result, loc);
|
var ent = EntityManager.SpawnEntity(prototype.Result, loc);
|
||||||
ent.Transform.LocalRotation = angle;
|
ent.Transform.LocalRotation = angle;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var frame = _serverEntityManager.SpawnEntity("structureconstructionframe", loc);
|
var frame = EntityManager.SpawnEntity("structureconstructionframe", loc);
|
||||||
var construction = frame.GetComponent<ConstructionComponent>();
|
var construction = frame.GetComponent<ConstructionComponent>();
|
||||||
construction.Init(prototype);
|
SetupComponent(construction, prototype);
|
||||||
frame.Transform.LocalRotation = angle;
|
frame.Transform.LocalRotation = angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +314,7 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activeHand.TryGetComponent(out StackComponent stack) || !ConstructionComponent.MaterialStackValidFor(matStep, stack))
|
if (!activeHand.TryGetComponent(out StackComponent stack) || !MaterialStackValidFor(matStep, stack))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -151,17 +329,243 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
if (prototype.Stages.Count == 2)
|
if (prototype.Stages.Count == 2)
|
||||||
{
|
{
|
||||||
// Exactly 2 stages, so don't make an intermediate frame.
|
// Exactly 2 stages, so don't make an intermediate frame.
|
||||||
var ent = _serverEntityManager.SpawnEntity(prototype.Result, placingEnt.Transform.GridPosition);
|
var ent = EntityManager.SpawnEntity(prototype.Result, placingEnt.Transform.GridPosition);
|
||||||
hands.PutInHandOrDrop(ent.GetComponent<ItemComponent>());
|
hands.PutInHandOrDrop(ent.GetComponent<ItemComponent>());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//TODO: Make these viable as an item and try putting them in the players hands
|
var frame = EntityManager.SpawnEntity("structureconstructionframe", placingEnt.Transform.GridPosition);
|
||||||
var frame = _serverEntityManager.SpawnEntity("structureconstructionframe", placingEnt.Transform.GridPosition);
|
|
||||||
var construction = frame.GetComponent<ConstructionComponent>();
|
var construction = frame.GetComponent<ConstructionComponent>();
|
||||||
construction.Init(prototype);
|
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 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 (!InteractionChecks.InRangeUnobstructed(user, constructEntity.Transform.MapPosition))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stage = constructPrototype.Stages[constructionComponent.Stage];
|
||||||
|
|
||||||
|
if (TryProcessStep(constructEntity, stage.Forward, handTool, user, transformComponent.GridPosition))
|
||||||
|
{
|
||||||
|
constructionComponent.Stage++;
|
||||||
|
if (constructionComponent.Stage == constructPrototype.Stages.Count - 1)
|
||||||
|
{
|
||||||
|
// Oh boy we get to finish construction!
|
||||||
|
var ent = EntityManager.SpawnEntity(constructPrototype.Result, transformComponent.GridPosition);
|
||||||
|
ent.Transform.LocalRotation = transformComponent.LocalRotation;
|
||||||
|
|
||||||
|
ReplaceInContainerOrGround(constructEntity, ent);
|
||||||
|
|
||||||
|
constructEntity.Delete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage = constructPrototype.Stages[constructionComponent.Stage];
|
||||||
|
if (stage.Icon != null)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerSetSprite(0, stage.Icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (TryProcessStep(constructEntity, stage.Backward, handTool, user, transformComponent.GridPosition))
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// Deconstruction complete.
|
||||||
|
constructEntity.Delete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage.Icon != null)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerSetSprite(0, stage.Icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryProcessStep(IEntity constructEntity, ConstructionStep step, IEntity slapped, IEntity user, GridCoordinates gridCoords)
|
||||||
|
{
|
||||||
|
if (step == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sound = EntitySystemManager.GetEntitySystem<AudioSystem>();
|
||||||
|
|
||||||
|
switch (step)
|
||||||
|
{
|
||||||
|
case ConstructionStepMaterial matStep:
|
||||||
|
if (!slapped.TryGetComponent(out StackComponent stack)
|
||||||
|
|| !MaterialStackValidFor(matStep, stack)
|
||||||
|
|| !stack.Use(matStep.Amount))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
&& welder.UseTool(user, constructEntity, toolStep.ToolQuality, toolStep.Amount);
|
||||||
|
|
||||||
|
return tool.UseTool(user, constructEntity, toolStep.ToolQuality);
|
||||||
|
|
||||||
|
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>
|
||||||
|
{
|
||||||
|
{ StackType.Cable, ConstructionStepMaterial.MaterialType.Cable },
|
||||||
|
{ StackType.Gold, ConstructionStepMaterial.MaterialType.Gold },
|
||||||
|
{ StackType.Glass, ConstructionStepMaterial.MaterialType.Glass },
|
||||||
|
{ StackType.Metal, ConstructionStepMaterial.MaterialType.Metal }
|
||||||
|
};
|
||||||
|
|
||||||
|
private static bool MaterialStackValidFor(ConstructionStepMaterial step, StackComponent stack)
|
||||||
|
{
|
||||||
|
return StackTypeMap.TryGetValue((StackType)stack.StackType, out var should) && should == step.Material;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupComponent(ConstructionComponent constructionComponent, ConstructionPrototype prototype)
|
||||||
|
{
|
||||||
|
constructionComponent.Prototype = prototype;
|
||||||
|
constructionComponent.Stage = 1;
|
||||||
|
var spriteComp = constructionComponent.Owner.GetComponent<SpriteComponent>();
|
||||||
|
if(prototype.Stages[1].Icon != null)
|
||||||
|
{
|
||||||
|
spriteComp.AddLayerWithSprite(prototype.Stages[1].Icon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
spriteComp.AddLayerWithSprite(prototype.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
|
Hand = hand;
|
||||||
|
Target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A component message that is raised when an entity is trying to be deconstructed.
|
||||||
|
/// </summary>
|
||||||
|
public class BeginDeconstructCompMsg : ComponentMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entity that initiated the deconstruction.
|
||||||
|
/// </summary>
|
||||||
|
public IEntity User { 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="BeginDeconstructCompMsg"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">Entity that initiated the deconstruction.</param>
|
||||||
|
public BeginDeconstructCompMsg(IEntity user)
|
||||||
|
{
|
||||||
|
User = user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@
|
|||||||
steps:
|
steps:
|
||||||
- material: Glass
|
- material: Glass
|
||||||
amount: 2
|
amount: 2
|
||||||
|
reverse:
|
||||||
|
tool: Screwing
|
||||||
|
|
||||||
- type: construction
|
- type: construction
|
||||||
name: low wall
|
name: low wall
|
||||||
|
|||||||
@@ -10,10 +10,16 @@
|
|||||||
steps:
|
steps:
|
||||||
- material: Metal
|
- material: Metal
|
||||||
amount: 1
|
amount: 1
|
||||||
|
reverse:
|
||||||
|
tool: Screwing
|
||||||
|
|
||||||
- material: Cable
|
- material: Cable
|
||||||
amount: 3
|
amount: 3
|
||||||
|
reverse:
|
||||||
|
tool: Screwing
|
||||||
|
|
||||||
# Should probably use a shard but y'know.
|
# Should probably use a shard but y'know.
|
||||||
- material: Glass
|
- material: Glass
|
||||||
amount: 1
|
amount: 1
|
||||||
|
reverse:
|
||||||
|
tool: Screwing
|
||||||
|
|||||||
@@ -82,6 +82,14 @@
|
|||||||
bounds:
|
bounds:
|
||||||
all: -0.15,-0.15,0.15,0.15
|
all: -0.15,-0.15,0.15,0.15
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: CableStack1
|
||||||
|
name: cable stack 1
|
||||||
|
parent: CableStack
|
||||||
|
components:
|
||||||
|
- type: Stack
|
||||||
|
count: 1
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: HVWireStack
|
id: HVWireStack
|
||||||
name: HV Wire Coil
|
name: HV Wire Coil
|
||||||
|
|||||||
Reference in New Issue
Block a user