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:
Acruid
2020-07-02 14:50:57 -07:00
committed by GitHub
parent 6c205c4a79
commit 3ee480a3b1
8 changed files with 480 additions and 162 deletions

View File

@@ -1,168 +1,41 @@
using System;
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 Content.Shared.Construction;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
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.Serialization;
using Robust.Shared.ViewVariables;
using static Content.Shared.Construction.ConstructionStepMaterial;
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, IInteractUsing
public class ConstructionComponent : Component
{
/// <inheritdoc />
public override string Name => "Construction";
/// <summary>
/// The current construction recipe being used to build this entity.
/// </summary>
[ViewVariables]
public ConstructionPrototype Prototype { get; private set; }
public ConstructionPrototype Prototype { get; set; }
/// <summary>
/// The current stage of construction.
/// </summary>
[ViewVariables]
public int Stage { get; private set; }
public int Stage { get; set; }
SpriteComponent Sprite;
ITransformComponent Transform;
public override void Initialize()
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.Initialize();
base.ExposeData(serializer);
Sprite = Owner.GetComponent<SpriteComponent>();
Transform = Owner.GetComponent<ITransformComponent>();
}
serializer.DataReadWriteFunction("prototype", null,
value => Prototype = value, () => Prototype);
public bool InteractUsing(InteractUsingEventArgs eventArgs)
{
// 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;
serializer.DataReadWriteFunction("stage", 0,
value => Stage = value, () => Stage);
}
}
}