#W# Craft: Added a nice crafting queue system. To access the queue there is a button on the bottom right. Under the Skill Guide.

This commit is contained in:
WarrentyExpired 2026-10-28 12:21:59 -04:00
parent 0fa220bb63
commit 712247762c
6 changed files with 469 additions and 66 deletions

View file

@ -15,6 +15,7 @@ namespace Server.Engines.Craft
public CraftContext()
{
Items = new List<CraftItem>();
Queue = new List<CraftQueueOrder>();
LastResourceIndex = -1;
LastResourceIndex2 = -1;
LastGroupIndex = -1;
@ -22,6 +23,8 @@ namespace Server.Engines.Craft
public List<CraftItem> Items { get; }
public List<CraftQueueOrder> Queue { get; }
public int LastResourceIndex { get; set; }
public int LastResourceIndex2 { get; set; }
@ -32,6 +35,8 @@ namespace Server.Engines.Craft
public CraftMarkOption MarkOption { get; set; }
public bool IsQueueRunning { get; set; }
// T2A: last hue used for hue-aware crafting (tailoring cloth)
public int LastHue { get; set; } = -1;
@ -52,6 +57,22 @@ namespace Server.Engines.Craft
}
}
/// <summary>
/// Retrieves the first uncompleted work order in the queue.
/// </summary>
public CraftQueueOrder GetActiveOrder()
{
for (int i = 0; i < Queue.Count; i++)
{
if (!Queue[i].IsFinished)
{
return Queue[i];
}
}
return null;
}
public void OnMade(CraftItem item)
{
Items.Remove(item);

View file

@ -102,11 +102,16 @@ public class CraftGump : DynamicGump
}
// ****************************************
// Skill Guide
// Skill Gain Guide
builder.AddButton(415, 342, 4011, 4012, GetButtonID(6, 9));
builder.AddLabel(445, 345, LabelHue, "Skill Guide");
// ****************************************
// ****************************************
// Crafting Queue
builder.AddButton(415, 362, 4011, 4012, GetButtonID(6, 10));
builder.AddLabel(445, 365, LabelHue, "Craft Queue");
// Enhance option
if (_craftSystem.CanEnhance)
{
@ -705,6 +710,12 @@ public class CraftGump : DynamicGump
_from.SendGump(new CraftOptimizerGump(_from, _craftSystem, _tool));
break;
}
case 10: // Open Craft Queue
{
_from.SendGump(new CraftGump(_from, _craftSystem, _tool, null, _page));
_from.SendGump(new CraftQueueGump(_from, _craftSystem, _tool));
break;
}
}
break;

View file

