1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-26 07:03:22 +00:00

Make net code more multi-loader friendly

NetworkMessage follows vanilla's Packet much more closely now: the
handler takes a context argument, which varies between the client and
server.

 - The server handler just returns the ServerPlayer who sent the
   message.
 - The client context provides handleXyz(...) methods for each
   clientbound packet. Our packet subclasses call the appropriate
   method - the implementation of which contains the actual
   implementation.

Doing this allows us to move a whole bunch of client-specific code into
the main client package, dropping several @OnlyIn annotations and
generally reducing the risk of accidentally loading client classes on
the server.

We also abstract out some packet sending and general networking code to
make it easier to use in a multi-loader context.
This commit is contained in:
Jonathan Coates 2022-11-06 20:12:32 +00:00
parent 564752c8dd
commit 53abe5e56e
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
49 changed files with 693 additions and 363 deletions

View File

@ -14,12 +14,12 @@
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.shared.Config;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.peripheral.generic.data.FluidData;
import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods;
import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;

View File

@ -5,9 +5,9 @@
*/
package dan200.computercraft.client.gui;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.shared.computer.core.InputHandler;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
@ -30,51 +30,51 @@ public ClientInputHandler(AbstractContainerMenu menu) {
@Override
public void turnOn() {
NetworkHandler.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
}
@Override
public void shutdown() {
NetworkHandler.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
}
@Override
public void reboot() {
NetworkHandler.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
ClientPlatformHelper.get().sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
}
@Override
public void queueEvent(String event, @Nullable Object[] arguments) {
NetworkHandler.sendToServer(new QueueEventServerMessage(menu, event, arguments));
ClientPlatformHelper.get().sendToServer(new QueueEventServerMessage(menu, event, arguments));
}
@Override
public void keyDown(int key, boolean repeat) {
NetworkHandler.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key));
}
@Override
public void keyUp(int key) {
NetworkHandler.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
ClientPlatformHelper.get().sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.TYPE_UP, key));
}
@Override
public void mouseClick(int button, int x, int y) {
NetworkHandler.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_CLICK, button, x, y));
}
@Override
public void mouseUp(int button, int x, int y) {
NetworkHandler.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_UP, button, x, y));
}
@Override
public void mouseDrag(int button, int x, int y) {
NetworkHandler.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_DRAG, button, x, y));
}
@Override
public void mouseScroll(int direction, int x, int y) {
NetworkHandler.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
ClientPlatformHelper.get().sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.TYPE_SCROLL, direction, x, y));
}
}

View File

@ -10,13 +10,13 @@
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.InputHandler;
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.server.UploadFileMessage;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
@ -182,7 +182,7 @@ public void onFilesDrop(@Nonnull List<Path> files) {
return;
}
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, NetworkHandler::sendToServer);
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientPlatformHelper.get()::sendToServer);
}
public void uploadResult(UploadResult result, @Nullable Component message) {

View File

@ -0,0 +1,118 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.platform;
import dan200.computercraft.client.ClientTableFormatter;
import dan200.computercraft.client.gui.ComputerScreenBase;
import dan200.computercraft.client.gui.OptionScreen;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import java.util.UUID;
/**
* The base implementation of {@link ClientNetworkContext}.
* <p>
* This should be extended by mod loader specific modules with the remaining abstract methods.
*/
public abstract class AbstractClientNetworkContext implements ClientNetworkContext {
@Override
public final void handleChatTable(TableBuilder table) {
ClientTableFormatter.INSTANCE.display(table);
}
@Override
public final void handleComputerTerminal(int containerId, TerminalState terminal) {
Player player = Minecraft.getInstance().player;
if (player != null && player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
menu.updateTerminal(terminal);
}
}
@Override
public final void handleMonitorData(BlockPos pos, TerminalState terminal) {
var player = Minecraft.getInstance().player;
if (player == null) return;
var te = player.level.getBlockEntity(pos);
if (!(te instanceof TileMonitor monitor)) return;
monitor.read(terminal);
}
@Override
public final void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal) {
var computer = ClientPocketComputers.get(instanceId, terminal.colour);
computer.setState(state, lightState);
if (terminal.hasTerminal()) computer.setTerminal(terminal);
}
@Override
public final void handlePocketComputerDeleted(int instanceId) {
ClientPocketComputers.remove(instanceId);
}
@Override
public final void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume) {
SpeakerManager.getSound(source).playAudio(reifyPosition(position), volume);
}
@Override
public final void handleSpeakerAudioPush(UUID source, ByteBuf buffer) {
SpeakerManager.getSound(source).pushAudio(buffer);
}
@Override
public final void handleSpeakerMove(UUID source, SpeakerPosition.Message position) {
SpeakerManager.moveSound(source, reifyPosition(position));
}
@Override
public final void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch) {
SpeakerManager.getSound(source).playSound(reifyPosition(position), sound, volume, pitch);
}
@Override
public final void handleSpeakerStop(UUID source) {
SpeakerManager.stopSound(source);
}
@Override
public final void handleUploadResult(int containerId, UploadResult result, Component errorMessage) {
var minecraft = Minecraft.getInstance();
var screen = OptionScreen.unwrap(minecraft.screen);
if (screen instanceof ComputerScreenBase<?> && ((ComputerScreenBase<?>) screen).getMenu().containerId == containerId) {
((ComputerScreenBase<?>) screen).uploadResult(result, errorMessage);
}
}
private static SpeakerPosition reifyPosition(SpeakerPosition.Message pos) {
var minecraft = Minecraft.getInstance();
Level level = minecraft.level;
if (level != null && !level.dimension().location().equals(pos.level())) level = null;
return new SpeakerPosition(
level, pos.position(),
level != null && pos.entity().isPresent() ? level.getEntity(pos.entity().getAsInt()) : null
);
}
}

View File

@ -0,0 +1,25 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
@AutoService(ClientNetworkContext.class)
public class ClientNetworkContextImpl extends AbstractClientNetworkContext {
@Override
public void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name) {
var mc = Minecraft.getInstance();
mc.levelRenderer.playStreamingMusic(sound, pos, null);
if (name != null) mc.gui.setNowPlaying(Component.literal(name));
}
}

