diff --git a/src/main/java/land/chipmunk/chipmunkmod/commands/UsernameCommand.java b/src/main/java/land/chipmunk/chipmunkmod/commands/UsernameCommand.java index 7860032..cfad981 100644 --- a/src/main/java/land/chipmunk/chipmunkmod/commands/UsernameCommand.java +++ b/src/main/java/land/chipmunk/chipmunkmod/commands/UsernameCommand.java @@ -8,63 +8,94 @@ import static com.mojang.brigadier.arguments.StringArgumentType.*; import static land.chipmunk.chipmunkmod.command.CommandManager.literal; import static land.chipmunk.chipmunkmod.command.CommandManager.argument; +import land.chipmunk.chipmunkmod.mixin.ClientCommonNetworkHandlerAccessor; +import land.chipmunk.chipmunkmod.util.RandomUtilities; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.client.gui.screen.multiplayer.ConnectScreen; +import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ServerInfo; import net.minecraft.client.network.ServerAddress; import net.minecraft.client.session.Session; +import net.minecraft.network.ClientConnection; import net.minecraft.text.Text; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import java.util.Objects; import java.util.Optional; -import java.util.UUID; +import java.util.Random; import land.chipmunk.chipmunkmod.mixin.MinecraftClientAccessor; +import net.minecraft.util.Uuids; public class UsernameCommand { - private static final Session ORIGINAL_SESSION = MinecraftClient.getInstance().getSession(); private static final SimpleCommandExceptionType USERNAME_TOO_LONG = new SimpleCommandExceptionType(Text.translatable("The specified username is longer than 16 characters")); + private static final char[] PREMIUM_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_" + .toCharArray(); + private static final char SECTION_CHAR = 'ยง'; + + private static final Session ORIGINAL_SESSION = MinecraftClient.getInstance().getSession(); + private static final Random RANDOM = new Random(); public static void register(CommandDispatcher dispatcher) { - dispatcher.register( - literal("username") - .then( - literal("set") - .then( - argument("username", string()) - .executes(UsernameCommand::updateUsername) - ) - ) - .then( - literal("revert") - .executes(c -> updateSession(c, ORIGINAL_SESSION)) - ) + dispatcher.register(literal("username") + .then(literal("set") + .then(argument("username", string()) + .executes(c -> changeOffline(c, getString(c, "username"))))) + .then(literal("revert") + .executes(c -> changeSession(c, ORIGINAL_SESSION))) + + .then(literal("random") + .then(literal("blank") + .executes(c -> changeOffline(c, RandomUtilities.emptyUsername(RANDOM, SECTION_CHAR)))) + .executes(c -> changeOffline(c, RandomUtilities.randomString(RANDOM, PREMIUM_CHARS, RANDOM.nextInt(3, 16))))) + .then(literal("empty") + .executes(c -> changeOffline(c, ""))) ); } - public static int updateUsername(CommandContext context) throws CommandSyntaxException { - final String username = getString(context, "username"); - if (username.length() > 16) throw USERNAME_TOO_LONG.create(); - final Session session = new Session(username, new UUID(0L, 0L), "", Optional.empty(), Optional.empty(), Session.AccountType.MOJANG); - return updateSession(context, session); + private static Session offline(final String username) { + // This is how Minecraft's Main class does it + return new Session(username, Uuids.getOfflinePlayerUuid(username), + "", Optional.empty(), Optional.empty(), Session.AccountType.LEGACY); } - public static int updateSession(CommandContext context, Session session) { - final FabricClientCommandSource source = context.getSource(); + // TODO: Put this in a separate class + private static void reconnect(final MinecraftClient client) { + final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler(); + if (networkHandler == null) return; // single-player? - final MinecraftClient client = source.getClient(); + final ServerInfo info = networkHandler.getServerInfo(); + if (info == null) return; // definitely single-player + final ClientConnection connection = networkHandler.getConnection(); + final Screen screen = Objects.requireNonNullElseGet( + ((ClientCommonNetworkHandlerAccessor) networkHandler).getPostDisconnectScreen(), + TitleScreen::new); + + // This stuff needs to run after we close chat, otherwise it kicks us to the title screen. Don't ask me why + client.send(() -> { + connection.disconnect(Text.translatable("disconnect.transfer")); + connection.tryDisableAutoRead(); + connection.handleDisconnection(); + + ConnectScreen.connect(screen, client, ServerAddress.parse(info.address), info, false, null); + }); + } + + private static int changeSession(final CommandContext context, final Session session) { + final MinecraftClient client = context.getSource().getClient(); ((MinecraftClientAccessor) client).session(session); - // TODO: Put this in a separate class - final ServerInfo info = client.getCurrentServerEntry(); - if (client.world != null) client.world.disconnect(); - client.disconnect(); - ConnectScreen.connect(new TitleScreen(), client, ServerAddress.parse(info.address), info, false, null); - + reconnect(client); return Command.SINGLE_SUCCESS; } + + private static int changeOffline(final CommandContext context, final String username) throws CommandSyntaxException { + if (username.length() > 16) throw USERNAME_TOO_LONG.create(); + return changeSession(context, offline(username)); + } } diff --git a/src/main/java/land/chipmunk/chipmunkmod/mixin/ClientCommonNetworkHandlerAccessor.java b/src/main/java/land/chipmunk/chipmunkmod/mixin/ClientCommonNetworkHandlerAccessor.java new file mode 100644 index 0000000..29d6cb6 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkmod/mixin/ClientCommonNetworkHandlerAccessor.java @@ -0,0 +1,12 @@ +package land.chipmunk.chipmunkmod.mixin; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.network.ClientCommonNetworkHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientCommonNetworkHandler.class) +public interface ClientCommonNetworkHandlerAccessor { + @Accessor("postDisconnectScreen") + Screen getPostDisconnectScreen(); +} diff --git a/src/main/java/land/chipmunk/chipmunkmod/util/RandomUtilities.java b/src/main/java/land/chipmunk/chipmunkmod/util/RandomUtilities.java index 06fd06b..47e3615 100644 --- a/src/main/java/land/chipmunk/chipmunkmod/util/RandomUtilities.java +++ b/src/main/java/land/chipmunk/chipmunkmod/util/RandomUtilities.java @@ -9,12 +9,16 @@ public final class RandomUtilities { .toCharArray(); public static String emptyUsername(final Random random) { + return RandomUtilities.emptyUsername(random, '&'); + } + + public static String emptyUsername(final Random random, char colorChar) { final char[] buf = new char[16]; for (int i = 0; i < 16; i += 2) { final int j = random.nextInt(LEGACY_STYLE_CODES.length); - buf[i] = '&'; + buf[i] = colorChar; buf[i + 1] = LEGACY_STYLE_CODES[j]; } diff --git a/src/main/resources/chipmunkmod.mixins.json b/src/main/resources/chipmunkmod.mixins.json index 6a09716..7110ff0 100644 --- a/src/main/resources/chipmunkmod.mixins.json +++ b/src/main/resources/chipmunkmod.mixins.json @@ -6,6 +6,7 @@ "client": [ "ChatInputSuggestorMixin", "ChatScreenMixin", + "ClientCommonNetworkHandlerAccessor", "ClientConnectionMixin", "ClientPlayerEntityMixin", "ClientPlayNetworkHandlerMixin",