implement tile entity add + remove, and make loading more robust.
All checks were successful
Node.js Build / build (20.x) (push) Successful in 5m18s

This commit is contained in:
Holly Stubbs 2024-11-25 22:28:33 +00:00
parent c0872ead39
commit d82b86546a
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
15 changed files with 75 additions and 1133 deletions

1133
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -172,6 +172,18 @@ export default class Chunk {
return this.skyLight.set(x << 11 | z << 7 | y, value);
}
public getTileEntity(x:number, y:number, z:number) {
return this.tileEntities.get((x & 0xf) << 11 | (z & 0xf) << 7 | y);
}
public setTileEntity(tileEntity:TileEntity, x:number, y:number, z:number) {
return this.tileEntities.set((x & 0xf) << 11 | (z & 0xf) << 7 | y, tileEntity);
}
public removeTileEntity(x:number, y:number, z:number) {
return this.tileEntities.remove((x & 0xf) << 11 | (z & 0xf) << 7 | y);
}
public getBlockBuffer() {
return Buffer.from(this.blocks);
}

View file

@ -196,6 +196,7 @@ export default class MPClient {
//this.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
//this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData());
const blockBehaviour = Block.blockBehaviours[brokenBlockId];
blockBehaviour?.destroyed(this.entity.world, x, y, z);
const itemId = blockBehaviour.droppedItem(brokenBlockId);
if (itemId !== -1) {
const itemCount = blockBehaviour.droppedCount(brokenBlockId);
@ -277,6 +278,7 @@ export default class MPClient {
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);
Block.blockBehaviours[itemStack.itemID]?.placed(this.entity.world, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
this.inventory.dropEmptyItemStacks();
}
} else {

View file

@ -16,4 +16,8 @@ export default class Rotation extends Vec2 {
public set pitch(value:number) {
this.y = value;
}
toString() {
return `Rotation(${this.x},${this.y})`;
}
}

View file

@ -32,4 +32,8 @@ export default class Vec2 {
this.x = this.y = 0;
}
}
toString() {
return `Vec2(${this.x},${this.y})`;
}
}

View file

@ -72,4 +72,8 @@ export default class Vec3 {
toAbs() {
return new Vec3(Math.round(this.x * 32), Math.round(this.y * 32), Math.round(this.z * 32));
}
toString() {
return `Vec3(${this.x},${this.y},${this.z})`;
}
}

View file

