Port to 24w03b (#3537)

* Deprecate FabricBlockSettings

* Deprecate FabricItemSettings

* Start on 24w03a

* Main menu :)

* Update mappings

* PayloadTypeRegistry

* Networking part 2 of many

* Networking part 3 of many

* Networking part 4 of many

* Recipe api

* Port Item API to 1.20.5

* Is this even right?

* Port FabricParticleTypes to 1.20.5

* Remove redundant fuel caching logic

* Remove fabric-containers-v0, deprecated since 2020

* Regsync work

* Adapt screen handler to new networking

* Update yarn + more work

* More mapping updates

* Compile fixes

* Checkstyle + small fixes

* Single and multiplayer fixes

* Handle play packets on main thread.

* Update mappings

* Even more networking

* Networking tests

* Fix todo's

* Update javadocs

* Networking API improvements

* Some small regsync refactors

* Fix handling of null NBT in NbtIngredient

* Update fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/FabricBlockSettings.java

Co-authored-by: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com>

* Update fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/block/FabricBlockSettings.java

Co-authored-by: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com>

* Add context objects

* ChannelInfoHolder.getPendingChannelsNames -> fabric_getPendingChannelsNames

* Fix crash

* send `c:register` packet for play phase instead of config (#3544)

* Bump version

---------

Co-authored-by: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com>
Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>
Co-authored-by: Drex <nicknamedrex@gmail.com>
Co-authored-by: deirn <deirn@bai.lol>
This commit is contained in:
modmuss 2024-01-22 18:24:37 +00:00 committed by GitHub
parent e89ad72381
commit 7b70ea8a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
143 changed files with 2272 additions and 3480 deletions

View file

@ -1,6 +0,0 @@
version = getSubprojectVersion(project)
moduleDependencies(project, [
'fabric-api-base',
'fabric-networking-api-v1'
])

View file

@ -1,29 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.screen.ScreenHandler;
/**
* @deprecated Use {@link net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry.Factory} instead.
*/
@Deprecated
@FunctionalInterface
public interface ContainerScreenFactory<C extends ScreenHandler> {
HandledScreen create(C container);
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.screen;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.container.ContainerFactory;
import net.fabricmc.fabric.api.container.ContainerProviderRegistry;
import net.fabricmc.fabric.impl.client.container.ScreenProviderRegistryImpl;
/**
* @deprecated Use {@link net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry} instead.
*/
@Deprecated
public interface ScreenProviderRegistry {
ScreenProviderRegistry INSTANCE = new ScreenProviderRegistryImpl();
/**
* Register a "Container -&gt; ContainerScreen" factory. This is used only on the client side.
*
* @param identifier a shared identifier, this identifier should also be used to register a container using {@link ContainerProviderRegistry}
* @param containerScreenFactory the supplier that should be used to create the new gui
*/
<C extends ScreenHandler> void registerFactory(Identifier identifier, ContainerScreenFactory<C> containerScreenFactory);
/**
* Register a "packet -&gt; ContainerScreen" factory. This is used only on the client side, and allows you
* to override the default behaviour of re-using the existing "packet -&gt; Container" logic.
*
* @param identifier a shared identifier, this identifier should also be used to register a container using {@link ContainerProviderRegistry}
* @param factory the gui factory, this should return a new {@link HandledScreen}
*/
void registerFactory(Identifier identifier, ContainerFactory<HandledScreen> factory);
}

View file

@ -1,93 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.client.container;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.screen.ContainerScreenFactory;
import net.fabricmc.fabric.api.client.screen.ScreenProviderRegistry;
import net.fabricmc.fabric.api.container.ContainerFactory;
import net.fabricmc.fabric.api.container.ContainerProviderRegistry;
import net.fabricmc.fabric.impl.container.ContainerProviderImpl;
public class ScreenProviderRegistryImpl implements ScreenProviderRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(ScreenProviderRegistryImpl.class);
private static final Map<Identifier, ContainerFactory<HandledScreen>> FACTORIES = new HashMap<>();
@Override
public void registerFactory(Identifier identifier, ContainerFactory<HandledScreen> factory) {
if (FACTORIES.containsKey(identifier)) {
throw new RuntimeException("A factory has already been registered as " + identifier + "!");
}
FACTORIES.put(identifier, factory);
}
@Override
public <C extends ScreenHandler> void registerFactory(Identifier identifier, ContainerScreenFactory<C> containerScreenFactory) {
registerFactory(identifier, (syncId, identifier1, player, buf) -> {
C container = ((ContainerProviderImpl) ContainerProviderRegistry.INSTANCE).createContainer(syncId, identifier1, player, buf);
if (container == null) {
LOGGER.error("Could not open container for {} - a null object was created!", identifier1.toString());
return null;
}
return containerScreenFactory.create(container);
});
}
public static void init() {
ClientPlayNetworking.registerGlobalReceiver(ContainerProviderImpl.OPEN_CONTAINER, (client, handler, buf, responseSender) -> {
Identifier identifier = buf.readIdentifier();
int syncId = buf.readUnsignedByte();
// Retain the buf since we must open the screen handler with it's extra modded data on the client thread
buf.retain();
client.execute(() -> {
try {
ContainerFactory<HandledScreen> factory = FACTORIES.get(identifier);
if (factory == null) {
LOGGER.error("No GUI factory found for {}!", identifier.toString());
return;
}
ClientPlayerEntity player = client.player;
HandledScreen<?> gui = factory.create(syncId, identifier, player, buf);
player.currentScreenHandler = gui.getScreenHandler();
client.setScreen(gui);
} finally {
buf.release();
}
});
});
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.container;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
/**
* @deprecated Use {@link net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry.ExtendedClientHandlerFactory} instead.
*/
@Deprecated
@FunctionalInterface
public interface ContainerFactory<T> {
/**
* Creates the new object.
*
* @param syncId The container synchronization ID.
* @param identifier the Identifier is the name that was used when registering the factory
* @param player the player that is opening the gui/container
* @param buf the buffer contains the same data that was provided with {@link net.fabricmc.fabric.api.container.ContainerProviderRegistry#openContainer}
* @return the new gui or container
*/
T create(int syncId, Identifier identifier, PlayerEntity player, PacketByteBuf buf);
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.container;
import java.util.function.Consumer;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.container.ContainerProviderImpl;
/**
* @deprecated Use {@link net.fabricmc.fabric.api.screenhandler.v1.ScreenHandlerRegistry} instead.
*/
@Deprecated
public interface ContainerProviderRegistry {
ContainerProviderRegistry INSTANCE = new ContainerProviderImpl();
/**
* Register a "packet buffer -&gt; container" factory. This is used both on the client and server side.
*
* @param identifier a shared identifier, this identifier should also be used to register a container using {@link net.fabricmc.fabric.api.client.screen.ScreenProviderRegistry}
* @param factory the ContainerFactory that should return a new {@link ScreenHandler}
*/
void registerFactory(Identifier identifier, ContainerFactory<ScreenHandler> factory);
/**
* Open a modded container.
*
* @param identifier the identifier that was used when registering the container
* @param player the player that should open the container
* @param writer a PacketByteBuf where data can be written to, this data is then accessible by the container factory when creating the container or the gui
*/
void openContainer(Identifier identifier, ServerPlayerEntity player, Consumer<PacketByteBuf> writer);
/**
* Open a modded container. This should be called on the server side - it has no effect on the client side.
*
* @param identifier the identifier that was used when registering the container
* @param player the player that should open the container
* @param writer a PacketByteBuf where data can be written to, this data is then accessible by the container factory when creating the container or the gui
*/
void openContainer(Identifier identifier, PlayerEntity player, Consumer<PacketByteBuf> writer);
}

View file

@ -1,117 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.container;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.container.ContainerFactory;
import net.fabricmc.fabric.api.container.ContainerProviderRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.mixin.container.ServerPlayerEntityAccessor;
public class ContainerProviderImpl implements ContainerProviderRegistry {
public static final Identifier OPEN_CONTAINER = new Identifier("fabric", "container/open");
private static final Logger LOGGER = LoggerFactory.getLogger(ContainerProviderImpl.class);
private static final Map<Identifier, ContainerFactory<ScreenHandler>> FACTORIES = new HashMap<>();
@Override
public void registerFactory(Identifier identifier, ContainerFactory<ScreenHandler> factory) {
if (FACTORIES.containsKey(identifier)) {
throw new RuntimeException("A factory has already been registered as " + identifier.toString());
}
FACTORIES.put(identifier, factory);
}
@Override
public void openContainer(Identifier identifier, PlayerEntity player, Consumer<PacketByteBuf> writer) {
if (!(player instanceof ServerPlayerEntity)) {
LOGGER.warn("Please only use ContainerProviderRegistry.openContainer() with server-sided player entities!");
return;
}
openContainer(identifier, (ServerPlayerEntity) player, writer);
}
private boolean emittedNoSyncHookWarning = false;
@Override
public void openContainer(Identifier identifier, ServerPlayerEntity player, Consumer<PacketByteBuf> writer) {
int syncId;
if (player instanceof ServerPlayerEntitySyncHook) {
ServerPlayerEntitySyncHook serverPlayerEntitySyncHook = (ServerPlayerEntitySyncHook) player;
syncId = serverPlayerEntitySyncHook.fabric_incrementSyncId();
} else if (player instanceof ServerPlayerEntityAccessor) {
if (!emittedNoSyncHookWarning) {
LOGGER.warn("ServerPlayerEntitySyncHook could not be applied - fabric-containers is using a hack!");
emittedNoSyncHookWarning = true;
}
syncId = (((ServerPlayerEntityAccessor) player).getScreenHandlerSyncId() + 1) % 100;
((ServerPlayerEntityAccessor) player).setScreenHandlerSyncId(syncId);
} else {
throw new RuntimeException("Neither ServerPlayerEntitySyncHook nor Accessor present! This should not happen!");
}
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
buf.writeIdentifier(identifier);
buf.writeByte(syncId);
writer.accept(buf);
player.networkHandler.sendPacket(ServerPlayNetworking.createS2CPacket(OPEN_CONTAINER, buf));
PacketByteBuf clonedBuf = new PacketByteBuf(buf.duplicate());
clonedBuf.readIdentifier();
clonedBuf.readUnsignedByte();
ScreenHandler screenHandler = createContainer(syncId, identifier, player, clonedBuf);
if (screenHandler == null) {
return;
}
player.currentScreenHandler = screenHandler;
((ServerPlayerEntityAccessor) player).callOnScreenHandlerOpened(screenHandler);
}
public <C extends ScreenHandler> C createContainer(int syncId, Identifier identifier, PlayerEntity player, PacketByteBuf buf) {
ContainerFactory<ScreenHandler> factory = FACTORIES.get(identifier);
if (factory == null) {
LOGGER.error("No container factory found for {}!", identifier.toString());
return null;
}
//noinspection unchecked
return (C) factory.create(syncId, identifier, player, buf);
}
}

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.container;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
@Mixin(ServerPlayerEntity.class)
public interface ServerPlayerEntityAccessor {
@Accessor
int getScreenHandlerSyncId();
@Accessor
void setScreenHandlerSyncId(int syncId);
@Invoker()
void callOnScreenHandlerOpened(ScreenHandler screenHandler);
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.container;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.server.network.ServerPlayerEntity;
import net.fabricmc.fabric.impl.container.ServerPlayerEntitySyncHook;
@Mixin(ServerPlayerEntity.class)
public abstract class ServerPlayerEntityMixin implements ServerPlayerEntitySyncHook {
@Shadow
private int screenHandlerSyncId;
@Shadow
protected abstract void incrementScreenHandlerSyncId();
@Override
public int fabric_incrementSyncId() {
incrementScreenHandlerSyncId();
return screenHandlerSyncId;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,11 +0,0 @@
{
"required": false,
"package": "net.fabricmc.fabric.mixin.container",
"compatibilityLevel": "JAVA_17",
"mixins": [
"ServerPlayerEntityMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -1,11 +0,0 @@
{
"required": false,
"package": "net.fabricmc.fabric.mixin.container",
"compatibilityLevel": "JAVA_17",
"mixins": [
"ServerPlayerEntityAccessor"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -1,36 +0,0 @@
{
"schemaVersion": 1,
"id": "fabric-containers-v0",
"name": "Fabric Containers (v0)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-containers-v0/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.15.6",
"fabric-api-base": "*",
"fabric-networking-api-v1": "*"
},
"description": "Adds hooks for containers.",
"mixins": [
"fabric-containers-v0.mixins.json",
"fabric-containers-v0.accurate.mixins.json"
],
"entrypoints": {
"client": [
"net.fabricmc.fabric.impl.client.container.ScreenProviderRegistryImpl::init"
]
},
"custom": {
"fabric-api:module-lifecycle": "deprecated"
}
}

View file

@ -18,6 +18,7 @@ package net.fabricmc.fabric.test.lookup;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.item.BlockItem; import net.minecraft.item.BlockItem;
import net.minecraft.item.Item; import net.minecraft.item.Item;
@ -28,7 +29,6 @@ import net.minecraft.util.math.Direction;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.fabricmc.fabric.test.lookup.api.ItemApis; import net.fabricmc.fabric.test.lookup.api.ItemApis;
import net.fabricmc.fabric.test.lookup.api.ItemInsertable; import net.fabricmc.fabric.test.lookup.api.ItemInsertable;
@ -41,17 +41,17 @@ public class FabricApiLookupTest implements ModInitializer {
public static final String MOD_ID = "fabric-lookup-api-v1-testmod"; public static final String MOD_ID = "fabric-lookup-api-v1-testmod";
// Chute - Block without model that transfers item from the container above to the container below. // Chute - Block without model that transfers item from the container above to the container below.
// It's meant to work with unsided containers: chests, dispensers, droppers and hoppers. // It's meant to work with unsided containers: chests, dispensers, droppers and hoppers.
public static final ChuteBlock CHUTE_BLOCK = new ChuteBlock(FabricBlockSettings.create()); public static final ChuteBlock CHUTE_BLOCK = new ChuteBlock(AbstractBlock.Settings.create());
public static final BlockItem CHUTE_ITEM = new BlockItem(CHUTE_BLOCK, new Item.Settings()); public static final BlockItem CHUTE_ITEM = new BlockItem(CHUTE_BLOCK, new Item.Settings());
public static BlockEntityType<ChuteBlockEntity> CHUTE_BLOCK_ENTITY_TYPE; public static BlockEntityType<ChuteBlockEntity> CHUTE_BLOCK_ENTITY_TYPE;
// Cobble gen - Block without model that can generate infinite cobblestone when placed above a chute. // Cobble gen - Block without model that can generate infinite cobblestone when placed above a chute.
// It's meant to test BlockApiLookup#registerSelf. // It's meant to test BlockApiLookup#registerSelf.
public static final CobbleGenBlock COBBLE_GEN_BLOCK = new CobbleGenBlock(FabricBlockSettings.create()); public static final CobbleGenBlock COBBLE_GEN_BLOCK = new CobbleGenBlock(AbstractBlock.Settings.create());
public static final BlockItem COBBLE_GEN_ITEM = new BlockItem(COBBLE_GEN_BLOCK, new Item.Settings()); public static final BlockItem COBBLE_GEN_ITEM = new BlockItem(COBBLE_GEN_BLOCK, new Item.Settings());
public static BlockEntityType<CobbleGenBlockEntity> COBBLE_GEN_BLOCK_ENTITY_TYPE; public static BlockEntityType<CobbleGenBlockEntity> COBBLE_GEN_BLOCK_ENTITY_TYPE;
// Testing for item api lookups is done in the `item` package. // Testing for item api lookups is done in the `item` package.
public static final InspectorBlock INSPECTOR_BLOCK = new InspectorBlock(FabricBlockSettings.create()); public static final InspectorBlock INSPECTOR_BLOCK = new InspectorBlock(AbstractBlock.Settings.create());
public static final BlockItem INSPECTOR_ITEM = new BlockItem(INSPECTOR_BLOCK, new Item.Settings()); public static final BlockItem INSPECTOR_ITEM = new BlockItem(INSPECTOR_BLOCK, new Item.Settings());
@Override @Override

View file

@ -18,12 +18,12 @@ package net.fabricmc.fabric.test.lookup;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.class_9062;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.ItemActionResult;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -38,7 +38,7 @@ public class InspectorBlock extends Block {
} }
@Override @Override
public class_9062 method_55765(ItemStack stack, BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult blockHitResult) { public ItemActionResult onUseWithItem(ItemStack stack, BlockState blockState, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult blockHitResult) {
Inspectable inspectable = FabricItemApiLookupTest.INSPECTABLE.find(stack, null); Inspectable inspectable = FabricItemApiLookupTest.INSPECTABLE.find(stack, null);
if (inspectable != null) { if (inspectable != null) {
@ -46,10 +46,10 @@ public class InspectorBlock extends Block {
player.sendMessage(inspectable.inspect(), true); player.sendMessage(inspectable.inspect(), true);
} }
return class_9062.method_55644(world.isClient()); return ItemActionResult.success(world.isClient());
} }
return class_9062.PASS_TO_DEFAULT_BLOCK_INTERACTION; return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
} }
@Override @Override

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.impl.content.registry; package net.fabricmc.fabric.impl.content.registry;
import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
@ -31,7 +30,6 @@ import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.tag.TagKey; import net.minecraft.registry.tag.TagKey;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
import net.fabricmc.fabric.api.registry.FuelRegistry; import net.fabricmc.fabric.api.registry.FuelRegistry;
// TODO: Clamp values to 32767 (+ add hook for mods which extend the limit to disable the check?) // TODO: Clamp values to 32767 (+ add hook for mods which extend the limit to disable the check?)
@ -39,23 +37,13 @@ public final class FuelRegistryImpl implements FuelRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(FuelRegistryImpl.class); private static final Logger LOGGER = LoggerFactory.getLogger(FuelRegistryImpl.class);
private final Object2IntMap<ItemConvertible> itemCookTimes = new Object2IntLinkedOpenHashMap<>(); private final Object2IntMap<ItemConvertible> itemCookTimes = new Object2IntLinkedOpenHashMap<>();
private final Object2IntMap<TagKey<Item>> tagCookTimes = new Object2IntLinkedOpenHashMap<>(); private final Object2IntMap<TagKey<Item>> tagCookTimes = new Object2IntLinkedOpenHashMap<>();
private volatile Map<Item, Integer> fuelTimeCache = null; // thread safe via copy-on-write mechanism
public FuelRegistryImpl() { public FuelRegistryImpl() {
// Reset cache after tags change since it depends on tags.
CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> {
resetCache();
});
} }
public Map<Item, Integer> getFuelTimes() { public Map<Item, Integer> getFuelTimes() {
Map<Item, Integer> ret = fuelTimeCache; // Cached by vanilla now
return AbstractFurnaceBlockEntity.createFuelTimeMap();
if (ret == null) {
fuelTimeCache = ret = new IdentityHashMap<>(AbstractFurnaceBlockEntity.createFuelTimeMap()); // IdentityHashMap is faster than vanilla's LinkedHashMap and suitable for Item keys
}
return ret;
} }
@Override @Override
@ -138,6 +126,7 @@ public final class FuelRegistryImpl implements FuelRegistry {
} }
public void resetCache() { public void resetCache() {
fuelTimeCache = null; // Note: tag reload is already handled by vanilla, see DataPackContents#refresh
AbstractFurnaceBlockEntity.clearFuelTimes();
} }
} }

View file

@ -168,7 +168,7 @@ public final class ContentRegistryTest implements ModInitializer {
} }
@Override @Override
public ActionResult method_55766(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
// Emit the test event // Emit the test event
world.emitGameEvent(player, TEST_EVENT, pos); world.emitGameEvent(player, TEST_EVENT, pos);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;

View file

@ -16,7 +16,7 @@
"FabricMC" "FabricMC"
], ],
"depends": { "depends": {
"fabricloader": ">=0.15.1", "fabricloader": ">=0.15.6",
"fabric-entity-events-v1": "*", "fabric-entity-events-v1": "*",
"fabric-object-builder-api-v1": "*" "fabric-object-builder-api-v1": "*"
}, },

View file

@ -56,7 +56,7 @@ public interface FabricElytraItem {
if (!entity.getWorld().isClient && nextRoll % 10 == 0) { if (!entity.getWorld().isClient && nextRoll % 10 == 0) {
if ((nextRoll / 10) % 2 == 0) { if ((nextRoll / 10) % 2 == 0) {
chestStack.damage(1, entity, p -> p.sendEquipmentBreakStatus(EquipmentSlot.CHEST)); chestStack.damage(1, entity, EquipmentSlot.CHEST);
} }
entity.emitGameEvent(GameEvent.ELYTRA_GLIDE); entity.emitGameEvent(GameEvent.ELYTRA_GLIDE);

View file

@ -40,7 +40,7 @@ abstract class LivingEntityMixin extends Entity {
* Handle ALLOW and CUSTOM {@link EntityElytraEvents} when an entity is fall flying. * Handle ALLOW and CUSTOM {@link EntityElytraEvents} when an entity is fall flying.
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/entity/EquipmentSlot;CHEST:Lnet/minecraft/entity/EquipmentSlot;"), method = "tickFallFlying()V", allow = 1, cancellable = true) @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getEquippedStack(Lnet/minecraft/entity/EquipmentSlot;)Lnet/minecraft/item/ItemStack;"), method = "tickFallFlying()V", allow = 1, cancellable = true)
void injectElytraTick(CallbackInfo info) { void injectElytraTick(CallbackInfo info) {
LivingEntity self = (LivingEntity) (Object) this; LivingEntity self = (LivingEntity) (Object) this;

View file

@ -46,7 +46,7 @@ public class TestBedBlock extends Block {
} }
@Override @Override
public ActionResult method_55766(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if (state.get(OCCUPIED)) { if (state.get(OCCUPIED)) {
player.sendMessage(Text.translatable("block.minecraft.bed.occupied"), true); player.sendMessage(Text.translatable("block.minecraft.bed.occupied"), true);
return ActionResult.CONSUME; return ActionResult.CONSUME;

View file

@ -21,7 +21,6 @@ import org.jetbrains.annotations.Nullable;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkSide; import net.minecraft.network.NetworkSide;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.listener.PacketListener;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
@ -31,7 +30,7 @@ public class FakePlayerNetworkHandler extends ServerPlayNetworkHandler {
private static final ClientConnection FAKE_CONNECTION = new FakeClientConnection(); private static final ClientConnection FAKE_CONNECTION = new FakeClientConnection();
public FakePlayerNetworkHandler(ServerPlayerEntity player) { public FakePlayerNetworkHandler(ServerPlayerEntity player) {
super(player.getServer(), FAKE_CONNECTION, player, ConnectedClientData.createDefault(player.getGameProfile())); super(player.getServer(), FAKE_CONNECTION, player, ConnectedClientData.createDefault(player.getGameProfile(), false));
} }
@Override @Override
@ -41,9 +40,5 @@ public class FakePlayerNetworkHandler extends ServerPlayNetworkHandler {
private FakeClientConnection() { private FakeClientConnection() {
super(NetworkSide.CLIENTBOUND); super(NetworkSide.CLIENTBOUND);
} }
@Override
public void setPacketListener(PacketListener packetListener) {
}
} }
} }

View file

@ -32,13 +32,11 @@ import net.minecraft.resource.ResourceFinder;
import net.minecraft.resource.ResourcePackManager; import net.minecraft.resource.ResourcePackManager;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.TestCommand; import net.minecraft.server.command.TestCommand;
import net.minecraft.test.GameTestBatch;
import net.minecraft.test.TestContext; import net.minecraft.test.TestContext;
import net.minecraft.test.TestFailureLogger; import net.minecraft.test.TestFailureLogger;
import net.minecraft.test.TestFunction; import net.minecraft.test.TestFunction;
import net.minecraft.test.TestFunctions; import net.minecraft.test.TestFunctions;
import net.minecraft.test.TestServer; import net.minecraft.test.TestServer;
import net.minecraft.test.TestUtil;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.level.storage.LevelStorage; import net.minecraft.world.level.storage.LevelStorage;
@ -80,7 +78,7 @@ public final class FabricGameTestHelper {
LOGGER.info("Starting test server"); LOGGER.info("Starting test server");
MinecraftServer server = TestServer.startServer(thread -> { MinecraftServer server = TestServer.startServer(thread -> {
return TestServer.create(thread, session, resourcePackManager, getBatches(), BlockPos.ORIGIN); return TestServer.create(thread, session, resourcePackManager, getTestFunctions(), BlockPos.ORIGIN);
}); });
} }
@ -128,10 +126,6 @@ public final class FabricGameTestHelper {
} }
} }
private static Collection<GameTestBatch> getBatches() {
return TestUtil.createBatches(getTestFunctions());
}
private static Collection<TestFunction> getTestFunctions() { private static Collection<TestFunction> getTestFunctions() {
return TestFunctions.getTestFunctions(); return TestFunctions.getTestFunctions();
} }

View file

@ -16,26 +16,27 @@
package net.fabricmc.fabric.api.item.v1; package net.fabricmc.fabric.api.item.v1;
import java.util.function.Consumer; import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
/** /**
* Allows an item to run custom logic when {@link ItemStack#damage(int, LivingEntity, Consumer)} is called. * Allows an item to run custom logic when {@link ItemStack#damage(int, LivingEntity, EquipmentSlot)} is called.
* This is useful for items that, for example, may drain durability from some other source before damaging * This is useful for items that, for example, may drain durability from some other source before damaging
* the stack itself. * the stack itself.
* *
* <p>Custom damage handlers can be set with {@link FabricItemSettings#customDamage}. * <p>Custom damage handlers can be set with {@link FabricItem.Settings#customDamage}.
*/ */
@FunctionalInterface @FunctionalInterface
public interface CustomDamageHandler { public interface CustomDamageHandler {
/** /**
* Called to apply damage to the given stack. * Called to apply damage to the given stack.
* This can be used to e.g. drain from a battery before actually damaging the item. * This can be used to e.g. drain from a battery before actually damaging the item.
* @param amount The amount of damage originally requested * Note that this does not get called if non-entities, such as dispensers, are damaging the item.
* @param breakCallback Callback when the stack reaches zero damage. See {@link ItemStack#damage(int, LivingEntity, Consumer)} and its callsites for more information. * Calling {@code breakCallback} breaks the item, bypassing the vanilla logic. The return value is
* ignored in this case.
* @param amount the amount of damage originally requested
* @return The amount of damage to pass to vanilla's logic * @return The amount of damage to pass to vanilla's logic
*/ */
int damage(ItemStack stack, int amount, LivingEntity entity, Consumer<LivingEntity> breakCallback); int damage(ItemStack stack, int amount, LivingEntity entity, EquipmentSlot slot, Runnable breakCallback);
} }

View file

@ -27,7 +27,7 @@ import net.minecraft.item.ItemStack;
* <p>The preferred requipment slot of an item stack can be queried using * <p>The preferred requipment slot of an item stack can be queried using
* {@link net.minecraft.entity.LivingEntity#getPreferredEquipmentSlot(ItemStack) LivingEntity.getPreferredEquipmentSlot()}. * {@link net.minecraft.entity.LivingEntity#getPreferredEquipmentSlot(ItemStack) LivingEntity.getPreferredEquipmentSlot()}.
* *
* <p>Equipment slot providers can be set with {@link FabricItemSettings#equipmentSlot(EquipmentSlotProvider)}. * <p>Equipment slot providers can be set with {@link FabricItem.Settings#equipmentSlot(EquipmentSlotProvider)}.
* *
* <p>Note that items extending {@link net.minecraft.item.ArmorItem} don't need to use this * <p>Note that items extending {@link net.minecraft.item.ArmorItem} don't need to use this
* as there's {@link net.minecraft.item.ArmorItem#getSlotType()}. * as there's {@link net.minecraft.item.ArmorItem#getSlotType()}.

View file

@ -30,6 +30,8 @@ import net.minecraft.item.ItemStack;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.fabricmc.fabric.impl.item.FabricItemInternals;
/** /**
* General-purpose Fabric-provided extensions for {@link Item} subclasses. * General-purpose Fabric-provided extensions for {@link Item} subclasses.
* *
@ -136,4 +138,32 @@ public interface FabricItem {
default @Nullable FoodComponent getFoodComponent(ItemStack stack) { default @Nullable FoodComponent getFoodComponent(ItemStack stack) {
return ((Item) this).getFoodComponent(); return ((Item) this).getFoodComponent();
} }
/**
* Fabric-provided extensions for {@link Item.Settings}.
* This interface is automatically implemented on all item settings via Mixin and interface injection.
*/
interface Settings {
/**
* Sets the equipment slot provider of the item.
*
* @param equipmentSlotProvider the equipment slot provider
* @return this builder
*/
default Item.Settings equipmentSlot(EquipmentSlotProvider equipmentSlotProvider) {
FabricItemInternals.computeExtraData((Item.Settings) this).equipmentSlot(equipmentSlotProvider);
return (Item.Settings) this;
}
/**
* Sets the custom damage handler of the item.
* Note that this is only called on an ItemStack if {@link ItemStack#isDamageable()} returns true.
*
* @see CustomDamageHandler
*/
default Item.Settings customDamage(CustomDamageHandler handler) {
FabricItemInternals.computeExtraData((Item.Settings) this).customDamage(handler);
return (Item.Settings) this;
}
}
} }

View file

@ -25,19 +25,18 @@ import net.minecraft.util.Rarity;
import net.fabricmc.fabric.impl.item.FabricItemInternals; import net.fabricmc.fabric.impl.item.FabricItemInternals;
/** /**
* Fabric's version of Item.Settings. Adds additional methods and hooks * @deprecated replace with {@link Item.Settings}
* not found in the original class.
*
* <p>To use it, simply replace {@code new Item.Settings()} with
* {@code new FabricItemSettings()}.
*/ */
@Deprecated
public class FabricItemSettings extends Item.Settings { public class FabricItemSettings extends Item.Settings {
/** /**
* Sets the equipment slot provider of the item. * Sets the equipment slot provider of the item.
* *
* @param equipmentSlotProvider the equipment slot provider * @param equipmentSlotProvider the equipment slot provider
* @return this builder * @return this builder
* @deprecated replace with {@link FabricItem.Settings#equipmentSlot(EquipmentSlotProvider)}
*/ */
@Deprecated
public FabricItemSettings equipmentSlot(EquipmentSlotProvider equipmentSlotProvider) { public FabricItemSettings equipmentSlot(EquipmentSlotProvider equipmentSlotProvider) {
FabricItemInternals.computeExtraData(this).equipmentSlot(equipmentSlotProvider); FabricItemInternals.computeExtraData(this).equipmentSlot(equipmentSlotProvider);
return this; return this;
@ -47,8 +46,10 @@ public class FabricItemSettings extends Item.Settings {
* Sets the custom damage handler of the item. * Sets the custom damage handler of the item.
* Note that this is only called on an ItemStack if {@link ItemStack#isDamageable()} returns true. * Note that this is only called on an ItemStack if {@link ItemStack#isDamageable()} returns true.
* *
* @deprecated replace with {@link FabricItem.Settings#customDamage(CustomDamageHandler)}
* @see CustomDamageHandler * @see CustomDamageHandler
*/ */
@Deprecated
public FabricItemSettings customDamage(CustomDamageHandler handler) { public FabricItemSettings customDamage(CustomDamageHandler handler) {
FabricItemInternals.computeExtraData(this).customDamage(handler); FabricItemInternals.computeExtraData(this).customDamage(handler);
return this; return this;

View file

@ -14,7 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
/** package net.fabricmc.fabric.mixin.item;
* API for working with screen handlers on the client.
*/ import org.spongepowered.asm.mixin.Mixin;
package net.fabricmc.fabric.api.client.screenhandler.v1;
import net.minecraft.item.Item;
import net.fabricmc.fabric.api.item.v1.FabricItem;
@Mixin(Item.Settings.class)
public class ItemSettingsMixin implements FabricItem.Settings {
}

View file

@ -16,28 +16,28 @@
package net.fabricmc.fabric.mixin.item; package net.fabricmc.fabric.mixin.item;
import java.util.function.Consumer;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttribute; import net.minecraft.entity.attribute.EntityAttribute;
import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.item.Item; import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.random.Random;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler; import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.FabricItemStack; import net.fabricmc.fabric.api.item.v1.FabricItemStack;
@ -48,33 +48,23 @@ import net.fabricmc.fabric.impl.item.ItemExtensions;
public abstract class ItemStackMixin implements FabricItemStack { public abstract class ItemStackMixin implements FabricItemStack {
@Shadow public abstract Item getItem(); @Shadow public abstract Item getItem();
@Unique @WrapOperation(method = "damage(ILnet/minecraft/entity/LivingEntity;Lnet/minecraft/entity/EquipmentSlot;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;damage(ILnet/minecraft/util/math/random/Random;Lnet/minecraft/server/network/ServerPlayerEntity;Ljava/lang/Runnable;)V"))
private LivingEntity fabric_damagingEntity; private void hookDamage(ItemStack instance, int amount, Random random, ServerPlayerEntity serverPlayerEntity, Runnable runnable, Operation<Void> original, @Local(argsOnly = true) EquipmentSlot slot) {
@Unique
private Consumer<LivingEntity> fabric_breakCallback;
@Inject(method = "damage(ILnet/minecraft/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At("HEAD"))
private void saveDamager(int amount, LivingEntity entity, Consumer<LivingEntity> breakCallback, CallbackInfo ci) {
this.fabric_damagingEntity = entity;
this.fabric_breakCallback = breakCallback;
}
@ModifyArg(method = "damage(ILnet/minecraft/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;damage(ILnet/minecraft/util/math/random/Random;Lnet/minecraft/server/network/ServerPlayerEntity;)Z"), index = 0)
private int hookDamage(int amount) {
CustomDamageHandler handler = ((ItemExtensions) getItem()).fabric_getCustomDamageHandler(); CustomDamageHandler handler = ((ItemExtensions) getItem()).fabric_getCustomDamageHandler();
if (handler != null) { if (handler != null) {
return handler.damage((ItemStack) (Object) this, amount, fabric_damagingEntity, fabric_breakCallback); // Track whether an item has been broken by custom handler
MutableBoolean mut = new MutableBoolean(false);
amount = handler.damage((ItemStack) (Object) this, amount, serverPlayerEntity, slot, () -> {
mut.setTrue();
runnable.run();
});
// If item is broken, there's no reason to call the original.
if (mut.booleanValue()) return;
} }
return amount; original.call(instance, amount, random, serverPlayerEntity, runnable);
}
@Inject(method = "damage(ILnet/minecraft/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At("RETURN"))
private <T extends LivingEntity> void clearDamage(int amount, T entity, Consumer<T> breakCallback, CallbackInfo ci) {
this.fabric_damagingEntity = null;
this.fabric_breakCallback = null;
} }
@Redirect( @Redirect(

View file

@ -12,6 +12,7 @@
"FoxEntityMixin", "FoxEntityMixin",
"HungerManagerMixin", "HungerManagerMixin",
"ItemMixin", "ItemMixin",
"ItemSettingsMixin",
"ItemStackMixin", "ItemStackMixin",
"LivingEntityMixin", "LivingEntityMixin",
"RecipeMixin", "RecipeMixin",

View file

@ -31,6 +31,7 @@
"fabric-api:module-lifecycle": "stable", "fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": { "loom:injected_interfaces": {
"net/minecraft/class_1792": ["net/fabricmc/fabric/api/item/v1/FabricItem"], "net/minecraft/class_1792": ["net/fabricmc/fabric/api/item/v1/FabricItem"],
"net/minecraft/class_1792\u0024class_1793": ["net/fabricmc/fabric/api/item/v1/FabricItem\u0024Settings"],
"net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"] "net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"]
} }
} }

View file

@ -30,7 +30,6 @@ import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler; import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistry; import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistry;
import net.fabricmc.fabric.api.registry.FuelRegistry; import net.fabricmc.fabric.api.registry.FuelRegistry;
@ -44,7 +43,7 @@ public class CustomDamageTest implements ModInitializer {
FabricBrewingRecipeRegistry.registerPotionRecipe(Potions.WATER, Ingredient.ofItems(WEIRD_PICK), Potions.AWKWARD); FabricBrewingRecipeRegistry.registerPotionRecipe(Potions.WATER, Ingredient.ofItems(WEIRD_PICK), Potions.AWKWARD);
} }
public static final CustomDamageHandler WEIRD_DAMAGE_HANDLER = (stack, amount, entity, breakCallback) -> { public static final CustomDamageHandler WEIRD_DAMAGE_HANDLER = (stack, amount, entity, slot, breakCallback) -> {
// If sneaking, apply all damage to vanilla. Otherwise, increment a tag on the stack by one and don't apply any damage // If sneaking, apply all damage to vanilla. Otherwise, increment a tag on the stack by one and don't apply any damage
if (entity.isSneaking()) { if (entity.isSneaking()) {
return amount; return amount;
@ -57,7 +56,7 @@ public class CustomDamageTest implements ModInitializer {
public static class WeirdPick extends PickaxeItem { public static class WeirdPick extends PickaxeItem {
protected WeirdPick() { protected WeirdPick() {
super(ToolMaterials.GOLD, 1, -2.8F, new FabricItemSettings().customDamage(WEIRD_DAMAGE_HANDLER)); super(ToolMaterials.GOLD, 1, -2.8F, new Item.Settings().customDamage(WEIRD_DAMAGE_HANDLER));
} }
@Override @Override

View file

@ -35,7 +35,7 @@ public class FabricItemSettingsTests implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
// Registers an item with a custom equipment slot. // Registers an item with a custom equipment slot.
Item testItem = new Item(new FabricItemSettings().equipmentSlot(stack -> EquipmentSlot.CHEST)); Item testItem = new Item(new Item.Settings().equipmentSlot(stack -> EquipmentSlot.CHEST));
Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "test_item"), testItem); Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "test_item"), testItem);
final List<String> missingMethods = new ArrayList<>(); final List<String> missingMethods = new ArrayList<>();

View file

@ -26,11 +26,10 @@ import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
public final class FoodGameInitializer implements ModInitializer { public final class FoodGameInitializer implements ModInitializer {
public static final Item DAMAGE = Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "damage_food"), new DamageFood(new FabricItemSettings().maxDamage(20))); public static final Item DAMAGE = Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "damage_food"), new DamageFood(new Item.Settings().maxDamage(20)));
public static final Item NAME = Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "name_food"), new NameFood(new FabricItemSettings())); public static final Item NAME = Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "name_food"), new NameFood(new Item.Settings()));
@Override @Override
public void onInitialize() { public void onInitialize() {

View file

@ -19,94 +19,55 @@ package net.fabricmc.fabric.api.client.networking.v1;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ServerCommonPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.thread.ThreadExecutor; import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PacketType; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor;
/** /**
* Offers access to configuration stage client-side networking functionalities. * Offers access to configuration stage client-side networking functionalities.
* *
* <p>Client-side networking functionalities include receiving clientbound packets, * <p>Client-side networking functionalities include receiving clientbound packets,
* sending serverbound packets, and events related to client-side network handlers. * sending serverbound packets, and events related to client-side network handlers.
* Packets <strong>received</strong> by this class must be registered to {@link
* PayloadTypeRegistry#configurationS2C()} on both ends.
* Packets <strong>sent</strong> by this class must be registered to {@link
* PayloadTypeRegistry#configurationC2S()} on both ends.
* Packets must be registered before registering any receivers.
* *
* <p>This class should be only used on the physical client and for the logical client. * <p>This class should be only used on the physical client and for the logical client.
* *
* <p>See {@link net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking} for information on how to use the packet * <p>See {@link ServerPlayNetworking} for information on how to use the packet
* object-based API. * object-based API.
* *
* @see ServerConfigurationNetworking * @see ServerConfigurationNetworking
*/ */
public final class ClientConfigurationNetworking { public final class ClientConfigurationNetworking {
/**
* Registers a handler to a channel.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The handler runs on the network thread. After reading the buffer there, access to game state
* must be performed in the render thread by calling {@link ThreadExecutor#execute(Runnable)}.
*
* <p>If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler.
*
* <p>For new code, {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)}
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param channelName the id of the channel
* @param channelHandler the handler
* @return false if a handler is already registered to the channel
* @see ClientConfigurationNetworking#unregisterGlobalReceiver(Identifier)
* @see ClientConfigurationNetworking#registerReceiver(Identifier, ConfigurationChannelHandler)
*/
public static boolean registerGlobalReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) {
return ClientNetworkingImpl.CONFIGURATION.registerGlobalReceiver(channelName, wrapUntyped(channelHandler));
}
/** /**
* Registers a handler for a packet type. * Registers a handler for a packet type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterGlobalReceiver(PacketType)} to unregister the existing handler. * Use {@link #unregisterGlobalReceiver(CustomPayload.Id)} to unregister the existing handler.
* *
* @param type the packet type * @param type the packet type
* @param handler the handler * @param handler the handler
* @return false if a handler is already registered to the channel * @return false if a handler is already registered to the channel
* @see ClientConfigurationNetworking#unregisterGlobalReceiver(PacketType) * @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#configurationS2C() registered} yet
* @see ClientConfigurationNetworking#registerReceiver(PacketType, ConfigurationPacketHandler) * @see ClientConfigurationNetworking#unregisterGlobalReceiver(CustomPayload.Id)
* @see ClientConfigurationNetworking#registerReceiver(CustomPayload.Id, ConfigurationPayloadHandler)
*/ */
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, ConfigurationPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, ConfigurationPayloadHandler<T> handler) {
return ClientNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.getId(), wrapTyped(type, handler)); return ClientNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.id(), handler);
}
/**
* Removes the handler of a channel.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel
* @see ClientConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)
* @see ClientConfigurationNetworking#unregisterReceiver(Identifier)
*/
@Nullable
public static ClientConfigurationNetworking.ConfigurationChannelHandler unregisterGlobalReceiver(Identifier channelName) {
return unwrapUntyped(ClientNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(channelName));
} }
/** /**
@ -115,15 +76,15 @@ public final class ClientConfigurationNetworking {
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code type} is guaranteed not to have an associated handler after this call.
* *
* @param type the packet type * @param id the packet id
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} * or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, ConfigurationPayloadHandler)}
* @see ClientConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler) * @see ClientConfigurationNetworking#registerGlobalReceiver(CustomPayload.Id, ConfigurationPayloadHandler)
* @see ClientConfigurationNetworking#unregisterReceiver(PacketType) * @see ClientConfigurationNetworking#unregisterReceiver(Identifier)
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> ClientConfigurationNetworking.ConfigurationPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) { public static ClientConfigurationNetworking.ConfigurationPayloadHandler<?> unregisterGlobalReceiver(CustomPayload.Id<?> id) {
return unwrapTyped(ClientNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(type.getId())); return ClientNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(id.id());
} }
/** /**
@ -137,93 +98,47 @@ public final class ClientConfigurationNetworking {
} }
/** /**
* Registers a handler to a channel. * Registers a handler for a packet type.
* *
* <p>If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler. * Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler.
* *
* <p>For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)} * <p>For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)}
* login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler. * login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler.
* *
* <p>For new code, {@link #registerReceiver(PacketType, ConfigurationPacketHandler)} * @param id the payload id
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param channelName the id of the channel
* @return false if a handler is already registered to the channel
* @throws IllegalStateException if the client is not connected to a server
* @see ClientPlayConnectionEvents#INIT
*/
public static boolean registerReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) {
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) {
return addon.registerChannel(channelName, wrapUntyped(channelHandler));
}
throw new IllegalStateException("Cannot register receiver while not configuring!");
}
/**
* Registers a handler for a packet type.
*
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(PacketType)} to unregister the existing handler.
*
* <p>For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)}
* login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler.
*
* @param type the packet type
* @param handler the handler * @param handler the handler
* @return {@code false} if a handler is already registered for the type * @return {@code false} if a handler is already registered for the type
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#configurationS2C() registered} yet
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
* @see ClientPlayConnectionEvents#INIT * @see ClientPlayConnectionEvents#INIT
*/ */
public static <T extends FabricPacket> boolean registerReceiver(PacketType<T> type, ConfigurationPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerReceiver(CustomPayload.Id<T> id, ConfigurationPayloadHandler<T> handler) {
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) { if (addon != null) {
return addon.registerChannel(type.getId(), wrapTyped(type, handler)); return addon.registerChannel(id.id(), handler);
} }
throw new IllegalStateException("Cannot register receiver while not configuring!"); throw new IllegalStateException("Cannot register receiver while not configuring!");
} }
/**
* Removes the handler of a channel.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel
* @throws IllegalStateException if the client is not connected to a server
*/
@Nullable
public static ClientConfigurationNetworking.ConfigurationChannelHandler unregisterReceiver(Identifier channelName) throws IllegalStateException {
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) {
return unwrapUntyped(addon.unregisterChannel(channelName));
}
throw new IllegalStateException("Cannot unregister receiver while not configuring!");
}
/** /**
* Removes the handler for a packet type. * Removes the handler for a packet type.
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code type} is guaranteed not to have an associated handler after this call.
* *
* @param type the packet type * @param id the payload id to unregister
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerReceiver(PacketType, ConfigurationPacketHandler)} * or it was not registered using {@link #registerReceiver(CustomPayload.Id, ConfigurationPayloadHandler)}
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> ClientConfigurationNetworking.ConfigurationPacketHandler<T> unregisterReceiver(PacketType<T> type) { public static ClientConfigurationNetworking.ConfigurationPayloadHandler<?> unregisterReceiver(Identifier id) {
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) { if (addon != null) {
return unwrapTyped(addon.unregisterChannel(type.getId())); return addon.unregisterChannel(id);
} }
throw new IllegalStateException("Cannot unregister receiver while not configuring!"); throw new IllegalStateException("Cannot unregister receiver while not configuring!");
@ -285,22 +200,8 @@ public final class ClientConfigurationNetworking {
* @param type the packet type * @param type the packet type
* @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel * @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel
*/ */
public static boolean canSend(PacketType<?> type) { public static boolean canSend(CustomPayload.Id<?> type) {
return canSend(type.getId()); return canSend(type.id());
}
/**
* Creates a packet which may be sent to the connected server.
*
* @param channelName the channel name
* @param buf the packet byte buf which represents the payload of the packet
* @return a new packet
*/
public static Packet<ServerCommonPacketListener> createC2SPacket(Identifier channelName, PacketByteBuf buf) {
Objects.requireNonNull(channelName, "Channel name cannot be null");
Objects.requireNonNull(buf, "Buf cannot be null");
return ClientNetworkingImpl.createC2SPacket(channelName, buf);
} }
/** /**
@ -322,35 +223,19 @@ public final class ClientConfigurationNetworking {
/** /**
* Sends a packet to the connected server. * Sends a packet to the connected server.
* *
* @param channelName the channel of the packet * <p>Any packets sent must be {@linkplain PayloadTypeRegistry#configurationC2S() registered}.</p>
* @param buf the payload of the packet
* @throws IllegalStateException if the client is not connected to a server
*/
public static void send(Identifier channelName, PacketByteBuf buf) throws IllegalStateException {
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) {
addon.sendPacket(createC2SPacket(channelName, buf));
return;
}
throw new IllegalStateException("Cannot send packet while not configuring!");
}
/**
* Sends a packet to the connected server.
* *
* @param packet the packet * @param payload to be sent
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
*/ */
public static <T extends FabricPacket> void send(T packet) { public static void send(CustomPayload payload) {
Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon(); final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) { if (addon != null) {
addon.sendPacket(packet); addon.sendPacket(payload);
return; return;
} }
@ -360,98 +245,37 @@ public final class ClientConfigurationNetworking {
private ClientConfigurationNetworking() { private ClientConfigurationNetworking() {
} }
private static ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> wrapUntyped(ConfigurationChannelHandler actualHandler) {
return new ResolvablePayload.Handler<>(null, actualHandler, (client, handler, payload, responseSender) -> {
actualHandler.receive(client, handler, ((UntypedPayload) payload).buffer(), responseSender);
});
}
@SuppressWarnings("unchecked")
private static <T extends FabricPacket> ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> wrapTyped(PacketType<T> type, ConfigurationPacketHandler<T> actualHandler) {
return new ResolvablePayload.Handler<>(type, actualHandler, (client, handler, payload, responseSender) -> {
T packet = (T) ((TypedPayload) payload).packet();
if (client.isOnThread()) {
// Do not submit to the render thread if we're already running there.
// Normally, packets are handled on the network IO thread - though it is
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
// Since we're handling it right now, connection check is redundant.
actualHandler.receive(packet, responseSender);
} else {
client.execute(() -> {
if (((ClientCommonNetworkHandlerAccessor) handler).getConnection().isOpen()) {
actualHandler.receive(packet, responseSender);
}
});
}
});
}
@Nullable
private static ConfigurationChannelHandler unwrapUntyped(@Nullable ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof ConfigurationChannelHandler actual) return actual;
return null;
}
@Nullable
@SuppressWarnings({"rawtypes", "unchecked"})
private static <T extends FabricPacket> ConfigurationPacketHandler<T> unwrapTyped(@Nullable ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof ConfigurationPacketHandler actual) return actual;
return null;
}
@FunctionalInterface
public interface ConfigurationChannelHandler {
/**
* Handles an incoming packet.
*
* <p>This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft client instance.
*
* <p>An example usage of this is to display an overlay message:
* <pre>{@code
* ClientConfigurationNetworking.registerReceiver(new Identifier("mymod", "overlay"), (client, handler, buf, responseSender) -> {
* String message = buf.readString(32767);
*
* // All operations on the server or world must be executed on the server thread
* client.execute(() -> {
* client.inGameHud.setOverlayMessage(message, true);
* });
* });
* }</pre>
* @param client the client
* @param handler the network handler that received this packet
* @param buf the payload of the packet
* @param responseSender the packet sender
*/
void receive(MinecraftClient client, ClientConfigurationNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender);
}
/** /**
* A thread-safe packet handler utilizing {@link FabricPacket}. * A packet handler utilizing {@link CustomPayload}.
* @param <T> the type of the packet * @param <T> the type of the packet
*/ */
@FunctionalInterface @FunctionalInterface
public interface ConfigurationPacketHandler<T extends FabricPacket> { public interface ConfigurationPayloadHandler<T extends CustomPayload> {
/** /**
* Handles the incoming packet. This is called on the render thread, and can safely * Handles the incoming packet.
* call client methods. *
* <p>Unlike {@link ClientPlayNetworking.PlayPayloadHandler} this method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled}.
* *
* <p>An example usage of this is to display an overlay message: * <p>An example usage of this is to display an overlay message:
* <pre>{@code * <pre>{@code
* // See FabricPacket for creating the packet * // See FabricPacket for creating the packet
* ClientConfigurationNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, packet, responseSender) -> { * ClientConfigurationNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (packet, responseSender) -> {
* MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true);
* }); * });
* }</pre> * }</pre>
* *
* * @param payload the packet payload
* @param packet the packet * @param context the configuration networking context
* @param responseSender the packet sender * @see CustomPayload
* @see FabricPacket
*/ */
void receive(T packet, PacketSender responseSender); void receive(T payload, Context context);
}
@ApiStatus.NonExtendable
public interface Context {
/**
* @return The packet sender
*/
PacketSender responseSender();
} }
} }

View file

@ -20,14 +20,13 @@ import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientLoginNetworkHandler; import net.minecraft.client.network.ClientLoginNetworkHandler;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.listener.PacketListener; import net.minecraft.network.listener.PacketListener;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -153,10 +152,10 @@ public final class ClientLoginNetworking {
* @param client the client * @param client the client
* @param handler the network handler that received this packet * @param handler the network handler that received this packet
* @param buf the payload of the packet * @param buf the payload of the packet
* @param listenerAdder listeners to be called when the response packet is sent to the server * @param callbacksConsumer listeners to be called when the response packet is sent to the server
* @return a completable future which contains the payload to respond to the server with. * @return a completable future which contains the payload to respond to the server with.
* If the future contains {@code null}, then the server will be notified that the client did not understand the query. * If the future contains {@code null}, then the server will be notified that the client did not understand the query.
*/ */
CompletableFuture<@Nullable PacketByteBuf> receive(MinecraftClient client, ClientLoginNetworkHandler handler, PacketByteBuf buf, Consumer<GenericFutureListener<? extends Future<? super Void>>> listenerAdder); CompletableFuture<@Nullable PacketByteBuf> receive(MinecraftClient client, ClientLoginNetworkHandler handler, PacketByteBuf buf, Consumer<PacketCallbacks> callbacksConsumer);
} }
} }

