diff --git a/Projects/UOContent/Engines/Craft/Core/CraftContext.cs b/Projects/UOContent/Engines/Craft/Core/CraftContext.cs index 8bd215839..41377fbfd 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftContext.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftContext.cs @@ -15,6 +15,7 @@ namespace Server.Engines.Craft public CraftContext() { Items = new List(); + Queue = new List(); LastResourceIndex = -1; LastResourceIndex2 = -1; LastGroupIndex = -1; @@ -22,6 +23,8 @@ namespace Server.Engines.Craft public List Items { get; } + public List 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 } } + /// + /// Retrieves the first uncompleted work order in the queue. + /// + 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); diff --git a/Projects/UOContent/Engines/Craft/Core/CraftGump.cs b/Projects/UOContent/Engines/Craft/Core/CraftGump.cs index 1dd0f4bd2..41b18629f 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftGump.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftGump.cs @@ -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; diff --git a/Projects/UOContent/Engines/Craft/Core/CraftGumpItem.cs b/Projects/UOContent/Engines/Craft/Core/CraftGumpItem.cs index c8e9a5fc2..c4a743da7 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftGumpItem.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftGumpItem.cs @@ -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, "Qty:"); + 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, "Add Queue"); } 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)); + } + } + } } } diff --git a/Projects/UOContent/Engines/Craft/Core/CraftItem.cs b/Projects/UOContent/Engines/Craft/Core/CraftItem.cs index a2ed9e045..a951a5877 100644 --- a/Projects/UOContent/Engines/Craft/Core/CraftItem.cs +++ b/Projects/UOContent/Engines/Craft/Core/CraftItem.cs @@ -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); } /// @@ -1806,7 +1811,7 @@ namespace Server.Engines.Craft item = ItemType.CreateInstance(); } - 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)); } + + /// + /// Scans the player's backpack for a valid, non-depleted tool for the specified craft system. + /// + public static BaseTool FindTool(Mobile from, CraftSystem system) + { + var pack = from.Backpack; + if (pack == null) return null; + + foreach (var item in pack.FindItemsByType()) + { + 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(); + 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(); + from.CloseGump(); + from.CloseGump(); + + 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."); + } + } + } } } diff --git a/Projects/UOContent/Engines/Craft/Core/CraftQueueOrder.cs b/Projects/UOContent/Engines/Craft/Core/CraftQueueOrder.cs new file mode 100644 index 000000000..921e0d1a7 --- /dev/null +++ b/Projects/UOContent/Engines/Craft/Core/CraftQueueOrder.cs @@ -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; + } + + /// + /// Returns true if the order is explicitly flagged complete or has reached its target count. + /// + public bool IsFinished => IsCompleted || AmountCompleted >= AmountRequested; +} diff --git a/Projects/UOContent/Engines/Craft/Core/CraftingQueueGump.cs b/Projects/UOContent/Engines/Craft/Core/CraftingQueueGump.cs new file mode 100644 index 000000000..b8ac998c8 --- /dev/null +++ b/Projects/UOContent/Engines/Craft/Core/CraftingQueueGump.cs @@ -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, "
Crafting Queue
"); + + // Bottom Action Controls + builder.AddButton(15, 402, 4017, 4019, 0); + builder.AddHtml(50, 405, 60, 18, "Close"); + + // Start/Stop button + if (context.IsQueueRunning) + { + builder.AddButton(110, 402, 4005, 4007, 1); + builder.AddHtml(145, 405, 60, 18, "Stop"); + } + else + { + builder.AddButton(110, 402, 4005, 4007, 1); + builder.AddHtml(145, 405, 60, 18, "Start"); + } + + // Clear queue button + builder.AddButton(205, 402, 4014, 4016, 2); + builder.AddHtml(240, 405, 60, 18, "Clear"); + + var queue = context.Queue; + + if (queue.Count == 0) + { + builder.AddPage(1); + builder.AddHtml(10, 50, 280, 20, "
Queue is empty.
"); + 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, + $"({order.AmountCompleted}/{order.AmountRequested}) | Exc: {order.AmountExceptional} | Fail: {order.AmountFailed}" + ); + + 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(); + _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(); + _from.CloseGump(); + _from.CloseGump(); + } + } + 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; + } + } +}