final module name refactor prior to release, hopefully

This commit is contained in:
Adrian Siekierka 2019-05-11 23:48:33 +02:00
parent 5a5f8c5c68
commit f16189181b
263 changed files with 212 additions and 206 deletions

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-networking-v0"
version = getSubprojectVersion(project, "0.1.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
}

View file

@ -0,0 +1,57 @@
/*
* 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.event.network;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Identifier;
import java.util.Collection;
/**
* Event for listening to packet type registrations and unregistrations
* (also known as "minecraft:register" and "minecraft:unregister")
* in the client -> server direction.
*/
public interface C2SPacketTypeCallback {
static final Event<C2SPacketTypeCallback> REGISTERED = EventFactory.createArrayBacked(
C2SPacketTypeCallback.class,
(callbacks) -> (client, types) -> {
for (C2SPacketTypeCallback callback : callbacks) {
callback.accept(client, types);
}
}
);
static final Event<C2SPacketTypeCallback> UNREGISTERED = EventFactory.createArrayBacked(
C2SPacketTypeCallback.class,
(callbacks) -> (client, types) -> {
for (C2SPacketTypeCallback callback : callbacks) {
callback.accept(client, types);
}
}
);
/**
* Accept a collection of types.
*
* @param client The player who is the source of the packet.
* @param types The provided collection of types.
*/
void accept(PlayerEntity client, Collection<Identifier> types);
}

View file

@ -0,0 +1,55 @@
/*
* 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.event.network;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.util.Identifier;
import java.util.Collection;
/**
* Event for listening to packet type registrations and unregistrations
* (also known as "minecraft:register" and "minecraft:unregister")
* in the server -> client direction.
*/
public interface S2CPacketTypeCallback {
static final Event<S2CPacketTypeCallback> REGISTERED = EventFactory.createArrayBacked(
S2CPacketTypeCallback.class,
(callbacks) -> (types) -> {
for (S2CPacketTypeCallback callback : callbacks) {
callback.accept(types);
}
}
);
static final Event<S2CPacketTypeCallback> UNREGISTERED = EventFactory.createArrayBacked(
S2CPacketTypeCallback.class,
(callbacks) -> (types) -> {
for (S2CPacketTypeCallback callback : callbacks) {
callback.accept(types);
}
}
);
/**
* Accept a collection of types.
*
* @param types The provided collection of types.
*/
void accept(Collection<Identifier> types);
}

View file

