From d6a4759bde2e897b300b73330d913331345a92bb Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 29 May 2024 17:06:15 +0100 Subject: [PATCH 1/4] Some fixes to c2c packets --- .../clientcommands/c2c/C2CPacketHandler.java | 96 ++++++++++++++++++- .../clientcommands/c2c/ConversionHelper.java | 19 ++-- .../command/arguments/PacketTypeArgument.java | 8 +- .../clientcommands/features/PacketDumper.java | 7 +- .../interfaces/IClientPacketListener_C2C.java | 8 ++ .../mixin/c2c/ChatComponentMixin.java | 63 +----------- .../mixin/c2c/ClientPacketListenerMixin.java | 28 ++++++ src/main/resources/mixins.clientcommands.json | 1 + 8 files changed, 155 insertions(+), 75 deletions(-) create mode 100644 src/main/java/net/earthcomputer/clientcommands/interfaces/IClientPacketListener_C2C.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ClientPacketListenerMixin.java diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java index 9ed9df6f7..1c6be3eff 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java @@ -3,11 +3,16 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.logging.LogUtils; +import io.netty.buffer.Unpooled; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; import net.earthcomputer.clientcommands.command.ListenCommand; +import net.earthcomputer.clientcommands.interfaces.IClientPacketListener_C2C; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.AccountProfileKeyPairManager; +import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.multiplayer.PlayerInfo; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; @@ -19,19 +24,25 @@ import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.ProtocolInfoBuilder; +import net.minecraft.world.entity.player.ProfileKeyPair; import net.minecraft.world.entity.player.ProfilePublicKey; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; import java.security.PublicKey; +import java.util.Arrays; +import java.util.Optional; public class C2CPacketHandler implements C2CPacketListener { + private static final Logger LOGGER = LogUtils.getLogger(); private static final DynamicCommandExceptionType MESSAGE_TOO_LONG_EXCEPTION = new DynamicCommandExceptionType(d -> Component.translatable("c2cpacket.messageTooLong", d)); private static final SimpleCommandExceptionType PUBLIC_KEY_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("c2cpacket.publicKeyNotFound")); private static final SimpleCommandExceptionType ENCRYPTION_FAILED_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("c2cpacket.encryptionFailed")); - public static final ProtocolInfo C2C = ProtocolInfoBuilder.protocolUnbound(ConnectionProtocol.PLAY, PacketFlow.CLIENTBOUND, builder -> builder + public static final ProtocolInfo.Unbound PROTOCOL_UNBOUND = ProtocolInfoBuilder.protocolUnbound(ConnectionProtocol.PLAY, PacketFlow.CLIENTBOUND, builder -> builder .addPacket(MessageC2CPacket.ID, MessageC2CPacket.CODEC) - ).bind(RegistryFriendlyByteBuf.decorator(Minecraft.getInstance().getConnection().registryAccess())); + ); private static final C2CPacketHandler instance = new C2CPacketHandler(); @@ -53,7 +64,11 @@ public void sendPacket(Packet packet, PlayerInfo recipient) t } PublicKey key = ppk.data().key(); FriendlyByteBuf buf = PacketByteBufs.create(); - C2C.codec().encode(buf, packet); + ProtocolInfo protocolInfo = getCurrentProtocolInfo(); + if (protocolInfo == null) { + return; + } + protocolInfo.codec().encode(buf, packet); byte[] uncompressed = new byte[buf.readableBytes()]; buf.getBytes(0, uncompressed); byte[] compressed = ConversionHelper.Gzip.compress(uncompressed); @@ -102,14 +117,85 @@ public void onMessageC2CPacket(MessageC2CPacket packet) { Minecraft.getInstance().gui.getChat().addMessage(component); } + public static boolean handleC2CPacket(String content) { + byte[] encrypted = ConversionHelper.BaseUTF8.fromUnicode(content); + // round down to multiple of 256 bytes + int length = encrypted.length & ~0xFF; + // copy to new array of arrays + byte[][] encryptedArrays = new byte[length / 256][]; + for (int i = 0; i < length; i += 256) { + encryptedArrays[i / 256] = Arrays.copyOfRange(encrypted, i, i + 256); + } + if (!(Minecraft.getInstance().getProfileKeyPairManager() instanceof AccountProfileKeyPairManager profileKeyPairManager)) { + return false; + } + Optional keyPair = profileKeyPairManager.keyPair.join(); + if (keyPair.isEmpty()) { + return false; + } + // decrypt + int len = 0; + byte[][] decryptedArrays = new byte[encryptedArrays.length][]; + for (int i = 0; i < encryptedArrays.length; i++) { + decryptedArrays[i] = ConversionHelper.RsaEcb.decrypt(encryptedArrays[i], keyPair.get().privateKey()); + if (decryptedArrays[i] == null) { + return false; + } + len += decryptedArrays[i].length; + } + // copy to new array + byte[] decrypted = new byte[len]; + int pos = 0; + for (byte[] decryptedArray : decryptedArrays) { + System.arraycopy(decryptedArray, 0, decrypted, pos, decryptedArray.length); + pos += decryptedArray.length; + } + byte[] uncompressed = ConversionHelper.Gzip.decompress(decrypted); + if (uncompressed == null) { + return false; + } + ProtocolInfo protocolInfo = getCurrentProtocolInfo(); + if (protocolInfo == null) { + return false; + } + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(uncompressed)); + Packet packet; + try { + packet = protocolInfo.codec().decode(buf); + } catch (Throwable e) { + LOGGER.error("Error decoding C2C packet", e); + return false; + } + if (buf.readableBytes() > 0) { + return false; + } + ListenCommand.onPacket(packet, ListenCommand.PacketFlow.C2C_INBOUND); + try { + packet.handle(C2CPacketHandler.getInstance()); + } catch (Throwable e) { + Minecraft.getInstance().gui.getChat().addMessage(Component.nullToEmpty(e.getMessage())); + LogUtils.getLogger().error("Error handling C2C packet", e); + } + return true; + } + + @Nullable + public static ProtocolInfo getCurrentProtocolInfo() { + ClientPacketListener connection = Minecraft.getInstance().getConnection(); + if (connection == null) { + return null; + } + return ((IClientPacketListener_C2C) connection).clientcommands_getC2CProtocolInfo(); + } + @Override public PacketFlow flow() { - return C2C.flow(); + return PacketFlow.CLIENTBOUND; } @Override public ConnectionProtocol protocol() { - return C2C.id(); + return ConnectionProtocol.PLAY; } @Override diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/ConversionHelper.java b/src/main/java/net/earthcomputer/clientcommands/c2c/ConversionHelper.java index e6ecc6a69..e02dd042f 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/ConversionHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/ConversionHelper.java @@ -1,5 +1,9 @@ package net.earthcomputer.clientcommands.c2c; +import com.mojang.logging.LogUtils; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; + import javax.crypto.Cipher; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -12,6 +16,7 @@ import java.util.zip.GZIPOutputStream; public class ConversionHelper { + private static final Logger LOGGER = LogUtils.getLogger(); /** * @author Wagyourtail @@ -91,7 +96,7 @@ private static int unmapCodepoint(int codepoint) { * @author Wagyourtail */ public static class Gzip { - public static byte[] compress(byte[] bytes) { + public static byte @Nullable [] compress(byte[] bytes) { if (bytes == null || bytes.length == 0) { return null; } @@ -99,12 +104,13 @@ public static byte[] compress(byte[] bytes) { try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { gzip.write(bytes); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Error compressing", e); + return null; } return out.toByteArray(); } - public static byte[] uncompress(byte[] bytes) { + public static byte @Nullable [] decompress(byte[] bytes) { if (bytes == null || bytes.length == 0) { return null; } @@ -117,7 +123,8 @@ public static byte[] uncompress(byte[] bytes) { out.write(buffer, 0, n); } } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Error decompressing", e); + return null; } return out.toByteArray(); } @@ -130,7 +137,7 @@ public static byte[] encrypt(byte[] bytes, PublicKey key) { cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(bytes); } catch (GeneralSecurityException e) { - e.printStackTrace(); + LOGGER.error("Error encrypting", e); return null; } } @@ -141,7 +148,7 @@ public static byte[] decrypt(byte[] bytes, PrivateKey key) { cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(bytes); } catch (GeneralSecurityException e) { - e.printStackTrace(); + LOGGER.error("Error decrypting", e); return null; } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PacketTypeArgument.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PacketTypeArgument.java index 35ebfa4d5..2929d4716 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PacketTypeArgument.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PacketTypeArgument.java @@ -10,6 +10,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import io.netty.channel.ChannelPipeline; import net.earthcomputer.clientcommands.c2c.C2CPacketHandler; +import net.earthcomputer.clientcommands.c2c.C2CPacketListener; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientPacketListener; @@ -86,12 +87,17 @@ private static PacketTypes get() { if (connection == null) { return null; } + ProtocolInfo protocolInfo = C2CPacketHandler.getCurrentProtocolInfo(); + if (protocolInfo == null) { + return null; + } + ChannelPipeline pipeline = connection.getConnection().channel.pipeline(); var decoder = (PacketDecoder) pipeline.get("decoder"); var clientbound = packetTypesCache.computeIfAbsent(decoder, k -> getPacketTypes(decoder.protocolInfo)); var encoder = (PacketEncoder) pipeline.get("encoder"); var serverbound = packetTypesCache.computeIfAbsent(encoder, k -> getPacketTypes(encoder.protocolInfo)); - var c2cbound = packetTypesCache.computeIfAbsent("c2c", k -> getPacketTypes(C2CPacketHandler.C2C)); + var c2cbound = packetTypesCache.computeIfAbsent("c2c", k -> getPacketTypes(protocolInfo)); return new PacketTypes(clientbound, serverbound, c2cbound); } } diff --git a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java index 16dc61b1c..1ee2e8f9e 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java @@ -28,6 +28,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.PacketDecoder; import net.minecraft.network.PacketEncoder; +import net.minecraft.network.ProtocolInfo; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamEncoder; import net.minecraft.network.protocol.Packet; @@ -76,8 +77,12 @@ public static void dumpPacket(Packet packet, JsonWriter writer) throws IOExce writer.beginArray(); try { if (packet.type().id().getNamespace().equals("clientcommands")) { + ProtocolInfo protocolInfo = C2CPacketHandler.getCurrentProtocolInfo(); + if (protocolInfo == null) { + throw new IOException("Not currently logged in"); + } //noinspection unchecked - C2CPacketHandler.C2C.codec().encode(new PacketDumpByteBuf(writer), (Packet) packet); + protocolInfo.codec().encode(new PacketDumpByteBuf(writer), (Packet) packet); } else { ChannelPipeline pipeline = Minecraft.getInstance().getConnection().getConnection().channel.pipeline(); @SuppressWarnings("unchecked") diff --git a/src/main/java/net/earthcomputer/clientcommands/interfaces/IClientPacketListener_C2C.java b/src/main/java/net/earthcomputer/clientcommands/interfaces/IClientPacketListener_C2C.java new file mode 100644 index 000000000..0cf0376b4 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/interfaces/IClientPacketListener_C2C.java @@ -0,0 +1,8 @@ +package net.earthcomputer.clientcommands.interfaces; + +import net.earthcomputer.clientcommands.c2c.C2CPacketListener; +import net.minecraft.network.ProtocolInfo; + +public interface IClientPacketListener_C2C { + ProtocolInfo clientcommands_getC2CProtocolInfo(); +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ChatComponentMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ChatComponentMixin.java index c89d85059..723c3b4f9 100644 --- a/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ChatComponentMixin.java +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ChatComponentMixin.java @@ -1,23 +1,15 @@ package net.earthcomputer.clientcommands.mixin.c2c; -import io.netty.buffer.Unpooled; import net.earthcomputer.clientcommands.Configs; import net.earthcomputer.clientcommands.c2c.C2CPacketHandler; -import net.earthcomputer.clientcommands.c2c.C2CPacketListener; -import net.earthcomputer.clientcommands.c2c.ConversionHelper; import net.earthcomputer.clientcommands.c2c.OutgoingPacketFilter; -import net.earthcomputer.clientcommands.command.ListenCommand; import net.minecraft.ChatFormatting; import net.minecraft.client.GuiMessageTag; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.ChatComponent; -import net.minecraft.client.multiplayer.AccountProfileKeyPairManager; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.MessageSignature; -import net.minecraft.network.protocol.Packet; -import net.minecraft.world.entity.player.ProfileKeyPair; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -26,9 +18,6 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Arrays; -import java.util.Optional; - @Mixin(ChatComponent.class) public class ChatComponentMixin { @@ -60,60 +49,10 @@ private void handleIfPacket(Component content, CallbackInfo ci) { ci.cancel(); return; } - if (handleC2CPacket(packetString)) { + if (C2CPacketHandler.handleC2CPacket(packetString)) { ci.cancel(); } else { this.minecraft.gui.getChat().addMessage(Component.translatable("c2cpacket.malformedPacket").withStyle(ChatFormatting.RED)); } } - - @Unique - private static boolean handleC2CPacket(String content) { - byte[] encrypted = ConversionHelper.BaseUTF8.fromUnicode(content); - // round down to multiple of 256 bytes - int length = encrypted.length & ~0xFF; - // copy to new array of arrays - byte[][] encryptedArrays = new byte[length / 256][]; - for (int i = 0; i < length; i += 256) { - encryptedArrays[i / 256] = Arrays.copyOfRange(encrypted, i, i + 256); - } - if (!(Minecraft.getInstance().getProfileKeyPairManager() instanceof AccountProfileKeyPairManager profileKeyPairManager)) { - return false; - } - Optional keyPair = profileKeyPairManager.keyPair.join(); - if (keyPair.isEmpty()) { - return false; - } - // decrypt - int len = 0; - byte[][] decryptedArrays = new byte[encryptedArrays.length][]; - for (int i = 0; i < encryptedArrays.length; i++) { - decryptedArrays[i] = ConversionHelper.RsaEcb.decrypt(encryptedArrays[i], keyPair.get().privateKey()); - if (decryptedArrays[i] == null) { - return false; - } - len += decryptedArrays[i].length; - } - // copy to new array - byte[] decrypted = new byte[len]; - int pos = 0; - for (byte[] decryptedArray : decryptedArrays) { - System.arraycopy(decryptedArray, 0, decrypted, pos, decryptedArray.length); - pos += decryptedArray.length; - } - byte[] uncompressed = ConversionHelper.Gzip.uncompress(decrypted); - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(uncompressed)); - Packet packet = C2CPacketHandler.C2C.codec().decode(buf); - if (buf.readableBytes() > 0) { - return false; - } - ListenCommand.onPacket(packet, ListenCommand.PacketFlow.C2C_INBOUND); - try { - packet.handle(C2CPacketHandler.getInstance()); - } catch (Exception e) { - Minecraft.getInstance().gui.getChat().addMessage(Component.nullToEmpty(e.getMessage())); - e.printStackTrace(); - } - return true; - } } diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ClientPacketListenerMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ClientPacketListenerMixin.java new file mode 100644 index 000000000..abc03ea95 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/c2c/ClientPacketListenerMixin.java @@ -0,0 +1,28 @@ +package net.earthcomputer.clientcommands.mixin.c2c; + +import net.earthcomputer.clientcommands.c2c.C2CPacketHandler; +import net.earthcomputer.clientcommands.c2c.C2CPacketListener; +import net.earthcomputer.clientcommands.interfaces.IClientPacketListener_C2C; +import net.minecraft.client.multiplayer.ClientPacketListener; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.ProtocolInfo; +import net.minecraft.network.RegistryFriendlyByteBuf; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(ClientPacketListener.class) +public class ClientPacketListenerMixin implements IClientPacketListener_C2C { + @Shadow + @Final + private RegistryAccess.Frozen registryAccess; + + @Unique + private final ProtocolInfo c2cProtocolInfo = C2CPacketHandler.PROTOCOL_UNBOUND.bind(RegistryFriendlyByteBuf.decorator(registryAccess)); + + @Override + public ProtocolInfo clientcommands_getC2CProtocolInfo() { + return c2cProtocolInfo; + } +} diff --git a/src/main/resources/mixins.clientcommands.json b/src/main/resources/mixins.clientcommands.json index cf16db199..884c57b4e 100644 --- a/src/main/resources/mixins.clientcommands.json +++ b/src/main/resources/mixins.clientcommands.json @@ -63,6 +63,7 @@ "requireAnnotations": true }, "client": [ + "c2c.ClientPacketListenerMixin", "commands.alias.ClientSuggestionProviderMixin", "commands.enchant.MultiPlayerGameModeMixin", "commands.generic.CommandSuggestionsMixin", From 2333dd0ce6a04e0cad0e43c7c2a25f1aa107efe0 Mon Sep 17 00:00:00 2001 From: joe Date: Wed, 29 May 2024 17:10:22 +0100 Subject: [PATCH 2/4] Use logger field --- .../net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java index 1c6be3eff..bb8dbc444 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java @@ -167,6 +167,7 @@ public static boolean handleC2CPacket(String content) { return false; } if (buf.readableBytes() > 0) { + LOGGER.error("Found extra bytes while reading C2C packet {}", packet.type()); return false; } ListenCommand.onPacket(packet, ListenCommand.PacketFlow.C2C_INBOUND); @@ -174,7 +175,7 @@ public static boolean handleC2CPacket(String content) { packet.handle(C2CPacketHandler.getInstance()); } catch (Throwable e) { Minecraft.getInstance().gui.getChat().addMessage(Component.nullToEmpty(e.getMessage())); - LogUtils.getLogger().error("Error handling C2C packet", e); + LOGGER.error("Error handling C2C packet", e); } return true; } From 3167e993daa7e969733303490ca3c27acdf8a07c Mon Sep 17 00:00:00 2001 From: Frederik van der Els <49305700+xpple@users.noreply.github.com> Date: Wed, 29 May 2024 19:12:10 +0200 Subject: [PATCH 3/4] Add multiplayer tic-tac-toe (#632) * Add multiplayer tic tac toe * Make enum private * Hyphenate "tic tac toe" * Remove magic numbers * Use CEntityArgument instead of CGameProfileArgument * Use single profile argument type * Use InputConstants instead of GLFW * Change casing for constants * Minor refactor --- gradle.properties | 2 +- .../clientcommands/ClientCommands.java | 1 + .../clientcommands/c2c/C2CPacketHandler.java | 53 +++- .../clientcommands/c2c/C2CPacketListener.java | 6 + .../packets/PutTicTacToeMarkC2CPacket.java | 35 +++ .../packets/StartTicTacToeGameC2CPacket.java | 34 +++ .../command/CGameModeCommand.java | 16 +- .../command/ClientCommandHelper.java | 23 +- .../clientcommands/command/PingCommand.java | 27 +- .../command/TicTacToeCommand.java | 280 ++++++++++++++++++ .../command/WhisperEncryptedCommand.java | 16 +- .../mixin/events/ScreenMixin.java | 34 +++ .../assets/clientcommands/lang/de_de.json | 1 - .../assets/clientcommands/lang/en_us.json | 20 +- .../assets/clientcommands/lang/id_id.json | 1 - .../assets/clientcommands/lang/ja_jp.json | 1 - .../assets/clientcommands/lang/zh_cn.json | 1 - .../textures/tic_tac_toe/grid.png | Bin 0 -> 2557 bytes .../textures/tic_tac_toe/marks.png | Bin 0 -> 5392 bytes src/main/resources/mixins.clientcommands.json | 1 + 20 files changed, 484 insertions(+), 68 deletions(-) create mode 100644 src/main/java/net/earthcomputer/clientcommands/c2c/packets/PutTicTacToeMarkC2CPacket.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/c2c/packets/StartTicTacToeGameC2CPacket.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/TicTacToeCommand.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/mixin/events/ScreenMixin.java create mode 100644 src/main/resources/assets/clientcommands/textures/tic_tac_toe/grid.png create mode 100644 src/main/resources/assets/clientcommands/textures/tic_tac_toe/marks.png diff --git a/gradle.properties b/gradle.properties index 26d640ad6..0dd79bd5b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ org.gradle.jvmargs=-Xmx2G # Dependencies # also check this on https://fabricmc.net/develop/ fabric_version=0.97.8+1.20.6 - clientarguments_version=1.8.2 + clientarguments_version=1.8.3 betterconfig_version=1.2.1 seedfinding_core_version=1.192.1 seedfinding_biome_version=1.171.1 diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index 81f1c5cc4..2e39e8c4a 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -161,6 +161,7 @@ public static void registerCommands(CommandDispatcher SnakeCommand.register(dispatcher); StartupCommand.register(dispatcher); TaskCommand.register(dispatcher); + TicTacToeCommand.register(dispatcher); TooltipCommand.register(dispatcher, context); TranslateCommand.register(dispatcher); UsageTreeCommand.register(dispatcher); diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java index bb8dbc444..701240f2c 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketHandler.java @@ -6,10 +6,14 @@ import com.mojang.logging.LogUtils; import io.netty.buffer.Unpooled; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; +import net.earthcomputer.clientcommands.c2c.packets.PutTicTacToeMarkC2CPacket; +import net.earthcomputer.clientcommands.c2c.packets.StartTicTacToeGameC2CPacket; import net.earthcomputer.clientcommands.command.ListenCommand; import net.earthcomputer.clientcommands.interfaces.IClientPacketListener_C2C; +import net.earthcomputer.clientcommands.command.TicTacToeCommand; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.AccountProfileKeyPairManager; import net.minecraft.client.multiplayer.ClientPacketListener; @@ -26,6 +30,7 @@ import net.minecraft.network.protocol.ProtocolInfoBuilder; import net.minecraft.world.entity.player.ProfileKeyPair; import net.minecraft.world.entity.player.ProfilePublicKey; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -42,6 +47,8 @@ public class C2CPacketHandler implements C2CPacketListener { public static final ProtocolInfo.Unbound PROTOCOL_UNBOUND = ProtocolInfoBuilder.protocolUnbound(ConnectionProtocol.PLAY, PacketFlow.CLIENTBOUND, builder -> builder .addPacket(MessageC2CPacket.ID, MessageC2CPacket.CODEC) + .addPacket(StartTicTacToeGameC2CPacket.ID, StartTicTacToeGameC2CPacket.CODEC) + .addPacket(PutTicTacToeMarkC2CPacket.ID, PutTicTacToeMarkC2CPacket.CODEC) ); private static final C2CPacketHandler instance = new C2CPacketHandler(); @@ -59,6 +66,7 @@ public void sendPacket(Packet packet, PlayerInfo recipient) t throw PUBLIC_KEY_NOT_FOUND_EXCEPTION.create(); } ProfilePublicKey ppk = session.profilePublicKey(); + //noinspection ConstantValue if (ppk == null) { throw PUBLIC_KEY_NOT_FOUND_EXCEPTION.create(); } @@ -72,6 +80,9 @@ public void sendPacket(Packet packet, PlayerInfo recipient) t byte[] uncompressed = new byte[buf.readableBytes()]; buf.getBytes(0, uncompressed); byte[] compressed = ConversionHelper.Gzip.compress(uncompressed); + if (compressed == null) { + return; + } // split compressed into 245 byte chunks int chunks = (compressed.length + 244) / 245; byte[][] chunked = new byte[chunks][]; @@ -96,7 +107,7 @@ public void sendPacket(Packet packet, PlayerInfo recipient) t } String packetString = ConversionHelper.BaseUTF8.toUnicode(joined); String commandString = "w " + recipient.getProfile().getName() + " CCENC:" + packetString; - if (commandString.length() >= 256) { + if (commandString.length() >= SharedConstants.MAX_CHAT_LENGTH) { throw MESSAGE_TOO_LONG_EXCEPTION.create(commandString.length()); } ListenCommand.onPacket(packet, ListenCommand.PacketFlow.C2C_OUTBOUND); @@ -104,19 +115,6 @@ public void sendPacket(Packet packet, PlayerInfo recipient) t OutgoingPacketFilter.addPacket(packetString); } - @Override - public void onMessageC2CPacket(MessageC2CPacket packet) { - String sender = packet.sender(); - String message = packet.message(); - MutableComponent prefix = Component.empty(); - prefix.append(Component.literal("[").withStyle(ChatFormatting.DARK_GRAY)); - prefix.append(Component.literal("/cwe").withStyle(ChatFormatting.AQUA)); - prefix.append(Component.literal("]").withStyle(ChatFormatting.DARK_GRAY)); - prefix.append(Component.literal(" ")); - Component component = prefix.append(Component.translatable("c2cpacket.messageC2CPacket.incoming", sender, message).withStyle(ChatFormatting.GRAY)); - Minecraft.getInstance().gui.getChat().addMessage(component); - } - public static boolean handleC2CPacket(String content) { byte[] encrypted = ConversionHelper.BaseUTF8.fromUnicode(content); // round down to multiple of 256 bytes @@ -180,6 +178,29 @@ public static boolean handleC2CPacket(String content) { return true; } + @Override + public void onMessageC2CPacket(MessageC2CPacket packet) { + String sender = packet.sender(); + String message = packet.message(); + MutableComponent prefix = Component.empty(); + prefix.append(Component.literal("[").withStyle(ChatFormatting.DARK_GRAY)); + prefix.append(Component.literal("/cwe").withStyle(ChatFormatting.AQUA)); + prefix.append(Component.literal("]").withStyle(ChatFormatting.DARK_GRAY)); + prefix.append(Component.literal(" ")); + Component component = prefix.append(Component.translatable("c2cpacket.messageC2CPacket.incoming", sender, message).withStyle(ChatFormatting.GRAY)); + Minecraft.getInstance().gui.getChat().addMessage(component); + } + + @Override + public void onStartTicTacToeGameC2CPacket(StartTicTacToeGameC2CPacket packet) { + TicTacToeCommand.onStartTicTacToeGameC2CPacket(packet); + } + + @Override + public void onPutTicTacToeMarkC2CPacket(PutTicTacToeMarkC2CPacket packet) { + TicTacToeCommand.onPutTicTacToeMarkC2CPacket(packet); + } + @Nullable public static ProtocolInfo getCurrentProtocolInfo() { ClientPacketListener connection = Minecraft.getInstance().getConnection(); @@ -190,12 +211,12 @@ public static ProtocolInfo getCurrentProtocolInfo() { } @Override - public PacketFlow flow() { + public @NotNull PacketFlow flow() { return PacketFlow.CLIENTBOUND; } @Override - public ConnectionProtocol protocol() { + public @NotNull ConnectionProtocol protocol() { return ConnectionProtocol.PLAY; } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketListener.java b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketListener.java index 72948f914..e4a52b1a8 100644 --- a/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketListener.java +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/C2CPacketListener.java @@ -1,8 +1,14 @@ package net.earthcomputer.clientcommands.c2c; import net.earthcomputer.clientcommands.c2c.packets.MessageC2CPacket; +import net.earthcomputer.clientcommands.c2c.packets.PutTicTacToeMarkC2CPacket; +import net.earthcomputer.clientcommands.c2c.packets.StartTicTacToeGameC2CPacket; import net.minecraft.network.PacketListener; public interface C2CPacketListener extends PacketListener { void onMessageC2CPacket(MessageC2CPacket packet); + + void onStartTicTacToeGameC2CPacket(StartTicTacToeGameC2CPacket packet); + + void onPutTicTacToeMarkC2CPacket(PutTicTacToeMarkC2CPacket packet); } diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/PutTicTacToeMarkC2CPacket.java b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/PutTicTacToeMarkC2CPacket.java new file mode 100644 index 000000000..1bac5f3a8 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/PutTicTacToeMarkC2CPacket.java @@ -0,0 +1,35 @@ +package net.earthcomputer.clientcommands.c2c.packets; + +import net.earthcomputer.clientcommands.c2c.C2CPacketListener; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.PacketType; +import net.minecraft.resources.ResourceLocation; + +public record PutTicTacToeMarkC2CPacket(String sender, byte x, byte y) implements Packet { + public static final StreamCodec CODEC = Packet.codec(PutTicTacToeMarkC2CPacket::write, PutTicTacToeMarkC2CPacket::new); + public static final PacketType ID = new PacketType<>(PacketFlow.CLIENTBOUND, new ResourceLocation("clientcommands", "put_tic_tac_toe_mark")); + + public PutTicTacToeMarkC2CPacket(FriendlyByteBuf buf) { + this(buf.readUtf(), buf.readByte(), buf.readByte()); + } + + public void write(FriendlyByteBuf buf) { + buf.writeUtf(this.sender); + buf.writeByte(this.x); + buf.writeByte(this.y); + } + + @Override + public void handle(C2CPacketListener handler) { + handler.onPutTicTacToeMarkC2CPacket(this); + } + + @Override + public PacketType> type() { + return ID; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/c2c/packets/StartTicTacToeGameC2CPacket.java b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/StartTicTacToeGameC2CPacket.java new file mode 100644 index 000000000..5d375ae5f --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/c2c/packets/StartTicTacToeGameC2CPacket.java @@ -0,0 +1,34 @@ +package net.earthcomputer.clientcommands.c2c.packets; + +import net.earthcomputer.clientcommands.c2c.C2CPacketListener; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.PacketType; +import net.minecraft.resources.ResourceLocation; + +public record StartTicTacToeGameC2CPacket(String sender, boolean accept) implements Packet { + public static final StreamCodec CODEC = Packet.codec(StartTicTacToeGameC2CPacket::write, StartTicTacToeGameC2CPacket::new); + public static final PacketType ID = new PacketType<>(PacketFlow.CLIENTBOUND, new ResourceLocation("clientcommands", "start_tic_tac_toe_game")); + + public StartTicTacToeGameC2CPacket(FriendlyByteBuf buf) { + this(buf.readUtf(), buf.readBoolean()); + } + + public void write(FriendlyByteBuf buf) { + buf.writeUtf(this.sender); + buf.writeBoolean(this.accept); + } + + @Override + public void handle(C2CPacketListener handler) { + handler.onStartTicTacToeGameC2CPacket(this); + } + + @Override + public PacketType> type() { + return ID; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/CGameModeCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/CGameModeCommand.java index a8be8bd6d..4eddd8331 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/CGameModeCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/CGameModeCommand.java @@ -10,7 +10,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.level.GameType; -import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; @@ -25,23 +24,20 @@ public class CGameModeCommand { public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("cgamemode") .then(literal("query") - .then(argument("player", gameProfile()) - .executes(ctx -> getPlayerGameMode(ctx.getSource(), getProfileArgument(ctx, "player"))))) + .then(argument("player", gameProfile(true)) + .executes(ctx -> getPlayerGameMode(ctx.getSource(), getSingleProfileArgument(ctx, "player"))))) .then(literal("list") .then(argument("gameMode", enumArg(GameType.class)) .executes(ctx -> listWithGameMode(ctx.getSource(), getEnum(ctx, "gameMode")))))); } - private static int getPlayerGameMode(FabricClientCommandSource source, Collection profiles) throws CommandSyntaxException { - if (profiles.size() != 1) { + private static int getPlayerGameMode(FabricClientCommandSource source, GameProfile player) throws CommandSyntaxException { + PlayerInfo playerInfo = source.getClient().getConnection().getPlayerInfo(player.getId()); + if (playerInfo == null) { throw PLAYER_NOT_FOUND_EXCEPTION.create(); } - PlayerInfo player = source.getClient().getConnection().getOnlinePlayers().stream() - .filter(p -> p.getProfile().getName().equalsIgnoreCase(profiles.iterator().next().getName())) - .findAny() - .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); - source.sendFeedback(Component.translatable("commands.cgamemode.playerGameMode", player.getProfile().getName(), player.getGameMode().getShortDisplayName())); + source.sendFeedback(Component.translatable("commands.cgamemode.playerGameMode", playerInfo.getProfile().getName(), playerInfo.getGameMode().getShortDisplayName())); return Command.SINGLE_SUCCESS; } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java b/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java index de310dd3a..f252b0a1f 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ClientCommandHelper.java @@ -13,6 +13,10 @@ import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.MutableComponent; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + public class ClientCommandHelper { public static T getFlag(CommandContext ctx, Flag flag) { @@ -55,7 +59,7 @@ public static void addOverlayMessage(Component message, int time) { public static Component getLookCoordsTextComponent(BlockPos pos) { return getCommandTextComponent(Component.translatable("commands.client.blockpos", pos.getX(), pos.getY(), pos.getZ()), - String.format("/clook block %d %d %d", pos.getX(), pos.getY(), pos.getZ())); + String.format("/clook block %d %d %d", pos.getX(), pos.getY(), pos.getZ())); } public static Component getLookCoordsTextComponent(MutableComponent component, BlockPos pos) { @@ -64,7 +68,7 @@ public static Component getLookCoordsTextComponent(MutableComponent component, B public static Component getGlowCoordsTextComponent(BlockPos pos) { return getCommandTextComponent(Component.translatable("commands.client.blockpos", pos.getX(), pos.getY(), pos.getZ()), - String.format("/cglow block %d %d %d 10", pos.getX(), pos.getY(), pos.getZ())); + String.format("/cglow block %d %d %d 10", pos.getX(), pos.getY(), pos.getZ())); } public static Component getGlowCoordsTextComponent(MutableComponent component, BlockPos pos) { @@ -77,8 +81,19 @@ public static Component getCommandTextComponent(@Translatable String translation public static Component getCommandTextComponent(MutableComponent component, String command) { return component.withStyle(style -> style.applyFormat(ChatFormatting.UNDERLINE) - .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)) - .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(command)))); + .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(command)))); } + public static final Map runnables = new HashMap<>(); + + public static String registerCode(Runnable code) { + String randomString = new Random().ints('0', 'z' + 1) + .filter(i -> (i <= '9' || i >= 'A') && (i <= 'Z' || i >= 'a')) + .limit(10) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + runnables.put(randomString, code); + return randomString; + } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/PingCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/PingCommand.java index 424103b2a..c0b669263 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/PingCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/PingCommand.java @@ -11,8 +11,6 @@ import net.minecraft.client.multiplayer.PlayerInfo; import net.minecraft.network.chat.Component; -import java.util.Collection; - import static dev.xpple.clientarguments.arguments.CGameProfileArgument.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; @@ -20,13 +18,12 @@ public class PingCommand { private static final SimpleCommandExceptionType PLAYER_IN_SINGLEPLAYER_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.cping.singleplayer")); private static final SimpleCommandExceptionType PLAYER_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.cping.playerNotFound")); - private static final SimpleCommandExceptionType TOO_MANY_PLAYERS_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.cping.tooManyPlayers")); public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("cping") .executes(ctx -> printPing(ctx.getSource())) - .then(argument("player", gameProfile()) - .executes(ctx -> printPing(ctx.getSource(), getProfileArgument(ctx, "player")))) + .then(argument("player", gameProfile(true)) + .executes(ctx -> printPing(ctx.getSource(), getSingleProfileArgument(ctx, "player")))) ); } @@ -44,28 +41,18 @@ private static int printPing(FabricClientCommandSource source) throws CommandSyn return Command.SINGLE_SUCCESS; } - private static int printPing(FabricClientCommandSource source, Collection profiles) throws CommandSyntaxException { + private static int printPing(FabricClientCommandSource source, GameProfile player) throws CommandSyntaxException { if (source.getClient().isLocalServer()) { throw PLAYER_IN_SINGLEPLAYER_EXCEPTION.create(); } - if (profiles.isEmpty()) { - throw PLAYER_NOT_FOUND_EXCEPTION.create(); - } - if (profiles.size() > 1) { - throw TOO_MANY_PLAYERS_EXCEPTION.create(); - } - GameProfile profile = profiles.iterator().next(); - - ClientPacketListener packetListener = source.getClient().getConnection(); - assert packetListener != null; - PlayerInfo player = packetListener.getPlayerInfo(profile.getId()); - if (player == null) { + PlayerInfo playerInfo = source.getClient().getConnection().getPlayerInfo(player.getId()); + if (playerInfo == null) { throw PLAYER_NOT_FOUND_EXCEPTION.create(); } - int ping = player.getLatency(); - source.sendFeedback(Component.translatable("commands.cping.success.other", profile.getName(), ping)); + int ping = playerInfo.getLatency(); + source.sendFeedback(Component.translatable("commands.cping.success.other", playerInfo.getProfile().getName(), ping)); return Command.SINGLE_SUCCESS; } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/TicTacToeCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/TicTacToeCommand.java new file mode 100644 index 000000000..3526c1f7d --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/TicTacToeCommand.java @@ -0,0 +1,280 @@ +package net.earthcomputer.clientcommands.command; + +import com.google.common.cache.CacheBuilder; +import com.mojang.authlib.GameProfile; +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.earthcomputer.clientcommands.c2c.C2CPacketHandler; +import net.earthcomputer.clientcommands.c2c.packets.PutTicTacToeMarkC2CPacket; +import net.earthcomputer.clientcommands.c2c.packets.StartTicTacToeGameC2CPacket; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import static com.mojang.brigadier.arguments.StringArgumentType.*; +import static dev.xpple.clientarguments.arguments.CGameProfileArgument.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class TicTacToeCommand { + + private static final SimpleCommandExceptionType PLAYER_NOT_FOUND_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.ctictactoe.playerNotFound")); + private static final SimpleCommandExceptionType NO_GAME_WITH_PLAYER_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.ctictactoe.noGameWithPlayer")); + + private static final Map activeGames = CacheBuilder.newBuilder().expireAfterWrite(Duration.ofMinutes(15)).build().asMap(); + private static final Set pendingInvites = Collections.newSetFromMap(CacheBuilder.newBuilder().expireAfterWrite(Duration.ofMinutes(5)).build().asMap()); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("ctictactoe") + .then(literal("start") + .then(argument("opponent", gameProfile(true)) + .executes(ctx -> start(ctx.getSource(), getSingleProfileArgument(ctx, "opponent"))))) + .then(literal("open") + .then(argument("opponent", word()) + .suggests((ctx, builder) -> SharedSuggestionProvider.suggest(activeGames.keySet(), builder)) + .executes(ctx -> open(ctx.getSource(), getString(ctx, "opponent")))))); + } + + private static int start(FabricClientCommandSource source, GameProfile player) throws CommandSyntaxException { + PlayerInfo recipient = source.getClient().getConnection().getPlayerInfo(player.getId()); + if (recipient == null) { + throw PLAYER_NOT_FOUND_EXCEPTION.create(); + } + + StartTicTacToeGameC2CPacket packet = new StartTicTacToeGameC2CPacket(source.getClient().getConnection().getLocalGameProfile().getName(), false); + C2CPacketHandler.getInstance().sendPacket(packet, recipient); + pendingInvites.add(recipient.getProfile().getName()); + source.sendFeedback(Component.translatable("c2cpacket.startTicTacToeGameC2CPacket.outgoing.invited", recipient.getProfile().getName())); + return Command.SINGLE_SUCCESS; + } + + public static void onStartTicTacToeGameC2CPacket(StartTicTacToeGameC2CPacket packet) { + String sender = packet.sender(); + PlayerInfo opponent = Minecraft.getInstance().getConnection().getPlayerInfo(sender); + if (opponent == null) { + return; + } + + if (packet.accept() && pendingInvites.remove(sender)) { + TicTacToeGame game = new TicTacToeGame(opponent, TicTacToeGame.Mark.CROSS); + activeGames.put(sender, game); + + MutableComponent component = Component.translatable("c2cpacket.startTicTacToeGameC2CPacket.incoming.accepted", sender); + component.withStyle(style -> style + .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ctictactoe open " + sender)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("/ctictactoe open " + sender)))); + Minecraft.getInstance().gui.getChat().addMessage(component); + return; + } + + MutableComponent component = Component.translatable("c2cpacket.startTicTacToeGameC2CPacket.incoming", sender); + component + .append(" [") + .append(Component.translatable("c2cpacket.startTicTacToeGameC2CPacket.incoming.accept").withStyle(style -> style + .withColor(ChatFormatting.GREEN) + .withClickEvent(new ClickEvent(ClickEvent.Action.CHANGE_PAGE, ClientCommandHelper.registerCode(() -> { + TicTacToeGame game = new TicTacToeGame(opponent, TicTacToeGame.Mark.NOUGHT); + activeGames.put(sender, game); + + StartTicTacToeGameC2CPacket acceptPacket = new StartTicTacToeGameC2CPacket(Minecraft.getInstance().getConnection().getLocalGameProfile().getName(), true); + try { + C2CPacketHandler.getInstance().sendPacket(acceptPacket, opponent); + } catch (CommandSyntaxException e) { + Minecraft.getInstance().gui.getChat().addMessage(Component.translationArg(e.getRawMessage())); + } + + Minecraft.getInstance().gui.getChat().addMessage(Component.translatable("c2cpacket.startTicTacToeGameC2CPacket.outgoing.accept")); + }))) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable("c2cpacket.startTicTacToeGameC2CPacket.incoming.accept.hover"))))) + .append("]"); + Minecraft.getInstance().gui.getChat().addMessage(component); + } + + private static int open(FabricClientCommandSource source, String name) throws CommandSyntaxException { + TicTacToeGame game = activeGames.get(name); + if (game == null) { + throw NO_GAME_WITH_PLAYER_EXCEPTION.create(); + } + + source.getClient().tell(() -> source.getClient().setScreen(new TicTacToeGameScreen(game))); + return Command.SINGLE_SUCCESS; + } + + public static void onPutTicTacToeMarkC2CPacket(PutTicTacToeMarkC2CPacket packet) { + String sender = packet.sender(); + TicTacToeGame game = activeGames.get(sender); + if (game == null) { + return; + } + if (game.putMark(packet.x(), packet.y(), game.yourMarks.opposite())) { + if (game.getWinner() == game.yourMarks.opposite()) { + Minecraft.getInstance().gui.getChat().addMessage(Component.translatable("c2cpacket.putTicTacToeMarkC2CPacket.incoming.lost", sender)); + activeGames.remove(sender); + return; + } + MutableComponent component = Component.translatable("c2cpacket.putTicTacToeMarkC2CPacket.incoming", sender); + component.withStyle(style -> style + .withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ctictactoe open " + sender)) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("/ctictactoe open " + sender)))); + Minecraft.getInstance().gui.getChat().addMessage(component); + } + } + + private static class TicTacToeGame { + public final PlayerInfo opponent; + + private final Mark[][] board = new Mark[3][3]; + private final Mark yourMarks; + private boolean yourTurn; + + public TicTacToeGame(PlayerInfo opponent, Mark yourMarks) { + this.opponent = opponent; + this.yourMarks = yourMarks; + this.yourTurn = yourMarks == Mark.CROSS; + } + + public boolean putMark(byte x, byte y, Mark mark) { + if (this.yourMarks == mark == this.yourTurn) { + if (this.board[x][y] == null) { + this.board[x][y] = mark; + this.yourTurn = !this.yourTurn; + return true; + } + } + return false; + } + + public Mark getWinner() { + for (byte x = 0; x < 3; x++) { + if (this.board[x][0] == this.board[x][1] && this.board[x][1] == this.board[x][2]) { + return this.board[x][0]; + } + if (this.board[0][x] == this.board[1][x] && this.board[1][x] == this.board[2][x]) { + return this.board[0][x]; + } + } + if (this.board[0][0] == this.board[1][1] && this.board[1][1] == this.board[2][2]) { + return this.board[0][0]; + } + if (this.board[0][2] == this.board[1][1] && this.board[1][1] == this.board[2][0]) { + return this.board[0][2]; + } + return null; + } + + private enum Mark { + NOUGHT(Component.translatable("ticTacToeGame.noughts")), + CROSS(Component.translatable("ticTacToeGame.crosses")); + + private final Component name; + + Mark(Component name) { + this.name = name; + } + + public Mark opposite() { + return this == NOUGHT ? CROSS : NOUGHT; + } + } + } + + private static class TicTacToeGameScreen extends Screen { + private final TicTacToeGame game; + + private static final ResourceLocation GRID_TEXTURE = new ResourceLocation("clientcommands", "textures/tic_tac_toe/grid.png"); + private static final ResourceLocation MARKS_TEXTURE = new ResourceLocation("clientcommands", "textures/tic_tac_toe/marks.png"); + + private static final int GRID_SIZE_TEXTURE = 512; + private static final int MARK_SIZE_TEXTURE = 152; + + private static final int GRID_SIZE = 256; + private static final int CELL_SIZE = 80; + private static final int BORDER_SIZE = 8; + private static final int MARK_SIZE = 76; + private static final int PADDING = 2; + + private TicTacToeGameScreen(TicTacToeGame game) { + super(Component.translatable("ticTacToeGame.title", game.opponent.getProfile().getName())); + this.game = game; + } + + @Override + public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + super.renderBackground(guiGraphics, mouseX, mouseY, partialTick); + int startX = (this.width - GRID_SIZE) / 2; + int startY = (this.height - GRID_SIZE) / 2; + + guiGraphics.drawString(this.font, this.title, startX, startY - 20, 0xff_ffffff); + guiGraphics.drawString(this.font, Component.translatable("ticTacToeGame.playingWith", this.game.yourMarks.name), startX, startY - 10, 0xff_ffffff); + + guiGraphics.blit(GRID_TEXTURE, startX, startY, GRID_SIZE, GRID_SIZE, 0, 0, GRID_SIZE_TEXTURE, GRID_SIZE_TEXTURE, GRID_SIZE_TEXTURE, GRID_SIZE_TEXTURE); + TicTacToeGame.Mark[][] board = this.game.board; + + for (byte x = 0; x < 3; x++) { + for (byte y = 0; y < 3; y++) { + TicTacToeGame.Mark mark = board[x][y]; + if (mark == null) { + continue; + } + int offset = switch (mark) { + case NOUGHT -> 0; + case CROSS -> MARK_SIZE_TEXTURE; + }; + guiGraphics.blit(MARKS_TEXTURE, startX + (CELL_SIZE + BORDER_SIZE) * x + PADDING, startY + (CELL_SIZE + BORDER_SIZE) * y + PADDING, MARK_SIZE, MARK_SIZE, offset, 0, MARK_SIZE_TEXTURE, MARK_SIZE_TEXTURE, 2 * MARK_SIZE_TEXTURE, MARK_SIZE_TEXTURE); + } + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + int startX = (this.width - GRID_SIZE) / 2; + int startY = (this.height - GRID_SIZE) / 2; + if (mouseX < startX || mouseX > startX + GRID_SIZE || mouseY < startY || mouseY > startY + GRID_SIZE) { + return super.mouseClicked(mouseX, mouseY, button); + } + if (button != InputConstants.MOUSE_BUTTON_LEFT) { + return false; + } + double relativeX = mouseX - startX; + byte x = (byte) (relativeX / (CELL_SIZE + BORDER_SIZE)); + if (relativeX > (CELL_SIZE + BORDER_SIZE) * (x + 1) - BORDER_SIZE) { + return false; + } + double relativeY = mouseY - startY; + byte y = (byte) (relativeY / (CELL_SIZE + BORDER_SIZE)); + if (relativeY > (CELL_SIZE + BORDER_SIZE) * (y + 1) - BORDER_SIZE) { + return false; + } + + if (this.game.putMark(x, y, this.game.yourMarks)) { + try { + PutTicTacToeMarkC2CPacket packet = new PutTicTacToeMarkC2CPacket(Minecraft.getInstance().getConnection().getLocalGameProfile().getName(), x, y); + C2CPacketHandler.getInstance().sendPacket(packet, this.game.opponent); + } catch (CommandSyntaxException e) { + Minecraft.getInstance().gui.getChat().addMessage(Component.translationArg(e.getRawMessage())); + } + if (this.game.getWinner() == this.game.yourMarks) { + activeGames.remove(this.game.opponent.getProfile().getName()); + } + return true; + } + return false; + } + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/WhisperEncryptedCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/WhisperEncryptedCommand.java index fed033b65..0bb5ede87 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/WhisperEncryptedCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/WhisperEncryptedCommand.java @@ -13,8 +13,6 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; -import java.util.Collection; - import static com.mojang.brigadier.arguments.StringArgumentType.*; import static dev.xpple.clientarguments.arguments.CGameProfileArgument.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; @@ -25,20 +23,16 @@ public class WhisperEncryptedCommand { public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("cwe") - .then(argument("player", gameProfile()) + .then(argument("player", gameProfile(true)) .then(argument("message", greedyString()) - .executes((ctx) -> whisper(ctx.getSource(), getProfileArgument(ctx, "player"), getString(ctx, "message")))))); + .executes((ctx) -> whisper(ctx.getSource(), getSingleProfileArgument(ctx, "player"), getString(ctx, "message")))))); } - private static int whisper(FabricClientCommandSource source, Collection profiles, String message) throws CommandSyntaxException { - assert source.getClient().getConnection() != null; - if (profiles.size() != 1) { + private static int whisper(FabricClientCommandSource source, GameProfile player, String message) throws CommandSyntaxException { + PlayerInfo recipient = source.getClient().getConnection().getPlayerInfo(player.getId()); + if (recipient == null) { throw PLAYER_NOT_FOUND_EXCEPTION.create(); } - PlayerInfo recipient = source.getClient().getConnection().getOnlinePlayers().stream() - .filter(p -> p.getProfile().getName().equalsIgnoreCase(profiles.iterator().next().getName())) - .findFirst() - .orElseThrow(PLAYER_NOT_FOUND_EXCEPTION::create); MessageC2CPacket packet = new MessageC2CPacket(source.getClient().getConnection().getLocalGameProfile().getName(), message); C2CPacketHandler.getInstance().sendPacket(packet, recipient); diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/events/ScreenMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/events/ScreenMixin.java new file mode 100644 index 000000000..619da805f --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/events/ScreenMixin.java @@ -0,0 +1,34 @@ +package net.earthcomputer.clientcommands.mixin.events; + +import net.earthcomputer.clientcommands.command.ClientCommandHelper; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Style; +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.CallbackInfoReturnable; + +@Mixin(Screen.class) +public class ScreenMixin { + @Inject(method = "handleComponentClicked", at = @At("HEAD"), cancellable = true) + private void executeCode(Style style, CallbackInfoReturnable cir) { + if (style == null) { + return; + } + ClickEvent clickEvent = style.getClickEvent(); + if (clickEvent == null) { + return; + } + if (clickEvent.getAction() != ClickEvent.Action.CHANGE_PAGE) { + return; + } + String value = clickEvent.getValue(); + Runnable runnable = ClientCommandHelper.runnables.get(value); + if (runnable == null) { + return; + } + runnable.run(); + cir.setReturnValue(true); + } +} diff --git a/src/main/resources/assets/clientcommands/lang/de_de.json b/src/main/resources/assets/clientcommands/lang/de_de.json index 62c4e8b00..7156ad1a2 100644 --- a/src/main/resources/assets/clientcommands/lang/de_de.json +++ b/src/main/resources/assets/clientcommands/lang/de_de.json @@ -172,7 +172,6 @@ "commands.cping.success.other": "Der durchschnittliche Ping von %s ist %dms", "commands.cping.singleplayer": "Du bist in einer Einzelspielerwelt (╯°□°)╯︵ ┻━┻", "commands.cping.playerNotFound": "Spieler nicht gefunden", - "commands.cping.tooManyPlayers": "Deine Abfrage kann maximal 1 Spieler betreffen", "commands.cpos.level.overworld": "die Oberwelt", "commands.cpos.level.the_end": "das Ende", diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index a07ffd4e6..c952b1186 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -173,7 +173,6 @@ "commands.cping.success.other": "The average ping of %s is %dms", "commands.cping.singleplayer": "You are in a singleplayer world (╯°□°)╯︵ ┻━┻", "commands.cping.playerNotFound": "Player not found", - "commands.cping.tooManyPlayers": "Your query can match at most 1 player", "commands.cpos.level.overworld": "the Overworld", "commands.cpos.level.the_end": "the End", @@ -203,6 +202,9 @@ "commands.ctask.stop.noMatch": "No matching tasks", "commands.ctask.stop.success": "Stopped %d tasks", + "commands.ctictactoe.playerNotFound": "Player not found", + "commands.ctictactoe.noGameWithPlayer": "Currently not playing a game with that player", + "commands.ctime.reset.success": "The time now matches the server", "commands.ctitle.cleared": "Cleared titles", @@ -334,6 +336,11 @@ "snakeGame.title": "Snake", "snakeGame.score": "Score: %d", + "ticTacToeGame.title": "Tic-tac-toe against %s", + "ticTacToeGame.playingWith": "You are playing with %s", + "ticTacToeGame.noughts": "noughts (O)", + "ticTacToeGame.crosses": "crosses (X)", + "c2cpacket.messageTooLong": "Message too long (max. 255 characters) got %s characters", "c2cpacket.publicKeyNotFound": "Public key not found", "c2cpacket.encryptionFailed": "Something failed while encrypting your message", @@ -342,5 +349,14 @@ "c2cpacket.sentC2CPacket": "You have sent a C2C packet, but you aren't accepting incoming C2C packets!", "c2cpacket.messageC2CPacket.incoming": "%s -> you: %s", - "c2cpacket.messageC2CPacket.outgoing": "you -> %s: %s" + "c2cpacket.messageC2CPacket.outgoing": "you -> %s: %s", + + "c2cpacket.startTicTacToeGameC2CPacket.outgoing.invited": "You invited %s to a game of tic-tac-toe", + "c2cpacket.startTicTacToeGameC2CPacket.incoming": "%s invited you to a game of tic-tac-toe", + "c2cpacket.startTicTacToeGameC2CPacket.incoming.accept": "Accept", + "c2cpacket.startTicTacToeGameC2CPacket.incoming.accept.hover": "Click to accept", + "c2cpacket.startTicTacToeGameC2CPacket.incoming.accepted": "%s has accepted your invitation, click to make your move", + "c2cpacket.startTicTacToeGameC2CPacket.outgoing.accept": "Accepted the invitation, your opponent will go first", + "c2cpacket.putTicTacToeMarkC2CPacket.incoming.lost": "%s has made a move in tic-tac-toe and won", + "c2cpacket.putTicTacToeMarkC2CPacket.incoming": "%s has made a move in tic-tac-toe, click to make your move" } diff --git a/src/main/resources/assets/clientcommands/lang/id_id.json b/src/main/resources/assets/clientcommands/lang/id_id.json index cf8571b4e..238888e30 100644 --- a/src/main/resources/assets/clientcommands/lang/id_id.json +++ b/src/main/resources/assets/clientcommands/lang/id_id.json @@ -157,7 +157,6 @@ "commands.cping.success.other": "Ping rata rata %s adalah %dms", "commands.cping.singleplayer": "Anda berada di dunia singleplayer (╯°□°)╯︵ ┻━┻", "commands.cping.playerNotFound": "Pemain tidak ditemukan", - "commands.cping.tooManyPlayers": "Pencarian anda hanya dapat cocok dengan maksimal 1 pemain", "commands.cpos.level.overworld": "Overworld", "commands.cpos.level.the_end": "End", diff --git a/src/main/resources/assets/clientcommands/lang/ja_jp.json b/src/main/resources/assets/clientcommands/lang/ja_jp.json index 50668cf2f..97a277a1a 100644 --- a/src/main/resources/assets/clientcommands/lang/ja_jp.json +++ b/src/main/resources/assets/clientcommands/lang/ja_jp.json @@ -157,7 +157,6 @@ "commands.cping.success.other": " %s の平均pingは %dms です", "commands.cping.singleplayer": "あなたはシングルプレイヤーワールドにいます (╯°□°)╯︵ ┻━┻", "commands.cping.playerNotFound": "プレイヤーが見つかりませんでした", - "commands.cping.tooManyPlayers": "あなたのクエリは最大でも1人のプレイヤーにしかマッチできません", "commands.cpos.level.overworld": "現世", "commands.cpos.level.the_end": "ジ・エンド", diff --git a/src/main/resources/assets/clientcommands/lang/zh_cn.json b/src/main/resources/assets/clientcommands/lang/zh_cn.json index b021431ca..bcda1d08a 100644 --- a/src/main/resources/assets/clientcommands/lang/zh_cn.json +++ b/src/main/resources/assets/clientcommands/lang/zh_cn.json @@ -170,7 +170,6 @@ "commands.cping.success.other": "%s的平均ping是%dms", "commands.cping.singleplayer": "你在一个单人世界里(╯°□°)╯︵ ┻━┻", "commands.cping.playerNotFound": "未找到玩家", - "commands.cping.tooManyPlayers": "你的查询最多可以匹配1名玩家", "commands.cpos.level.overworld": "主世界", "commands.cpos.level.the_end": "末地", diff --git a/src/main/resources/assets/clientcommands/textures/tic_tac_toe/grid.png b/src/main/resources/assets/clientcommands/textures/tic_tac_toe/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..e8ee23d53ad54e2e6c79e2c81c2766c2b2598f49 GIT binary patch literal 2557 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 Date: Wed, 29 May 2024 18:14:31 +0100 Subject: [PATCH 4/4] Remove no-longer-relevant old ViaFabricPlus code --- .../util/MultiVersionCompat.java | 48 +------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/util/MultiVersionCompat.java b/src/main/java/net/earthcomputer/clientcommands/util/MultiVersionCompat.java index d221d3e0b..7ad545a8b 100644 --- a/src/main/java/net/earthcomputer/clientcommands/util/MultiVersionCompat.java +++ b/src/main/java/net/earthcomputer/clientcommands/util/MultiVersionCompat.java @@ -5,9 +5,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import net.fabricmc.loader.api.Version; -import net.fabricmc.loader.api.VersionParsingException; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.client.Minecraft; @@ -41,26 +38,13 @@ public boolean doesItemExist(Item item) { return true; } - private static final Version V3_0_6 = Util.make(() -> { - try { - return Version.parse("3.0.6"); - } catch (VersionParsingException e) { - throw new AssertionError(e); - } - }); - public static final MultiVersionCompat INSTANCE = Util.make(() -> { try { FabricLoader loader = FabricLoader.getInstance(); - ModContainer modContainer; if (loader.isModLoaded("viafabric")) { return new ViaFabric(); - } else if ((modContainer = loader.getModContainer("viafabricplus").orElse(null)) != null) { - if (modContainer.getMetadata().getVersion().compareTo(V3_0_6) > 0) { - return new ViaFabricPlus(); // VFP 3.1.0 and newer - } else { - return new ViaFabricPlus3_0_6AndOlder(); // Once 1.20.5 is released, this can be removed - } + } else if (loader.isModLoaded("viafabricplus")) { + return new ViaFabricPlus(); } else { return new None(); } @@ -189,34 +173,6 @@ private int doGetProtocolVersion() throws ReflectiveOperationException { } } - private static final class ViaFabricPlus3_0_6AndOlder extends AbstractViaVersion { - private final Method getTargetVersion; - private final Method versionEnumGetProtocol; - private final Method itemRegistryDiffKeepItem; - - private ViaFabricPlus3_0_6AndOlder() throws ReflectiveOperationException { - Class protocolHack = Class.forName("de.florianmichael.viafabricplus.protocolhack.ProtocolHack"); - Class itemRegistryDiff = Class.forName("de.florianmichael.viafabricplus.fixes.data.ItemRegistryDiff"); - getTargetVersion = protocolHack.getMethod("getTargetVersion"); - versionEnumGetProtocol = getTargetVersion.getReturnType().getMethod("getProtocol"); - itemRegistryDiffKeepItem = itemRegistryDiff.getMethod("keepItem", Item.class); - } - - @Override - protected Object getCurrentVersion() throws ReflectiveOperationException { - return versionEnumGetProtocol.invoke(getTargetVersion.invoke(null)); - } - - @Override - public boolean doesItemExist(Item item) { - try { - return (Boolean) itemRegistryDiffKeepItem.invoke(null, item); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - } - private static final class ViaFabricPlus extends AbstractViaVersion { private final Method getTargetVersion; private final Method olderThan;