working inventories
All checks were successful
Node.js Build / build (20.x) (push) Successful in 5m16s

This commit is contained in:
Holly Stubbs 2024-12-02 00:25:03 +00:00
parent 7c59d531ae
commit 2e7535bf89
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
9 changed files with 149 additions and 41 deletions

View file

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

View file

@ -128,7 +128,7 @@ export default class MinecraftServer {
this.worlds = new FunkyArray<number, World>();
//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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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