@ -8,7 +8,7 @@ namespace Server.Engines.Craft;
public class CraftGumpItem : DynamicGump
{
private const int LabelHue = 0x480; // 0x384
private const int LabelHue = 0x480;
private const int RedLabelHue = 0x20;
private const int LabelColor = 0x7FFF;
@ -79,8 +79,16 @@ public class CraftGumpItem : DynamicGump
}
else
{
builder.AddButton(270, 387, 4005, 4007, 1);
builder.AddHtmlLocalized(305, 390, 150, 18, 1044151, LabelColor); // MAKE NOW
builder.AddButton(200, 387, 4005, 4007, 1);
builder.AddHtmlLocalized(235, 390, 80, 18, 1044151, LabelColor); // MAKE NOW
// --- Queue System Controls ---
builder.AddHtml(320, 390, 40, 18, "<BASEFONT COLOR=#FFFFFF>Qty:</BASEFONT>");
builder.AddImageTiled(350, 387, 40, 22, 0xBBC);
builder.AddTextEntry(352, 388, 36, 20, LabelHue, 1, "1");
builder.AddButton(400, 387, 4005, 4007, 2);
builder.AddHtml(435, 390, 80, 18, "<BASEFONT COLOR=#FFFFFF>Add Queue</BASEFONT>");
}
if (_craftItem.NameNumber > 0)
@ -312,7 +320,7 @@ public class CraftGumpItem : DynamicGump
{
CraftItem.ShowCraftMenu(from, _craftSystem, _tool, null);
}
else // Make Button
else if (info.ButtonID == 1) // Make Button
{
var num = _craftSystem.CanCraft(from, _tool, _craftItem.ItemType);
@ -323,7 +331,6 @@ public class CraftGumpItem : DynamicGump
else
{
Type type = null;
var context = _craftSystem.GetContext(from);
if (context != null)
@ -340,5 +347,34 @@ public class CraftGumpItem : DynamicGump
_craftSystem.CreateItem(from, _craftItem.ItemType, type, _tool, _craftItem);
}
}
else if (info.ButtonID == 2) // Queue Button
{
var text = info.GetTextEntry(1);
if (!string.IsNullOrEmpty(text) && int.TryParse(text, out int amount) && amount > 0)
{
Type type = null;
var context = _craftSystem.GetContext(from);
if (context != null)
{
var res = _craftItem.UseSubRes2 ? _craftSystem.CraftSubRes2 : _craftSystem.CraftSubRes;
var resIndex = _craftItem.UseSubRes2 ? context.LastResourceIndex2 : context.LastResourceIndex;
if (resIndex > -1)
{
type = res.GetAt(resIndex).ItemType;
}
// Log the memory directly onto the player's clipboard
context.Queue.Add(new CraftQueueOrder(_craftItem, type, amount));
// Immediately render the side-panel queue window
from.SendGump(new CraftQueueGump(from, _craftSystem, _tool));
// Resend the details gump so they can queue additional orders
from.SendGump(new CraftGumpItem(from, _craftSystem, _craftItem, _tool));
}
}
}
}
}

View file