View file

@ -19,36 +19,34 @@ package net.fabricmc.fabric.api.client.networking.v1;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ServerCommonPacketListener; import net.minecraft.network.listener.ServerCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PacketType; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl;
import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon; import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
/** /**
* Offers access to play stage client-side networking functionalities. * Offers access to play stage client-side networking functionalities.
* *
* <p>Client-side networking functionalities include receiving clientbound packets, * <p>Client-side networking functionalities include receiving clientbound packets,
* sending serverbound packets, and events related to client-side network handlers. * sending serverbound packets, and events related to client-side network handlers.
* Packets <strong>received</strong> by this class must be registered to {@link PayloadTypeRegistry#playS2C()} on both ends.
* Packets <strong>sent</strong> by this class must be registered to {@link PayloadTypeRegistry#playC2S()} on both ends.
* Packets must be registered before registering any receivers.
* *
* <p>This class should be only used on the physical client and for the logical client. * <p>This class should be only used on the physical client and for the logical client.
* *
* <p>See {@link ServerPlayNetworking} for information on how to use the packet * <p>See {@link ServerPlayNetworking} for information on how to use the payload
* object-based API. * object-based API.
* *
* @see ClientLoginNetworking * @see ClientLoginNetworking
@ -57,76 +55,38 @@ import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
*/ */
public final class ClientPlayNetworking { public final class ClientPlayNetworking {
/** /**
* Registers a handler to a channel. * Registers a handler for a payload type.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The handler runs on the network thread. After reading the buffer there, access to game state
* must be performed in the render thread by calling {@link ThreadExecutor#execute(Runnable)}.
*
* <p>If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler.
*
* <p>For new code, {@link #registerGlobalReceiver(PacketType, PlayPacketHandler)}
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param channelName the id of the channel
* @param channelHandler the handler
* @return false if a handler is already registered to the channel
* @see ClientPlayNetworking#unregisterGlobalReceiver(Identifier)
* @see ClientPlayNetworking#registerReceiver(Identifier, PlayChannelHandler)
*/
public static boolean registerGlobalReceiver(Identifier channelName, PlayChannelHandler channelHandler) {
return ClientNetworkingImpl.PLAY.registerGlobalReceiver(channelName, wrapUntyped(channelHandler));
}
/**
* Registers a handler for a packet type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterGlobalReceiver(PacketType)} to unregister the existing handler. * Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler.
* *
* @param type the packet type * @param type the payload type
* @param handler the handler * @param handler the handler
* @return false if a handler is already registered to the channel * @return false if a handler is already registered to the channel
* @see ClientPlayNetworking#unregisterGlobalReceiver(PacketType) * @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#playS2C() registered} yet
* @see ClientPlayNetworking#registerReceiver(PacketType, PlayPacketHandler) * @see ClientPlayNetworking#unregisterGlobalReceiver(Identifier)
* @see ClientPlayNetworking#registerReceiver(CustomPayload.Id, PlayPayloadHandler)
*/ */
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, PlayPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, PlayPayloadHandler<T> handler) {
return ClientNetworkingImpl.PLAY.registerGlobalReceiver(type.getId(), wrapTyped(type, handler)); return ClientNetworkingImpl.PLAY.registerGlobalReceiver(type.id(), handler);
} }
/** /**
* Removes the handler of a channel. * Removes the handler for a payload type.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel
* @see ClientPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler)
* @see ClientPlayNetworking#unregisterReceiver(Identifier)
*/
@Nullable
public static PlayChannelHandler unregisterGlobalReceiver(Identifier channelName) {
return unwrapUntyped(ClientNetworkingImpl.PLAY.unregisterGlobalReceiver(channelName));
}
/**
* Removes the handler for a packet type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code type} is guaranteed not to have an associated handler after this call.
* *
* @param type the packet type * @param id the payload id
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerGlobalReceiver(PacketType, PlayPacketHandler)} * or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)}
* @see ClientPlayNetworking#registerGlobalReceiver(PacketType, PlayPacketHandler) * @see ClientPlayNetworking#registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)
* @see ClientPlayNetworking#unregisterReceiver(PacketType) * @see ClientPlayNetworking#unregisterReceiver(Identifier)
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) { public static ClientPlayNetworking.PlayPayloadHandler<?> unregisterGlobalReceiver(Identifier id) {
return unwrapTyped(ClientNetworkingImpl.PLAY.unregisterGlobalReceiver(type.getId())); return ClientNetworkingImpl.PLAY.unregisterGlobalReceiver(id);
} }
/** /**
@ -140,93 +100,47 @@ public final class ClientPlayNetworking {
} }
/** /**
* Registers a handler to a channel. * Registers a handler for a payload type.
* *
* <p>If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler. * Use {@link #unregisterReceiver(Identifier)} to unregister the existing handler.
* *
* <p>For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)} * <p>For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)}
* login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler. * login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler.
* *
* <p>For new code, {@link #registerReceiver(PacketType, PlayPacketHandler)} * @param type the payload type
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param channelName the id of the channel
* @return false if a handler is already registered to the channel
* @throws IllegalStateException if the client is not connected to a server
* @see ClientPlayConnectionEvents#INIT
*/
public static boolean registerReceiver(Identifier channelName, PlayChannelHandler channelHandler) {
final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon();
if (addon != null) {
return addon.registerChannel(channelName, wrapUntyped(channelHandler));
}
throw new IllegalStateException("Cannot register receiver while not in game!");
}
/**
* Registers a handler for a packet type.
*
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(PacketType)} to unregister the existing handler.
*
* <p>For example, if you only register a receiver using this method when a {@linkplain ClientLoginNetworking#registerGlobalReceiver(Identifier, ClientLoginNetworking.LoginQueryRequestHandler)}
* login query has been received, you should use {@link ClientPlayConnectionEvents#INIT} to register the channel handler.
*
* @param type the packet type
* @param handler the handler * @param handler the handler
* @return {@code false} if a handler is already registered for the type * @return {@code false} if a handler is already registered for the type
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#playS2C() registered} yet
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
* @see ClientPlayConnectionEvents#INIT * @see ClientPlayConnectionEvents#INIT
*/ */
public static <T extends FabricPacket> boolean registerReceiver(PacketType<T> type, PlayPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerReceiver(CustomPayload.Id<T> type, PlayPayloadHandler<T> handler) {
final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon(); final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon();
if (addon != null) { if (addon != null) {
return addon.registerChannel(type.getId(), wrapTyped(type, handler)); return addon.registerChannel(type.id(), handler);
} }
throw new IllegalStateException("Cannot register receiver while not in game!"); throw new IllegalStateException("Cannot register receiver while not in game!");
} }
/** /**
* Removes the handler of a channel. * Removes the handler for a payload id.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel
* @throws IllegalStateException if the client is not connected to a server
*/
@Nullable
public static PlayChannelHandler unregisterReceiver(Identifier channelName) throws IllegalStateException {
final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon();
if (addon != null) {
return unwrapUntyped(addon.unregisterChannel(channelName));
}
throw new IllegalStateException("Cannot unregister receiver while not in game!");
}
/**
* Removes the handler for a packet type.
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code type} is guaranteed not to have an associated handler after this call.
* *
* @param type the packet type * @param id the payload id
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerReceiver(PacketType, PlayPacketHandler)} * or it was not registered using {@link #registerReceiver(CustomPayload.Id, PlayPayloadHandler)}
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterReceiver(PacketType<T> type) { public static ClientPlayNetworking.PlayPayloadHandler<?> unregisterReceiver(Identifier id) {
final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon(); final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon();
if (addon != null) { if (addon != null) {
return unwrapTyped(addon.unregisterChannel(type.getId())); return addon.unregisterChannel(id);
} }
throw new IllegalStateException("Cannot unregister receiver while not in game!"); throw new IllegalStateException("Cannot unregister receiver while not in game!");
@ -265,10 +179,10 @@ public final class ClientPlayNetworking {
} }
/** /**
* Checks if the connected server declared the ability to receive a packet on a specified channel name. * Checks if the connected server declared the ability to receive a payload on a specified channel name.
* *
* @param channelName the channel name * @param channelName the channel name
* @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel. * @return {@code true} if the connected server has declared the ability to receive a payload on the specified channel.
* False if the client is not in game. * False if the client is not in game.
*/ */
public static boolean canSend(Identifier channelName) throws IllegalArgumentException { public static boolean canSend(Identifier channelName) throws IllegalArgumentException {
@ -281,44 +195,30 @@ public final class ClientPlayNetworking {
} }
/** /**
* Checks if the connected server declared the ability to receive a packet on a specified channel name. * Checks if the connected server declared the ability to receive a payload on a specified channel name.
* This returns {@code false} if the client is not in game. * This returns {@code false} if the client is not in game.
* *
* @param type the packet type * @param type the payload type
* @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel * @return {@code true} if the connected server has declared the ability to receive a payload on the specified channel
*/ */
public static boolean canSend(PacketType<?> type) { public static boolean canSend(CustomPayload.Id<?> type) {
return canSend(type.getId()); return canSend(type.id());
} }
/** /**
* Creates a packet which may be sent to the connected server. * Creates a payload which may be sent to the connected server.
* *
* @param channelName the channel name * @param packet the fabric payload
* @param buf the packet byte buf which represents the payload of the packet * @return a new payload
* @return a new packet
*/ */
public static Packet<ServerCommonPacketListener> createC2SPacket(Identifier channelName, PacketByteBuf buf) { public static <T extends CustomPayload> Packet<ServerCommonPacketListener> createC2SPacket(T packet) {
Objects.requireNonNull(channelName, "Channel name cannot be null");
Objects.requireNonNull(buf, "Buf cannot be null");
return ClientNetworkingImpl.createC2SPacket(channelName, buf);
}
/**
* Creates a packet which may be sent to the connected server.
*
* @param packet the fabric packet
* @return a new packet
*/
public static <T extends FabricPacket> Packet<ServerCommonPacketListener> createC2SPacket(T packet) {
return ClientNetworkingImpl.createC2SPacket(packet); return ClientNetworkingImpl.createC2SPacket(packet);
} }
/** /**
* Gets the packet sender which sends packets to the connected server. * Gets the payload sender which sends packets to the connected server.
* *
* @return the client's packet sender * @return the client's payload sender
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
*/ */
public static PacketSender getSender() throws IllegalStateException { public static PacketSender getSender() throws IllegalStateException {
@ -327,39 +227,24 @@ public final class ClientPlayNetworking {
return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler()); return ClientNetworkingImpl.getAddon(MinecraftClient.getInstance().getNetworkHandler());
} }
throw new IllegalStateException("Cannot get packet sender when not in game!"); throw new IllegalStateException("Cannot get payload sender when not in game!");
} }
/** /**
* Sends a packet to the connected server. * Sends a payload to the connected server.
* *
* @param channelName the channel of the packet * <p>Any packets sent must be {@linkplain PayloadTypeRegistry#playC2S() registered}.</p>
* @param buf the payload of the packet *
* @param payload the payload
* @throws IllegalStateException if the client is not connected to a server * @throws IllegalStateException if the client is not connected to a server
*/ */
public static void send(Identifier channelName, PacketByteBuf buf) throws IllegalStateException { public static void send(CustomPayload payload) {
// You cant send without a client player, so this is fine Objects.requireNonNull(payload, "Payload cannot be null");
if (MinecraftClient.getInstance().getNetworkHandler() != null) { Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(channelName, buf));
return;
}
throw new IllegalStateException("Cannot send packets when not in game!");
}
/**
* Sends a packet to the connected server.
*
* @param packet the packet
* @throws IllegalStateException if the client is not connected to a server
*/
public static <T extends FabricPacket> void send(T packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
// You cant send without a client player, so this is fine // You cant send without a client player, so this is fine
if (MinecraftClient.getInstance().getNetworkHandler() != null) { if (MinecraftClient.getInstance().getNetworkHandler() != null) {
MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(packet)); MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(payload));
return; return;
} }
@ -369,98 +254,48 @@ public final class ClientPlayNetworking {
private ClientPlayNetworking() { private ClientPlayNetworking() {
} }
private static ResolvablePayload.Handler<ClientPlayNetworkAddon.Handler> wrapUntyped(PlayChannelHandler actualHandler) {
return new ResolvablePayload.Handler<>(null, actualHandler, (client, handler, payload, responseSender) -> {
actualHandler.receive(client, handler, ((UntypedPayload) payload).buffer(), responseSender);
});
}
@SuppressWarnings("unchecked")
private static <T extends FabricPacket> ResolvablePayload.Handler<ClientPlayNetworkAddon.Handler> wrapTyped(PacketType<T> type, PlayPacketHandler<T> actualHandler) {
return new ResolvablePayload.Handler<>(type, actualHandler, (client, handler, payload, responseSender) -> {
T packet = (T) ((TypedPayload) payload).packet();
if (client.isOnThread()) {
// Do not submit to the render thread if we're already running there.
// Normally, packets are handled on the network IO thread - though it is
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
// Since we're handling it right now, connection check is redundant.
actualHandler.receive(packet, client.player, responseSender);
} else {
client.execute(() -> {
if (handler.getConnection().isOpen()) actualHandler.receive(packet, client.player, responseSender);
});
}
});
}
@Nullable
private static PlayChannelHandler unwrapUntyped(@Nullable ResolvablePayload.Handler<ClientPlayNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof PlayChannelHandler actual) return actual;
return null;
}
@Nullable
@SuppressWarnings({"rawtypes", "unchecked"})
private static <T extends FabricPacket> PlayPacketHandler<T> unwrapTyped(@Nullable ResolvablePayload.Handler<ClientPlayNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof PlayPacketHandler actual) return actual;
return null;
}
@FunctionalInterface
public interface PlayChannelHandler {
/**
* Handles an incoming packet.
*
* <p>This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain net.minecraft.util.thread.ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft client instance.
*
* <p>An example usage of this is to display an overlay message:
* <pre>{@code
* ClientPlayNetworking.registerReceiver(new Identifier("mymod", "overlay"), (client, handler, buf, responseSender) -> {
* String message = buf.readString(32767);
*
* // All operations on the server or world must be executed on the server thread
* client.execute(() -> {
* client.inGameHud.setOverlayMessage(message, true);
* });
* });
* }</pre>
* @param client the client
* @param handler the network handler that received this packet
* @param buf the payload of the packet
* @param responseSender the packet sender
*/
void receive(MinecraftClient client, ClientPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender);
}
/** /**
* A thread-safe packet handler utilizing {@link FabricPacket}. * A thread-safe payload handler utilizing {@link CustomPayload}.
* @param <T> the type of the packet * @param <T> the type of the payload
*/ */
@FunctionalInterface @FunctionalInterface
public interface PlayPacketHandler<T extends FabricPacket> { public interface PlayPayloadHandler<T extends CustomPayload> {
/** /**
* Handles the incoming packet. This is called on the render thread, and can safely * Handles the incoming payload. This is called on the render thread, and can safely
* call client methods. * call client methods.
* *
* <p>An example usage of this is to display an overlay message: * <p>An example usage of this is to display an overlay message:
* <pre>{@code * <pre>{@code
* // See FabricPacket for creating the packet * // See FabricPacket for creating the payload
* ClientPlayNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, packet, responseSender) -> { * ClientPlayNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, payload, responseSender) -> {
* MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true); * MinecraftClient.getInstance().inGameHud.setOverlayMessage(payload.message(), true);
* }); * });
* }</pre> * }</pre>
* *
* <p>The network handler can be accessed via {@link ClientPlayerEntity#networkHandler}. * <p>The network handler can be accessed via {@link ClientPlayerEntity#networkHandler}.
* *
* @param packet the packet * @param payload the packet payload
* @param player the player that received the packet * @param context the play networking context
* @param responseSender the packet sender * @see CustomPayload
* @see FabricPacket
*/ */
void receive(T packet, ClientPlayerEntity player, PacketSender responseSender); void receive(T payload, Context context);
}
@ApiStatus.NonExtendable
public interface Context {
/**
* @return The MinecraftClient instance
*/
MinecraftClient client();
/**
* @return The player that received the payload
*/
ClientPlayerEntity player();
/**
* @return The packet sender
*/
PacketSender responseSender();
} }
} }

