diff --git a/server/MPClient.ts b/server/MPClient.ts index 5e0f57b..61c4c4d 100644 --- a/server/MPClient.ts +++ b/server/MPClient.ts @@ -34,6 +34,7 @@ import TileEntityChest from "./tileentities/TileEntityChest"; import WindowCrafting from "./windows/WindowCrafting"; import PacketWindowClick from "./packets/WindowClick"; import PlayerCombinedInventory from "./inventories/PlayerCombinedInventory"; +import PacketCloseWindow from "./packets/CloseWindow"; export default class MPClient { private readonly mcServer:MinecraftServer; @@ -98,6 +99,7 @@ export default class MPClient { //case Packets.UseBed: break; case Packet.Animation: this.handlePacketAnimation(new PacketAnimation().readData(reader)); break; case Packet.EntityAction: this.handlePacketEntityAction(new PacketEntityAction().readData(reader)); break; + case Packet.CloseWindow: this.handleWindowClose(new PacketCloseWindow().readData(reader)); break; case Packet.WindowClick: this.handleWindowClick(new PacketWindowClick().readData(reader)); break; case Packet.DisconnectKick: this.handleDisconnectKick(); break; default: return Console.printWarn(`UNIMPLEMENTED PACKET: ${Packet[packetId]} 0x${packetId < 10 ? `0${packetId.toString(16).toUpperCase()}` : packetId.toString(16).toUpperCase()}`); @@ -221,6 +223,26 @@ export default class MPClient { } } + private throwItemStack(itemStack: ItemStack) { + const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemStack.itemID, 1, itemStack.damage)); + itemEntity.pickupDelay = 10; + itemEntity.position.set(this.entity.position.x, this.entity.position.y + 1.50, this.entity.position.z); + itemEntity.motion.set( + -Math.sin((this.entity.rotation.yaw / 180) * Math.PI) * Math.cos((this.entity.rotation.pitch / 180) * Math.PI) * 0.3, + -Math.sin((this.entity.rotation.pitch / 180) * Math.PI) * 0.3 + 0.1, + Math.cos((this.entity.rotation.yaw / 180) * Math.PI) * Math.cos((this.entity.rotation.pitch / 180) * Math.PI) * 0.3 + ); + // Add random motion vector + const twoPIRandomised = Math.random() * Math.PI * 2; + const rngMult = 0.02 * Math.random(); + itemEntity.motion.add( + Math.cos(twoPIRandomised) * rngMult, + (Math.random() - Math.random()) * 0.1, + Math.sin(twoPIRandomised) * rngMult + ); + this.entity.world.addEntity(itemEntity); + } + // TODO: Cap how far away a player is able to break blocks private handlePacketPlayerDigging(packet:PacketPlayerDigging) { @@ -228,24 +250,7 @@ export default class MPClient { if (packet.status === 4) { const itemStack = this.getHeldItemStack(); if (itemStack !== null && itemStack.size > 0) { - itemStack.size--; - const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemStack.itemID, 1, itemStack.damage)); - itemEntity.pickupDelay = 10; - itemEntity.position.set(this.entity.position.x, this.entity.position.y + 1.50, this.entity.position.z); - itemEntity.motion.set( - -Math.sin((this.entity.rotation.yaw / 180) * Math.PI) * Math.cos((this.entity.rotation.pitch / 180) * Math.PI) * 0.3, - -Math.sin((this.entity.rotation.pitch / 180) * Math.PI) * 0.3 + 0.1, - Math.cos((this.entity.rotation.yaw / 180) * Math.PI) * Math.cos((this.entity.rotation.pitch / 180) * Math.PI) * 0.3 - ); - // Add random motion vector - const twoPIRandomised = Math.random() * Math.PI * 2; - const rngMult = 0.02 * Math.random(); - itemEntity.motion.add( - Math.cos(twoPIRandomised) * rngMult, - (Math.random() - Math.random()) * 0.1, - Math.sin(twoPIRandomised) * rngMult - ); - this.entity.world.addEntity(itemEntity); + this.throwItemStack(itemStack.split(1)); this.inventory.dropEmptyItemStacks(); this.inventory.sendUpdatedStacks([this.holdingIndex]); @@ -281,14 +286,14 @@ export default class MPClient { if (blockClicked.is(Block.chest)) { const tileEntity = this.entity.world.getChunk(packet.x >> 4, packet.z >> 4).getTileEntity(packet.x, packet.y, packet.z); if (tileEntity && tileEntity instanceof TileEntityChest) { - const window = new WindowChest(PlayerCombinedInventory.FromExisting(this, tileEntity.inventory, tileEntity.inventory.name)); + const window = new WindowChest(this, PlayerCombinedInventory.FromExisting(this, tileEntity.inventory, tileEntity.inventory.name)); this.windows.set(window.windowId, window); - window.openWindow(this); + window.openWindow(); } } else if (blockClicked.is(Block.craftingTable)) { - const window = new WindowCrafting(new PlayerCombinedInventory(this, 10, "Crafting")); + const window = new WindowCrafting(this, new PlayerCombinedInventory(this, 10, "Crafting")); this.windows.set(window.windowId, window); - window.openWindow(this); + window.openWindow(); } return; @@ -359,7 +364,28 @@ export default class MPClient { } private handleWindowClick(windowClick: PacketWindowClick) { - console.log(windowClick); + const window = this.windows.get(windowClick.windowId); + if (!window) { + return this.send(new PacketDisconnectKick("Attempted to perform action on window that does not exist.").writeData()); + } + + window.clickedWindow(windowClick.slot, windowClick.rightClick); + } + + private handleWindowClose(closeWindow: PacketCloseWindow) { + if (closeWindow.windowId === 0) { + return; + } + + const window = this.windows.get(closeWindow.windowId); + if (window) { + window.closeWindow(); + if (window.cursorItemStack && window.cursorItemStack.size > 0) { + this.throwItemStack(window.cursorItemStack); + window.cursorItemStack = null; + } + this.windows.remove(window.windowId); + } } private handleDisconnectKick() { diff --git a/server/MinecraftServer.ts b/server/MinecraftServer.ts index 4904fb0..8f88126 100644 --- a/server/MinecraftServer.ts +++ b/server/MinecraftServer.ts @@ -128,7 +128,7 @@ export default class MinecraftServer { this.worlds = new FunkyArray(); //this.worlds.set(0, new World(this.saveManager, 0, worldSeed, new NewOverworld(worldSeed))); this.worlds.set(0, new World(this.saveManager, 0, worldSeed, new HillyGenerator(worldSeed))); - this.worlds.set(-1, new World(this.saveManager, -1, worldSeed, new NetherGenerator(worldSeed))); + //this.worlds.set(-1, new World(this.saveManager, -1, worldSeed, new NetherGenerator(worldSeed))); (async () => { const generateStartTime = Date.now(); diff --git a/server/inventories/Inventory.ts b/server/inventories/Inventory.ts index 5140e3d..a50aaab 100644 --- a/server/inventories/Inventory.ts +++ b/server/inventories/Inventory.ts @@ -6,14 +6,14 @@ import ItemStack from "./ItemStack"; export default class Inventory implements IInventory { private static CHANGE_HANDLER_ROLLING_HANDLE_ID = 0; - public changeHandlers:FunkyArray void>; + public changeHandlers:FunkyArray void>; public itemStacks:Array; public readonly size:number; public readonly name:string; public constructor(size:number, name:string) { - this.changeHandlers = new FunkyArray void>(); + this.changeHandlers = new FunkyArray void>(); this.itemStacks = new Array(); for (let i = 0; i < size; i++) { this.itemStacks.push(null); @@ -23,7 +23,7 @@ export default class Inventory implements IInventory { this.name = name; } - registerChangeHandler(changeHandler: (itemStack: ItemStack) => void) { + registerChangeHandler(changeHandler: (slotId: number) => void) { const changeHandlerHandle = Inventory.CHANGE_HANDLER_ROLLING_HANDLE_ID++; this.changeHandlers.set(changeHandlerHandle, changeHandler); return changeHandlerHandle; @@ -54,6 +54,10 @@ export default class Inventory implements IInventory { } } + sendSlotUpdate(slotId: number) { + this.changeHandlers.forEach(handler => handler(slotId)); + } + addItemStack(itemStack:ItemStack) { for (let slotId = 0; slotId < this.itemStacks.length; slotId++) { if (itemStack.size === 0) { @@ -61,6 +65,7 @@ export default class Inventory implements IInventory { } this.itemStacks[slotId]?.insert(itemStack); + this.changeHandlers.forEach(handler => handler(slotId)); } } @@ -81,6 +86,7 @@ export default class Inventory implements IInventory { const itemStack = this.itemStacks[i]; if (itemStack?.size === 0) { this.itemStacks[i] = null; + this.changeHandlers.forEach(handler => handler(i)); } } } @@ -91,6 +97,7 @@ export default class Inventory implements IInventory { } this.itemStacks[slotId] = itemStack; + this.changeHandlers.forEach(handler => handler(slotId)); return this; } diff --git a/server/inventories/PlayerCombinedInventory.ts b/server/inventories/PlayerCombinedInventory.ts index bc65b04..0a73fea 100644 --- a/server/inventories/PlayerCombinedInventory.ts +++ b/server/inventories/PlayerCombinedInventory.ts @@ -4,7 +4,7 @@ import Inventory from "./Inventory"; import ItemStack from "./ItemStack"; export default class PlayerCombinedInventory extends Inventory { - private static PLAYER_INVENTORY_OFFSET = 10; + private static PLAYER_INVENTORY_OFFSET = 9; private mpClient: MPClient; @@ -16,7 +16,7 @@ export default class PlayerCombinedInventory extends Inventory { private getSlotId(slotId: number) { if (slotId > this.size - 1) { - return slotId + PlayerCombinedInventory.PLAYER_INVENTORY_OFFSET; + return (slotId - this.size) + PlayerCombinedInventory.PLAYER_INVENTORY_OFFSET; } else { return slotId; } @@ -25,6 +25,7 @@ export default class PlayerCombinedInventory extends Inventory { static FromExisting(mpClient: MPClient, inventory: Inventory, name: string) { const linkedInventory = new PlayerCombinedInventory(mpClient, inventory.size, name); linkedInventory.itemStacks = inventory.itemStacks; + linkedInventory.changeHandlers = inventory.changeHandlers; return linkedInventory; } @@ -55,7 +56,11 @@ export default class PlayerCombinedInventory extends Inventory { // } getSlotItemStack(slotId:number) { - return this.itemStacks[slotId] ?? this.mpClient.entity.inventory.itemStacks[this.getSlotId(slotId)]; + if (slotId > this.size - 1) { + return this.mpClient.entity.inventory.itemStacks[this.getSlotId(slotId)]; + } + + return this.itemStacks[slotId]; } dropEmptyItemStacks() { diff --git a/server/inventories/PlayerInventory.ts b/server/inventories/PlayerInventory.ts index de3d2d5..4534fb4 100644 --- a/server/inventories/PlayerInventory.ts +++ b/server/inventories/PlayerInventory.ts @@ -24,6 +24,7 @@ export default class PlayerInventory extends Inventory { } else { buffer = new PacketSetSlot(0, slotId, slotItem.itemID, slotItem.size, slotItem.damage).writeData(); } + this.changeHandlers.forEach(handler => handler(slotId)); updateBuffer = Buffer.concat([updateBuffer, buffer], updateBuffer.length + buffer.length); } diff --git a/server/packets/CloseWindow.ts b/server/packets/CloseWindow.ts new file mode 100644 index 0000000..cdcb1ce --- /dev/null +++ b/server/packets/CloseWindow.ts @@ -0,0 +1,22 @@ +import { createWriter, IReader, Endian } from "bufferstuff"; +import IPacket from "./IPacket"; +import Packet from "../enums/Packet"; + +export default class PacketCloseWindow implements IPacket { + public packetId = Packet.CloseWindow; + public windowId:number; + + public constructor(windowId?:number) { + this.windowId = windowId ?? Number.MIN_VALUE; + } + + public readData(reader:IReader) { + this.windowId = reader.readByte(); + + return this; + } + + public writeData() { + return createWriter(Endian.BE, 2).writeUByte(this.packetId).writeByte(this.windowId).toBuffer(); + } +} \ No newline at end of file diff --git a/server/windows/Window.ts b/server/windows/Window.ts index 58cbfc2..b6e13c8 100644 --- a/server/windows/Window.ts +++ b/server/windows/Window.ts @@ -4,6 +4,7 @@ import ItemStack from "../inventories/ItemStack"; import PlayerCombinedInventory from "../inventories/PlayerCombinedInventory"; import MPClient from "../MPClient"; import PacketOpenWindow from "../packets/OpenWindow"; +import PacketSetSlot from "../packets/SetSlot"; import PacketWindowItems from "../packets/WindowItems"; export default abstract class Window { @@ -14,31 +15,76 @@ export default abstract class Window { public windowId = Window.WINDOW_GLOBAL_COUNTER++; public inventoryType: InventoryType; public inventory: PlayerCombinedInventory; + private readonly mpClient: MPClient; + + private readonly inventoryUpdateHandle: number; public cursorItemStack: ItemStack | null; - public constructor(inventoryType: InventoryType, inventory: PlayerCombinedInventory, inventorySize: number) { + public constructor(inventoryType: InventoryType, inventory: PlayerCombinedInventory, mpClient: MPClient, inventorySize: number) { this.inventorySize = inventorySize; this.inventoryType = inventoryType; this.inventory = inventory; + this.mpClient = mpClient; + + this.inventoryUpdateHandle = this.inventory.registerChangeHandler((slotId) => { + const slotItem = this.inventory.getSlotItemStack(slotId); + if (slotItem == null) { + this.mpClient.send(new PacketSetSlot(this.windowId, slotId, -1).writeData()); + } else { + this.mpClient.send(new PacketSetSlot(this.windowId, slotId, slotItem.itemID, slotItem.size, slotItem.damage).writeData()); + } + }); this.cursorItemStack = null; } - openWindow(mpClient: MPClient) { + openWindow() { const windowPacket = new PacketOpenWindow(this.windowId, this.inventoryType, this.inventory.getInventoryName(), this.inventory.getInventorySize()).writeData(); const windowItems = new PacketWindowItems(this.windowId, this.inventorySize, this.inventory.constructInventoryPayload()).writeData(); - mpClient.send(Buffer.concat([ windowPacket, windowItems ], windowPacket.length + windowItems.length)); - //mpClient.send(windowPacket); - //mpClient.send(inventoryDataPayload); + this.mpClient.send(Buffer.concat([ windowPacket, windowItems ], windowPacket.length + windowItems.length)); } clickedWindow(slotId: number, rightClick: boolean) { + const slotItemStack = this.inventory.getSlotItemStack(slotId); if (this.cursorItemStack) { - + if (rightClick) { + if (slotItemStack) { + slotItemStack.insert(this.cursorItemStack.split(1)); + this.inventory.sendSlotUpdate(slotId); + } else { + this.inventory.setSlotItemStack(slotId, this.cursorItemStack.split(1)); + } + if (this.cursorItemStack.size === 0) { + this.cursorItemStack = null; + } + } else { + if (slotItemStack) { + slotItemStack.insert(this.cursorItemStack); + this.inventory.sendSlotUpdate(slotId); + if (this.cursorItemStack.size === 0) { + this.cursorItemStack = null; + } + } else { + this.inventory.setSlotItemStack(slotId, this.cursorItemStack); + this.cursorItemStack = null; + } + } } else { - this.cursorItemStack = this.inventory.getSlotItemStack(slotId); + if (slotItemStack) { + if (rightClick) { + this.cursorItemStack = slotItemStack.split(Math.ceil(slotItemStack.size / 2)); + this.inventory.sendSlotUpdate(slotId); + } else { + this.cursorItemStack = slotItemStack; + this.inventory.setSlotItemStack(slotId, null); + } + } } } + + closeWindow() { + this.inventory.unregisterChangeHandler(this.inventoryUpdateHandle); + } } \ No newline at end of file diff --git a/server/windows/WindowChest.ts b/server/windows/WindowChest.ts index b123a8d..e06232d 100644 --- a/server/windows/WindowChest.ts +++ b/server/windows/WindowChest.ts @@ -1,10 +1,11 @@ import InventoryType from "../enums/InventoryType"; import Inventory from "../inventories/Inventory"; import PlayerCombinedInventory from "../inventories/PlayerCombinedInventory"; +import MPClient from "../MPClient"; import Window from "./Window"; export default class WindowChest extends Window { - public constructor(inventory: PlayerCombinedInventory) { - super(InventoryType.Chest, inventory, 62); + public constructor(mpClient: MPClient, inventory: PlayerCombinedInventory) { + super(InventoryType.Chest, inventory, mpClient, 62); } } \ No newline at end of file diff --git a/server/windows/WindowCrafting.ts b/server/windows/WindowCrafting.ts index 7df006c..6d82119 100644 --- a/server/windows/WindowCrafting.ts +++ b/server/windows/WindowCrafting.ts @@ -5,7 +5,7 @@ import MPClient from "../MPClient"; import Window from "./Window"; export default class WindowCrafting extends Window { - public constructor(inventory: PlayerCombinedInventory) { - super(InventoryType.CraftingTable, inventory, 45); + public constructor(mpClient: MPClient, inventory: PlayerCombinedInventory) { + super(InventoryType.CraftingTable, inventory, mpClient, 45); } } \ No newline at end of file