diff --git a/nibbleArray.ts b/nibbleArray.ts index 622b2b2..5500cc9 100644 --- a/nibbleArray.ts +++ b/nibbleArray.ts @@ -7,35 +7,25 @@ export class NibbleArray { } else if (size instanceof Uint8Array) { this.array = new Uint8Array(size); } else { - this.array = new Uint8Array(Math.round(size / 2)); + this.array = new Uint8Array(size >> 1); } } - // We can determine which side of the byte to read - // from if the halved index has a remainder. - private isLowOrHighNibble(index:number) { - return index % 1 !== 0; - } - public get(index:number) { - index = index / 2; - - const arrayIndex = index | 0; - if (this.isLowOrHighNibble(index)) { - return this.array[arrayIndex] >> 4; + const arrayIndex = index >> 1; + if ((index & 1) === 0) { + return this.array[arrayIndex] & 0xf; } else { - return this.array[arrayIndex] & 0x0f; + return this.array[arrayIndex] >> 4 & 0xf; } } public set(index:number, value:number) { - index = index / 2; - - const arrayIndex = index | 0; - if (this.isLowOrHighNibble(index)) { - this.array[arrayIndex] = value << 4 | this.array[arrayIndex] & 0xf; + const arrayIndex = index >> 1; + if ((index & 1) === 0) { + this.array[arrayIndex] = this.array[arrayIndex] & 0xf0 | value & 0xf; } else { - this.array[arrayIndex] = this.array[arrayIndex] & 0xf0 | value; + this.array[arrayIndex] = this.array[arrayIndex] & 0xf | (value & 0xf) << 4; } } diff --git a/server/MPClient.ts b/server/MPClient.ts index 262ffb0..8f84de1 100644 --- a/server/MPClient.ts +++ b/server/MPClient.ts @@ -19,6 +19,9 @@ import { PacketPlayerBlockPlacement } from "./packets/PlayerBlockPlacement"; import { Inventory } from "./inventories/Inventory"; import { PacketHoldingChange } from "./packets/HoldingChange"; import { PacketDisconnectKick } from "./packets/DisconnectKick"; +import { ItemStack } from "./inventories/ItemStack"; +import { PacketWindowItems } from "./packets/WindowItems"; +import { Block } from "./blocks/Block"; export class MPClient { private readonly mcServer:MinecraftServer; @@ -86,24 +89,24 @@ export class MPClient { if (message[0].startsWith("/")) { packet.message = ""; if (message[0] === "/tp") { - const x = this.entity.x = parseFloat(message[1]); - const y = this.entity.y = parseFloat(message[2]); - const z = this.entity.z = parseFloat(message[3]); + const x = this.entity.position.x = parseFloat(message[1]); + const y = this.entity.position.y = parseFloat(message[2]); + const z = this.entity.position.z = parseFloat(message[3]); this.send(new PacketPlayerPositionLook(x, y, y + 0.62, z, 0, 0, false).writeData()); Console.printInfo(packet.message = `Teleported ${this.entity.username} to ${message[1]} ${message[2]} ${message[3]}`); } else if (message[0] === "/csay") { this.mcServer.sendChatMessage(`[CONSOLE] ${message.slice(1, message.length).join(" ")}`); } else if (message[0] === "/top") { packet.message = `Woosh!`; - const topBlock = this.entity.world.getChunk(this.entity.x >> 4, this.entity.z >> 4).getTopBlockY(this.entity.x & 0xf, this.entity.z & 0xf); - this.send(new PacketPlayerPosition(this.entity.x, topBlock + 3, topBlock + 3.62, this.entity.z, false).writeData()); + const topBlock = this.entity.chunk.getTopBlockY(this.entity.position.x & 0xf, this.entity.position.z & 0xf); + this.send(new PacketPlayerPosition(this.entity.position.x, topBlock + 3, topBlock + 3.62, this.entity.position.z, false).writeData()); } else if (message[0] === "/tpx") { const dimension = parseInt(message[1]); if (this.mcServer.worlds.has(dimension)) { packet.message = "\u00a76Switching dimensions..."; this.switchDimension(dimension); } else { - packet.message = `\u00a7cNo dimension by id "${dimension} exists!"`; + packet.message = `\u00a7cNo dimension by id "${dimension}" exists!`; } } @@ -124,22 +127,16 @@ export class MPClient { } private handlePacketPlayerPosition(packet:PacketPlayerPosition) { - this.entity.x = packet.x; - this.entity.y = packet.y; - this.entity.z = packet.z; + this.entity.position.set(packet.x, packet.y, packet.z); } private handlePacketPlayerLook(packet:PacketPlayerLook) { - this.entity.yaw = packet.yaw; - this.entity.pitch = packet.pitch; + this.entity.rotation.set(packet.yaw, packet.pitch); } private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) { - this.entity.x = packet.x; - this.entity.y = packet.y; - this.entity.z = packet.z; - this.entity.yaw = packet.yaw; - this.entity.pitch = packet.pitch; + this.entity.position.set(packet.x, packet.y, packet.z); + this.entity.rotation.set(packet.yaw, packet.pitch); } private handlePacketPlayerDigging(packet:PacketPlayerDigging) { @@ -155,20 +152,37 @@ export class MPClient { if (packet.status === 0) { // Started digging } else if (packet.status === 2) { - if (this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) != 0) { + let brokenBlockId:number; + if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0) { + const metadata = this.entity.world.getBlockMetadata(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z); this.entity.world.setBlockWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, 0); + console.log("Metadata: ", metadata); + this.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata)); + this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData()); } } } + public getHeldItemStack() { + return this.inventory.getSlotItemStack(this.holdingIndex); + } + 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); + const itemStack = this.getHeldItemStack(); + if (itemStack == null || itemStack.size == 0) { + return; + } + + if (itemStack.isBlock) { + if (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); + } + } else { + // TODO: Handle item usage } } @@ -209,9 +223,7 @@ export class MPClient { this.send(new PacketRespawn(dimension).writeData()); //this.send(new PacketSpawnPosition(8, 64, 8).writeData()); - this.entity.x = 8; - this.entity.y = 70; - this.entity.z = 8; + this.entity.position.set(8, 60, 8); this.send(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData()); this.entity.forceUpdatePlayerChunks(); diff --git a/server/MinecraftServer.ts b/server/MinecraftServer.ts index 8f34212..5ebc5c9 100644 --- a/server/MinecraftServer.ts +++ b/server/MinecraftServer.ts @@ -225,10 +225,10 @@ export class MinecraftServer { socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, dimension).writeData()); socket.write(new PacketSpawnPosition(8, 64, 8).writeData()); - const thisPlayerSpawn = new PacketNamedEntitySpawn(clientEntity.entityId, clientEntity.username, clientEntity.absX, clientEntity.absY, clientEntity.absZ, clientEntity.absYaw, clientEntity.absPitch, 0).writeData(); + const thisPlayerSpawn = new PacketNamedEntitySpawn(clientEntity.entityId, clientEntity.username, clientEntity.absPosition.x, clientEntity.absPosition.y, clientEntity.absPosition.z, clientEntity.absRotation.yaw, clientEntity.absRotation.pitch, clientEntity.mpClient?.getHeldItemStack()?.itemID).writeData(); world.players.forEach(player => { if (player.entityId !== clientEntity.entityId && clientEntity.distanceTo(player) < World.ENTITY_MAX_SEND_DISTANCE) { - socket.write(new PacketNamedEntitySpawn(player.entityId, player.username, player.absX, player.absY, player.absZ, player.absYaw, player.absPitch, 0).writeData()); + socket.write(new PacketNamedEntitySpawn(player.entityId, player.username, player.absPosition.x, player.absPosition.y, player.absPosition.z, player.absRotation.yaw, player.absRotation.pitch, player.mpClient?.getHeldItemStack()?.itemID).writeData()); player.mpClient?.send(thisPlayerSpawn); } }); @@ -237,7 +237,6 @@ export class MinecraftServer { 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/Rotation.ts b/server/Rotation.ts new file mode 100644 index 0000000..63f4aff --- /dev/null +++ b/server/Rotation.ts @@ -0,0 +1,19 @@ +import { Vec2 } from "./Vec2"; + +export class Rotation extends Vec2 { + public get yaw() { + return this.x; + } + + public set yaw(value:number) { + this.x = value; + } + + public get pitch() { + return this.y; + } + + public set pitch(value:number) { + this.y = value; + } +} \ No newline at end of file diff --git a/server/Vec2.ts b/server/Vec2.ts new file mode 100644 index 0000000..98f2aac --- /dev/null +++ b/server/Vec2.ts @@ -0,0 +1,31 @@ +export class Vec2 { + public x:number; + public y:number; + + public constructor(x?:Vec2 | number, y?:number) { + if (typeof(x) === "number" && typeof(y) === "number") { + this.x = x; + this.y = y; + } else if (typeof(x) === "number" && typeof(y) !== "number") { + this.x = x; + this.y = x; + } else if (x instanceof Vec2) { + this.x = x.x; + this.y = x.y; + } else { + this.x = this.y = 0; + } + } + + public set(x?:Vec2 | number, y?:number) { + if (x instanceof Vec2) { + this.x = x.x; + this.y = x.y; + } else if (typeof(x) === "number" && typeof(y) === "number") { + this.x = x; + this.y = y; + } else { + this.x = this.y = 0; + } + } +} \ No newline at end of file diff --git a/server/Vec3.ts b/server/Vec3.ts index 2fef13b..ff2604d 100644 --- a/server/Vec3.ts +++ b/server/Vec3.ts @@ -3,7 +3,7 @@ export class Vec3 { public y:number; public z:number; - public constructor(x?:number, y?:number, z?:number) { + public constructor(x?:Vec3 | number, y?:number, z?:number) { if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") { this.x = x; this.y = y; @@ -12,14 +12,26 @@ export class Vec3 { this.x = x; this.y = x; this.z = x; + } else if (x instanceof Vec3) { + this.x = x.x; + this.y = x.y; + this.z = x.z; } else { this.x = this.y = this.z = 0; } } - public set(x:number, y:number, z:number) { - this.x = x; - this.y = y; - this.z = z; + public set(x?:Vec3 | number, y?:number, z?:number) { + if (x instanceof Vec3) { + this.x = x.x; + this.y = x.y; + this.z = x.z; + } else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") { + this.x = x; + this.y = y; + this.z = z; + } else { + this.x = this.y = this.z = 0; + } } } \ No newline at end of file diff --git a/server/blocks/Block.ts b/server/blocks/Block.ts index 7d124ea..c1cfd56 100644 --- a/server/blocks/Block.ts +++ b/server/blocks/Block.ts @@ -1,13 +1,19 @@ import { World } from "../World"; +import { BlockBehaviour } from "./BlockBehaviour"; import { BlockBehaviourFlower } from "./BlockBehaviourFlower"; +import { BlockBehaviourGrass } from "./BlockBehaviourGrass"; import { IBlockBehaviour } from "./IBlockBehaviour"; abstract class Behaviour { + public static base = new BlockBehaviour(); + + public static grass = new BlockBehaviourGrass(); public static flower = new BlockBehaviourFlower(); } export class Block { public readonly blockId:number; + public static readonly blocks:Array = new Array(); public static readonly lightPassage:Array = new Array(); public static readonly blockBehaviours:Array = new Array(); @@ -17,6 +23,7 @@ export class Block { Block.blocks[blockId] = this; Block.lightPassage[blockId] = 0; Block.blockNames[blockId] = ""; + Block.blockBehaviours[blockId] = Behaviour.base; this.blockId = blockId; } @@ -60,14 +67,16 @@ export class Block { } public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) { - if (this.behaviour !== undefined) { - this.behaviour.neighborBlockChange(world, x, y, z, blockId); - } + this.behaviour.neighborBlockChange(world, x, y, z, blockId); + } + + public droppedItem(blockId:number) { + this.behaviour.droppedItem(blockId); } // Define statics here static readonly stone = new Block(1).setBlockName("Stone"); - static readonly grass = new Block(2).setBlockName("Grass"); + static readonly grass = new Block(2).setBehaviour(Behaviour.grass).setBlockName("Grass"); static readonly dirt = new Block(3).setBlockName("Dirt"); static readonly bedrock = new Block(7).setBlockName("Bedrock"); diff --git a/server/blocks/BlockBehaviour.ts b/server/blocks/BlockBehaviour.ts index 9a4f8c1..c4ff9fa 100644 --- a/server/blocks/BlockBehaviour.ts +++ b/server/blocks/BlockBehaviour.ts @@ -3,4 +3,5 @@ import { IBlockBehaviour } from "./IBlockBehaviour"; export class BlockBehaviour implements IBlockBehaviour { public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {} + public droppedItem(blockId:number) { return blockId; } } \ No newline at end of file diff --git a/server/blocks/BlockBehaviourGrass.ts b/server/blocks/BlockBehaviourGrass.ts new file mode 100644 index 0000000..39504df --- /dev/null +++ b/server/blocks/BlockBehaviourGrass.ts @@ -0,0 +1,8 @@ +import { Block } from "./Block"; +import { BlockBehaviour } from "./BlockBehaviour"; + +export class BlockBehaviourGrass extends BlockBehaviour { + public droppedItem(blockId:number) { + return Block.dirt.blockId; + } +} \ No newline at end of file diff --git a/server/blocks/IBlockBehaviour.ts b/server/blocks/IBlockBehaviour.ts index d4f99b4..56cce8f 100644 --- a/server/blocks/IBlockBehaviour.ts +++ b/server/blocks/IBlockBehaviour.ts @@ -1,5 +1,6 @@ import { World } from "../World"; export interface IBlockBehaviour { - neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void + neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void, + droppedItem: (blockId:number) => number } \ No newline at end of file diff --git a/server/entities/Entity.ts b/server/entities/Entity.ts index 0588119..3457e27 100644 --- a/server/entities/Entity.ts +++ b/server/entities/Entity.ts @@ -1,8 +1,14 @@ import { Chunk } from "../Chunk"; import { MetadataEntry, MetadataWriter } from "../MetadataWriter"; +import { Rotation } from "../Rotation"; +import { Vec3 } from "../Vec3"; import { World } from "../World"; import { MetadataFieldType } from "../enums/MetadataFieldType"; +import { PacketEntityLook } from "../packets/EntityLook"; +import { PacketEntityLookRelativeMove } from "../packets/EntityLookRelativeMove"; import { PacketEntityMetadata } from "../packets/EntityMetadata"; +import { PacketEntityRelativeMove } from "../packets/EntityRelativeMove"; +import { PacketEntityTeleport } from "../packets/EntityTeleport"; import { IEntity } from "./IEntity"; export class Entity implements IEntity { @@ -11,14 +17,22 @@ export class Entity implements IEntity { public entityId:number; public world:World; - public x:number; - public y:number; - public z:number; - public lastX:number; - public lastY:number; - public lastZ:number; + + public position:Vec3; + public lastPosition:Vec3; + public absPosition:Vec3; + public lastAbsPosition:Vec3; + + public rotation:Rotation; + public lastRotation:Rotation; + public absRotation:Rotation; + public lastAbsRotation:Rotation; + + public velocity:Vec3; public health:number; + public wasHurt:boolean; + public isDead:boolean; public fire:number; @@ -36,12 +50,26 @@ export class Entity implements IEntity { this.fire = 0; this.world = world; - this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0; + + this.position = new Vec3(); + this.lastPosition = new Vec3(); + this.absPosition = new Vec3(); + this.lastAbsPosition = new Vec3(); + + this.rotation = new Rotation(); + this.lastRotation = new Rotation(); + this.absRotation = new Rotation(); + this.lastAbsRotation = new Rotation(); + + this.velocity = new Vec3(); + this.crouching = this.lastCrouchState = this.lastFireState = this.queuedChunkUpdate = false; - this.chunk = world.getChunk(this.x >> 4, this.z >> 4); + this.chunk = world.getChunk(this.position.x >> 4, this.position.z >> 4); this.health = 20; + this.wasHurt = false; + this.isDead = false; } sendToNearby(buffer:Buffer) { @@ -61,10 +89,7 @@ export class Entity implements IEntity { // 1 = On Fire // 2 = Player crouched // 4 = Player on mount? - //metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, 1)); - if (crouchStateChanged) { - metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, Number(this.fire > 0) + Number(this.crouching) * 2)); - } + metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, Number(this.fire > 0) + Number(this.crouching) * 2)); this.sendToNearby(new PacketEntityMetadata(this.entityId, metadata.writeBuffer()).writeData()); @@ -74,9 +99,9 @@ export class Entity implements IEntity { } distanceTo(entity:IEntity) { - const dX = entity.x - this.x, - dY = entity.y - this.y, - dZ = entity.z - this.z; + const dX = entity.position.x - this.position.x, + dY = entity.position.y - this.position.y, + dZ = entity.position.z - this.position.z; return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2)); } @@ -89,12 +114,14 @@ export class Entity implements IEntity { if (entity === undefined) { this.health -= damage; } + + this.wasHurt = true; } updateEntityChunk() { - const bitX = this.x >> 4; - const bitZ = this.z >> 4; - if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.queuedChunkUpdate) { + const bitX = this.position.x >> 4; + const bitZ = this.position.z >> 4; + if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.queuedChunkUpdate) { if (this.world.chunkExists(bitX, bitZ)) { this.chunk = this.world.getChunk(bitX, bitZ); this.queuedChunkUpdate = false; @@ -104,6 +131,44 @@ export class Entity implements IEntity { } } + private constrainRot(rot:number) { + return Math.min(Math.max(rot, -128), 127); + } + + private sendPositionUpdate() { + this.absPosition.set(Math.floor(this.position.x * 32), Math.floor(this.position.y * 32), Math.floor(this.position.z * 32)); + + // This is suuuuuper jank + this.absRotation.set( + this.constrainRot(Math.floor(((this.rotation.yaw - 180 >= 0 ? this.rotation.yaw - 180 : (this.rotation.yaw - 180) % 360 + 360) % 360 / 360) * 256) - 128), // Yaw + this.constrainRot(Math.floor((this.rotation.pitch % 360 * 256) / 360)) // Pitch + ); + const diffX = this.absPosition.x - this.lastAbsPosition.x; + const diffY = this.absPosition.y - this.lastAbsPosition.y; + const diffZ = this.absPosition.z - this.lastAbsPosition.z; + const diffYaw = this.absRotation.yaw - this.lastAbsRotation.yaw; + const diffPitch = this.absRotation.pitch - this.lastAbsRotation.pitch; + + const doRelativeMove = Math.abs(diffX) >= 4 || Math.abs(diffY) >= 4 || Math.abs(diffZ) >= 4; + const doLook = Math.abs(diffYaw) >= 4 || Math.abs(diffPitch) >= 4; + if (Math.abs(diffX) > 128 || Math.abs(diffY) > 128 || Math.abs(diffZ) > 128) { + this.world.sendToNearbyClients(this, new PacketEntityTeleport(this.entityId, this.absPosition.x, this.absPosition.y, this.absPosition.z, this.absRotation.yaw, this.absRotation.pitch).writeData()); + } else if (doRelativeMove && doLook) { + this.world.sendToNearbyClients(this, new PacketEntityLookRelativeMove(this.entityId, diffX, diffY, diffZ, this.absRotation.yaw, this.absRotation.pitch).writeData()); + } else if (doRelativeMove) { + this.world.sendToNearbyClients(this, new PacketEntityRelativeMove(this.entityId, diffX, diffY, diffZ).writeData()); + } else if (doLook) { + this.world.sendToNearbyClients(this, new PacketEntityLook(this.entityId, this.absRotation.yaw, this.absRotation.pitch).writeData()); + } + + if (doRelativeMove) { + this.lastAbsPosition.set(this.absPosition); + } + if (doLook) { + this.lastAbsRotation.set(this.absRotation); + } + } + onTick() { this.updateMetadata(); this.updateEntityChunk(); @@ -116,8 +181,17 @@ export class Entity implements IEntity { this.fire--; } - this.lastX = this.x; - this.lastY = this.y; - this.lastZ = this.z; + if (!this.isDead && this.health <= 0) { + this.isDead = true; + + } + + if (this.wasHurt) { + this.wasHurt = false; + } + + this.sendPositionUpdate(); + + this.lastPosition.set(this.position); } } \ No newline at end of file diff --git a/server/entities/EntityItem.ts b/server/entities/EntityItem.ts index f79fe4f..413bec4 100644 --- a/server/entities/EntityItem.ts +++ b/server/entities/EntityItem.ts @@ -6,11 +6,8 @@ export class EntityItem extends Entity { public age:number; public itemStack:ItemStack; - public constructor(world:World, x:number, y:number, z:number, itemStack:ItemStack) { + public constructor(world:World, itemStack:ItemStack) { super(world); - this.x = x; - this.y = y; - this.z = z; this.itemStack = itemStack; diff --git a/server/entities/EntityLiving.ts b/server/entities/EntityLiving.ts index 937c75d..f4282a1 100644 --- a/server/entities/EntityLiving.ts +++ b/server/entities/EntityLiving.ts @@ -1,3 +1,5 @@ +import { Rotation } from "../Rotation"; +import { Vec3 } from "../Vec3"; import { World } from "../World"; import { Block } from "../blocks/Block"; import { PacketAnimation } from "../packets/Animation"; @@ -10,30 +12,15 @@ import { Entity } from "./Entity"; import { IEntity } from "./IEntity"; export class EntityLiving extends Entity { - public yaw:number; - public lastYaw:number; - public pitch:number; - public lastPitch:number; public onGround:boolean; public fallDistance:number; public timeInWater:number; public headHeight:number; - public absX:number; - public absY:number; - public absZ:number; - public absYaw:number; - public absPitch:number; - public lastAbsX:number; - public lastAbsY:number; - public lastAbsZ:number; - public lastAbsYaw:number; - public lastAbsPitch:number; - public constructor(world:World) { super(world); - this.fallDistance = this.yaw = this.lastYaw = this.pitch = this.lastPitch = this.absX = this.absY = this.absZ = this.absYaw = this.absPitch = this.lastAbsX = this.lastAbsY = this.lastAbsZ = this.lastAbsYaw = this.lastAbsPitch = this.timeInWater = 0; + this.fallDistance = this.timeInWater = 0; this.onGround = true; this.headHeight = 1.62; } @@ -43,57 +30,17 @@ export class EntityLiving extends Entity { return; } super.damageFrom(damage, entity); + // Send Damage Animation packet this.sendToAllNearby(new PacketEntityStatus(this.entityId, 2).writeData()); } isInWater() { - return this.world.getChunkBlockId(this.chunk, this.x, this.y + this.headHeight, this.z) === Block.waterStill.blockId; - } - - private constrainRot(rot:number) { - return Math.min(Math.max(rot, -128), 127); - } - - private sendPositionUpdate() { - this.absX = Math.floor(this.x * 32); - this.absY = Math.floor(this.y * 32); - this.absZ = Math.floor(this.z * 32); - // This is suuuuuper jank - this.absYaw = this.constrainRot(Math.floor(((this.yaw - 180 >= 0 ? this.yaw - 180 : (this.yaw - 180) % 360 + 360) % 360 / 360) * 256) - 128); - this.absPitch = this.constrainRot(Math.floor((this.pitch % 360 * 256) / 360)); - const diffX = this.absX - this.lastAbsX; - const diffY = this.absY - this.lastAbsY; - const diffZ = this.absZ - this.lastAbsZ; - const diffYaw = this.absYaw - this.lastAbsYaw; - const diffPitch = this.absPitch - this.lastAbsPitch; - - const doRelativeMove = Math.abs(diffX) >= 4 || Math.abs(diffY) >= 4 || Math.abs(diffZ) >= 4; - const doLook = Math.abs(diffYaw) >= 4 || Math.abs(diffPitch) >= 4; - if (Math.abs(diffX) > 128 || Math.abs(diffY) > 128 || Math.abs(diffZ) > 128) { - this.world.sendToNearbyClients(this, new PacketEntityTeleport(this.entityId, this.absX, this.absY, this.absZ, this.absYaw, this.absPitch).writeData()); - } else if (doRelativeMove && doLook) { - this.world.sendToNearbyClients(this, new PacketEntityLookRelativeMove(this.entityId, diffX, diffY, diffZ, this.absYaw, this.absPitch).writeData()); - } else if (doRelativeMove) { - this.world.sendToNearbyClients(this, new PacketEntityRelativeMove(this.entityId, diffX, diffY, diffZ).writeData()); - } else if (doLook) { - this.world.sendToNearbyClients(this, new PacketEntityLook(this.entityId, this.absYaw, this.absPitch).writeData()); - } - - if (doRelativeMove) { - this.lastAbsX = this.absX; - this.lastAbsY = this.absY; - this.lastAbsZ = this.absZ; - } - if (doLook) { - this.lastAbsYaw = this.absYaw; - this.lastAbsPitch = this.absPitch; - } + return this.world.getChunkBlockId(this.chunk, this.position.x, this.position.y + this.headHeight, this.position.z) === Block.waterStill.blockId; } onTick() { super.onTick(); - this.sendPositionUpdate(); if (!this.onGround) { this.fallDistance @@ -111,8 +58,5 @@ export class EntityLiving extends Entity { } else { this.timeInWater = Number.MIN_SAFE_INTEGER; } - - this.lastYaw = this.yaw; - this.lastPitch = this.pitch; } } \ No newline at end of file diff --git a/server/entities/IEntity.ts b/server/entities/IEntity.ts index 832a069..810dae8 100644 --- a/server/entities/IEntity.ts +++ b/server/entities/IEntity.ts @@ -1,11 +1,10 @@ +import { Vec3 } from "../Vec3" + export interface IEntity { entityId:number, - x:number, - y:number, - z:number, - lastX:number, - lastY:number, - lastZ:number, + position:Vec3, + lastPosition:Vec3, + velocity:Vec3, crouching:boolean, updateMetadata:() => void, distanceTo:(entity:IEntity) => number, diff --git a/server/entities/Player.ts b/server/entities/Player.ts index 121a5a6..a122dcc 100644 --- a/server/entities/Player.ts +++ b/server/entities/Player.ts @@ -9,6 +9,8 @@ import { PacketUpdateHealth } from "../packets/UpdateHealth"; import { Inventory } from "../inventories/Inventory"; import { ItemStack } from "../inventories/ItemStack"; import { Block } from "../blocks/Block"; +import PlayerInventory from "../inventories/PlayerInventory"; +import { Item } from "../items/Item"; const CHUNK_LOAD_RANGE = 5; @@ -19,7 +21,7 @@ export class Player extends EntityLiving { public loadedChunks:Array; public justUnloaded:Array; public mpClient?:MPClient; - public inventory:Inventory; + public inventory:PlayerInventory; private lastHealth:number; @@ -30,16 +32,16 @@ export class Player extends EntityLiving { this.loadedChunks = new Array(); this.justUnloaded = new Array(); - this.inventory = new Inventory(44, "Player Inventory"); + this.inventory = new PlayerInventory(); - 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.inventory.setSlotItemStack(36, new ItemStack(Item.ironSword, 1)); + this.inventory.setSlotItemStack(37, new ItemStack(Item.ironPickaxe, 1)); + this.inventory.setSlotItemStack(38, new ItemStack(Item.ironShovel, 1)); + this.inventory.setSlotItemStack(39, new ItemStack(Item.ironAxe, 1)); + this.inventory.setSlotItemStack(43, new ItemStack(Block.dirt, 32)); this.username = username; - this.x = 8; - this.y = 64; - this.z = 8; + this.position.set(8, 64, 8); this.lastHealth = this.health; } @@ -49,9 +51,9 @@ export class Player extends EntityLiving { } private async updatePlayerChunks() { - const bitX = this.x >> 4; - const bitZ = this.z >> 4; - if (bitX != this.lastX >> 4 || bitZ != this.lastZ >> 4 || this.firstUpdate) { + const bitX = this.position.x >> 4; + const bitZ = this.position.z >> 4; + if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.firstUpdate) { if (this.firstUpdate) { this.firstUpdate = false; // TODO: Make this based on the player's initial coords diff --git a/server/inventories/Inventory.ts b/server/inventories/Inventory.ts index a842c29..5dabc42 100644 --- a/server/inventories/Inventory.ts +++ b/server/inventories/Inventory.ts @@ -3,7 +3,7 @@ import { ItemStack } from "./ItemStack"; import IInventory from "./IInventory"; export class Inventory implements IInventory { - private itemStacks:Array; + public itemStacks:Array; private size:number; private name:string; @@ -18,6 +18,20 @@ export class Inventory implements IInventory { this.name = name; } + addItemStack(itemStack:ItemStack) { + // Check bottom inventory row (hotbar) first. + /*let workingItemStack:ItemStack | null; + for (let slotId = 9; slotId <= 35; slotId++) { + if (itemStack.size === 0) { + break; + } + + if ((workingItemStack = this.itemStacks[slotId]) != null) { + workingItemStack.insert(itemStack); + } + }*/ + } + getInventoryName() { return this.name; } diff --git a/server/inventories/ItemStack.ts b/server/inventories/ItemStack.ts index bcd9f74..7941376 100644 --- a/server/inventories/ItemStack.ts +++ b/server/inventories/ItemStack.ts @@ -3,13 +3,15 @@ import { Item } from "../items/Item"; export class ItemStack { public readonly itemID:number; + public readonly isBlock:boolean; + public readonly maxSize: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.size = 0; this.damage = 0; } else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && damage === undefined) { this.itemID = blockOrItemOrItemID.blockId; @@ -21,7 +23,7 @@ export class ItemStack { this.damage = damage; } else if (blockOrItemOrItemID instanceof Item && size === undefined && damage === undefined) { this.itemID = blockOrItemOrItemID.shiftedItemID; - this.size = 1; + this.size = 0; this.damage = 0; } else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && damage === undefined) { this.itemID = blockOrItemOrItemID.shiftedItemID; @@ -35,9 +37,43 @@ export class ItemStack { this.itemID = blockOrItemOrItemID; this.size = size; this.damage = damage; + } else if (typeof(blockOrItemOrItemID) === "number" && typeof(size) === "number" && damage === undefined) { + this.itemID = blockOrItemOrItemID; + this.size = size; + this.damage = 0; + } else if (typeof(blockOrItemOrItemID) === "number" && size === undefined && damage === undefined) { + this.itemID = blockOrItemOrItemID; + this.size = 0; + this.damage = 0; } else { throw new Error(`ItemStack created with invalid properties (${typeof(blockOrItemOrItemID)}, ${typeof(size)}, ${typeof(damage)})`); } + + this.isBlock = this.itemID < 256; + this.maxSize = !this.isBlock ? Item.getByShiftedItemId(this.itemID).maxStackSize : 64; + } + + public insert(itemStack:ItemStack) { + const remainingSpace = this.spaceAvaliable; + if (remainingSpace === 0) { + return; + } + + if (remainingSpace >= itemStack.size) { + this.size += itemStack.size; + itemStack.size = 0; + return; + } + + if (remainingSpace < itemStack.size) { + this.size += remainingSpace; + itemStack.size -= remainingSpace; + } + } + + public get spaceAvaliable() { + // Stack size check for Item(s) and Block(s). + return Math.max(this.maxSize - this.size, 0); } split(amount:number) { diff --git a/server/inventories/PlayerInventory.ts b/server/inventories/PlayerInventory.ts new file mode 100644 index 0000000..ab464ac --- /dev/null +++ b/server/inventories/PlayerInventory.ts @@ -0,0 +1,69 @@ +import { Inventory } from "./Inventory"; +import { ItemStack } from "./ItemStack"; + +export default class PlayerInventory extends Inventory { + public constructor() { + super(44, "Player Inventory"); + } + + addItemStack(itemStack:ItemStack) { + const itemStacksOfSameType:Array = new Array(); + + // Check bottom inventory row (hotbar) first. + let workingItemStack:ItemStack | null; + for (let slotId = 36; slotId <= 44; slotId++) { + if ((workingItemStack = this.itemStacks[slotId]) != null) { + itemStacksOfSameType.push(workingItemStack); + } + } + + for (let slotId = 9; slotId <= 35; slotId++) { + if ((workingItemStack = this.itemStacks[slotId]) != null) { + itemStacksOfSameType.push(workingItemStack); + } + } + + // Insert into existing stacks first. + for (const inventoryItemStack of itemStacksOfSameType) { + // Exit early if we have nothing left + if (itemStack.size === 0) { + return; + } + + if (inventoryItemStack.itemID !== itemStack.itemID || inventoryItemStack.damage !== itemStack.damage) { + continue; + } + + inventoryItemStack.insert(itemStack); + } + + // Exit early if we have nothing left + if (itemStack.size === 0) { + return; + } + + for (let slotId = 36; slotId <= 44; slotId++) { + // Exit early if we have nothing left + if (itemStack.size === 0) { + return; + } + + if ((workingItemStack = this.itemStacks[slotId]) == null) { + const stack = this.itemStacks[slotId] = new ItemStack(itemStack.itemID, 0, itemStack.damage); + stack.insert(itemStack); + } + } + + for (let slotId = 9; slotId <= 35; slotId++) { + // Exit early if we have nothing left + if (itemStack.size === 0) { + return; + } + + if ((workingItemStack = this.itemStacks[slotId]) == null) { + const stack = this.itemStacks[slotId] = new ItemStack(itemStack.itemID, 0, itemStack.damage); + stack.insert(itemStack); + } + } + } +} \ No newline at end of file diff --git a/server/items/IItemBehaviour.ts b/server/items/IItemBehaviour.ts new file mode 100644 index 0000000..db70273 --- /dev/null +++ b/server/items/IItemBehaviour.ts @@ -0,0 +1,3 @@ +export interface IItemBehaviour { + +} \ No newline at end of file diff --git a/server/items/Item.ts b/server/items/Item.ts index 85a3a69..9b18e1a 100644 --- a/server/items/Item.ts +++ b/server/items/Item.ts @@ -1,10 +1,20 @@ export class Item { + public static items:Array = new Array(); + public maxStackSize:number; public shiftedItemID:number; + public name:string; public constructor(itemID:number) { this.shiftedItemID = 256 + itemID; this.maxStackSize = 64; + this.name = "UNNAMED"; + + Item.items[itemID] = this; + } + + public static getByShiftedItemId(shiftedItemID:number) { + return Item.items[shiftedItemID - 256]; } public setMaxStackSize(stackSize:number) { @@ -12,5 +22,19 @@ export class Item { return this; } + public setName(name:string) { + this.name = name; + + return this; + } + + public getName() { + return this.name; + } + // Define statics here + static ironShovel = new Item(0).setName("Iron Shovel"); + static ironPickaxe = new Item(1).setName("Iron Pickaxe"); + static ironAxe = new Item(2).setName("Iron Axe"); + static ironSword = new Item(11).setName("Iron Sword"); } \ No newline at end of file