View file

@ -21,36 +21,37 @@ import java.util.List;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientConfigurationNetworkHandler; import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.C2SConfigurationChannelEvents; import net.fabricmc.fabric.api.client.networking.v1.C2SConfigurationChannelEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload; import net.fabricmc.fabric.impl.networking.RegistrationPayload;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientConfigurationNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.networking.client.accessor.ClientConfigurationNetworkHandlerAccessor;
public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ClientConfigurationNetworkAddon.Handler> { public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ClientConfigurationNetworking.ConfigurationPayloadHandler<?>> {
private final ClientConfigurationNetworkHandler handler; private final ClientConfigurationNetworkHandler handler;
private final MinecraftClient client; private final MinecraftClient client;
private final ContextImpl context;
private boolean sentInitialRegisterPacket; private boolean sentInitialRegisterPacket;
public ClientConfigurationNetworkAddon(ClientConfigurationNetworkHandler handler, MinecraftClient client) { public ClientConfigurationNetworkAddon(ClientConfigurationNetworkHandler handler, MinecraftClient client) {
super(ClientNetworkingImpl.CONFIGURATION, ((ClientCommonNetworkHandlerAccessor) handler).getConnection(), "ClientPlayNetworkAddon for " + ((ClientConfigurationNetworkHandlerAccessor) handler).getProfile().getName()); super(ClientNetworkingImpl.CONFIGURATION, ((ClientCommonNetworkHandlerAccessor) handler).getConnection(), "ClientPlayNetworkAddon for " + ((ClientConfigurationNetworkHandlerAccessor) handler).getProfile().getName());
this.handler = handler; this.handler = handler;
this.client = client; this.client = client;
this.context = new ContextImpl(this);
// Must register pending channels via lateinit // Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.CONFIGURATION); this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION);
} }
@Override @Override
@ -63,8 +64,8 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
} }
@Override @Override
protected void receiveRegistration(boolean register, ResolvablePayload resolvable) { protected void receiveRegistration(boolean register, RegistrationPayload payload) {
super.receiveRegistration(register, resolvable); super.receiveRegistration(register, payload);
if (register && !this.sentInitialRegisterPacket) { if (register && !this.sentInitialRegisterPacket) {
this.sendInitialChannelRegistrationPacket(); this.sendInitialChannelRegistrationPacket();
@ -73,8 +74,8 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
} }
@Override @Override
protected void receive(Handler handler, ResolvedPayload payload) { protected void receive(ClientConfigurationNetworking.ConfigurationPayloadHandler<?> handler, CustomPayload payload) {
handler.receive(this.client, this.handler, payload, this); ((ClientConfigurationNetworking.ConfigurationPayloadHandler) handler).receive(payload, this.context);
} }
// impl details // impl details
@ -85,12 +86,7 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
} }
@Override @Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) { public Packet<?> createPacket(CustomPayload packet) {
return ClientPlayNetworking.createC2SPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ClientPlayNetworking.createC2SPacket(packet); return ClientPlayNetworking.createC2SPacket(packet);
} }
@ -108,10 +104,10 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleRegistration(Identifier channelName) { protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel // If we can already send packets, immediately send the register packet for this channel
if (this.sentInitialRegisterPacket) { if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.REGISTER, Collections.singleton(channelName));
if (buf != null) { if (payload != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); this.sendPacket(payload);
} }
} }
} }
@ -120,10 +116,10 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleUnregistration(Identifier channelName) { protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel // If we can already send packets, immediately send the unregister packet for this channel
if (this.sentInitialRegisterPacket) { if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.UNREGISTER, Collections.singleton(channelName));
if (buf != null) { if (payload != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); this.sendPacket(payload);
} }
} }
} }
@ -147,7 +143,6 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
return (ChannelInfoHolder) ((ClientCommonNetworkHandlerAccessor) handler).getConnection(); return (ChannelInfoHolder) ((ClientCommonNetworkHandlerAccessor) handler).getConnection();
} }
public interface Handler { private record ContextImpl(PacketSender responseSender) implements ClientConfigurationNetworking.Context {
void receive(MinecraftClient client, ClientConfigurationNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
} }
} }

View file

@ -20,23 +20,20 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientLoginNetworkHandler; import net.minecraft.client.network.ClientLoginNetworkHandler;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket; import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking;
import net.fabricmc.fabric.api.networking.v1.FutureListeners;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon; import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload; import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.networking.client.accessor.ClientLoginNetworkHandlerAccessor;
@ -77,19 +74,18 @@ public final class ClientLoginNetworkAddon extends AbstractNetworkAddon<ClientLo
} }
PacketByteBuf buf = PacketByteBufs.slice(originalBuf); PacketByteBuf buf = PacketByteBufs.slice(originalBuf);
List<GenericFutureListener<? extends Future<? super Void>>> futureListeners = new ArrayList<>(); List<PacketCallbacks> callbacks = new ArrayList<>();
try { try {
CompletableFuture<@Nullable PacketByteBuf> future = handler.receive(this.client, this.handler, buf, futureListeners::add); CompletableFuture<@Nullable PacketByteBuf> future = handler.receive(this.client, this.handler, buf, callbacks::add);
future.thenAccept(result -> { future.thenAccept(result -> {
LoginQueryResponseC2SPacket packet = new LoginQueryResponseC2SPacket(queryId, result == null ? null : new PacketByteBufLoginQueryResponse(result)); LoginQueryResponseC2SPacket packet = new LoginQueryResponseC2SPacket(queryId, result == null ? null : new PacketByteBufLoginQueryResponse(result));
GenericFutureListener<? extends Future<? super Void>> listener = null; ((ClientLoginNetworkHandlerAccessor) this.handler).getConnection().send(packet, new PacketCallbacks() {
@Override
for (GenericFutureListener<? extends Future<? super Void>> each : futureListeners) { public void onSuccess() {
listener = FutureListeners.union(listener, each); callbacks.forEach(PacketCallbacks::onSuccess);
} }
});
((ClientLoginNetworkHandlerAccessor) this.handler).getConnection().send(packet, GenericFutureListenerHolder.create(listener));
}); });
} catch (Throwable ex) { } catch (Throwable ex) {
this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex); this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex);

View file

@ -26,19 +26,18 @@ import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.client.network.ClientLoginNetworkHandler; import net.minecraft.client.network.ClientLoginNetworkHandler;
import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.NetworkSide;
import net.minecraft.network.listener.ServerCommonPacketListener; import net.minecraft.network.listener.ServerCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.CommonPacketsImpl; import net.fabricmc.fabric.impl.networking.CommonPacketsImpl;
import net.fabricmc.fabric.impl.networking.CommonRegisterPayload; import net.fabricmc.fabric.impl.networking.CommonRegisterPayload;
@ -46,17 +45,14 @@ import net.fabricmc.fabric.impl.networking.CommonVersionPayload;
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload; import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
import net.fabricmc.fabric.mixin.networking.client.accessor.ConnectScreenAccessor; import net.fabricmc.fabric.mixin.networking.client.accessor.ConnectScreenAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.MinecraftClientAccessor; import net.fabricmc.fabric.mixin.networking.client.accessor.MinecraftClientAccessor;
public final class ClientNetworkingImpl { public final class ClientNetworkingImpl {
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN); public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkSide.CLIENTBOUND, NetworkPhase.LOGIN, null);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION); public static final GlobalReceiverRegistry<ClientConfigurationNetworking.ConfigurationPayloadHandler<?>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkSide.CLIENTBOUND, NetworkPhase.CONFIGURATION, PayloadTypeRegistryImpl.CONFIGURATION_S2C);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ClientPlayNetworkAddon.Handler>> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY); public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayPayloadHandler<?>> PLAY = new GlobalReceiverRegistry<>(NetworkSide.CLIENTBOUND, NetworkPhase.PLAY, PayloadTypeRegistryImpl.PLAY_S2C);
private static ClientPlayNetworkAddon currentPlayAddon; private static ClientPlayNetworkAddon currentPlayAddon;
private static ClientConfigurationNetworkAddon currentConfigurationAddon; private static ClientConfigurationNetworkAddon currentConfigurationAddon;
@ -73,22 +69,15 @@ public final class ClientNetworkingImpl {
return (ClientLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); return (ClientLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
} }
public static Packet<ServerCommonPacketListener> createC2SPacket(Identifier channelName, PacketByteBuf buf) { public static Packet<ServerCommonPacketListener> createC2SPacket(CustomPayload payload) {
return new CustomPayloadC2SPacket(new UntypedPayload(channelName, buf)); Objects.requireNonNull(payload, "Payload cannot be null");
} Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
public static Packet<ServerCommonPacketListener> createC2SPacket(FabricPacket packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
ResolvedPayload payload = new TypedPayload(packet);
if (NetworkingImpl.FORCE_PACKET_SERIALIZATION) payload = payload.resolve(null);
return new CustomPayloadC2SPacket(payload); return new CustomPayloadC2SPacket(payload);
} }
/** /**
* Due to the way logging into a integrated or remote dedicated server will differ, we need to obtain the login client connection differently. * Due to the way logging into an integrated or remote dedicated server will differ, we need to obtain the login client connection differently.
*/ */
@Nullable @Nullable
public static ClientConnection getLoginConnection() { public static ClientConnection getLoginConnection() {
@ -153,28 +142,26 @@ public final class ClientNetworkingImpl {
}); });
// Version packet // Version packet
ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (client, handler, buf, responseSender) -> { ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.ID, (payload, context) -> {
var payload = new CommonVersionPayload(buf); int negotiatedVersion = handleVersionPacket(payload, context.responseSender());
int negotiatedVersion = handleVersionPacket(payload, responseSender); ClientNetworkingImpl.getClientConfigurationAddon().onCommonVersionPacket(negotiatedVersion);
ClientNetworkingImpl.getAddon(handler).onCommonVersionPacket(negotiatedVersion);
}); });
// Register packet // Register packet
ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (client, handler, buf, responseSender) -> { ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.ID, (payload, context) -> {
var payload = new CommonRegisterPayload(buf); ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getAddon(handler);
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) { if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
if (payload.version() != addon.getNegotiatedVersion()) { if (payload.version() != addon.getNegotiatedVersion()) {
throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version())); throw new IllegalStateException("Negotiated common packet version: %d but received packet with version: %d".formatted(addon.getNegotiatedVersion(), payload.version()));
} }
addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels()); addon.getChannelInfoHolder().fabric_getPendingChannelsNames(NetworkPhase.PLAY).addAll(payload.channels());
NetworkingImpl.LOGGER.debug("Received accepted channels from the server"); NetworkingImpl.LOGGER.debug("Received accepted channels from the server");
responseSender.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers())); context.responseSender().sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers()));
} else { } else {
addon.onCommonRegisterPacket(payload); addon.onCommonRegisterPacket(payload);
responseSender.sendPacket(addon.createRegisterPayload()); context.responseSender().sendPacket(addon.createRegisterPayload());
} }
}); });
} }

View file

@ -24,24 +24,25 @@ import org.slf4j.Logger;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.NetworkState; import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents; import net.fabricmc.fabric.api.client.networking.v1.C2SPlayChannelEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload; import net.fabricmc.fabric.impl.networking.RegistrationPayload;
public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<ClientPlayNetworkAddon.Handler> { public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<ClientPlayNetworking.PlayPayloadHandler<?>> {
private final ClientPlayNetworkHandler handler; private final ClientPlayNetworkHandler handler;
private final MinecraftClient client; private final MinecraftClient client;
private final ClientPlayNetworking.Context context;
private boolean sentInitialRegisterPacket; private boolean sentInitialRegisterPacket;
private static final Logger LOGGER = LogUtils.getLogger(); private static final Logger LOGGER = LogUtils.getLogger();
@ -50,9 +51,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
super(ClientNetworkingImpl.PLAY, handler.getConnection(), "ClientPlayNetworkAddon for " + handler.getProfile().getName()); super(ClientNetworkingImpl.PLAY, handler.getConnection(), "ClientPlayNetworkAddon for " + handler.getProfile().getName());
this.handler = handler; this.handler = handler;
this.client = client; this.client = client;
this.context = new ContextImpl(client, client.player, this);
// Must register pending channels via lateinit // Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.PLAY); this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.PLAY);
} }
@Override @Override
@ -73,8 +75,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
} }
@Override @Override
protected void receive(Handler handler, ResolvedPayload payload) { protected void receive(ClientPlayNetworking.PlayPayloadHandler<?> handler, CustomPayload payload) {
handler.receive(this.client, this.handler, payload, this); this.client.execute(() -> {
((ClientPlayNetworking.PlayPayloadHandler) handler).receive(payload, context);
});
} }
// impl details // impl details
@ -85,12 +89,7 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
} }
@Override @Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) { public Packet<?> createPacket(CustomPayload packet) {
return ClientPlayNetworking.createC2SPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ClientPlayNetworking.createC2SPacket(packet); return ClientPlayNetworking.createC2SPacket(packet);
} }
@ -108,10 +107,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
protected void handleRegistration(Identifier channelName) { protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel // If we can already send packets, immediately send the register packet for this channel
if (this.sentInitialRegisterPacket) { if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.REGISTER, Collections.singleton(channelName));
if (buf != null) { if (payload != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); this.sendPacket(payload);
} }
} }
} }
@ -120,10 +119,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
protected void handleUnregistration(Identifier channelName) { protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel // If we can already send packets, immediately send the unregister packet for this channel
if (this.sentInitialRegisterPacket) { if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.UNREGISTER, Collections.singleton(channelName));
if (buf != null) { if (payload != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); this.sendPacket(payload);
} }
} }
} }
@ -138,7 +137,6 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
return NetworkingImpl.isReservedCommonChannel(channelName); return NetworkingImpl.isReservedCommonChannel(channelName);
} }
public interface Handler { private record ContextImpl(MinecraftClient client, ClientPlayerEntity player, PacketSender responseSender) implements ClientPlayNetworking.Context {
void receive(MinecraftClient client, ClientPlayNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
} }
} }

View file

@ -25,13 +25,12 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.network.ClientCommonNetworkHandler; import net.minecraft.client.network.ClientCommonNetworkHandler;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon; import net.fabricmc.fabric.impl.networking.client.ClientPlayNetworkAddon;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.RetainedPayload;
@Mixin(ClientCommonNetworkHandler.class) @Mixin(ClientCommonNetworkHandler.class)
public abstract class ClientCommonNetworkHandlerMixin implements NetworkHandlerExtensions { public abstract class ClientCommonNetworkHandlerMixin implements NetworkHandlerExtensions {
@ -41,25 +40,18 @@ public abstract class ClientCommonNetworkHandlerMixin implements NetworkHandlerE
@Inject(method = "onCustomPayload(Lnet/minecraft/network/packet/s2c/common/CustomPayloadS2CPacket;)V", at = @At("HEAD"), cancellable = true) @Inject(method = "onCustomPayload(Lnet/minecraft/network/packet/s2c/common/CustomPayloadS2CPacket;)V", at = @At("HEAD"), cancellable = true)
public void onCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) { public void onCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) {
if (packet.payload() instanceof ResolvablePayload payload) { final CustomPayload payload = packet.payload();
boolean handled; boolean handled;
if (this.getAddon() instanceof ClientPlayNetworkAddon addon) { if (this.getAddon() instanceof ClientPlayNetworkAddon addon) {
handled = addon.handle(payload); handled = addon.handle(payload);
} else if (this.getAddon() instanceof ClientConfigurationNetworkAddon addon) { } else if (this.getAddon() instanceof ClientConfigurationNetworkAddon addon) {
handled = addon.handle(payload); handled = addon.handle(payload);
} else { } else {
throw new IllegalStateException("Unknown network addon"); throw new IllegalStateException("Unknown network addon");
} }
if (!handled && payload instanceof RetainedPayload retained && retained.buf().refCnt() > 0) {
// Duplicate the vanilla log message, as we cancel further processing.
LOGGER.warn("Unknown custom packet payload: {}", payload.id());
retained.buf().skipBytes(retained.buf().readableBytes());
retained.buf().release();
}
if (handled) {
ci.cancel(); ci.cancel();
} }
} }

View file