View File

@ -0,0 +1,22 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.platform;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
public interface ClientPlatformHelper extends dan200.computercraft.impl.client.ClientPlatformHelper {
static ClientPlatformHelper get() {
return (ClientPlatformHelper) dan200.computercraft.impl.client.ClientPlatformHelper.get();
}
/**
* Send a network message to the server.
*
* @param message The message to send.
*/
void sendToServer(NetworkMessage<ServerNetworkContext> message);
}

View File

@ -6,15 +6,23 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
@AutoService(ClientPlatformHelper.class)
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
return manager.getModel(location);
}
@Override
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
NetworkHandler.sendToServer(message);
}
}

View File

@ -11,9 +11,9 @@
import dan200.computercraft.shared.computer.core.ResourceMount;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.metrics.ComputerMBean;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
@ -116,9 +116,9 @@ public static void onAddReloadListeners(AddReloadListenerEvent event) {
public static void onDatapackSync(OnDatapackSyncEvent event) {
var packet = new UpgradesLoadedMessage();
if (event.getPlayer() == null) {
NetworkHandler.sendToAllPlayers(packet);
PlatformHelper.get().sendToAllPlayers(packet, event.getPlayerList().getServer());
} else {
NetworkHandler.sendToPlayer(event.getPlayer(), packet);
PlatformHelper.get().sendToPlayer(packet, event.getPlayer());
}
}
}

View File

@ -275,13 +275,13 @@ public static class Menus {
static final RegistrationHelper<MenuType<?>> REGISTRY = PlatformHelper.get().createRegistrationHelper(Registry.MENU_REGISTRY);
public static final RegistryEntry<MenuType<ContainerComputerBase>> COMPUTER = REGISTRY.register("computer",
() -> ContainerData.toType(ComputerContainerData::new, ComputerMenuWithoutInventory::new));
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.COMPUTER.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ContainerComputerBase>> POCKET_COMPUTER = REGISTRY.register("pocket_computer",
() -> ContainerData.toType(ComputerContainerData::new, ComputerMenuWithoutInventory::new));
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ContainerComputerBase>> POCKET_COMPUTER_NO_TERM = REGISTRY.register("pocket_computer_no_term",
() -> ContainerData.toType(ComputerContainerData::new, ComputerMenuWithoutInventory::new));
() -> ContainerData.toType(ComputerContainerData::new, (id, inv, data) -> new ComputerMenuWithoutInventory(Menus.POCKET_COMPUTER_NO_TERM.get(), id, inv, data)));
public static final RegistryEntry<MenuType<ContainerTurtle>> TURTLE = REGISTRY.register("turtle",
() -> ContainerData.toType(ComputerContainerData::new, ContainerTurtle::ofMenuData));

View File

