package com.github.zly2006.carpetslsaddition.command; import carpet.CarpetSettings; import carpet.fakes.ServerPlayerInterface; import carpet.helpers.EntityPlayerActionPack; import carpet.patches.EntityPlayerMPFake; import carpet.patches.FakeClientConnection; import carpet.utils.Messenger; import com.github.zly2006.carpetslsaddition.SLSCarpetSettings; import com.github.zly2006.carpetslsaddition.ServerMain; import com.github.zly2006.carpetslsaddition.util.access.PlayerAccessor; import com.github.zly2006.carpetslsaddition.util.access.SLSBotAccessor; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.SharedConstants; import net.minecraft.command.argument.DimensionArgumentType; import net.minecraft.command.argument.GameModeArgumentType; import net.minecraft.command.argument.RotationArgumentType; import net.minecraft.command.argument.Vec3ArgumentType; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.NetworkSide; import net.minecraft.network.packet.c2s.common.SyncedClientOptions; import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; import net.minecraft.registry.RegistryKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.PlayerManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.UserCache; import net.minecraft.util.Uuids; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec2f; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameMode; import net.minecraft.world.World; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; import static net.minecraft.command.CommandSource.suggestMatching; import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.server.command.CommandManager.literal; public class BotCommand { public static void register(CommandDispatcher dispatcher) { LiteralArgumentBuilder command = literal("bot") .requires(source -> source.hasPermissionLevel(4) || Permissions.check(source, "slsaddition.command.bot")) .then(argument("player", StringArgumentType.word()) .suggests((c, b) -> suggestMatching(getPlayerSuggestions(c.getSource()), b)) .then(literal("stop").executes(manipulation(EntityPlayerActionPack::stopAll))) .then(makeActionCommand("use", EntityPlayerActionPack.ActionType.USE)) .then(makeActionCommand("jump", EntityPlayerActionPack.ActionType.JUMP)) .then(makeActionCommand("attack", EntityPlayerActionPack.ActionType.ATTACK)) .then(makeActionCommand("drop", EntityPlayerActionPack.ActionType.DROP_ITEM)) .then(makeDropCommand("drop", false)) .then(makeActionCommand("dropStack", EntityPlayerActionPack.ActionType.DROP_STACK)) .then(makeDropCommand("dropStack", true)) .then(makeActionCommand("swapHands", EntityPlayerActionPack.ActionType.SWAP_HANDS)) .then(literal("hotbar") .then(argument("slot", IntegerArgumentType.integer(1, 9)) .executes(c -> manipulate(c, ap -> ap.setSlot(IntegerArgumentType.getInteger(c, "slot")))))) .then(literal("kill").executes(BotCommand::kill)) .then(literal("respawn").executes(BotCommand::respawn)) .then(literal("mount").executes(manipulation(ap -> ap.mount(true))) .then(literal("anything").executes(manipulation(ap -> ap.mount(false))))) .then(literal("dismount").executes(manipulation(EntityPlayerActionPack::dismount))) .then(literal("sneak").executes(manipulation(ap -> ap.setSneaking(true)))) .then(literal("unsneak").executes(manipulation(ap -> ap.setSneaking(false)))) .then(literal("sprint").executes(manipulation(ap -> ap.setSprinting(true)))) .then(literal("unsprint").executes(manipulation(ap -> ap.setSprinting(false)))) .then(literal("look") .then(literal("north").executes(manipulation(ap -> ap.look(Direction.NORTH)))) .then(literal("south").executes(manipulation(ap -> ap.look(Direction.SOUTH)))) .then(literal("east").executes(manipulation(ap -> ap.look(Direction.EAST)))) .then(literal("west").executes(manipulation(ap -> ap.look(Direction.WEST)))) .then(literal("up").executes(manipulation(ap -> ap.look(Direction.UP)))) .then(literal("down").executes(manipulation(ap -> ap.look(Direction.DOWN)))) .then(literal("at").then(argument("position", Vec3ArgumentType.vec3()) .executes(c -> manipulate(c, ap -> ap.lookAt(Vec3ArgumentType.getVec3(c, "position")))))) .then(argument("direction", RotationArgumentType.rotation()) .executes(c -> manipulate(c, ap -> ap.look(RotationArgumentType.getRotation(c, "direction").toAbsoluteRotation(c.getSource()))))) ).then(literal("turn") .then(literal("left").executes(manipulation(ap -> ap.turn(-90, 0)))) .then(literal("right").executes(manipulation(ap -> ap.turn(90, 0)))) .then(literal("back").executes(manipulation(ap -> ap.turn(180, 0)))) .then(argument("rotation", RotationArgumentType.rotation()) .executes(c -> manipulate(c, ap -> ap.turn(RotationArgumentType.getRotation(c, "rotation").toAbsoluteRotation(c.getSource()))))) ).then(literal("move").executes(manipulation(EntityPlayerActionPack::stopMovement)) .then(literal("forward").executes(manipulation(ap -> ap.setForward(1)))) .then(literal("backward").executes(manipulation(ap -> ap.setForward(-1)))) .then(literal("left").executes(manipulation(ap -> ap.setStrafing(1)))) .then(literal("right").executes(manipulation(ap -> ap.setStrafing(-1)))) ).then(literal("spawn").executes(BotCommand::spawn) .then(literal("in").requires((player) -> player.hasPermissionLevel(2)) .then(argument("gamemode", GameModeArgumentType.gameMode()) .executes(BotCommand::spawn))) .then(literal("at").then(argument("position", Vec3ArgumentType.vec3()).executes(BotCommand::spawn) .then(literal("facing").then(argument("direction", RotationArgumentType.rotation()).executes(BotCommand::spawn) .then(literal("in").then(argument("dimension", DimensionArgumentType.dimension()).executes(BotCommand::spawn) .then(literal("in").requires((player) -> player.hasPermissionLevel(2)) .then(argument("gamemode", GameModeArgumentType.gameMode()) .executes(BotCommand::spawn) ))) ))) )) ) ); dispatcher.register(command); } private static LiteralArgumentBuilder makeActionCommand(String actionName, EntityPlayerActionPack.ActionType type) { return literal(actionName) .executes(manipulation(ap -> ap.start(type, EntityPlayerActionPack.Action.once()))) .then(literal("once").executes(manipulation(ap -> ap.start(type, EntityPlayerActionPack.Action.once())))) .then(literal("continuous").executes(manipulation(ap -> ap.start(type, EntityPlayerActionPack.Action.continuous())))) .then(literal("interval").then(argument("ticks", IntegerArgumentType.integer(1)) .executes(c -> manipulate(c, ap -> ap.start(type, EntityPlayerActionPack.Action.interval(IntegerArgumentType.getInteger(c, "ticks"))))))); } private static LiteralArgumentBuilder makeDropCommand(String actionName, boolean dropAll) { return literal(actionName) .then(literal("all").executes(manipulation(ap -> ap.drop(-2, dropAll)))) .then(literal("mainhand").executes(manipulation(ap -> ap.drop(-1, dropAll)))) .then(literal("offhand").executes(manipulation(ap -> ap.drop(40, dropAll)))) .then(argument("slot", IntegerArgumentType.integer(0, 40)). executes(c -> manipulate(c, ap -> ap.drop(IntegerArgumentType.getInteger(c, "slot"), dropAll)))); } private static Collection getPlayerSuggestions(ServerCommandSource source) { Set players = new LinkedHashSet<>(List.of("Steve", "Alex")); players.addAll(source.getPlayerNames()); return players; } private static ServerPlayerEntity getPlayer(CommandContext context) { String playerName = StringArgumentType.getString(context, "player"); MinecraftServer server = context.getSource().getServer(); return server.getPlayerManager().getPlayer(playerName); } private static boolean cantManipulate(CommandContext context) { PlayerEntity player = getPlayer(context); ServerCommandSource source = context.getSource(); if (player == null) { Messenger.m(source, "r Can only manipulate existing players"); return true; } PlayerEntity sender = source.getPlayer(); if (sender == null) { return false; } if (!source.getServer().getPlayerManager().isOperator(sender.getGameProfile())) { if (sender != player && !(player instanceof EntityPlayerMPFake)) { Messenger.m(source, "r Non OP players can't control other real players"); return true; } } return false; } private static boolean cantReMove(CommandContext context) { if (cantManipulate(context)) return true; PlayerEntity player = getPlayer(context); if (player instanceof EntityPlayerMPFake) return false; Messenger.m(context.getSource(), "r Only fake players can be moved or killed"); return true; } private static boolean cantSpawn(CommandContext context) { String playerName = getBotPrefix() + StringArgumentType.getString(context, "player"); MinecraftServer server = context.getSource().getServer(); PlayerManager manager = server.getPlayerManager(); if (manager.getPlayer(playerName) != null) { Messenger.m(context.getSource(), "r Player ", "rb " + playerName, "r is already logged on"); return true; } GameProfile profile = server.getUserCache().findByName(playerName).orElse(null); if (profile == null) { if (!CarpetSettings.allowSpawningOfflinePlayers) { Messenger.m(context.getSource(), "r Player "+playerName+" is either banned by Mojang, or auth servers are down. " + "Banned players can only be summoned in Singleplayer and in servers in off-line mode."); return true; } else { profile = new GameProfile(Uuids.getOfflinePlayerUuid(playerName), playerName); } } if (manager.getUserBanList().contains(profile)) { Messenger.m(context.getSource(), "r Player ", "rb " + playerName, "r is banned on this server"); return true; } if (manager.isWhitelistEnabled() && manager.isWhitelisted(profile) && !context.getSource().hasPermissionLevel(2)) { Messenger.m(context.getSource(), "r Whitelisted players can only be spawned by operators"); return true; } return false; } private static String getBotPrefix() { return SLSCarpetSettings.botPrefix.equals("#none") ? "" : SLSCarpetSettings.botPrefix; } private static int kill(CommandContext context) { if (cantReMove(context)) return 0; getPlayer(context).kill(); return 1; } private static int respawn(CommandContext context) { var player = getPlayer(context); if (player instanceof EntityPlayerMPFake && ((SLSBotAccessor)player).carpet_SLS_Addition$isBot()) { ((SLSBotAccessor)player).carpet_SLS_Addition$setSpawnTime(System.currentTimeMillis()); context.getSource().sendMessage( Text.translatable("carpet.slsa.bot.respawned", player.getNameForScoreboard()) .setStyle( Style.EMPTY.withColor(Formatting.GREEN) ) ); return Command.SINGLE_SUCCESS; } context.getSource().sendMessage( Text.translatable("carpet.slsa.bot.not_a_bot", player.getNameForScoreboard()) .setStyle( Style.EMPTY.withColor(Formatting.RED) ) ); return 0; } @FunctionalInterface interface SupplierWithCSE { T get() throws CommandSyntaxException; } private static T getArgOrDefault(BotCommand.SupplierWithCSE getter, T defaultValue) throws CommandSyntaxException { try { return getter.get(); } catch (IllegalArgumentException e) { return defaultValue; } } private static int spawn(CommandContext context) throws CommandSyntaxException { if (cantSpawn(context)) return 0; ServerCommandSource source = context.getSource(); Vec3d pos = getArgOrDefault( () -> Vec3ArgumentType.getVec3(context, "position"), source.getPosition() ); Vec2f facing = getArgOrDefault( () -> RotationArgumentType.getRotation(context, "direction").toAbsoluteRotation(source), source.getRotation() ); RegistryKey dimType = getArgOrDefault( () -> DimensionArgumentType.getDimensionArgument(context, "dimension").getRegistryKey(), source.getWorld().getRegistryKey() ); String playerName = getBotPrefix() + StringArgumentType.getString(context, "player"); if (playerName.length() > maxNameLength(source.getServer())) { Messenger.m(source, "rb Player name: " + playerName + " is too long"); return 0; } if (!World.isValid(BlockPos.ofFloored(pos))) { Messenger.m(source, "rb Player " + playerName + " cannot be placed outside of the world"); return 0; } EntityPlayerMPFake bot = createBot(playerName, source.getServer(), pos, facing.y, facing.x, dimType); if (bot == null) { Messenger.m(source, "rb Player " + playerName + " doesn't exist and cannot spawn in online mode. " + "Turn the server offline to spawn non-existing players"); return 0; } ((PlayerAccessor) bot).carpet_SLS_Addition$setDisplayName(Text.empty().append(Text.literal("[%s] ".formatted(source.getName())).setStyle(Style.EMPTY.withColor(Formatting.AQUA))).append(Text.literal(playerName).setStyle(Style.EMPTY))); ServerMain.server.getPlayerManager().broadcast(Text.empty() .append(Text.literal("假人").setStyle(Style.EMPTY.withColor(Formatting.GREEN))) .append(Text.literal(playerName).setStyle(Style.EMPTY.withColor(Formatting.GOLD).withBold(true))) .append(Text.literal("由玩家").setStyle(Style.EMPTY.withColor(Formatting.GREEN))) .append(source.getDisplayName()) .append(Text.literal("召唤!").setStyle(Style.EMPTY.withColor(Formatting.GREEN))), false); ServerMain.server.getPlayerManager().sendToAll(new PlayerListS2CPacket( PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME, bot )); return 1; } @SuppressWarnings("unchecked") private static EntityPlayerMPFake createBot(String username, MinecraftServer server, Vec3d pos, double yaw, double pitch, RegistryKey dimensionId) { ServerWorld worldIn = server.getWorld(dimensionId); UserCache.setUseRemote(false); GameProfile gameprofile; try { gameprofile = server.getUserCache().findByName(username).orElse(null); //findByName .orElse(null) } finally { UserCache.setUseRemote(server.isDedicated() && server.isOnlineMode()); } if (gameprofile == null) { if (!CarpetSettings.allowSpawningOfflinePlayers) { return null; } else { gameprofile = new GameProfile(Uuids.getOfflinePlayerUuid(username), username); } } // 孩子不懂,用反射写着玩的,报错了记得随Carpet一起升级一下 try { Class fakePlayerClass = (Class)Class.forName("carpet.patches.EntityPlayerMPFake"); Constructor constructor = fakePlayerClass.getDeclaredConstructor( MinecraftServer.class, ServerWorld.class, GameProfile.class, SyncedClientOptions.class, boolean.class ); constructor.setAccessible(true); EntityPlayerMPFake bot = constructor.newInstance(server, worldIn, gameprofile, SyncedClientOptions.createDefault(), false); bot.fixStartingPosition = () -> bot.refreshPositionAndAngles(pos.x, pos.y, pos.z, (float) yaw, (float) pitch); server.getPlayerManager().onPlayerConnect(new FakeClientConnection(NetworkSide.SERVERBOUND), bot, new ConnectedClientData(gameprofile, 0, bot.getClientOptions(), false)); bot.teleport(worldIn, pos.x, pos.y, pos.z, (float) yaw, (float) pitch); bot.setHealth(20.0F); bot.unsetRemoved(); bot.getAttributeInstance(EntityAttributes.GENERIC_STEP_HEIGHT).setBaseValue(0.6F); bot.interactionManager.changeGameMode(GameMode.SURVIVAL); server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(bot, (byte) (bot.headYaw * 256 / 360)), dimensionId);//bot.dimension); server.getPlayerManager().sendToDimension(new EntityPositionS2CPacket(bot), dimensionId); bot.getDataTracker().set(PlayerEntity.PLAYER_MODEL_PARTS, (byte) 0x7f); // show all model layers (incl. capes) bot.getAbilities().flying = false; ((SLSBotAccessor)bot).carpet_SLS_Addition$setBot(true); ((SLSBotAccessor)bot).carpet_SLS_Addition$setSpawnTime(System.currentTimeMillis()); return bot; } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("An error has occurred when trying spawn a new bot with reflection", e); } } private static int maxNameLength(MinecraftServer server) { return server.getServerPort() >= 0 ? SharedConstants.field_49170 : 40; } private static int manipulate(CommandContext context, Consumer action) { if (cantManipulate(context)) return 0; ServerPlayerEntity player = getPlayer(context); action.accept(((ServerPlayerInterface) player).getActionPack()); return 1; } private static Command manipulation(Consumer action) { return c -> manipulate(c, action); } }