@ -51,7 +51,7 @@ public abstract class ClientConfigurationNetworkHandlerMixin extends ClientCommo
this.addon.lateInit(); this.addon.lateInit();
} }
@Inject(method = "onReady", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;setPacketListener(Lnet/minecraft/network/listener/PacketListener;)V", shift = At.Shift.BEFORE)) @Inject(method = "onReady", at = @At(value = "NEW", target = "(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/network/ClientConnection;Lnet/minecraft/client/network/ClientConnectionState;)Lnet/minecraft/client/network/ClientPlayNetworkHandler;"))
public void onReady(ReadyS2CPacket packet, CallbackInfo ci) { public void onReady(ReadyS2CPacket packet, CallbackInfo ci) {
this.addon.handleReady(); this.addon.handleReady();
} }

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
/**
* A packet to be sent using Networking API. An instance of this class is created
* each time the packet is sent. This can be used on both the client and the server.
*
* <p>Implementations should have fields of values sent over the network.
* For example, a packet consisting of two integers should have two {@code int}
* fields with appropriate name. This is written to the buffer in {@link #write}.
* The packet should have two constructors: one that creates a packet on the sender,
* which initializes the fields to be written, and one that takes a {@link PacketByteBuf}
* and reads the packet.
*
* <p>For each packet class, a corresponding {@link PacketType} instance should be created.
* The type should be stored in a {@code static final} field, and {@link #getType} should
* return that type.
*
* <p>Example of a packet:
* <pre>{@code
* public record BoomPacket(boolean fire) implements FabricPacket {
* public static final PacketType<BoomPacket> TYPE = PacketType.create(new Identifier("example:boom"), BoomPacket::new);
*
* public BoomPacket(PacketByteBuf buf) {
* this(buf.readBoolean());
* }
*
* @Override
* public void write(PacketByteBuf buf) {
* buf.writeBoolean(this.fire);
* }
*
* @Override
* public PacketType<?> getType() {
* return TYPE;
* }
* }
* }</pre>
*
* @see ServerPlayNetworking#registerGlobalReceiver(PacketType, ServerPlayNetworking.PlayPacketHandler)
* @see ServerPlayNetworking#send(ServerPlayerEntity, PacketType, FabricPacket)
* @see PacketSender#sendPacket(FabricPacket)
*/
public interface FabricPacket {
/**
* Writes the contents of this packet to the buffer.
* @param buf the output buffer
*/
void write(PacketByteBuf buf);
/**
* Returns the packet type of this packet.
*
* <p>Implementations should store the packet type instance in a {@code static final}
* field and return that here, instead of creating a new instance.
*
* @return the type of this packet
*/
PacketType<?> getType();
}

View file

@ -1,96 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import java.util.Objects;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.network.PacketByteBuf;
/**
* Utilities for working with netty's future listeners.
* @see FutureListener
* @see ChannelFutureListener
*/
public final class FutureListeners {
/**
* Returns a future listener that releases a packet byte buf when the buffer has been sent to a remote connection.
*
* @param buf the buffer
* @return the future listener
*/
public static ChannelFutureListener free(PacketByteBuf buf) {
Objects.requireNonNull(buf, "PacketByteBuf cannot be null");
return (future) -> {
if (!isLocalChannel(future.channel())) {
buf.release();
}
};
}
/**
* Returns whether a netty channel performs local transportation, or if the message objects in the channel are directly passed than written to and read from a byte buf.
*
* @param channel the channel to check
* @return whether the channel is local
*/
public static boolean isLocalChannel(Channel channel) {
return channel instanceof LocalServerChannel || channel instanceof LocalChannel;
}
/**
* Combines two future listeners.
*
* @param first the first future listener
* @param second the second future listener
* @param <A> the future type of the first listener, used for casting
* @param <B> the future type of the second listener, used for casting
* @return the combined future listener.
*/
// A, B exist just to allow casting
@SuppressWarnings("unchecked")
public static <A extends Future<? super Void>, B extends Future<? super Void>> GenericFutureListener<? extends Future<? super Void>> union(GenericFutureListener<A> first, GenericFutureListener<B> second) {
// Return an empty future listener in the case of both parameters somehow being null
if (first == null && second == null) {
return future -> { };
}
if (first == null) {
return second;
}
if (second == null) {
return first;
}
return future -> {
first.operationComplete((A) future);
second.operationComplete((B) future);
};
}
private FutureListeners() {
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import java.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
/**
* Represents something that supports sending packets to login channels.
* @see PacketSender
*/
@ApiStatus.NonExtendable
public interface LoginPacketSender extends PacketSender {
/**
* Creates a packet for sending to a login channel.
*
* @param channelName the id of the channel
* @param buf the content of the packet
* @return the created packet
*/
Packet<?> createPacket(Identifier channelName, PacketByteBuf buf);
/**
* Sends a packet to a channel.
*
* @param channel the id of the channel
* @param buf the content of the packet
*/
default void sendPacket(Identifier channel, PacketByteBuf buf) {
Objects.requireNonNull(channel, "Channel cannot be null");
Objects.requireNonNull(buf, "Payload cannot be null");
this.sendPacket(this.createPacket(channel, buf));
}
/**
* Sends a packet to a channel.
*
* @param channel the id of the channel
* @param buf the content of the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}
*/
default void sendPacket(Identifier channel, PacketByteBuf buf, @Nullable PacketCallbacks callback) {
Objects.requireNonNull(channel, "Channel cannot be null");
Objects.requireNonNull(buf, "Payload cannot be null");
this.sendPacket(this.createPacket(channel, buf), callback);
}
}

View file

@ -16,42 +16,26 @@
package net.fabricmc.fabric.api.networking.v1; package net.fabricmc.fabric.api.networking.v1;
import java.util.Objects;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier; import net.minecraft.text.Text;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
/** /**
* Represents something that supports sending packets to channels. * Represents something that supports sending packets to channels.
* @see PacketByteBufs * Any packets sent must be {@linkplain PayloadTypeRegistry registered} in the appropriate registry.
*/ */
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
public interface PacketSender { public interface PacketSender {
/** /**
* Makes a packet for a channel. * Creates a packet from a packet payload.
* *
* @param channelName the id of the channel * @param payload the packet payload
* @param buf the content of the packet
*/ */
Packet<?> createPacket(Identifier channelName, PacketByteBuf buf); Packet<?> createPacket(CustomPayload payload);
/**
* Makes a packet for a fabric packet.
*
* @param packet the fabric packet
*/
Packet<?> createPacket(FabricPacket packet);
/** /**
* Sends a packet. * Sends a packet.
@ -59,15 +43,7 @@ public interface PacketSender {
* @param packet the packet * @param packet the packet
*/ */
default void sendPacket(Packet<?> packet) { default void sendPacket(Packet<?> packet) {
sendPacket(packet, (PacketCallbacks) null); sendPacket(packet, null);
}
/**
* Sends a packet.
* @param packet the packet
*/
default <T extends FabricPacket> void sendPacket(T packet) {
sendPacket(createPacket(packet));
} }
/** /**
@ -75,107 +51,30 @@ public interface PacketSender {
* @param payload the payload * @param payload the payload
*/ */
default void sendPacket(CustomPayload payload) { default void sendPacket(CustomPayload payload) {
PacketByteBuf buf = PacketByteBufs.create(); sendPacket(createPacket(payload));
payload.write(buf);
sendPacket(payload.id(), buf);
} }
/** /**
* Sends a packet. * Sends a packet.
* *
* @param packet the packet * @param packet the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. * @param callback an optional callback to execute after the packet is sent, may be {@code null}.
*/
void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback);
/**
* Sends a packet.
*
* @param packet the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default <T extends FabricPacket> void sendPacket(T packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
sendPacket(createPacket(packet), callback);
}
/**
* Sends a packet.
*
* @param payload the payload
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default void sendPacket(CustomPayload payload, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
PacketByteBuf buf = PacketByteBufs.create();
payload.write(buf);
sendPacket(payload.id(), buf, callback);
}
/**
* Sends a packet.
*
* @param packet the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/ */
void sendPacket(Packet<?> packet, @Nullable PacketCallbacks callback); void sendPacket(Packet<?> packet, @Nullable PacketCallbacks callback);
/**
* Sends a packet.
*
* @param packet the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}.
*/
default <T extends FabricPacket> void sendPacket(T packet, @Nullable PacketCallbacks callback) {
sendPacket(createPacket(packet), callback);
}
/** /**
* Sends a packet. * Sends a packet.
* *
* @param payload the payload * @param payload the payload
* @param callback an optional callback to execute after the packet is sent, may be {@code null}. The callback may also accept a {@link ChannelFutureListener}. * @param callback an optional callback to execute after the packet is sent, may be {@code null}.
*/ */
default void sendPacket(CustomPayload payload, @Nullable PacketCallbacks callback) { default void sendPacket(CustomPayload payload, @Nullable PacketCallbacks callback) {
PacketByteBuf buf = PacketByteBufs.create(); sendPacket(createPacket(payload), callback);
payload.write(buf);
sendPacket(payload.id(), buf, callback);
} }
/** /**
* Sends a packet to a channel. * Disconnects the player.
* * @param disconnectReason the reason for disconnection
* @param channel the id of the channel
* @param buf the content of the packet
*/ */
default void sendPacket(Identifier channel, PacketByteBuf buf) { void disconnect(Text disconnectReason);
Objects.requireNonNull(channel, "Channel cannot be null");
Objects.requireNonNull(buf, "Payload cannot be null");
this.sendPacket(this.createPacket(channel, buf));
}
/**
* Sends a packet to a channel.
*
* @param channel the id of the channel
* @param buf the content of the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}
*/
// the generic future listener can accept ChannelFutureListener
default void sendPacket(Identifier channel, PacketByteBuf buf, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
sendPacket(channel, buf, GenericFutureListenerHolder.create(callback));
}
/**
* Sends a packet to a channel.
*
* @param channel the id of the channel
* @param buf the content of the packet
* @param callback an optional callback to execute after the packet is sent, may be {@code null}
*/
default void sendPacket(Identifier channel, PacketByteBuf buf, @Nullable PacketCallbacks callback) {
Objects.requireNonNull(channel, "Channel cannot be null");
Objects.requireNonNull(buf, "Payload cannot be null");
this.sendPacket(this.createPacket(channel, buf), callback);
}
} }

View file

@ -1,71 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import java.util.function.Function;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
/**
* A type of packet. An instance of this should be created per a {@link FabricPacket} implementation.
* This holds the channel ID used for the packet.
*
* @param <T> the type of the packet
* @see FabricPacket
*/
public final class PacketType<T extends FabricPacket> {
private final Identifier id;
private final Function<PacketByteBuf, T> constructor;
private PacketType(Identifier id, Function<PacketByteBuf, T> constructor) {
this.id = id;
this.constructor = constructor;
}
/**
* Creates a new packet type.
* @param id the channel ID used for the packets
* @param constructor the reader that reads the received buffer
* @param <P> the type of the packet
* @return the newly created type
*/
public static <P extends FabricPacket> PacketType<P> create(Identifier id, Function<PacketByteBuf, P> constructor) {
return new PacketType<>(id, constructor);
}
/**
* Returns the identifier of the channel used to send the packet.
* @return the identifier of the associated channel.
*/
public Identifier getId() {
return id;
}
/**
* Reads the packet from the buffer.
* @param buf the buffer
* @return the packet
*/
public T read(PacketByteBuf buf) {
try {
return this.constructor.apply(buf);
} catch (RuntimeException e) {
throw new RuntimeException("Error while handling packet \"%s\": %s".formatted(this.id, e.getMessage()), e);
}
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.networking.v1;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
/**
* A registry for payload types.
*/
@ApiStatus.NonExtendable
public interface PayloadTypeRegistry<B extends PacketByteBuf> {
/**
* Registers a custom payload type.
*
* <p>This must be done on both the sending and receiving side, usually during mod initialization
* and <strong>before registering a packet handler</strong>.
*
* @param id the id of the payload type
* @param codec the codec for the payload type
* @param <T> the payload type
* @return the registered payload type
*/
<T extends CustomPayload> CustomPayload.Type<? super B, T> register(CustomPayload.Id<T> id, PacketCodec<? super B, T> codec);
/**
* @return the {@link PayloadTypeRegistry} instance for the client to server configuration channel.
*/
static PayloadTypeRegistry<PacketByteBuf> configurationC2S() {
return PayloadTypeRegistryImpl.CONFIGURATION_C2S;
}
/**
* @return the {@link PayloadTypeRegistry} instance for the server to client configuration channel.
*/
static PayloadTypeRegistry<PacketByteBuf> configurationS2C() {
return PayloadTypeRegistryImpl.CONFIGURATION_S2C;
}
/**
* @return the {@link PayloadTypeRegistry} instance for the client to server play channel.
*/
static PayloadTypeRegistry<RegistryByteBuf> playC2S() {
return PayloadTypeRegistryImpl.PLAY_C2S;
}
/**
* @return the {@link PayloadTypeRegistry} instance for the server to client play channel.
*/
static PayloadTypeRegistry<RegistryByteBuf> playS2C() {
return PayloadTypeRegistryImpl.PLAY_S2C;
}
}

View file

@ -19,20 +19,17 @@ package net.fabricmc.fabric.api.networking.v1;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientCommonPacketListener; import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler; import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.thread.ThreadExecutor; import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor;
@ -40,11 +37,13 @@ import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerA
* Offers access to configuration stage server-side networking functionalities. * Offers access to configuration stage server-side networking functionalities.
* *
* <p>Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers. * <p>Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers.
* Packets <strong>received</strong> by this class must be registered to {@link PayloadTypeRegistry#configurationC2S()} on both ends.
* Packets <strong>sent</strong> by this class must be registered to {@link PayloadTypeRegistry#configurationS2C()} on both ends.
* Packets must be registered before registering any receivers.
* *
* <p>This class should be only used for the logical server. * <p>This class should be only used for the logical server.
* *
* <p>See {@link ServerPlayNetworking} for information on how to use the packet * <p>See {@link ServerPlayNetworking} for information on sending and receiving play phase packets.
* object-based API.
* *
* <p>See the documentation on each class for more information. * <p>See the documentation on each class for more information.
* *
@ -53,76 +52,38 @@ import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerA
*/ */
public final class ServerConfigurationNetworking { public final class ServerConfigurationNetworking {
/** /**
* Registers a handler to a channel. * Registers a handler for a payload type.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The handler runs on the network thread. After reading the buffer there, the server
* must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}.
*
* <p>If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)} to unregister the existing handler.
*
* <p>For new code, {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)}
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param channelName the id of the channel
* @param channelHandler the handler
* @return false if a handler is already registered to the channel
* @see ServerConfigurationNetworking#unregisterGlobalReceiver(Identifier)
* @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, Identifier, ConfigurationChannelHandler)
*/
public static boolean registerGlobalReceiver(Identifier channelName, ConfigurationChannelHandler channelHandler) {
return ServerNetworkingImpl.CONFIGURATION.registerGlobalReceiver(channelName, wrapUntyped(channelHandler));
}
/**
* Registers a handler for a packet type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, PacketType)} to unregister the existing handler. * Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)} to unregister the existing handler.
* *
* @param type the packet type * @param type the packet type
* @param handler the handler * @param handler the handler
* @return {@code false} if a handler is already registered to the channel * @return {@code false} if a handler is already registered to the channel
* @see ServerConfigurationNetworking#unregisterGlobalReceiver(PacketType) * @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#configurationC2S() registered} yet
* @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler) * @see ServerConfigurationNetworking#unregisterGlobalReceiver(Identifier)
* @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, CustomPayload.Id, ConfigurationPacketHandler)
*/ */
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, ConfigurationPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, ConfigurationPacketHandler<T> handler) {
return ServerNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.getId(), wrapTyped(type, handler)); return ServerNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.id(), handler);
} }
/** /**
* Removes the handler of a channel. * Removes the handler for a payload type.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The {@code channel} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel
* @see ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)
* @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)
*/
@Nullable
public static ServerConfigurationNetworking.ConfigurationChannelHandler unregisterGlobalReceiver(Identifier channelName) {
return unwrapUntyped(ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(channelName));
}
/**
* Removes the handler for a packet type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code type} is guaranteed not to have an associated handler after this call.
* *
* @param type the packet type * @param id the packet payload id
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} * or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, ConfigurationPacketHandler)}
* @see ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler) * @see ServerConfigurationNetworking#registerGlobalReceiver(CustomPayload.Id, ConfigurationPacketHandler)
* @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, PacketType) * @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> ServerConfigurationNetworking.ConfigurationPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) { public static ServerConfigurationNetworking.ConfigurationPacketHandler<?> unregisterGlobalReceiver(Identifier id) {
return unwrapTyped(ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(type.getId())); return ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(id);
} }
/** /**
@ -136,82 +97,36 @@ public final class ServerConfigurationNetworking {
} }
/** /**
* Registers a handler to a channel. * Registers a handler for a payload type.
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)} since * This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(CustomPayload.Id, ConfigurationPacketHandler)} since
* the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}. * the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}.
* *
* <p>The handler runs on the network thread. After reading the buffer there, the world
* must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}.
*
* <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
* login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler.
*
* <p>If a handler is already registered to the {@code channelName}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)} to unregister the existing handler.
*
* <p>For new code, {@link #registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler)}
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param networkHandler the handler
* @param channelName the id of the channel
* @param channelHandler the handler
* @return false if a handler is already registered to the channel name
* @see ServerPlayConnectionEvents#INIT
*/
public static boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, Identifier channelName, ConfigurationChannelHandler channelHandler) {
Objects.requireNonNull(networkHandler, "Network handler cannot be null");
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(channelName, wrapUntyped(channelHandler));
}
/**
* Registers a handler for a packet type.
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler)} since
* the channel handler will only be applied to the client represented by the {@link ServerConfigurationNetworkHandler}.
*
* <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
* login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler.
*
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, PacketType)} to unregister the existing handler. * Use {@link #unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)} to unregister the existing handler.
* *
* @param networkHandler the network handler * @param networkHandler the network handler
* @param type the packet type * @param type the packet type
* @param handler the handler * @param handler the handler
* @return {@code false} if a handler is already registered to the channel name * @return {@code false} if a handler is already registered to the channel name
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#configurationC2S() registered} yet
* @see ServerPlayConnectionEvents#INIT * @see ServerPlayConnectionEvents#INIT
*/ */
public static <T extends FabricPacket> boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, PacketType<T> type, ConfigurationPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, CustomPayload.Id<T> type, ConfigurationPacketHandler<T> handler) {
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.getId(), wrapTyped(type, handler)); return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.id(), handler);
} }
/** /**
* Removes the handler of a channel. * Removes the handler for a payload type.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel name
*/
@Nullable
public static ServerConfigurationNetworking.ConfigurationChannelHandler unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, Identifier channelName) {
Objects.requireNonNull(networkHandler, "Network handler cannot be null");
return unwrapUntyped(ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(channelName));
}
/**
* Removes the handler for a packet type.
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code type} is guaranteed not to have an associated handler after this call.
* *
* @param type the type of the packet * @param id the id of the payload
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler)} * or it was not registered using {@link #registerReceiver(ServerConfigurationNetworkHandler, CustomPayload.Id, ConfigurationPacketHandler)}
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> ServerConfigurationNetworking.ConfigurationPacketHandler<T> unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, PacketType<T> type) { public static ServerConfigurationNetworking.ConfigurationPacketHandler<?> unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, Identifier id) {
return unwrapTyped(ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(type.getId())); return ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(id);
} }
/** /**
@ -256,41 +171,27 @@ public final class ServerConfigurationNetworking {
* Checks if the connected client declared the ability to receive a specific type of packet. * Checks if the connected client declared the ability to receive a specific type of packet.
* *
* @param handler the network handler * @param handler the network handler
* @param type the packet type * @param id the payload id
* @return {@code true} if the connected client has declared the ability to receive a specific type of packet * @return {@code true} if the connected client has declared the ability to receive a specific type of packet
*/ */
public static boolean canSend(ServerConfigurationNetworkHandler handler, PacketType<?> type) { public static boolean canSend(ServerConfigurationNetworkHandler handler, CustomPayload.Id<?> id) {
Objects.requireNonNull(handler, "Server configuration network handler cannot be null"); Objects.requireNonNull(handler, "Server configuration network handler cannot be null");
Objects.requireNonNull(type, "Packet type cannot be null"); Objects.requireNonNull(id, "Payload id cannot be null");
return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(type.getId()); return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(id.id());
} }
/** /**
* Creates a packet which may be sent to a connected client. * Creates a packet which may be sent to a connected client.
* *
* @param channelName the channel name * @param payload the payload
* @param buf the packet byte buf which represents the payload of the packet
* @return a new packet * @return a new packet
*/ */
public static Packet<ClientCommonPacketListener> createS2CPacket(Identifier channelName, PacketByteBuf buf) { public static Packet<ClientCommonPacketListener> createS2CPacket(CustomPayload payload) {
Objects.requireNonNull(channelName, "Channel cannot be null"); Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(buf, "Buf cannot be null"); Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
return ServerNetworkingImpl.createS2CPacket(channelName, buf); return ServerNetworkingImpl.createS2CPacket(payload);
}
/**
* Creates a packet which may be sent to a connected client.
*
* @param packet the fabric packet
* @return a new packet
*/
public static <T extends FabricPacket> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
return ServerNetworkingImpl.createS2CPacket(packet);
} }
/** /**
@ -308,30 +209,17 @@ public final class ServerConfigurationNetworking {
/** /**
* Sends a packet to a configuring player. * Sends a packet to a configuring player.
* *
* @param handler the handler to send the packet to * <p>Any packets sent must be {@linkplain PayloadTypeRegistry#configurationS2C() registered}.</p>
* @param channelName the channel of the packet
* @param buf the payload of the packet.
*/
public static void send(ServerConfigurationNetworkHandler handler, Identifier channelName, PacketByteBuf buf) {
Objects.requireNonNull(handler, "Server configuration entity cannot be null");
Objects.requireNonNull(channelName, "Channel name cannot be null");
Objects.requireNonNull(buf, "Packet byte buf cannot be null");
handler.sendPacket(createS2CPacket(channelName, buf));
}
/**
* Sends a packet to a configuring player.
* *
* @param handler the network handler to send the packet to * @param handler the network handler to send the packet to
* @param packet the packet * @param payload to be sent
*/ */
public static <T extends FabricPacket> void send(ServerConfigurationNetworkHandler handler, T packet) { public static void send(ServerConfigurationNetworkHandler handler, CustomPayload payload) {
Objects.requireNonNull(handler, "Server configuration handler cannot be null"); Objects.requireNonNull(handler, "Server configuration handler cannot be null");
Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
handler.sendPacket(createS2CPacket(packet)); handler.sendPacket(createS2CPacket(payload));
} }
// Helper methods // Helper methods
@ -350,72 +238,16 @@ public final class ServerConfigurationNetworking {
private ServerConfigurationNetworking() { private ServerConfigurationNetworking() {
} }
private static ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> wrapUntyped(ConfigurationChannelHandler actualHandler) {
return new ResolvablePayload.Handler<>(null, actualHandler, (server, handler, payload, responseSender) -> {
actualHandler.receive(server, handler, ((UntypedPayload) payload).buffer(), responseSender);
});
}
@SuppressWarnings("unchecked")
private static <T extends FabricPacket> ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> wrapTyped(PacketType<T> type, ConfigurationPacketHandler<T> actualHandler) {
return new ResolvablePayload.Handler<>(type, actualHandler, (server, handler, payload, responseSender) -> {
T packet = (T) ((TypedPayload) payload).packet();
actualHandler.receive(packet, handler, responseSender);
});
}
@Nullable
private static ConfigurationChannelHandler unwrapUntyped(@Nullable ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof ConfigurationChannelHandler actual) return actual;
return null;
}
@Nullable
@SuppressWarnings({"rawtypes", "unchecked"})
private static <T extends FabricPacket> ConfigurationPacketHandler<T> unwrapTyped(@Nullable ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof ConfigurationPacketHandler actual) return actual;
return null;
}
@FunctionalInterface
public interface ConfigurationChannelHandler {
/**
* Handles an incoming packet.
*
* <p>This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}.
*
* <p>An example usage of this is:
* <pre>{@code
* ServerConfigurationNetworking.registerReceiver(new Identifier("mymod", "boom"), (server, handler, buf, responseSender) -> {
* boolean fire = buf.readBoolean();
*
* // All operations on the server must be executed on the server thread
* server.execute(() -> {
*
* });
* });
* }</pre>
* @param server the server
* @param handler the network handler that received this packet, representing the client who sent the packet
* @param buf the payload of the packet
* @param responseSender the packet sender
*/
void receive(MinecraftServer server, ServerConfigurationNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender);
}
/** /**
* A thread-safe packet handler utilizing {@link FabricPacket}. * A packet handler utilizing {@link CustomPayload}.
* @param <T> the type of the packet * @param <T> the type of the packet
*/ */
@FunctionalInterface @FunctionalInterface
public interface ConfigurationPacketHandler<T extends FabricPacket> { public interface ConfigurationPacketHandler<T extends CustomPayload> {
/** /**
* Handles an incoming packet. * Handles an incoming packet.
* *
* <p>Unlike {@link ServerPlayNetworking.PlayPacketHandler} this method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}. * <p>Unlike {@link ServerPlayNetworking.PlayPayloadHandler} this method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the Minecraft server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}. * Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the Minecraft server instance from {@link ServerConfigurationNetworking#getServer(ServerConfigurationNetworkHandler)}.
* *
* <p>An example usage of this: * <p>An example usage of this:
@ -427,11 +259,23 @@ public final class ServerConfigurationNetworking {
* }</pre> * }</pre>
* *
* *
* @param packet the packet * @param payload the packet payload
* @param networkHandler the network handler * @param context the configuration networking context
* @param responseSender the packet sender * @see CustomPayload
* @see FabricPacket
*/ */
void receive(T packet, ServerConfigurationNetworkHandler networkHandler, PacketSender responseSender); void receive(T payload, Context context);
}
@ApiStatus.NonExtendable
public interface Context {
/**
* @return The ServerConfigurationNetworkHandler instance
*/
ServerConfigurationNetworkHandler networkHandler();
/**
* @return The packet sender
*/
PacketSender responseSender();
} }
} }

View file