@ -9,6 +9,7 @@ import SaveCompressionType from "./enums/SaveCompressionType";
import TileEntityLoader from "./tileentities/TileEntityLoader";
import UnsupportedError from "./errors/UnsupportedError";
import World from "./World";
import Block from "./blocks/Block";
enum FileMagic {
Chunk = 0xFC,
@ -267,7 +268,12 @@ export default class WorldSaveManager {
const tileEntityCount = chunkData.readUShort();
for (let i = 0; i < tileEntityCount; i++) {
const tileEntity = TileEntityLoader.FromSave(chunkData);
const blockAtTileEntity = chunk.getBlockId(tileEntity.pos.x, tileEntity.pos.y, tileEntity.pos.z);
if (blockAtTileEntity === tileEntity.forBlock.blockId) {
chunk.tileEntities.set(tileEntity.pos.x << 11 | tileEntity.pos.z << 7 | tileEntity.pos.y, tileEntity);
} else {
Console.printWarn(`Tile entity in chunk ${chunk.x},${chunk.z} block ${tileEntity.pos} has no associated block of type ${tileEntity.forBlock.blockName}, instead found ${Block.blockNames[blockAtTileEntity] ?? "Air"}. Skipping...`);
}
}
}

View file

@ -11,6 +11,7 @@ import BlockBehaviourStone from "./BlockBehaviourStone";
import BlockBehaviourTallGrass from "./BlockBehaviourTallGrass";
import IBlockBehaviour from "./IBlockBehaviour";
import World from "../World";
import BlockBehaviourChest from "./BlockBehaviourChest";
abstract class Behaviour {
public static base = new BlockBehaviour();
@ -23,6 +24,8 @@ abstract class Behaviour {
public static tallGrass = new BlockBehaviourTallGrass();
public static flower = new BlockBehaviourFlower();
public static chest = new BlockBehaviourChest();
public static redstoneOre = new BlockBehaviourRedstoneOre();
public static clay = new BlockBehaviourClay();
@ -210,7 +213,7 @@ export default class Block {
static readonly fire = new Block(51).setHardness(0).setLightEmission(1).setBlockName("Fire"); // TODO: Behavior script
static readonly mobSpawner = new Block(52).setHardness(5).setBlockName("Mob Spawner"); // TODO: Behavior script
static readonly woodenStairs = new Block(53).setBlockName("Wooden Stairs"); // TODO: Behavior script
static readonly chest = new Block(54).setHardness(2.5).setBlockName("Chest"); // TODO: Behavior script
static readonly chest = new Block(54).setHardness(2.5).setBehaviour(Behaviour.chest).setBlockName("Chest"); // TODO: Behavior script
static readonly redstoneDust = new Block(55).setHardness(0).setBlockName("Redstone Dust"); // TODO: Behavior script
static readonly diamondOre = new Block(56).setHardness(3).setBlockName("Diamond Ore"); // TODO: Behavior script
static readonly diamondBlock = new Block(57).setHardness(5).setBlockName("Diamond Block"); // TODO: Behavior script

View file

@ -7,6 +7,8 @@ import World from "../World";
export default class BlockBehaviour implements IBlockBehaviour {
public block!:Block;
public placed(world:World, x:number, y:number, z:number) {}
public destroyed(world:World, x:number, y:number, z:number) {}
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {}
public droppedItem(blockId:number) { return blockId; }
public droppedCount(blockId:number) { return 1; }

View file

@ -0,0 +1,16 @@
import TileEntityChest from "../tileentities/TileEntityChest";
import Vec3 from "../Vec3";
import World from "../World";
import BlockBehaviour from "./BlockBehaviour";
export default class BlockBehaviourChest extends BlockBehaviour {
public placed(world:World, x:number, y:number, z:number) {
const chunk = world.getChunk(x >> 4, z >> 4);
chunk.setTileEntity(new TileEntityChest(new Vec3(x & 0xf, y, z & 0xf)), x, y, z);
}
public destroyed(world:World, x:number, y:number, z:number) {
const chunk = world.getChunk(x >> 4, z >> 4);
chunk.removeTileEntity(x, y, z);
}
}

View file

@ -6,6 +6,8 @@ import World from "../World";
export default interface IBlockBehaviour {
block:Block,
placed(world:World, x:number, y:number, z:number): void,
destroyed(world:World, x:number, y:number, z:number): void,
neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void,
droppedItem: (blockId:number) => number,
droppedCount: (blockId:number) => number,

View file

@ -42,6 +42,7 @@ export default class Player extends EntityLiving {
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(42, new ItemStack(Block.chest, 32));
this.inventory.setSlotItemStack(43, new ItemStack(Block.dirt, 32));
this.trackedEquipment = new Array<ItemStack | null>();

View file

@ -2,9 +2,8 @@ import { IReader, IWriter } from "bufferstuff";
import Block from "../blocks/Block";
import TileEntityType from "../enums/TileEntityType";
import Vec3 from "../Vec3";
import TileEntityChest from "./TileEntityChest";
export default class TileEntity {
export default abstract class TileEntity {
public readonly type: TileEntityType;
public readonly forBlock: Block;
public readonly pos: Vec3;
@ -19,7 +18,6 @@ export default class TileEntity {
public toSave(writer:IWriter) {
writer.writeUByte(this.type);
writer.writeUByte(this.forBlock.blockId);
writer.writeUByte(this.pos.x).writeUByte(this.pos.y).writeUByte(this.pos.z);
}
}

View file

@ -8,8 +8,8 @@ import Inventory from "../inventories/Inventory";
export default class TileEntityChest extends TileEntity {
public inventory:Inventory;
public constructor(type: TileEntityType, forBlockId: Block, position: Vec3) {
super(type, forBlockId, position);
public constructor(position: Vec3) {
super(TileEntityType.Chest, Block.chest, position);
this.inventory = new Inventory(9 * 3, "Chest");
}

View file

@ -1,21 +1,20 @@
import { IReader } from "bufferstuff";
import Block from "../blocks/Block";
import TileEntity from "./TileEntity";
import TileEntityChest from "./TileEntityChest";
import TileEntityType from "../enums/TileEntityType";
import Vec3 from "../Vec3";
import UnsupportedError from "../errors/UnsupportedError";
// I would like this to be in TileEntity, but recursive dependency.
export default abstract class TileEntityLoader {
public static FromSave(reader:IReader) : TileEntity {
let tileEntity:TileEntity;
const type: TileEntityType = reader.readUByte();
const forBlock = Block.blocks[reader.readUByte()];
const position = new Vec3(reader.readUByte(), reader.readUByte(), reader.readUByte());
if (type === TileEntityType.Chest) {
tileEntity = new TileEntityChest(type, forBlock, position);
tileEntity = new TileEntityChest(position);
} else {
tileEntity = new TileEntity(type, forBlock, position);
throw new UnsupportedError("Unsupported tile entity type encountered");
}
// Call instantiated class' fromSave
tileEntity.fromSave(reader);