Batchable lathe jobs, editable lathe job order (#38624)

* batchable lathe jobs, editable order

* requested changes

* LatheComponent comment, menu strings
This commit is contained in:
Whatstone
2025-08-24 11:02:47 -04:00
committed by GitHub
parent 30aa61c29c
commit b5529ecf2b
11 changed files with 397 additions and 35 deletions

View File

@@ -74,6 +74,9 @@ namespace Content.Server.Lathe
SubscribeLocalEvent<LatheComponent, LatheQueueRecipeMessage>(OnLatheQueueRecipeMessage);
SubscribeLocalEvent<LatheComponent, LatheSyncRequestMessage>(OnLatheSyncRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheDeleteRequestMessage>(OnLatheDeleteRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheMoveRequestMessage>(OnLatheMoveRequestMessage);
SubscribeLocalEvent<LatheComponent, LatheAbortFabricationMessage>(OnLatheAbortFabricationMessage);
SubscribeLocalEvent<LatheComponent, BeforeActivatableUIOpenEvent>((u, c, _) => UpdateUserInterfaceState(u, c));
SubscribeLocalEvent<LatheComponent, MaterialAmountChangedEvent>(OnMaterialAmountChanged);
@@ -167,23 +170,32 @@ namespace Content.Server.Lathe
return ev.Recipes.ToList();
}
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, LatheComponent? component = null)
public bool TryAddToQueue(EntityUid uid, LatheRecipePrototype recipe, int quantity, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return false;
if (!CanProduce(uid, recipe, 1, component))
if (quantity <= 0)
return false;
quantity = int.Min(quantity, MaxItemsPerRequest);
if (!CanProduce(uid, recipe, quantity, component))
return false;
foreach (var (mat, amount) in recipe.Materials)
{
var adjustedAmount = recipe.ApplyMaterialDiscount
? (int) (-amount * component.MaterialUseMultiplier)
? (int)(-amount * component.MaterialUseMultiplier)
: -amount;
adjustedAmount *= quantity;
_materialStorage.TryChangeMaterialAmount(uid, mat, adjustedAmount);
}
component.Queue.Enqueue(recipe);
if (component.Queue.Last is { } node && node.ValueRef.Recipe == recipe.ID)
node.ValueRef.ItemsRequested += quantity;
else
component.Queue.AddLast(new LatheRecipeBatch(recipe.ID, 0, quantity));
return true;
}
@@ -195,8 +207,11 @@ namespace Content.Server.Lathe
if (component.CurrentRecipe != null || component.Queue.Count <= 0 || !this.IsPowered(uid, EntityManager))
return false;
var recipeProto = component.Queue.Dequeue();
var recipe = _proto.Index(recipeProto);
var batch = component.Queue.First();
batch.ItemsPrinted++;
if (batch.ItemsPrinted >= batch.ItemsRequested || batch.ItemsPrinted < 0) // Rollover sanity check
component.Queue.RemoveFirst();
var recipe = _proto.Index(batch.Recipe);
var time = _reagentSpeed.ApplySpeed(uid, recipe.CompleteTime) * component.TimeMultiplier;
@@ -271,8 +286,8 @@ namespace Content.Server.Lathe
return;
var producing = component.CurrentRecipe;
if (producing == null && component.Queue.TryPeek(out var next))
producing = next;
if (producing == null && component.Queue.First is { } node)
producing = node.Value.Recipe;
var state = new LatheUpdateState(GetAvailableRecipes(uid, component), component.Queue.ToArray(), producing);
_uiSys.SetUiState(uid, LatheUiKey.Key, state);
@@ -349,12 +364,10 @@ namespace Content.Server.Lathe
{
if (!args.Powered)
{
RemComp<LatheProducingComponent>(uid);
UpdateRunningAppearance(uid, false);
AbortProduction(uid);
}
else if (component.CurrentRecipe != null)
else
{
EnsureComp<LatheProducingComponent>(uid);
TryStartProducing(uid, component);
}
}
@@ -416,25 +429,46 @@ namespace Content.Server.Lathe
return GetAvailableRecipes(uid, component).Contains(recipe.ID);
}
public void AbortProduction(EntityUid uid, LatheComponent? component = null)
{
if (!Resolve(uid, ref component))
return;
if (component.CurrentRecipe != null)
{
if (component.Queue.Count > 0)
{
// Batch abandoned while printing last item, need to create a one-item batch
var batch = component.Queue.First();
if (batch.Recipe != component.CurrentRecipe)
{
var newBatch = new LatheRecipeBatch(component.CurrentRecipe.Value, 0, 1);
component.Queue.AddFirst(newBatch);
}
else if (batch.ItemsPrinted > 0)
{
batch.ItemsPrinted--;
}
}
component.CurrentRecipe = null;
}
RemCompDeferred<LatheProducingComponent>(uid);
UpdateUserInterfaceState(uid, component);
UpdateRunningAppearance(uid, false);
}
#region UI Messages
private void OnLatheQueueRecipeMessage(EntityUid uid, LatheComponent component, LatheQueueRecipeMessage args)
{
if (_proto.TryIndex(args.ID, out LatheRecipePrototype? recipe))
{
var count = 0;
for (var i = 0; i < args.Quantity; i++)
{
if (TryAddToQueue(uid, recipe, component))
count++;
else
break;
}
if (count > 0)
if (TryAddToQueue(uid, recipe, args.Quantity, component))
{
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} queued {count} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
$"{ToPrettyString(args.Actor):player} queued {args.Quantity} {GetRecipeName(recipe)} at {ToPrettyString(uid):lathe}");
}
}
TryStartProducing(uid, component);
@@ -445,6 +479,92 @@ namespace Content.Server.Lathe
{
UpdateUserInterfaceState(uid, component);
}
/// <summary>
/// Removes a batch from the batch queue by index.
/// If the index given does not exist or is outside of the bounds of the lathe's batch queue, nothing happens.
/// </summary>
/// <param name="uid">The lathe whose queue is being altered.</param>
/// <param name="component"></param>
/// <param name="args"></param>
public void OnLatheDeleteRequestMessage(EntityUid uid, LatheComponent component, ref LatheDeleteRequestMessage args)
{
if (args.Index < 0 || args.Index >= component.Queue.Count)
return;
var node = component.Queue.First;
for (int i = 0; i < args.Index; i++)
node = node?.Next;
if (node == null) // Shouldn't happen with checks above.
return;
var batch = node.Value;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} deleted a lathe job for ({batch.ItemsPrinted}/{batch.ItemsRequested}) {GetRecipeName(batch.Recipe)} at {ToPrettyString(uid):lathe}");
component.Queue.Remove(node);
UpdateUserInterfaceState(uid, component);
}
public void OnLatheMoveRequestMessage(EntityUid uid, LatheComponent component, ref LatheMoveRequestMessage args)
{
if (args.Change == 0 || args.Index < 0 || args.Index >= component.Queue.Count)
return;
// New index must be within the bounds of the batch.
var newIndex = args.Index + args.Change;
if (newIndex < 0 || newIndex >= component.Queue.Count)
return;
var node = component.Queue.First;
for (int i = 0; i < args.Index; i++)
node = node?.Next;
if (node == null) // Something went wrong.
return;
if (args.Change > 0)
{
var newRelativeNode = node.Next;
for (int i = 1; i < args.Change; i++) // 1-indexed: starting from Next
newRelativeNode = newRelativeNode?.Next;
if (newRelativeNode == null) // Something went wrong.
return;
component.Queue.Remove(node);
component.Queue.AddAfter(newRelativeNode, node);
}
else
{
var newRelativeNode = node.Previous;
for (int i = 1; i < -args.Change; i++) // 1-indexed: starting from Previous
newRelativeNode = newRelativeNode?.Previous;
if (newRelativeNode == null) // Something went wrong.
return;
component.Queue.Remove(node);
component.Queue.AddBefore(newRelativeNode, node);
}
UpdateUserInterfaceState(uid, component);
}
public void OnLatheAbortFabricationMessage(EntityUid uid, LatheComponent component, ref LatheAbortFabricationMessage args)
{
if (component.CurrentRecipe == null)
return;
_adminLogger.Add(LogType.Action,
LogImpact.Low,
$"{ToPrettyString(args.Actor):player} aborted printing {GetRecipeName(component.CurrentRecipe.Value)} at {ToPrettyString(uid):lathe}");
component.CurrentRecipe = null;
FinishProducing(uid, component);
}
#endregion
}
}