@ -44,7 +44,7 @@ public final class ServerLoginConnectionEvents {
* using {@link ServerLoginNetworking#registerReceiver(ServerLoginNetworkHandler, Identifier, ServerLoginNetworking.LoginQueryResponseHandler)} * using {@link ServerLoginNetworking#registerReceiver(ServerLoginNetworkHandler, Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
* since this event is fired just before the first login query response is processed. * since this event is fired just before the first login query response is processed.
* *
* <p>You may send login queries to the connected client using the provided {@link PacketSender}. * <p>You may send login queries to the connected client using the provided {@link LoginPacketSender}.
*/ */
public static final Event<QueryStart> QUERY_START = EventFactory.createArrayBacked(QueryStart.class, callbacks -> (handler, server, sender, synchronizer) -> { public static final Event<QueryStart> QUERY_START = EventFactory.createArrayBacked(QueryStart.class, callbacks -> (handler, server, sender, synchronizer) -> {
for (QueryStart callback : callbacks) { for (QueryStart callback : callbacks) {
@ -79,7 +79,7 @@ public final class ServerLoginConnectionEvents {
*/ */
@FunctionalInterface @FunctionalInterface
public interface QueryStart { public interface QueryStart {
void onLoginStart(ServerLoginNetworkHandler handler, MinecraftServer server, PacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer); void onLoginStart(ServerLoginNetworkHandler handler, MinecraftServer server, LoginPacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer);
} }
/** /**

View file

@ -19,46 +19,39 @@ package net.fabricmc.fabric.api.networking.v1;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientCommonPacketListener; import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.thread.ThreadExecutor;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon;
/** /**
* Offers access to play stage server-side networking functionalities. * Offers access to play stage server-side networking functionalities.
* *
* <p>Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers. * <p>Server-side networking functionalities include receiving serverbound packets, sending clientbound packets, and events related to server-side network handlers.
* Packets <strong>received</strong> by this class must be registered to {@link PayloadTypeRegistry#playC2S()} on both ends.
* Packets <strong>sent</strong> by this class must be registered to {@link PayloadTypeRegistry#playS2C()} on both ends.
* Packets must be registered before registering any receivers.
* *
* <p>This class should be only used for the logical server. * <p>This class should be only used for the logical server.
* *
* <h2>Packet object-based API</h2> * <h2>Packet object-based API</h2>
* *
* <p>This class provides a classic registration method, {@link #registerGlobalReceiver(Identifier, PlayChannelHandler)}, * <p>This class provides a registration method, utilizing packet objects, {@link #registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)}.
* and a newer method utilizing packet objects, {@link #registerGlobalReceiver(PacketType, PlayPacketHandler)}. * This handler executes the callback in the server thread, ensuring thread safety.
* For most mods, using the newer method will improve the readability of the code by separating packet
* reading/writing code to a separate class. Additionally, the newer method executes the callback in the
* server thread, ensuring thread safety. For this reason using the newer method is highly recommended.
* The two methods are network-compatible with each other, so long as the buffer contents are read and written
* in the same order.
* *
* <p>The newer, packet object-based API involves three classes: * <p>This payload object-based API involves three classes:
* *
* <ul> * <ul>
* <li>A class implementing {@link FabricPacket} that is "sent" over the network</li> * <li>A class implementing {@link CustomPayload} that is "sent" over the network</li>
* <li>{@link PacketType} instance, which represents the packet's type (and its channel)</li> * <li>{@link CustomPayload.Type} instance, which represents the packet's type (and its codec)</li>
* <li>{@link PlayPacketHandler}, which handles the packet (usually implemented as a functional interface)</li> * <li>{@link PlayPayloadHandler}, which handles the packet (usually implemented as a functional interface)</li>
* </ul> * </ul>
* *
* <p>See the documentation on each class for more information. * <p>See the documentation on each class for more information.
@ -68,76 +61,37 @@ import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon;
*/ */
public final class ServerPlayNetworking { public final class ServerPlayNetworking {
/** /**
* Registers a handler to a channel. * Registers a handler for a payload type.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The handler runs on the network thread. After reading the buffer there, the world
* must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}.
*
* <p>If a handler is already registered to the {@code channel}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerPlayNetworkHandler, Identifier)} to unregister the existing handler.
*
* <p>For new code, {@link #registerGlobalReceiver(PacketType, PlayPacketHandler)}
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param channelName the id of the channel
* @param channelHandler the handler
* @return false if a handler is already registered to the channel
* @see ServerPlayNetworking#unregisterGlobalReceiver(Identifier)
* @see ServerPlayNetworking#registerReceiver(ServerPlayNetworkHandler, Identifier, PlayChannelHandler)
*/
public static boolean registerGlobalReceiver(Identifier channelName, PlayChannelHandler channelHandler) {
return ServerNetworkingImpl.PLAY.registerGlobalReceiver(channelName, wrapUntyped(channelHandler));
}
/**
* Registers a handler for a packet type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerPlayNetworkHandler, PacketType)} to unregister the existing handler. * Use {@link #unregisterGlobalReceiver(Identifier)} to unregister the existing handler.
* *
* @param type the packet type * @param type the packet type
* @param handler the handler * @param handler the handler
* @return {@code false} if a handler is already registered to the channel * @return {@code false} if a handler is already registered to the channel
* @see ServerPlayNetworking#unregisterGlobalReceiver(PacketType) * @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#playC2S() registered} yet
* @see ServerPlayNetworking#registerReceiver(ServerPlayNetworkHandler, PacketType, PlayPacketHandler) * @see ServerPlayNetworking#unregisterGlobalReceiver(Identifier)
*/ */
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, PlayPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, PlayPayloadHandler<T> handler) {
return ServerNetworkingImpl.PLAY.registerGlobalReceiver(type.getId(), wrapTyped(type, handler)); return ServerNetworkingImpl.PLAY.registerGlobalReceiver(type.id(), handler);
} }
/** /**
* Removes the handler of a channel. * Removes the handler for a payload type.
* A global receiver is registered to all connections, in the present and future. * A global receiver is registered to all connections, in the present and future.
* *
* <p>The {@code channel} is guaranteed not to have a handler after this call. * <p>The {@code id} is guaranteed not to have an associated handler after this call.
* *
* @param channelName the id of the channel * @param id the payload id
* @return the previous handler, or {@code null} if no handler was bound to the channel * @return the previous handler, or {@code null} if no handler was bound to the channel,
* @see ServerPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler) * or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)}
* @see ServerPlayNetworking#registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)
* @see ServerPlayNetworking#unregisterReceiver(ServerPlayNetworkHandler, Identifier) * @see ServerPlayNetworking#unregisterReceiver(ServerPlayNetworkHandler, Identifier)
*/ */
@Nullable @Nullable
public static PlayChannelHandler unregisterGlobalReceiver(Identifier channelName) { public static ServerPlayNetworking.PlayPayloadHandler<?> unregisterGlobalReceiver(Identifier id) {
return unwrapUntyped(ServerNetworkingImpl.PLAY.unregisterGlobalReceiver(channelName)); return ServerNetworkingImpl.PLAY.unregisterGlobalReceiver(id);
}
/**
* Removes the handler for a packet type.
* A global receiver is registered to all connections, in the present and future.
*
* <p>The {@code type} is guaranteed not to have an associated handler after this call.
*
* @param type the packet type
* @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerGlobalReceiver(PacketType, PlayPacketHandler)}
* @see ServerPlayNetworking#registerGlobalReceiver(PacketType, PlayPacketHandler)
* @see ServerPlayNetworking#unregisterReceiver(ServerPlayNetworkHandler, PacketType)
*/
@Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) {
return unwrapTyped(ServerNetworkingImpl.PLAY.unregisterGlobalReceiver(type.getId()));
} }
/** /**
@ -151,82 +105,39 @@ public final class ServerPlayNetworking {
} }
/** /**
* Registers a handler to a channel. * Registers a handler for a payload type.
* This method differs from {@link ServerPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler)} since * This method differs from {@link ServerPlayNetworking#registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)} since
* the channel handler will only be applied to the player represented by the {@link ServerPlayNetworkHandler}.
*
* <p>The handler runs on the network thread. After reading the buffer there, the world
* must be modified in the server thread by calling {@link ThreadExecutor#execute(Runnable)}.
*
* <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
* login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler.
*
* <p>If a handler is already registered to the {@code channelName}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerPlayNetworkHandler, Identifier)} to unregister the existing handler.
*
* <p>For new code, {@link #registerReceiver(ServerPlayNetworkHandler, PacketType, PlayPacketHandler)}
* is preferred, as it is designed in a way that prevents thread safety issues.
*
* @param networkHandler the handler
* @param channelName the id of the channel
* @param channelHandler the handler
* @return false if a handler is already registered to the channel name
* @see ServerPlayConnectionEvents#INIT
*/
public static boolean registerReceiver(ServerPlayNetworkHandler networkHandler, Identifier channelName, PlayChannelHandler channelHandler) {
Objects.requireNonNull(networkHandler, "Network handler cannot be null");
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(channelName, wrapUntyped(channelHandler));
}
/**
* Registers a handler for a packet type.
* This method differs from {@link ServerPlayNetworking#registerGlobalReceiver(PacketType, PlayPacketHandler)} since
* the channel handler will only be applied to the player represented by the {@link ServerPlayNetworkHandler}. * the channel handler will only be applied to the player represented by the {@link ServerPlayNetworkHandler}.
* *
* <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)} * <p>For example, if you only register a receiver using this method when a {@linkplain ServerLoginNetworking#registerGlobalReceiver(Identifier, ServerLoginNetworking.LoginQueryResponseHandler)}
* login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler. * login response has been received, you should use {@link ServerPlayConnectionEvents#INIT} to register the channel handler.
* *
* <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made. * <p>If a handler is already registered for the {@code type}, this method will return {@code false}, and no change will be made.
* Use {@link #unregisterReceiver(ServerPlayNetworkHandler, PacketType)} to unregister the existing handler. * Use {@link #unregisterReceiver(ServerPlayNetworkHandler, Identifier)} to unregister the existing handler.
* *
* @param networkHandler the network handler * @param networkHandler the network handler
* @param type the packet type * @param type the packet type
* @param handler the handler * @param handler the handler
* @return {@code false} if a handler is already registered to the channel name * @return {@code false} if a handler is already registered to the channel name
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#playC2S() registered} yet
* @see ServerPlayConnectionEvents#INIT * @see ServerPlayConnectionEvents#INIT
*/ */
public static <T extends FabricPacket> boolean registerReceiver(ServerPlayNetworkHandler networkHandler, PacketType<T> type, PlayPacketHandler<T> handler) { public static <T extends CustomPayload> boolean registerReceiver(ServerPlayNetworkHandler networkHandler, CustomPayload.Id<T> type, PlayPayloadHandler<T> handler) {
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.getId(), wrapTyped(type, handler)); return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.id(), handler);
}
/**
* Removes the handler of a channel.
*
* <p>The {@code channelName} is guaranteed not to have a handler after this call.
*
* @param channelName the id of the channel
* @return the previous handler, or {@code null} if no handler was bound to the channel name
*/
@Nullable
public static PlayChannelHandler unregisterReceiver(ServerPlayNetworkHandler networkHandler, Identifier channelName) {
Objects.requireNonNull(networkHandler, "Network handler cannot be null");
return unwrapUntyped(ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(channelName));
} }
/** /**
* Removes the handler for a packet type. * Removes the handler for a packet type.
* *
* <p>The {@code type} is guaranteed not to have an associated handler after this call. * <p>The {@code id} is guaranteed not to have an associated handler after this call.
* *
* @param type the type of the packet * @param id the id of the payload
* @return the previous handler, or {@code null} if no handler was bound to the channel, * @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerReceiver(ServerPlayNetworkHandler, PacketType, PlayPacketHandler)} * or it was not registered using {@link #registerReceiver(ServerPlayNetworkHandler, CustomPayload.Id, PlayPayloadHandler)}
*/ */
@Nullable @Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterReceiver(ServerPlayNetworkHandler networkHandler, PacketType<T> type) { public static ServerPlayNetworking.PlayPayloadHandler<?> unregisterReceiver(ServerPlayNetworkHandler networkHandler, Identifier id) {
return unwrapTyped(ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(type.getId())); return ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(id);
} }
/** /**
@ -297,10 +208,10 @@ public final class ServerPlayNetworking {
* @param type the packet type * @param type the packet type
* @return {@code true} if the connected client has declared the ability to receive a specific type of packet * @return {@code true} if the connected client has declared the ability to receive a specific type of packet
*/ */
public static boolean canSend(ServerPlayerEntity player, PacketType<?> type) { public static boolean canSend(ServerPlayerEntity player, CustomPayload.Id<?> type) {
Objects.requireNonNull(player, "Server player entity cannot be null"); Objects.requireNonNull(player, "Server player entity cannot be null");
return canSend(player.networkHandler, type.getId()); return canSend(player.networkHandler, type.id());
} }
/** /**
@ -324,34 +235,20 @@ public final class ServerPlayNetworking {
* @param type the packet type * @param type the packet type
* @return {@code true} if the connected client has declared the ability to receive a specific type of packet * @return {@code true} if the connected client has declared the ability to receive a specific type of packet
*/ */
public static boolean canSend(ServerPlayNetworkHandler handler, PacketType<?> type) { public static boolean canSend(ServerPlayNetworkHandler handler, CustomPayload.Id<?> type) {
Objects.requireNonNull(handler, "Server play network handler cannot be null"); Objects.requireNonNull(handler, "Server play network handler cannot be null");
Objects.requireNonNull(type, "Packet type cannot be null"); Objects.requireNonNull(type, "Packet type cannot be null");
return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(type.getId()); return ServerNetworkingImpl.getAddon(handler).getSendableChannels().contains(type.id());
} }
/** /**
* Creates a packet which may be sent to a connected client. * Creates a packet which may be sent to a connected client.
* *
* @param channelName the channel name * @param packet the packet
* @param buf the packet byte buf which represents the payload of the packet
* @return a new packet * @return a new packet
*/ */
public static Packet<ClientCommonPacketListener> createS2CPacket(Identifier channelName, PacketByteBuf buf) { public static <T extends CustomPayload> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
Objects.requireNonNull(channelName, "Channel cannot be null");
Objects.requireNonNull(buf, "Buf cannot be null");
return ServerNetworkingImpl.createS2CPacket(channelName, buf);
}
/**
* Creates a packet which may be sent to a connected client.
*
* @param packet the fabric packet
* @return a new packet
*/
public static <T extends FabricPacket> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
return ServerNetworkingImpl.createS2CPacket(packet); return ServerNetworkingImpl.createS2CPacket(packet);
} }
@ -382,142 +279,60 @@ public final class ServerPlayNetworking {
/** /**
* Sends a packet to a player. * Sends a packet to a player.
* *
* @param player the player to send the packet to * <p>Any packets sent must be {@linkplain PayloadTypeRegistry#playS2C() registered}.</p>
* @param channelName the channel of the packet
* @param buf the payload of the packet.
*/
public static void send(ServerPlayerEntity player, Identifier channelName, PacketByteBuf buf) {
Objects.requireNonNull(player, "Server player entity cannot be null");
Objects.requireNonNull(channelName, "Channel name cannot be null");
Objects.requireNonNull(buf, "Packet byte buf cannot be null");
player.networkHandler.sendPacket(createS2CPacket(channelName, buf));
}
/**
* Sends a packet to a player.
* *
* @param player the player to send the packet to * @param player the player to send the packet to
* @param packet the packet * @param payload the payload to send
*/ */
public static <T extends FabricPacket> void send(ServerPlayerEntity player, T packet) { public static void send(ServerPlayerEntity player, CustomPayload payload) {
Objects.requireNonNull(player, "Server player entity cannot be null"); Objects.requireNonNull(player, "Server player entity cannot be null");
Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null"); Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
player.networkHandler.sendPacket(createS2CPacket(packet)); player.networkHandler.sendPacket(createS2CPacket(payload));
}
// Helper methods
/**
* Returns the <i>Minecraft</i> Server of a server play network handler.
*
* @param handler the server play network handler
*/
public static MinecraftServer getServer(ServerPlayNetworkHandler handler) {
Objects.requireNonNull(handler, "Network handler cannot be null");
return handler.player.server;
} }
private ServerPlayNetworking() { private ServerPlayNetworking() {
} }
private static ResolvablePayload.Handler<ServerPlayNetworkAddon.Handler> wrapUntyped(PlayChannelHandler actualHandler) {
return new ResolvablePayload.Handler<>(null, actualHandler, (server, player, handler, payload, responseSender) -> {
actualHandler.receive(server, player, handler, ((UntypedPayload) payload).buffer(), responseSender);
});
}
@SuppressWarnings("unchecked")
private static <T extends FabricPacket> ResolvablePayload.Handler<ServerPlayNetworkAddon.Handler> wrapTyped(PacketType<T> type, PlayPacketHandler<T> actualHandler) {
return new ResolvablePayload.Handler<>(type, actualHandler, (server, player, handler, payload, responseSender) -> {
T packet = (T) ((TypedPayload) payload).packet();
if (server.isOnThread()) {
// Do not submit to the server thread if we're already running there.
// Normally, packets are handled on the network IO thread - though it is
// not guaranteed (for example, with 1.19.4 S2C packet bundling)
// Since we're handling it right now, connection check is redundant.
actualHandler.receive(packet, player, responseSender);
} else {
server.execute(() -> {
if (handler.isConnectionOpen()) actualHandler.receive(packet, player, responseSender);
});
}
});
}
@Nullable
private static PlayChannelHandler unwrapUntyped(@Nullable ResolvablePayload.Handler<ServerPlayNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof PlayChannelHandler actual) return actual;
return null;
}
@Nullable
@SuppressWarnings({"rawtypes", "unchecked"})
private static <T extends FabricPacket> PlayPacketHandler<T> unwrapTyped(@Nullable ResolvablePayload.Handler<ServerPlayNetworkAddon.Handler> handler) {
if (handler == null) return null;
if (handler.actual() instanceof PlayPacketHandler actual) return actual;
return null;
}
@FunctionalInterface
public interface PlayChannelHandler {
/**
* Handles an incoming packet.
*
* <p>This method is executed on {@linkplain io.netty.channel.EventLoop netty's event loops}.
* Modification to the game should be {@linkplain ThreadExecutor#submit(Runnable) scheduled} using the provided Minecraft server instance.
*
* <p>An example usage of this is to create an explosion where the player is looking:
* <pre>{@code
* ServerPlayNetworking.registerReceiver(new Identifier("mymod", "boom"), (server, player, handler, buf, responseSender) -> {
* boolean fire = buf.readBoolean();
*
* // All operations on the server or world must be executed on the server thread
* server.execute(() -> {
* ModPacketHandler.createExplosion(player, fire);
* });
* });
* }</pre>
* @param server the server
* @param player the player
* @param handler the network handler that received this packet, representing the player/client who sent the packet
* @param buf the payload of the packet
* @param responseSender the packet sender
*/
void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender);
}
/** /**
* A thread-safe packet handler utilizing {@link FabricPacket}. * A thread-safe packet handler utilizing {@link CustomPayload}.
* @param <T> the type of the packet * @param <T> the type of the packet
*/ */
@FunctionalInterface @FunctionalInterface
public interface PlayPacketHandler<T extends FabricPacket> { public interface PlayPayloadHandler<T extends CustomPayload> {
/** /**
* Handles the incoming packet. This is called on the server thread, and can safely * Handles the incoming packet. This is called on the server thread, and can safely
* manipulate the world. * manipulate the world.
* *
* <p>An example usage of this is to create an explosion where the player is looking: * <p>An example usage of this is to create an explosion where the player is looking:
* <pre>{@code * <pre>{@code
* // See FabricPacket for creating the packet * // use PayloadTypeRegistry for registering the payload
* ServerPlayNetworking.registerReceiver(BOOM_PACKET_TYPE, (player, packet, responseSender) -> { * ServerPlayNetworking.registerReceiver(BoomPayload.ID, (payload, player, responseSender) -> {
* ModPacketHandler.createExplosion(player, packet.fire()); * ModPacketHandler.createExplosion(player, payload.fire());
* }); * });
* }</pre> * }</pre>
* *
* <p>The server and the network handler can be accessed via {@link ServerPlayerEntity#server} * <p>The server and the network handler can be accessed via {@link ServerPlayerEntity#server}
* and {@link ServerPlayerEntity#networkHandler}, respectively. * and {@link ServerPlayerEntity#networkHandler}, respectively.
* *
* @param packet the packet * @param payload the packet payload
* @param player the player that received the packet * @param context the play networking context
* @param responseSender the packet sender * @see CustomPayload
* @see FabricPacket
*/ */
void receive(T packet, ServerPlayerEntity player, PacketSender responseSender); void receive(T payload, Context context);
}
@ApiStatus.NonExtendable
public interface Context {
/**
* @return The player that received the packet
*/
ServerPlayerEntity player();
/**
* @return The packet sender
*/
PacketSender responseSender();
} }
} }

View file

@ -44,7 +44,7 @@
* </dl> * </dl>
* *
* <p>In addition, this API includes helpers for {@linkplain * <p>In addition, this API includes helpers for {@linkplain
* net.fabricmc.fabric.api.networking.v1.PacketByteBufs buffer creations} and {@linkplain * net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry registering custom packet payloads} and {@linkplain
* net.fabricmc.fabric.api.networking.v1.PlayerLookup player lookups}. * net.fabricmc.fabric.api.networking.v1.PlayerLookup player lookups}.
*/ */

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.impl.networking; package net.fabricmc.fabric.impl.networking;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -25,46 +24,39 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import io.netty.util.AsciiString;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
/** /**
* A network addon which is aware of the channels the other side may receive. * A network addon which is aware of the channels the other side may receive.
* *
* @param <H> the channel handler type * @param <H> the channel handler type
*/ */
public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<ResolvablePayload.Handler<H>> implements PacketSender, CommonPacketHandler { public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAddon<H> implements PacketSender, CommonPacketHandler {
protected final ClientConnection connection; protected final ClientConnection connection;
protected final GlobalReceiverRegistry<ResolvablePayload.Handler<H>> receiver; protected final GlobalReceiverRegistry<H> receiver;
protected final Set<Identifier> sendableChannels; protected final Set<Identifier> sendableChannels;
protected int commonVersion = -1; protected int commonVersion = -1;
protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry<ResolvablePayload.Handler<H>> receiver, ClientConnection connection, String description) { protected AbstractChanneledNetworkAddon(GlobalReceiverRegistry<H> receiver, ClientConnection connection, String description) {
super(receiver, description); super(receiver, description);
this.connection = connection; this.connection = connection;
this.receiver = receiver; this.receiver = receiver;
this.sendableChannels = Collections.synchronizedSet(new HashSet<>()); this.sendableChannels = Collections.synchronizedSet(new HashSet<>());
} }
protected void registerPendingChannels(ChannelInfoHolder holder, NetworkState state) { protected void registerPendingChannels(ChannelInfoHolder holder, NetworkPhase state) {
final Collection<Identifier> pending = holder.getPendingChannelsNames(state); final Collection<Identifier> pending = holder.fabric_getPendingChannelsNames(state);
if (!pending.isEmpty()) { if (!pending.isEmpty()) {
register(new ArrayList<>(pending)); register(new ArrayList<>(pending));
@ -73,30 +65,31 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
} }
// always supposed to handle async! // always supposed to handle async!
public boolean handle(ResolvablePayload resolvable) { public boolean handle(CustomPayload payload) {
Identifier channelName = resolvable.id(); final Identifier channelName = payload.getId().id();
this.logger.debug("Handling inbound packet from channel with name \"{}\"", channelName); this.logger.debug("Handling inbound packet from channel with name \"{}\"", channelName);
// Handle reserved packets // Handle reserved packets
if (NetworkingImpl.REGISTER_CHANNEL.equals(channelName)) { if (payload instanceof RegistrationPayload registrationPayload) {
this.receiveRegistration(true, resolvable); if (NetworkingImpl.REGISTER_CHANNEL.equals(channelName)) {
return true; this.receiveRegistration(true, registrationPayload);
return true;
}
if (NetworkingImpl.UNREGISTER_CHANNEL.equals(channelName)) {
this.receiveRegistration(false, registrationPayload);
return true;
}
} }
if (NetworkingImpl.UNREGISTER_CHANNEL.equals(channelName)) { @Nullable H handler = this.getHandler(channelName);
this.receiveRegistration(false, resolvable);
return true;
}
@Nullable ResolvablePayload.Handler<H> handler = this.getHandler(channelName);
if (handler == null) { if (handler == null) {
return false; return false;
} }
try { try {
ResolvedPayload resolved = resolvable.resolve(handler.type()); this.receive(handler, payload);
this.receive(handler.internal(), resolved);
} catch (Throwable ex) { } catch (Throwable ex) {
this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex); this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex);
throw ex; throw ex;
@ -105,63 +98,31 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
return true; return true;
} }
protected abstract void receive(H handler, ResolvedPayload payload); protected abstract void receive(H handler, CustomPayload payload);
protected void sendInitialChannelRegistrationPacket() { protected void sendInitialChannelRegistrationPacket() {
final PacketByteBuf buf = this.createRegistrationPacket(this.getReceivableChannels()); final RegistrationPayload payload = createRegistrationPayload(RegistrationPayload.REGISTER, this.getReceivableChannels());
if (buf != null) { if (payload != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); this.sendPacket(payload);
} }
} }
@Nullable @Nullable
protected PacketByteBuf createRegistrationPacket(Collection<Identifier> channels) { protected RegistrationPayload createRegistrationPayload(CustomPayload.Id<RegistrationPayload> id, Collection<Identifier> channels) {
if (channels.isEmpty()) { if (channels.isEmpty()) {
return null; return null;
} }
PacketByteBuf buf = PacketByteBufs.create(); return new RegistrationPayload(id, new ArrayList<>(channels));
boolean first = true;
for (Identifier channel : channels) {
if (first) {
first = false;
} else {
buf.writeByte(0);
}
buf.writeBytes(channel.toString().getBytes(StandardCharsets.US_ASCII));
}
return buf;
} }
// wrap in try with res (buf) // wrap in try with res (buf)
protected void receiveRegistration(boolean register, ResolvablePayload resolvable) { protected void receiveRegistration(boolean register, RegistrationPayload payload) {
UntypedPayload payload = (UntypedPayload) resolvable.resolve(null);
PacketByteBuf buf = payload.buffer();
List<Identifier> ids = new ArrayList<>();
StringBuilder active = new StringBuilder();
while (buf.isReadable()) {
byte b = buf.readByte();
if (b != 0) {
active.append(AsciiString.b2c(b));
} else {
this.addId(ids, active);
active = new StringBuilder();
}
}
this.addId(ids, active);
if (register) { if (register) {
register(ids); register(payload.channels());
} else { } else {
unregister(ids); unregister(payload.channels());
} }
} }
@ -175,11 +136,6 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
schedule(() -> this.invokeUnregisterEvent(ids)); schedule(() -> this.invokeUnregisterEvent(ids));
} }
@Override
public void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
sendPacket(packet, GenericFutureListenerHolder.create(callback));
}
@Override @Override
public void sendPacket(Packet<?> packet, PacketCallbacks callback) { public void sendPacket(Packet<?> packet, PacketCallbacks callback) {
Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(packet, "Packet cannot be null");
@ -187,6 +143,13 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
this.connection.send(packet, callback); this.connection.send(packet, callback);
} }
@Override
public void disconnect(Text disconnectReason) {
Objects.requireNonNull(disconnectReason, "Disconnect reason cannot be null");
this.connection.disconnect(disconnectReason);
}
/** /**
* Schedules a task to run on the main thread. * Schedules a task to run on the main thread.
*/ */
@ -196,16 +159,6 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
protected abstract void invokeUnregisterEvent(List<Identifier> ids); protected abstract void invokeUnregisterEvent(List<Identifier> ids);
private void addId(List<Identifier> ids, StringBuilder sb) {
String literal = sb.toString();
try {
ids.add(new Identifier(literal));
} catch (InvalidIdentifierException ex) {
this.logger.warn("Received invalid channel identifier \"{}\" from connection {}", literal, this.connection);
}
}
public Set<Identifier> getSendableChannels() { public Set<Identifier> getSendableChannels() {
return Collections.unmodifiableSet(this.sendableChannels); return Collections.unmodifiableSet(this.sendableChannels);
} }
@ -231,7 +184,7 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
if (currentPhase == null) { if (currentPhase == null) {
// We don't support receiving the register packet during this phase. See getPhase() for supported phases. // We don't support receiving the register packet during this phase. See getPhase() for supported phases.
// The normal case where the play channels are sent during configuration is handled in the client/common configuration packet handlers. // The normal case where the play channels are sent during configuration is handled in the client/common configuration packet handlers.
logger.warn("Received common register packet for phase {} in network state: {}", payload.phase(), receiver.getState()); logger.warn("Received common register packet for phase {} in network state: {}", payload.phase(), receiver.getPhase());
return; return;
} }
@ -259,7 +212,7 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
@Nullable @Nullable
private String getPhase() { private String getPhase() {
return switch (receiver.getState()) { return switch (receiver.getPhase()) {
case PLAY -> CommonRegisterPayload.PLAY_PHASE; case PLAY -> CommonRegisterPayload.PLAY_PHASE;
case CONFIGURATION -> CommonRegisterPayload.CONFIGURATION_PHASE; case CONFIGURATION -> CommonRegisterPayload.CONFIGURATION_PHASE;
default -> null; // We don't support receiving this packet on any other phase default -> null; // We don't support receiving this packet on any other phase

View file

@ -102,6 +102,8 @@ public abstract class AbstractNetworkAddon<H> {
Objects.requireNonNull(handler, "Packet handler cannot be null"); Objects.requireNonNull(handler, "Packet handler cannot be null");
assertNotReserved(channelName); assertNotReserved(channelName);
receiver.assertPayloadType(channelName);
Lock lock = this.lock.writeLock(); Lock lock = this.lock.writeLock();
lock.lock(); lock.lock();

View file

@ -18,12 +18,12 @@ package net.fabricmc.fabric.impl.networking;
import java.util.Collection; import java.util.Collection;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public interface ChannelInfoHolder { public interface ChannelInfoHolder {
/** /**
* @return Channels which are declared as receivable by the other side but have not been declared yet. * @return Channels which are declared as receivable by the other side but have not been declared yet.
*/ */
Collection<Identifier> getPendingChannelsNames(NetworkState state); Collection<Identifier> fabric_getPendingChannelsNames(NetworkPhase state);
} }

View file

@ -19,12 +19,14 @@ package net.fabricmc.fabric.impl.networking;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerConfigurationTask; import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
@ -33,16 +35,23 @@ public class CommonPacketsImpl {
public static final int[] SUPPORTED_COMMON_PACKET_VERSIONS = new int[]{ PACKET_VERSION_1 }; public static final int[] SUPPORTED_COMMON_PACKET_VERSIONS = new int[]{ PACKET_VERSION_1 };
public static void init() { public static void init() {
ServerConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (server, handler, buf, responseSender) -> { PayloadTypeRegistry.configurationC2S().register(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
var payload = new CommonVersionPayload(buf); PayloadTypeRegistry.configurationS2C().register(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler); PayloadTypeRegistry.playC2S().register(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
PayloadTypeRegistry.playS2C().register(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
PayloadTypeRegistry.configurationC2S().register(CommonRegisterPayload.ID, CommonRegisterPayload.CODEC);
PayloadTypeRegistry.configurationS2C().register(CommonRegisterPayload.ID, CommonRegisterPayload.CODEC);
PayloadTypeRegistry.playC2S().register(CommonRegisterPayload.ID, CommonRegisterPayload.CODEC);
PayloadTypeRegistry.playS2C().register(CommonRegisterPayload.ID, CommonRegisterPayload.CODEC);
ServerConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.ID, (payload, context) -> {
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(context.networkHandler());
addon.onCommonVersionPacket(getNegotiatedVersion(payload)); addon.onCommonVersionPacket(getNegotiatedVersion(payload));
handler.completeTask(CommonVersionConfigurationTask.KEY); context.networkHandler().completeTask(CommonVersionConfigurationTask.KEY);
}); });
ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (server, handler, buf, responseSender) -> { ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.ID, (payload, context) -> {
var payload = new CommonRegisterPayload(buf); ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(context.networkHandler());
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) { if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
if (payload.version() != addon.getNegotiatedVersion()) { if (payload.version() != addon.getNegotiatedVersion()) {
@ -50,24 +59,24 @@ public class CommonPacketsImpl {
} }
// Play phase hasnt started yet, add them to the pending names. // Play phase hasnt started yet, add them to the pending names.
addon.getChannelInfoHolder().getPendingChannelsNames(NetworkState.PLAY).addAll(payload.channels()); addon.getChannelInfoHolder().fabric_getPendingChannelsNames(NetworkPhase.PLAY).addAll(payload.channels());
NetworkingImpl.LOGGER.debug("Received accepted channels from the client for play phase"); NetworkingImpl.LOGGER.debug("Received accepted channels from the client for play phase");
} else { } else {
addon.onCommonRegisterPacket(payload); addon.onCommonRegisterPacket(payload);
} }
handler.completeTask(CommonRegisterConfigurationTask.KEY); context.networkHandler().completeTask(CommonRegisterConfigurationTask.KEY);
}); });
// Create a configuration task to send and receive the common packets // Create a configuration task to send and receive the common packets
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> { ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
final ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler); final ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.PACKET_ID)) { if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.ID)) {
// Tasks are processed in order. // Tasks are processed in order.
handler.addTask(new CommonVersionConfigurationTask(addon)); handler.addTask(new CommonVersionConfigurationTask(addon));
if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.PACKET_ID)) { if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.ID)) {
handler.addTask(new CommonRegisterConfigurationTask(addon)); handler.addTask(new CommonRegisterConfigurationTask(addon));
} }
} }
@ -76,7 +85,7 @@ public class CommonPacketsImpl {
// A configuration phase task to send and receive the version packets. // A configuration phase task to send and receive the version packets.
private record CommonVersionConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask { private record CommonVersionConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key(CommonVersionPayload.PACKET_ID.toString()); public static final Key KEY = new Key(CommonVersionPayload.ID.id().toString());
@Override @Override
public void sendPacket(Consumer<Packet<?>> sender) { public void sendPacket(Consumer<Packet<?>> sender) {
@ -91,11 +100,11 @@ public class CommonPacketsImpl {
// A configuration phase task to send and receive the registration packets. // A configuration phase task to send and receive the registration packets.
private record CommonRegisterConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask { private record CommonRegisterConfigurationTask(ServerConfigurationNetworkAddon addon) implements ServerPlayerConfigurationTask {
public static final Key KEY = new Key(CommonRegisterPayload.PACKET_ID.toString()); public static final Key KEY = new Key(CommonRegisterPayload.ID.id().toString());
@Override @Override
public void sendPacket(Consumer<Packet<?>> sender) { public void sendPacket(Consumer<Packet<?>> sender) {
addon.sendPacket(addon.createRegisterPayload()); addon.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ServerPlayNetworking.getGlobalReceivers()));
} }
@Override @Override

View file

@ -20,16 +20,18 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public record CommonRegisterPayload(int version, String phase, Set<Identifier> channels) implements CustomPayload { public record CommonRegisterPayload(int version, String phase, Set<Identifier> channels) implements CustomPayload {
public static final Identifier PACKET_ID = new Identifier("c", "register"); public static final CustomPayload.Id<CommonRegisterPayload> ID = CustomPayload.id("c:register");
public static final PacketCodec<PacketByteBuf, CommonRegisterPayload> CODEC = CustomPayload.codecOf(CommonRegisterPayload::write, CommonRegisterPayload::new);
public static final String PLAY_PHASE = "play"; public static final String PLAY_PHASE = "play";
public static final String CONFIGURATION_PHASE = "configuration"; public static final String CONFIGURATION_PHASE = "configuration";
public CommonRegisterPayload(PacketByteBuf buf) { private CommonRegisterPayload(PacketByteBuf buf) {
this( this(
buf.readVarInt(), buf.readVarInt(),
buf.readString(), buf.readString(),
@ -37,7 +39,6 @@ public record CommonRegisterPayload(int version, String phase, Set<Identifier> c
); );
} }
@Override
public void write(PacketByteBuf buf) { public void write(PacketByteBuf buf) {
buf.writeVarInt(version); buf.writeVarInt(version);
buf.writeString(phase); buf.writeString(phase);
@ -45,7 +46,7 @@ public record CommonRegisterPayload(int version, String phase, Set<Identifier> c
} }
@Override @Override
public Identifier id() { public Id<CommonRegisterPayload> getId() {
return PACKET_ID; return ID;
} }
} }

View file

@ -17,23 +17,23 @@
package net.fabricmc.fabric.impl.networking; package net.fabricmc.fabric.impl.networking;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
public record CommonVersionPayload(int[] versions) implements CustomPayload { public record CommonVersionPayload(int[] versions) implements CustomPayload {
public static final Identifier PACKET_ID = new Identifier("c", "version"); public static final PacketCodec<PacketByteBuf, CommonVersionPayload> CODEC = CustomPayload.codecOf(CommonVersionPayload::write, CommonVersionPayload::new);
public static final CustomPayload.Id<CommonVersionPayload> ID = CustomPayload.id("c:version");
public CommonVersionPayload(PacketByteBuf buf) { private CommonVersionPayload(PacketByteBuf buf) {
this(buf.readIntArray()); this(buf.readIntArray());
} }
@Override
public void write(PacketByteBuf buf) { public void write(PacketByteBuf buf) {
buf.writeIntArray(versions); buf.writeIntArray(versions);
} }
@Override @Override
public Identifier id() { public Id<? extends CustomPayload> getId() {
return PACKET_ID; return ID;
} }
} }

View file

@ -14,16 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package net.fabricmc.fabric.impl.container; package net.fabricmc.fabric.impl.networking;
/** import net.minecraft.network.PacketByteBuf;
* This is a interface that is present on a ServerPlayerEntity, it allows access to the sync id. import net.minecraft.network.packet.CustomPayload;
*/ import net.minecraft.util.Identifier;
public interface ServerPlayerEntitySyncHook {
/** public interface CustomPayloadTypeProvider<B extends PacketByteBuf> {
* Gets and sets the new player sync id, and returns the new value. CustomPayload.Type<B, ? extends CustomPayload> get(B packetByteBuf, Identifier identifier);
*
* @return the new sync id of the player
*/
int fabric_incrementSyncId();
} }

View file

@ -14,7 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package net.fabricmc.fabric.impl.networking.payload; package net.fabricmc.fabric.impl.networking;
public sealed interface ResolvedPayload extends ResolvablePayload permits TypedPayload, UntypedPayload { import net.minecraft.network.PacketByteBuf;
public interface FabricCustomPayloadPacketCodec<B extends PacketByteBuf> {
void fabric_setPacketCodecProvider(CustomPayloadTypeProvider<B> customPayloadTypeProvider);
} }

View file

@ -1,63 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking;
import java.util.Objects;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.Packet;
/**
* We still need to support {@link GenericFutureListener} so we use this disguise impl {@link PacketCallbacks}
* to get our {@link GenericFutureListener} to into {@link ClientConnection}.
*/
public final class GenericFutureListenerHolder implements PacketCallbacks {
private final GenericFutureListener<? extends Future<? super Void>> delegate;
private GenericFutureListenerHolder(GenericFutureListener<? extends Future<? super Void>> delegate) {
this.delegate = Objects.requireNonNull(delegate);
}
@Nullable
public static GenericFutureListenerHolder create(@Nullable GenericFutureListener<? extends Future<? super Void>> delegate) {
if (delegate == null) {
return null;
}
return new GenericFutureListenerHolder(delegate);
}
public GenericFutureListener<? extends Future<? super Void>> getDelegate() {
return delegate;
}
@Override
public void onSuccess() {
throw new AssertionError("Should not be called");
}
@Nullable
@Override
public Packet<?> getFailurePacket() {
throw new AssertionError("Should not be called");
}
}

View file

@ -29,25 +29,31 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.NetworkSide;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
public final class GlobalReceiverRegistry<H> { public final class GlobalReceiverRegistry<H> {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalReceiverRegistry.class); private static final Logger LOGGER = LoggerFactory.getLogger(GlobalReceiverRegistry.class);
private final NetworkState state; private final NetworkSide side;
private final NetworkPhase phase;
@Nullable
private final PayloadTypeRegistryImpl<?> payloadTypeRegistry;
private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<Identifier, H> handlers; private final Map<Identifier, H> handlers = new HashMap<>();
private final Set<AbstractNetworkAddon<H>> trackedAddons = new HashSet<>(); private final Set<AbstractNetworkAddon<H>> trackedAddons = new HashSet<>();
public GlobalReceiverRegistry(NetworkState state) { public GlobalReceiverRegistry(NetworkSide side, NetworkPhase phase, @Nullable PayloadTypeRegistryImpl<?> payloadTypeRegistry) {
this(state, new HashMap<>()); // sync map should be fine as there is little read write competitions this.side = side;
} this.phase = phase;
this.payloadTypeRegistry = payloadTypeRegistry;
public GlobalReceiverRegistry(NetworkState state, Map<Identifier, H> map) { if (payloadTypeRegistry != null) {
this.state = state; assert phase == payloadTypeRegistry.getPhase();
this.handlers = map; assert side == payloadTypeRegistry.getSide();
}
} }
@Nullable @Nullable
@ -70,6 +76,8 @@ public final class GlobalReceiverRegistry<H> {
throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName)); throw new IllegalArgumentException(String.format("Cannot register handler for reserved channel with name \"%s\"", channelName));
} }
assertPayloadType(channelName);
Lock lock = this.lock.writeLock(); Lock lock = this.lock.writeLock();
lock.lock(); lock.lock();
@ -166,7 +174,7 @@ public final class GlobalReceiverRegistry<H> {
*/ */
private void logTrackedAddonSize() { private void logTrackedAddonSize() {
if (LOGGER.isTraceEnabled() && this.trackedAddons.size() > 1) { if (LOGGER.isTraceEnabled() && this.trackedAddons.size() > 1) {
LOGGER.trace("{} receiver registry tracks {} addon instances", state.getId(), trackedAddons.size()); LOGGER.trace("{} receiver registry tracks {} addon instances", phase.getId(), trackedAddons.size());
} }
} }
@ -200,7 +208,17 @@ public final class GlobalReceiverRegistry<H> {
} }
} }
public NetworkState getState() { public void assertPayloadType(Identifier channelName) {
return state; if (payloadTypeRegistry == null) {
return;
}
if (payloadTypeRegistry.get(channelName) == null) {
throw new IllegalArgumentException(String.format("Cannot register handler as no payload type has been registered with name \"%s\" for %s %s", channelName, side, phase));
}
}
public NetworkPhase getPhase() {
return phase;
} }
} }

View file