@ -1540,6 +1540,20 @@ namespace Server.Engines.Craft
from.AddToBackpack(item);
// --- Queue System Tally (Success) ---
{
var queueContext = craftSystem.GetContext(from);
var activeOrder = queueContext?.GetActiveOrder();
if (activeOrder != null && activeOrder.Item == this)
{
activeOrder.AmountCompleted++;
if (endquality == 2 || quality == 2)
{
activeOrder.AmountExceptional++;
}
}
}
if (from.AccessLevel > AccessLevel.Player)
{
CommandLogging.WriteLine(
@ -1548,6 +1562,10 @@ namespace Server.Engines.Craft
);
}
num = craftSystem.PlayEndingEffect(from, false, true, toolBroken, endquality, false, this);
ProcessPostCraft(from, craftSystem, tool, num);
return;
// from.PlaySound( 0x57 );
}
@ -1609,26 +1627,10 @@ namespace Server.Engines.Craft
)
);
}
else if (tool?.Deleted == false && tool.UsesRemaining > 0)
else
{
if (T2ACraftSystem.Enabled)
{
if (num > 0)
{
from.SendLocalizedMessage(num);
}
ShowCraftMenu(from, craftSystem, tool);
}
else
{
ShowCraftMenu(from, craftSystem, tool, num);
}
ProcessPostCraft(from, craftSystem, tool, num);
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
return;
}
@ -1684,17 +1686,20 @@ namespace Server.Engines.Craft
}
}
// --- Queue System Tally (Failure) ---
{
var queueContext = craftSystem.GetContext(from);
var activeOrder = queueContext?.GetActiveOrder();
if (activeOrder != null && activeOrder.Item == this)
{
activeOrder.AmountFailed++;
}
}
// SkillCheck failed.
num = craftSystem.PlayEndingEffect(from, true, true, toolBroken, endquality, false, this);
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
ShowCraftMenu(from, craftSystem, tool, num);
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
ProcessPostCraft(from, craftSystem, tool, num);
}
/// <summary>
@ -1806,7 +1811,7 @@ namespace Server.Engines.Craft
item = ItemType.CreateInstance<Item>();
}
if (item != null)
if (item != null)
{
// Stamped here, not in OnCraft: most craftables do not implement ICraftable.
item.PlayerConstructed = true;
@ -1822,33 +1827,23 @@ namespace Server.Engines.Craft
from.AddToBackpack(item);
// --- Queue System Tally (Success) ---
{
var queueContext = craftSystem.GetContext(from);
var activeOrder = queueContext?.GetActiveOrder();
if (activeOrder != null && activeOrder.Item == this)
{
activeOrder.AmountCompleted++;
if (endquality == 2 || quality == 2)
{
activeOrder.AmountExceptional++;
}
}
}
num = craftSystem.PlayEndingEffect(from, false, true, toolBroken, endquality, false, this);
if (T2ACraftSystem.Enabled)
{
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
if (num > 0)
{
from.SendLocalizedMessage(num);
}
ShowCraftMenu(from, craftSystem, tool);
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
}
else if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
ShowCraftMenu(from, craftSystem, tool, num);
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
ProcessPostCraft(from, craftSystem, tool, num);
return;
}
}
@ -1887,16 +1882,19 @@ namespace Server.Engines.Craft
}
}
// --- Queue System Tally (Failure) ---
{
var queueContext = craftSystem.GetContext(from);
var activeOrder = queueContext?.GetActiveOrder();
if (activeOrder != null && activeOrder.Item == this)
{
activeOrder.AmountFailed++;
}
}
num = craftSystem.PlayEndingEffect(from, true, true, toolBroken, endquality, false, this);
if (tool?.Deleted == false && tool.UsesRemaining > 0)
{
ShowCraftMenu(from, craftSystem, tool, num);
}
else if (num > 0)
{
from.SendLocalizedMessage(num);
}
ProcessPostCraft(from, craftSystem, tool, num);
}
private class InternalTimer : Timer
@ -2052,5 +2050,97 @@ namespace Server.Engines.Craft
from.SendGump(new CraftGump(from, system, tool, message));
}
/// <summary>
/// Scans the player's backpack for a valid, non-depleted tool for the specified craft system.
/// </summary>
public static BaseTool FindTool(Mobile from, CraftSystem system)
{
var pack = from.Backpack;
if (pack == null) return null;
foreach (var item in pack.FindItemsByType<BaseTool>())
{
if (item != null && !item.Deleted && item.UsesRemaining > 0 && item.CraftSystem == system)
{
return item;
}
}
return null;
}
public static void ProcessPostCraft(Mobile from, CraftSystem craftSystem, BaseTool tool, TextDefinition message = null)
{
// T2A Era Override: Bypass the modern queue system entirely
if (T2ACraftSystem.Enabled)
{
ShowCraftMenu(from, craftSystem, tool, message);
return;
}
var context = craftSystem.GetContext(from);
// Auto-swap: If current tool broke or depleted, look for a replacement in the backpack
if (tool == null || tool.Deleted || tool.UsesRemaining <= 0)
{
tool = FindTool(from, craftSystem);
}
// If we have a valid tool
if (tool != null && !tool.Deleted && tool.UsesRemaining > 0)
{
if (context != null && context.Queue.Count > 0)
{
from.SendGump(new CraftQueueGump(from, craftSystem, tool));
}
var activeOrder = context?.GetActiveOrder();
// ONLY kick off the next tick if the queue is explicitly set to running!
if (activeOrder != null && context != null && context.IsQueueRunning)
{
activeOrder.Item.Craft(from, craftSystem, activeOrder.ResourceType, tool);
}
else
{
// If finished or stopped, reset running state and reopen menu
if (context != null)
{
context.IsQueueRunning = false;
}
ShowCraftMenu(from, craftSystem, tool, message);
from.CloseGump<CraftQueueGump>();
from.SendGump(new CraftQueueGump(from, craftSystem, tool));
}
}
else
{
if (context != null)
{
context.IsQueueRunning = false;
}
// No valid tool remaining in backpack: close gumps cleanly
from.CloseGump<CraftQueueGump>();
from.CloseGump<CraftGump>();
from.CloseGump<CraftGumpItem>();
if (message != null)
{
if (message.Number > 0)
{
from.SendLocalizedMessage(message.Number);
}
else if (!string.IsNullOrEmpty(message.String))
{
from.SendMessage(message.String);
}
}
else
{
from.SendMessage("Your tool broke and no replacement was found in your backpack.");
}
}
}
}
}

