From d8d1eabcf4ff52a3335506b91fa5162aab93f1d9 Mon Sep 17 00:00:00 2001 From: Holly Date: Sun, 29 Oct 2023 05:08:26 +0000 Subject: [PATCH] WIP: Inventories --- package-lock.json | 63 ++++++++++++------- package.json | 10 ++-- server/Chunk.ts | 4 +- server/EntityMetadata.ts | 26 ++++++++ server/MPClient.ts | 34 ++++++++++- server/MetadataWriter.ts | 2 + server/MinecraftServer.ts | 5 ++ server/containers/Container.ts | 12 ---- server/containers/ItemStack.ts | 49 --------------- server/containers/Slot.ts | 11 ---- server/entities/{Item.ts => EntityItem.ts} | 2 +- server/entities/Player.ts | 10 ++++ server/enums/Packet.ts | 5 ++ server/inventories/IInventory.ts | 7 ++- server/inventories/Inventory.ts | 67 +++++++++++++++++++++ server/inventories/ItemStack.ts | 47 +++++++++++++++ server/packets/HoldingChange.ts | 26 ++++++++ server/packets/PlayerBlockPlacement.ts | 70 ++++++++++++++++++++++ server/packets/WindowItems.ts | 32 ++++++++++ 19 files changed, 374 insertions(+), 108 deletions(-) create mode 100644 server/EntityMetadata.ts delete mode 100644 server/containers/Container.ts delete mode 100644 server/containers/ItemStack.ts delete mode 100644 server/containers/Slot.ts rename server/entities/{Item.ts => EntityItem.ts} (87%) create mode 100644 server/inventories/Inventory.ts create mode 100644 server/inventories/ItemStack.ts create mode 100644 server/packets/HoldingChange.ts create mode 100644 server/packets/PlayerBlockPlacement.ts create mode 100644 server/packets/WindowItems.ts diff --git a/package-lock.json b/package-lock.json index fe1748a..ae68f04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,18 +9,18 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "bufferstuff": "^1.3.0", + "bufferstuff": "^1.3.4", "hsconsole": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.5.1", + "@types/node": "^20.8.9", "check-outdated": "^2.12.0", "nodemon": "^3.0.1", "npm-run-all": "^4.1.5", - "terser": "^5.19.2", - "ts-loader": "^9.4.4", + "terser": "^5.22.0", + "ts-loader": "^9.5.0", "ts-node": "^10.9.1", - "typescript": "^5.1.6" + "typescript": "^5.2.2" } }, "node_modules/@cspotcode/source-map-support": { @@ -170,10 +170,13 @@ "peer": true }, "node_modules/@types/node": { - "version": "20.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", - "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", - "dev": true + "version": "20.8.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", + "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", @@ -550,9 +553,9 @@ "dev": true }, "node_modules/bufferstuff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bufferstuff/-/bufferstuff-1.3.0.tgz", - "integrity": "sha512-w+9eS70G3pzEAy8PzLt1ZX/h5SYEf+c2FYDu1zMPLldVTqMIoJaNLwCYKAzav3CWFAjJYXTTXCuO51uufBZtZw==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/bufferstuff/-/bufferstuff-1.3.4.tgz", + "integrity": "sha512-oBaMs5roIpkG242dWSoS38BWeVxlpTKVhRTRnGmZy5/6H+XU6YJMGa6jmPl7ychEnOqpuSA8wiq2k12zPt4BDA==" }, "node_modules/call-bind": { "version": "1.0.2", @@ -2325,9 +2328,9 @@ } }, "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", + "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -2402,15 +2405,16 @@ } }, "node_modules/ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -2435,6 +2439,15 @@ "node": ">=10" } }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -2493,9 +2506,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -2526,6 +2539,12 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", diff --git a/package.json b/package.json index ca23697..a4128b5 100644 --- a/package.json +++ b/package.json @@ -25,17 +25,17 @@ }, "homepage": "https://github.com/tgpholly/mc-beta-server#readme", "dependencies": { - "bufferstuff": "^1.3.0", + "bufferstuff": "^1.3.4", "hsconsole": "^1.0.2" }, "devDependencies": { - "@types/node": "^20.5.1", + "@types/node": "^20.8.9", "check-outdated": "^2.12.0", "nodemon": "^3.0.1", "npm-run-all": "^4.1.5", - "terser": "^5.19.2", - "ts-loader": "^9.4.4", + "terser": "^5.22.0", + "ts-loader": "^9.5.0", "ts-node": "^10.9.1", - "typescript": "^5.1.6" + "typescript": "^5.2.2" } } diff --git a/server/Chunk.ts b/server/Chunk.ts index ea5e077..9c8d9b5 100644 --- a/server/Chunk.ts +++ b/server/Chunk.ts @@ -154,11 +154,11 @@ export class Chunk { } public getBlockLightBuffer() { - return this.metadata.toBuffer(); + return this.blockLight.toBuffer(); } public getSkyLightBuffer() { - return this.metadata.toBuffer(); + return this.skyLight.toBuffer(); } public getBlockData() { diff --git a/server/EntityMetadata.ts b/server/EntityMetadata.ts new file mode 100644 index 0000000..921b36f --- /dev/null +++ b/server/EntityMetadata.ts @@ -0,0 +1,26 @@ +import { MetadataEntry, MetadataWriter } from "./MetadataWriter"; +import { MetadataFieldType } from "./enums/MetadataFieldType"; + +export default class EntityMetadata { + public onFire:boolean = false; + public crouched:boolean = false; + public ridingEntity:boolean = false; + + private finalValue:number = 0; + + private static readonly ENTITY_ON_FIRE = 1 << 0; + private static readonly ENTITY_CROUCHING = 1 << 1; + private static readonly ENTITY_RIDING = 1 << 2; + + writeMetadata() { + const metadataWriter = new MetadataWriter(); + this.finalValue = + (this.onFire ? EntityMetadata.ENTITY_ON_FIRE : 0) | // On Fire + (this.crouched ? EntityMetadata.ENTITY_CROUCHING : 0) | // Crouching + (this.ridingEntity ? EntityMetadata.ENTITY_CROUCHING : 0); // Riding entity + + metadataWriter.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, this.finalValue)); + + return metadataWriter.writeBuffer(); + } +} \ No newline at end of file diff --git a/server/MPClient.ts b/server/MPClient.ts index 36ac5d8..262ffb0 100644 --- a/server/MPClient.ts +++ b/server/MPClient.ts @@ -15,23 +15,30 @@ import { Socket } from "net"; import { Vec3 } from "./Vec3"; import { PacketRespawn } from "./packets/Respawn"; import { PacketSpawnPosition } from "./packets/SpawnPosition"; +import { PacketPlayerBlockPlacement } from "./packets/PlayerBlockPlacement"; +import { Inventory } from "./inventories/Inventory"; +import { PacketHoldingChange } from "./packets/HoldingChange"; +import { PacketDisconnectKick } from "./packets/DisconnectKick"; export class MPClient { private readonly mcServer:MinecraftServer; private readonly socket:Socket; public entity:Player; + private inventory:Inventory; + private holdingIndex:number = 36; // First hotbar slot. private diggingAt:Vec3; public constructor(mcServer:MinecraftServer, socket:Socket, entity:Player) { this.mcServer = mcServer; this.socket = socket; this.entity = entity; + this.inventory = entity.inventory; this.diggingAt = new Vec3(); } - private mapCoordsToFace(pos:Vec3, face:number) { + private mapCoordsFromFace(pos:Vec3, face:number) { switch (face) { case 0: pos.y--; @@ -64,8 +71,8 @@ export class MPClient { case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break; case Packet.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break; case Packet.PlayerDigging: this.handlePacketPlayerDigging(new PacketPlayerDigging().readData(reader)); break; - //case Packets.PlayerBlockPlacement: break; - //case Packets.HoldingChange: break; + case Packet.PlayerBlockPlacement: this.handlePacketBlockPlacement(new PacketPlayerBlockPlacement().readData(reader)); break; + case Packet.HoldingChange: this.handlePacketHoldingChange(new PacketHoldingChange().readData(reader)); break; //case Packets.UseBed: break; case Packet.Animation: this.handlePacketAnimation(new PacketAnimation().readData(reader)); break; case Packet.EntityAction: this.handlePacketEntityAction(new PacketEntityAction().readData(reader)); break; @@ -154,6 +161,27 @@ export class MPClient { } } + private handlePacketBlockPlacement(packet:PacketPlayerBlockPlacement) { + this.diggingAt.set(packet.x, packet.y, packet.z); + this.mapCoordsFromFace(this.diggingAt, packet.face); + + const itemStack = this.inventory.getSlotItemStack(this.holdingIndex); + if (itemStack != null && itemStack.size > 0 && this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) === 0) { + itemStack.size--; + this.entity.world.setBlockAndMetadataWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, itemStack.itemID, itemStack.damage); + } + } + + private handlePacketHoldingChange(packet:PacketHoldingChange) { + if (packet.slotId < 0 || packet.slotId > 8) { + this.send(new PacketDisconnectKick("Out of Bounds Holding Index!").writeData()); + this.socket.end(); + return; + } + + this.holdingIndex = 36 + packet.slotId; + } + // Animation start private handlePacketAnimation(packet:PacketAnimation) { // Forward this packet to all nearby clients diff --git a/server/MetadataWriter.ts b/server/MetadataWriter.ts index 89e44db..ae0d5af 100644 --- a/server/MetadataWriter.ts +++ b/server/MetadataWriter.ts @@ -35,6 +35,8 @@ export class MetadataWriter { case MetadataFieldType.String: if (typeof(entry.value) === "string") { size += 2 + entry.value.length * 2; break; + } else { + throw "Non-string value assigned to a String MetadataEntry"; } } }) diff --git a/server/MinecraftServer.ts b/server/MinecraftServer.ts index 1a9af02..8f34212 100644 --- a/server/MinecraftServer.ts +++ b/server/MinecraftServer.ts @@ -21,6 +21,7 @@ import { Chunk } from "./Chunk"; import { PacketTimeUpdate } from "./packets/TimeUpdate"; import { HillyGenerator } from "./generators/Hilly"; import { NetherGenerator } from "./generators/Nether"; +import { PacketWindowItems } from "./packets/WindowItems"; export class MinecraftServer { private static readonly PROTOCOL_VERSION = 14; @@ -233,6 +234,10 @@ export class MinecraftServer { }); socket.write(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData()); + + const playerInventory = clientEntity.inventory; + socket.write(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData()); + console.log(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData()); } else { socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData()); } diff --git a/server/containers/Container.ts b/server/containers/Container.ts deleted file mode 100644 index cd7d420..0000000 --- a/server/containers/Container.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ItemStack } from "./ItemStack"; -import { ContainerSlot } from "./Slot"; - -export abstract class Container { - public itemSlots:Array; - public itemStacks:Array; - - public constructor() { - this.itemSlots = new Array(); - this.itemStacks = new Array(); - } -} \ No newline at end of file diff --git a/server/containers/ItemStack.ts b/server/containers/ItemStack.ts deleted file mode 100644 index 53f6be8..0000000 --- a/server/containers/ItemStack.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Block } from "../blocks/Block"; -import { Item } from "../items/Item"; - -export class ItemStack { - public readonly itemID:number; - public stackSize:number; - public damage:number; - - public constructor(blockOrItemOrItemID:Block|Item|number, stackSize?:number, damage?:number) { - if (blockOrItemOrItemID instanceof Block && stackSize === undefined && damage === undefined) { - this.itemID = blockOrItemOrItemID.blockId; - this.stackSize = 1; - this.damage = 0; - } else if (blockOrItemOrItemID instanceof Block && typeof(stackSize) === "number" && damage === undefined) { - this.itemID = blockOrItemOrItemID.blockId; - this.stackSize = stackSize; - this.damage = 0; - } else if (blockOrItemOrItemID instanceof Block && typeof(stackSize) === "number" && typeof(damage) === "number") { - this.itemID = blockOrItemOrItemID.blockId; - this.stackSize = stackSize; - this.damage = damage; - } else if (blockOrItemOrItemID instanceof Item && stackSize === undefined && damage === undefined) { - this.itemID = blockOrItemOrItemID.shiftedItemID; - this.stackSize = 1; - this.damage = 0; - } else if (blockOrItemOrItemID instanceof Item && typeof(stackSize) === "number" && damage === undefined) { - this.itemID = blockOrItemOrItemID.shiftedItemID; - this.stackSize = stackSize; - this.damage = 0; - } else if (blockOrItemOrItemID instanceof Item && typeof(stackSize) === "number" && typeof(damage) === "number") { - this.itemID = blockOrItemOrItemID.shiftedItemID; - this.stackSize = stackSize; - this.damage = damage; - } else if (typeof(blockOrItemOrItemID) === "number" && typeof(stackSize) === "number" && typeof(damage) === "number") { - this.itemID = blockOrItemOrItemID; - this.stackSize = stackSize; - this.damage = damage; - } else { - this.itemID = Number.MIN_VALUE; - this.stackSize = Number.MIN_VALUE; - this.damage = Number.MIN_VALUE; - } - } - - split(amount:number) { - this.stackSize -= amount; - return new ItemStack(this.itemID, amount, this.damage); - } -} \ No newline at end of file diff --git a/server/containers/Slot.ts b/server/containers/Slot.ts deleted file mode 100644 index 643b201..0000000 --- a/server/containers/Slot.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IInventory } from "../inventories/IInventory"; - -export class ContainerSlot { - public inventory:IInventory; - public index:number; - - public constructor(inventory:IInventory, index:number) { - this.inventory = inventory; - this.index = index; - } -} \ No newline at end of file diff --git a/server/entities/Item.ts b/server/entities/EntityItem.ts similarity index 87% rename from server/entities/Item.ts rename to server/entities/EntityItem.ts index e8a2334..f79fe4f 100644 --- a/server/entities/Item.ts +++ b/server/entities/EntityItem.ts @@ -1,5 +1,5 @@ import { World } from "../World"; -import { ItemStack } from "../containers/ItemStack"; +import { ItemStack } from "../inventories/ItemStack"; import { Entity } from "./Entity"; export class EntityItem extends Entity { diff --git a/server/entities/Player.ts b/server/entities/Player.ts index 4559052..121a5a6 100644 --- a/server/entities/Player.ts +++ b/server/entities/Player.ts @@ -6,6 +6,9 @@ import { PacketMapChunk } from "../packets/MapChunk"; import { EntityLiving } from "./EntityLiving"; import { PacketPreChunk } from "../packets/PreChunk"; import { PacketUpdateHealth } from "../packets/UpdateHealth"; +import { Inventory } from "../inventories/Inventory"; +import { ItemStack } from "../inventories/ItemStack"; +import { Block } from "../blocks/Block"; const CHUNK_LOAD_RANGE = 5; @@ -16,6 +19,7 @@ export class Player extends EntityLiving { public loadedChunks:Array; public justUnloaded:Array; public mpClient?:MPClient; + public inventory:Inventory; private lastHealth:number; @@ -26,6 +30,12 @@ export class Player extends EntityLiving { this.loadedChunks = new Array(); this.justUnloaded = new Array(); + this.inventory = new Inventory(44, "Player Inventory"); + + this.inventory.setSlotItemStack(36, new ItemStack(Block.dirt, 1)); + this.inventory.setSlotItemStack(37, new ItemStack(Block.dirt, 2)); + this.inventory.setSlotItemStack(38, new ItemStack(Block.dirt, 3)); + this.username = username; this.x = 8; this.y = 64; diff --git a/server/enums/Packet.ts b/server/enums/Packet.ts index b5fa923..cca3454 100644 --- a/server/enums/Packet.ts +++ b/server/enums/Packet.ts @@ -36,5 +36,10 @@ export enum Packet { EntityLookRelativeMove = 0x21, EntityTeleport = 0x22, + CloseWindow = 0x65, + WindowClick = 0x66, + SetSlot = 0x67, + WindowItems = 0x68, + DisconnectKick = 0xff } \ No newline at end of file diff --git a/server/inventories/IInventory.ts b/server/inventories/IInventory.ts index aed63ff..58013d5 100644 --- a/server/inventories/IInventory.ts +++ b/server/inventories/IInventory.ts @@ -1,7 +1,8 @@ -import { ItemStack } from "../containers/ItemStack"; +import { ItemStack } from "./ItemStack"; -export interface IInventory { +export default interface IInventory { getInventoryName:() => string, getInventorySize:() => number, - getSlotItemStack:(slotId:number) => ItemStack + getSlotItemStack:(slotId:number) => ItemStack | null + setSlotItemStack:(slotId:number, itemStack:ItemStack | null) => IInventory } \ No newline at end of file diff --git a/server/inventories/Inventory.ts b/server/inventories/Inventory.ts new file mode 100644 index 0000000..a842c29 --- /dev/null +++ b/server/inventories/Inventory.ts @@ -0,0 +1,67 @@ +import { Endian, createWriter } from "bufferstuff"; +import { ItemStack } from "./ItemStack"; +import IInventory from "./IInventory"; + +export class Inventory implements IInventory { + private itemStacks:Array; + + private size:number; + private name:string; + + public constructor(size:number, name:string) { + this.itemStacks = new Array(); + for (let i = 0; i < size; i++) { + this.itemStacks.push(null); + } + + this.size = size; + this.name = name; + } + + getInventoryName() { + return this.name; + } + + getInventorySize() { + return this.itemStacks.length; + } + + getSlotItemStack(slotId:number) { + return this.itemStacks[slotId]; + } + + setSlotItemStack(slotId:number, itemStack: ItemStack | null) { + if (slotId < 0 || slotId > this.size - 1) { + throw new Error(`Tried to set an Inventory ItemStack out of bounds! Requested slot: ${slotId}, Inventory Size: ${this.size}`); + } + + this.itemStacks[slotId] = itemStack; + + return this; + } + + private calculateInventoryPayloadSize() { + let bufferSize = 0; + for (const stack of this.itemStacks) { + if (stack) { + bufferSize += 5; // short + byte + short + } else { + bufferSize += 2; // short + } + } + return bufferSize; + } + + constructInventoryPayload() { + const writer = createWriter(Endian.BE, this.calculateInventoryPayloadSize()); + for (const stack of this.itemStacks) { + writer.writeShort(stack == null ? -1 : stack.itemID); + if (stack != null) { + writer.writeByte(stack.size); + writer.writeShort(stack.damage); + } + } + + return writer.toBuffer(); + } +} \ No newline at end of file diff --git a/server/inventories/ItemStack.ts b/server/inventories/ItemStack.ts new file mode 100644 index 0000000..bcd9f74 --- /dev/null +++ b/server/inventories/ItemStack.ts @@ -0,0 +1,47 @@ +import { Block } from "../blocks/Block"; +import { Item } from "../items/Item"; + +export class ItemStack { + public readonly itemID:number; + public size:number; + public damage:number; + + public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) { + if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) { + this.itemID = blockOrItemOrItemID.blockId; + this.size = 1; + this.damage = 0; + } else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && damage === undefined) { + this.itemID = blockOrItemOrItemID.blockId; + this.size = size; + this.damage = 0; + } else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && typeof(damage) === "number") { + this.itemID = blockOrItemOrItemID.blockId; + this.size = size; + this.damage = damage; + } else if (blockOrItemOrItemID instanceof Item && size === undefined && damage === undefined) { + this.itemID = blockOrItemOrItemID.shiftedItemID; + this.size = 1; + this.damage = 0; + } else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && damage === undefined) { + this.itemID = blockOrItemOrItemID.shiftedItemID; + this.size = size; + this.damage = 0; + } else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && typeof(damage) === "number") { + this.itemID = blockOrItemOrItemID.shiftedItemID; + this.size = size; + this.damage = damage; + } else if (typeof(blockOrItemOrItemID) === "number" && typeof(size) === "number" && typeof(damage) === "number") { + this.itemID = blockOrItemOrItemID; + this.size = size; + this.damage = damage; + } else { + throw new Error(`ItemStack created with invalid properties (${typeof(blockOrItemOrItemID)}, ${typeof(size)}, ${typeof(damage)})`); + } + } + + split(amount:number) { + this.size -= amount; + return new ItemStack(this.itemID, amount, this.damage); + } +} \ No newline at end of file diff --git a/server/packets/HoldingChange.ts b/server/packets/HoldingChange.ts new file mode 100644 index 0000000..3d2c6a8 --- /dev/null +++ b/server/packets/HoldingChange.ts @@ -0,0 +1,26 @@ +import { createWriter, IReader, Endian } from "bufferstuff"; +import { IPacket } from "./IPacket"; +import { Packet } from "../enums/Packet"; + +export class PacketHoldingChange implements IPacket { + public packetId = Packet.HoldingChange; + public slotId:number; + + public constructor(slotId?:number) { + if (typeof(slotId) === "number") { + this.slotId = slotId; + } else { + this.slotId = Number.MIN_VALUE; + } + } + + public readData(reader:IReader) { + this.slotId = reader.readShort(); + + return this; + } + + public writeData() { + return createWriter(Endian.BE, 3).writeUByte(this.packetId).writeShort(this.slotId).toBuffer(); + } +} \ No newline at end of file diff --git a/server/packets/PlayerBlockPlacement.ts b/server/packets/PlayerBlockPlacement.ts new file mode 100644 index 0000000..c54937c --- /dev/null +++ b/server/packets/PlayerBlockPlacement.ts @@ -0,0 +1,70 @@ +import { createWriter, IReader, Endian } from "bufferstuff"; +import { IPacket } from "./IPacket"; +import { Packet } from "../enums/Packet"; + +export class PacketPlayerBlockPlacement implements IPacket { + public packetId = Packet.PlayerBlockPlacement; + public x:number; + public y:number; + public z:number; + public face:number; + public blockOrItemId:number; + public amount?:number; + public damage?:number; + + public constructor(x?:number, y?:number, z?:number, face?:number, blockOrItemId?:number, amount?:number, damage?:number) { + if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number" && typeof(face) === "number" && typeof(blockOrItemId) === "number") { + this.x = x; + this.y = y; + this.z = z; + this.face = face; + this.blockOrItemId = blockOrItemId; + + } else { + this.x = Number.MIN_VALUE; + this.y = Number.MIN_VALUE; + this.z = Number.MIN_VALUE; + this.face = Number.MIN_VALUE; + this.blockOrItemId = Number.MIN_VALUE; + } + + this.amount = amount; + this.damage = damage; + } + + public readData(reader:IReader) { + this.x = reader.readInt(); + this.y = reader.readByte(); + this.z = reader.readInt(); + this.face = reader.readByte(); + this.blockOrItemId = reader.readShort(); + if (this.blockOrItemId >= 0) { + this.amount = reader.readByte(); + this.damage = reader.readShort(); + } + + return this; + } + + private calculatePacketSize() { + return this.blockOrItemId >= 0 && this.amount != null && this.damage != null ? 16 : 13; + } + + public writeData() { + const packetSize = this.calculatePacketSize(); + + const writer = createWriter(Endian.BE, packetSize) + .writeUByte(this.packetId) + .writeInt(this.x) + .writeByte(this.y) + .writeInt(this.z) + .writeByte(this.face) + .writeShort(this.blockOrItemId); + + if (this.amount != null && this.damage != null) { + writer.writeByte(this.amount).writeShort(this.damage); + } + + return writer.toBuffer(); + } +} \ No newline at end of file diff --git a/server/packets/WindowItems.ts b/server/packets/WindowItems.ts new file mode 100644 index 0000000..06d2b67 --- /dev/null +++ b/server/packets/WindowItems.ts @@ -0,0 +1,32 @@ +import { createWriter, IReader, Endian } from "bufferstuff"; +import { IPacket } from "./IPacket"; +import { Packet } from "../enums/Packet"; + +export class PacketWindowItems implements IPacket { + public packetId = Packet.WindowItems; + public windowId:number; + public count:number; + public payload:Buffer; + + public constructor(windowId?:number, count?:number, payload?:Buffer) { + if (typeof(windowId) === "number" && typeof(count) === "number" && payload instanceof Buffer) { + this.windowId = windowId; + this.count = count; + this.payload = payload; + } else { + this.windowId = Number.MIN_VALUE; + this.count = Number.MIN_VALUE; + this.payload = Buffer.alloc(0); + } + } + + public readData(reader:IReader) { + reader.readByte(); + + return this; + } + + public writeData() { + return createWriter(Endian.BE, 4).writeUByte(this.packetId).writeByte(this.windowId).writeShort(this.count).writeBuffer(this.payload).toBuffer(); + } +} \ No newline at end of file