@ -21,23 +21,12 @@ import org.slf4j.LoggerFactory;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
import net.fabricmc.loader.api.FabricLoader;
public final class NetworkingImpl { public final class NetworkingImpl {
public static final String MOD_ID = "fabric-networking-api-v1"; public static final String MOD_ID = "fabric-networking-api-v1";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
/**
* Force {@link TypedPayload} to be serialized into {@link UntypedPayload}, mimicking remote connection.
*
* <p>Defaults to {@code true} in dev env and {@code false} in production.
*/
public static final boolean FORCE_PACKET_SERIALIZATION = Boolean.parseBoolean(System.getProperty(
"fabric-api.networking.force-packet-serialization",
Boolean.toString(FabricLoader.getInstance().isDevelopmentEnvironment())));
/** /**
* Id of packet used to register supported channels. * Id of packet used to register supported channels.
*/ */
@ -48,15 +37,18 @@ public final class NetworkingImpl {
*/ */
public static final Identifier UNREGISTER_CHANNEL = new Identifier("minecraft", "unregister"); public static final Identifier UNREGISTER_CHANNEL = new Identifier("minecraft", "unregister");
public static final ThreadLocal<Boolean> FACTORY_RETAIN = ThreadLocal.withInitial(() -> Boolean.FALSE);
public static boolean isReservedCommonChannel(Identifier channelName) { public static boolean isReservedCommonChannel(Identifier channelName) {
return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL); return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL);
} }
static { public static void init() {
if (FORCE_PACKET_SERIALIZATION) { PayloadTypeRegistry.configurationS2C().register(RegistrationPayload.REGISTER, RegistrationPayload.REGISTER_CODEC);
LOGGER.info("Force Packet Serialization is enabled to mimic remote connection on single player, this is the default behaviour on dev env. Add -Dfabric-api.networking.force-packet-serialization=false JVM arg to disable it."); PayloadTypeRegistry.configurationS2C().register(RegistrationPayload.UNREGISTER, RegistrationPayload.UNREGISTER_CODEC);
} PayloadTypeRegistry.configurationC2S().register(RegistrationPayload.REGISTER, RegistrationPayload.REGISTER_CODEC);
PayloadTypeRegistry.configurationC2S().register(RegistrationPayload.UNREGISTER, RegistrationPayload.UNREGISTER_CODEC);
PayloadTypeRegistry.playS2C().register(RegistrationPayload.REGISTER, RegistrationPayload.REGISTER_CODEC);
PayloadTypeRegistry.playS2C().register(RegistrationPayload.UNREGISTER, RegistrationPayload.UNREGISTER_CODEC);
PayloadTypeRegistry.playC2S().register(RegistrationPayload.REGISTER, RegistrationPayload.REGISTER_CODEC);
PayloadTypeRegistry.playC2S().register(RegistrationPayload.UNREGISTER, RegistrationPayload.UNREGISTER_CODEC);
} }
} }

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
public class PayloadTypeRegistryImpl<B extends PacketByteBuf> implements PayloadTypeRegistry<B> {
public static final PayloadTypeRegistryImpl<PacketByteBuf> CONFIGURATION_C2S = new PayloadTypeRegistryImpl<>(NetworkPhase.CONFIGURATION, NetworkSide.SERVERBOUND);
public static final PayloadTypeRegistryImpl<PacketByteBuf> CONFIGURATION_S2C = new PayloadTypeRegistryImpl<>(NetworkPhase.CONFIGURATION, NetworkSide.CLIENTBOUND);
public static final PayloadTypeRegistryImpl<RegistryByteBuf> PLAY_C2S = new PayloadTypeRegistryImpl<>(NetworkPhase.PLAY, NetworkSide.SERVERBOUND);
public static final PayloadTypeRegistryImpl<RegistryByteBuf> PLAY_S2C = new PayloadTypeRegistryImpl<>(NetworkPhase.PLAY, NetworkSide.CLIENTBOUND);
private final Map<Identifier, CustomPayload.Type<B, ? extends CustomPayload>> packetTypes = new HashMap<>();
private final NetworkPhase state;
private final NetworkSide side;
private PayloadTypeRegistryImpl(NetworkPhase state, NetworkSide side) {
this.state = state;
this.side = side;
}
@Override
public <T extends CustomPayload> CustomPayload.Type<? super B, T> register(CustomPayload.Id<T> id, PacketCodec<? super B, T> codec) {
Objects.requireNonNull(id, "id");
Objects.requireNonNull(codec, "codec");
final CustomPayload.Type<B, T> payloadType = new CustomPayload.Type<>(id, codec.cast());
if (packetTypes.containsKey(id.id())) {
throw new IllegalArgumentException("Packet type " + id + " is already registered!");
}
packetTypes.put(id.id(), payloadType);
return payloadType;
}
@Nullable
public CustomPayload.Type<B, ? extends CustomPayload> get(Identifier id) {
return packetTypes.get(id);
}
@Nullable
public <T extends CustomPayload> CustomPayload.Type<B, T> get(CustomPayload.Id<T> id) {
//noinspection unchecked
return (CustomPayload.Type<B, T>) packetTypes.get(id.id());
}
public NetworkPhase getPhase() {
return state;
}
public NetworkSide getSide() {
return side;
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.netty.util.AsciiString;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
public record RegistrationPayload(Id<RegistrationPayload> id, List<Identifier> channels) implements CustomPayload {
public static final CustomPayload.Id<RegistrationPayload> REGISTER = new CustomPayload.Id<>(NetworkingImpl.REGISTER_CHANNEL);
public static final CustomPayload.Id<RegistrationPayload> UNREGISTER = new CustomPayload.Id<>(NetworkingImpl.UNREGISTER_CHANNEL);
public static final PacketCodec<PacketByteBuf, RegistrationPayload> REGISTER_CODEC = codec(REGISTER);
public static final PacketCodec<PacketByteBuf, RegistrationPayload> UNREGISTER_CODEC = codec(UNREGISTER);
private RegistrationPayload(Id<RegistrationPayload> id, PacketByteBuf buf) {
this(id, read(buf));
}
private void write(PacketByteBuf buf) {
boolean first = true;
for (Identifier channel : channels) {
if (first) {
first = false;
} else {
buf.writeByte(0);
}
buf.writeBytes(channel.toString().getBytes(StandardCharsets.US_ASCII));
}
}
private static List<Identifier> read(PacketByteBuf buf) {
List<Identifier> ids = new ArrayList<>();
StringBuilder active = new StringBuilder();
while (buf.isReadable()) {
byte b = buf.readByte();
if (b != 0) {
active.append(AsciiString.b2c(b));
} else {
addId(ids, active);
active = new StringBuilder();
}
}
addId(ids, active);
return Collections.unmodifiableList(ids);
}
private static void addId(List<Identifier> ids, StringBuilder sb) {
String literal = sb.toString();
try {
ids.add(new Identifier(literal));
} catch (InvalidIdentifierException ex) {
NetworkingImpl.LOGGER.warn("Received invalid channel identifier \"{}\"", literal);
}
}
@Override
public Id<? extends CustomPayload> getId() {
return id;
}
private static PacketCodec<PacketByteBuf, RegistrationPayload> codec(Id<RegistrationPayload> id) {
return CustomPayload.codecOf(RegistrationPayload::write, buf -> new RegistrationPayload(id, buf));
}
}

View file

@ -17,7 +17,6 @@
package net.fabricmc.fabric.impl.networking.payload; package net.fabricmc.fabric.impl.networking.payload;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
@ -35,18 +34,6 @@ public class PayloadHelper {
return newBuf; return newBuf;
} }
public static ResolvablePayload readCustom(Identifier id, PacketByteBuf buf, int maxSize, boolean retain) {
assertSize(buf, maxSize);
if (retain) {
RetainedPayload payload = new RetainedPayload(id, PacketByteBufs.retainedSlice(buf));
buf.skipBytes(buf.readableBytes());
return payload;
} else {
return new UntypedPayload(id, read(buf, maxSize));
}
}
private static void assertSize(PacketByteBuf buf, int maxSize) { private static void assertSize(PacketByteBuf buf, int maxSize) {
int size = buf.readableBytes(); int size = buf.readableBytes();

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking.payload;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.packet.CustomPayload;
import net.fabricmc.fabric.api.networking.v1.PacketType;
public sealed interface ResolvablePayload extends CustomPayload permits ResolvedPayload, RetainedPayload {
/**
* Resolve the payload to one of the resolved types.
*
* @return {@link UntypedPayload} if type is {@code null}, {@link TypedPayload} if otherwise.
*/
ResolvedPayload resolve(@Nullable PacketType<?> type);
/**
* @param type the packet type, if it has any
* @param actual the public handler that exposed to API consumer
* @param internal the internal handler
*/
record Handler<H>(@Nullable PacketType<?> type, Object actual, H internal) {
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking.payload;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketType;
public record RetainedPayload(Identifier id, PacketByteBuf buf) implements ResolvablePayload {
@Override
public ResolvedPayload resolve(@Nullable PacketType<?> type) {
try {
if (type == null) {
PacketByteBuf copy = PacketByteBufs.create();
copy.writeBytes(buf);
return new UntypedPayload(this.id, copy);
} else {
TypedPayload typed = new TypedPayload(type.read(buf));
int dangling = buf.readableBytes();
if (dangling > 0) {
throw new IllegalStateException("Found " + dangling + " extra bytes when reading packet " + id);
}
return typed;
}
} finally {
buf.release();
}
}
@Override
public void write(PacketByteBuf buf) {
throw new UnsupportedOperationException("RetainedPayload shouldn't be used to send");
}
}

View file

@ -1,49 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking.payload;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketType;
public record TypedPayload(FabricPacket packet) implements ResolvedPayload {
@Override
public ResolvedPayload resolve(@Nullable PacketType<?> type) {
if (type == null) {
PacketByteBuf buf = PacketByteBufs.create();
write(buf);
return new UntypedPayload(packet.getType().getId(), buf);
} else {
return this;
}
}
@Override
public void write(PacketByteBuf buf) {
packet.write(buf);
}
@Override
public Identifier id() {
return packet.getType().getId();
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.networking.payload;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketType;
public record UntypedPayload(Identifier id, PacketByteBuf buffer) implements ResolvedPayload {
@Override
public ResolvedPayload resolve(@Nullable PacketType<?> type) {
if (type == null) {
return this;
} else {
PacketByteBuf copy = PacketByteBufs.copy(buffer);
TypedPayload typed = new TypedPayload(type.read(copy));
int dangling = copy.readableBytes();
if (dangling > 0) {
throw new IllegalStateException("Found " + dangling + " extra bytes when reading packet " + id);
}
return typed;
}
}
@Override
public void write(PacketByteBuf buf) {
buf.writeBytes(buffer.copy());
}
@Override
public PacketByteBuf buffer() {
return PacketByteBufs.copy(buffer);
}
}

View file

@ -19,39 +19,40 @@ package net.fabricmc.fabric.impl.networking.server;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket; import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler; import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.S2CConfigurationChannelEvents; import net.fabricmc.fabric.api.networking.v1.S2CConfigurationChannelEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload; import net.fabricmc.fabric.impl.networking.RegistrationPayload;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.networking.accessor.ServerCommonNetworkHandlerAccessor;
public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ServerConfigurationNetworkAddon.Handler> { public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetworkAddon<ServerConfigurationNetworking.ConfigurationPacketHandler<?>> {
private final ServerConfigurationNetworkHandler handler; private final ServerConfigurationNetworkHandler handler;
private final MinecraftServer server; private final MinecraftServer server;
private final ServerConfigurationNetworking.Context context;
private RegisterState registerState = RegisterState.NOT_SENT; private RegisterState registerState = RegisterState.NOT_SENT;
public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) { public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName()); super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName());
this.handler = handler; this.handler = handler;
this.server = server; this.server = server;
this.context = new ContextImpl(handler, this);
// Must register pending channels via lateinit // Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.CONFIGURATION); this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION);
} }
@Override @Override
@ -84,7 +85,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
} }
@Override @Override
protected void receiveRegistration(boolean register, ResolvablePayload resolvable) { protected void receiveRegistration(boolean register, RegistrationPayload resolvable) {
super.receiveRegistration(register, resolvable); super.receiveRegistration(register, resolvable);
if (register && registerState == RegisterState.SENT) { if (register && registerState == RegisterState.SENT) {
@ -103,8 +104,8 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
} }
@Override @Override
protected void receive(Handler handler, ResolvedPayload payload) { protected void receive(ServerConfigurationNetworking.ConfigurationPacketHandler<?> handler, CustomPayload payload) {
handler.receive(this.server, this.handler, payload, this); ((ServerConfigurationNetworking.ConfigurationPacketHandler) handler).receive(payload, this.context);
} }
// impl details // impl details
@ -115,12 +116,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
} }
@Override @Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) { public Packet<?> createPacket(CustomPayload packet) {
return ServerPlayNetworking.createS2CPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ServerPlayNetworking.createS2CPacket(packet); return ServerPlayNetworking.createS2CPacket(packet);
} }
@ -138,10 +134,10 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleRegistration(Identifier channelName) { protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel // If we can already send packets, immediately send the register packet for this channel
if (this.registerState != RegisterState.NOT_SENT) { if (this.registerState != RegisterState.NOT_SENT) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); RegistrationPayload registrationPayload = this.createRegistrationPayload(RegistrationPayload.REGISTER, Collections.singleton(channelName));
if (buf != null) { if (registrationPayload != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); this.sendPacket(registrationPayload);
} }
} }
} }
@ -150,10 +146,10 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleUnregistration(Identifier channelName) { protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel // If we can already send packets, immediately send the unregister packet for this channel
if (this.registerState != RegisterState.NOT_SENT) { if (this.registerState != RegisterState.NOT_SENT) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); RegistrationPayload registrationPayload = this.createRegistrationPayload(RegistrationPayload.UNREGISTER, Collections.singleton(channelName));
if (buf != null) { if (registrationPayload != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); this.sendPacket(registrationPayload);
} }
} }
} }
@ -184,7 +180,6 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
return (ChannelInfoHolder) ((ServerCommonNetworkHandlerAccessor) handler).getConnection(); return (ChannelInfoHolder) ((ServerCommonNetworkHandlerAccessor) handler).getConnection();
} }
public interface Handler { private record ContextImpl(ServerConfigurationNetworkHandler networkHandler, PacketSender responseSender) implements ServerConfigurationNetworking.Context {
void receive(MinecraftServer server, ServerConfigurationNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
} }
} }

View file

@ -26,33 +26,31 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket; import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginCompressionS2CPacket; import net.minecraft.network.packet.s2c.login.LoginCompressionS2CPacket;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket; import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.LoginPacketSender;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon; import net.fabricmc.fabric.impl.networking.AbstractNetworkAddon;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
import net.fabricmc.fabric.impl.networking.payload.FabricPacketLoginQueryRequestPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload; import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryRequestPayload;
import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse; import net.fabricmc.fabric.impl.networking.payload.PacketByteBufLoginQueryResponse;
import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor; import net.fabricmc.fabric.mixin.networking.accessor.ServerLoginNetworkHandlerAccessor;
public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLoginNetworking.LoginQueryResponseHandler> implements PacketSender { public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLoginNetworking.LoginQueryResponseHandler> implements LoginPacketSender {
private final ClientConnection connection; private final ClientConnection connection;
private final ServerLoginNetworkHandler handler; private final ServerLoginNetworkHandler handler;
private final MinecraftServer server; private final MinecraftServer server;
@ -160,23 +158,17 @@ public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLo
return true; return true;
} }
@Override
public Packet<?> createPacket(CustomPayload packet) {
throw new UnsupportedOperationException("Cannot send CustomPayload during login");
}
@Override @Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) { public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
int queryId = this.queryIdFactory.nextId(); int queryId = this.queryIdFactory.nextId();
return new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf)); return new LoginQueryRequestS2CPacket(queryId, new PacketByteBufLoginQueryRequestPayload(channelName, buf));
} }
@Override
public Packet<?> createPacket(FabricPacket packet) {
int queryId = this.queryIdFactory.nextId();
return new LoginQueryRequestS2CPacket(queryId, new FabricPacketLoginQueryRequestPayload(packet));
}
@Override
public void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends io.netty.util.concurrent.Future<? super Void>> callback) {
sendPacket(packet, GenericFutureListenerHolder.create(callback));
}
@Override @Override
public void sendPacket(Packet<?> packet, PacketCallbacks callback) { public void sendPacket(Packet<?> packet, PacketCallbacks callback) {
Objects.requireNonNull(packet, "Packet cannot be null"); Objects.requireNonNull(packet, "Packet cannot be null");
@ -184,6 +176,13 @@ public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLo
this.connection.send(packet, callback); this.connection.send(packet, callback);
} }
@Override
public void disconnect(Text disconnectReason) {
Objects.requireNonNull(disconnectReason, "Disconnect reason cannot be null");
this.connection.disconnect(disconnectReason);
}
public void registerOutgoingPacket(LoginQueryRequestS2CPacket packet) { public void registerOutgoingPacket(LoginQueryRequestS2CPacket packet) {
this.channels.put(packet.queryId(), packet.payload().id()); this.channels.put(packet.queryId(), packet.payload().id());
} }

View file

@ -18,30 +18,27 @@ package net.fabricmc.fabric.impl.networking.server;
import java.util.Objects; import java.util.Objects;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.NetworkSide;
import net.minecraft.network.listener.ClientCommonPacketListener; import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.minecraft.server.network.ServerConfigurationNetworkHandler; import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
public final class ServerNetworkingImpl { public final class ServerNetworkingImpl {
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN); public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkSide.SERVERBOUND, NetworkPhase.LOGIN, null);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION); public static final GlobalReceiverRegistry<ServerConfigurationNetworking.ConfigurationPacketHandler<?>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkSide.SERVERBOUND, NetworkPhase.CONFIGURATION, PayloadTypeRegistryImpl.CONFIGURATION_C2S);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ServerPlayNetworkAddon.Handler>> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY); public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayPayloadHandler<?>> PLAY = new GlobalReceiverRegistry<>(NetworkSide.SERVERBOUND, NetworkPhase.PLAY, PayloadTypeRegistryImpl.PLAY_C2S);
public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) { public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) {
return (ServerPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); return (ServerPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
@ -55,16 +52,9 @@ public final class ServerNetworkingImpl {
return (ServerConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon(); return (ServerConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
} }
public static Packet<ClientCommonPacketListener> createS2CPacket(Identifier channel, PacketByteBuf buf) { public static Packet<ClientCommonPacketListener> createS2CPacket(CustomPayload payload) {
return new CustomPayloadS2CPacket(new UntypedPayload(channel, buf)); Objects.requireNonNull(payload, "Payload cannot be null");
} Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
public static Packet<ClientCommonPacketListener> createS2CPacket(FabricPacket packet) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
ResolvedPayload payload = new TypedPayload(packet);
if (NetworkingImpl.FORCE_PACKET_SERIALIZATION) payload = payload.resolve(null);
return new CustomPayloadS2CPacket(payload); return new CustomPayloadS2CPacket(payload);
} }

View file

@ -20,15 +20,14 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents; import net.fabricmc.fabric.api.networking.v1.S2CPlayChannelEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
@ -36,20 +35,22 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon; import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload; import net.fabricmc.fabric.impl.networking.RegistrationPayload;
public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<ServerPlayNetworkAddon.Handler> { public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<ServerPlayNetworking.PlayPayloadHandler<?>> {
private final ServerPlayNetworkHandler handler; private final ServerPlayNetworkHandler handler;
private final MinecraftServer server; private final MinecraftServer server;
private boolean sentInitialRegisterPacket; private boolean sentInitialRegisterPacket;
private final ServerPlayNetworking.Context context;
public ServerPlayNetworkAddon(ServerPlayNetworkHandler handler, ClientConnection connection, MinecraftServer server) { public ServerPlayNetworkAddon(ServerPlayNetworkHandler handler, ClientConnection connection, MinecraftServer server) {
super(ServerNetworkingImpl.PLAY, connection, "ServerPlayNetworkAddon for " + handler.player.getDisplayName()); super(ServerNetworkingImpl.PLAY, connection, "ServerPlayNetworkAddon for " + handler.player.getDisplayName());
this.handler = handler; this.handler = handler;
this.server = server; this.server = server;
this.context = new ContextImpl(handler.player, this);
// Must register pending channels via lateinit // Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.PLAY); this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.PLAY);
} }
@Override @Override
@ -65,8 +66,10 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
} }
@Override @Override
protected void receive(Handler handler, ResolvedPayload payload) { protected void receive(ServerPlayNetworking.PlayPayloadHandler<?> payloadHandler, CustomPayload payload) {
handler.receive(this.server, this.handler.player, this.handler, payload, this); this.server.execute(() -> {
((ServerPlayNetworking.PlayPayloadHandler) payloadHandler).receive(payload, ServerPlayNetworkAddon.this.context);
});
} }
// impl details // impl details
@ -77,12 +80,7 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
} }
@Override @Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) { public Packet<?> createPacket(CustomPayload packet) {
return ServerPlayNetworking.createS2CPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
return ServerPlayNetworking.createS2CPacket(packet); return ServerPlayNetworking.createS2CPacket(packet);
} }
@ -100,10 +98,10 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
protected void handleRegistration(Identifier channelName) { protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel // If we can already send packets, immediately send the register packet for this channel
if (this.sentInitialRegisterPacket) { if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); RegistrationPayload registrationPayload = this.createRegistrationPayload(RegistrationPayload.REGISTER, Collections.singleton(channelName));
if (buf != null) { if (registrationPayload != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf); this.sendPacket(registrationPayload);
} }
} }
} }
@ -112,10 +110,10 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
protected void handleUnregistration(Identifier channelName) { protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel // If we can already send packets, immediately send the unregister packet for this channel
if (this.sentInitialRegisterPacket) { if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName)); RegistrationPayload registrationPayload = this.createRegistrationPayload(RegistrationPayload.UNREGISTER, Collections.singleton(channelName));
if (buf != null) { if (registrationPayload != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf); this.sendPacket(registrationPayload);
} }
} }
} }
@ -130,7 +128,6 @@ public final class ServerPlayNetworkAddon extends AbstractChanneledNetworkAddon<
return NetworkingImpl.isReservedCommonChannel(channelName); return NetworkingImpl.isReservedCommonChannel(channelName);
} }
public interface Handler { private record ContextImpl(ServerPlayerEntity player, PacketSender responseSender) implements ServerPlayNetworking.Context {
void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
} }
} }

View file

@ -21,8 +21,6 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@ -32,9 +30,9 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.NetworkSide; import net.minecraft.network.NetworkSide;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketCallbacks; import net.minecraft.network.PacketCallbacks;
@ -45,7 +43,6 @@ import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.DisconnectPacketSource; import net.fabricmc.fabric.impl.networking.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.PacketCallbackListener; import net.fabricmc.fabric.impl.networking.PacketCallbackListener;
@ -61,7 +58,7 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder {
public abstract void send(Packet<?> packet, @Nullable PacketCallbacks arg); public abstract void send(Packet<?> packet, @Nullable PacketCallbacks arg);
@Unique @Unique
private Map<NetworkState, Collection<Identifier>> playChannels; private Map<NetworkPhase, Collection<Identifier>> playChannels;
@Inject(method = "<init>", at = @At("RETURN")) @Inject(method = "<init>", at = @At("RETURN"))
private void initAddedFields(NetworkSide side, CallbackInfo ci) { private void initAddedFields(NetworkSide side, CallbackInfo ci) {
@ -89,7 +86,7 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder {
} }
@Inject(method = "setPacketListener", at = @At("HEAD")) @Inject(method = "setPacketListener", at = @At("HEAD"))
private void unwatchAddon(PacketListener packetListener, CallbackInfo ci) { private void unwatchAddon(NetworkState<?> state, PacketListener listener, CallbackInfo ci) {
if (this.packetListener instanceof NetworkHandlerExtensions oldListener) { if (this.packetListener instanceof NetworkHandlerExtensions oldListener) {
oldListener.getAddon().endSession(); oldListener.getAddon().endSession();
} }
@ -109,17 +106,8 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder {
} }
} }
@Inject(method = "sendInternal", at = @At(value = "INVOKE", target = "Lio/netty/channel/ChannelFuture;addListener(Lio/netty/util/concurrent/GenericFutureListener;)Lio/netty/channel/ChannelFuture;", remap = false), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
private void sendInternal(Packet<?> packet, @Nullable PacketCallbacks callbacks, boolean flush, CallbackInfo ci, ChannelFuture channelFuture) {
if (callbacks instanceof GenericFutureListenerHolder holder) {
channelFuture.addListener(holder.getDelegate());
channelFuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
ci.cancel();
}
}
@Override @Override
public Collection<Identifier> getPendingChannelsNames(NetworkState state) { public Collection<Identifier> fabric_getPendingChannelsNames(NetworkPhase state) {
return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>())); return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
} }
} }

View file