View file

@ -0,0 +1,26 @@
using System;
namespace Server.Engines.Craft;
public class CraftQueueOrder
{
public CraftItem Item { get; }
public Type ResourceType { get; }
public int AmountRequested { get; set; }
public int AmountCompleted { get; set; }
public int AmountExceptional { get; set; }
public int AmountFailed { get; set; }
public bool IsCompleted { get; set; }
public CraftQueueOrder(CraftItem item, Type resourceType, int amountRequested)
{
Item = item;
ResourceType = resourceType;
AmountRequested = amountRequested;
}
/// <summary>
/// Returns true if the order is explicitly flagged complete or has reached its target count.
/// </summary>
public bool IsFinished => IsCompleted || AmountCompleted >= AmountRequested;
}

View file

@ -0,0 +1,219 @@
using System;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using System.Globalization;
using Server;
namespace Server.Engines.Craft;
public class CraftQueueGump : DynamicGump
{
private const int LabelColor = 0x7FFF;
private const int GreyLabelColor = 0x3DEF;
private readonly Mobile _from;
private readonly CraftSystem _system;
private readonly BaseTool _tool;
public override bool Singleton => true;
public CraftQueueGump(Mobile from, CraftSystem system, BaseTool tool) : base(575, 40)
{
_from = from;
_system = system;
_tool = tool;
}
private static bool GetResourceDetails(Type resourceType, out string name, out int hue)
{
name = null;
hue = 0;
if (resourceType == null)
return false;
CraftResource resource = CraftResources.GetFromType(resourceType);
if (resource == CraftResource.None ||
resource == CraftResource.Iron ||
resource == CraftResource.RegularWood ||
resource == CraftResource.RegularLeather)
{
return false;
}
CraftResourceInfo info = CraftResources.GetInfo(resource);
if (info == null)
return false;
name = info.Name;
hue = info.Hue;
return true;
}
protected override void BuildLayout(ref DynamicGumpBuilder builder)
{
var context = _system.GetContext(_from);
if (context == null) return;
builder.AddPage(0);
builder.AddBackground(0, 0, 300, 437, 5054);
builder.AddImageTiled(10, 10, 280, 22, 2624);
builder.AddAlphaRegion(10, 10, 280, 417);
builder.AddHtml(10, 12, 280, 20, "<CENTER><BASEFONT COLOR=#FFCC00>Crafting Queue</BASEFONT></CENTER>");
// Bottom Action Controls
builder.AddButton(15, 402, 4017, 4019, 0);
builder.AddHtml(50, 405, 60, 18, "<BASEFONT COLOR=#FFFFFF>Close</BASEFONT>");
// Start/Stop button
if (context.IsQueueRunning)
{
builder.AddButton(110, 402, 4005, 4007, 1);
builder.AddHtml(145, 405, 60, 18, "<BASEFONT COLOR=#FFFFFF>Stop</BASEFONT>");
}
else
{
builder.AddButton(110, 402, 4005, 4007, 1);
builder.AddHtml(145, 405, 60, 18, "<BASEFONT COLOR=#FFFFFF>Start</BASEFONT>");
}
// Clear queue button
builder.AddButton(205, 402, 4014, 4016, 2);
builder.AddHtml(240, 405, 60, 18, "<BASEFONT COLOR=#FFFFFF>Clear</BASEFONT>");
var queue = context.Queue;
if (queue.Count == 0)
{
builder.AddPage(1);
builder.AddHtml(10, 50, 280, 20, "<CENTER><BASEFONT COLOR=#808080>Queue is empty.</BASEFONT></CENTER>");
return;
}
const int itemsPerPage = 7;
int pageCount = (queue.Count + itemsPerPage - 1) / itemsPerPage;
for (int page = 1; page <= pageCount; page++)
{
builder.AddPage(page);
int startIndex = (page - 1) * itemsPerPage;
int endIndex = Math.Min(startIndex + itemsPerPage, queue.Count);
int y = 40;
for (int i = startIndex; i < endIndex; i++)
{
var order = queue[i];
int textColor = order.IsFinished ? GreyLabelColor : LabelColor;
string statColor = order.IsFinished ? "#808080" : "#FFFFFF";
int xPos = 15;
if (GetResourceDetails(order.ResourceType, out string resName, out int resHue))
{
int resourceLabelHue = order.IsFinished ? 0x384 : resHue;
string formattedResName = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(resName);
builder.AddLabel(xPos, y, 1152, formattedResName);
xPos += (formattedResName.Length * 7) + 5;
}
// Line 1: Localized Cliloc Name or Custom String Name
if (order.Item.NameNumber > 0)
{
builder.AddHtmlLocalized(xPos, y, 160, 20, order.Item.NameNumber, textColor);
}
else
{
builder.AddLabel(xPos, y, order.IsFinished ? 0x384 : 0x480, order.Item.NameString ?? "Unknown Item");
}
// Line 2: Stats (Crafted / Exc / Fail)
builder.AddHtml(
25,
y + 18,
260,
20,
$"<BASEFONT COLOR={statColor}>({order.AmountCompleted}/{order.AmountRequested}) | Exc: {order.AmountExceptional} | Fail: {order.AmountFailed}</BASEFONT>"
);
y += 46;
}
// Page Navigation Controls
if (page > 1)
{
builder.AddButton(15, 375, 4014, 4016, 0, GumpButtonType.Page, page - 1);
}
if (page < pageCount)
{
builder.AddButton(250, 375, 4005, 4007, 0, GumpButtonType.Page, page + 1);
}
}
}
public override void OnResponse(NetState sender, in RelayInfo info)
{
var context = _system.GetContext(_from);
if (context == null) return;
switch (info.ButtonID)
{
case 0: // Close
break;
case 1: // Start / Stop Toggle
if (context.IsQueueRunning)
{
// Player clicked STOP: set running flag to false
context.IsQueueRunning = false;
_from.SendMessage("Crafting queue stopped.");
_from.CloseGump<CraftQueueGump>();
_from.SendGump(new CraftQueueGump(_from, _system, _tool));
}
else
{
// Player clicked START
var active = context.GetActiveOrder();
if (active != null)
{
var currentTool = _tool;
if (currentTool == null || currentTool.Deleted || currentTool.UsesRemaining <= 0)
{
currentTool = CraftItem.FindTool(_from, _system);
}
if (currentTool != null && !currentTool.Deleted && currentTool.UsesRemaining > 0)
{
context.IsQueueRunning = true; // Flag queue as actively running
_from.SendGump(new CraftQueueGump(_from, _system, currentTool));
active.Item.Craft(_from, _system, active.ResourceType, currentTool);
}
else
{
_from.SendMessage("You do not have a valid tool to start crafting.");
_from.CloseGump<CraftQueueGump>();
_from.CloseGump<CraftGump>();
_from.CloseGump<CraftGumpItem>();
}
}
else
{
_from.SendMessage("There are no pending items in your queue.");
_from.SendGump(new CraftQueueGump(_from, _system, _tool));
}
}
break;
case 2: // Clear
context.IsQueueRunning = false;
context.Queue.Clear();
_from.SendGump(new CraftQueueGump(_from, _system, _tool));
break;
}
}
}