using System; using TrueCraft.API.Logic; using TrueCraft.API; using TrueCraft.API.Networking; using TrueCraft.API.World; using TrueCraft.Core.Windows; using TrueCraft.API.Windows; using System.Collections.Generic; using fNbt; using TrueCraft.API.Server; using TrueCraft.Core.Networking.Packets; using TrueCraft.Core.Entities; namespace TrueCraft.Core.Logic.Blocks { public class FurnaceBlock : BlockProvider, ICraftingRecipe { protected class FurnaceState { public short BurnTimeRemaining { get; set; } public short BurnTimeTotal { get; set; } public short CookTime { get; set; } public ItemStack[] Items { get; set; } public FurnaceState() { Items = new ItemStack[3]; } } protected class FurnaceEventSubject : IEventSubject { public event EventHandler Disposed; public void Dispose() { if (Disposed != null) Dispose(); } } public static readonly byte BlockID = 0x3D; public override byte ID { get { return 0x3D; } } public override double BlastResistance { get { return 17.5; } } public override double Hardness { get { return 3.5; } } public override byte Luminance { get { return 0; } } public override string DisplayName { get { return "Furnace"; } } public override ToolType EffectiveTools { get { return ToolType.Pickaxe; } } protected override ItemStack[] GetDrop(BlockDescriptor descriptor, ItemStack item) { return new[] { new ItemStack(BlockID) }; } protected static Dictionary TrackedFurnaces { get; set; } protected static Dictionary> TrackedFurnaceWindows { get; set; } public FurnaceBlock() { TrackedFurnaces = new Dictionary(); TrackedFurnaceWindows = new Dictionary>(); } private NbtCompound CreateTileEntity() { return new NbtCompound(new NbtTag[] { new NbtShort("BurnTime", 0), new NbtShort("BurnTotal", 0), new NbtShort("CookTime", -1), new NbtList("Items", new[] { ItemStack.EmptyStack.ToNbt(), ItemStack.EmptyStack.ToNbt(), ItemStack.EmptyStack.ToNbt() }, NbtTagType.Compound) }); } private FurnaceState GetState(IWorld world, Coordinates3D coords) { var tileEntity = world.GetTileEntity(coords); if (tileEntity == null) tileEntity = CreateTileEntity(); var burnTime = tileEntity.Get("BurnTime"); var burnTotal = tileEntity.Get("BurnTotal"); var cookTime = tileEntity.Get("CookTime"); var state = new FurnaceState { BurnTimeTotal = burnTotal == null ? (short)0 : burnTotal.Value, BurnTimeRemaining = burnTime == null ? (short)0 : burnTime.Value, CookTime = cookTime == null ? (short)200 : cookTime.Value }; var items = tileEntity.Get("Items"); if (items != null) { int i = 0; foreach (var item in items) state.Items[i++] = ItemStack.FromNbt((NbtCompound)item); } return state; } private void UpdateWindows(Coordinates3D coords, FurnaceState state) { if (TrackedFurnaceWindows.ContainsKey(coords)) { Handling = true; foreach (var window in TrackedFurnaceWindows[coords]) { window[0] = state.Items[0]; window[1] = state.Items[1]; window[2] = state.Items[2]; window.Client.QueuePacket(new UpdateProgressPacket( window.ID, UpdateProgressPacket.ProgressTarget.ItemCompletion, state.CookTime)); var burnProgress = state.BurnTimeRemaining / (double)state.BurnTimeTotal; var burn = (short)(burnProgress * 250); window.Client.QueuePacket(new UpdateProgressPacket( window.ID, UpdateProgressPacket.ProgressTarget.AvailableHeat, burn)); } Handling = false; } } private void SetState(IWorld world, Coordinates3D coords, FurnaceState state) { world.SetTileEntity(coords, new NbtCompound(new NbtTag[] { new NbtShort("BurnTime", state.BurnTimeRemaining), new NbtShort("BurnTotal", state.BurnTimeTotal), new NbtShort("CookTime", state.CookTime), new NbtList("Items", new[] { state.Items[0].ToNbt(), state.Items[1].ToNbt(), state.Items[2].ToNbt() }, NbtTagType.Compound) })); UpdateWindows(coords, state); } public override void BlockLoadedFromChunk(Coordinates3D coords, IMultiplayerServer server, IWorld world) { var state = GetState(world, coords); TryInitializeFurnace(state, server.Scheduler, world, coords, server.ItemRepository); } public override void BlockMined(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) { var entity = world.GetTileEntity(descriptor.Coordinates); if (entity != null) { foreach (var item in (NbtList)entity["Items"]) { var manager = user.Server.GetEntityManagerForWorld(world); var slot = ItemStack.FromNbt((NbtCompound)item); manager.SpawnEntity(new ItemEntity(descriptor.Coordinates + new Vector3(0.5), slot)); } world.SetTileEntity(descriptor.Coordinates, null); } base.BlockMined(descriptor, face, world, user); } public override bool BlockRightClicked(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) { var window = new FurnaceWindow(user.Server.Scheduler, descriptor.Coordinates, user.Server.ItemRepository, (InventoryWindow)user.Inventory); var state = GetState(world, descriptor.Coordinates); for (int i = 0; i < state.Items.Length; i++) window[i] = state.Items[i]; user.OpenWindow(window); if (!TrackedFurnaceWindows.ContainsKey(descriptor.Coordinates)) TrackedFurnaceWindows[descriptor.Coordinates] = new List(); TrackedFurnaceWindows[descriptor.Coordinates].Add(window); window.Disposed += (sender, e) => TrackedFurnaceWindows.Remove(descriptor.Coordinates); UpdateWindows(descriptor.Coordinates, state); // TODO: Set window progress appropriately window.WindowChange += (sender, e) => FurnaceWindowChanged(sender, e, world); return false; } private bool Handling = false; protected void FurnaceWindowChanged(object sender, WindowChangeEventArgs e, IWorld world) { if (Handling) return; var window = sender as FurnaceWindow; var index = e.SlotIndex; if (index >= FurnaceWindow.MainIndex) return; Handling = true; e.Handled = true; window[index] = e.Value; var state = GetState(world, window.Coordinates); state.Items[0] = window[0]; state.Items[1] = window[1]; state.Items[2] = window[2]; SetState(world, window.Coordinates, state); Handling = true; if (!TrackedFurnaces.ContainsKey(window.Coordinates)) { // Set up the initial state TryInitializeFurnace(state, window.EventScheduler, world, window.Coordinates, window.ItemRepository); } Handling = false; } private void TryInitializeFurnace(FurnaceState state, IEventScheduler scheduler, IWorld world, Coordinates3D coords, IItemRepository itemRepository) { if (TrackedFurnaces.ContainsKey(coords)) return; var inputStack = state.Items[FurnaceWindow.IngredientIndex]; var fuelStack = state.Items[FurnaceWindow.FuelIndex]; var outputStack = state.Items[FurnaceWindow.OutputIndex]; var input = itemRepository.GetItemProvider(inputStack.ID) as ISmeltableItem; var fuel = itemRepository.GetItemProvider(fuelStack.ID) as IBurnableItem; if (state.BurnTimeRemaining > 0) { if (state.CookTime == -1 && input != null && (outputStack.Empty || outputStack.CanMerge(input.SmeltingOutput))) { state.CookTime = 0; SetState(world, coords, state); } var subject = new FurnaceEventSubject(); TrackedFurnaces[coords] = subject; scheduler.ScheduleEvent("smelting", subject, TimeSpan.FromSeconds(1), server => UpdateFurnace(server.Scheduler, world, coords, itemRepository)); return; } if (fuel != null && input != null) // We can maybe start { if (outputStack.Empty || outputStack.CanMerge(input.SmeltingOutput)) { // We can definitely start state.BurnTimeRemaining = state.BurnTimeTotal = (short)(fuel.BurnTime.TotalSeconds * 20); state.CookTime = 0; state.Items[FurnaceWindow.FuelIndex].Count--; SetState(world, coords, state); world.SetBlockID(coords, LitFurnaceBlock.BlockID); var subject = new FurnaceEventSubject(); TrackedFurnaces[coords] = subject; scheduler.ScheduleEvent("smelting", subject, TimeSpan.FromSeconds(1), server => UpdateFurnace(server.Scheduler, world, coords, itemRepository)); } } } private void UpdateFurnace(IEventScheduler scheduler, IWorld world, Coordinates3D coords, IItemRepository itemRepository) { if (TrackedFurnaces.ContainsKey(coords)) TrackedFurnaces.Remove(coords); if (world.GetBlockID(coords) != FurnaceBlock.BlockID && world.GetBlockID(coords) != LitFurnaceBlock.BlockID) { /*if (window != null && !window.IsDisposed) window.Dispose();*/ return; } var state = GetState(world, coords); var inputStack = state.Items[FurnaceWindow.IngredientIndex]; var outputStack = state.Items[FurnaceWindow.OutputIndex]; var input = itemRepository.GetItemProvider(inputStack.ID) as ISmeltableItem; // Update burn time var burnTime = state.BurnTimeRemaining; if (state.BurnTimeRemaining > 0) { state.BurnTimeRemaining -= 20; // ticks if (state.BurnTimeRemaining <= 0) { state.BurnTimeRemaining = 0; state.BurnTimeTotal = 0; world.SetBlockID(coords, FurnaceBlock.BlockID); } } // Update cook time if (state.CookTime < 200 && state.CookTime >= 0) { state.CookTime += 20; // ticks if (state.CookTime >= 200) state.CookTime = 200; } // Are we done cooking? if (state.CookTime == 200 && burnTime > 0) { state.CookTime = -1; if (input != null && (outputStack.Empty || outputStack.CanMerge(input.SmeltingOutput))) { if (outputStack.Empty) outputStack = input.SmeltingOutput; else if (outputStack.CanMerge(input.SmeltingOutput)) outputStack.Count += input.SmeltingOutput.Count; state.Items[FurnaceWindow.OutputIndex] = outputStack; state.Items[FurnaceWindow.IngredientIndex].Count--; } } SetState(world, coords, state); TryInitializeFurnace(state, scheduler, world, coords, itemRepository); } public override Tuple GetTextureMap(byte metadata) { return new Tuple(13, 2); } public ItemStack[,] Pattern { get { return new[,] { { new ItemStack(CobblestoneBlock.BlockID), new ItemStack(CobblestoneBlock.BlockID), new ItemStack(CobblestoneBlock.BlockID) }, { new ItemStack(CobblestoneBlock.BlockID), ItemStack.EmptyStack, new ItemStack(CobblestoneBlock.BlockID) }, { new ItemStack(CobblestoneBlock.BlockID), new ItemStack(CobblestoneBlock.BlockID), new ItemStack(CobblestoneBlock.BlockID) } }; } } public ItemStack Output { get { return new ItemStack(BlockID); } } public bool SignificantMetadata { get { return false; } } public override void BlockPlaced(BlockDescriptor descriptor, BlockFace face, IWorld world, IRemoteClient user) { world.SetMetadata(descriptor.Coordinates, (byte)MathHelper.DirectionByRotationFlat(user.Entity.Yaw, true)); } } public class LitFurnaceBlock : FurnaceBlock { public static readonly new byte BlockID = 0x3E; public override byte ID { get { return 0x3E; } } public override byte Luminance { get { return 13; } } public override bool Opaque { get { return false; } } public override string DisplayName { get { return "Furnace (lit)"; } } } }