@ -16,33 +16,42 @@
package net.fabricmc.fabric.mixin.networking; package net.fabricmc.fabric.mixin.networking;
import org.spongepowered.asm.mixin.Final; import java.util.List;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.FabricCustomPayloadPacketCodec;
import net.fabricmc.fabric.impl.networking.payload.PayloadHelper; import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
@Mixin(CustomPayloadC2SPacket.class) @Mixin(CustomPayloadC2SPacket.class)
public class CustomPayloadC2SPacketMixin { public class CustomPayloadC2SPacketMixin {
@Shadow @WrapOperation(
@Final method = "<clinit>",
private static int MAX_PAYLOAD_SIZE; at = @At(
value = "INVOKE",
@Inject( target = "Lnet/minecraft/network/packet/CustomPayload;createCodec(Lnet/minecraft/network/packet/CustomPayload$CodecFactory;Ljava/util/List;)Lnet/minecraft/network/codec/PacketCodec;"
method = "readPayload", )
at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/c2s/common/CustomPayloadC2SPacket;readUnknownPayload(Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)Lnet/minecraft/network/packet/UnknownCustomPayload;"),
cancellable = true
) )
private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable<CustomPayload> cir) { private static PacketCodec<PacketByteBuf, CustomPayload> wrapCodec(CustomPayload.CodecFactory<PacketByteBuf> unknownCodecFactory, List<CustomPayload.Type<PacketByteBuf, ?>> types, Operation<PacketCodec<PacketByteBuf, CustomPayload>> original) {
cir.setReturnValue(PayloadHelper.readCustom(id, buf, MAX_PAYLOAD_SIZE, NetworkingImpl.FACTORY_RETAIN.get())); PacketCodec<PacketByteBuf, CustomPayload> codec = original.call(unknownCodecFactory, types);
FabricCustomPayloadPacketCodec<PacketByteBuf> fabricCodec = (FabricCustomPayloadPacketCodec<PacketByteBuf>) codec;
fabricCodec.fabric_setPacketCodecProvider((packetByteBuf, identifier) -> {
// CustomPayloadC2SPacket does not have a separate codec for play/configuration. We know if the packetByteBuf is a PacketByteBuf we are in the play phase.
if (packetByteBuf instanceof RegistryByteBuf) {
return (CustomPayload.Type<PacketByteBuf, ? extends CustomPayload>) (Object) PayloadTypeRegistryImpl.PLAY_C2S.get(identifier);
}
return PayloadTypeRegistryImpl.CONFIGURATION_C2S.get(identifier);
});
return codec;
} }
} }

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.networking;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.CustomPayloadTypeProvider;
import net.fabricmc.fabric.impl.networking.FabricCustomPayloadPacketCodec;
@Mixin(targets = "net/minecraft/network/packet/CustomPayload$1")
public abstract class CustomPayloadPacketCodecMixin<B extends PacketByteBuf> implements PacketCodec<B, CustomPayload>, FabricCustomPayloadPacketCodec<B> {
@Unique
private CustomPayloadTypeProvider<B> customPayloadTypeProvider;
@Override
public void fabric_setPacketCodecProvider(CustomPayloadTypeProvider<B> customPayloadTypeProvider) {
if (this.customPayloadTypeProvider != null) {
throw new IllegalStateException("Payload codec provider is already set!");
}
this.customPayloadTypeProvider = customPayloadTypeProvider;
}
@WrapOperation(method = {
"encode(Lnet/minecraft/network/PacketByteBuf;Lnet/minecraft/network/packet/CustomPayload$Id;Lnet/minecraft/network/packet/CustomPayload;)V",
"decode(Lnet/minecraft/network/PacketByteBuf;)Lnet/minecraft/network/packet/CustomPayload;"
}, at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/CustomPayload$1;getCodec(Lnet/minecraft/util/Identifier;)Lnet/minecraft/network/codec/PacketCodec;"))
private PacketCodec<B, ? extends CustomPayload> wrapGetCodec(@Coerce PacketCodec<B, CustomPayload> instance, Identifier identifier, Operation<PacketCodec<B, CustomPayload>> original, B packetByteBuf) {
if (customPayloadTypeProvider != null) {
CustomPayload.Type<B, ? extends CustomPayload> payloadType = customPayloadTypeProvider.get(packetByteBuf, identifier);
if (payloadType != null) {
return payloadType.codec();
}
}
return original.call(instance, identifier);
}
}

View file

@ -16,33 +16,51 @@
package net.fabricmc.fabric.mixin.networking; package net.fabricmc.fabric.mixin.networking;
import org.spongepowered.asm.mixin.Final; import java.util.List;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.NetworkingImpl; import net.fabricmc.fabric.impl.networking.FabricCustomPayloadPacketCodec;
import net.fabricmc.fabric.impl.networking.payload.PayloadHelper; import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
@Mixin(CustomPayloadS2CPacket.class) @Mixin(CustomPayloadS2CPacket.class)
public class CustomPayloadS2CPacketMixin { public class CustomPayloadS2CPacketMixin {
@Shadow @WrapOperation(
@Final method = "<clinit>",
private static int MAX_PAYLOAD_SIZE; at = @At(
value = "INVOKE",
@Inject( target = "Lnet/minecraft/network/packet/CustomPayload;createCodec(Lnet/minecraft/network/packet/CustomPayload$CodecFactory;Ljava/util/List;)Lnet/minecraft/network/codec/PacketCodec;",
method = "readPayload", ordinal = 0
at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/common/CustomPayloadS2CPacket;readUnknownPayload(Lnet/minecraft/util/Identifier;Lnet/minecraft/network/PacketByteBuf;)Lnet/minecraft/network/packet/UnknownCustomPayload;"), )
cancellable = true
) )
private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable<CustomPayload> cir) { private static PacketCodec<RegistryByteBuf, CustomPayload> wrapPlayCodec(CustomPayload.CodecFactory<RegistryByteBuf> unknownCodecFactory, List<CustomPayload.Type<RegistryByteBuf, ?>> types, Operation<PacketCodec<RegistryByteBuf, CustomPayload>> original) {
cir.setReturnValue(PayloadHelper.readCustom(id, buf, MAX_PAYLOAD_SIZE, NetworkingImpl.FACTORY_RETAIN.get())); PacketCodec<RegistryByteBuf, CustomPayload> codec = original.call(unknownCodecFactory, types);
FabricCustomPayloadPacketCodec<RegistryByteBuf> fabricCodec = (FabricCustomPayloadPacketCodec<RegistryByteBuf>) codec;
fabricCodec.fabric_setPacketCodecProvider((packetByteBuf, identifier) -> PayloadTypeRegistryImpl.PLAY_S2C.get(identifier));
return codec;
}
@WrapOperation(
method = "<clinit>",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/network/packet/CustomPayload;createCodec(Lnet/minecraft/network/packet/CustomPayload$CodecFactory;Ljava/util/List;)Lnet/minecraft/network/codec/PacketCodec;",
ordinal = 1
)
)
private static PacketCodec<PacketByteBuf, CustomPayload> wrapConfigCodec(CustomPayload.CodecFactory<PacketByteBuf> unknownCodecFactory, List<CustomPayload.Type<PacketByteBuf, ?>> types, Operation<PacketCodec<PacketByteBuf, CustomPayload>> original) {
PacketCodec<PacketByteBuf, CustomPayload> codec = original.call(unknownCodecFactory, types);
FabricCustomPayloadPacketCodec<PacketByteBuf> fabricCodec = (FabricCustomPayloadPacketCodec<PacketByteBuf>) codec;
fabricCodec.fabric_setPacketCodecProvider((packetByteBuf, identifier) -> PayloadTypeRegistryImpl.CONFIGURATION_S2C.get(identifier));
return codec;
} }
} }

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.networking;
import java.util.function.Function;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.RetainedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
@Mixin(targets = "net.minecraft.network.NetworkState$InternalPacketHandler")
public class NetworkStateInternalPacketHandlerMixin {
/**
* Only retain custom packet buffer to {@link RetainedPayload} on the receiving side,
* otherwise resolve to {@link UntypedPayload}.
*/
@ModifyVariable(method = "register", at = @At("HEAD"), argsOnly = true)
private Function<PacketByteBuf, Packet<?>> replaceCustomPayloadFactory(Function<PacketByteBuf, Packet<?>> original, Class<?> type) {
if (type == CustomPayloadC2SPacket.class) {
return buf -> {
try {
NetworkingImpl.FACTORY_RETAIN.set(true);
return new CustomPayloadC2SPacket(buf);
} finally {
NetworkingImpl.FACTORY_RETAIN.set(false);
}
};
} else if (type == CustomPayloadS2CPacket.class) {
return buf -> {
try {
NetworkingImpl.FACTORY_RETAIN.set(true);
return new CustomPayloadS2CPacket(buf);
} finally {
NetworkingImpl.FACTORY_RETAIN.set(false);
}
};
}
return original;
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.networking;
import com.llamalad7.mixinextras.sugar.Local;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.EncoderException;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.handler.PacketCodecDispatcher;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
@Mixin(PacketCodecDispatcher.class)
public abstract class PacketCodecDispatcherMixin<B extends ByteBuf, V, T> implements PacketCodec<B, V> {
// Add the custom payload id to the error message
@Inject(method = "encode(Lio/netty/buffer/ByteBuf;Ljava/lang/Object;)V", at = @At(value = "NEW", target = "(Ljava/lang/String;Ljava/lang/Throwable;)Lio/netty/handler/codec/EncoderException;"))
public void encode(B byteBuf, V packet, CallbackInfo ci, @Local(ordinal = 1) T packetId, @Local Exception e) {
CustomPayload payload = null;
if (packet instanceof CustomPayloadC2SPacket customPayloadC2SPacket) {
payload = customPayloadC2SPacket.payload();
} else if (packet instanceof CustomPayloadS2CPacket customPayloadS2CPacket) {
payload = customPayloadS2CPacket.payload();
}
if (payload != null && payload.getId() != null) {
throw new EncoderException("Failed to encode packet '%s' (%s)".formatted(packetId, payload.getId().id().toString()), e);
}
}
}

View file

@ -21,13 +21,12 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.c2s.common.CommonPongC2SPacket; import net.minecraft.network.packet.c2s.common.CommonPongC2SPacket;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket; import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.server.network.ServerCommonNetworkHandler; import net.minecraft.server.network.ServerCommonNetworkHandler;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions; import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.RetainedPayload;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon;
@ -35,23 +34,20 @@ import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon;
public abstract class ServerCommonNetworkHandlerMixin implements NetworkHandlerExtensions { public abstract class ServerCommonNetworkHandlerMixin implements NetworkHandlerExtensions {
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true) @Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
private void handleCustomPayloadReceivedAsync(CustomPayloadC2SPacket packet, CallbackInfo ci) { private void handleCustomPayloadReceivedAsync(CustomPayloadC2SPacket packet, CallbackInfo ci) {
if (packet.payload() instanceof ResolvablePayload payload) { final CustomPayload payload = packet.payload();
boolean handled;
if (getAddon() instanceof ServerPlayNetworkAddon addon) { boolean handled;
handled = addon.handle(payload);
} else if (getAddon() instanceof ServerConfigurationNetworkAddon addon) {
handled = addon.handle(payload);
} else {
throw new IllegalStateException("Unknown addon");
}
if (handled) { if (getAddon() instanceof ServerPlayNetworkAddon addon) {
ci.cancel(); handled = addon.handle(payload);
} else if (payload instanceof RetainedPayload retained) { } else if (getAddon() instanceof ServerConfigurationNetworkAddon addon) {
retained.buf().skipBytes(retained.buf().readableBytes()); handled = addon.handle(payload);
retained.buf().release(); } else {
} throw new IllegalStateException("Unknown addon");
}
if (handled) {
ci.cancel();
} }
} }

View file

@ -1,3 +1,2 @@
accessWidener v2 named accessWidener v2 named
accessible class net/minecraft/network/NetworkState$InternalPacketHandler

View file

@ -3,14 +3,15 @@
"package": "net.fabricmc.fabric.mixin.networking", "package": "net.fabricmc.fabric.mixin.networking",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"mixins": [ "mixins": [
"PacketCodecDispatcherMixin",
"ClientConnectionMixin", "ClientConnectionMixin",
"CommandManagerMixin", "CommandManagerMixin",
"CustomPayloadC2SPacketMixin", "CustomPayloadC2SPacketMixin",
"CustomPayloadS2CPacketMixin", "CustomPayloadS2CPacketMixin",
"CustomPayloadPacketCodecMixin",
"EntityTrackerEntryMixin", "EntityTrackerEntryMixin",
"LoginQueryRequestS2CPacketMixin", "LoginQueryRequestS2CPacketMixin",
"LoginQueryResponseC2SPacketMixin", "LoginQueryResponseC2SPacketMixin",
"NetworkStateInternalPacketHandlerMixin",
"PlayerManagerMixin", "PlayerManagerMixin",
"ServerCommonNetworkHandlerMixin", "ServerCommonNetworkHandlerMixin",
"ServerConfigurationNetworkHandlerMixin", "ServerConfigurationNetworkHandlerMixin",

View file

@ -17,7 +17,8 @@
], ],
"entrypoints": { "entrypoints": {
"main": [ "main": [
"net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init" "net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init",
"net.fabricmc.fabric.impl.networking.NetworkingImpl::init"
], ],
"client": [ "client": [
"net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl::clientInit" "net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl::clientInit"

View file

@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -41,8 +41,10 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import net.minecraft.client.network.ClientConfigurationNetworkHandler; import net.minecraft.client.network.ClientConfigurationNetworkHandler;
import net.minecraft.network.NetworkState; import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload; import net.minecraft.network.packet.CustomPayload;
import net.minecraft.server.network.ServerConfigurationNetworkHandler; import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -51,6 +53,7 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworkin
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder; import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.CommonPacketHandler; import net.fabricmc.fabric.impl.networking.CommonPacketHandler;
@ -59,11 +62,13 @@ import net.fabricmc.fabric.impl.networking.CommonRegisterPayload;
import net.fabricmc.fabric.impl.networking.CommonVersionPayload; import net.fabricmc.fabric.impl.networking.CommonVersionPayload;
import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl; import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon; import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl; import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
public class CommonPacketTests { public class CommonPacketTests {
private static final CustomPayload.Type<PacketByteBuf, CommonVersionPayload> VERSION_PAYLOAD_TYPE = new CustomPayload.Type<>(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
private static final CustomPayload.Type<PacketByteBuf, CommonRegisterPayload> REGISTER_PAYLOAD_TYPE = new CustomPayload.Type<>(CommonRegisterPayload.ID, CommonRegisterPayload.CODEC);
private PacketSender packetSender; private PacketSender packetSender;
private ChannelInfoHolder channelInfoHolder; private ChannelInfoHolder channelInfoHolder;
@ -73,16 +78,41 @@ public class CommonPacketTests {
private ServerConfigurationNetworkHandler serverNetworkHandler; private ServerConfigurationNetworkHandler serverNetworkHandler;
private ServerConfigurationNetworkAddon serverAddon; private ServerConfigurationNetworkAddon serverAddon;
private ClientConfigurationNetworking.Context clientContext;
private ServerConfigurationNetworking.Context serverContext;
@BeforeAll @BeforeAll
static void beforeAll() { static void beforeAll() {
CommonPacketsImpl.init(); CommonPacketsImpl.init();
ClientNetworkingImpl.clientInit(); ClientNetworkingImpl.clientInit();
// Register a receiver to send in the play registry response // Register the packet codec on both sides
ClientPlayNetworking.registerGlobalReceiver(new Identifier("fabric", "global_client"), (client, handler, buf, responseSender) -> { PayloadTypeRegistry.playS2C().register(TestPayload.ID, TestPayload.CODEC);
// Listen for the payload on the client
ClientPlayNetworking.registerGlobalReceiver(TestPayload.ID, (payload, context) -> {
System.out.println(payload.data());
}); });
} }
private record TestPayload(String data) implements CustomPayload {
static final CustomPayload.Id<TestPayload> ID = new CustomPayload.Id<>(new Identifier("fabric", "global_client"));
static final PacketCodec<RegistryByteBuf, TestPayload> CODEC = CustomPayload.codecOf(TestPayload::write, TestPayload::new);
TestPayload(RegistryByteBuf buf) {
this(buf.readString());
}
private void write(RegistryByteBuf buf) {
buf.writeString(data);
}
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
@BeforeEach @BeforeEach
void setUp() { void setUp() {
packetSender = mock(PacketSender.class); packetSender = mock(PacketSender.class);
@ -97,27 +127,41 @@ public class CommonPacketTests {
serverAddon = mock(ServerConfigurationNetworkAddon.class); serverAddon = mock(ServerConfigurationNetworkAddon.class);
when(ServerNetworkingImpl.getAddon(serverNetworkHandler)).thenReturn(serverAddon); when(ServerNetworkingImpl.getAddon(serverNetworkHandler)).thenReturn(serverAddon);
when(serverAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder); when(serverAddon.getChannelInfoHolder()).thenReturn(channelInfoHolder);
ClientNetworkingImpl.setClientConfigurationAddon(clientAddon);
clientContext = () -> packetSender;
serverContext = new ServerConfigurationNetworking.Context() {
@Override
public ServerConfigurationNetworkHandler networkHandler() {
return serverNetworkHandler;
}
@Override
public PacketSender responseSender() {
return packetSender;
}
};
} }
// Test handling the version packet on the client // Test handling the version packet on the client
@Test @Test
void handleVersionPacketClient() { void handleVersionPacketClient() {
ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonVersionPayload> packetHandler = (ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonVersionPayload>) ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
// Receive a packet from the server // Receive a packet from the server
PacketByteBuf buf = PacketByteBufs.create(); PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{1, 2, 3}); buf.writeIntArray(new int[]{1, 2, 3});
// The actual handler doesn't copy the buffer CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); packetHandler.receive(payload, clientContext);
actualHandler.receive(null, clientNetworkHandler, buf, packetSender);
// Assert the entire packet was read // Assert the entire packet was read
assertEquals(0, buf.readableBytes()); assertEquals(0, buf.readableBytes());
// Check the response we are sending back to the server // Check the response we are sending back to the server
PacketByteBuf response = readResponse(packetSender); PacketByteBuf response = readResponse(packetSender, VERSION_PAYLOAD_TYPE);
assertArrayEquals(new int[]{1}, response.readIntArray()); assertArrayEquals(new int[]{1}, response.readIntArray());
assertEquals(0, response.readableBytes()); assertEquals(0, response.readableBytes());
@ -127,7 +171,7 @@ public class CommonPacketTests {
// Test handling the version packet on the client, when the server sends unsupported versions // Test handling the version packet on the client, when the server sends unsupported versions
@Test @Test
void handleVersionPacketClientUnsupported() { void handleVersionPacketClientUnsupported() {
ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonVersionPayload> packetHandler = (ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonVersionPayload>) ClientNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
// Receive a packet from the server // Receive a packet from the server
@ -135,8 +179,8 @@ public class CommonPacketTests {
buf.writeIntArray(new int[]{2, 3}); // We only support version 1 buf.writeIntArray(new int[]{2, 3}); // We only support version 1
assertThrows(UnsupportedOperationException.class, () -> { assertThrows(UnsupportedOperationException.class, () -> {
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
actualHandler.receive(null, clientNetworkHandler, buf, packetSender); packetHandler.receive(payload, clientContext);
}); });
// Assert the entire packet was read // Assert the entire packet was read
@ -146,15 +190,15 @@ public class CommonPacketTests {
// Test handling the version packet on the server // Test handling the version packet on the server
@Test @Test
void handleVersionPacketServer() { void handleVersionPacketServer() {
ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); ServerConfigurationNetworking.ConfigurationPacketHandler<CommonVersionPayload> packetHandler = (ServerConfigurationNetworking.ConfigurationPacketHandler<CommonVersionPayload>) ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
// Receive a packet from the client // Receive a packet from the client
PacketByteBuf buf = PacketByteBufs.create(); PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{1, 2, 3}); buf.writeIntArray(new int[]{1, 2, 3});
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
actualHandler.receive(null, serverNetworkHandler, buf, null); packetHandler.receive(payload, serverContext);
// Assert the entire packet was read // Assert the entire packet was read
assertEquals(0, buf.readableBytes()); assertEquals(0, buf.readableBytes());
@ -164,7 +208,7 @@ public class CommonPacketTests {
// Test handling the version packet on the server unsupported version // Test handling the version packet on the server unsupported version
@Test @Test
void handleVersionPacketServerUnsupported() { void handleVersionPacketServerUnsupported() {
ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.PACKET_ID); ServerConfigurationNetworking.ConfigurationPacketHandler<CommonVersionPayload> packetHandler = (ServerConfigurationNetworking.ConfigurationPacketHandler<CommonVersionPayload>) ServerNetworkingImpl.CONFIGURATION.getHandler(CommonVersionPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
// Receive a packet from the client // Receive a packet from the client
@ -172,8 +216,8 @@ public class CommonPacketTests {
buf.writeIntArray(new int[]{3}); // Server only supports version 1 buf.writeIntArray(new int[]{3}); // Server only supports version 1
assertThrows(UnsupportedOperationException.class, () -> { assertThrows(UnsupportedOperationException.class, () -> {
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
actualHandler.receive(null, serverNetworkHandler, buf, null); packetHandler.receive(payload, serverContext);
}); });
// Assert the entire packet was read // Assert the entire packet was read
@ -183,7 +227,7 @@ public class CommonPacketTests {
// Test handing the play registry packet on the client configuration handler // Test handing the play registry packet on the client configuration handler
@Test @Test
void handlePlayRegistryClient() { void handlePlayRegistryClient() {
ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonRegisterPayload> packetHandler = (ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonRegisterPayload>) ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
when(clientAddon.getNegotiatedVersion()).thenReturn(1); when(clientAddon.getNegotiatedVersion()).thenReturn(1);
@ -194,15 +238,15 @@ public class CommonPacketTests {
buf.writeString("play"); // Target phase buf.writeString("play"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
actualHandler.receive(null, clientNetworkHandler, buf, packetSender); packetHandler.receive(payload, clientContext);
// Assert the entire packet was read // Assert the entire packet was read
assertEquals(0, buf.readableBytes()); assertEquals(0, buf.readableBytes());
assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY)); assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.fabric_getPendingChannelsNames(NetworkPhase.PLAY));
// Check the response we are sending back to the server // Check the response we are sending back to the server
PacketByteBuf response = readResponse(packetSender); PacketByteBuf response = readResponse(packetSender, REGISTER_PAYLOAD_TYPE);
assertEquals(1, response.readVarInt()); assertEquals(1, response.readVarInt());
assertEquals("play", response.readString()); assertEquals("play", response.readString());
assertIterableEquals(List.of(new Identifier("fabric", "global_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier)); assertIterableEquals(List.of(new Identifier("fabric", "global_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier));
@ -212,7 +256,7 @@ public class CommonPacketTests {
// Test handling the configuration registry packet on the client configuration handler // Test handling the configuration registry packet on the client configuration handler
@Test @Test
void handleConfigurationRegistryClient() { void handleConfigurationRegistryClient() {
ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler> packetHandler = ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonRegisterPayload> packetHandler = (ClientConfigurationNetworking.ConfigurationPayloadHandler<CommonRegisterPayload>) ClientNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
when(clientAddon.getNegotiatedVersion()).thenReturn(1); when(clientAddon.getNegotiatedVersion()).thenReturn(1);
@ -224,15 +268,15 @@ public class CommonPacketTests {
buf.writeString("configuration"); // Target phase buf.writeString("configuration"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
actualHandler.receive(null, clientNetworkHandler, buf, packetSender); packetHandler.receive(payload, clientContext);
// Assert the entire packet was read // Assert the entire packet was read
assertEquals(0, buf.readableBytes()); assertEquals(0, buf.readableBytes());
verify(clientAddon, times(1)).onCommonRegisterPacket(any()); verify(clientAddon, times(1)).onCommonRegisterPacket(any());
// Check the response we are sending back to the server // Check the response we are sending back to the server
PacketByteBuf response = readResponse(packetSender); PacketByteBuf response = readResponse(packetSender, REGISTER_PAYLOAD_TYPE);
assertEquals(1, response.readVarInt()); assertEquals(1, response.readVarInt());
assertEquals("configuration", response.readString()); assertEquals("configuration", response.readString());
assertIterableEquals(List.of(new Identifier("fabric", "global_configuration_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier)); assertIterableEquals(List.of(new Identifier("fabric", "global_configuration_client")), response.readCollection(HashSet::new, PacketByteBuf::readIdentifier));
@ -242,7 +286,7 @@ public class CommonPacketTests {
// Test handing the play registry packet on the server configuration handler // Test handing the play registry packet on the server configuration handler
@Test @Test
void handlePlayRegistryServer() { void handlePlayRegistryServer() {
ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); ServerConfigurationNetworking.ConfigurationPacketHandler<CommonRegisterPayload> packetHandler = (ServerConfigurationNetworking.ConfigurationPacketHandler<CommonRegisterPayload>) ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
when(serverAddon.getNegotiatedVersion()).thenReturn(1); when(serverAddon.getNegotiatedVersion()).thenReturn(1);
@ -253,18 +297,18 @@ public class CommonPacketTests {
buf.writeString("play"); // Target phase buf.writeString("play"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
actualHandler.receive(null, serverNetworkHandler, buf, null); packetHandler.receive(payload, serverContext);
// Assert the entire packet was read // Assert the entire packet was read
assertEquals(0, buf.readableBytes()); assertEquals(0, buf.readableBytes());
assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.getPendingChannelsNames(NetworkState.PLAY)); assertIterableEquals(List.of(new Identifier("fabric", "test")), channelInfoHolder.fabric_getPendingChannelsNames(NetworkPhase.PLAY));
} }
// Test handing the configuration registry packet on the server configuration handler // Test handing the configuration registry packet on the server configuration handler
@Test @Test
void handleConfigurationRegistryServer() { void handleConfigurationRegistryServer() {
ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler> packetHandler = ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.PACKET_ID); ServerConfigurationNetworking.ConfigurationPacketHandler<CommonRegisterPayload> packetHandler = (ServerConfigurationNetworking.ConfigurationPacketHandler<CommonRegisterPayload>) ServerNetworkingImpl.CONFIGURATION.getHandler(CommonRegisterPayload.ID.id());
assertNotNull(packetHandler); assertNotNull(packetHandler);
when(serverAddon.getNegotiatedVersion()).thenReturn(1); when(serverAddon.getNegotiatedVersion()).thenReturn(1);
@ -275,8 +319,8 @@ public class CommonPacketTests {
buf.writeString("configuration"); // Target phase buf.writeString("configuration"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier); buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual(); CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
actualHandler.receive(null, serverNetworkHandler, buf, null); packetHandler.receive(payload, serverContext);
// Assert the entire packet was read // Assert the entire packet was read
assertEquals(0, buf.readableBytes()); assertEquals(0, buf.readableBytes());
@ -318,12 +362,13 @@ public class CommonPacketTests {
assertEquals(3, CommonPacketsImpl.getHighestCommonVersion(a, b)); assertEquals(3, CommonPacketsImpl.getHighestCommonVersion(a, b));
} }
private static PacketByteBuf readResponse(PacketSender packetSender) { private static <T extends CustomPayload> PacketByteBuf readResponse(PacketSender packetSender, CustomPayload.Type<PacketByteBuf, T> type) {
ArgumentCaptor<CustomPayload> responseCaptor = ArgumentCaptor.forClass(CustomPayload.class); ArgumentCaptor<CustomPayload> responseCaptor = ArgumentCaptor.forClass(CustomPayload.class);
verify(packetSender, times(1)).sendPacket(responseCaptor.capture()); verify(packetSender, times(1)).sendPacket(responseCaptor.capture());
PacketByteBuf buf = PacketByteBufs.create(); final T payload = (T) responseCaptor.getValue();
responseCaptor.getValue().write(buf); final PacketByteBuf buf = PacketByteBufs.create();
type.codec().encode(buf, payload);
return buf; return buf;
} }
@ -335,10 +380,10 @@ public class CommonPacketTests {
} }
private static class MockChannelInfoHolder implements ChannelInfoHolder { private static class MockChannelInfoHolder implements ChannelInfoHolder {
private final Map<NetworkState, Collection<Identifier>> playChannels = new ConcurrentHashMap<>(); private final Map<NetworkPhase, Collection<Identifier>> playChannels = new ConcurrentHashMap<>();
@Override @Override
public Collection<Identifier> getPendingChannelsNames(NetworkState state) { public Collection<Identifier> fabric_getPendingChannelsNames(NetworkPhase state) {
return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>())); return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
} }
} }

View file

@ -0,0 +1,154 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.networking.unit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import net.minecraft.Bootstrap;
import net.minecraft.SharedConstants;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
public class PayloadTypeRegistryTests {
@BeforeAll
static void beforeAll() {
SharedConstants.createGameVersion();
Bootstrap.initialize();
PayloadTypeRegistry.playC2S().register(C2SPlayPayload.ID, C2SPlayPayload.CODEC);
PayloadTypeRegistry.playS2C().register(S2CPlayPayload.ID, S2CPlayPayload.CODEC);
PayloadTypeRegistry.configurationC2S().register(C2SConfigPayload.ID, C2SConfigPayload.CODEC);
PayloadTypeRegistry.configurationS2C().register(S2CConfigPayload.ID, S2CConfigPayload.CODEC);
}
@Test
void C2SPlay() {
RegistryByteBuf buf = new RegistryByteBuf(PacketByteBufs.create(), null);
var packetToSend = new CustomPayloadC2SPacket(new C2SPlayPayload("Hello"));
CustomPayloadC2SPacket.CODEC.encode(buf, packetToSend);
CustomPayloadC2SPacket decodedPacket = CustomPayloadC2SPacket.CODEC.decode(buf);
if (decodedPacket.payload() instanceof C2SPlayPayload payload) {
assertEquals("Hello", payload.value());
} else {
fail();
}
}
@Test
void S2CPlay() {
RegistryByteBuf buf = new RegistryByteBuf(PacketByteBufs.create(), null);
var packetToSend = new CustomPayloadS2CPacket(new S2CPlayPayload("Hello"));
CustomPayloadS2CPacket.PLAY_CODEC.encode(buf, packetToSend);
CustomPayloadS2CPacket decodedPacket = CustomPayloadS2CPacket.PLAY_CODEC.decode(buf);
if (decodedPacket.payload() instanceof S2CPlayPayload payload) {
assertEquals("Hello", payload.value());
} else {
fail();
}
}
@Test
void C2SConfig() {
PacketByteBuf buf = PacketByteBufs.create();
var packetToSend = new CustomPayloadC2SPacket(new C2SConfigPayload("Hello"));
CustomPayloadC2SPacket.CODEC.encode(buf, packetToSend);
CustomPayloadC2SPacket decodedPacket = CustomPayloadC2SPacket.CODEC.decode(buf);
if (decodedPacket.payload() instanceof C2SConfigPayload payload) {
assertEquals("Hello", payload.value());
} else {
fail();
}
}
@Test
void S2CConfig() {
PacketByteBuf buf = PacketByteBufs.create();
var packetToSend = new CustomPayloadS2CPacket(new S2CConfigPayload("Hello"));
CustomPayloadS2CPacket.CONFIGURATION_CODEC.encode(buf, packetToSend);
CustomPayloadS2CPacket decodedPacket = CustomPayloadS2CPacket.CONFIGURATION_CODEC.decode(buf);
if (decodedPacket.payload() instanceof S2CConfigPayload payload) {
assertEquals("Hello", payload.value());
} else {
fail();
}
}
private record C2SPlayPayload(String value) implements CustomPayload {
public static final CustomPayload.Id<C2SPlayPayload> ID = CustomPayload.id("fabric:c2s_play");
public static final PacketCodec<RegistryByteBuf, C2SPlayPayload> CODEC = PacketCodecs.STRING.xmap(C2SPlayPayload::new, C2SPlayPayload::value).cast();
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
private record S2CPlayPayload(String value) implements CustomPayload {
public static final CustomPayload.Id<S2CPlayPayload> ID = CustomPayload.id("fabric:s2c_play");
public static final PacketCodec<RegistryByteBuf, S2CPlayPayload> CODEC = PacketCodecs.STRING.xmap(S2CPlayPayload::new, S2CPlayPayload::value).cast();
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
private record C2SConfigPayload(String value) implements CustomPayload {
public static final CustomPayload.Id<C2SConfigPayload> ID = CustomPayload.id("fabric:c2s_config");
public static final PacketCodec<PacketByteBuf, C2SConfigPayload> CODEC = PacketCodecs.STRING.xmap(C2SConfigPayload::new, C2SConfigPayload::value).cast();
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
private record S2CConfigPayload(String value) implements CustomPayload {
public static final CustomPayload.Id<S2CConfigPayload> ID = CustomPayload.id("fabric:s2c_config");
public static final PacketCodec<PacketByteBuf, S2CConfigPayload> CODEC = PacketCodecs.STRING.xmap(S2CConfigPayload::new, S2CConfigPayload::value).cast();
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}
}

View file

@ -35,6 +35,8 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import net.minecraft.command.CommandSource; import net.minecraft.command.CommandSource;
import net.minecraft.command.EntitySelector; import net.minecraft.command.EntitySelector;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
@ -43,6 +45,7 @@ import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
public final class NetworkingChannelTest implements ModInitializer { public final class NetworkingChannelTest implements ModInitializer {
@Override @Override
@ -88,7 +91,7 @@ public final class NetworkingChannelTest implements ModInitializer {
}); });
} }
private static CompletableFuture<Suggestions> suggestReceivableChannels(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) throws CommandSyntaxException { private static CompletableFuture<Suggestions> suggestReceivableChannels(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) {
final ServerPlayerEntity player = context.getSource().getPlayer(); final ServerPlayerEntity player = context.getSource().getPlayer();
return CommandSource.suggestIdentifiers(ServerPlayNetworking.getReceived(player), builder); return CommandSource.suggestIdentifiers(ServerPlayNetworking.getReceived(player), builder);
@ -101,20 +104,24 @@ public final class NetworkingChannelTest implements ModInitializer {
throw new SimpleCommandExceptionType(Text.literal(String.format("Cannot register channel %s twice for server player", channel))).create(); throw new SimpleCommandExceptionType(Text.literal(String.format("Cannot register channel %s twice for server player", channel))).create();
} }
ServerPlayNetworking.registerReceiver(executor.networkHandler, channel, (server, player, handler, buf, sender) -> { CustomPayload.Type<RegistryByteBuf, ? extends CustomPayload> payloadType = PayloadTypeRegistryImpl.PLAY_C2S.get(channel);
System.out.printf("Received packet on channel %s%n", channel);
});
context.getSource().sendFeedback(() -> Text.literal(String.format("Registered channel %s for %s", channel, executor.getDisplayName())), false); if (payloadType != null) {
ServerPlayNetworking.registerReceiver(executor.networkHandler, payloadType.id(), (payload, ctx) -> {
return 1; System.out.printf("Received packet on channel %s%n", payloadType.id().id());
});
context.getSource().sendFeedback(() -> Text.literal(String.format("Registered channel %s for %s", channel, executor.getDisplayName())), false);
return 1;
} else {
throw new SimpleCommandExceptionType(Text.literal("Unknown channel id")).create();
}
} }
private static int unregisterChannel(CommandContext<ServerCommandSource> context, ServerPlayerEntity player) throws CommandSyntaxException { private static int unregisterChannel(CommandContext<ServerCommandSource> context, ServerPlayerEntity player) throws CommandSyntaxException {
final Identifier channel = getIdentifier(context, "channel"); final Identifier channel = getIdentifier(context, "channel");
if (!ServerPlayNetworking.getReceived(player).contains(channel)) { if (!ServerPlayNetworking.getReceived(player).contains(channel)) {
throw new SimpleCommandExceptionType(Text.literal("Cannot unregister channel the server player entity cannot recieve packets on")).create(); throw new SimpleCommandExceptionType(Text.literal("Cannot unregister channel the server player entity cannot receive packets on")).create();
} }
ServerPlayNetworking.unregisterReceiver(player.networkHandler, channel); ServerPlayNetworking.unregisterReceiver(player.networkHandler, channel);

View file

@ -19,14 +19,15 @@ package net.fabricmc.fabric.test.networking.configuration;
import java.util.function.Consumer; import java.util.function.Consumer;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ServerPlayerConfigurationTask; import net.minecraft.server.network.ServerPlayerConfigurationTask;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking; import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.test.networking.NetworkingTestmods; import net.fabricmc.fabric.test.networking.NetworkingTestmods;
@ -37,9 +38,12 @@ import net.fabricmc.fabric.test.networking.NetworkingTestmods;
public class NetworkingConfigurationTest implements ModInitializer { public class NetworkingConfigurationTest implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
PayloadTypeRegistry.configurationS2C().register(ConfigurationPacket.ID, ConfigurationPacket.CODEC);
PayloadTypeRegistry.configurationC2S().register(ConfigurationCompletePacket.ID, ConfigurationCompletePacket.CODEC);
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> { ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
// You must check to see if the client can handle your config task // You must check to see if the client can handle your config task
if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.PACKET_TYPE)) { if (ServerConfigurationNetworking.canSend(handler, ConfigurationPacket.ID)) {
handler.addTask(new TestConfigurationTask("Example data")); handler.addTask(new TestConfigurationTask("Example data"));
} else { } else {
// You can opt to disconnect the client if it cannot handle the configuration task // You can opt to disconnect the client if it cannot handle the configuration task
@ -47,8 +51,8 @@ public class NetworkingConfigurationTest implements ModInitializer {
} }
}); });
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.PACKET_TYPE, (packet, networkHandler, responseSender) -> { ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.ID, (packet, context) -> {
networkHandler.completeTask(TestConfigurationTask.KEY); context.networkHandler().completeTask(TestConfigurationTask.KEY);
}); });
} }
@ -67,38 +71,35 @@ public class NetworkingConfigurationTest implements ModInitializer {
} }
} }
public record ConfigurationPacket(String data) implements FabricPacket { public record ConfigurationPacket(String data) implements CustomPayload {
public static final PacketType<ConfigurationPacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure"), ConfigurationPacket::new); public static final CustomPayload.Id<ConfigurationPacket> ID = new Id<>(new Identifier(NetworkingTestmods.ID, "configure"));
public static final PacketCodec<PacketByteBuf, ConfigurationPacket> CODEC = CustomPayload.codecOf(ConfigurationPacket::write, ConfigurationPacket::new);
public ConfigurationPacket(PacketByteBuf buf) { public ConfigurationPacket(PacketByteBuf buf) {
this(buf.readString()); this(buf.readString());
} }
@Override
public void write(PacketByteBuf buf) { public void write(PacketByteBuf buf) {
buf.writeString(data); buf.writeString(data);
} }
@Override @Override
public PacketType<?> getType() { public Id<? extends CustomPayload> getId() {
return PACKET_TYPE; return ID;
} }
} }
public record ConfigurationCompletePacket() implements FabricPacket { public static class ConfigurationCompletePacket implements CustomPayload {
public static final PacketType<ConfigurationCompletePacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure_complete"), ConfigurationCompletePacket::new); public static final ConfigurationCompletePacket INSTANCE = new ConfigurationCompletePacket();
public static final CustomPayload.Id<ConfigurationCompletePacket> ID = new Id<>(new Identifier(NetworkingTestmods.ID, "configure_complete"));
public static final PacketCodec<PacketByteBuf, ConfigurationCompletePacket> CODEC = PacketCodec.unit(INSTANCE);
public ConfigurationCompletePacket(PacketByteBuf buf) { private ConfigurationCompletePacket() {
this();
} }
@Override @Override
public void write(PacketByteBuf buf) { public Id<? extends CustomPayload> getId() {
} return ID;
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
} }
} }
} }

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.networking.keybindreciever;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
public class KeybindPayload implements CustomPayload {
public static final KeybindPayload INSTANCE = new KeybindPayload();
public static final CustomPayload.Id<KeybindPayload> ID = new CustomPayload.Id<>(NetworkingTestmods.id("keybind_press_test"));
public static final PacketCodec<RegistryByteBuf, KeybindPayload> CODEC = PacketCodec.unit(INSTANCE);
private KeybindPayload() { }
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View file

@ -16,31 +16,24 @@
package net.fabricmc.fabric.test.networking.keybindreciever; package net.fabricmc.fabric.test.networking.keybindreciever;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
// Listens for a packet from the client which is sent to the server when a keybinding is pressed. // Listens for a packet from the client which is sent to the server when a keybinding is pressed.
// In response the server will send a message containing the keybind text letting the client know it pressed that key. // In response the server will send a message containing the keybind text letting the client know it pressed that key.
public final class NetworkingKeybindPacketTest implements ModInitializer { public final class NetworkingKeybindPacketTest implements ModInitializer {
public static final Identifier KEYBINDING_PACKET_ID = NetworkingTestmods.id("keybind_press_test"); private static void receive(KeybindPayload payload, ServerPlayNetworking.Context context) {
context.player().server.execute(() -> context.player().sendMessage(Text.literal("So you pressed ").append(Text.keybind("fabric-networking-api-v1-testmod-keybind").styled(style -> style.withFormatting(Formatting.BLUE))), false));
private static void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) {
server.execute(() -> player.sendMessage(Text.literal("So you pressed ").append(Text.keybind("fabric-networking-api-v1-testmod-keybind").styled(style -> style.withFormatting(Formatting.BLUE))), false));
} }
@Override @Override
public void onInitialize() { public void onInitialize() {
ServerPlayConnectionEvents.INIT.register((handler, server) -> ServerPlayNetworking.registerReceiver(handler, KEYBINDING_PACKET_ID, NetworkingKeybindPacketTest::receive)); PayloadTypeRegistry.playC2S().register(KeybindPayload.ID, KeybindPayload.CODEC);
ServerPlayConnectionEvents.INIT.register((handler, server) -> ServerPlayNetworking.registerReceiver(handler, KeybindPayload.ID, NetworkingKeybindPacketTest::receive));
} }
} }

View file

@ -25,6 +25,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.Util; import net.minecraft.util.Util;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.LoginPacketSender;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents;
@ -94,7 +95,7 @@ public final class NetworkingLoginQueryTest implements ModInitializer {
} }
} }
private void onLoginStart(ServerLoginNetworkHandler networkHandler, MinecraftServer server, PacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer) { private void onLoginStart(ServerLoginNetworkHandler networkHandler, MinecraftServer server, LoginPacketSender sender, ServerLoginNetworking.LoginSynchronizer synchronizer) {
// Send a dummy query when the client starts accepting queries. // Send a dummy query when the client starts accepting queries.
sender.sendPacket(GLOBAL_TEST_CHANNEL, PacketByteBufs.empty()); // dummy packet sender.sendPacket(GLOBAL_TEST_CHANNEL, PacketByteBufs.empty()); // dummy packet
} }

View file

@ -21,50 +21,42 @@ import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.server.command.CommandManager.literal;
import java.util.List; import java.util.List;
import java.util.UUID;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientPlayPacketListener; import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.Packet; import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket; import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.s2c.play.BundleS2CPacket; import net.minecraft.network.packet.s2c.play.BundleS2CPacket;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.networking.v1.FabricPacket; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.test.networking.NetworkingTestmods; import net.fabricmc.fabric.test.networking.NetworkingTestmods;
import net.fabricmc.loader.api.FabricLoader;
public final class NetworkingPlayPacketTest implements ModInitializer { public final class NetworkingPlayPacketTest implements ModInitializer {
public static final Identifier TEST_CHANNEL = NetworkingTestmods.id("test_channel");
private static final Identifier UNKNOWN_TEST_CHANNEL = NetworkingTestmods.id("unknown_test_channel");
private static boolean spamUnknownPackets = false; private static boolean spamUnknownPackets = false;
public static void sendToTestChannel(ServerPlayerEntity player, String stuff) { public static void sendToTestChannel(ServerPlayerEntity player, String stuff) {
ServerPlayNetworking.getSender(player).sendPacket(new OverlayPacket(Text.literal(stuff)), future -> { ServerPlayNetworking.getSender(player).sendPacket(new OverlayPacket(Text.literal(stuff)), PacketCallbacks.always(() -> {
NetworkingTestmods.LOGGER.info("Sent custom payload packet in {}", TEST_CHANNEL); NetworkingTestmods.LOGGER.info("Sent custom payload packet");
}); }));
} }
private static void sendToUnknownChannel(ServerPlayerEntity player) { private static void sendToUnknownChannel(ServerPlayerEntity player) {
PacketByteBuf buf = PacketByteBufs.create(); ServerPlayNetworking.getSender(player).sendPacket(new UnknownPayload("Hello"));
for (int i = 0; i < 20; i++) {
buf.writeUuid(UUID.randomUUID());
}
ServerPlayNetworking.getSender(player).sendPacket(UNKNOWN_TEST_CHANNEL, buf);
} }
public static void registerCommand(CommandDispatcher<ServerCommandSource> dispatcher) { public static void registerCommand(CommandDispatcher<ServerCommandSource> dispatcher) {
@ -85,30 +77,16 @@ public final class NetworkingPlayPacketTest implements ModInitializer {
ctx.getSource().sendMessage(Text.literal("Spamming unknown packets state:" + spamUnknownPackets)); ctx.getSource().sendMessage(Text.literal("Spamming unknown packets state:" + spamUnknownPackets));
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
})) }))
.then(literal("bufctor").executes(ctx -> { .then(literal("simple").executes(ctx -> {
PacketByteBuf buf = PacketByteBufs.create(); ServerPlayNetworking.send(ctx.getSource().getPlayer(), new OverlayPacket(Text.literal("simple")));
buf.writeIdentifier(TEST_CHANNEL);
buf.writeText(Text.literal("bufctor"));
ctx.getSource().getPlayer().networkHandler.sendPacket(new CustomPayloadS2CPacket(buf));
return Command.SINGLE_SUCCESS;
}))
.then(literal("repeat").executes(ctx -> {
PacketByteBuf buf = PacketByteBufs.create();
buf.writeText(Text.literal("repeat"));
ServerPlayNetworking.send(ctx.getSource().getPlayer(), TEST_CHANNEL, buf);
ServerPlayNetworking.send(ctx.getSource().getPlayer(), TEST_CHANNEL, buf);
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
})) }))
.then(literal("bundled").executes(ctx -> { .then(literal("bundled").executes(ctx -> {
PacketByteBuf buf1 = PacketByteBufs.create(); BundleS2CPacket packet = new BundleS2CPacket(List.of(
buf1.writeText(Text.literal("bundled #1")); ServerPlayNetworking.createS2CPacket(new OverlayPacket(Text.literal("bundled #1"))),
PacketByteBuf buf2 = PacketByteBufs.create(); ServerPlayNetworking.createS2CPacket(new OverlayPacket(Text.literal("bundled #2")))
buf2.writeText(Text.literal("bundled #2")); ));
ServerPlayNetworking.getSender(ctx.getSource().getPlayer()).sendPacket(packet);
BundleS2CPacket packet = new BundleS2CPacket((List<Packet<ClientPlayPacketListener>>) (Object) List.of(
ServerPlayNetworking.createS2CPacket(TEST_CHANNEL, buf1),
ServerPlayNetworking.createS2CPacket(TEST_CHANNEL, buf2)));
ctx.getSource().getPlayer().networkHandler.sendPacket(packet);
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
}))); })));
} }
@ -117,6 +95,12 @@ public final class NetworkingPlayPacketTest implements ModInitializer {
public void onInitialize() { public void onInitialize() {
NetworkingTestmods.LOGGER.info("Hello from networking user!"); NetworkingTestmods.LOGGER.info("Hello from networking user!");
PayloadTypeRegistry.playS2C().register(OverlayPacket.ID, OverlayPacket.CODEC);
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER) {
PayloadTypeRegistry.playS2C().register(UnknownPayload.ID, UnknownPayload.CODEC);
}
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
NetworkingPlayPacketTest.registerCommand(dispatcher); NetworkingPlayPacketTest.registerCommand(dispatcher);
}); });
@ -135,21 +119,31 @@ public final class NetworkingPlayPacketTest implements ModInitializer {
}); });
} }
public record OverlayPacket(Text message) implements FabricPacket { public record OverlayPacket(Text message) implements CustomPayload {
public static final PacketType<OverlayPacket> PACKET_TYPE = PacketType.create(TEST_CHANNEL, OverlayPacket::new); public static final CustomPayload.Id<OverlayPacket> ID = new Id<>(NetworkingTestmods.id("test_channel"));
public static final PacketCodec<RegistryByteBuf, OverlayPacket> CODEC = CustomPayload.codecOf(OverlayPacket::write, OverlayPacket::new);
public OverlayPacket(PacketByteBuf buf) { public OverlayPacket(PacketByteBuf buf) {
this(buf.readText()); this(buf.readText());
} }
@Override
public void write(PacketByteBuf buf) { public void write(PacketByteBuf buf) {
buf.writeText(this.message); buf.writeText(this.message);
} }
@Override @Override
public PacketType<?> getType() { public Id<? extends CustomPayload> getId() {
return PACKET_TYPE; return ID;
}
}
private record UnknownPayload(String data) implements CustomPayload {
private static final CustomPayload.Id<UnknownPayload> ID = new Id<>(NetworkingTestmods.id("unknown_test_channel_s2c"));
private static final PacketCodec<PacketByteBuf, UnknownPayload> CODEC = PacketCodecs.STRING.xmap(UnknownPayload::new, UnknownPayload::data).cast();
@Override
public Id<? extends CustomPayload> getId() {
return ID;
} }
} }
} }

View file

@ -23,11 +23,11 @@ import net.fabricmc.fabric.test.networking.configuration.NetworkingConfiguration
public class NetworkingConfigurationClientTest implements ClientModInitializer { public class NetworkingConfigurationClientTest implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> { ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.ID, (packet, context) -> {
// Handle stuff here // Handle stuff here
// Respond back to the server that the task is complete // Respond back to the server that the task is complete
responseSender.sendPacket(new NetworkingConfigurationTest.ConfigurationCompletePacket()); context.responseSender().sendPacket(NetworkingConfigurationTest.ConfigurationCompletePacket.INSTANCE);
}); });
} }
} }

