Partial lathe ECS, fix cursed lathe visualizer, a bit more audiovisual feedback for lathes (#7238)

* Prototype that's mostly borked rather than completely borked

* ECS inserting mats

* Partial ECS mostly done, needs cleanup and visualizer

* Replace timers

* Power visualizes at least

* First ""working"" version

* Clean up all lathes

* Colors

* Fix animation timing

* Fixes greyscale, adds a bunch of colors

* Give every (used) material a color

* Made most lathes take long enough you can at least see there's some sort of animation

* Insertion feedback popup

* Sound for circuit printer and uniform printer

* Fix queueing, optimize update

* Remove mono crash

* cleanup

* Fix test failure

* Techfab inserting sprite

* Cleanup and commenting

* Fix bug in CanProduce check

* Fix UI resolves

* Mirror review stuff
This commit is contained in:
Rane
2022-04-17 03:34:14 -04:00
committed by GitHub
parent 5f9cb8271d
commit 93cdca4f82
70 changed files with 781 additions and 1183 deletions

View File

@@ -1,219 +1,53 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Materials;
using Content.Server.Power.Components;
using Content.Server.Research.Components;
using Content.Server.Stack;
using Content.Server.UserInterface;
using Content.Shared.Interaction;
using Content.Shared.Lathe;
using Content.Shared.Power;
using Content.Shared.Research.Prototypes;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Content.Shared.Sound;
namespace Content.Server.Lathe.Components
{
[RegisterComponent]
public sealed class LatheComponent : SharedLatheComponent, IInteractUsing
public sealed class LatheComponent : SharedLatheComponent
{
[Dependency] private readonly IEntityManager _entMan = default!;
public const int VolumePerSheet = 100;
/// <summary>
/// How much volume in cm^3 each sheet of material adds
/// </summary>
public int VolumePerSheet = 100;
/// <summary>
/// The lathe's construction queue
/// </summary>
[ViewVariables]
public Queue<LatheRecipePrototype> Queue { get; } = new();
/// <summary>
/// The recipe the lathe is currently producing
/// </summary>
[ViewVariables]
public bool Producing { get; private set; }
private LatheState _state = LatheState.Base;
private LatheState State
{
get => _state;
set => _state = value;
}
public LatheRecipePrototype? ProducingRecipe;
/// <summary>
/// How long the inserting animation will play
/// </summary>
[ViewVariables]
private LatheRecipePrototype? _producingRecipe;
public float InsertionTime = 0.79f; // 0.01 off for animation timing
/// <summary>
/// Update accumulator for the insertion time
/// </suummary>
public float InsertionAccumulator = 0f;
/// <summary>
/// Production accumulator for the production time.
/// </summary>
[ViewVariables]
private bool Powered => !_entMan.TryGetComponent(Owner, out ApcPowerReceiverComponent? receiver) || receiver.Powered;
public float ProducingAccumulator = 0f;
private static readonly TimeSpan InsertionTime = TimeSpan.FromSeconds(0.9f);
/// <summary>
/// The sound that plays when the lathe is producing an item, if any
/// </summary>
[DataField("producingSound")]
public SoundSpecifier? ProducingSound;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(LatheUiKey.Key);
protected override void Initialize()
{
base.Initialize();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if (!Powered)
return;
switch (message.Message)
{
case LatheQueueRecipeMessage msg:
PrototypeManager.TryIndex(msg.ID, out LatheRecipePrototype? recipe);
if (recipe != null!)
for (var i = 0; i < msg.Quantity; i++)
{
Queue.Enqueue(recipe);
UserInterface?.SendMessage(new LatheFullQueueMessage(GetIdQueue()));
}
break;
case LatheSyncRequestMessage _:
if (!_entMan.HasComponent<MaterialStorageComponent>(Owner)) return;
UserInterface?.SendMessage(new LatheFullQueueMessage(GetIdQueue()));
if (_producingRecipe != null)
UserInterface?.SendMessage(new LatheProducingRecipeMessage(_producingRecipe.ID));
break;
case LatheServerSelectionMessage _:
if (!_entMan.TryGetComponent(Owner, out ResearchClientComponent? researchClient)) return;
researchClient.OpenUserInterface(message.Session);
break;
case LatheServerSyncMessage _:
if (!_entMan.TryGetComponent(Owner, out TechnologyDatabaseComponent? database)
|| !_entMan.TryGetComponent(Owner, out ProtolatheDatabaseComponent? protoDatabase)) return;
if (database.SyncWithServer())
protoDatabase.Sync();
break;
}
}
internal bool Produce(LatheRecipePrototype recipe)
{
if (Producing || !Powered || !CanProduce(recipe) || !_entMan.TryGetComponent(Owner, out MaterialStorageComponent? storage)) return false;
UserInterface?.SendMessage(new LatheFullQueueMessage(GetIdQueue()));
Producing = true;
_producingRecipe = recipe;
foreach (var (material, amount) in recipe.RequiredMaterials)
{
// This should always return true, otherwise CanProduce fucked up.
storage.RemoveMaterial(material, amount);
}
UserInterface?.SendMessage(new LatheProducingRecipeMessage(recipe.ID));
State = LatheState.Producing;
SetAppearance(LatheVisualState.Producing);
Owner.SpawnTimer(recipe.CompleteTime, () =>
{
Producing = false;
_producingRecipe = null;
_entMan.SpawnEntity(recipe.Result, _entMan.GetComponent<TransformComponent>(Owner).Coordinates);
UserInterface?.SendMessage(new LatheStoppedProducingRecipeMessage());
State = LatheState.Base;
SetAppearance(LatheVisualState.Idle);
});
return true;
}
public void OpenUserInterface(IPlayerSession session)
{
UserInterface?.Open(session);
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!_entMan.TryGetComponent(Owner, out MaterialStorageComponent? storage)
|| !_entMan.TryGetComponent(eventArgs.Using, out MaterialComponent? material)) return false;
var multiplier = 1;
if (_entMan.TryGetComponent(eventArgs.Using, out StackComponent? stack)) multiplier = stack.Count;
var totalAmount = 0;
// Check if it can insert all materials.
foreach (var mat in material.MaterialIds)
{
// TODO: Change how MaterialComponent works so this is not hard-coded.
if (!storage.CanInsertMaterial(mat, VolumePerSheet * multiplier)) return false;
totalAmount += VolumePerSheet * multiplier;
}
// Check if it can take ALL of the material's volume.
if (storage.CanTakeAmount(totalAmount)) return false;
foreach (var mat in material.MaterialIds)
{
storage.InsertMaterial(mat, VolumePerSheet * multiplier);
}
State = LatheState.Inserting;
switch (material.Materials.FirstOrDefault()?.ID)
{
case "Steel":
SetAppearance(LatheVisualState.InsertingMetal);
break;
case "Glass":
SetAppearance(LatheVisualState.InsertingGlass);
break;
case "Gold":
SetAppearance(LatheVisualState.InsertingGold);
break;
case "Plastic":
SetAppearance(LatheVisualState.InsertingPlastic);
break;
case "Plasma":
SetAppearance(LatheVisualState.InsertingPlasma);
break;
}
Owner.SpawnTimer(InsertionTime, () =>
{
State = LatheState.Base;
SetAppearance(LatheVisualState.Idle);
});
_entMan.DeleteEntity(eventArgs.Using);
return true;
}
private void SetAppearance(LatheVisualState state)
{
if (_entMan.TryGetComponent(Owner, out AppearanceComponent? appearance))
{
appearance.SetData(PowerDeviceVisuals.VisualState, state);
}
}
private Queue<string> GetIdQueue()
{
var queue = new Queue<string>();
foreach (var recipePrototype in Queue)
{
queue.Enqueue(recipePrototype.ID);
}
return queue;
}
private enum LatheState : byte
{
Base,
Inserting,
Producing
}
/// <summmary>
/// The lathe's UI.
/// </summary>
[ViewVariables] public BoundUserInterface? UserInterface;
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Lathe.Components
{
/// <summary>
/// For EntityQuery to keep track of which lathes are inserting
/// <summary>
[RegisterComponent]
public sealed class LatheInsertingComponent : Component
{}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Lathe.Components
{
/// <summary>
/// For EntityQuery to keep track of which lathes are producing
/// <summary>
[RegisterComponent]
public sealed class LatheProducingComponent : Component
{}
}