@ -6,8 +6,8 @@
package dan200.computercraft.shared.command.text;
import dan200.computercraft.shared.command.CommandUtils;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.ChatTableClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
@ -106,7 +106,7 @@ public void trim(int height) {
public void display(CommandSourceStack source) {
if (CommandUtils.isPlayer(source)) {
trim(18);
NetworkHandler.sendToPlayer((ServerPlayer) source.getEntity(), new ChatTableClientMessage(this));
PlatformHelper.get().sendToPlayer(new ChatTableClientMessage(this), (ServerPlayer) source.getEntity());
} else {
trim(100);
new ServerTableFormatter(source).display(this);

View File

@ -18,9 +18,9 @@
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.inventory.AbstractContainerMenu;
@ -138,7 +138,7 @@ private void sendToAllInteracting(Function<AbstractContainerMenu, NetworkMessage
for (var player : server.getPlayerList().getPlayers()) {
if (player.containerMenu instanceof ComputerMenu && ((ComputerMenu) player.containerMenu).getComputer() == this) {
NetworkHandler.sendToPlayer(player, createPacket.apply(player.containerMenu));
PlatformHelper.get().sendToPlayer(createPacket.apply(player.containerMenu), player);
}
}
}

View File

@ -7,9 +7,8 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.upload.*;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.UploadResultMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.network.chat.Component;
@ -133,8 +132,7 @@ public void finishUpload(ServerPlayer uploader, UUID uploadId) {
return;
}
NetworkMessage message = finishUpload(uploader);
NetworkHandler.sendToPlayer(uploader, message);
PlatformHelper.get().sendToPlayer(finishUpload(uploader), uploader);
}
private UploadResultMessage finishUpload(ServerPlayer player) {

View File

@ -6,8 +6,8 @@
package dan200.computercraft.shared.computer.upload;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.UploadResultMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.AbstractContainerMenu;
@ -47,7 +47,7 @@ private void consumed() {
if (consumed.getAndSet(true)) return;
if (player.isAlive() && player.containerMenu == container) {
NetworkHandler.sendToPlayer(player, UploadResultMessage.consumed(container));
PlatformHelper.get().sendToPlayer(UploadResultMessage.consumed(container), player);
}
}
}

View File

@ -1,114 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.network.client.*;
import dan200.computercraft.shared.network.server.*;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
import java.util.Collection;
import java.util.function.Function;
public final class NetworkHandler {
private static SimpleChannel network;
private static final IntSet usedIds = new IntOpenHashSet();
private NetworkHandler() {
}
public static void setup() {
var version = ComputerCraftAPI.getInstalledVersion();
network = NetworkRegistry.ChannelBuilder.named(new ResourceLocation(ComputerCraft.MOD_ID, "network"))
.networkProtocolVersion(() -> version)
.clientAcceptedVersions(version::equals).serverAcceptedVersions(version::equals)
.simpleChannel();
// Server messages
registerMainThread(0, NetworkDirection.PLAY_TO_SERVER, ComputerActionServerMessage.class, ComputerActionServerMessage::new);
registerMainThread(1, NetworkDirection.PLAY_TO_SERVER, QueueEventServerMessage.class, QueueEventServerMessage::new);
registerMainThread(2, NetworkDirection.PLAY_TO_SERVER, KeyEventServerMessage.class, KeyEventServerMessage::new);
registerMainThread(3, NetworkDirection.PLAY_TO_SERVER, MouseEventServerMessage.class, MouseEventServerMessage::new);
registerMainThread(4, NetworkDirection.PLAY_TO_SERVER, UploadFileMessage.class, UploadFileMessage::new);
// Client messages
registerMainThread(10, NetworkDirection.PLAY_TO_CLIENT, ChatTableClientMessage.class, ChatTableClientMessage::new);
registerMainThread(11, NetworkDirection.PLAY_TO_CLIENT, PocketComputerDataMessage.class, PocketComputerDataMessage::new);
registerMainThread(12, NetworkDirection.PLAY_TO_CLIENT, PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
registerMainThread(13, NetworkDirection.PLAY_TO_CLIENT, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
registerMainThread(14, NetworkDirection.PLAY_TO_CLIENT, PlayRecordClientMessage.class, PlayRecordClientMessage::new);
registerMainThread(15, NetworkDirection.PLAY_TO_CLIENT, MonitorClientMessage.class, MonitorClientMessage::new);
registerMainThread(16, NetworkDirection.PLAY_TO_CLIENT, SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
registerMainThread(17, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
registerMainThread(18, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
registerMainThread(19, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
registerMainThread(20, NetworkDirection.PLAY_TO_CLIENT, UploadResultMessage.class, UploadResultMessage::new);
registerMainThread(21, NetworkDirection.PLAY_TO_CLIENT, UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
}
public static void sendToPlayer(ServerPlayer player, NetworkMessage packet) {
network.sendTo(packet, player.connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToAllPlayers(NetworkMessage packet) {
network.send(PacketDistributor.ALL.noArg(), packet);
}
public static void sendToServer(NetworkMessage packet) {
network.sendToServer(packet);
}
public static void sendToAllAround(NetworkMessage packet, Level world, Vec3 pos, double range) {
var target = new PacketDistributor.TargetPoint(pos.x, pos.y, pos.z, range, world.dimension());
network.send(PacketDistributor.NEAR.with(() -> target), packet);
}
public static void sendToAllTracking(NetworkMessage packet, LevelChunk chunk) {
network.send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), packet);
}
public static void sendToPlayers(NetworkMessage packet, Collection<ServerPlayer> players) {
if (players.isEmpty()) return;
var vanillaPacket = network.toVanillaPacket(packet, NetworkDirection.PLAY_TO_CLIENT);
for (var player : players) player.connection.send(vanillaPacket);
}
/**
* Register packet, and a thread-unsafe handler for it.
*
* @param <T> The type of the packet to send.
* @param type The class of the type of packet to send.
* @param id The identifier for this packet type.
* @param direction A network direction which will be asserted before any processing of this message occurs
* @param decoder The factory for this type of packet.
*/
private static <T extends NetworkMessage> void registerMainThread(int id, NetworkDirection direction, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
if (!usedIds.add(id)) throw new IllegalStateException("Duplicate message kind for for id " + id);
network.messageBuilder(type, id, direction)
.encoder(NetworkMessage::toBytes)
.decoder(decoder)
.consumerMainThread((packet, contextSup) -> {
var context = contextSup.get();
context.enqueueWork(() -> packet.handle(context));
context.setPacketHandled(true);
})
.add();
}
}

View File

@ -5,18 +5,20 @@
*/
package dan200.computercraft.shared.network;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
/**
* The base interface for any message which will be sent to the client or server.
*
* @see dan200.computercraft.shared.network.client
* @see dan200.computercraft.shared.network.server
* @param <T> The context under which packets are evaluated.
* @see ClientNetworkContext
* @see ServerNetworkContext
*/
public interface NetworkMessage {
public interface NetworkMessage<T> {
/**
* Write this packet to a buffer.
* <p>
@ -31,5 +33,5 @@ public interface NetworkMessage {
*
* @param context The context with which to handle this message
*/
void handle(NetworkEvent.Context context);
void handle(T context);
}

View File

@ -0,0 +1,52 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network;
import dan200.computercraft.shared.network.client.*;
import dan200.computercraft.shared.network.server.*;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.network.FriendlyByteBuf;
import java.util.function.Function;
/**
* Registry for all packets provided by CC: Tweaked.
*
* @see PlatformHelper The platform helper is used to send packets.
*/
public final class NetworkMessages {
private NetworkMessages() {
}
public interface PacketRegistry {
<T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
<T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
}
public static void register(PacketRegistry registry) {
// Server messages
registry.registerServerbound(0, ComputerActionServerMessage.class, ComputerActionServerMessage::new);
registry.registerServerbound(1, QueueEventServerMessage.class, QueueEventServerMessage::new);
registry.registerServerbound(2, KeyEventServerMessage.class, KeyEventServerMessage::new);
registry.registerServerbound(3, MouseEventServerMessage.class, MouseEventServerMessage::new);
registry.registerServerbound(4, UploadFileMessage.class, UploadFileMessage::new);
// Client messages
registry.registerClientbound(10, ChatTableClientMessage.class, ChatTableClientMessage::new);
registry.registerClientbound(11, PocketComputerDataMessage.class, PocketComputerDataMessage::new);
registry.registerClientbound(12, PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
registry.registerClientbound(13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
registry.registerClientbound(14, PlayRecordClientMessage.class, PlayRecordClientMessage::new);
registry.registerClientbound(15, MonitorClientMessage.class, MonitorClientMessage::new);
registry.registerClientbound(16, SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
registry.registerClientbound(17, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
registry.registerClientbound(18, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
registry.registerClientbound(19, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
registry.registerClientbound(20, UploadResultMessage.class, UploadResultMessage::new);
registry.registerClientbound(21, UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
}
}

View File

@ -5,16 +5,14 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.ClientTableFormatter;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
public class ChatTableClientMessage implements NetworkMessage {
public class ChatTableClientMessage implements NetworkMessage<ClientNetworkContext> {
private static final int MAX_LEN = 16;
private final TableBuilder table;
@ -64,7 +62,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
public void handle(NetworkEvent.Context context) {
ClientTableFormatter.INSTANCE.display(table);
public void handle(ClientNetworkContext context) {
context.handleChatTable(table);
}
}

View File

@ -0,0 +1,69 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.impl.Services;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import io.netty.buffer.ByteBuf;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* The context under which clientbound packets are evaluated.
*/
public interface ClientNetworkContext {
static ClientNetworkContext get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientNetworkContext.class, Instance.ERROR) : instance;
}
void handleChatTable(TableBuilder table);
void handleComputerTerminal(int containerId, TerminalState terminal);
void handleMonitorData(BlockPos pos, TerminalState terminal);
void handlePlayRecord(BlockPos pos, @Nullable SoundEvent sound, @Nullable String name);
void handlePocketComputerData(int instanceId, ComputerState state, int lightState, TerminalState terminal);
void handlePocketComputerDeleted(int instanceId);
void handleSpeakerAudio(UUID source, SpeakerPosition.Message position, float volume);
void handleSpeakerAudioPush(UUID source, ByteBuf buffer);
void handleSpeakerMove(UUID source, SpeakerPosition.Message position);
void handleSpeakerPlay(UUID source, SpeakerPosition.Message position, ResourceLocation sound, float volume, float pitch);
void handleSpeakerStop(UUID source);
void handleUploadResult(int containerId, UploadResult result, Component errorMessage);
final class Instance {
static final @Nullable ClientNetworkContext INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ClientNetworkContext.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@ -5,20 +5,14 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
public class ComputerTerminalClientMessage implements NetworkMessage {
public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetworkContext> {
private final int containerId;
private final TerminalState terminal;
@ -39,11 +33,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
Player player = Minecraft.getInstance().player;
if (player != null && player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
menu.updateTerminal(terminal);
}
public void handle(ClientNetworkContext context) {
context.handleComputerTerminal(containerId, terminal);
}
}

View File

@ -7,17 +7,12 @@
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
public class MonitorClientMessage implements NetworkMessage {
public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext> {
private final BlockPos pos;
private final TerminalState state;
@ -38,14 +33,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
var player = Minecraft.getInstance().player;
if (player == null || player.level == null) return;
var te = player.level.getBlockEntity(pos);
if (!(te instanceof TileMonitor)) return;
((TileMonitor) te).read(state);
public void handle(ClientNetworkContext context) {
context.handleMonitorData(pos, state);
}
}

View File

@ -7,14 +7,9 @@
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.platform.Registries;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
@ -25,7 +20,7 @@
*
* @see dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive
*/
public class PlayRecordClientMessage implements NetworkMessage {
public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkContext> {
private final BlockPos pos;
private final String name;
private final SoundEvent soundEvent;
@ -66,10 +61,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
var mc = Minecraft.getInstance();
mc.levelRenderer.playStreamingMusic(soundEvent, pos, null);
if (name != null) mc.gui.setNowPlaying(Component.literal(name));
public void handle(ClientNetworkContext context) {
context.handlePlayRecord(pos, soundEvent, name);
}
}

View File

@ -5,19 +5,17 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent;
/**
* Provides additional data about a client computer, such as its ID and current state.
*/
public class PocketComputerDataMessage implements NetworkMessage {
public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkContext> {
private final int instanceId;
private final ComputerState state;
private final int lightState;
@ -46,9 +44,7 @@ public void toBytes(FriendlyByteBuf buf) {
}
@Override
public void handle(NetworkEvent.Context context) {
var computer = ClientPocketComputers.get(instanceId, terminal.colour);
computer.setState(state, lightState);
if (terminal.hasTerminal()) computer.setTerminal(terminal);
public void handle(ClientNetworkContext context) {
context.handlePocketComputerData(instanceId, state, lightState, terminal);
}
}

View File

@ -5,14 +5,12 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
public class PocketComputerDeletedClientMessage implements NetworkMessage {
public class PocketComputerDeletedClientMessage implements NetworkMessage<ClientNetworkContext> {
private final int instanceId;
public PocketComputerDeletedClientMessage(int instanceId) {
@ -29,7 +27,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
public void handle(NetworkEvent.Context context) {
ClientPocketComputers.remove(instanceId);
public void handle(ClientNetworkContext context) {
context.handlePocketComputerDeleted(instanceId);
}
}

View File

@ -5,13 +5,9 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
@ -24,7 +20,7 @@
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerAudioClientMessage implements NetworkMessage {
public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
private final SpeakerPosition.Message pos;
private final ByteBuffer content;
@ -42,7 +38,9 @@ public SpeakerAudioClientMessage(FriendlyByteBuf buf) {
pos = SpeakerPosition.Message.read(buf);
volume = buf.readFloat();
SpeakerManager.getSound(source).pushAudio(buf);
// TODO: Remove this, so we no longer need a getter for ClientNetworkContext. However, doing so without
// leaking or redundantly copying the buffer is hard.
ClientNetworkContext.get().handleSpeakerAudioPush(source, buf);
content = null;
}
@ -55,8 +53,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
SpeakerManager.getSound(source).playAudio(pos.reify(), volume);
public void handle(ClientNetworkContext context) {
context.handleSpeakerAudio(source, pos, volume);
}
}

View File

@ -5,13 +5,9 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.util.UUID;
@ -23,7 +19,7 @@
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerMoveClientMessage implements NetworkMessage {
public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
private final SpeakerPosition.Message pos;
@ -44,8 +40,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
SpeakerManager.moveSound(source, pos.reify());
public void handle(ClientNetworkContext context) {
context.handleSpeakerMove(source, pos);
}
}

View File

@ -5,14 +5,10 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.util.UUID;
@ -24,7 +20,7 @@
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerPlayClientMessage implements NetworkMessage {
public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
private final SpeakerPosition.Message pos;
private final ResourceLocation sound;
@ -57,8 +53,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
SpeakerManager.getSound(source).playSound(pos.reify(), sound, volume, pitch);
public void handle(ClientNetworkContext context) {
context.handleSpeakerPlay(source, pos, sound, volume, pitch);
}
}

View File

@ -5,12 +5,8 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.util.UUID;
@ -22,7 +18,7 @@
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerStopClientMessage implements NetworkMessage {
public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkContext> {
private final UUID source;
public SpeakerStopClientMessage(UUID source) {
@ -39,8 +35,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
@OnlyIn(Dist.CLIENT)
public void handle(NetworkEvent.Context context) {
SpeakerManager.stopSound(source);
public void handle(ClientNetworkContext context) {
context.handleSpeakerStop(source);
}
}

View File

@ -20,7 +20,6 @@
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.util.HashMap;
@ -30,7 +29,7 @@
/**
* Syncs turtle and pocket upgrades to the client.
*/
public class UpgradesLoadedMessage implements NetworkMessage {
public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
private final Map<String, UpgradeManager.UpgradeWrapper<TurtleUpgradeSerialiser<?>, ITurtleUpgrade>> turtleUpgrades;
private final Map<String, UpgradeManager.UpgradeWrapper<PocketUpgradeSerialiser<?>, IPocketUpgrade>> pocketUpgrades;
@ -94,7 +93,7 @@ private <R extends UpgradeSerialiser<? extends T>, T extends IUpgradeBase> void
}
@Override
public void handle(NetworkEvent.Context context) {
public void handle(ClientNetworkContext context) {
TurtleUpgrades.instance().loadFromNetwork(turtleUpgrades);
PocketUpgrades.instance().loadFromNetwork(pocketUpgrades);
}

View File

@ -5,20 +5,16 @@
*/
package dan200.computercraft.shared.network.client;
import dan200.computercraft.client.gui.ComputerScreenBase;
import dan200.computercraft.client.gui.OptionScreen;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class UploadResultMessage implements NetworkMessage {
public class UploadResultMessage implements NetworkMessage<ClientNetworkContext> {
private final int containerId;
private final UploadResult result;
private final Component errorMessage;
@ -55,12 +51,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
public void handle(NetworkEvent.Context context) {
var minecraft = Minecraft.getInstance();
var screen = OptionScreen.unwrap(minecraft.screen);
if (screen instanceof ComputerScreenBase<?> && ((ComputerScreenBase<?>) screen).getMenu().containerId == containerId) {
((ComputerScreenBase<?>) screen).uploadResult(result, errorMessage);
}
public void handle(ClientNetworkContext context) {
context.handleUploadResult(containerId, result, errorMessage);
}
}

View File

@ -5,60 +5,39 @@
*/
package dan200.computercraft.shared.network.container;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.network.IContainerFactory;
import net.minecraftforge.network.NetworkHooks;
import javax.annotation.Nonnull;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* An extension over the basic {@link IForgeMenuType}/{@link NetworkHooks#openGui(ServerPlayer, MenuProvider, Consumer)}
* hooks, with a more convenient way of reading and writing data.
* Additional data to send when opening a menu. Like {@link NetworkMessage}, this should be immutable.
*/
public interface ContainerData {
void toBytes(FriendlyByteBuf buf);
default void open(Player player, MenuProvider owner) {
NetworkHooks.openScreen((ServerPlayer) player, owner, this::toBytes);
/**
* Open a menu for a specific player using this data.
*
* @param player The player to open the menu for.
* @param menu The underlying menu provider.
*/
default void open(Player player, MenuProvider menu) {
PlatformHelper.get().openMenu(player, menu, this);
}
static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType(Function<FriendlyByteBuf, T> reader, Factory<C, T> factory) {
return IForgeMenuType.create((id, player, data) -> factory.create(id, player, reader.apply(data)));
}
static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType(Function<FriendlyByteBuf, T> reader, FixedFactory<C, T> factory) {
return new FixedPointContainerFactory<>(reader, factory).type;
return PlatformHelper.get().createMenuType(reader, factory);
}
interface Factory<C extends AbstractContainerMenu, T extends ContainerData> {
C create(int id, @Nonnull Inventory inventory, T data);
}
interface FixedFactory<C extends AbstractContainerMenu, T extends ContainerData> {
C create(MenuType<C> type, int id, @Nonnull Inventory inventory, T data);
}
final class FixedPointContainerFactory<C extends AbstractContainerMenu, T extends ContainerData> implements IContainerFactory<C> {
private final IContainerFactory<C> impl;
private final MenuType<C> type;
private FixedPointContainerFactory(Function<FriendlyByteBuf, T> reader, FixedFactory<C, T> factory) {
var type = this.type = IForgeMenuType.create(this);
impl = (id, player, data) -> factory.create(type, id, player, reader.apply(data));
}
@Override
public C create(int windowId, Inventory inv, FriendlyByteBuf data) {
return impl.create(windowId, inv, data);
}
}
}

View File

@ -8,7 +8,6 @@
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
@ -32,7 +31,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
protected void handle(NetworkEvent.Context context, @Nonnull ComputerMenu container) {
protected void handle(ServerNetworkContext context, @Nonnull ComputerMenu container) {
switch (action) {
case TURN_ON -> container.getInput().turnOn();
case REBOOT -> container.getInput().reboot();

View File

@ -10,7 +10,6 @@
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import javax.annotation.OverridingMethodsMustInvokeSuper;
@ -18,7 +17,7 @@
/**
* A packet, which performs an action on the currently open {@link ComputerMenu}.
*/
public abstract class ComputerServerMessage implements NetworkMessage {
public abstract class ComputerServerMessage implements NetworkMessage<ServerNetworkContext> {
private final int containerId;
protected ComputerServerMessage(AbstractContainerMenu menu) {
@ -36,12 +35,12 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
public void handle(NetworkEvent.Context context) {
public void handle(ServerNetworkContext context) {
Player player = context.getSender();
if (player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu) {
handle(context, (ComputerMenu) player.containerMenu);
}
}
protected abstract void handle(NetworkEvent.Context context, @Nonnull ComputerMenu container);
protected abstract void handle(ServerNetworkContext context, @Nonnull ComputerMenu container);
}

View File

@ -8,7 +8,6 @@
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
@ -40,7 +39,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
protected void handle(NetworkEvent.Context context, @Nonnull ComputerMenu container) {
protected void handle(ServerNetworkContext context, @Nonnull ComputerMenu container) {
var input = container.getInput();
if (type == TYPE_UP) {
input.keyUp(key);

View File

@ -8,7 +8,6 @@
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
@ -49,7 +48,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
protected void handle(NetworkEvent.Context context, @Nonnull ComputerMenu container) {
protected void handle(ServerNetworkContext context, @Nonnull ComputerMenu container) {
var input = container.getInput();
switch (type) {
case TYPE_CLICK -> input.mouseClick(arg, x, y);

View File

@ -11,7 +11,6 @@
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -47,7 +46,7 @@ public void toBytes(@Nonnull FriendlyByteBuf buf) {
}
@Override
protected void handle(NetworkEvent.Context context, @Nonnull ComputerMenu container) {
protected void handle(ServerNetworkContext context, @Nonnull ComputerMenu container) {
container.getInput().queueEvent(event, args);
}
}

View File

@ -0,0 +1,20 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.network.server;
import net.minecraft.server.level.ServerPlayer;
/**
* The context under which serverbound packets are evaluated.
*/
public interface ServerNetworkContext {
/**
* Get the player who sent this packet.
*
* @return The sending player.
*/
ServerPlayer getSender();
}

View File

@ -12,7 +12,6 @@
import io.netty.handler.codec.DecoderException;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
@ -155,7 +154,7 @@ public static void send(AbstractContainerMenu container, List<FileUpload> files,
}
@Override
protected void handle(NetworkEvent.Context context, @Nonnull ComputerMenu container) {
protected void handle(ServerNetworkContext context, @Nonnull ComputerMenu container) {
var player = context.getSender();
if (player != null) {
var input = container.getInput();

View File

@ -20,7 +20,6 @@
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.*;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Inventory;
@ -36,7 +35,6 @@
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraftforge.network.NetworkHooks;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -105,9 +103,7 @@ public InteractionResult onActivate(Player player, InteractionHand hand, BlockHi
return InteractionResult.SUCCESS;
} else {
// Open the GUI
if (!getLevel().isClientSide && isUsable(player)) {
NetworkHooks.openScreen((ServerPlayer) player, this);
}
if (!getLevel().isClientSide && isUsable(player)) player.openMenu(this);
return InteractionResult.SUCCESS;
}
}

View File

@ -7,8 +7,8 @@
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.MonitorClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.server.level.ServerLevel;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.level.ChunkWatchEvent;
@ -44,7 +44,7 @@ public static void onWatch(ChunkWatchEvent.Watch event) {
if (serverMonitor == null || monitor.enqueued) continue;
var state = getState(monitor, serverMonitor);
NetworkHandler.sendToPlayer(event.getPlayer(), new MonitorClientMessage(monitor.getBlockPos(), state));
PlatformHelper.get().sendToPlayer(new MonitorClientMessage(monitor.getBlockPos(), state), event.getPlayer());
}
}
@ -73,7 +73,7 @@ public static void onTick(TickEvent.ServerTickEvent event) {
}
var state = getState(tile, monitor);
NetworkHandler.sendToAllTracking(new MonitorClientMessage(pos, state), chunk);
PlatformHelper.get().sendToAllTracking(new MonitorClientMessage(pos, state), chunk);
limit -= state.size();
}

View File

@ -16,7 +16,6 @@
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.*;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
@ -34,7 +33,6 @@
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraftforge.items.wrapper.SidedInvWrapper;
import net.minecraftforge.network.NetworkHooks;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -90,9 +88,7 @@ public boolean isUsable(Player player) {
public InteractionResult onActivate(Player player, InteractionHand hand, BlockHitResult hit) {
if (player.isCrouching()) return InteractionResult.PASS;
if (!getLevel().isClientSide && isUsable(player)) {
NetworkHooks.openScreen((ServerPlayer) player, this);
}
if (!getLevel().isClientSide && isUsable(player)) player.openMenu(this);
return InteractionResult.SUCCESS;
}

View File

@ -12,17 +12,18 @@
import dan200.computercraft.api.lua.LuaTable;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.SpeakerAudioClientMessage;
import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage;
import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.Registries;
import dan200.computercraft.shared.util.PauseAwareTimer;
import net.minecraft.ResourceLocationException;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
@ -112,22 +113,22 @@ public void update() {
// Stop the speaker and nuke the position, so we don't update it again.
if (shouldStop && lastPosition != null) {
lastPosition = null;
NetworkHandler.sendToAllPlayers(new SpeakerStopClientMessage(getSource()));
PlatformHelper.get().sendToAllPlayers(new SpeakerStopClientMessage(getSource()), level.getServer());
return;
}
var now = PauseAwareTimer.getTime();
if (sound != null) {
lastPlayTime = clock;
NetworkHandler.sendToAllAround(
PlatformHelper.get().sendToAllAround(
new SpeakerPlayClientMessage(getSource(), position, sound.location, sound.volume, sound.pitch),
level, pos, sound.volume * 16
(ServerLevel) level, pos, sound.volume * 16
);
syncedPosition(position);
} else if (dfpwmState != null && dfpwmState.shouldSendPending(now)) {
// If clients need to receive another batch of audio, send it and then notify computers our internal buffer is
// free again.
NetworkHandler.sendToAllTracking(
PlatformHelper.get().sendToAllTracking(
new SpeakerAudioClientMessage(getSource(), position, dfpwmState.getVolume(), dfpwmState.pullPending(now)),
level.getChunkAt(new BlockPos(pos))
);
@ -146,7 +147,7 @@ public void update() {
// in the last second.
if (lastPosition != null && (clock - lastPositionTime) >= 20 && !lastPosition.withinDistance(position, 0.1)) {
// TODO: What to do when entities move away? How do we notify people left behind that they're gone.
NetworkHandler.sendToAllTracking(
PlatformHelper.get().sendToAllTracking(
new SpeakerMoveClientMessage(getSource(), position),
level.getChunkAt(new BlockPos(pos))
);

View File

@ -5,14 +5,11 @@
*/
package dan200.computercraft.shared.peripheral.speaker;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -36,25 +33,19 @@ public Message asMessage() {
return new Message(level.dimension().location(), position, entity == null ? OptionalInt.empty() : OptionalInt.of(entity.getId()));
}
public static final class Message {
private final ResourceLocation level;
private final Vec3 position;
private final OptionalInt entity;
private Message(ResourceLocation level, Vec3 position, OptionalInt entity) {
this.level = level;
this.position = position;
this.entity = entity;
}
public static Message read(@Nonnull FriendlyByteBuf buffer) {
public record Message(
ResourceLocation level,
Vec3 position,
OptionalInt entity
) {
public static Message read(FriendlyByteBuf buffer) {
var level = buffer.readResourceLocation();
var position = new Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble());
var entity = buffer.readBoolean() ? OptionalInt.of(buffer.readInt()) : OptionalInt.empty();
return new Message(level, position, entity);
}
public void write(@Nonnull FriendlyByteBuf buffer) {
public void write(FriendlyByteBuf buffer) {
buffer.writeResourceLocation(level);
buffer.writeDouble(position.x);
@ -64,18 +55,5 @@ public void write(@Nonnull FriendlyByteBuf buffer) {
buffer.writeBoolean(entity.isPresent());
if (entity.isPresent()) buffer.writeInt(entity.getAsInt());
}
@Nonnull
@OnlyIn(Dist.CLIENT)
public SpeakerPosition reify() {
var minecraft = Minecraft.getInstance();
Level level = minecraft.level;
if (level != null && !level.dimension().location().equals(this.level)) level = null;
return new SpeakerPosition(
level, position,
level != null && entity.isPresent() ? level.getEntity(entity.getAsInt()) : null
);
}
}
}

View File

@ -7,8 +7,8 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@ -40,7 +40,7 @@ protected void serverTick() {
public void setRemoved() {
super.setRemoved();
if (level != null && !level.isClientSide) {
NetworkHandler.sendToAllPlayers(new SpeakerStopClientMessage(peripheral.getSource()));
PlatformHelper.get().sendToAllPlayers(new SpeakerStopClientMessage(peripheral.getSource()), getLevel().getServer());
}
}

View File

@ -6,9 +6,8 @@
package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import net.minecraftforge.server.ServerLifecycleHooks;
import dan200.computercraft.shared.platform.PlatformHelper;
import javax.annotation.Nonnull;
@ -23,9 +22,11 @@ public void detach(@Nonnull IComputerAccess computer) {
super.detach(computer);
// We could be in the process of shutting down the server, so we can't send packets in this case.
var server = ServerLifecycleHooks.getCurrentServer();
var level = getPosition().level();
if (level == null) return;
var server = level.getServer();
if (server == null || server.isStopped()) return;
NetworkHandler.sendToAllPlayers(new SpeakerStopClientMessage(getSource()));
PlatformHelper.get().sendToAllPlayers(new SpeakerStopClientMessage(getSource()), server);
}
}

View File

@ -0,0 +1,121 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.platform;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.simple.SimpleChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.function.Function;
public final class NetworkHandler {
private static final Logger LOG = LoggerFactory.getLogger(NetworkHandler.class);
private static final SimpleChannel network;
static {
var version = ComputerCraftAPI.getInstalledVersion();
network = NetworkRegistry.ChannelBuilder.named(new ResourceLocation(ComputerCraftAPI.MOD_ID, "network"))
.networkProtocolVersion(() -> version)
.clientAcceptedVersions(version::equals).serverAcceptedVersions(version::equals)
.simpleChannel();
}
private NetworkHandler() {
}
public static void setup() {
IntSet usedIds = new IntOpenHashSet();
NetworkMessages.register(new NetworkMessages.PacketRegistry() {
@Override
public <T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
if (!usedIds.add(id)) throw new IllegalArgumentException("Already have a packet with id " + id);
registerMainThread(id, NetworkDirection.PLAY_TO_CLIENT, type, decoder, x -> ClientNetworkContext.get());
}
@Override
public <T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
if (!usedIds.add(id)) throw new IllegalArgumentException("Already have a packet with id " + id);
registerMainThread(id, NetworkDirection.PLAY_TO_SERVER, type, decoder, c -> () -> c.getSender());
}
});
}
static void sendToPlayer(NetworkMessage<ClientNetworkContext> packet, ServerPlayer player) {
network.sendTo(packet, player.connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
static void sendToPlayers(NetworkMessage<ClientNetworkContext> packet, Collection<ServerPlayer> players) {
if (players.isEmpty()) return;
var vanillaPacket = network.toVanillaPacket(packet, NetworkDirection.PLAY_TO_CLIENT);
for (var player : players) player.connection.send(vanillaPacket);
}
static void sendToAllPlayers(NetworkMessage<ClientNetworkContext> packet) {
network.send(PacketDistributor.ALL.noArg(), packet);
}
static void sendToAllAround(NetworkMessage<ClientNetworkContext> packet, Level world, Vec3 pos, double range) {
var target = new PacketDistributor.TargetPoint(pos.x, pos.y, pos.z, range, world.dimension());
network.send(PacketDistributor.NEAR.with(() -> target), packet);
}
static void sendToAllTracking(NetworkMessage<ClientNetworkContext> packet, LevelChunk chunk) {
network.send(PacketDistributor.TRACKING_CHUNK.with(() -> chunk), packet);
}
public static void sendToServer(NetworkMessage<ServerNetworkContext> packet) {
network.sendToServer(packet);
}
/**
* Register packet, and a thread-unsafe handler for it.
*
* @param <T> The type of the packet to send.
* @param <H> The context this packet is evaluated under.
* @param type The class of the type of packet to send.
* @param id The identifier for this packet type.
* @param direction A network direction which will be asserted before any processing of this message occurs
* @param decoder The factory for this type of packet.
* @param handler Gets or constructs the handler for this packet.
*/
static <H, T extends NetworkMessage<H>> void registerMainThread(
int id, NetworkDirection direction, Class<T> type, Function<FriendlyByteBuf, T> decoder,
Function<NetworkEvent.Context, H> handler
) {
network.messageBuilder(type, id, direction)
.encoder(NetworkMessage::toBytes)
.decoder(decoder)
.consumerMainThread((packet, contextSup) -> {
try {
packet.handle(handler.apply(contextSup.get()));
} catch (RuntimeException | Error e) {
LOG.error("Failed handling packet", e);
throw e;
}
})
.add();
}
}

View File

@ -5,17 +5,32 @@
*/
package dan200.computercraft.shared.platform;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* This extends {@linkplain dan200.computercraft.impl.PlatformHelper the API's loader abstraction layer}, adding
@ -69,4 +84,66 @@ static PlatformHelper get() {
* @return The new block entity type.
*/
<T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFunction<BlockPos, BlockState, T> factory, Block block);
/**
* Create a menu type which sends additional data when opened.
*
* @param reader Parse the additional container data into a usable type.
* @param factory The factory to create the new menu.
* @param <C> The menu/container than we open.
* @param <T> The data that we send to the client.
* @return The menu type for this container.
*/
<C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> createMenuType(Function<FriendlyByteBuf, T> reader, ContainerData.Factory<C, T> factory);
/**
* Open a container using a specific {@link ContainerData}.
*
* @param player The player to open the menu for.
* @param owner The underlying menu provider.
* @param menu The menu data.
*/
void openMenu(Player player, MenuProvider owner, ContainerData menu);
/**
* Send a message to a specific player.
*
* @param message The message to send.
* @param player The player to send it to.
*/
void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player);
/**
* Send a message to a set of players.
*
* @param message The message to send.
* @param players The players to send it to.
*/
void sendToPlayers(NetworkMessage<ClientNetworkContext> message, Collection<ServerPlayer> players);
/**
* Send a message to all players.
*
* @param message The message to send.
* @param server The current server.
*/
void sendToAllPlayers(NetworkMessage<ClientNetworkContext> message, MinecraftServer server);
/**
* Send a message to all players around a point.
*
* @param message The message to send.
* @param level The level the point is in.
* @param pos The centre position.
* @param distance The distance to the centre players must be within.
*/
void sendToAllAround(NetworkMessage<ClientNetworkContext> message, ServerLevel level, Vec3 pos, float distance);
/**
* Send a message to all players tracking a chunk.
*
* @param message The message to send.
* @param chunk The chunk players must be tracking.
*/
void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk);
}

View File

@ -7,26 +7,43 @@
import com.google.auto.service.AutoService;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.network.NetworkHooks;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistry;
import net.minecraftforge.registries.RegistryManager;
import net.minecraftforge.registries.RegistryObject;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@AutoService(dan200.computercraft.impl.PlatformHelper.class)
@ -66,6 +83,41 @@ public <T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFuncti
return new BlockEntityType<>(factory::apply, Set.of(block), null);
}
@Override
public <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> createMenuType(Function<FriendlyByteBuf, T> reader, ContainerData.Factory<C, T> factory) {
return IForgeMenuType.create((id, player, data) -> factory.create(id, player, reader.apply(data)));
}
@Override
public void openMenu(Player player, MenuProvider owner, ContainerData menu) {
NetworkHooks.openScreen((ServerPlayer) player, owner, menu::toBytes);
}
@Override
public void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player) {
NetworkHandler.sendToPlayer(message, player);
}
@Override
public void sendToPlayers(NetworkMessage<ClientNetworkContext> message, Collection<ServerPlayer> players) {
NetworkHandler.sendToPlayers(message, players);
}
@Override
public void sendToAllPlayers(NetworkMessage<ClientNetworkContext> message, MinecraftServer server) {
NetworkHandler.sendToAllPlayers(message);
}
@Override
public void sendToAllAround(NetworkMessage<ClientNetworkContext> message, ServerLevel level, Vec3 pos, float distance) {
NetworkHandler.sendToAllAround(message, level, pos, distance);
}
@Override
public void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk) {
NetworkHandler.sendToAllTracking(message, chunk);
}
@Nullable
@Override
public CompoundTag getShareTag(ItemStack item) {

View File

@ -13,9 +13,9 @@
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
@ -164,7 +164,7 @@ public void tickServer() {
if (sendState) {
// Broadcast the state to all players
tracking.addAll(getLevel().players());
NetworkHandler.sendToPlayers(new PocketComputerDataMessage(this, false), tracking);
PlatformHelper.get().sendToPlayers(new PocketComputerDataMessage(this, false), tracking);
} else {
// Broadcast the state to new players.
List<ServerPlayer> added = new ArrayList<>();
@ -172,7 +172,7 @@ public void tickServer() {
if (tracking.add(player)) added.add(player);
}
if (!added.isEmpty()) {
NetworkHandler.sendToPlayers(new PocketComputerDataMessage(this, false), added);
PlatformHelper.get().sendToPlayers(new PocketComputerDataMessage(this, false), added);
}
}
}
@ -183,13 +183,13 @@ protected void onTerminalChanged() {
if (entity instanceof ServerPlayer player && entity.isAlive()) {
// Broadcast the terminal to the current player.
NetworkHandler.sendToPlayer(player, new PocketComputerDataMessage(this, true));
PlatformHelper.get().sendToPlayer(new PocketComputerDataMessage(this, true), player);
}
}
@Override
protected void onRemoved() {
super.onRemoved();
NetworkHandler.sendToAllPlayers(new PocketComputerDeletedClientMessage(getInstanceID()));
PlatformHelper.get().sendToAllPlayers(new PocketComputerDeletedClientMessage(getInstanceID()), getLevel().getServer());
}
}

View File

@ -5,10 +5,11 @@
*/
package dan200.computercraft.shared.util;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.PlayRecordClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
@ -19,6 +20,6 @@ private RecordUtil() {
public static void playRecord(SoundEvent record, String recordInfo, Level world, BlockPos pos) {
NetworkMessage packet = record != null ? new PlayRecordClientMessage(pos, record, recordInfo) : new PlayRecordClientMessage(pos);
NetworkHandler.sendToAllAround(packet, world, Vec3.atCenterOf(pos), 64);
PlatformHelper.get().sendToAllAround(packet, (ServerLevel) world, Vec3.atCenterOf(pos), 64);
}
}