Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
cf626ea
chore(protocol): start protocol changes for v1001
PMK744 Jun 17, 2026
366c891
chore: push current progress
PMK744 Jul 9, 2026
096a290
Fix name according to mojang
AnyBananaGAME Jul 9, 2026
f072fe0
Implement ClientBoundAttributeLayerSyncPacket
AnyBananaGAME Jul 9, 2026
6681889
Use new String enum for LevelSoundEventPacket
AnyBananaGAME Jul 9, 2026
3f697d6
Fix enum types + use mojang names
AnyBananaGAME Jul 9, 2026
5809024
Implement ClientCacheBlobStatus
AnyBananaGAME Jul 9, 2026
b76e4a5
Implement ClientboundUpdateSoundDataPacket
AnyBananaGAME Jul 9, 2026
3cce477
Use NetworkItemStackDescriptorCereal in MobArmorEquipmentPacket
AnyBananaGAME Jul 9, 2026
f9df67d
Implement new PartyDestinationCookieResponsePacket
AnyBananaGAME Jul 9, 2026
0416cf1
chunkId is now i32!?
AnyBananaGAME Jul 9, 2026
964da83
Implement SendPartyDestinationCookiePacket
AnyBananaGAME Jul 9, 2026
b173491
Implement ServerPresenceInfoPacket
AnyBananaGAME Jul 9, 2026
02623f9
A bunch of optional data ScriptDebugShape
AnyBananaGAME Jul 9, 2026
dae7e54
Implement ServerStoreInfoPacket
AnyBananaGAME Jul 9, 2026
60d997c
Implement SystemDiagnosticTimingInfo
AnyBananaGAME Jul 9, 2026
2116ad6
Implement ServerboundDiagnosticsPacket
AnyBananaGAME Jul 9, 2026
019aa0a
fix SubChunkRequestPacket
AnyBananaGAME Jul 9, 2026
bc85621
New field playerIdentifier inside GraphicsOverrideParameterPayload
AnyBananaGAME Jul 9, 2026
e5c882e
AttributeData
AnyBananaGAME Jul 9, 2026
02d7560
Add DolphinBoost and geyserboost movement effect types
AnyBananaGAME Jul 9, 2026
1a086ad
Event data
AnyBananaGAME Jul 9, 2026
0cdd500
Attribute enums
AnyBananaGAME Jul 9, 2026
966c46e
exports
AnyBananaGAME Jul 9, 2026
0770d7c
add new packets inside the enum
AnyBananaGAME Jul 9, 2026
56316b0
Use new Cereal Item types
AnyBananaGAME Jul 9, 2026
cee7346
Fix entity attacking
AnyBananaGAME Jul 9, 2026
2590175
Remove debug log
AnyBananaGAME Jul 9, 2026
b4981a1
Fix bad data
AnyBananaGAME Jul 9, 2026
afd5f35
remove debug logs from devapp
AnyBananaGAME Jul 9, 2026
03fc412
fix(plugins): align plugin hook typings with WorldEvent
wimziicals Oct 9, 2026
aef29c0
fix(plugins): bind event hooks passed via plugin options
wimziicals Oct 9, 2026
639ccd8
fix(plugins): apply the priority option
wimziicals Oct 9, 2026
6d89944
chore(protocol): start protocol changes for v2193
wimziicals Oct 10, 2026
e04a06f
fix(protocol): update transfer, play sound and boss event for v2193
wimziicals Oct 10, 2026
240c697
feat(protocol): implement clientbound update sound data payloads
wimziicals Oct 10, 2026
a1f053a
fix(protocol): use the v2168 serialized skin format
wimziicals Oct 10, 2026
731807d
fix(protocol): copy uuid bytes before reversing them
wimziicals Oct 10, 2026
3056af3
fix(protocol): update resource pack packets for v2168
wimziicals Oct 10, 2026
6f81620
fix(protocol): update start game for v2168
wimziicals Oct 10, 2026
8c7af39
fix(protocol): use the v2168 item and recipe formats
wimziicals Oct 10, 2026
98f8d09
fix(protocol): use the v2168 item stack layout
wimziicals Oct 10, 2026
b4bbfdb
fix(protocol): update player list, entity data and actor links for v2168
wimziicals Oct 10, 2026
6558fa9
fix(protocol): update chunk and movement packets for v2193
wimziicals Oct 10, 2026
b0295f9
fix(protocol): update player auth input and inventory transactions fo…
wimziicals Oct 10, 2026
fef6dab
fix(protocol): reset the stream offset in custom packet serializers
wimziicals Oct 10, 2026
c519652
fix(protocol): write int game rules as int32 in start game
wimziicals Oct 10, 2026
b8ccc35
feat(nbt): keep the element type of empty list tags
wimziicals Oct 10, 2026
0283127
feat(protocol): implement the jigsaw structure data packet
wimziicals Oct 10, 2026
88e337b
fix(core): send jigsaw structure data before start game
wimziicals Oct 10, 2026
4a1ad20
fix(protocol): update item stack requests for v2168
wimziicals Oct 10, 2026
6628790
fix(protocol): update item stack responses for v2193
wimziicals Oct 10, 2026
b220bf9
fix(protocol): remove inventory transaction presence markers for v2193
wimziicals Oct 10, 2026
8a7a8e1
feat(data): regenerate vanilla data for 1.26.50
wimziicals Oct 10, 2026
559a971
fix(core): send the definitions of vanilla data driven blocks
wimziicals Oct 10, 2026
f6e7adc
fix(protocol): update the shield network id for v2193
wimziicals Oct 10, 2026
85baf40
feat(core): connect fences, glass panes, bars and walls
wimziicals Oct 10, 2026
cadcf81
fix(core): keep torches standing on walls, fences and panes
wimziicals Oct 10, 2026
cefa217
fix(core): save worlds without players
wimziicals Oct 10, 2026
9c83a76
fix(core): stop sending chunks to disconnected players
wimziicals Oct 10, 2026
7455bd4
fix(core): only store block traits the block type does not provide
wimziicals Oct 10, 2026
8a26837
fix(protocol): write command permission levels as numbers in abilities
wimziicals Oct 10, 2026
c4a59fe
fix(core): follow the sneaking flag of player input
wimziicals Oct 10, 2026
d96a1fb
fix(core): do not place blocks on blocks players interact with
wimziicals Oct 10, 2026
3ef8d57
fix(core): keep the storage given when a block changes type
wimziicals Oct 10, 2026
0acd7d7
feat(core): execute commands at a position
wimziicals Oct 10, 2026
0de844c
feat(core): power and chain command blocks
wimziicals Oct 10, 2026
b17af52
fix(core): send the item of the slot a player is made to hold
wimziicals Oct 10, 2026
eb69017
fix(core): pick blocks as vanilla does
wimziicals Oct 10, 2026
f0e1ffe
fix(core): place blocks picked with their data
wimziicals Oct 10, 2026
9425ef0
fix(core): add item stacks to containers fully
wimziicals Oct 10, 2026
e020c9f
fix(core): give the requested amount of items
wimziicals Oct 10, 2026
96c42cb
fix(core): press buttons for as long as vanilla does
wimziicals Oct 10, 2026
70d0c25
fix(core): place blocks facing the way vanilla does
wimziicals Oct 10, 2026
b17f477
feat(core): break blocks that lose what holds them up
wimziicals Oct 10, 2026
7b672c1
fix(core): send placed blocks once, in their final state
wimziicals Oct 10, 2026
517b63b
chore(protocol): fix lint warnings in the v2193 changes
wimziicals Oct 15, 2026
8132d69
fix(core): only send the definitions of blocks defined by data
wimziicals Oct 15, 2026
8c39b62
feat(core): play the sounds players make to everyone around them
wimziicals Oct 15, 2026
e073a37
fix(core): craft from the recipe book, in the amount asked for
wimziicals Oct 15, 2026
95cb8de
fix(core): let players climb ladders and vines
wimziicals Oct 15, 2026
c71f477
fix(core): pick blocks into the first empty hotbar slot
wimziicals Oct 15, 2026
e9c9a6d
fix(core): place and drop the items of blocks named otherwise
wimziicals Oct 15, 2026
bb315c8
feat(core): place, use and break blocks as vanilla does
wimziicals Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion devapp/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Serenity, LevelDBProvider } from "@serenityjs/core";
import { Serenity, LevelDBProvider, NetworkBound } from "@serenityjs/core";
import { Pipeline } from "@serenityjs/plugins";
import { Packet } from "@serenityjs/protocol";

