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 net.minecraft.block.AbstractBlock;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
@ -28,7 +29,6 @@ import net.minecraft.util.math.Direction;
import net.fabricmc.api.ModInitializer;
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.test.lookup.api.ItemApis;
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";
// 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.
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 BlockEntityType<ChuteBlockEntity> CHUTE_BLOCK_ENTITY_TYPE;
// Cobble gen - Block without model that can generate infinite cobblestone when placed above a chute.
// 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 BlockEntityType<CobbleGenBlockEntity> COBBLE_GEN_BLOCK_ENTITY_TYPE;
// 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());
@Override

View file

@ -18,12 +18,12 @@ package net.fabricmc.fabric.test.lookup;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.class_9062;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Hand;
import net.minecraft.util.ItemActionResult;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
@ -38,7 +38,7 @@ public class InspectorBlock extends Block {
}
@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);
if (inspectable != null) {
@ -46,10 +46,10 @@ public class InspectorBlock extends Block {
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

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.impl.content.registry;
import java.util.IdentityHashMap;
import java.util.Map;
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.tag.TagKey;
import net.fabricmc.fabric.api.event.lifecycle.v1.CommonLifecycleEvents;
import net.fabricmc.fabric.api.registry.FuelRegistry;
// 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 final Object2IntMap<ItemConvertible> itemCookTimes = 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() {
// Reset cache after tags change since it depends on tags.
CommonLifecycleEvents.TAGS_LOADED.register((registries, client) -> {
resetCache();
});
}
public Map<Item, Integer> getFuelTimes() {
Map<Item, Integer> ret = fuelTimeCache;
if (ret == null) {
fuelTimeCache = ret = new IdentityHashMap<>(AbstractFurnaceBlockEntity.createFuelTimeMap()); // IdentityHashMap is faster than vanilla's LinkedHashMap and suitable for Item keys
}
return ret;
// Cached by vanilla now
return AbstractFurnaceBlockEntity.createFuelTimeMap();
}
@Override
@ -138,6 +126,7 @@ public final class FuelRegistryImpl implements FuelRegistry {
}
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
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
world.emitGameEvent(player, TEST_EVENT, pos);
return ActionResult.SUCCESS;

View file

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

View file

@ -56,7 +56,7 @@ public interface FabricElytraItem {
if (!entity.getWorld().isClient && nextRoll % 10 == 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);

View file

@ -40,7 +40,7 @@ abstract class LivingEntityMixin extends Entity {
* Handle ALLOW and CUSTOM {@link EntityElytraEvents} when an entity is fall flying.
*/
@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) {
LivingEntity self = (LivingEntity) (Object) this;

View file

@ -46,7 +46,7 @@ public class TestBedBlock extends Block {
}
@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)) {
player.sendMessage(Text.translatable("block.minecraft.bed.occupied"), true);
return ActionResult.CONSUME;

View file

@ -21,7 +21,6 @@ import org.jetbrains.annotations.Nullable;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.listener.PacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerPlayNetworkHandler;
@ -31,7 +30,7 @@ public class FakePlayerNetworkHandler extends ServerPlayNetworkHandler {
private static final ClientConnection FAKE_CONNECTION = new FakeClientConnection();
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
@ -41,9 +40,5 @@ public class FakePlayerNetworkHandler extends ServerPlayNetworkHandler {
private FakeClientConnection() {
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.server.MinecraftServer;
import net.minecraft.server.command.TestCommand;
import net.minecraft.test.GameTestBatch;
import net.minecraft.test.TestContext;
import net.minecraft.test.TestFailureLogger;
import net.minecraft.test.TestFunction;
import net.minecraft.test.TestFunctions;
import net.minecraft.test.TestServer;
import net.minecraft.test.TestUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.level.storage.LevelStorage;
@ -80,7 +78,7 @@ public final class FabricGameTestHelper {
LOGGER.info("Starting test server");
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() {
return TestFunctions.getTestFunctions();
}

View file

@ -16,26 +16,27 @@
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.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
* 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
public interface CustomDamageHandler {
/**
* Called to apply damage to the given stack.
* This can be used to e.g. drain from a battery before actually damaging the item.
* @param amount The amount of damage originally requested
* @param breakCallback Callback when the stack reaches zero damage. See {@link ItemStack#damage(int, LivingEntity, Consumer)} and its callsites for more information.
* Note that this does not get called if non-entities, such as dispensers, are damaging the item.
* 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
*/
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
* {@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
* 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.util.Hand;
import net.fabricmc.fabric.impl.item.FabricItemInternals;
/**
* General-purpose Fabric-provided extensions for {@link Item} subclasses.
*
@ -136,4 +138,32 @@ public interface FabricItem {
default @Nullable FoodComponent getFoodComponent(ItemStack stack) {
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;
/**
* Fabric's version of Item.Settings. Adds additional methods and hooks
* not found in the original class.
*
* <p>To use it, simply replace {@code new Item.Settings()} with
* {@code new FabricItemSettings()}.
* @deprecated replace with {@link Item.Settings}
*/
@Deprecated
public class FabricItemSettings extends Item.Settings {
/**
* Sets the equipment slot provider of the item.
*
* @param equipmentSlotProvider the equipment slot provider
* @return this builder
* @deprecated replace with {@link FabricItem.Settings#equipmentSlot(EquipmentSlotProvider)}
*/
@Deprecated
public FabricItemSettings equipmentSlot(EquipmentSlotProvider equipmentSlotProvider) {
FabricItemInternals.computeExtraData(this).equipmentSlot(equipmentSlotProvider);
return this;
@ -47,8 +46,10 @@ public class FabricItemSettings extends Item.Settings {
* Sets the custom damage handler of the item.
* 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
*/
@Deprecated
public FabricItemSettings customDamage(CustomDamageHandler handler) {
FabricItemInternals.computeExtraData(this).customDamage(handler);
return this;

View file

@ -14,7 +14,14 @@
* limitations under the License.
*/
/**
* API for working with screen handlers on the client.
*/
package net.fabricmc.fabric.api.client.screenhandler.v1;
package net.fabricmc.fabric.mixin.item;
import org.spongepowered.asm.mixin.Mixin;
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;
import java.util.function.Consumer;
import com.google.common.collect.LinkedHashMultimap;
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.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
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.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.block.BlockState;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttribute;
import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
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.FabricItemStack;
@ -48,33 +48,23 @@ import net.fabricmc.fabric.impl.item.ItemExtensions;
public abstract class ItemStackMixin implements FabricItemStack {
@Shadow public abstract Item getItem();
@Unique
private LivingEntity fabric_damagingEntity;
@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) {
@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 void hookDamage(ItemStack instance, int amount, Random random, ServerPlayerEntity serverPlayerEntity, Runnable runnable, Operation<Void> original, @Local(argsOnly = true) EquipmentSlot slot) {
CustomDamageHandler handler = ((ItemExtensions) getItem()).fabric_getCustomDamageHandler();
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;
}
@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;
original.call(instance, amount, random, serverPlayerEntity, runnable);
}
@Redirect(

View file

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

View file

@ -31,6 +31,7 @@
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"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"]
}
}

View file

@ -30,7 +30,6 @@ import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
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.FuelRegistry;
@ -44,7 +43,7 @@ public class CustomDamageTest implements ModInitializer {
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 (entity.isSneaking()) {
return amount;
@ -57,7 +56,7 @@ public class CustomDamageTest implements ModInitializer {
public static class WeirdPick extends PickaxeItem {
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

View file

@ -35,7 +35,7 @@ public class FabricItemSettingsTests implements ModInitializer {
@Override
public void onInitialize() {
// 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);
final List<String> missingMethods = new ArrayList<>();

View file

@ -26,11 +26,10 @@ import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
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 NAME = Registry.register(Registries.ITEM, new Identifier("fabric-item-api-v1-testmod", "name_food"), new NameFood(new FabricItemSettings()));
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 Item.Settings()));
@Override
public void onInitialize() {

View file

@ -19,94 +19,55 @@ package net.fabricmc.fabric.api.client.networking.v1;
import java.util.Objects;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient;
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.network.packet.CustomPayload;
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.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.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.
*
* <p>Client-side networking functionalities include receiving clientbound packets,
* 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>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.
*
* @see ServerConfigurationNetworking
*/
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.
* 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.
* 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 handler the handler
* @return false if a handler is already registered to the channel
* @see ClientConfigurationNetworking#unregisterGlobalReceiver(PacketType)
* @see ClientConfigurationNetworking#registerReceiver(PacketType, ConfigurationPacketHandler)
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#configurationS2C() registered} yet
* @see ClientConfigurationNetworking#unregisterGlobalReceiver(CustomPayload.Id)
* @see ClientConfigurationNetworking#registerReceiver(CustomPayload.Id, ConfigurationPayloadHandler)
*/
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, ConfigurationPacketHandler<T> handler) {
return ClientNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.getId(), wrapTyped(type, 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));
public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, ConfigurationPayloadHandler<T> handler) {
return ClientNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.id(), handler);
}
/**
@ -115,15 +76,15 @@ public final class ClientConfigurationNetworking {
*
* <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,
* or it was not registered using {@link #registerGlobalReceiver(PacketType, ConfigurationPacketHandler)}
* @see ClientConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler)
* @see ClientConfigurationNetworking#unregisterReceiver(PacketType)
* or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, ConfigurationPayloadHandler)}
* @see ClientConfigurationNetworking#registerGlobalReceiver(CustomPayload.Id, ConfigurationPayloadHandler)
* @see ClientConfigurationNetworking#unregisterReceiver(Identifier)
*/
@Nullable
public static <T extends FabricPacket> ClientConfigurationNetworking.ConfigurationPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) {
return unwrapTyped(ClientNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(type.getId()));
public static ClientConfigurationNetworking.ConfigurationPayloadHandler<?> unregisterGlobalReceiver(CustomPayload.Id<?> id) {
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.
*
* <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.
*
* <p>For new code, {@link #registerReceiver(PacketType, ConfigurationPacketHandler)}
* 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 id the payload id
* @param handler the handler
* @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
* @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();
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!");
}
/**
* 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.
*
* <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,
* 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
*/
@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();
if (addon != null) {
return unwrapTyped(addon.unregisterChannel(type.getId()));
return addon.unregisterChannel(id);
}
throw new IllegalStateException("Cannot unregister receiver while not configuring!");
@ -285,22 +200,8 @@ public final class ClientConfigurationNetworking {
* @param type the packet type
* @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) {
return canSend(type.getId());
}
/**
* 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);
public static boolean canSend(CustomPayload.Id<?> type) {
return canSend(type.id());
}
/**
@ -322,35 +223,19 @@ public final class ClientConfigurationNetworking {
/**
* Sends a packet to the connected server.
*
* @param channelName the channel of the packet
* @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.
* <p>Any packets sent must be {@linkplain PayloadTypeRegistry#configurationC2S() registered}.</p>
*
* @param packet the packet
* @param payload to be sent
* @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");
public static void send(CustomPayload payload) {
Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
final ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (addon != null) {
addon.sendPacket(packet);
addon.sendPacket(payload);
return;
}
@ -360,98 +245,37 @@ public final class 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
*/
@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
* call client methods.
* Handles the incoming packet.
*
* <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:
* <pre>{@code
* // See FabricPacket for creating the packet
* ClientConfigurationNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, packet, responseSender) -> {
* MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true);
* ClientConfigurationNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (packet, responseSender) -> {
* });
* }</pre>
*
*
* @param packet the packet
* @param responseSender the packet sender
* @see FabricPacket
* @param payload the packet payload
* @param context the configuration networking context
* @see CustomPayload
*/
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.function.Consumer;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientLoginNetworkHandler;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.listener.PacketListener;
import net.minecraft.util.Identifier;
@ -153,10 +152,10 @@ public final class ClientLoginNetworking {
* @param client the client
* @param handler the network handler that received this 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.
* 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.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ServerCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
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.PacketType;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.client.ClientNetworkingImpl;
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.
*
* <p>Client-side networking functionalities include receiving clientbound packets,
* 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>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.
*
* @see ClientLoginNetworking
@ -57,76 +55,38 @@ import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
*/
public final class ClientPlayNetworking {
/**
* 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, 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.
* Registers a handler for a payload type.
* 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.
* 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
* @return false if a handler is already registered to the channel
* @see ClientPlayNetworking#unregisterGlobalReceiver(PacketType)
* @see ClientPlayNetworking#registerReceiver(PacketType, PlayPacketHandler)
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#playS2C() registered} yet
* @see ClientPlayNetworking#unregisterGlobalReceiver(Identifier)
* @see ClientPlayNetworking#registerReceiver(CustomPayload.Id, PlayPayloadHandler)
*/
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, PlayPacketHandler<T> handler) {
return ClientNetworkingImpl.PLAY.registerGlobalReceiver(type.getId(), wrapTyped(type, handler));
public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, PlayPayloadHandler<T> handler) {
return ClientNetworkingImpl.PLAY.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 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.
* Removes the handler for a payload 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
* @param id the payload id
* @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 ClientPlayNetworking#registerGlobalReceiver(PacketType, PlayPacketHandler)
* @see ClientPlayNetworking#unregisterReceiver(PacketType)
* or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)}
* @see ClientPlayNetworking#registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)
* @see ClientPlayNetworking#unregisterReceiver(Identifier)
*/
@Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) {
return unwrapTyped(ClientNetworkingImpl.PLAY.unregisterGlobalReceiver(type.getId()));
public static ClientPlayNetworking.PlayPayloadHandler<?> unregisterGlobalReceiver(Identifier id) {
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.
*
* <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.
*
* <p>For new code, {@link #registerReceiver(PacketType, PlayPacketHandler)}
* 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 type the payload type
* @param handler the handler
* @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
* @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();
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!");
}
/**
* 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 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.
* Removes the handler for a payload id.
*
* <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,
* 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
*/
@Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterReceiver(PacketType<T> type) {
public static ClientPlayNetworking.PlayPayloadHandler<?> unregisterReceiver(Identifier id) {
final ClientPlayNetworkAddon addon = ClientNetworkingImpl.getClientPlayAddon();
if (addon != null) {
return unwrapTyped(addon.unregisterChannel(type.getId()));
return addon.unregisterChannel(id);
}
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
* @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.
*/
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.
*
* @param type the packet type
* @return {@code true} if the connected server has declared the ability to receive a packet on the specified channel
* @param type the payload type
* @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) {
return canSend(type.getId());
public static boolean canSend(CustomPayload.Id<?> type) {
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 buf the packet byte buf which represents the payload of the packet
* @return a new packet
* @param packet the fabric payload
* @return a new payload
*/
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);
}
/**
* 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) {
public static <T extends CustomPayload> Packet<ServerCommonPacketListener> createC2SPacket(T 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
*/
public static PacketSender getSender() throws IllegalStateException {
@ -327,39 +227,24 @@ public final class ClientPlayNetworking {
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
* @param buf the payload of the packet
* <p>Any packets sent must be {@linkplain PayloadTypeRegistry#playC2S() registered}.</p>
*
* @param payload the payload
* @throws IllegalStateException if the client is not connected to a server
*/
public static void send(Identifier channelName, PacketByteBuf buf) throws IllegalStateException {
// You cant send without a client player, so this is fine
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
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");
public static void send(CustomPayload payload) {
Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
// You cant send without a client player, so this is fine
if (MinecraftClient.getInstance().getNetworkHandler() != null) {
MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(packet));
MinecraftClient.getInstance().getNetworkHandler().sendPacket(createC2SPacket(payload));
return;
}
@ -369,98 +254,48 @@ public final class 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}.
* @param <T> the type of the packet
* A thread-safe payload handler utilizing {@link CustomPayload}.
* @param <T> the type of the payload
*/
@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.
*
* <p>An example usage of this is to display an overlay message:
* <pre>{@code
* // See FabricPacket for creating the packet
* ClientPlayNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, packet, responseSender) -> {
* MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true);
* // See FabricPacket for creating the payload
* ClientPlayNetworking.registerReceiver(OVERLAY_PACKET_TYPE, (player, payload, responseSender) -> {
* MinecraftClient.getInstance().inGameHud.setOverlayMessage(payload.message(), true);
* });
* }</pre>
*
* <p>The network handler can be accessed via {@link ClientPlayerEntity#networkHandler}.
*
* @param packet the packet
* @param player the player that received the packet
* @param responseSender the packet sender
* @see FabricPacket
* @param payload the packet payload
* @param context the play networking context
* @see CustomPayload
*/
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.network.ClientConfigurationNetworkHandler;
import net.minecraft.network.NetworkState;
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.util.Identifier;
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.ClientConfigurationNetworking;
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.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.impl.networking.RegistrationPayload;
import net.fabricmc.fabric.mixin.networking.client.accessor.ClientCommonNetworkHandlerAccessor;
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 MinecraftClient client;
private final ContextImpl context;
private boolean sentInitialRegisterPacket;
public ClientConfigurationNetworkAddon(ClientConfigurationNetworkHandler handler, MinecraftClient client) {
super(ClientNetworkingImpl.CONFIGURATION, ((ClientCommonNetworkHandlerAccessor) handler).getConnection(), "ClientPlayNetworkAddon for " + ((ClientConfigurationNetworkHandlerAccessor) handler).getProfile().getName());
this.handler = handler;
this.client = client;
this.context = new ContextImpl(this);
// Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.CONFIGURATION);
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION);
}
@Override
@ -63,8 +64,8 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
}
@Override
protected void receiveRegistration(boolean register, ResolvablePayload resolvable) {
super.receiveRegistration(register, resolvable);
protected void receiveRegistration(boolean register, RegistrationPayload payload) {
super.receiveRegistration(register, payload);
if (register && !this.sentInitialRegisterPacket) {
this.sendInitialChannelRegistrationPacket();
@ -73,8 +74,8 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
}
@Override
protected void receive(Handler handler, ResolvedPayload payload) {
handler.receive(this.client, this.handler, payload, this);
protected void receive(ClientConfigurationNetworking.ConfigurationPayloadHandler<?> handler, CustomPayload payload) {
((ClientConfigurationNetworking.ConfigurationPayloadHandler) handler).receive(payload, this.context);
}
// impl details
@ -85,12 +86,7 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
}
@Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
return ClientPlayNetworking.createC2SPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
public Packet<?> createPacket(CustomPayload packet) {
return ClientPlayNetworking.createC2SPacket(packet);
}
@ -108,10 +104,10 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel
if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.REGISTER, Collections.singleton(channelName));
if (buf != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf);
if (payload != null) {
this.sendPacket(payload);
}
}
}
@ -120,10 +116,10 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel
if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.UNREGISTER, Collections.singleton(channelName));
if (buf != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf);
if (payload != null) {
this.sendPacket(payload);
}
}
}
@ -147,7 +143,6 @@ public final class ClientConfigurationNetworkAddon extends AbstractChanneledNetw
return (ChannelInfoHolder) ((ClientCommonNetworkHandlerAccessor) handler).getConnection();
}
public interface Handler {
void receive(MinecraftClient client, ClientConfigurationNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
private record ContextImpl(PacketSender responseSender) implements ClientConfigurationNetworking.Context {
}
}

View file

@ -20,23 +20,20 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientLoginNetworkHandler;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents;
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.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.PacketByteBufLoginQueryResponse;
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);
List<GenericFutureListener<? extends Future<? super Void>>> futureListeners = new ArrayList<>();
List<PacketCallbacks> callbacks = new ArrayList<>();
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 -> {
LoginQueryResponseC2SPacket packet = new LoginQueryResponseC2SPacket(queryId, result == null ? null : new PacketByteBufLoginQueryResponse(result));
GenericFutureListener<? extends Future<? super Void>> listener = null;
for (GenericFutureListener<? extends Future<? super Void>> each : futureListeners) {
listener = FutureListeners.union(listener, each);
}
((ClientLoginNetworkHandlerAccessor) this.handler).getConnection().send(packet, GenericFutureListenerHolder.create(listener));
((ClientLoginNetworkHandlerAccessor) this.handler).getConnection().send(packet, new PacketCallbacks() {
@Override
public void onSuccess() {
callbacks.forEach(PacketCallbacks::onSuccess);
}
});
});
} catch (Throwable 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.ClientPlayNetworkHandler;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.listener.ServerCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
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.ClientConfigurationNetworking;
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.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.CommonPacketsImpl;
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.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
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;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
import net.fabricmc.fabric.mixin.networking.client.accessor.ConnectScreenAccessor;
import net.fabricmc.fabric.mixin.networking.client.accessor.MinecraftClientAccessor;
public final class ClientNetworkingImpl {
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ClientConfigurationNetworkAddon.Handler>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ClientPlayNetworkAddon.Handler>> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY);
public static final GlobalReceiverRegistry<ClientLoginNetworking.LoginQueryRequestHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkSide.CLIENTBOUND, NetworkPhase.LOGIN, null);
public static final GlobalReceiverRegistry<ClientConfigurationNetworking.ConfigurationPayloadHandler<?>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkSide.CLIENTBOUND, NetworkPhase.CONFIGURATION, PayloadTypeRegistryImpl.CONFIGURATION_S2C);
public static final GlobalReceiverRegistry<ClientPlayNetworking.PlayPayloadHandler<?>> PLAY = new GlobalReceiverRegistry<>(NetworkSide.CLIENTBOUND, NetworkPhase.PLAY, PayloadTypeRegistryImpl.PLAY_S2C);
private static ClientPlayNetworkAddon currentPlayAddon;
private static ClientConfigurationNetworkAddon currentConfigurationAddon;
@ -73,22 +69,15 @@ public final class ClientNetworkingImpl {
return (ClientLoginNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
}
public static Packet<ServerCommonPacketListener> createC2SPacket(Identifier channelName, PacketByteBuf buf) {
return new CustomPayloadC2SPacket(new UntypedPayload(channelName, buf));
}
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);
public static Packet<ServerCommonPacketListener> createC2SPacket(CustomPayload payload) {
Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
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
public static ClientConnection getLoginConnection() {
@ -153,28 +142,26 @@ public final class ClientNetworkingImpl {
});
// Version packet
ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (client, handler, buf, responseSender) -> {
var payload = new CommonVersionPayload(buf);
int negotiatedVersion = handleVersionPacket(payload, responseSender);
ClientNetworkingImpl.getAddon(handler).onCommonVersionPacket(negotiatedVersion);
ClientConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.ID, (payload, context) -> {
int negotiatedVersion = handleVersionPacket(payload, context.responseSender());
ClientNetworkingImpl.getClientConfigurationAddon().onCommonVersionPacket(negotiatedVersion);
});
// Register packet
ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (client, handler, buf, responseSender) -> {
var payload = new CommonRegisterPayload(buf);
ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getAddon(handler);
ClientConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.ID, (payload, context) -> {
ClientConfigurationNetworkAddon addon = ClientNetworkingImpl.getClientConfigurationAddon();
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
if (payload.version() != addon.getNegotiatedVersion()) {
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");
responseSender.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers()));
context.responseSender().sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ClientPlayNetworking.getGlobalReceivers()));
} else {
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.network.ClientPlayNetworkHandler;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
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.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
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 MinecraftClient client;
private final ClientPlayNetworking.Context context;
private boolean sentInitialRegisterPacket;
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());
this.handler = handler;
this.client = client;
this.context = new ContextImpl(client, client.player, this);
// Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.PLAY);
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.PLAY);
}
@Override
@ -73,8 +75,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
}
@Override
protected void receive(Handler handler, ResolvedPayload payload) {
handler.receive(this.client, this.handler, payload, this);
protected void receive(ClientPlayNetworking.PlayPayloadHandler<?> handler, CustomPayload payload) {
this.client.execute(() -> {
((ClientPlayNetworking.PlayPayloadHandler) handler).receive(payload, context);
});
}
// impl details
@ -85,12 +89,7 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
}
@Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
return ClientPlayNetworking.createC2SPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
public Packet<?> createPacket(CustomPayload packet) {
return ClientPlayNetworking.createC2SPacket(packet);
}
@ -108,10 +107,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel
if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.REGISTER, Collections.singleton(channelName));
if (buf != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf);
if (payload != null) {
this.sendPacket(payload);
}
}
}
@ -120,10 +119,10 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel
if (this.sentInitialRegisterPacket) {
final PacketByteBuf buf = this.createRegistrationPacket(Collections.singleton(channelName));
final RegistrationPayload payload = this.createRegistrationPayload(RegistrationPayload.UNREGISTER, Collections.singleton(channelName));
if (buf != null) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf);
if (payload != null) {
this.sendPacket(payload);
}
}
}
@ -138,7 +137,6 @@ public final class ClientPlayNetworkAddon extends AbstractChanneledNetworkAddon<
return NetworkingImpl.isReservedCommonChannel(channelName);
}
public interface Handler {
void receive(MinecraftClient client, ClientPlayNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
private record ContextImpl(MinecraftClient client, ClientPlayerEntity player, PacketSender responseSender) implements ClientPlayNetworking.Context {
}
}

View file

@ -25,13 +25,12 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.network.ClientCommonNetworkHandler;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.client.ClientConfigurationNetworkAddon;
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)
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)
public void onCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci) {
if (packet.payload() instanceof ResolvablePayload payload) {
boolean handled;
final CustomPayload payload = packet.payload();
boolean handled;
if (this.getAddon() instanceof ClientPlayNetworkAddon addon) {
handled = addon.handle(payload);
} else if (this.getAddon() instanceof ClientConfigurationNetworkAddon addon) {
handled = addon.handle(payload);
} else {
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 (this.getAddon() instanceof ClientPlayNetworkAddon addon) {
handled = addon.handle(payload);
} else if (this.getAddon() instanceof ClientConfigurationNetworkAddon addon) {
handled = addon.handle(payload);
} else {
throw new IllegalStateException("Unknown network addon");
}
if (handled) {
ci.cancel();
}
}

View file

@ -51,7 +51,7 @@ public abstract class ClientConfigurationNetworkHandlerMixin extends ClientCommo
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) {
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;
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.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
import net.minecraft.text.Text;
/**
* Represents something that supports sending packets to channels.
* @see PacketByteBufs
* Any packets sent must be {@linkplain PayloadTypeRegistry registered} in the appropriate registry.
*/
@ApiStatus.NonExtendable
public interface PacketSender {
/**
* Makes a packet for a channel.
* Creates a packet from a packet payload.
*
* @param channelName the id of the channel
* @param buf the content of the packet
* @param payload the packet payload
*/
Packet<?> createPacket(Identifier channelName, PacketByteBuf buf);
/**
* Makes a packet for a fabric packet.
*
* @param packet the fabric packet
*/
Packet<?> createPacket(FabricPacket packet);
Packet<?> createPacket(CustomPayload payload);
/**
* Sends a packet.
@ -59,15 +43,7 @@ public interface PacketSender {
* @param packet the packet
*/
default void sendPacket(Packet<?> packet) {
sendPacket(packet, (PacketCallbacks) null);
}
/**
* Sends a packet.
* @param packet the packet
*/
default <T extends FabricPacket> void sendPacket(T packet) {
sendPacket(createPacket(packet));
sendPacket(packet, null);
}
/**
@ -75,107 +51,30 @@ public interface PacketSender {
* @param payload the payload
*/
default void sendPacket(CustomPayload payload) {
PacketByteBuf buf = PacketByteBufs.create();
payload.write(buf);
sendPacket(payload.id(), buf);
sendPacket(createPacket(payload));
}
/**
* 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 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}.
* @param callback an optional callback to execute after the packet is sent, may be {@code null}.
*/
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.
*
* @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) {
PacketByteBuf buf = PacketByteBufs.create();
payload.write(buf);
sendPacket(payload.id(), buf, callback);
sendPacket(createPacket(payload), callback);
}
/**
* Sends a packet to a channel.
*
* @param channel the id of the channel
* @param buf the content of the packet
* Disconnects the player.
* @param disconnectReason the reason for disconnection
*/
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}
*/
// 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);
}
void disconnect(Text disconnectReason);
}

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.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
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.ServerConfigurationNetworkAddon;
import net.fabricmc.fabric.impl.networking.server.ServerNetworkingImpl;
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.
*
* <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>See {@link ServerPlayNetworking} for information on how to use the packet
* object-based API.
* <p>See {@link ServerPlayNetworking} for information on sending and receiving play phase packets.
*
* <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 {
/**
* 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, 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.
* Registers a handler for a payload type.
* 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.
* 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 handler the handler
* @return {@code false} if a handler is already registered to the channel
* @see ServerConfigurationNetworking#unregisterGlobalReceiver(PacketType)
* @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler)
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#configurationC2S() registered} yet
* @see ServerConfigurationNetworking#unregisterGlobalReceiver(Identifier)
* @see ServerConfigurationNetworking#registerReceiver(ServerConfigurationNetworkHandler, CustomPayload.Id, ConfigurationPacketHandler)
*/
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, ConfigurationPacketHandler<T> handler) {
return ServerNetworkingImpl.CONFIGURATION.registerGlobalReceiver(type.getId(), wrapTyped(type, handler));
public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, ConfigurationPacketHandler<T> handler) {
return ServerNetworkingImpl.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 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.
* Removes the handler for a payload 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
* @param id the packet payload id
* @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)}
* @see ServerConfigurationNetworking#registerGlobalReceiver(PacketType, ConfigurationPacketHandler)
* @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, PacketType)
* or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, ConfigurationPacketHandler)}
* @see ServerConfigurationNetworking#registerGlobalReceiver(CustomPayload.Id, ConfigurationPacketHandler)
* @see ServerConfigurationNetworking#unregisterReceiver(ServerConfigurationNetworkHandler, Identifier)
*/
@Nullable
public static <T extends FabricPacket> ServerConfigurationNetworking.ConfigurationPacketHandler<T> unregisterGlobalReceiver(PacketType<T> type) {
return unwrapTyped(ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(type.getId()));
public static ServerConfigurationNetworking.ConfigurationPacketHandler<?> unregisterGlobalReceiver(Identifier id) {
return ServerNetworkingImpl.CONFIGURATION.unregisterGlobalReceiver(id);
}
/**
@ -136,82 +97,36 @@ public final class ServerConfigurationNetworking {
}
/**
* Registers a handler to a channel.
* This method differs from {@link ServerConfigurationNetworking#registerGlobalReceiver(Identifier, ConfigurationChannelHandler)} since
* Registers a handler for a payload type.
* 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}.
*
* <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.
* 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 type the packet type
* @param handler the handler
* @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
*/
public static <T extends FabricPacket> boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, PacketType<T> type, ConfigurationPacketHandler<T> handler) {
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.getId(), wrapTyped(type, handler));
public static <T extends CustomPayload> boolean registerReceiver(ServerConfigurationNetworkHandler networkHandler, CustomPayload.Id<T> type, ConfigurationPacketHandler<T> 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 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.
* Removes the handler for a payload type.
*
* <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,
* or it was not registered using {@link #registerReceiver(ServerConfigurationNetworkHandler, PacketType, ConfigurationPacketHandler)}
* or it was not registered using {@link #registerReceiver(ServerConfigurationNetworkHandler, CustomPayload.Id, ConfigurationPacketHandler)}
*/
@Nullable
public static <T extends FabricPacket> ServerConfigurationNetworking.ConfigurationPacketHandler<T> unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, PacketType<T> type) {
return unwrapTyped(ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(type.getId()));
public static ServerConfigurationNetworking.ConfigurationPacketHandler<?> unregisterReceiver(ServerConfigurationNetworkHandler networkHandler, Identifier id) {
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.
*
* @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
*/
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(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.
*
* @param channelName the channel name
* @param buf the packet byte buf which represents the payload of the packet
* @param payload the payload
* @return a new packet
*/
public static Packet<ClientCommonPacketListener> createS2CPacket(Identifier channelName, PacketByteBuf buf) {
Objects.requireNonNull(channelName, "Channel cannot be null");
Objects.requireNonNull(buf, "Buf cannot be null");
public static Packet<ClientCommonPacketListener> createS2CPacket(CustomPayload payload) {
Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
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) {
Objects.requireNonNull(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
return ServerNetworkingImpl.createS2CPacket(packet);
return ServerNetworkingImpl.createS2CPacket(payload);
}
/**
@ -308,30 +209,17 @@ public final class ServerConfigurationNetworking {
/**
* Sends a packet to a configuring player.
*
* @param handler the handler to send the packet to
* @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.
* <p>Any packets sent must be {@linkplain PayloadTypeRegistry#configurationS2C() registered}.</p>
*
* @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(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
Objects.requireNonNull(payload, "Payload cannot be 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
@ -350,72 +238,16 @@ public final class 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
*/
@FunctionalInterface
public interface ConfigurationPacketHandler<T extends FabricPacket> {
public interface ConfigurationPacketHandler<T extends CustomPayload> {
/**
* 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)}.
*
* <p>An example usage of this:
@ -427,11 +259,23 @@ public final class ServerConfigurationNetworking {
* }</pre>
*
*
* @param packet the packet
* @param networkHandler the network handler
* @param responseSender the packet sender
* @see FabricPacket
* @param payload the packet payload
* @param context the configuration networking context
* @see CustomPayload
*/
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)}
* 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) -> {
for (QueryStart callback : callbacks) {
@ -79,7 +79,7 @@ public final class ServerLoginConnectionEvents {
*/
@FunctionalInterface
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.Set;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
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.ServerPlayNetworkAddon;
/**
* 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.
* 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.
*
* <h2>Packet object-based API</h2>
*
* <p>This class provides a classic registration method, {@link #registerGlobalReceiver(Identifier, PlayChannelHandler)},
* and a newer method utilizing packet objects, {@link #registerGlobalReceiver(PacketType, PlayPacketHandler)}.
* 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>This class provides a registration method, utilizing packet objects, {@link #registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)}.
* This handler executes the callback in the server thread, ensuring thread safety.
*
* <p>The newer, packet object-based API involves three classes:
* <p>This payload object-based API involves three classes:
*
* <ul>
* <li>A class implementing {@link FabricPacket} that is "sent" over the network</li>
* <li>{@link PacketType} instance, which represents the packet's type (and its channel)</li>
* <li>{@link PlayPacketHandler}, which handles the packet (usually implemented as a functional interface)</li>
* <li>A class implementing {@link CustomPayload} that is "sent" over the network</li>
* <li>{@link CustomPayload.Type} instance, which represents the packet's type (and its codec)</li>
* <li>{@link PlayPayloadHandler}, which handles the packet (usually implemented as a functional interface)</li>
* </ul>
*
* <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 {
/**
* 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, 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.
* Registers a handler for a payload type.
* 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.
* 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 handler the handler
* @return {@code false} if a handler is already registered to the channel
* @see ServerPlayNetworking#unregisterGlobalReceiver(PacketType)
* @see ServerPlayNetworking#registerReceiver(ServerPlayNetworkHandler, PacketType, PlayPacketHandler)
* @throws IllegalArgumentException if the codec for {@code type} has not been {@linkplain PayloadTypeRegistry#playC2S() registered} yet
* @see ServerPlayNetworking#unregisterGlobalReceiver(Identifier)
*/
public static <T extends FabricPacket> boolean registerGlobalReceiver(PacketType<T> type, PlayPacketHandler<T> handler) {
return ServerNetworkingImpl.PLAY.registerGlobalReceiver(type.getId(), wrapTyped(type, handler));
public static <T extends CustomPayload> boolean registerGlobalReceiver(CustomPayload.Id<T> type, PlayPayloadHandler<T> 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.
*
* <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
* @return the previous handler, or {@code null} if no handler was bound to the channel
* @see ServerPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler)
* @param id the payload id
* @return the previous handler, or {@code null} if no handler was bound to the channel,
* or it was not registered using {@link #registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)}
* @see ServerPlayNetworking#registerGlobalReceiver(CustomPayload.Id, PlayPayloadHandler)
* @see ServerPlayNetworking#unregisterReceiver(ServerPlayNetworkHandler, Identifier)
*/
@Nullable
public static PlayChannelHandler unregisterGlobalReceiver(Identifier channelName) {
return unwrapUntyped(ServerNetworkingImpl.PLAY.unregisterGlobalReceiver(channelName));
}
/**
* 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()));
public static ServerPlayNetworking.PlayPayloadHandler<?> unregisterGlobalReceiver(Identifier id) {
return ServerNetworkingImpl.PLAY.unregisterGlobalReceiver(id);
}
/**
@ -151,82 +105,39 @@ public final class ServerPlayNetworking {
}
/**
* Registers a handler to a channel.
* This method differs from {@link ServerPlayNetworking#registerGlobalReceiver(Identifier, PlayChannelHandler)} 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
* Registers a handler for a payload type.
* 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>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.
* 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 type the packet type
* @param handler the handler
* @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
*/
public static <T extends FabricPacket> boolean registerReceiver(ServerPlayNetworkHandler networkHandler, PacketType<T> type, PlayPacketHandler<T> handler) {
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.getId(), wrapTyped(type, 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));
public static <T extends CustomPayload> boolean registerReceiver(ServerPlayNetworkHandler networkHandler, CustomPayload.Id<T> type, PlayPayloadHandler<T> handler) {
return ServerNetworkingImpl.getAddon(networkHandler).registerChannel(type.id(), handler);
}
/**
* 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,
* or it was not registered using {@link #registerReceiver(ServerPlayNetworkHandler, PacketType, PlayPacketHandler)}
* or it was not registered using {@link #registerReceiver(ServerPlayNetworkHandler, CustomPayload.Id, PlayPayloadHandler)}
*/
@Nullable
public static <T extends FabricPacket> PlayPacketHandler<T> unregisterReceiver(ServerPlayNetworkHandler networkHandler, PacketType<T> type) {
return unwrapTyped(ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(type.getId()));
public static ServerPlayNetworking.PlayPayloadHandler<?> unregisterReceiver(ServerPlayNetworkHandler networkHandler, Identifier id) {
return ServerNetworkingImpl.getAddon(networkHandler).unregisterChannel(id);
}
/**
@ -297,10 +208,10 @@ public final class ServerPlayNetworking {
* @param type the packet type
* @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");
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
* @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(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.
*
* @param channelName the channel name
* @param buf the packet byte buf which represents the payload of the packet
* @param packet the packet
* @return a new packet
*/
public static Packet<ClientCommonPacketListener> createS2CPacket(Identifier channelName, PacketByteBuf buf) {
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) {
public static <T extends CustomPayload> Packet<ClientCommonPacketListener> createS2CPacket(T packet) {
return ServerNetworkingImpl.createS2CPacket(packet);
}
@ -382,142 +279,60 @@ public final class ServerPlayNetworking {
/**
* Sends a packet to a player.
*
* @param player the player to send the packet to
* @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.
* <p>Any packets sent must be {@linkplain PayloadTypeRegistry#playS2C() registered}.</p>
*
* @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(packet, "Packet cannot be null");
Objects.requireNonNull(packet.getType(), "Packet#getType cannot return null");
Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
player.networkHandler.sendPacket(createS2CPacket(packet));
}
// 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;
player.networkHandler.sendPacket(createS2CPacket(payload));
}
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
*/
@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
* manipulate the world.
*
* <p>An example usage of this is to create an explosion where the player is looking:
* <pre>{@code
* // See FabricPacket for creating the packet
* ServerPlayNetworking.registerReceiver(BOOM_PACKET_TYPE, (player, packet, responseSender) -> {
* ModPacketHandler.createExplosion(player, packet.fire());
* // use PayloadTypeRegistry for registering the payload
* ServerPlayNetworking.registerReceiver(BoomPayload.ID, (payload, player, responseSender) -> {
* ModPacketHandler.createExplosion(player, payload.fire());
* });
* }</pre>
*
* <p>The server and the network handler can be accessed via {@link ServerPlayerEntity#server}
* and {@link ServerPlayerEntity#networkHandler}, respectively.
*
* @param packet the packet
* @param player the player that received the packet
* @param responseSender the packet sender
* @see FabricPacket
* @param payload the packet payload
* @param context the play networking context
* @see CustomPayload
*/
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>
*
* <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}.
*/

View file

@ -16,7 +16,6 @@
package net.fabricmc.fabric.impl.networking;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -25,46 +24,39 @@ import java.util.List;
import java.util.Objects;
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 net.minecraft.network.ClientConnection;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.text.Text;
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.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.
*
* @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 GlobalReceiverRegistry<ResolvablePayload.Handler<H>> receiver;
protected final GlobalReceiverRegistry<H> receiver;
protected final Set<Identifier> sendableChannels;
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);
this.connection = connection;
this.receiver = receiver;
this.sendableChannels = Collections.synchronizedSet(new HashSet<>());
}
protected void registerPendingChannels(ChannelInfoHolder holder, NetworkState state) {
final Collection<Identifier> pending = holder.getPendingChannelsNames(state);
protected void registerPendingChannels(ChannelInfoHolder holder, NetworkPhase state) {
final Collection<Identifier> pending = holder.fabric_getPendingChannelsNames(state);
if (!pending.isEmpty()) {
register(new ArrayList<>(pending));
@ -73,30 +65,31 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
}
// always supposed to handle async!
public boolean handle(ResolvablePayload resolvable) {
Identifier channelName = resolvable.id();
public boolean handle(CustomPayload payload) {
final Identifier channelName = payload.getId().id();
this.logger.debug("Handling inbound packet from channel with name \"{}\"", channelName);
// Handle reserved packets
if (NetworkingImpl.REGISTER_CHANNEL.equals(channelName)) {
this.receiveRegistration(true, resolvable);
return true;
if (payload instanceof RegistrationPayload registrationPayload) {
if (NetworkingImpl.REGISTER_CHANNEL.equals(channelName)) {
this.receiveRegistration(true, registrationPayload);
return true;
}
if (NetworkingImpl.UNREGISTER_CHANNEL.equals(channelName)) {
this.receiveRegistration(false, registrationPayload);
return true;
}
}
if (NetworkingImpl.UNREGISTER_CHANNEL.equals(channelName)) {
this.receiveRegistration(false, resolvable);
return true;
}
@Nullable ResolvablePayload.Handler<H> handler = this.getHandler(channelName);
@Nullable H handler = this.getHandler(channelName);
if (handler == null) {
return false;
}
try {
ResolvedPayload resolved = resolvable.resolve(handler.type());
this.receive(handler.internal(), resolved);
this.receive(handler, payload);
} catch (Throwable ex) {
this.logger.error("Encountered exception while handling in channel with name \"{}\"", channelName, ex);
throw ex;
@ -105,63 +98,31 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
return true;
}
protected abstract void receive(H handler, ResolvedPayload payload);
protected abstract void receive(H handler, CustomPayload payload);
protected void sendInitialChannelRegistrationPacket() {
final PacketByteBuf buf = this.createRegistrationPacket(this.getReceivableChannels());
final RegistrationPayload payload = createRegistrationPayload(RegistrationPayload.REGISTER, this.getReceivableChannels());
if (buf != null) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf);
if (payload != null) {
this.sendPacket(payload);
}
}
@Nullable
protected PacketByteBuf createRegistrationPacket(Collection<Identifier> channels) {
protected RegistrationPayload createRegistrationPayload(CustomPayload.Id<RegistrationPayload> id, Collection<Identifier> channels) {
if (channels.isEmpty()) {
return null;
}
PacketByteBuf buf = PacketByteBufs.create();
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;
return new RegistrationPayload(id, new ArrayList<>(channels));
}
// wrap in try with res (buf)
protected void receiveRegistration(boolean register, ResolvablePayload resolvable) {
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);
protected void receiveRegistration(boolean register, RegistrationPayload payload) {
if (register) {
register(ids);
register(payload.channels());
} else {
unregister(ids);
unregister(payload.channels());
}
}
@ -175,11 +136,6 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
schedule(() -> this.invokeUnregisterEvent(ids));
}
@Override
public void sendPacket(Packet<?> packet, @Nullable GenericFutureListener<? extends Future<? super Void>> callback) {
sendPacket(packet, GenericFutureListenerHolder.create(callback));
}
@Override
public void sendPacket(Packet<?> packet, PacketCallbacks callback) {
Objects.requireNonNull(packet, "Packet cannot be null");
@ -187,6 +143,13 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
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.
*/
@ -196,16 +159,6 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
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() {
return Collections.unmodifiableSet(this.sendableChannels);
}
@ -231,7 +184,7 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
if (currentPhase == null) {
// 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.
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;
}
@ -259,7 +212,7 @@ public abstract class AbstractChanneledNetworkAddon<H> extends AbstractNetworkAd
@Nullable
private String getPhase() {
return switch (receiver.getState()) {
return switch (receiver.getPhase()) {
case PLAY -> CommonRegisterPayload.PLAY_PHASE;
case CONFIGURATION -> CommonRegisterPayload.CONFIGURATION_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");
assertNotReserved(channelName);
receiver.assertPayloadType(channelName);
Lock lock = this.lock.writeLock();
lock.lock();

View file

@ -18,12 +18,12 @@ package net.fabricmc.fabric.impl.networking;
import java.util.Collection;
import net.minecraft.network.NetworkState;
import net.minecraft.network.NetworkPhase;
import net.minecraft.util.Identifier;
public interface ChannelInfoHolder {
/**
* @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.function.Consumer;
import net.minecraft.network.NetworkState;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.packet.Packet;
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.ServerConfigurationNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.server.ServerConfigurationNetworkAddon;
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 void init() {
ServerConfigurationNetworking.registerGlobalReceiver(CommonVersionPayload.PACKET_ID, (server, handler, buf, responseSender) -> {
var payload = new CommonVersionPayload(buf);
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
PayloadTypeRegistry.configurationC2S().register(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
PayloadTypeRegistry.configurationS2C().register(CommonVersionPayload.ID, CommonVersionPayload.CODEC);
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));
handler.completeTask(CommonVersionConfigurationTask.KEY);
context.networkHandler().completeTask(CommonVersionConfigurationTask.KEY);
});
ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.PACKET_ID, (server, handler, buf, responseSender) -> {
var payload = new CommonRegisterPayload(buf);
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
ServerConfigurationNetworking.registerGlobalReceiver(CommonRegisterPayload.ID, (payload, context) -> {
ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(context.networkHandler());
if (CommonRegisterPayload.PLAY_PHASE.equals(payload.phase())) {
if (payload.version() != addon.getNegotiatedVersion()) {
@ -50,24 +59,24 @@ public class CommonPacketsImpl {
}
// 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");
} else {
addon.onCommonRegisterPacket(payload);
}
handler.completeTask(CommonRegisterConfigurationTask.KEY);
context.networkHandler().completeTask(CommonRegisterConfigurationTask.KEY);
});
// Create a configuration task to send and receive the common packets
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
final ServerConfigurationNetworkAddon addon = ServerNetworkingImpl.getAddon(handler);
if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.PACKET_ID)) {
if (ServerConfigurationNetworking.canSend(handler, CommonVersionPayload.ID)) {
// Tasks are processed in order.
handler.addTask(new CommonVersionConfigurationTask(addon));
if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.PACKET_ID)) {
if (ServerConfigurationNetworking.canSend(handler, CommonRegisterPayload.ID)) {
handler.addTask(new CommonRegisterConfigurationTask(addon));
}
}
@ -76,7 +85,7 @@ public class CommonPacketsImpl {
// A configuration phase task to send and receive the version packets.
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
public void sendPacket(Consumer<Packet<?>> sender) {
@ -91,11 +100,11 @@ public class CommonPacketsImpl {
// A configuration phase task to send and receive the registration packets.
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
public void sendPacket(Consumer<Packet<?>> sender) {
addon.sendPacket(addon.createRegisterPayload());
addon.sendPacket(new CommonRegisterPayload(addon.getNegotiatedVersion(), CommonRegisterPayload.PLAY_PHASE, ServerPlayNetworking.getGlobalReceivers()));
}
@Override

View file

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

View file

@ -17,23 +17,23 @@
package net.fabricmc.fabric.impl.networking;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.util.Identifier;
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());
}
@Override
public void write(PacketByteBuf buf) {
buf.writeIntArray(versions);
}
@Override
public Identifier id() {
return PACKET_ID;
public Id<? extends CustomPayload> getId() {
return ID;
}
}

View file

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

View file

@ -14,7 +14,10 @@
* 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.LoggerFactory;
import net.minecraft.network.NetworkState;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.NetworkSide;
import net.minecraft.util.Identifier;
public final class GlobalReceiverRegistry<H> {
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 Map<Identifier, H> handlers;
private final Map<Identifier, H> handlers = new HashMap<>();
private final Set<AbstractNetworkAddon<H>> trackedAddons = new HashSet<>();
public GlobalReceiverRegistry(NetworkState state) {
this(state, new HashMap<>()); // sync map should be fine as there is little read write competitions
}
public GlobalReceiverRegistry(NetworkSide side, NetworkPhase phase, @Nullable PayloadTypeRegistryImpl<?> payloadTypeRegistry) {
this.side = side;
this.phase = phase;
this.payloadTypeRegistry = payloadTypeRegistry;
public GlobalReceiverRegistry(NetworkState state, Map<Identifier, H> map) {
this.state = state;
this.handlers = map;
if (payloadTypeRegistry != null) {
assert phase == payloadTypeRegistry.getPhase();
assert side == payloadTypeRegistry.getSide();
}
}
@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));
}
assertPayloadType(channelName);
Lock lock = this.lock.writeLock();
lock.lock();
@ -166,7 +174,7 @@ public final class GlobalReceiverRegistry<H> {
*/
private void logTrackedAddonSize() {
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() {
return state;
public void assertPayloadType(Identifier channelName) {
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.fabricmc.fabric.impl.networking.payload.TypedPayload;
import net.fabricmc.fabric.impl.networking.payload.UntypedPayload;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
public final class NetworkingImpl {
public static final String MOD_ID = "fabric-networking-api-v1";
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.
*/
@ -48,15 +37,18 @@ public final class NetworkingImpl {
*/
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) {
return channelName.equals(REGISTER_CHANNEL) || channelName.equals(UNREGISTER_CHANNEL);
}
static {
if (FORCE_PACKET_SERIALIZATION) {
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.");
}
public static void init() {
PayloadTypeRegistry.configurationS2C().register(RegistrationPayload.REGISTER, RegistrationPayload.REGISTER_CODEC);
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;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
@ -35,18 +34,6 @@ public class PayloadHelper {
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) {
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.List;
import net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.CommonPingS2CPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
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.S2CConfigurationChannelEvents;
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.impl.networking.AbstractChanneledNetworkAddon;
import net.fabricmc.fabric.impl.networking.ChannelInfoHolder;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
import net.fabricmc.fabric.impl.networking.payload.ResolvablePayload;
import net.fabricmc.fabric.impl.networking.payload.ResolvedPayload;
import net.fabricmc.fabric.impl.networking.RegistrationPayload;
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 MinecraftServer server;
private final ServerConfigurationNetworking.Context context;
private RegisterState registerState = RegisterState.NOT_SENT;
public ServerConfigurationNetworkAddon(ServerConfigurationNetworkHandler handler, MinecraftServer server) {
super(ServerNetworkingImpl.CONFIGURATION, ((ServerCommonNetworkHandlerAccessor) handler).getConnection(), "ServerConfigurationNetworkAddon for " + handler.getDebugProfile().getName());
this.handler = handler;
this.server = server;
this.context = new ContextImpl(handler, this);
// Must register pending channels via lateinit
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkState.CONFIGURATION);
this.registerPendingChannels((ChannelInfoHolder) this.connection, NetworkPhase.CONFIGURATION);
}
@Override
@ -84,7 +85,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
}
@Override
protected void receiveRegistration(boolean register, ResolvablePayload resolvable) {
protected void receiveRegistration(boolean register, RegistrationPayload resolvable) {
super.receiveRegistration(register, resolvable);
if (register && registerState == RegisterState.SENT) {
@ -103,8 +104,8 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
}
@Override
protected void receive(Handler handler, ResolvedPayload payload) {
handler.receive(this.server, this.handler, payload, this);
protected void receive(ServerConfigurationNetworking.ConfigurationPacketHandler<?> handler, CustomPayload payload) {
((ServerConfigurationNetworking.ConfigurationPacketHandler) handler).receive(payload, this.context);
}
// impl details
@ -115,12 +116,7 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
}
@Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
return ServerPlayNetworking.createS2CPacket(channelName, buf);
}
@Override
public Packet<?> createPacket(FabricPacket packet) {
public Packet<?> createPacket(CustomPayload packet) {
return ServerPlayNetworking.createS2CPacket(packet);
}
@ -138,10 +134,10 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleRegistration(Identifier channelName) {
// If we can already send packets, immediately send the register packet for this channel
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) {
this.sendPacket(NetworkingImpl.REGISTER_CHANNEL, buf);
if (registrationPayload != null) {
this.sendPacket(registrationPayload);
}
}
}
@ -150,10 +146,10 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
protected void handleUnregistration(Identifier channelName) {
// If we can already send packets, immediately send the unregister packet for this channel
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) {
this.sendPacket(NetworkingImpl.UNREGISTER_CHANNEL, buf);
if (registrationPayload != null) {
this.sendPacket(registrationPayload);
}
}
}
@ -184,7 +180,6 @@ public final class ServerConfigurationNetworkAddon extends AbstractChanneledNetw
return (ChannelInfoHolder) ((ServerCommonNetworkHandlerAccessor) handler).getConnection();
}
public interface Handler {
void receive(MinecraftServer server, ServerConfigurationNetworkHandler handler, ResolvedPayload payload, PacketSender responseSender);
private record ContextImpl(ServerConfigurationNetworkHandler networkHandler, PacketSender responseSender) implements ServerConfigurationNetworking.Context {
}
}

View file

@ -26,33 +26,31 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import io.netty.util.concurrent.GenericFutureListener;
import org.jetbrains.annotations.Nullable;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.PacketCallbacks;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.c2s.login.LoginQueryResponseC2SPacket;
import net.minecraft.network.packet.s2c.login.LoginCompressionS2CPacket;
import net.minecraft.network.packet.s2c.login.LoginQueryRequestS2CPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerLoginNetworkHandler;
import net.minecraft.text.Text;
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.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking;
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.PacketByteBufLoginQueryResponse;
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 ServerLoginNetworkHandler handler;
private final MinecraftServer server;
@ -160,23 +158,17 @@ public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLo
return true;
}
@Override
public Packet<?> createPacket(CustomPayload packet) {
throw new UnsupportedOperationException("Cannot send CustomPayload during login");
}
@Override
public Packet<?> createPacket(Identifier channelName, PacketByteBuf buf) {
int queryId = this.queryIdFactory.nextId();
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
public void sendPacket(Packet<?> packet, PacketCallbacks callback) {
Objects.requireNonNull(packet, "Packet cannot be null");
@ -184,6 +176,13 @@ public final class ServerLoginNetworkAddon extends AbstractNetworkAddon<ServerLo
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) {
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 net.minecraft.network.NetworkState;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.NetworkPhase;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.listener.ClientCommonPacketListener;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
import net.minecraft.server.network.ServerLoginNetworkHandler;
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.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
import net.fabricmc.fabric.impl.networking.NetworkingImpl;
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;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
public final class ServerNetworkingImpl {
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkState.LOGIN);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ServerConfigurationNetworkAddon.Handler>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkState.CONFIGURATION);
public static final GlobalReceiverRegistry<ResolvablePayload.Handler<ServerPlayNetworkAddon.Handler>> PLAY = new GlobalReceiverRegistry<>(NetworkState.PLAY);
public static final GlobalReceiverRegistry<ServerLoginNetworking.LoginQueryResponseHandler> LOGIN = new GlobalReceiverRegistry<>(NetworkSide.SERVERBOUND, NetworkPhase.LOGIN, null);
public static final GlobalReceiverRegistry<ServerConfigurationNetworking.ConfigurationPacketHandler<?>> CONFIGURATION = new GlobalReceiverRegistry<>(NetworkSide.SERVERBOUND, NetworkPhase.CONFIGURATION, PayloadTypeRegistryImpl.CONFIGURATION_C2S);
public static final GlobalReceiverRegistry<ServerPlayNetworking.PlayPayloadHandler<?>> PLAY = new GlobalReceiverRegistry<>(NetworkSide.SERVERBOUND, NetworkPhase.PLAY, PayloadTypeRegistryImpl.PLAY_C2S);
public static ServerPlayNetworkAddon getAddon(ServerPlayNetworkHandler handler) {
return (ServerPlayNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
@ -55,16 +52,9 @@ public final class ServerNetworkingImpl {
return (ServerConfigurationNetworkAddon) ((NetworkHandlerExtensions) handler).getAddon();
}
public static Packet<ClientCommonPacketListener> createS2CPacket(Identifier channel, PacketByteBuf buf) {
return new CustomPayloadS2CPacket(new UntypedPayload(channel, buf));
}
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);
public static Packet<ClientCommonPacketListener> createS2CPacket(CustomPayload payload) {
Objects.requireNonNull(payload, "Payload cannot be null");
Objects.requireNonNull(payload.getId(), "CustomPayload#getId() cannot return null for payload class: " + payload.getClass());
return new CustomPayloadS2CPacket(payload);
}

View file

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

View file

@ -21,8 +21,6 @@ import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.Nullable;
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.Redirect;
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.NetworkPhase;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.NetworkState;
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.DisconnectPacketSource;
import net.fabricmc.fabric.impl.networking.GenericFutureListenerHolder;
import net.fabricmc.fabric.impl.networking.NetworkHandlerExtensions;
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);
@Unique
private Map<NetworkState, Collection<Identifier>> playChannels;
private Map<NetworkPhase, Collection<Identifier>> playChannels;
@Inject(method = "<init>", at = @At("RETURN"))
private void initAddedFields(NetworkSide side, CallbackInfo ci) {
@ -89,7 +86,7 @@ abstract class ClientConnectionMixin implements ChannelInfoHolder {
}
@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) {
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
public Collection<Identifier> getPendingChannelsNames(NetworkState state) {
public Collection<Identifier> fabric_getPendingChannelsNames(NetworkPhase state) {
return this.playChannels.computeIfAbsent(state, (key) -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
}
}

View file

@ -16,33 +16,42 @@
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.Shadow;
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.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
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.payload.PayloadHelper;
import net.fabricmc.fabric.impl.networking.FabricCustomPayloadPacketCodec;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
@Mixin(CustomPayloadC2SPacket.class)
public class CustomPayloadC2SPacketMixin {
@Shadow
@Final
private static int MAX_PAYLOAD_SIZE;
@Inject(
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
@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;"
)
)
private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable<CustomPayload> cir) {
cir.setReturnValue(PayloadHelper.readCustom(id, buf, MAX_PAYLOAD_SIZE, NetworkingImpl.FACTORY_RETAIN.get()));
private static PacketCodec<PacketByteBuf, CustomPayload> wrapCodec(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) -> {
// 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;
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.Shadow;
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.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
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.payload.PayloadHelper;
import net.fabricmc.fabric.impl.networking.FabricCustomPayloadPacketCodec;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
@Mixin(CustomPayloadS2CPacket.class)
public class CustomPayloadS2CPacketMixin {
@Shadow
@Final
private static int MAX_PAYLOAD_SIZE;
@Inject(
method = "readPayload",
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
@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 = 0
)
)
private static void readPayload(Identifier id, PacketByteBuf buf, CallbackInfoReturnable<CustomPayload> cir) {
cir.setReturnValue(PayloadHelper.readCustom(id, buf, MAX_PAYLOAD_SIZE, NetworkingImpl.FACTORY_RETAIN.get()));
private static PacketCodec<RegistryByteBuf, CustomPayload> wrapPlayCodec(CustomPayload.CodecFactory<RegistryByteBuf> unknownCodecFactory, List<CustomPayload.Type<RegistryByteBuf, ?>> types, Operation<PacketCodec<RegistryByteBuf, CustomPayload>> original) {
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.callback.CallbackInfo;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.network.packet.c2s.common.CommonPongC2SPacket;
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket;
import net.minecraft.server.network.ServerCommonNetworkHandler;
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.ServerPlayNetworkAddon;
@ -35,23 +34,20 @@ import net.fabricmc.fabric.impl.networking.server.ServerPlayNetworkAddon;
public abstract class ServerCommonNetworkHandlerMixin implements NetworkHandlerExtensions {
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
private void handleCustomPayloadReceivedAsync(CustomPayloadC2SPacket packet, CallbackInfo ci) {
if (packet.payload() instanceof ResolvablePayload payload) {
boolean handled;
final CustomPayload payload = packet.payload();
if (getAddon() instanceof ServerPlayNetworkAddon addon) {
handled = addon.handle(payload);
} else if (getAddon() instanceof ServerConfigurationNetworkAddon addon) {
handled = addon.handle(payload);
} else {
throw new IllegalStateException("Unknown addon");
}
boolean handled;
if (handled) {
ci.cancel();
} else if (payload instanceof RetainedPayload retained) {
retained.buf().skipBytes(retained.buf().readableBytes());
retained.buf().release();
}
if (getAddon() instanceof ServerPlayNetworkAddon addon) {
handled = addon.handle(payload);
} else if (getAddon() instanceof ServerConfigurationNetworkAddon addon) {
handled = addon.handle(payload);
} else {
throw new IllegalStateException("Unknown addon");
}
if (handled) {
ci.cancel();
}
}

View file

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

View file

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

View file

@ -17,7 +17,8 @@
],
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init"
"net.fabricmc.fabric.impl.networking.CommonPacketsImpl::init",
"net.fabricmc.fabric.impl.networking.NetworkingImpl::init"
],
"client": [
"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.assertNotNull;
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.times;
import static org.mockito.Mockito.verify;
@ -41,8 +41,10 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
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.RegistryByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.server.network.ServerConfigurationNetworkHandler;
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.networking.v1.PacketByteBufs;
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.impl.networking.ChannelInfoHolder;
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.client.ClientConfigurationNetworkAddon;
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.ServerNetworkingImpl;
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 ChannelInfoHolder channelInfoHolder;
@ -73,16 +78,41 @@ public class CommonPacketTests {
private ServerConfigurationNetworkHandler serverNetworkHandler;
private ServerConfigurationNetworkAddon serverAddon;
private ClientConfigurationNetworking.Context clientContext;
private ServerConfigurationNetworking.Context serverContext;
@BeforeAll
static void beforeAll() {
CommonPacketsImpl.init();
ClientNetworkingImpl.clientInit();
// Register a receiver to send in the play registry response
ClientPlayNetworking.registerGlobalReceiver(new Identifier("fabric", "global_client"), (client, handler, buf, responseSender) -> {
// Register the packet codec on both sides
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
void setUp() {
packetSender = mock(PacketSender.class);
@ -97,27 +127,41 @@ public class CommonPacketTests {
serverAddon = mock(ServerConfigurationNetworkAddon.class);
when(ServerNetworkingImpl.getAddon(serverNetworkHandler)).thenReturn(serverAddon);
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
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);
// Receive a packet from the server
PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{1, 2, 3});
// The actual handler doesn't copy the buffer
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, clientNetworkHandler, buf, packetSender);
CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
packetHandler.receive(payload, clientContext);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
// 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());
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
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);
// Receive a packet from the server
@ -135,8 +179,8 @@ public class CommonPacketTests {
buf.writeIntArray(new int[]{2, 3}); // We only support version 1
assertThrows(UnsupportedOperationException.class, () -> {
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, clientNetworkHandler, buf, packetSender);
CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
packetHandler.receive(payload, clientContext);
});
// Assert the entire packet was read
@ -146,15 +190,15 @@ public class CommonPacketTests {
// Test handling the version packet on the server
@Test
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);
// Receive a packet from the client
PacketByteBuf buf = PacketByteBufs.create();
buf.writeIntArray(new int[]{1, 2, 3});
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, serverNetworkHandler, buf, null);
CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
packetHandler.receive(payload, serverContext);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
@ -164,7 +208,7 @@ public class CommonPacketTests {
// Test handling the version packet on the server unsupported version
@Test
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);
// Receive a packet from the client
@ -172,8 +216,8 @@ public class CommonPacketTests {
buf.writeIntArray(new int[]{3}); // Server only supports version 1
assertThrows(UnsupportedOperationException.class, () -> {
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, serverNetworkHandler, buf, null);
CommonVersionPayload payload = CommonVersionPayload.CODEC.decode(buf);
packetHandler.receive(payload, serverContext);
});
// 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
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);
when(clientAddon.getNegotiatedVersion()).thenReturn(1);
@ -194,15 +238,15 @@ public class CommonPacketTests {
buf.writeString("play"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, clientNetworkHandler, buf, packetSender);
CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
packetHandler.receive(payload, clientContext);
// Assert the entire packet was read
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
PacketByteBuf response = readResponse(packetSender);
PacketByteBuf response = readResponse(packetSender, REGISTER_PAYLOAD_TYPE);
assertEquals(1, response.readVarInt());
assertEquals("play", response.readString());
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
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);
when(clientAddon.getNegotiatedVersion()).thenReturn(1);
@ -224,15 +268,15 @@ public class CommonPacketTests {
buf.writeString("configuration"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ClientConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ClientConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, clientNetworkHandler, buf, packetSender);
CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
packetHandler.receive(payload, clientContext);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
verify(clientAddon, times(1)).onCommonRegisterPacket(any());
// 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("configuration", response.readString());
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
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);
when(serverAddon.getNegotiatedVersion()).thenReturn(1);
@ -253,18 +297,18 @@ public class CommonPacketTests {
buf.writeString("play"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, serverNetworkHandler, buf, null);
CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
packetHandler.receive(payload, serverContext);
// Assert the entire packet was read
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
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);
when(serverAddon.getNegotiatedVersion()).thenReturn(1);
@ -275,8 +319,8 @@ public class CommonPacketTests {
buf.writeString("configuration"); // Target phase
buf.writeCollection(List.of(new Identifier("fabric", "test")), PacketByteBuf::writeIdentifier);
ServerConfigurationNetworking.ConfigurationChannelHandler actualHandler = (ServerConfigurationNetworking.ConfigurationChannelHandler) packetHandler.actual();
actualHandler.receive(null, serverNetworkHandler, buf, null);
CommonRegisterPayload payload = CommonRegisterPayload.CODEC.decode(buf);
packetHandler.receive(payload, serverContext);
// Assert the entire packet was read
assertEquals(0, buf.readableBytes());
@ -318,12 +362,13 @@ public class CommonPacketTests {
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);
verify(packetSender, times(1)).sendPacket(responseCaptor.capture());
PacketByteBuf buf = PacketByteBufs.create();
responseCaptor.getValue().write(buf);
final T payload = (T) responseCaptor.getValue();
final PacketByteBuf buf = PacketByteBufs.create();
type.codec().encode(buf, payload);
return buf;
}
@ -335,10 +380,10 @@ public class CommonPacketTests {
}
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
public Collection<Identifier> getPendingChannelsNames(NetworkState state) {
public Collection<Identifier> fabric_getPendingChannelsNames(NetworkPhase state) {
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.EntitySelector;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.network.packet.CustomPayload;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
@ -43,6 +45,7 @@ import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.impl.networking.PayloadTypeRegistryImpl;
public final class NetworkingChannelTest implements ModInitializer {
@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();
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();
}
ServerPlayNetworking.registerReceiver(executor.networkHandler, channel, (server, player, handler, buf, sender) -> {
System.out.printf("Received packet on channel %s%n", channel);
});
CustomPayload.Type<RegistryByteBuf, ? extends CustomPayload> payloadType = PayloadTypeRegistryImpl.PLAY_C2S.get(channel);
context.getSource().sendFeedback(() -> Text.literal(String.format("Registered channel %s for %s", channel, executor.getDisplayName())), false);
return 1;
if (payloadType != null) {
ServerPlayNetworking.registerReceiver(executor.networkHandler, payloadType.id(), (payload, ctx) -> {
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 {
final Identifier channel = getIdentifier(context, "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);

View file

@ -19,14 +19,15 @@ package net.fabricmc.fabric.test.networking.configuration;
import java.util.function.Consumer;
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.server.network.ServerPlayerConfigurationTask;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerConfigurationNetworking;
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
@ -37,9 +38,12 @@ import net.fabricmc.fabric.test.networking.NetworkingTestmods;
public class NetworkingConfigurationTest implements ModInitializer {
@Override
public void onInitialize() {
PayloadTypeRegistry.configurationS2C().register(ConfigurationPacket.ID, ConfigurationPacket.CODEC);
PayloadTypeRegistry.configurationC2S().register(ConfigurationCompletePacket.ID, ConfigurationCompletePacket.CODEC);
ServerConfigurationConnectionEvents.CONFIGURE.register((handler, server) -> {
// 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"));
} else {
// 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) -> {
networkHandler.completeTask(TestConfigurationTask.KEY);
ServerConfigurationNetworking.registerGlobalReceiver(ConfigurationCompletePacket.ID, (packet, context) -> {
context.networkHandler().completeTask(TestConfigurationTask.KEY);
});
}
@ -67,38 +71,35 @@ public class NetworkingConfigurationTest implements ModInitializer {
}
}
public record ConfigurationPacket(String data) implements FabricPacket {
public static final PacketType<ConfigurationPacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure"), ConfigurationPacket::new);
public record ConfigurationPacket(String data) implements CustomPayload {
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) {
this(buf.readString());
}
@Override
public void write(PacketByteBuf buf) {
buf.writeString(data);
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
public Id<? extends CustomPayload> getId() {
return ID;
}
}
public record ConfigurationCompletePacket() implements FabricPacket {
public static final PacketType<ConfigurationCompletePacket> PACKET_TYPE = PacketType.create(new Identifier(NetworkingTestmods.ID, "configure_complete"), ConfigurationCompletePacket::new);
public static class ConfigurationCompletePacket implements CustomPayload {
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) {
this();
private ConfigurationCompletePacket() {
}
@Override
public void write(PacketByteBuf buf) {
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
public Id<? extends CustomPayload> getId() {
return ID;
}
}
}

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;
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.util.Formatting;
import net.minecraft.util.Identifier;
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.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.
// 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 static final Identifier KEYBINDING_PACKET_ID = NetworkingTestmods.id("keybind_press_test");
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));
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));
}
@Override
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.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.PacketSender;
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.
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 java.util.List;
import java.util.UUID;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.common.CustomPayloadS2CPacket;
import net.minecraft.network.PacketCallbacks;
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.s2c.play.BundleS2CPacket;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
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.test.networking.NetworkingTestmods;
import net.fabricmc.loader.api.FabricLoader;
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;
public static void sendToTestChannel(ServerPlayerEntity player, String stuff) {
ServerPlayNetworking.getSender(player).sendPacket(new OverlayPacket(Text.literal(stuff)), future -> {
NetworkingTestmods.LOGGER.info("Sent custom payload packet in {}", TEST_CHANNEL);
});
ServerPlayNetworking.getSender(player).sendPacket(new OverlayPacket(Text.literal(stuff)), PacketCallbacks.always(() -> {
NetworkingTestmods.LOGGER.info("Sent custom payload packet");
}));
}
private static void sendToUnknownChannel(ServerPlayerEntity player) {
PacketByteBuf buf = PacketByteBufs.create();
for (int i = 0; i < 20; i++) {
buf.writeUuid(UUID.randomUUID());
}
ServerPlayNetworking.getSender(player).sendPacket(UNKNOWN_TEST_CHANNEL, buf);
ServerPlayNetworking.getSender(player).sendPacket(new UnknownPayload("Hello"));
}
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));
return Command.SINGLE_SUCCESS;
}))
.then(literal("bufctor").executes(ctx -> {
PacketByteBuf buf = PacketByteBufs.create();
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);
.then(literal("simple").executes(ctx -> {
ServerPlayNetworking.send(ctx.getSource().getPlayer(), new OverlayPacket(Text.literal("simple")));
return Command.SINGLE_SUCCESS;
}))
.then(literal("bundled").executes(ctx -> {
PacketByteBuf buf1 = PacketByteBufs.create();
buf1.writeText(Text.literal("bundled #1"));
PacketByteBuf buf2 = PacketByteBufs.create();
buf2.writeText(Text.literal("bundled #2"));
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);
BundleS2CPacket packet = new BundleS2CPacket(List.of(
ServerPlayNetworking.createS2CPacket(new OverlayPacket(Text.literal("bundled #1"))),
ServerPlayNetworking.createS2CPacket(new OverlayPacket(Text.literal("bundled #2")))
));
ServerPlayNetworking.getSender(ctx.getSource().getPlayer()).sendPacket(packet);
return Command.SINGLE_SUCCESS;
})));
}
@ -117,6 +95,12 @@ public final class NetworkingPlayPacketTest implements ModInitializer {
public void onInitialize() {
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) -> {
NetworkingPlayPacketTest.registerCommand(dispatcher);
});
@ -135,21 +119,31 @@ public final class NetworkingPlayPacketTest implements ModInitializer {
});
}
public record OverlayPacket(Text message) implements FabricPacket {
public static final PacketType<OverlayPacket> PACKET_TYPE = PacketType.create(TEST_CHANNEL, OverlayPacket::new);
public record OverlayPacket(Text message) implements CustomPayload {
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) {
this(buf.readText());
}
@Override
public void write(PacketByteBuf buf) {
buf.writeText(this.message);
}
@Override
public PacketType<?> getType() {
return PACKET_TYPE;
public Id<? extends CustomPayload> getId() {
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 {
@Override
public void onInitializeClient() {
ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.PACKET_TYPE, (packet, responseSender) -> {
ClientConfigurationNetworking.registerGlobalReceiver(NetworkingConfigurationTest.ConfigurationPacket.ID, (packet, context) -> {
// Handle stuff here
// 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.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.test.networking.keybindreciever.NetworkingKeybindPacketTest;
import net.fabricmc.fabric.test.networking.keybindreciever.KeybindPayload;
// Sends a packet to the server when a keybinding was pressed
// 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 (TEST_BINDING.wasPressed()) {
// 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 net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.network.packet.CustomPayload;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
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.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.test.networking.NetworkingTestmods;
import net.fabricmc.fabric.test.networking.play.NetworkingPlayPacketTest;
public final class NetworkingPlayPacketClientTest implements ClientModInitializer, ClientPlayNetworking.PlayPacketHandler<NetworkingPlayPacketTest.OverlayPacket> {
private static final Identifier UNKNOWN_TEST_CHANNEL = NetworkingTestmods.id("unknown_test_channel");
public final class NetworkingPlayPacketClientTest implements ClientModInitializer {
@Override
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(
ClientCommandManager.literal("clientnetworktestcommand")
.then(ClientCommandManager.literal("unknown").executes(context -> {
ClientPlayNetworking.send(UNKNOWN_TEST_CHANNEL, PacketByteBufs.create());
ClientPlayNetworking.send(new UnknownPayload("Hello"));
return Command.SINGLE_SUCCESS;
}
))));
}
@Override
public void receive(NetworkingPlayPacketTest.OverlayPacket packet, ClientPlayerEntity player, PacketSender sender) {
MinecraftClient.getInstance().inGameHud.setOverlayMessage(packet.message(), true);
private record UnknownPayload(String data) implements CustomPayload {
private static final CustomPayload.Id<UnknownPayload> ID = new Id<>(NetworkingTestmods.id("unknown_test_channel_c2s"));
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;
/**
* Fabric's version of Block.Settings. Adds additional methods and hooks
* 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 replace with {@link AbstractBlock.Settings}
*/
@Deprecated
public class FabricBlockSettings extends AbstractBlock.Settings {
protected FabricBlockSettings() {
super();
@ -97,56 +91,74 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
this.postProcess(otherAccessor.getPostProcessPredicate());
}
/**
* @deprecated replace with {@link AbstractBlock.Settings#create()}
*/
@Deprecated
public static FabricBlockSettings create() {
return new FabricBlockSettings();
}
/**
* @deprecated Use {@link FabricBlockSettings#create()} instead.
* @deprecated replace with {@link AbstractBlock.Settings#create()}
*/
@Deprecated
public static FabricBlockSettings of() {
return create();
}
/**
* @deprecated replace with {@link AbstractBlock.Settings#copy(AbstractBlock)}
*/
@Deprecated
public static FabricBlockSettings copyOf(AbstractBlock block) {
return new FabricBlockSettings(((AbstractBlockAccessor) block).getSettings());
}
/**
* @deprecated replace with {@link AbstractBlock.Settings#copy(AbstractBlock)}
*/
@Deprecated
public static FabricBlockSettings copyOf(AbstractBlock.Settings settings) {
return new FabricBlockSettings(settings);
}
@Deprecated
@Override
public FabricBlockSettings noCollision() {
super.noCollision();
return this;
}
@Deprecated
@Override
public FabricBlockSettings nonOpaque() {
super.nonOpaque();
return this;
}
@Deprecated
@Override
public FabricBlockSettings slipperiness(float value) {
super.slipperiness(value);
return this;
}
@Deprecated
@Override
public FabricBlockSettings velocityMultiplier(float velocityMultiplier) {
super.velocityMultiplier(velocityMultiplier);
return this;
}
@Deprecated
@Override
public FabricBlockSettings jumpVelocityMultiplier(float jumpVelocityMultiplier) {
super.jumpVelocityMultiplier(jumpVelocityMultiplier);
return this;
}
@Deprecated
@Override
public FabricBlockSettings sounds(BlockSoundGroup group) {
super.sounds(group);
@ -161,89 +173,105 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
return this.luminance(levelFunction);
}
@Deprecated
@Override
public FabricBlockSettings luminance(ToIntFunction<BlockState> luminanceFunction) {
super.luminance(luminanceFunction);
return this;
}
@Deprecated
@Override
public FabricBlockSettings strength(float hardness, float resistance) {
super.strength(hardness, resistance);
return this;
}
@Deprecated
@Override
public FabricBlockSettings breakInstantly() {
super.breakInstantly();
return this;
}
@Deprecated
@Override
public FabricBlockSettings strength(float strength) {
super.strength(strength);
return this;
}
@Deprecated
@Override
public FabricBlockSettings ticksRandomly() {
super.ticksRandomly();
return this;
}
@Deprecated
@Override
public FabricBlockSettings dynamicBounds() {
super.dynamicBounds();
return this;
}
@Deprecated
@Override
public FabricBlockSettings dropsNothing() {
super.dropsNothing();
return this;
}
@Deprecated
@Override
public FabricBlockSettings dropsLike(Block block) {
super.dropsLike(block);
return this;
}
@Deprecated
@Override
public FabricBlockSettings air() {
super.air();
return this;
}
@Deprecated
@Override
public FabricBlockSettings allowsSpawning(AbstractBlock.TypedContextPredicate<EntityType<?>> predicate) {
super.allowsSpawning(predicate);
return this;
}
@Deprecated
@Override
public FabricBlockSettings solidBlock(AbstractBlock.ContextPredicate predicate) {
super.solidBlock(predicate);
return this;
}
@Deprecated
@Override
public FabricBlockSettings suffocates(AbstractBlock.ContextPredicate predicate) {
super.suffocates(predicate);
return this;
}
@Deprecated
@Override
public FabricBlockSettings blockVision(AbstractBlock.ContextPredicate predicate) {
super.blockVision(predicate);
return this;
}
@Deprecated
@Override
public FabricBlockSettings postProcess(AbstractBlock.ContextPredicate predicate) {
super.postProcess(predicate);
return this;
}
@Deprecated
@Override
public FabricBlockSettings emissiveLighting(AbstractBlock.ContextPredicate 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.
*/
@Deprecated
@Override
public FabricBlockSettings requiresTool() {
super.requiresTool();
return this;
}
@Deprecated
@Override
public FabricBlockSettings mapColor(MapColor color) {
super.mapColor(color);
return this;
}
@Deprecated
@Override
public FabricBlockSettings hardness(float hardness) {
super.hardness(hardness);
return this;
}
@Deprecated
@Override
public FabricBlockSettings resistance(float resistance) {
super.resistance(resistance);
return this;
}
@Deprecated
@Override
public FabricBlockSettings offset(AbstractBlock.OffsetType offsetType) {
super.offset(offsetType);
return this;
}
@Deprecated
@Override
public FabricBlockSettings noBlockBreakParticles() {
super.noBlockBreakParticles();
return this;
}
@Deprecated
@Override
public FabricBlockSettings requires(FeatureFlag... features) {
super.requires(features);
return this;
}
@Deprecated
@Override
public FabricBlockSettings mapColor(Function<BlockState, MapColor> mapColorProvider) {
super.mapColor(mapColorProvider);
return this;
}
@Deprecated
@Override
public FabricBlockSettings burnable() {
super.burnable();
return this;
}
@Deprecated
@Override
public FabricBlockSettings liquid() {
super.liquid();
return this;
}
@Deprecated
@Override
public FabricBlockSettings solid() {
super.solid();
return this;
}
@Deprecated
@Override
public FabricBlockSettings notSolid() {
super.notSolid();
return this;
}
@Deprecated
@Override
public FabricBlockSettings pistonBehavior(PistonBehavior pistonBehavior) {
super.pistonBehavior(pistonBehavior);
return this;
}
@Deprecated
@Override
public FabricBlockSettings instrument(Instrument instrument) {
super.instrument(instrument);
return this;
}
@Deprecated
@Override
public FabricBlockSettings replaceable() {
super.replaceable();
@ -354,11 +397,16 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
return this;
}
/**
* @deprecated replace with {@link AbstractBlock.Settings#luminance(ToIntFunction)}
*/
@Deprecated
public FabricBlockSettings luminance(int luminance) {
this.luminance(ignored -> luminance);
return this;
}
@Deprecated
public FabricBlockSettings drops(Identifier dropTableId) {
((AbstractBlockSettingsAccessor) this).setLootTableId(dropTableId);
return this;
@ -367,7 +415,7 @@ public class FabricBlockSettings extends AbstractBlock.Settings {
/* FABRIC DELEGATE WRAPPERS */
/**
* @deprecated Please migrate to {@link FabricBlockSettings#mapColor(MapColor)}
* @deprecated Please migrate to {@link AbstractBlock.Settings#mapColor(MapColor)}
*/
@Deprecated
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
public FabricBlockSettings materialColor(DyeColor color) {
return this.mapColor(color);
}
/**
* @deprecated Please migrate to {@link AbstractBlock.Settings#mapColor(DyeColor)}
*/
@Deprecated
public FabricBlockSettings mapColor(DyeColor color) {
return this.mapColor(color.getMapColor());
}
@Deprecated
public FabricBlockSettings collidable(boolean collidable) {
((AbstractBlockSettingsAccessor) this).setCollidable(collidable);
return this;

View file

@ -81,7 +81,7 @@ public class BlockEntityTypeBuilderTest implements ModInitializer {
}
@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()) {
BlockEntity blockEntity = world.getBlockEntity(pos);

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.test.object.builder;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
@ -36,7 +37,6 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
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.type.BlockSetTypeBuilder;
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 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 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
public TealSign createBlockEntity(BlockPos pos, BlockState 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
public TealSign createBlockEntity(BlockPos pos, BlockState 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
public TealHangingSign createBlockEntity(BlockPos pos, BlockState 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
public TealHangingSign createBlockEntity(BlockPos pos, BlockState state) {
return new TealHangingSign(pos, state);

View file

@ -16,8 +16,12 @@
package net.fabricmc.fabric.api.particle.v1;
import java.util.function.Function;
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.ParticleEffect;
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) {
return complex(false, 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, 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 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) {
@Override
public Codec<T> getCodec() {
//TODO fix me
return null;
return codec;
}
@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