@ -0,0 +1,85 @@
/*
* 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.network;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.fabricmc.fabric.impl.network.ClientSidePacketRegistryImpl;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
/**
* The client-side packet registry.
* <p>
* It is used for:
* <p>
* - registering client-side packet receivers (server -> client packets)
* - sending packets to the server (client -> server packets).
*/
public interface ClientSidePacketRegistry extends PacketRegistry {
static final ClientSidePacketRegistry INSTANCE = new ClientSidePacketRegistryImpl();
/**
* Check if the server declared the ability to receive a given packet ID
* using the vanilla "register/unregister" protocol.
*
* @param id The packet identifier.
* @return True if the server side declared a given packet identifier.
*/
boolean canServerReceive(Identifier id);
/**
* Send a packet to the server.
*
* @param packet The packet to be sent.
* @param completionListener Completion listener. Can be used to check for
* the success or failure of sending a given packet, among others.
*/
void sendToServer(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> completionListener);
/**
* Send an identifier/buffer-based packet to the server.
*
* @param id The packet identifier.
* @param buf The packet byte buffer.
* @param completionListener Completion listener. Can be used to check for
* the success or failure of sending a given packet, among others.
*/
default void sendToServer(Identifier id, PacketByteBuf buf, GenericFutureListener<? extends Future<? super Void>> completionListener) {
sendToServer(toPacket(id, buf), completionListener);
}
/**
* Send a packet to the server.
*
* @param packet The packet to be sent.
*/
default void sendToServer(Packet<?> packet) {
sendToServer(packet, null);
}
/**
* Send an identifier/buffer-based packet to the server.
*
* @param id The packet identifier.
* @param buf The packet byte buffer.
*/
default void sendToServer(Identifier id, PacketByteBuf buf) {
sendToServer(id, buf, null);
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.network;
import net.minecraft.util.PacketByteBuf;
/**
* Interface for receiving CustomPayload-based packets.
*/
@FunctionalInterface
public interface PacketConsumer {
/**
* Receive a CustomPayload-based packet.
* <p>
* Please keep in mind that this CAN be called OUTSIDE of the main thread!
* Most game operations are not thread-safe, so you should look into using
* the thread task queue ({@link PacketContext#getTaskQueue()}) to split
* the "reading" (which should, but does not have to, happen off-thread)
* and "applying" (which, unless you know what you're doing, should happen
* on the main thread).
*
* @param context The context (receiving player, side, etc.)
* @param buffer The byte buffer containing the received packet data.
*/
void accept(PacketContext context, PacketByteBuf buffer);
}

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.api.network;
import net.fabricmc.api.EnvType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ThreadExecutor;
/**
* Interface defining a context used during packet processing. Allows access
* to additional information, such as the source/target of the player, or
* the correct task queue to enqueue synchronization-requiring code on.
*/
public interface PacketContext {
/**
* Get the environment associated with the packet.
*
* @return EnvType.CLIENT if processing packet on the client side,
* EnvType.SERVER otherwise.
*/
EnvType getPacketEnvironment();
/**
* Get the player associated with the packet.
* <p>
* On the client side, this always returns the client-side player instance.
* On the server side, it returns the player belonging to the client this
* packet was sent by.
*
* @return The player associated with the packet.
*/
PlayerEntity getPlayer();
/**
* Get the task queue for a given side.
* <p>
* As Minecraft networking I/O is asynchronous, but a lot of its logic is
* not thread-safe, it is recommended to do the following:
* <p>
* - read and parse the PacketByteBuf,
* - run the packet response logic through the main thread task queue via
* ThreadTaskQueue.execute(). The method will check if it's not already
* on the main thread in order to avoid unnecessary delays, so don't
* worry about that!
*
* @return The thread task queue.
*/
ThreadExecutor getTaskQueue();
}

View file

@ -0,0 +1,48 @@
/*
* 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.network;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
public interface PacketRegistry {
/**
* Turn a (identifier, byte buffer) pair into a "custom payload" packet
* suitable for sending in the PacketRegistry's sending direction.
*
* @param id The identifier.
* @param buf The byte buffer.
* @return
*/
Packet<?> toPacket(Identifier id, PacketByteBuf buf);
/**
* Register a packet.
*
* @param id The packet Identifier.
* @param consumer The method used for handling the packet.
*/
void register(Identifier id, PacketConsumer consumer);
/**
* Unregister a packet.
*
* @param id The packet Identifier.
*/
void unregister(Identifier id);
}

View file

@ -0,0 +1,93 @@
/*
* 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.network;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.fabricmc.fabric.api.server.PlayerStream;
import net.fabricmc.fabric.impl.network.ServerSidePacketRegistryImpl;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
/**
* The server-side packet registry.
* <p>
* It is used for:
* <p>
* - registering server-side packet receivers (client -> server packets)
* - sending packets to clients (server -> client packets).
* <p>
* For iterating over clients in a server, see {@link PlayerStream}.
*/
public interface ServerSidePacketRegistry extends PacketRegistry {
static final ServerSidePacketRegistry INSTANCE = new ServerSidePacketRegistryImpl();
/**
* Check if a given client declared the ability to receive a given packet ID
* using the vanilla "register/unregister" protocol.
*
* @param id The packet identifier.
* @return True if the client side declared a given packet identifier.
*/
boolean canPlayerReceive(PlayerEntity player, Identifier id);
/**
* Send a packet to a given client.
*
* @param player The given client.
* @param packet The packet to be sent.
* @param completionListener Completion listener. Can be used to check for
* the success or failure of sending a given packet, among others.
*/
void sendToPlayer(PlayerEntity player, Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> completionListener);
/**
* Send an identifier/buffer-based packet to a given client.
*
* @param player The given client.
* @param id The packet identifier.
* @param buf The packet byte buffer.
* @param completionListener Completion listener. Can be used to check for
* the success or failure of sending a given packet, among others.
*/
default void sendToPlayer(PlayerEntity player, Identifier id, PacketByteBuf buf, GenericFutureListener<? extends Future<? super Void>> completionListener) {
sendToPlayer(player, toPacket(id, buf), completionListener);
}
/**
* Send a packet to a given client.
*
* @param player The given client.
* @param packet The packet to be sent.
*/
default void sendToPlayer(PlayerEntity player, Packet<?> packet) {
sendToPlayer(player, packet, null);
}
/**
* Send an identifier/buffer-based packet to a given client.
*
* @param player The given client.
* @param id The packet identifier.
* @param buf The packet byte buffer.
*/
default void sendToPlayer(PlayerEntity player, Identifier id, PacketByteBuf buf) {
sendToPlayer(player, id, buf, null);
}
}

View file

@ -0,0 +1,111 @@
/*
* 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.server;
import net.fabricmc.fabric.impl.server.EntityTrackerStorageAccessor;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.chunk.ChunkManager;
import java.util.stream.Stream;
/**
* Helper streams for looking up players on a server.
* <p>
* In general, most of these methods will only function with a {@link ServerWorld} instance.
*/
public final class PlayerStream {
private PlayerStream() {
}
public static Stream<ServerPlayerEntity> all(MinecraftServer server) {
if (server.getPlayerManager() != null) {
return server.getPlayerManager().getPlayerList().stream();
} else {
return Stream.empty();
}
}
public static Stream<PlayerEntity> world(World world) {
if (world instanceof ServerWorld) {
// noinspection unchecked
return ((Stream<PlayerEntity>) (Stream) ((ServerWorld) world).getPlayers().stream());
} else {
throw new RuntimeException("Only supported on ServerWorld!");
}
}
public static Stream<PlayerEntity> watching(World world, ChunkPos pos) {
ChunkManager manager = world.getChunkManager();
if (!(manager instanceof ServerChunkManager)) {
throw new RuntimeException("Only supported on ServerWorld!");
} else {
//noinspection unchecked
return ((Stream<PlayerEntity>) (Stream) ((ServerChunkManager) manager).threadedAnvilChunkStorage.getPlayersWatchingChunk(pos, false));
}
}
/**
* Warning: If the provided entity is a PlayerEntity themselves, it is not
* guaranteed by the contract that said PlayerEntity is included in the
* resulting stream.
*/
@SuppressWarnings("JavaDoc")
public static Stream<PlayerEntity> watching(Entity entity) {
ChunkManager manager = entity.getEntityWorld().getChunkManager();
if (manager instanceof ServerChunkManager) {
ThreadedAnvilChunkStorage storage = ((ServerChunkManager) manager).threadedAnvilChunkStorage;
if (storage instanceof EntityTrackerStorageAccessor) {
//noinspection unchecked
return ((Stream<PlayerEntity>) (Stream) ((EntityTrackerStorageAccessor) storage).fabric_getTrackingPlayers(entity));
}
}
// fallback
return watching(entity.getEntityWorld(), new ChunkPos((int) (entity.x / 16.0D), (int) (entity.z / 16.0D)));
}
public static Stream<PlayerEntity> watching(BlockEntity entity) {
return watching(entity.getWorld(), entity.getPos());
}
public static Stream<PlayerEntity> watching(World world, BlockPos pos) {
return watching(world, new ChunkPos(pos));
}
public static Stream<PlayerEntity> around(World world, Vec3d vector, double radius) {
double radiusSq = radius * radius;
return world(world).filter((p) -> p.squaredDistanceTo(vector) <= radiusSq);
}
public static Stream<PlayerEntity> around(World world, BlockPos pos, double radius) {
double radiusSq = radius * radius;
return world(world).filter((p) -> p.squaredDistanceTo(pos.getX(), pos.getY(), pos.getZ()) <= radiusSq);
}
}

View file

@ -0,0 +1,102 @@
/*
* 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.network;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.fabricmc.fabric.api.event.network.S2CPacketTypeCallback;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.fabricmc.fabric.api.network.PacketContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.packet.CustomPayloadS2CPacket;
import net.minecraft.network.Packet;
import net.minecraft.server.network.packet.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
public class ClientSidePacketRegistryImpl extends PacketRegistryImpl implements ClientSidePacketRegistry {
private final Collection<Identifier> serverPayloadIds = new HashSet<>();
public static void invalidateRegisteredIdList() {
((ClientSidePacketRegistryImpl) ClientSidePacketRegistry.INSTANCE).serverPayloadIds.clear();
}
@Override
public boolean canServerReceive(Identifier id) {
return serverPayloadIds.contains(id);
}
@Override
public void sendToServer(Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> completionListener) {
ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler();
if (handler != null) {
if (completionListener == null) {
// stay closer to the vanilla codepath
handler.sendPacket(packet);
} else {
handler.getClientConnection().send(packet, completionListener);
}
} else {
LOGGER.warn("Sending packet " + packet + " to server failed, not connected!");
}
}
@Override
public Packet<?> toPacket(Identifier id, PacketByteBuf buf) {
return new CustomPayloadC2SPacket(id, buf);
}
@Override
protected void onRegister(Identifier id) {
ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler();
if (handler != null) {
createRegisterTypePacket(PacketTypes.REGISTER, Collections.singleton(id)).ifPresent(handler::sendPacket);
}
}
@Override
protected void onUnregister(Identifier id) {
ClientPlayNetworkHandler handler = MinecraftClient.getInstance().getNetworkHandler();
if (handler != null) {
createRegisterTypePacket(PacketTypes.UNREGISTER, Collections.singleton(id)).ifPresent(handler::sendPacket);
}
}
@Override
protected Collection<Identifier> getIdCollectionFor(PacketContext context) {
return serverPayloadIds;
}
@Override
protected void onReceivedRegisterPacket(PacketContext context, Collection<Identifier> ids) {
S2CPacketTypeCallback.REGISTERED.invoker().accept(ids);
}
@Override
protected void onReceivedUnregisterPacket(PacketContext context, Collection<Identifier> ids) {
S2CPacketTypeCallback.UNREGISTERED.invoker().accept(ids);
}
public final boolean accept(CustomPayloadS2CPacket packet, PacketContext context) {
return accept(packet.getChannel(), context, packet.getData());
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.network;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
/**
* Helper interface containing getters for CustomPayloadC2SPacket
* which were omitted from the compiled game.
*/
public interface CustomPayloadC2SPacketAccessor {
Identifier getChannel();
PacketByteBuf getData();
}

View file

@ -0,0 +1,168 @@
/*
* 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.network;
import io.netty.buffer.Unpooled;
import net.fabricmc.fabric.api.network.PacketConsumer;
import net.fabricmc.fabric.api.network.PacketContext;
import net.fabricmc.fabric.api.network.PacketRegistry;
import net.minecraft.network.Packet;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.PacketByteBuf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.charset.StandardCharsets;
import java.util.*;
public abstract class PacketRegistryImpl implements PacketRegistry {
protected static final Logger LOGGER = LogManager.getLogger();
protected final Map<Identifier, PacketConsumer> consumerMap;
PacketRegistryImpl() {
consumerMap = new LinkedHashMap<>();
}
public static Optional<Packet<?>> createInitialRegisterPacket(PacketRegistry registry) {
PacketRegistryImpl impl = (PacketRegistryImpl) registry;
return impl.createRegisterTypePacket(PacketTypes.REGISTER, impl.consumerMap.keySet());
}
@Override
public void register(Identifier id, PacketConsumer consumer) {
boolean isNew = true;
if (consumerMap.containsKey(id)) {
LOGGER.warn("Registered duplicate packet " + id + "!");
LOGGER.trace(new Throwable());
isNew = false;
}
consumerMap.put(id, consumer);
if (isNew) {
onRegister(id);
}
}
@Override
public void unregister(Identifier id) {
if (consumerMap.remove(id) != null) {
onUnregister(id);
} else {
LOGGER.warn("Tried to unregister non-registered packet " + id + "!");
LOGGER.trace(new Throwable());
}
}
protected abstract void onRegister(Identifier id);
protected abstract void onUnregister(Identifier id);
protected abstract Collection<Identifier> getIdCollectionFor(PacketContext context);
protected abstract void onReceivedRegisterPacket(PacketContext context, Collection<Identifier> ids);
protected abstract void onReceivedUnregisterPacket(PacketContext context, Collection<Identifier> ids);
protected Optional<Packet<?>> createRegisterTypePacket(Identifier id, Collection<Identifier> ids) {
if (ids.isEmpty()) {
return Optional.empty();
}
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
boolean first = true;
for (Identifier a : ids) {
if (!first) {
buf.writeByte(0);
} else {
first = false;
}
buf.writeBytes(a.toString().getBytes(StandardCharsets.US_ASCII));
}
return Optional.of(toPacket(id, buf));
}
private boolean acceptRegisterType(Identifier id, PacketContext context, PacketByteBuf buf) {
Collection<Identifier> ids = new HashSet<>();
{
StringBuilder sb = new StringBuilder();
char c;
int oldIndex = buf.readerIndex();
while (buf.readerIndex() < buf.writerIndex()) {
c = (char) buf.readByte();
if (c == 0) {
String s = sb.toString();
if (!s.isEmpty()) {
try {
ids.add(new Identifier(s));
} catch (InvalidIdentifierException e) {
LOGGER.warn("Received invalid identifier in " + id + ": " + s + " (" + e.getLocalizedMessage() + ")");
LOGGER.trace(e);
}
}
sb = new StringBuilder();
} else {
sb.append(c);
}
}
buf.readerIndex(oldIndex);
String s = sb.toString();
if (!s.isEmpty()) {
ids.add(new Identifier(s));
}
}
Collection<Identifier> target = getIdCollectionFor(context);
if (id.equals(PacketTypes.UNREGISTER)) {
target.removeAll(ids);
onReceivedUnregisterPacket(context, ids);
} else {
target.addAll(ids);
onReceivedRegisterPacket(context, ids);
}
return false; // continue execution for other mods
}
/**
* Hook for accepting packets used in Fabric mixins.
*
* @param id The packet Identifier received.
* @param context The packet context provided.
* @param buf The packet data buffer received.
* @return Whether or not the packet was handled by this packet registry.
*/
public boolean accept(Identifier id, PacketContext context, PacketByteBuf buf) {
if (id.equals(PacketTypes.REGISTER) || id.equals(PacketTypes.UNREGISTER)) {
return acceptRegisterType(id, context, buf);
}
PacketConsumer consumer = consumerMap.get(id);
if (consumer != null) {
try {
consumer.accept(context, buf);
} catch (Throwable t) {
LOGGER.warn("Failed to handle packet " + id + "!", t);
}
return true;
} else {
return false;
}
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.network;
import net.minecraft.util.Identifier;
public final class PacketTypes {
public static final Identifier BRAND = new Identifier("minecraft:brand");
public static final Identifier REGISTER = new Identifier("minecraft:register");
public static final Identifier UNREGISTER = new Identifier("minecraft:unregister");
public static final Identifier OPEN_CONTAINER = new Identifier("fabric", "container/open");
}

View file

@ -0,0 +1,117 @@
/*
* 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.network;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import net.fabricmc.fabric.api.event.network.C2SPacketTypeCallback;
import net.fabricmc.fabric.api.network.PacketContext;
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
import net.minecraft.client.network.packet.CustomPayloadS2CPacket;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.network.packet.CustomPayloadC2SPacket;
import net.minecraft.server.network.packet.LoginQueryResponseC2SPacket;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.function.Consumer;
public class ServerSidePacketRegistryImpl extends PacketRegistryImpl implements ServerSidePacketRegistry {
private final WeakHashMap<PlayerEntity, Collection<Identifier>> playerPayloadIds = new WeakHashMap<>();
private final Set<WeakReference<ServerPlayNetworkHandler>> handlers = new HashSet<>();
public void onQueryResponse(LoginQueryResponseC2SPacket packet) {
}
public void addNetworkHandler(ServerPlayNetworkHandler handler) {
handlers.add(new WeakReference<>(handler));
}
protected void forEachHandler(Consumer<ServerPlayNetworkHandler> consumer) {
Iterator<WeakReference<ServerPlayNetworkHandler>> it = handlers.iterator();
while (it.hasNext()) {
ServerPlayNetworkHandler server = it.next().get();
if (server != null) {
consumer.accept(server);
} else {
it.remove();
}
}
}
@Override
public boolean canPlayerReceive(PlayerEntity player, Identifier id) {
Collection<Identifier> ids = playerPayloadIds.get(player);
if (ids != null) {
return ids.contains(id);
} else {
return false;
}
}
@Override
public void sendToPlayer(PlayerEntity player, Packet<?> packet, GenericFutureListener<? extends Future<? super Void>> completionListener) {
if (!(player instanceof ServerPlayerEntity)) {
throw new RuntimeException("Can only send to ServerPlayerEntities!");
} else {
((ServerPlayerEntity) player).networkHandler.sendPacket(packet, completionListener);
}
}
@Override
public Packet<?> toPacket(Identifier id, PacketByteBuf buf) {
return new CustomPayloadS2CPacket(id, buf);
}
@Override
protected void onRegister(Identifier id) {
createRegisterTypePacket(PacketTypes.REGISTER, Collections.singleton(id))
.ifPresent((packet) -> forEachHandler((n) -> n.sendPacket(packet)));
}
@Override
protected void onUnregister(Identifier id) {
createRegisterTypePacket(PacketTypes.UNREGISTER, Collections.singleton(id))
.ifPresent((packet) -> forEachHandler((n) -> n.sendPacket(packet)));
}
@Override
protected Collection<Identifier> getIdCollectionFor(PacketContext context) {
return playerPayloadIds.computeIfAbsent(context.getPlayer(), (p) -> new HashSet<>());
}
@Override
protected void onReceivedRegisterPacket(PacketContext context, Collection<Identifier> ids) {
C2SPacketTypeCallback.REGISTERED.invoker().accept(context.getPlayer(), ids);
}
@Override
protected void onReceivedUnregisterPacket(PacketContext context, Collection<Identifier> ids) {
C2SPacketTypeCallback.UNREGISTERED.invoker().accept(context.getPlayer(), ids);
}
public final boolean accept(CustomPayloadC2SPacket packet, PacketContext context) {
CustomPayloadC2SPacketAccessor accessor = ((CustomPayloadC2SPacketAccessor) packet);
return accept(accessor.getChannel(), context, accessor.getData());
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.server;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import java.util.stream.Stream;
public interface EntityTrackerStorageAccessor {
Stream<ServerPlayerEntity> fabric_getTrackingPlayers(Entity entity);
}

View file

@ -0,0 +1,25 @@
/*
* 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.server;
import net.minecraft.server.network.ServerPlayerEntity;
import java.util.stream.Stream;
public interface EntityTrackerStreamAccessor {
Stream<ServerPlayerEntity> fabric_getTrackingPlayers();
}

View file

@ -0,0 +1,86 @@
/*
* 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.network;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.fabricmc.fabric.api.network.PacketContext;
import net.fabricmc.fabric.impl.network.ClientSidePacketRegistryImpl;
import net.fabricmc.fabric.impl.network.PacketRegistryImpl;
import net.fabricmc.fabric.impl.network.PacketTypes;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.packet.CustomPayloadS2CPacket;
import net.minecraft.client.network.packet.GameJoinS2CPacket;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.Packet;
import net.minecraft.util.ThreadExecutor;
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.CallbackInfo;
import java.util.Optional;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class MixinClientPlayNetworkHandler implements PacketContext {
@Shadow
private MinecraftClient client;
@Shadow
public abstract void sendPacket(Packet<?> var1);
@Inject(at = @At("RETURN"), method = "onGameJoin")
public void onGameJoin(GameJoinS2CPacket packet, CallbackInfo info) {
Optional<Packet<?>> optionalPacket = PacketRegistryImpl.createInitialRegisterPacket(ClientSidePacketRegistry.INSTANCE);
//noinspection OptionalIsPresent
if (optionalPacket.isPresent()) {
sendPacket(optionalPacket.get());
}
}
// Optional hook: it only removes a warning message.
@Inject(method = "onCustomPayload", at = @At(value = "CONSTANT", args = "stringValue=Unknown custom packed identifier: {}"), cancellable = true, require = 0)
public void onCustomPayloadNotFound(CustomPayloadS2CPacket packet, CallbackInfo info) {
if (packet.getChannel().equals(PacketTypes.REGISTER) || packet.getChannel().equals(PacketTypes.UNREGISTER)) {
info.cancel();
}
}
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
public void onCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo info) {
if (((ClientSidePacketRegistryImpl) ClientSidePacketRegistry.INSTANCE).accept(packet, this)) {
info.cancel();
}
}
@Override
public EnvType getPacketEnvironment() {
return EnvType.CLIENT;
}
@Override
public PlayerEntity getPlayer() {
return client.player;
}
@Override
public ThreadExecutor getTaskQueue() {
return client;
}
}

View file

@ -0,0 +1,42 @@
/*
* 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.network;
import net.fabricmc.fabric.impl.network.CustomPayloadC2SPacketAccessor;
import net.minecraft.server.network.packet.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(CustomPayloadC2SPacket.class)
public class MixinCustomPayloadC2SPacket implements CustomPayloadC2SPacketAccessor {
@Shadow
private Identifier channel;
@Shadow
private PacketByteBuf data;
@Override
public Identifier getChannel() {
return channel;
}
@Override
public PacketByteBuf getData() {
return data;
}
}

View file

@ -0,0 +1,38 @@
/*
* 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.network;
import net.fabricmc.fabric.impl.server.EntityTrackerStreamAccessor;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Set;
import java.util.stream.Stream;
@Mixin(targets = "net.minecraft.server.world.ThreadedAnvilChunkStorage$EntityTracker")
public class MixinEntityTracker implements EntityTrackerStreamAccessor {
@Shadow
@Final
private Set<ServerPlayerEntity> playersTracking;
@Override
public Stream<ServerPlayerEntity> fabric_getTrackingPlayers() {
return playersTracking.stream();
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.network;
import net.fabricmc.fabric.impl.network.ClientSidePacketRegistryImpl;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Screen;
import org.apache.logging.log4j.Logger;
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.CallbackInfo;
@Mixin(MinecraftClient.class)
public class MixinMinecraftClient {
@Inject(at = @At("RETURN"), method = "disconnect(Lnet/minecraft/client/gui/Screen;)V")
public void disconnectAfter(Screen screen_1, CallbackInfo info) {
ClientSidePacketRegistryImpl.invalidateRegisteredIdList();
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.network;
import net.fabricmc.fabric.api.network.PacketRegistry;
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
import net.fabricmc.fabric.impl.network.PacketRegistryImpl;
import net.fabricmc.fabric.impl.network.ServerSidePacketRegistryImpl;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.Packet;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
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 java.util.Optional;
@Mixin(priority = 500, value = PlayerManager.class)
public abstract class MixinPlayerManager {
@Inject(method = "onPlayerConnect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/packet/DifficultyS2CPacket;<init>(Lnet/minecraft/world/Difficulty;Z)V"))
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo info) {
Optional<Packet<?>> optionalPacket = PacketRegistryImpl.createInitialRegisterPacket(ServerSidePacketRegistry.INSTANCE);
//noinspection OptionalIsPresent
if (optionalPacket.isPresent()) {
player.networkHandler.sendPacket(optionalPacket.get());
((ServerSidePacketRegistryImpl) ServerSidePacketRegistry.INSTANCE).addNetworkHandler(player.networkHandler);
}
}
}

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.network;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.network.PacketContext;
import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
import net.fabricmc.fabric.impl.network.ServerSidePacketRegistryImpl;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.network.packet.CustomPayloadC2SPacket;
import net.minecraft.util.ThreadExecutor;
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.CallbackInfo;
@Mixin(ServerPlayNetworkHandler.class)
public class MixinServerPlayNetworkHandler implements PacketContext {
@Shadow
private MinecraftServer server;
@Shadow
private ServerPlayerEntity player;
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
public void onCustomPayload(CustomPayloadC2SPacket packet, CallbackInfo info) {
if (((ServerSidePacketRegistryImpl) ServerSidePacketRegistry.INSTANCE).accept(packet, this)) {
info.cancel();
}
}
@Override
public EnvType getPacketEnvironment() {
return EnvType.SERVER;
}
@Override
public PlayerEntity getPlayer() {
return player;
}
@Override
public ThreadExecutor getTaskQueue() {
return server;
}
}

View file

@ -0,0 +1,42 @@
/*
* 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.network;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.fabricmc.fabric.impl.server.EntityTrackerStorageAccessor;
import net.fabricmc.fabric.impl.server.EntityTrackerStreamAccessor;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.stream.Stream;
@Mixin(ThreadedAnvilChunkStorage.class)
public class MixinThreadedAnvilChunkStorage implements EntityTrackerStorageAccessor {
@Shadow
@Final
private Int2ObjectMap<EntityTrackerStreamAccessor> entityTrackers;
@Override
public Stream<ServerPlayerEntity> fabric_getTrackingPlayers(Entity entity) {
EntityTrackerStreamAccessor accessor = entityTrackers.get(entity.getEntityId());
return accessor != null ? accessor.fabric_getTrackingPlayers() : Stream.empty();
}
}

View file

@ -0,0 +1,19 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.network",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinCustomPayloadC2SPacket",
"MixinEntityTracker",
"MixinPlayerManager",
"MixinServerPlayNetworkHandler",
"MixinThreadedAnvilChunkStorage"
],
"client": [
"MixinClientPlayNetworkHandler",
"MixinMinecraftClient"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,9 @@
{
"schemaVersion": 1,
"id": "fabric-networking-v0",
"version": "${version}",
"license": "Apache-2.0",
"mixins": [
"fabric-networking-v0.mixins.json"
]
}