// Create a new Serenity instance
const serenity = new Serenity({
Expand All @@ -20,3 +21,13 @@ serenity.registerProvider(LevelDBProvider, { path: "./worlds" });
serenity.start().catch((reason) => {
serenity.logger.error("Failed to start SerenityJS server:", reason);
});

// serenity.network.on("all", ({ bound, packet }) => {
// if (bound !== NetworkBound.Client) return;

// if (packet.getId() === Packet.LevelChunk) return;
// if (packet.getId() === Packet.NetworkChunkPublisherUpdate) return;
// if (packet.getId() === Packet.BossEvent) return;

// console.log(`sending packet ${Packet[packet.getId()]} to client`);
// });
110 changes: 105 additions & 5 deletions packages/core/src/block/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
Gamemode,
LevelEvent,
LevelEventPacket,
LevelSoundEvent,
LevelSoundEventPacket,
UpdateBlockLayerType,
Vector3f
} from "@serenityjs/protocol";
Expand Down Expand Up @@ -48,6 +50,28 @@ import { BlockLevelStorage } from "./storage";
import { BlockDirectionTrait, BlockTrait } from "./traits";
import { BlockState } from "./types";

/**
* The blocks that a placed block takes the place of, rather than going beside, besides air and liquids.
*/
const ReplaceableBlocks = new Set<string>([
BlockIdentifier.Fire,
BlockIdentifier.SoulFire,
BlockIdentifier.ShortGrass,
BlockIdentifier.Fern,
BlockIdentifier.Deadbush,
BlockIdentifier.Seagrass,
BlockIdentifier.CrimsonRoots,
BlockIdentifier.WarpedRoots,
BlockIdentifier.NetherSprouts,
BlockIdentifier.HangingRoots,
BlockIdentifier.GlowLichen,
BlockIdentifier.Vine,
BlockIdentifier.StructureVoid,
"minecraft:bush",
"minecraft:short_dry_grass",
"minecraft:tall_dry_grass"
]);

/**
* Block is a class the represents an instance of a block in a dimension of a world.
* The Block instance contains its position, dimension, type, permutation, components, traits, and nbt data.
Expand Down Expand Up @@ -160,6 +184,22 @@ class Block {
return this.permutation.type.solid;
}

/**
* Whether or not a placed block takes the place of this block, rather than going beside it.
* This is the case for air, liquids, fire, and plants like short grass; snow only while it is one layer deep.
*/
public get isReplaceable(): boolean {
// Air and liquids are always replaced
if (this.isAir || this.isLiquid) return true;

// Snow is replaced only while it is one layer deep
if (this.identifier === BlockIdentifier.SnowLayer)
return this.getState<number>("height") === 0;

// Check if the block is one that is replaced
return ReplaceableBlocks.has(this.identifier);
}

/**
* Whether or not the block is loggable.
* Depening on the value, this will determine if the block can be logged with a fluid type block.
Expand Down Expand Up @@ -309,6 +349,13 @@ class Block {
continue;
}

// Check if the block type provides the trait, which is added below.
// Older saves stored these with the block too, so remove it from the storage.
if (this.isTypeTrait(traitType)) {
this.peekStorage()?.removeTrait(identifier);
continue;
}

// Add the trait to the block.
const trait = this.addTrait(traitType);

Expand Down Expand Up @@ -463,6 +510,13 @@ class Block {
continue;
}

// Check if the block type provides the trait, which is added below.
// Older saves stored these with the block too, so remove it from the storage.
if (this.isTypeTrait(traitType)) {
storage.removeTrait(identifier);
continue;
}

// Add the trait to the block.
const trait = this.addTrait(traitType);

Expand All @@ -474,14 +528,18 @@ class Block {
}

// Push the nbt data of the permutation to the block's nbt.
if (permutation.nbt.size > 0) this.getStorage().push(...permutation.nbt.values());
if (permutation.nbt.size > 0)
this.getStorage().push(...permutation.nbt.values());

// Iterate over all the traits and apply them to the block
for (const [, trait] of permutation.type.traits) this.addTrait(trait);

// Check if the block should be cached.
const blockStorage = this.peekStorage();
if (((blockStorage?.size ?? 0) > 0 || this.traits.size > 0) && !permutation.type.air) {
if (
((blockStorage?.size ?? 0) > 0 || this.traits.size > 0) &&
!permutation.type.air
) {
// Calculate the block hash using the position
const hash = BlockPosition.hash(this.position);

Expand Down Expand Up @@ -653,8 +711,9 @@ class Block {
? instance.identifier + "@" + instance.state
: instance.identifier;

// Add the trait to the block's storage
this.getStorage().addTrait(identifier);
// Add the trait to the block's storage, unless the block type provides it,
// as those are added to the block again whenever it is loaded
if (!this.isTypeTrait(trait)) this.getStorage().addTrait(identifier);

// Return the trait that was added
return instance;
Expand All @@ -673,6 +732,15 @@ class Block {
}
}

/**
* Whether a trait is provided by the block's type, rather than added to the block itself.
* @param trait The trait to check.
* @returns Whether the block's type provides the trait.
*/
private isTypeTrait(trait: typeof BlockTrait): boolean {
return this.type.traits.get(trait.identifier) === trait;
}

/**
* Get all traits currently applied to the block.
* @returns An array of all traits currently applied to the block.
Expand All @@ -699,7 +767,9 @@ class Block {
public peekStorage(): BlockLevelStorage | null {
if (this.storage) return this.storage;

this.storage = this.chunk.getBlockStorage(this.position) as BlockLevelStorage | null;
this.storage = this.chunk.getBlockStorage(
this.position
) as BlockLevelStorage | null;

return this.storage;
}
Expand Down Expand Up @@ -800,6 +870,33 @@ class Block {
this.dimension.broadcast(packet);
}

/**
* Plays a sound from the middle of the block to all players in the dimension.
* The client plays the sound from the sound set of the block the data names, so a lever clicks as a lever does.
* @param event The sound to play.
* @param data The network id of the block making the sound; defaults to this block's.
*/
public playSound(
event: LevelSoundEvent,
data = this.permutation.networkId
): void {
// Get the position of the block
const { x, y, z } = this.position;

// Create a new LevelSoundEventPacket
const packet = new LevelSoundEventPacket();
packet.event = event;
packet.position = new Vector3f(x + 0.5, y + 0.5, z + 0.5);
packet.data = data;
packet.actorIdentifier = ":";
packet.isBabyMob = false;
packet.isGlobal = false;
packet.uniqueActorId = -1n;

// Send the packet to all players in the dimension
this.dimension.broadcast(packet);
}

/**
* Gets the tool type required to break the block.
* @returns
Expand Down Expand Up @@ -1078,6 +1175,9 @@ class Block {
// Check if the drop is air, if so we will skip it.
if (drop.type === BlockIdentifier.Air) continue;

// Skip drops with no item, as blocks like fire have none
if (!this.world.itemPalette.getType(drop.type)) continue;

// Roll the drop amount.
const stackSize = drop.roll();

Expand Down
37 changes: 33 additions & 4 deletions packages/core/src/block/identity/type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BinaryStream } from "@serenityjs/binarystream";
import { CompoundTag, ListTag, StringTag } from "@serenityjs/nbt";
import { NetworkBlockTypeDefinition } from "@serenityjs/protocol";
import {
Expand All @@ -9,6 +10,7 @@ import {

import { BlockIdentifier, BlockMaterialSound } from "../../enums";
import { BlockTypeDefinition, BlockTypeProperties } from "../../types";
import { BlockItems } from "../../constants/block-items";
import { BlockState } from "../types";

import { ItemDrop } from "./drops";
Expand All @@ -17,6 +19,11 @@ import { BlockPermutation } from "./permutation";

import type { BlockTrait } from "../traits";

/**
* Matches the identifiers of double slabs.
*/
const DoubleSlabPattern = /double.*_slab$/;

/**
* BlockType represents a block type in the game, which hold all possible permutations the block can have.
*
Expand Down Expand Up @@ -73,8 +80,13 @@ class BlockType {
instance.drops.push(itemDrop);
}
}
// Register the default drop for the block type.
else instance.drops.push(new ItemDrop(type.identifier, 1, 1, 1));
// Register the default drop for the block type, which is its item.
// Blocks like signs drop an item of another name, and double slabs drop two of their slab.
else {
const item = BlockItems[type.identifier] ?? type.identifier;
const amount = DoubleSlabPattern.test(type.identifier) ? 2 : 1;
instance.drops.push(new ItemDrop(item, amount, amount, 1));
}

// Set the block type in the registry.
this.types.set(type.identifier as BlockIdentifier, instance);
Expand Down Expand Up @@ -107,6 +119,20 @@ class BlockType {
// Register the block permutation in the registry.
BlockPermutation.permutations.set(instance.networkId, instance);
}

// Iterate over the vanilla block types that are defined by data.
// The client only knows these from the definitions the server sends,
// which are added after the permutations, as creating those would add to them.
for (const type of BLOCK_TYPES) {
if (!("properties" in type) || !type.properties) continue;

// Read the definition of the block type.
const stream = new BinaryStream(Buffer.from(type.properties, "base64"));
const definition = CompoundTag.read(stream);

// Add the definition to the block type properties.
this.types.get(type.identifier)?.properties.push(...definition.values());
}
}

/**
Expand Down Expand Up @@ -240,11 +266,14 @@ class BlockType {
public getPermutation(state?: BlockState): BlockPermutation {
if (!state) return this.permutations[0] as BlockPermutation;

const cached = this.permutationStateIndex.get(this.getPermutationStateKey(state));
const cached = this.permutationStateIndex.get(
this.getPermutationStateKey(state)
);
if (cached) return cached;

for (const permutation of this.permutations)
if (permutation.matches(state as BlockState)) return permutation as BlockPermutation;
if (permutation.matches(state as BlockState))
return permutation as BlockPermutation;

return this.permutations[0] as BlockPermutation;
}
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/block/palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ class BlockPalette {
return this.getAllTypes().filter((type) => type instanceof CustomBlockType);
}

/**
* Gets the block types the client only knows from the definitions the server sends:
* custom block types, and the vanilla block types defined by data, like the wool and concrete stairs.
* Other vanilla block types are known to the client, and a definition sent for one replaces what it knows, like its sounds.
* @returns The block types defined by data.
*/
public getAllDataDrivenTypes(): Array<BlockType> {
return this.getAllTypes().filter(
(type) =>
type instanceof CustomBlockType ||
type.properties.has("vanilla_block_data")
);
}

/**
* Gets all block permutations from the palette.
* @returns All block permutations from the palette.
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/block/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,9 @@ class BlockLevelStorage extends CompoundTag {
if (index !== -1) {
traits.splice(index, 1);

// Remove the list once it is empty, so an empty list is not stored
if (traits.length === 0) super.delete("traits");

// Mark the chunk as needing to be saved
if (this.size > 0) this.chunk.dirty = true;
}
Expand Down
Loading
Loading