Implement crafting benches

This commit is contained in:
Drew DeVault 2015-02-07 17:14:41 -07:00
parent a070e7498e
commit 7c4c2ef5fc
16 changed files with 284 additions and 36 deletions

View File

@ -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);
}
}

View File

@ -7,6 +7,9 @@ namespace TrueCraft.API.Windows
event EventHandler<WindowChangeEventArgs> 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.
/// </summary>
bool PickUpStack(ItemStack slot);
/// <summary>
/// Copy the contents of this window back into an inventory window after changes have been made.
/// </summary>
void CopyToInventory(IWindow inventoryWindow);
}
}

View File

@ -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<int, int> GetTextureMap(byte metadata)
{
return new Tuple<int, int>(11, 3);

View File

@ -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);
}
}
}
}

View File

@ -72,7 +72,7 @@ namespace TrueCraft.Core.Networking
RegisterPacketType<LightningPacket>(serverbound: false, clientbound: true); // 0x47
RegisterPacketType<OpenWindowPacket>(serverbound: false, clientbound: true); // 0x64
RegisterPacketType<CloseWindow>(); // 0x65
RegisterPacketType<CloseWindowPacket>(); // 0x65
RegisterPacketType<ClickWindowPacket>(serverbound: true, clientbound: false); // 0x66
RegisterPacketType<SetSlotPacket>(serverbound: false, clientbound: true); // 0x67
RegisterPacketType<WindowItemsPacket>(serverbound: false, clientbound: true); // 0x68

View File

@ -6,10 +6,15 @@ namespace TrueCraft.Core.Networking.Packets
/// <summary>
/// Sent by the server to forcibly close an inventory window, or from the client when naturally closed.
/// </summary>
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)

View File

@ -8,25 +8,25 @@ namespace TrueCraft.Core.Networking.Packets
/// </summary>
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);
}

View File

@ -153,7 +153,6 @@
<Compile Include="Networking\Packets\EnvironmentStatePacket.cs" />
<Compile Include="Networking\Packets\LightningPacket.cs" />
<Compile Include="Networking\Packets\OpenWindowPacket.cs" />
<Compile Include="Networking\Packets\CloseWindow.cs" />
<Compile Include="Networking\Packets\ClickWindowPacket.cs" />
<Compile Include="Networking\Packets\SetSlotPacket.cs" />
<Compile Include="Networking\Packets\WindowItemsPacket.cs" />
@ -270,6 +269,8 @@
<Compile Include="Entities\PlayerEntity.cs" />
<Compile Include="Logic\Blocks\AirBlock.cs" />
<Compile Include="Entities\EntityFlags.cs" />
<Compile Include="Windows\CraftingBenchWindow.cs" />
<Compile Include="Networking\Packets\CloseWindowPacket.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -26,6 +26,10 @@ namespace TrueCraft.Core.Windows
WindowChange(this, new WindowChangeEventArgs(destination + to.StartIndex, slot));
}
public sbyte ID { get; set; }
public abstract string Name { get; }
public abstract sbyte Type { get; }
/// <summary>
/// When shift-clicking items between areas, this method is used
/// to determine which area links to which.
@ -34,6 +38,7 @@ namespace TrueCraft.Core.Windows
/// <param name="slot">The item being moved</param>
/// <returns>The area to place the item into</returns>
protected abstract IWindowArea GetLinkedArea(int index, ItemStack slot);
public abstract void CopyToInventory(IWindow inventoryWindow);
/// <summary>
/// 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];

View File

@ -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,16 +70,31 @@ 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);
}
if (!item.Empty)
// 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;
}
}
}
return false;
}
}

View File

@ -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;

View File

@ -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)

View File

@ -37,6 +37,7 @@ namespace TrueCraft
KnownEntities = new List<IEntity>();
Disconnected = false;
EnableLogging = server.EnableClientLogging;
NextWindowID = 1;
}
/// <summary>
@ -44,6 +45,7 @@ namespace TrueCraft
/// </summary>
internal List<IEntity> 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)