View file

@ -25,8 +25,7 @@ import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.test.networking.keybindreciever.KeybindPayload;
import net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindPacketTest;
// Sends a packet to the server when a keybinding was pressed // Sends a packet to the server when a keybinding was pressed
// The server in response will send a chat message to the client. // The server in response will send a chat message to the client.
@ -40,7 +39,8 @@ public class NetworkingKeybindClientPacketTest implements ClientModInitializer {
if (client.getNetworkHandler() != null) { if (client.getNetworkHandler() != null) {
if (TEST_BINDING.wasPressed()) { if (TEST_BINDING.wasPressed()) {
// Send an empty payload, server just needs to be told when packet is sent // Send an empty payload, server just needs to be told when packet is sent
ClientPlayNetworking.send(NetworkingKeybindPacketTest.KEYBINDING_PACKET_ID, PacketByteBufs.empty()); // Since KeybindPayload is an empty payload, it can be a singleton.
ClientPlayNetworking.send(KeybindPayload.INSTANCE);
} }
} }
}); });

View file

@ -18,38 +18,44 @@ package net.fabricmc.fabric.test.networking.client.play;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import net.minecraft.client.MinecraftClient; import net.minecraft.network.PacketByteBuf;
import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.network.codec.PacketCodec;
import net.minecraft.util.Identifier; import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.test.networking.NetworkingTestmods; import net.fabricmc.fabric.test.networking.NetworkingTestmods;
import net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest; import net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest;
public final class NetworkingPlayPacketClientTest implements ClientModInitializer, ClientPlayNetworking.PlayPacketHandler<NetworkingPlayPacketTest.OverlayPacket> { public final class NetworkingPlayPacketClientTest implements ClientModInitializer {
private static final Identifier UNKNOWN_TEST_CHANNEL = NetworkingTestmods.id("unknown_test_channel");
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
ClientPlayConnectionEvents.INIT.register((handler, client) -> ClientPlayNetworking.registerReceiver(NetworkingPlayPacketTest.OverlayPacket.PACKET_TYPE, this)); // Register the payload only on the client.
PayloadTypeRegistry.playC2S().register(UnknownPayload.ID, UnknownPayload.CODEC);
ClientPlayConnectionEvents.INIT.register((handler, client) -> ClientPlayNetworking.registerReceiver(NetworkingPlayPacketTest.OverlayPacket.ID, (payload, context) -> context.client().inGameHud.setOverlayMessage(payload.message(), true)));
ClientCommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register( ClientCommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(
ClientCommandManager.literal("clientnetworktestcommand") ClientCommandManager.literal("clientnetworktestcommand")
.then(ClientCommandManager.literal("unknown").executes(context -> { .then(ClientCommandManager.literal("unknown").executes(context -> {
ClientPlayNetworking.send(UNKNOWN_TEST_CHANNEL, PacketByteBufs.create()); ClientPlayNetworking.send(new UnknownPayload("Hello"));
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
} }
)))); ))));
} }
@Override private record UnknownPayload(String data) implements CustomPayload {
public void receive(NetworkingPlayPacketTest.OverlayPacket packet, ClientPlayerEntity player, PacketSender sender) { private static final CustomPayload.Id<UnknownPayload> ID = new Id<>(NetworkingTestmods.id("unknown_test_channel_c2s"));
MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true); private static final PacketCodec<PacketByteBuf, UnknownPayload> CODEC = PacketCodecs.STRING.xmap(UnknownPayload::new, UnknownPayload::data).cast();
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
} }
} }

View file

@ -35,15 +35,9 @@ import net.fabricmc.fabric.mixin.object.builder.AbstractBlockAccessor;
import net.fabricmc.fabric.mixin.object.builder.AbstractBlockSettingsAccessor; import net.fabricmc.fabric.mixin.object.builder.AbstractBlockSettingsAccessor;
/** /**
* Fabric's version of Block.Settings. Adds additional methods and hooks * @deprecated replace with {@link AbstractBlock.Settings}
* not found in the original class.
*
* <p>Make note that this behaves slightly different from the
* vanilla counterpart, copying some settings that vanilla does not.
*
* <p>To use it, simply replace Block.Settings.of() with
* FabricBlockSettings.of().
*/ */
@Deprecated
public class FabricBlockSettings extends AbstractBlock.Settings { public class FabricBlockSettings extends AbstractBlock.Settings {
protected FabricBlockSettings() { protected FabricBlockSettings() {
super(); super();
@ -97,56 +91,74 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
this.postProcess(otherAccessor.getPostProcessPredicate()); this.postProcess(otherAccessor.getPostProcessPredicate());
} }
/**
* @deprecated replace with {@link AbstractBlock.Settings#create()}
*/
@Deprecated
public static FabricBlockSettings create() { public static FabricBlockSettings create() {
return new FabricBlockSettings(); return new FabricBlockSettings();
} }
/** /**
* @deprecated Use {@link FabricBlockSettings#create()} instead. * @deprecated replace with {@link AbstractBlock.Settings#create()}
*/ */
@Deprecated @Deprecated
public static FabricBlockSettings of() { public static FabricBlockSettings of() {
return create(); return create();
} }
/**
* @deprecated replace with {@link AbstractBlock.Settings#copy(AbstractBlock)}
*/
@Deprecated
public static FabricBlockSettings copyOf(AbstractBlock block) { public static FabricBlockSettings copyOf(AbstractBlock block) {
return new FabricBlockSettings(((AbstractBlockAccessor) block).getSettings()); return new FabricBlockSettings(((AbstractBlockAccessor) block).getSettings());
} }
/**
* @deprecated replace with {@link AbstractBlock.Settings#copy(AbstractBlock)}
*/
@Deprecated
public static FabricBlockSettings copyOf(AbstractBlock.Settings settings) { public static FabricBlockSettings copyOf(AbstractBlock.Settings settings) {
return new FabricBlockSettings(settings); return new FabricBlockSettings(settings);
} }
@Deprecated
@Override @Override
public FabricBlockSettings noCollision() { public FabricBlockSettings noCollision() {
super.noCollision(); super.noCollision();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings nonOpaque() { public FabricBlockSettings nonOpaque() {
super.nonOpaque(); super.nonOpaque();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings slipperiness(float value) { public FabricBlockSettings slipperiness(float value) {
super.slipperiness(value); super.slipperiness(value);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings velocityMultiplier(float velocityMultiplier) { public FabricBlockSettings velocityMultiplier(float velocityMultiplier) {
super.velocityMultiplier(velocityMultiplier); super.velocityMultiplier(velocityMultiplier);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings jumpVelocityMultiplier(float jumpVelocityMultiplier) { public FabricBlockSettings jumpVelocityMultiplier(float jumpVelocityMultiplier) {
super.jumpVelocityMultiplier(jumpVelocityMultiplier); super.jumpVelocityMultiplier(jumpVelocityMultiplier);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings sounds(BlockSoundGroup group) { public FabricBlockSettings sounds(BlockSoundGroup group) {
super.sounds(group); super.sounds(group);
@ -161,89 +173,105 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
return this.luminance(levelFunction); return this.luminance(levelFunction);
} }
@Deprecated
@Override @Override
public FabricBlockSettings luminance(ToIntFunction<BlockState> luminanceFunction) { public FabricBlockSettings luminance(ToIntFunction<BlockState> luminanceFunction) {
super.luminance(luminanceFunction); super.luminance(luminanceFunction);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings strength(float hardness, float resistance) { public FabricBlockSettings strength(float hardness, float resistance) {
super.strength(hardness, resistance); super.strength(hardness, resistance);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings breakInstantly() { public FabricBlockSettings breakInstantly() {
super.breakInstantly(); super.breakInstantly();
return this; return this;
} }
@Deprecated
@Override
public FabricBlockSettings strength(float strength) { public FabricBlockSettings strength(float strength) {
super.strength(strength); super.strength(strength);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings ticksRandomly() { public FabricBlockSettings ticksRandomly() {
super.ticksRandomly(); super.ticksRandomly();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings dynamicBounds() { public FabricBlockSettings dynamicBounds() {
super.dynamicBounds(); super.dynamicBounds();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings dropsNothing() { public FabricBlockSettings dropsNothing() {
super.dropsNothing(); super.dropsNothing();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings dropsLike(Block block) { public FabricBlockSettings dropsLike(Block block) {
super.dropsLike(block); super.dropsLike(block);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings air() { public FabricBlockSettings air() {
super.air(); super.air();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings allowsSpawning(AbstractBlock.TypedContextPredicate<EntityType<?>> predicate) { public FabricBlockSettings allowsSpawning(AbstractBlock.TypedContextPredicate<EntityType<?>> predicate) {
super.allowsSpawning(predicate); super.allowsSpawning(predicate);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings solidBlock(AbstractBlock.ContextPredicate predicate) { public FabricBlockSettings solidBlock(AbstractBlock.ContextPredicate predicate) {
super.solidBlock(predicate); super.solidBlock(predicate);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings suffocates(AbstractBlock.ContextPredicate predicate) { public FabricBlockSettings suffocates(AbstractBlock.ContextPredicate predicate) {
super.suffocates(predicate); super.suffocates(predicate);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings blockVision(AbstractBlock.ContextPredicate predicate) { public FabricBlockSettings blockVision(AbstractBlock.ContextPredicate predicate) {
super.blockVision(predicate); super.blockVision(predicate);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings postProcess(AbstractBlock.ContextPredicate predicate) { public FabricBlockSettings postProcess(AbstractBlock.ContextPredicate predicate) {
super.postProcess(predicate); super.postProcess(predicate);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings emissiveLighting(AbstractBlock.ContextPredicate predicate) { public FabricBlockSettings emissiveLighting(AbstractBlock.ContextPredicate predicate) {
super.emissiveLighting(predicate); super.emissiveLighting(predicate);
@ -253,90 +281,105 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
/** /**
* Make the block require tool to drop and slows down mining speed if the incorrect tool is used. * Make the block require tool to drop and slows down mining speed if the incorrect tool is used.
*/ */
@Deprecated
@Override @Override
public FabricBlockSettings requiresTool() { public FabricBlockSettings requiresTool() {
super.requiresTool(); super.requiresTool();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings mapColor(MapColor color) { public FabricBlockSettings mapColor(MapColor color) {
super.mapColor(color); super.mapColor(color);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings hardness(float hardness) { public FabricBlockSettings hardness(float hardness) {
super.hardness(hardness); super.hardness(hardness);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings resistance(float resistance) { public FabricBlockSettings resistance(float resistance) {
super.resistance(resistance); super.resistance(resistance);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings offset(AbstractBlock.OffsetType offsetType) { public FabricBlockSettings offset(AbstractBlock.OffsetType offsetType) {
super.offset(offsetType); super.offset(offsetType);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings noBlockBreakParticles() { public FabricBlockSettings noBlockBreakParticles() {
super.noBlockBreakParticles(); super.noBlockBreakParticles();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings requires(FeatureFlag... features) { public FabricBlockSettings requires(FeatureFlag... features) {
super.requires(features); super.requires(features);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings mapColor(Function<BlockState, MapColor> mapColorProvider) { public FabricBlockSettings mapColor(Function<BlockState, MapColor> mapColorProvider) {
super.mapColor(mapColorProvider); super.mapColor(mapColorProvider);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings burnable() { public FabricBlockSettings burnable() {
super.burnable(); super.burnable();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings liquid() { public FabricBlockSettings liquid() {
super.liquid(); super.liquid();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings solid() { public FabricBlockSettings solid() {
super.solid(); super.solid();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings notSolid() { public FabricBlockSettings notSolid() {
super.notSolid(); super.notSolid();
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings pistonBehavior(PistonBehavior pistonBehavior) { public FabricBlockSettings pistonBehavior(PistonBehavior pistonBehavior) {
super.pistonBehavior(pistonBehavior); super.pistonBehavior(pistonBehavior);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings instrument(Instrument instrument) { public FabricBlockSettings instrument(Instrument instrument) {
super.instrument(instrument); super.instrument(instrument);
return this; return this;
} }
@Deprecated
@Override @Override
public FabricBlockSettings replaceable() { public FabricBlockSettings replaceable() {
super.replaceable(); super.replaceable();
@ -354,11 +397,16 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
return this; return this;
} }
/**
* @deprecated replace with {@link AbstractBlock.Settings#luminance(ToIntFunction)}
*/
@Deprecated
public FabricBlockSettings luminance(int luminance) { public FabricBlockSettings luminance(int luminance) {
this.luminance(ignored -> luminance); this.luminance(ignored -> luminance);
return this; return this;
} }
@Deprecated
public FabricBlockSettings drops(Identifier dropTableId) { public FabricBlockSettings drops(Identifier dropTableId) {
((AbstractBlockSettingsAccessor) this).setLootTableId(dropTableId); ((AbstractBlockSettingsAccessor) this).setLootTableId(dropTableId);
return this; return this;
@ -367,7 +415,7 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
/* FABRIC DELEGATE WRAPPERS */ /* FABRIC DELEGATE WRAPPERS */
/** /**
* @deprecated Please migrate to {@link FabricBlockSettings#mapColor(MapColor)} * @deprecated Please migrate to {@link AbstractBlock.Settings#mapColor(MapColor)}
*/ */
@Deprecated @Deprecated
public FabricBlockSettings materialColor(MapColor color) { public FabricBlockSettings materialColor(MapColor color) {
@ -375,17 +423,22 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
} }
/** /**
* @deprecated Please migrate to {@link FabricBlockSettings#mapColor(DyeColor)} * @deprecated Please migrate to {@link AbstractBlock.Settings#mapColor(DyeColor)}
*/ */
@Deprecated @Deprecated
public FabricBlockSettings materialColor(DyeColor color) { public FabricBlockSettings materialColor(DyeColor color) {
return this.mapColor(color); return this.mapColor(color);
} }
/**
* @deprecated Please migrate to {@link AbstractBlock.Settings#mapColor(DyeColor)}
*/
@Deprecated
public FabricBlockSettings mapColor(DyeColor color) { public FabricBlockSettings mapColor(DyeColor color) {
return this.mapColor(color.getMapColor()); return this.mapColor(color.getMapColor());
} }
@Deprecated
public FabricBlockSettings collidable(boolean collidable) { public FabricBlockSettings collidable(boolean collidable) {
((AbstractBlockSettingsAccessor) this).setCollidable(collidable); ((AbstractBlockSettingsAccessor) this).setCollidable(collidable);
return this; return this;

View file

@ -81,7 +81,7 @@ public class BlockEntityTypeBuilderTest implements ModInitializer {
} }
@Override @Override
public ActionResult method_55766(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) { public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if (!world.isClient()) { if (!world.isClient()) {
BlockEntity blockEntity = world.getBlockEntity(pos); BlockEntity blockEntity = world.getBlockEntity(pos);

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.test.object.builder; package net.fabricmc.fabric.test.object.builder;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockSetType; import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@ -36,7 +37,6 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeBuilder;
import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeBuilder; import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeBuilder;
@ -45,25 +45,25 @@ public class TealSignTest implements ModInitializer {
public static final Identifier TEAL_TYPE_ID = ObjectBuilderTestConstants.id("teal"); public static final Identifier TEAL_TYPE_ID = ObjectBuilderTestConstants.id("teal");
public static final BlockSetType TEAL_BLOCK_SET_TYPE = BlockSetTypeBuilder.copyOf(BlockSetType.OAK).build(TEAL_TYPE_ID); public static final BlockSetType TEAL_BLOCK_SET_TYPE = BlockSetTypeBuilder.copyOf(BlockSetType.OAK).build(TEAL_TYPE_ID);
public static final WoodType TEAL_WOOD_TYPE = WoodTypeBuilder.copyOf(WoodType.OAK).build(TEAL_TYPE_ID, TEAL_BLOCK_SET_TYPE); public static final WoodType TEAL_WOOD_TYPE = WoodTypeBuilder.copyOf(WoodType.OAK).build(TEAL_TYPE_ID, TEAL_BLOCK_SET_TYPE);
public static final SignBlock TEAL_SIGN = new SignBlock(TEAL_WOOD_TYPE, FabricBlockSettings.copy(Blocks.OAK_SIGN)) { public static final SignBlock TEAL_SIGN = new SignBlock(TEAL_WOOD_TYPE, AbstractBlock.Settings.copy(Blocks.OAK_SIGN)) {
@Override @Override
public TealSign createBlockEntity(BlockPos pos, BlockState state) { public TealSign createBlockEntity(BlockPos pos, BlockState state) {
return new TealSign(pos, state); return new TealSign(pos, state);
} }
}; };
public static final WallSignBlock TEAL_WALL_SIGN = new WallSignBlock(TEAL_WOOD_TYPE, FabricBlockSettings.copy(Blocks.OAK_SIGN)) { public static final WallSignBlock TEAL_WALL_SIGN = new WallSignBlock(TEAL_WOOD_TYPE, AbstractBlock.Settings.copy(Blocks.OAK_SIGN)) {
@Override @Override
public TealSign createBlockEntity(BlockPos pos, BlockState state) { public TealSign createBlockEntity(BlockPos pos, BlockState state) {
return new TealSign(pos, state); return new TealSign(pos, state);
} }
}; };
public static final HangingSignBlock TEAL_HANGING_SIGN = new HangingSignBlock(TEAL_WOOD_TYPE, FabricBlockSettings.copy(Blocks.OAK_HANGING_SIGN)) { public static final HangingSignBlock TEAL_HANGING_SIGN = new HangingSignBlock(TEAL_WOOD_TYPE, AbstractBlock.Settings.copy(Blocks.OAK_HANGING_SIGN)) {
@Override @Override
public TealHangingSign createBlockEntity(BlockPos pos, BlockState state) { public TealHangingSign createBlockEntity(BlockPos pos, BlockState state) {
return new TealHangingSign(pos, state); return new TealHangingSign(pos, state);
} }
}; };
public static final WallHangingSignBlock TEAL_WALL_HANGING_SIGN = new WallHangingSignBlock(TEAL_WOOD_TYPE, FabricBlockSettings.copy(Blocks.OAK_HANGING_SIGN)) { public static final WallHangingSignBlock TEAL_WALL_HANGING_SIGN = new WallHangingSignBlock(TEAL_WOOD_TYPE, AbstractBlock.Settings.copy(Blocks.OAK_HANGING_SIGN)) {
@Override @Override
public TealHangingSign createBlockEntity(BlockPos pos, BlockState state) { public TealHangingSign createBlockEntity(BlockPos pos, BlockState state) {
return new TealHangingSign(pos, state); return new TealHangingSign(pos, state);

View file

@ -16,8 +16,12 @@
package net.fabricmc.fabric.api.particle.v1; package net.fabricmc.fabric.api.particle.v1;
import java.util.function.Function;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.particle.DefaultParticleType; import net.minecraft.particle.DefaultParticleType;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.particle.ParticleType; import net.minecraft.particle.ParticleType;
@ -59,26 +63,69 @@ public final class FabricParticleTypes {
} }
/** /**
* Creates a new particle type with a custom factory for packet/data serialization. * Creates a new particle type with a custom factory and codecs for packet/data serialization.
* *
* @param factory A factory for serializing packet data and string command parameters into a particle effect. * @param factory A factory for serializing string command parameters into a particle effect.
* @param codec The codec for serialization.
* @param packetCodec The packet codec for network serialization.
*/ */
public static <T extends ParticleEffect> ParticleType<T> complex(ParticleEffect.Factory<T> factory) { public static <T extends ParticleEffect> ParticleType<T> complex(ParticleEffect.Factory<T> factory, final Function<ParticleType<T>, Codec<T>> codecGetter, final Codec<T> codec, final PacketCodec<? super RegistryByteBuf, T> packetCodec) {
return complex(false, factory); return complex(false, factory, codec, packetCodec);
} }
/** /**
* Creates a new particle type with a custom factory for packet/data serialization. * Creates a new particle type with a custom factory and codecs for packet/data serialization.
* *
* @param alwaysSpawn True to always spawn the particle regardless of distance. * @param alwaysSpawn True to always spawn the particle regardless of distance.
* @param factory A factory for serializing packet data and string command parameters into a particle effect. * @param factory A factory for serializing string command parameters into a particle effect.
* @param codec The codec for serialization.
* @param packetCodec The packet codec for network serialization.
*/ */
public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, ParticleEffect.Factory<T> factory) { public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, ParticleEffect.Factory<T> factory, final Codec<T> codec, final PacketCodec<? super RegistryByteBuf, T> packetCodec) {
return new ParticleType<T>(alwaysSpawn, factory) { return new ParticleType<T>(alwaysSpawn, factory) {
@Override @Override
public Codec<T> getCodec() { public Codec<T> getCodec() {
//TODO fix me return codec;
return null; }
@Override
public PacketCodec<? super RegistryByteBuf, T> getPacketCodec() {
return packetCodec;
}
};
}
/**
* Creates a new particle type with a custom factory and codecs for packet/data serialization.
* This method is useful when two different {@link ParticleType}s share the same {@link ParticleEffect} implementation.
*
* @param factory A factory for serializing string command parameters into a particle effect.
* @param codecGetter A function that, given the newly created type, returns the codec for serialization.
* @param packetCodecGetter A function that, given the newly created type, returns the packet codec for network serialization.
*/
public static <T extends ParticleEffect> ParticleType<T> complex(ParticleEffect.Factory<T> factory, final Function<ParticleType<T>, Codec<T>> codecGetter, final Function<ParticleType<T>, PacketCodec<? super RegistryByteBuf, T>> packetCodecGetter) {
return complex(false, factory, codecGetter, packetCodecGetter);
}
/**
* Creates a new particle type with a custom factory and codecs for packet/data serialization.
* This method is useful when two different {@link ParticleType}s share the same {@link ParticleEffect} implementation.
*
* @param alwaysSpawn True to always spawn the particle regardless of distance.
* @param factory A factory for serializing string command parameters into a particle effect.
* @param codecGetter A function that, given the newly created type, returns the codec for serialization.
* @param packetCodecGetter A function that, given the newly created type, returns the packet codec for network serialization.
*/
public static <T extends ParticleEffect> ParticleType<T> complex(boolean alwaysSpawn, ParticleEffect.Factory<T> factory, final Function<ParticleType<T>, Codec<T>> codecGetter, final Function<ParticleType<T>, PacketCodec<? super RegistryByteBuf, T>> packetCodecGetter) {
return new ParticleType<T>(alwaysSpawn, factory) {
@Override
public Codec<T> getCodec() {
return codecGetter.apply(this);
}
@Override
public PacketCodec<? super RegistryByteBuf, T> getPacketCodec() {
return packetCodecGetter.apply(this);
} }
}; };
} }

Some files were not shown because too many files have changed in this diff Show more