WIP: Inventories

This commit is contained in:
Holly Stubbs 2023-10-29 05:08:26 +00:00
parent 74374cb0d6
commit d8d1eabcf4
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
19 changed files with 374 additions and 108 deletions

63
package-lock.json generated
View file

@ -9,18 +9,18 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bufferstuff": "^1.3.0", "bufferstuff": "^1.3.4",
"hsconsole": "^1.0.2" "hsconsole": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.5.1", "@types/node": "^20.8.9",
"check-outdated": "^2.12.0", "check-outdated": "^2.12.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"terser": "^5.19.2", "terser": "^5.22.0",
"ts-loader": "^9.4.4", "ts-loader": "^9.5.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.1.6" "typescript": "^5.2.2"
} }
}, },
"node_modules/@cspotcode/source-map-support": { "node_modules/@cspotcode/source-map-support": {
@ -170,10 +170,13 @@
"peer": true "peer": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.5.1", "version": "20.8.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz",
"integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==",
"dev": true "dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
}, },
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.11.6", "version": "1.11.6",
@ -550,9 +553,9 @@
"dev": true "dev": true
}, },
"node_modules/bufferstuff": { "node_modules/bufferstuff": {
"version": "1.3.0", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/bufferstuff/-/bufferstuff-1.3.0.tgz", "resolved": "https://registry.npmjs.org/bufferstuff/-/bufferstuff-1.3.4.tgz",
"integrity": "sha512-w+9eS70G3pzEAy8PzLt1ZX/h5SYEf+c2FYDu1zMPLldVTqMIoJaNLwCYKAzav3CWFAjJYXTTXCuO51uufBZtZw==" "integrity": "sha512-oBaMs5roIpkG242dWSoS38BWeVxlpTKVhRTRnGmZy5/6H+XU6YJMGa6jmPl7ychEnOqpuSA8wiq2k12zPt4BDA=="
}, },
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.2", "version": "1.0.2",
@ -2325,9 +2328,9 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.19.2", "version": "5.22.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz",
"integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.3", "@jridgewell/source-map": "^0.3.3",
@ -2402,15 +2405,16 @@
} }
}, },
"node_modules/ts-loader": { "node_modules/ts-loader": {
"version": "9.4.4", "version": "9.5.0",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz",
"integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chalk": "^4.1.0", "chalk": "^4.1.0",
"enhanced-resolve": "^5.0.0", "enhanced-resolve": "^5.0.0",
"micromatch": "^4.0.0", "micromatch": "^4.0.0",
"semver": "^7.3.4" "semver": "^7.3.4",
"source-map": "^0.7.4"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.0.0"
@ -2435,6 +2439,15 @@
"node": ">=10" "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": { "node_modules/ts-node": {
"version": "10.9.1", "version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@ -2493,9 +2506,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.1.6", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -2526,6 +2539,12 @@
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true "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": { "node_modules/update-browserslist-db": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",

View file

@ -25,17 +25,17 @@
}, },
"homepage": "https://github.com/tgpholly/mc-beta-server#readme", "homepage": "https://github.com/tgpholly/mc-beta-server#readme",
"dependencies": { "dependencies": {
"bufferstuff": "^1.3.0", "bufferstuff": "^1.3.4",
"hsconsole": "^1.0.2" "hsconsole": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.5.1", "@types/node": "^20.8.9",
"check-outdated": "^2.12.0", "check-outdated": "^2.12.0",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"terser": "^5.19.2", "terser": "^5.22.0",
"ts-loader": "^9.4.4", "ts-loader": "^9.5.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.1.6" "typescript": "^5.2.2"
} }
} }

View file

@ -154,11 +154,11 @@ export class Chunk {
} }
public getBlockLightBuffer() { public getBlockLightBuffer() {
return this.metadata.toBuffer(); return this.blockLight.toBuffer();
} }
public getSkyLightBuffer() { public getSkyLightBuffer() {
return this.metadata.toBuffer(); return this.skyLight.toBuffer();
} }
public getBlockData() { public getBlockData() {

26
server/EntityMetadata.ts Normal file
View file

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

View file

@ -15,23 +15,30 @@ import { Socket } from "net";
import { Vec3 } from "./Vec3"; import { Vec3 } from "./Vec3";
import { PacketRespawn } from "./packets/Respawn"; import { PacketRespawn } from "./packets/Respawn";
import { PacketSpawnPosition } from "./packets/SpawnPosition"; 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 { export class MPClient {
private readonly mcServer:MinecraftServer; private readonly mcServer:MinecraftServer;
private readonly socket:Socket; private readonly socket:Socket;
public entity:Player; public entity:Player;
private inventory:Inventory;
private holdingIndex:number = 36; // First hotbar slot.
private diggingAt:Vec3; private diggingAt:Vec3;
public constructor(mcServer:MinecraftServer, socket:Socket, entity:Player) { public constructor(mcServer:MinecraftServer, socket:Socket, entity:Player) {
this.mcServer = mcServer; this.mcServer = mcServer;
this.socket = socket; this.socket = socket;
this.entity = entity; this.entity = entity;
this.inventory = entity.inventory;
this.diggingAt = new Vec3(); this.diggingAt = new Vec3();
} }
private mapCoordsToFace(pos:Vec3, face:number) { private mapCoordsFromFace(pos:Vec3, face:number) {
switch (face) { switch (face) {
case 0: case 0:
pos.y--; pos.y--;
@ -64,8 +71,8 @@ export class MPClient {
case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break; case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break;
case Packet.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break; case Packet.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break;
case Packet.PlayerDigging: this.handlePacketPlayerDigging(new PacketPlayerDigging().readData(reader)); break; case Packet.PlayerDigging: this.handlePacketPlayerDigging(new PacketPlayerDigging().readData(reader)); break;
//case Packets.PlayerBlockPlacement: break; case Packet.PlayerBlockPlacement: this.handlePacketBlockPlacement(new PacketPlayerBlockPlacement().readData(reader)); break;
//case Packets.HoldingChange: break; case Packet.HoldingChange: this.handlePacketHoldingChange(new PacketHoldingChange().readData(reader)); break;
//case Packets.UseBed: break; //case Packets.UseBed: break;
case Packet.Animation: this.handlePacketAnimation(new PacketAnimation().readData(reader)); break; case Packet.Animation: this.handlePacketAnimation(new PacketAnimation().readData(reader)); break;
case Packet.EntityAction: this.handlePacketEntityAction(new PacketEntityAction().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 // Animation start
private handlePacketAnimation(packet:PacketAnimation) { private handlePacketAnimation(packet:PacketAnimation) {
// Forward this packet to all nearby clients // Forward this packet to all nearby clients

View file

@ -35,6 +35,8 @@ export class MetadataWriter {
case MetadataFieldType.String: case MetadataFieldType.String:
if (typeof(entry.value) === "string") { if (typeof(entry.value) === "string") {
size += 2 + entry.value.length * 2; break; size += 2 + entry.value.length * 2; break;
} else {
throw "Non-string value assigned to a String MetadataEntry";
} }
} }
}) })

View file

@ -21,6 +21,7 @@ import { Chunk } from "./Chunk";
import { PacketTimeUpdate } from "./packets/TimeUpdate"; import { PacketTimeUpdate } from "./packets/TimeUpdate";
import { HillyGenerator } from "./generators/Hilly"; import { HillyGenerator } from "./generators/Hilly";
import { NetherGenerator } from "./generators/Nether"; import { NetherGenerator } from "./generators/Nether";
import { PacketWindowItems } from "./packets/WindowItems";
export class MinecraftServer { export class MinecraftServer {
private static readonly PROTOCOL_VERSION = 14; 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()); 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 { } else {
socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData()); socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData());
} }

View file

@ -1,12 +0,0 @@
import { ItemStack } from "./ItemStack";
import { ContainerSlot } from "./Slot";
export abstract class Container {
public itemSlots:Array<ContainerSlot>;
public itemStacks:Array<ItemStack>;
public constructor() {
this.itemSlots = new Array<ContainerSlot>();
this.itemStacks = new Array<ItemStack>();
}
}

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { World } from "../World"; import { World } from "../World";
import { ItemStack } from "../containers/ItemStack"; import { ItemStack } from "../inventories/ItemStack";
import { Entity } from "./Entity"; import { Entity } from "./Entity";
export class EntityItem extends Entity { export class EntityItem extends Entity {

View file

@ -6,6 +6,9 @@ import { PacketMapChunk } from "../packets/MapChunk";
import { EntityLiving } from "./EntityLiving"; import { EntityLiving } from "./EntityLiving";
import { PacketPreChunk } from "../packets/PreChunk"; import { PacketPreChunk } from "../packets/PreChunk";
import { PacketUpdateHealth } from "../packets/UpdateHealth"; 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; const CHUNK_LOAD_RANGE = 5;
@ -16,6 +19,7 @@ export class Player extends EntityLiving {
public loadedChunks:Array<number>; public loadedChunks:Array<number>;
public justUnloaded:Array<number>; public justUnloaded:Array<number>;
public mpClient?:MPClient; public mpClient?:MPClient;
public inventory:Inventory;
private lastHealth:number; private lastHealth:number;
@ -26,6 +30,12 @@ export class Player extends EntityLiving {
this.loadedChunks = new Array<number>(); this.loadedChunks = new Array<number>();
this.justUnloaded = new Array<number>(); this.justUnloaded = new Array<number>();
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.username = username;
this.x = 8; this.x = 8;
this.y = 64; this.y = 64;

View file

@ -36,5 +36,10 @@ export enum Packet {
EntityLookRelativeMove = 0x21, EntityLookRelativeMove = 0x21,
EntityTeleport = 0x22, EntityTeleport = 0x22,
CloseWindow = 0x65,
WindowClick = 0x66,
SetSlot = 0x67,
WindowItems = 0x68,
DisconnectKick = 0xff DisconnectKick = 0xff
} }

View file

@ -1,7 +1,8 @@
import { ItemStack } from "../containers/ItemStack"; import { ItemStack } from "./ItemStack";
export interface IInventory { export default interface IInventory {
getInventoryName:() => string, getInventoryName:() => string,
getInventorySize:() => number, getInventorySize:() => number,
getSlotItemStack:(slotId:number) => ItemStack getSlotItemStack:(slotId:number) => ItemStack | null
setSlotItemStack:(slotId:number, itemStack:ItemStack | null) => IInventory
} }

View file

@ -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<ItemStack | null>;
private size:number;
private name:string;
public constructor(size:number, name:string) {
this.itemStacks = new Array<ItemStack | null>();
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();
}
}

View file

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

View file

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

View file

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

View file

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