diff --git a/TrueCraft.API/Networking/IRemoteClient.cs b/TrueCraft.API/Networking/IRemoteClient.cs index 3fed5ff..87ab33e 100644 --- a/TrueCraft.API/Networking/IRemoteClient.cs +++ b/TrueCraft.API/Networking/IRemoteClient.cs @@ -22,5 +22,6 @@ namespace TrueCraft.API.Networking void QueuePacket(IPacket packet); void SendMessage(string message); void Log(string message, params object[] parameters); + void OpenWindow(IWindow window); } } \ No newline at end of file diff --git a/TrueCraft.API/Windows/IWindow.cs b/TrueCraft.API/Windows/IWindow.cs index c12e4a8..3938b93 100644 --- a/TrueCraft.API/Windows/IWindow.cs +++ b/TrueCraft.API/Windows/IWindow.cs @@ -7,6 +7,9 @@ namespace TrueCraft.API.Windows event EventHandler WindowChange; IWindowArea[] WindowAreas { get; } + sbyte ID { get; set; } + string Name { get; } + sbyte Type { get; } int Length { get; } ItemStack this[int index] { get; set; } bool Empty { get; } @@ -23,5 +26,9 @@ namespace TrueCraft.API.Windows /// Adds the specified item stack to this window, merging with established slots as neccessary. /// bool PickUpStack(ItemStack slot); + /// + /// Copy the contents of this window back into an inventory window after changes have been made. + /// + void CopyToInventory(IWindow inventoryWindow); } } \ No newline at end of file diff --git a/TrueCraft.Core/Logic/Blocks/CraftingTableBlock.cs b/TrueCraft.Core/Logic/Blocks/CraftingTableBlock.cs index 821c2dd..42aa522 100644 --- a/TrueCraft.Core/Logic/Blocks/CraftingTableBlock.cs +++ b/TrueCraft.Core/Logic/Blocks/CraftingTableBlock.cs @@ -1,6 +1,9 @@ using System; using TrueCraft.API.Logic; using TrueCraft.API; +using TrueCraft.API.World; +using TrueCraft.API.Networking; +using TrueCraft.Core.Windows; namespace TrueCraft.Core.Logic.Blocks { @@ -18,6 +21,13 @@ namespace TrueCraft.Core.Logic.Blocks public override string DisplayName { get { return "Crafting Table"; } } + public override bool BlockRightClicked(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) + { + var window = new CraftingBenchWindow(user.Server.CraftingRepository, (InventoryWindow)user.Inventory); + user.OpenWindow(window); + return false; + } + public override Tuple GetTextureMap(byte metadata) { return new Tuple(11, 3); diff --git a/TrueCraft.Core/Logic/Items/BedItem.cs b/TrueCraft.Core/Logic/Items/BedItem.cs index 3292f31..b5c8033 100644 --- a/TrueCraft.Core/Logic/Items/BedItem.cs +++ b/TrueCraft.Core/Logic/Items/BedItem.cs @@ -7,7 +7,7 @@ using TrueCraft.Core.Logic.Blocks; namespace TrueCraft.Core.Logic.Items { - public class BedItem : ItemProvider + public class BedItem : ItemProvider, ICraftingRecipe { public static readonly short ItemID = 0x163; @@ -59,5 +59,33 @@ namespace TrueCraft.Core.Logic.Items item.Count--; user.Inventory[user.SelectedSlot] = item; } + + public ItemStack[,] Pattern + { + get + { + return new[,] + { + { + new ItemStack(WoolBlock.BlockID), + new ItemStack(WoolBlock.BlockID), + new ItemStack(WoolBlock.BlockID), + }, + { + new ItemStack(WoodenPlanksBlock.BlockID), + new ItemStack(WoodenPlanksBlock.BlockID), + new ItemStack(WoodenPlanksBlock.BlockID), + } + }; + } + } + + public ItemStack Output + { + get + { + return new ItemStack(ItemID); + } + } } } \ No newline at end of file diff --git a/TrueCraft.Core/Networking/PacketReader.cs b/TrueCraft.Core/Networking/PacketReader.cs index 7608edc..e708247 100644 --- a/TrueCraft.Core/Networking/PacketReader.cs +++ b/TrueCraft.Core/Networking/PacketReader.cs @@ -72,7 +72,7 @@ namespace TrueCraft.Core.Networking RegisterPacketType(serverbound: false, clientbound: true); // 0x47 RegisterPacketType(serverbound: false, clientbound: true); // 0x64 - RegisterPacketType(); // 0x65 + RegisterPacketType(); // 0x65 RegisterPacketType(serverbound: true, clientbound: false); // 0x66 RegisterPacketType(serverbound: false, clientbound: true); // 0x67 RegisterPacketType(serverbound: false, clientbound: true); // 0x68 diff --git a/TrueCraft.Core/Networking/Packets/CloseWindow.cs b/TrueCraft.Core/Networking/Packets/CloseWindowPacket.cs similarity index 79% rename from TrueCraft.Core/Networking/Packets/CloseWindow.cs rename to TrueCraft.Core/Networking/Packets/CloseWindowPacket.cs index c781ebe..060bd07 100644 --- a/TrueCraft.Core/Networking/Packets/CloseWindow.cs +++ b/TrueCraft.Core/Networking/Packets/CloseWindowPacket.cs @@ -6,10 +6,15 @@ namespace TrueCraft.Core.Networking.Packets /// /// Sent by the server to forcibly close an inventory window, or from the client when naturally closed. /// - public struct CloseWindow : IPacket + public struct CloseWindowPacket : IPacket { public byte ID { get { return 0x65; } } + public CloseWindowPacket(sbyte windowID) + { + WindowID = windowID; + } + public sbyte WindowID; public void ReadPacket(IMinecraftStream stream) diff --git a/TrueCraft.Core/Networking/Packets/OpenWindowPacket.cs b/TrueCraft.Core/Networking/Packets/OpenWindowPacket.cs index e15a107..d1c3a60 100644 --- a/TrueCraft.Core/Networking/Packets/OpenWindowPacket.cs +++ b/TrueCraft.Core/Networking/Packets/OpenWindowPacket.cs @@ -8,25 +8,25 @@ namespace TrueCraft.Core.Networking.Packets /// public struct OpenWindowPacket : IPacket { - public enum WindowType - { - Chest = 0, - Workbench = 1, - Furnace = 2, - Dispenser = 3 - } - public byte ID { get { return 0x64; } } + public OpenWindowPacket(sbyte windowID, sbyte type, string title, sbyte totalSlots) + { + WindowID = windowID; + Type = type; + Title = title; + TotalSlots = totalSlots; + } + public sbyte WindowID; - public WindowType Type; + public sbyte Type; public string Title; public sbyte TotalSlots; public void ReadPacket(IMinecraftStream stream) { WindowID = stream.ReadInt8(); - Type = (WindowType)stream.ReadInt8(); + Type = stream.ReadInt8(); Title = stream.ReadString8(); TotalSlots = stream.ReadInt8(); } @@ -34,7 +34,7 @@ namespace TrueCraft.Core.Networking.Packets public void WritePacket(IMinecraftStream stream) { stream.WriteInt8(WindowID); - stream.WriteInt8((sbyte)Type); + stream.WriteInt8(Type); stream.WriteString8(Title); stream.WriteInt8(TotalSlots); } diff --git a/TrueCraft.Core/TrueCraft.Core.csproj b/TrueCraft.Core/TrueCraft.Core.csproj index d69e2b3..5be8cf5 100644 --- a/TrueCraft.Core/TrueCraft.Core.csproj +++ b/TrueCraft.Core/TrueCraft.Core.csproj @@ -153,7 +153,6 @@ - @@ -270,6 +269,8 @@ + + diff --git a/TrueCraft.Core/Windows/CraftingBenchWindow.cs b/TrueCraft.Core/Windows/CraftingBenchWindow.cs new file mode 100644 index 0000000..fff87e0 --- /dev/null +++ b/TrueCraft.Core/Windows/CraftingBenchWindow.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using TrueCraft.API.Windows; +using TrueCraft.API.Logic; +using TrueCraft.API; + +namespace TrueCraft.Core.Windows +{ + public class CraftingBenchWindow : Window + { + public CraftingBenchWindow(ICraftingRepository craftingRepository, InventoryWindow inventory) + { + WindowAreas = new[] + { + new CraftingWindowArea(craftingRepository, CraftingOutputIndex, 3, 3), + new WindowArea(MainIndex, 27, 9, 3), // Main inventory + new WindowArea(HotbarIndex, 9, 9, 1) // Hotbar + }; + inventory.MainInventory.CopyTo(MainInventory); + inventory.Hotbar.CopyTo(Hotbar); + foreach (var area in WindowAreas) + area.WindowChange += (s, e) => OnWindowChange(new WindowChangeEventArgs( + (s as WindowArea).StartIndex + e.SlotIndex, e.Value)); + Copying = false; + inventory.WindowChange += (sender, e) => + { + if (Copying) return; + if ((e.SlotIndex >= InventoryWindow.MainIndex && e.SlotIndex < InventoryWindow.MainIndex + inventory.MainInventory.Length) + || (e.SlotIndex >= InventoryWindow.HotbarIndex && e.SlotIndex < InventoryWindow.HotbarIndex + inventory.Hotbar.Length)) + { + inventory.MainInventory.CopyTo(MainInventory); + inventory.Hotbar.CopyTo(Hotbar); + } + }; + } + + #region Variables + + private bool Copying { get; set; } + + public const int HotbarIndex = 37; + public const int CraftingGridIndex = 1; + public const int CraftingOutputIndex = 0; + public const int MainIndex = 10; + + public override string Name + { + get + { + return "Workbench"; + } + } + + public override sbyte Type + { + get + { + return 1; + } + } + + public override IWindowArea[] WindowAreas { get; protected set; } + + #region Properties + + public IWindowArea CraftingGrid + { + get { return WindowAreas[0]; } + } + + public IWindowArea MainInventory + { + get { return WindowAreas[1]; } + } + + public IWindowArea Hotbar + { + get { return WindowAreas[2]; } + } + + #endregion + + #endregion + + public override ItemStack[] GetSlots() + { + var relevantAreas = new[] { CraftingGrid }; + int length = relevantAreas.Sum(area => area.Length); + var slots = new ItemStack[length]; + foreach (var windowArea in relevantAreas) + Array.Copy(windowArea.Items, 0, slots, windowArea.StartIndex, windowArea.Length); + return slots; + } + + public override void CopyToInventory(IWindow inventoryWindow) + { + var window = (InventoryWindow)inventoryWindow; + Copying = true; + MainInventory.CopyTo(window.MainInventory); + Hotbar.CopyTo(window.Hotbar); + Copying = false; + } + + protected override IWindowArea GetLinkedArea(int index, ItemStack slot) + { + if (index < MainIndex) + return MainInventory; + return Hotbar; + } + + public override bool PickUpStack(ItemStack slot) + { + var area = MainInventory; + foreach (var item in Hotbar.Items) + { + if (item.Empty || (slot.ID == item.ID && slot.Metadata == item.Metadata)) + //&& item.Count + slot.Count < Item.GetMaximumStackSize(new ItemDescriptor(item.Id, item.Metadata)))) // TODO + { + area = Hotbar; + break; + } + } + int index = area.MoveOrMergeItem(-1, slot, null); + return index != -1; + } + } +} diff --git a/TrueCraft.Core/Windows/CraftingWindowArea.cs b/TrueCraft.Core/Windows/CraftingWindowArea.cs index ac63b33..54fdd1e 100644 --- a/TrueCraft.Core/Windows/CraftingWindowArea.cs +++ b/TrueCraft.Core/Windows/CraftingWindowArea.cs @@ -9,7 +9,8 @@ namespace TrueCraft.Core.Windows public static readonly int CraftingOutput = 0; public ICraftingRepository Repository { get; set; } - public CraftingWindowArea(ICraftingRepository repository, int startIndex) : base(startIndex, 5, 2, 2) + public CraftingWindowArea(ICraftingRepository repository, int startIndex, int width = 2, int height = 2) + : base(startIndex, width * height + 1, width, height) { Repository = repository; WindowChange += HandleWindowChange; @@ -50,12 +51,12 @@ namespace TrueCraft.Core.Windows if (found) break; } // Remove items - for (int _x = 0; _x < recipe.Pattern.GetLength(0); _x++) + for (int _x = 0; _x < recipe.Pattern.GetLength(1); _x++) { - for (int _y = 0; _y < recipe.Pattern.GetLength(1); _y++) + for (int _y = 0; _y < recipe.Pattern.GetLength(0); _y++) { var item = Items[(y + _y) * Width + (x + _x) + 1]; - item.Count -= recipe.Pattern[_x, _y].Count; + item.Count -= recipe.Pattern[_y, _x].Count; Items[(y + _y) * Width + (x + _x) + 1] = item; } } @@ -65,7 +66,7 @@ namespace TrueCraft.Core.Windows { get { - var result = new WindowArea(1, 4, 2, 2); + var result = new WindowArea(1, Width * Height, Width, Height); for (var i = 1; i < Items.Length; i++) result.Items[i - 1] = Items[i]; return result; diff --git a/TrueCraft.Core/Windows/InventoryWindow.cs b/TrueCraft.Core/Windows/InventoryWindow.cs index 88557fd..6fa4aa9 100644 --- a/TrueCraft.Core/Windows/InventoryWindow.cs +++ b/TrueCraft.Core/Windows/InventoryWindow.cs @@ -32,6 +32,22 @@ namespace TrueCraft.Core.Windows public const int ArmorIndex = 5; public const int MainIndex = 9; + public override string Name + { + get + { + return "Inventory"; + } + } + + public override sbyte Type + { + get + { + return -1; // NOTE: This window does not have a type + } + } + public override IWindowArea[] WindowAreas { get; protected set; } #region Properties @@ -60,11 +76,13 @@ namespace TrueCraft.Core.Windows #endregion + public override void CopyToInventory(IWindow inventoryWindow) + { + // This space intentionally left blank + } + protected override IWindowArea GetLinkedArea(int index, ItemStack slot) { - // TODO: Link armor - //if (!slot.Empty && slot.AsItem() is IArmorItem && (index == 2 || index == 3)) - // return Armor; if (index == 0 || index == 1 || index == 3) return MainInventory; return Hotbar; diff --git a/TrueCraft.Core/Windows/Window.cs b/TrueCraft.Core/Windows/Window.cs index 03511be..c87193a 100644 --- a/TrueCraft.Core/Windows/Window.cs +++ b/TrueCraft.Core/Windows/Window.cs @@ -25,6 +25,10 @@ namespace TrueCraft.Core.Windows if (WindowChange != null && destination != -1) WindowChange(this, new WindowChangeEventArgs(destination + to.StartIndex, slot)); } + + public sbyte ID { get; set; } + public abstract string Name { get; } + public abstract sbyte Type { get; } /// /// When shift-clicking items between areas, this method is used @@ -34,6 +38,7 @@ namespace TrueCraft.Core.Windows /// The item being moved /// The area to place the item into protected abstract IWindowArea GetLinkedArea(int index, ItemStack slot); + public abstract void CopyToInventory(IWindow inventoryWindow); /// /// Gets the window area to handle this index and adjust index accordingly @@ -81,7 +86,7 @@ namespace TrueCraft.Core.Windows } } - public ItemStack[] GetSlots() + public virtual ItemStack[] GetSlots() { int length = WindowAreas.Sum(area => area.Length); var slots = new ItemStack[length]; diff --git a/TrueCraft/CraftingRepository.cs b/TrueCraft/CraftingRepository.cs index 092f052..c26e593 100644 --- a/TrueCraft/CraftingRepository.cs +++ b/TrueCraft/CraftingRepository.cs @@ -47,14 +47,14 @@ namespace TrueCraft public bool TestRecipe(IWindowArea craftingArea, ICraftingRecipe recipe, int x, int y) { - if (x + recipe.Pattern.GetLength(0) > craftingArea.Width || y + recipe.Pattern.GetLength(1) > craftingArea.Height) + if (x + recipe.Pattern.GetLength(1) > craftingArea.Width || y + recipe.Pattern.GetLength(0) > craftingArea.Height) return false; - for (int _x = 0; _x < recipe.Pattern.GetLength(0); _x++) + for (int _x = 0; _x < recipe.Pattern.GetLength(1); _x++) { - for (int _y = 0; _y < recipe.Pattern.GetLength(1); _y++) + for (int _y = 0; _y < recipe.Pattern.GetLength(0); _y++) { var supplied = craftingArea[(y + _y) * craftingArea.Width + (x + _x)]; - var required = recipe.Pattern[_x, _y]; + var required = recipe.Pattern[_y, _x]; if (supplied.ID != required.ID || supplied.Count < required.Count) { return false; @@ -70,14 +70,29 @@ namespace TrueCraft { for (int y = 0; y < craftingArea.Height; y++) { - var item = craftingArea[y * craftingArea.Width + x]; - if (item.ID == recipe.Pattern[0, 0].ID && - item.Count >= recipe.Pattern[0, 0].Count) + if (TestRecipe(craftingArea, recipe, x, y)) { - return TestRecipe(craftingArea, recipe, x, y); + // Check to make sure there aren't any sneaky unused items in the grid + for (int _x = 0; _x < x; x++) + { + for (int _y = 0; _y < y; _y++) + { + var supplied = craftingArea[(y + _y) * craftingArea.Width + (x + _x)]; + if (!supplied.Empty) + return false; + } + } + for (int _y = 0; _y < y; _y++) + { + for (int _x = 0; _x < x; x++) + { + var supplied = craftingArea[(y + _y) * craftingArea.Width + (x + _x)]; + if (!supplied.Empty) + return false; + } + } + return true; } - if (!item.Empty) - return false; } } return false; diff --git a/TrueCraft/Handlers/InteractionHandlers.cs b/TrueCraft/Handlers/InteractionHandlers.cs index bad7df6..9323ed3 100644 --- a/TrueCraft/Handlers/InteractionHandlers.cs +++ b/TrueCraft/Handlers/InteractionHandlers.cs @@ -124,7 +124,8 @@ namespace TrueCraft.Handlers return; ItemStack existing = window[packet.SlotIndex]; ItemStack held = client.ItemStaging; - if (packet.SlotIndex == InventoryWindow.CraftingOutputIndex) + if (packet.SlotIndex == InventoryWindow.CraftingOutputIndex + && (window is InventoryWindow || window is CraftingBenchWindow)) { // Stupid special case because Minecraft was written by morons if (held.ID == existing.ID || held.Empty) @@ -208,6 +209,13 @@ namespace TrueCraft.Handlers } } + public static void HandleCloseWindowPacket(IPacket _packet, IRemoteClient _client, IMultiplayerServer server) + { + var packet = (CloseWindowPacket)_packet; + if (packet.WindowID != 0) + (_client as RemoteClient).CloseWindow(true); + } + public static void HandleChangeHeldItem(IPacket _packet, IRemoteClient _client, IMultiplayerServer server) { var packet = (ChangeHeldItemPacket)_packet; diff --git a/TrueCraft/Handlers/PacketHandlers.cs b/TrueCraft/Handlers/PacketHandlers.cs index 6c52881..a18d418 100644 --- a/TrueCraft/Handlers/PacketHandlers.cs +++ b/TrueCraft/Handlers/PacketHandlers.cs @@ -29,6 +29,7 @@ namespace TrueCraft.Handlers server.RegisterPacketHandler(new ChangeHeldItemPacket().ID, InteractionHandlers.HandleChangeHeldItem); server.RegisterPacketHandler(new PlayerActionPacket().ID, InteractionHandlers.HandlePlayerAction); server.RegisterPacketHandler(new ClickWindowPacket().ID, InteractionHandlers.HandleClickWindowPacket); + server.RegisterPacketHandler(new CloseWindowPacket().ID, InteractionHandlers.HandleCloseWindowPacket); } internal static void HandleKeepAlive(IPacket _packet, IRemoteClient _client, IMultiplayerServer server) diff --git a/TrueCraft/RemoteClient.cs b/TrueCraft/RemoteClient.cs index e4a9c6d..db9905a 100644 --- a/TrueCraft/RemoteClient.cs +++ b/TrueCraft/RemoteClient.cs @@ -37,6 +37,7 @@ namespace TrueCraft KnownEntities = new List(); Disconnected = false; EnableLogging = server.EnableClientLogging; + NextWindowID = 1; } /// @@ -44,6 +45,7 @@ namespace TrueCraft /// internal List KnownEntities { get; set; } internal bool Disconnected { get; set; } + internal sbyte NextWindowID { get; set; } public NetworkStream NetworkStream { get; set; } public IMinecraftStream MinecraftStream { get; internal set; } @@ -55,7 +57,7 @@ namespace TrueCraft public IWindow Inventory { get; private set; } public short SelectedSlot { get; internal set; } public ItemStack ItemStaging { get; set; } - public IWindow CurrentWindow { get; set; } + public IWindow CurrentWindow { get; internal set; } public bool EnableLogging { get; set; } private IEntity _Entity; @@ -114,6 +116,23 @@ namespace TrueCraft } } + public void OpenWindow(IWindow window) + { + CurrentWindow = window; + window.ID = NextWindowID++; + if (NextWindowID < 0) NextWindowID = 1; + QueuePacket(new OpenWindowPacket(window.ID, window.Type, window.Name, (sbyte)window.Length)); + QueuePacket(new WindowItemsPacket(window.ID, window.GetSlots())); + } + + public void CloseWindow(bool clientInitiated = false) + { + if (!clientInitiated) + QueuePacket(new CloseWindowPacket(CurrentWindow.ID)); + CurrentWindow.CopyToInventory(Inventory); + CurrentWindow = InventoryWindow; + } + public void Log(string message, params object[] parameters) { if (EnableLogging)