SpreadPlayersCommand.java

net.minecraft.server.commands.SpreadPlayersCommand

信息

  • 全限定名:net.minecraft.server.commands.SpreadPlayersCommand
  • 类型:public class
  • 包:net.minecraft.server.commands
  • 源码路径:src/main/java/net/minecraft/server/commands/SpreadPlayersCommand.java
  • 起始行号:L34
  • 职责:

    TODO

字段/常量

  • MAX_ITERATION_COUNT

    • 类型: int
    • 修饰符: private static final
    • 源码定位: L35
    • 说明:

      TODO

  • ERROR_FAILED_TO_SPREAD_TEAMS

    • 类型: Dynamic4CommandExceptionType
    • 修饰符: private static final
    • 源码定位: L36
    • 说明:

      TODO

  • ERROR_FAILED_TO_SPREAD_ENTITIES

    • 类型: Dynamic4CommandExceptionType
    • 修饰符: private static final
    • 源码定位: L39
    • 说明:

      TODO

  • ERROR_INVALID_MAX_HEIGHT

    • 类型: Dynamic2CommandExceptionType
    • 修饰符: private static final
    • 源码定位: L42
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.server.commands.SpreadPlayersCommand.Position
    • 类型: class
    • 修饰符: private static
    • 源码定位: L292
    • 说明:

      TODO

构造器

方法

下面的方法块按源码顺序生成。

public static void register(CommandDispatcher<CommandSourceStack> dispatcher) @ L46

  • 方法名:register
  • 源码定位:L46
  • 返回类型:void
  • 修饰符:public static

参数:

  • dispatcher: CommandDispatcher

说明:

TODO

private static int spreadPlayers(CommandSourceStack source, Vec2 center, float spreadDistance, float maxRange, int maxHeight, boolean respectTeams, Collection<?extends Entity> entities) @ L102

  • 方法名:spreadPlayers
  • 源码定位:L102
  • 返回类型:int
  • 修饰符:private static

参数:

  • source: CommandSourceStack
  • center: Vec2
  • spreadDistance: float
  • maxRange: float
  • maxHeight: int
  • respectTeams: boolean
  • entities: Collection<?extends Entity>

说明:

TODO

private static int getNumberOfTeams(Collection<?extends Entity> players) @ L140

  • 方法名:getNumberOfTeams
  • 源码定位:L140
  • 返回类型:int
  • 修饰符:private static

参数:

  • players: Collection<?extends Entity>

说明:

TODO

private static void spreadPositions(Vec2 center, double spreadDist, ServerLevel level, RandomSource random, double minX, double minZ, double maxX, double maxZ, int maxHeight, SpreadPlayersCommand.Position[] positions, boolean respectTeams) @ L154

  • 方法名:spreadPositions
  • 源码定位:L154
  • 返回类型:void
  • 修饰符:private static

参数:

  • center: Vec2
  • spreadDist: double
  • level: ServerLevel
  • random: RandomSource
  • minX: double
  • minZ: double
  • maxX: double
  • maxZ: double
  • maxHeight: int
  • positions: SpreadPlayersCommand.Position[]
  • respectTeams: boolean

说明:

TODO

private static double setPlayerPositions(Collection<?extends Entity> entities, ServerLevel level, SpreadPlayersCommand.Position[] positions, int maxHeight, boolean respectTeams) @ L235

  • 方法名:setPlayerPositions
  • 源码定位:L235
  • 返回类型:double
  • 修饰符:private static

参数:

  • entities: Collection<?extends Entity>
  • level: ServerLevel
  • positions: SpreadPlayersCommand.Position[]
  • maxHeight: int
  • respectTeams: boolean

说明:

TODO

private static SpreadPlayersCommand.Position[] createInitialPositions(RandomSource random, int count, double minX, double minZ, double maxX, double maxZ) @ L280

  • 方法名:createInitialPositions
  • 源码定位:L280
  • 返回类型:SpreadPlayersCommand.Position[]
  • 修饰符:private static

参数:

  • random: RandomSource
  • count: int
  • minX: double
  • minZ: double
  • maxX: double
  • maxZ: double

说明:

TODO

代码

public class SpreadPlayersCommand {
    private static final int MAX_ITERATION_COUNT = 10000;
    private static final Dynamic4CommandExceptionType ERROR_FAILED_TO_SPREAD_TEAMS = new Dynamic4CommandExceptionType(
        (count, x, z, recommended) -> Component.translatableEscape("commands.spreadplayers.failed.teams", count, x, z, recommended)
    );
    private static final Dynamic4CommandExceptionType ERROR_FAILED_TO_SPREAD_ENTITIES = new Dynamic4CommandExceptionType(
        (count, x, z, recommended) -> Component.translatableEscape("commands.spreadplayers.failed.entities", count, x, z, recommended)
    );
    private static final Dynamic2CommandExceptionType ERROR_INVALID_MAX_HEIGHT = new Dynamic2CommandExceptionType(
        (suppliedMaxHeight, worldMinHeight) -> Component.translatableEscape("commands.spreadplayers.failed.invalid.height", suppliedMaxHeight, worldMinHeight)
    );
 
    public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
        dispatcher.register(
            Commands.literal("spreadplayers")
                .requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))
                .then(
                    Commands.argument("center", Vec2Argument.vec2())
                        .then(
                            Commands.argument("spreadDistance", FloatArgumentType.floatArg(0.0F))
                                .then(
                                    Commands.argument("maxRange", FloatArgumentType.floatArg(1.0F))
                                        .then(
                                            Commands.argument("respectTeams", BoolArgumentType.bool())
                                                .then(
                                                    Commands.argument("targets", EntityArgument.entities())
                                                        .executes(
                                                            c -> spreadPlayers(
                                                                c.getSource(),
                                                                Vec2Argument.getVec2(c, "center"),
                                                                FloatArgumentType.getFloat(c, "spreadDistance"),
                                                                FloatArgumentType.getFloat(c, "maxRange"),
                                                                c.getSource().getLevel().getMaxY() + 1,
                                                                BoolArgumentType.getBool(c, "respectTeams"),
                                                                EntityArgument.getEntities(c, "targets")
                                                            )
                                                        )
                                                )
                                        )
                                        .then(
                                            Commands.literal("under")
                                                .then(
                                                    Commands.argument("maxHeight", IntegerArgumentType.integer())
                                                        .then(
                                                            Commands.argument("respectTeams", BoolArgumentType.bool())
                                                                .then(
                                                                    Commands.argument("targets", EntityArgument.entities())
                                                                        .executes(
                                                                            c -> spreadPlayers(
                                                                                c.getSource(),
                                                                                Vec2Argument.getVec2(c, "center"),
                                                                                FloatArgumentType.getFloat(c, "spreadDistance"),
                                                                                FloatArgumentType.getFloat(c, "maxRange"),
                                                                                IntegerArgumentType.getInteger(c, "maxHeight"),
                                                                                BoolArgumentType.getBool(c, "respectTeams"),
                                                                                EntityArgument.getEntities(c, "targets")
                                                                            )
                                                                        )
                                                                )
                                                        )
                                                )
                                        )
                                )
                        )
                )
        );
    }
 
    private static int spreadPlayers(
        CommandSourceStack source,
        Vec2 center,
        float spreadDistance,
        float maxRange,
        int maxHeight,
        boolean respectTeams,
        Collection<? extends Entity> entities
    ) throws CommandSyntaxException {
        ServerLevel level = source.getLevel();
        int minY = level.getMinY();
        if (maxHeight < minY) {
            throw ERROR_INVALID_MAX_HEIGHT.create(maxHeight, minY);
        } else {
            RandomSource random = RandomSource.createThreadLocalInstance();
            double minX = center.x - maxRange;
            double minZ = center.y - maxRange;
            double maxX = center.x + maxRange;
            double maxZ = center.y + maxRange;
            SpreadPlayersCommand.Position[] positions = createInitialPositions(
                random, respectTeams ? getNumberOfTeams(entities) : entities.size(), minX, minZ, maxX, maxZ
            );
            spreadPositions(center, spreadDistance, level, random, minX, minZ, maxX, maxZ, maxHeight, positions, respectTeams);
            double distance = setPlayerPositions(entities, level, positions, maxHeight, respectTeams);
            source.sendSuccess(
                () -> Component.translatable(
                    "commands.spreadplayers.success." + (respectTeams ? "teams" : "entities"),
                    positions.length,
                    center.x,
                    center.y,
                    String.format(Locale.ROOT, "%.2f", distance)
                ),
                true
            );
            return positions.length;
        }
    }
 
    private static int getNumberOfTeams(Collection<? extends Entity> players) {
        Set<Team> teams = Sets.newHashSet();
 
        for (Entity player : players) {
            if (player instanceof Player) {
                teams.add(player.getTeam());
            } else {
                teams.add(null);
            }
        }
 
        return teams.size();
    }
 
    private static void spreadPositions(
        Vec2 center,
        double spreadDist,
        ServerLevel level,
        RandomSource random,
        double minX,
        double minZ,
        double maxX,
        double maxZ,
        int maxHeight,
        SpreadPlayersCommand.Position[] positions,
        boolean respectTeams
    ) throws CommandSyntaxException {
        boolean hasCollisions = true;
        double minDistance = Float.MAX_VALUE;
 
        int iteration;
        for (iteration = 0; iteration < 10000 && hasCollisions; iteration++) {
            hasCollisions = false;
            minDistance = Float.MAX_VALUE;
 
            for (int i = 0; i < positions.length; i++) {
                SpreadPlayersCommand.Position position = positions[i];
                int neighbourCount = 0;
                SpreadPlayersCommand.Position averageNeighbourPos = new SpreadPlayersCommand.Position();
 
                for (int j = 0; j < positions.length; j++) {
                    if (i != j) {
                        SpreadPlayersCommand.Position neighbour = positions[j];
                        double dist = position.dist(neighbour);
                        minDistance = Math.min(dist, minDistance);
                        if (dist < spreadDist) {
                            neighbourCount++;
                            averageNeighbourPos.x = averageNeighbourPos.x + (neighbour.x - position.x);
                            averageNeighbourPos.z = averageNeighbourPos.z + (neighbour.z - position.z);
                        }
                    }
                }
 
                if (neighbourCount > 0) {
                    averageNeighbourPos.x /= neighbourCount;
                    averageNeighbourPos.z /= neighbourCount;
                    double length = averageNeighbourPos.getLength();
                    if (length > 0.0) {
                        averageNeighbourPos.normalize();
                        position.moveAway(averageNeighbourPos);
                    } else {
                        position.randomize(random, minX, minZ, maxX, maxZ);
                    }
 
                    hasCollisions = true;
                }
 
                if (position.clamp(minX, minZ, maxX, maxZ)) {
                    hasCollisions = true;
                }
            }
 
            if (!hasCollisions) {
                for (SpreadPlayersCommand.Position position : positions) {
                    if (!position.isSafe(level, maxHeight)) {
                        position.randomize(random, minX, minZ, maxX, maxZ);
                        hasCollisions = true;
                    }
                }
            }
        }
 
        if (minDistance == Float.MAX_VALUE) {
            minDistance = 0.0;
        }
 
        if (iteration >= 10000) {
            if (respectTeams) {
                throw ERROR_FAILED_TO_SPREAD_TEAMS.create(positions.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", minDistance));
            } else {
                throw ERROR_FAILED_TO_SPREAD_ENTITIES.create(positions.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", minDistance));
            }
        }
    }
 
    private static double setPlayerPositions(
        Collection<? extends Entity> entities, ServerLevel level, SpreadPlayersCommand.Position[] positions, int maxHeight, boolean respectTeams
    ) {
        double avgDistance = 0.0;
        int positionIndex = 0;
        Map<Team, SpreadPlayersCommand.Position> teamPositions = Maps.newHashMap();
 
        for (Entity entity : entities) {
            SpreadPlayersCommand.Position position;
            if (respectTeams) {
                Team team = entity instanceof Player ? entity.getTeam() : null;
                if (!teamPositions.containsKey(team)) {
                    teamPositions.put(team, positions[positionIndex++]);
                }
 
                position = teamPositions.get(team);
            } else {
                position = positions[positionIndex++];
            }
 
            entity.teleportTo(
                level,
                Mth.floor(position.x) + 0.5,
                position.getSpawnY(level, maxHeight),
                Mth.floor(position.z) + 0.5,
                Set.of(),
                entity.getYRot(),
                entity.getXRot(),
                true
            );
            double closest = Double.MAX_VALUE;
 
            for (SpreadPlayersCommand.Position testPosition : positions) {
                if (position != testPosition) {
                    double dist = position.dist(testPosition);
                    closest = Math.min(dist, closest);
                }
            }
 
            avgDistance += closest;
        }
 
        return entities.size() < 2 ? 0.0 : avgDistance / entities.size();
    }
 
    private static SpreadPlayersCommand.Position[] createInitialPositions(RandomSource random, int count, double minX, double minZ, double maxX, double maxZ) {
        SpreadPlayersCommand.Position[] result = new SpreadPlayersCommand.Position[count];
 
        for (int i = 0; i < result.length; i++) {
            SpreadPlayersCommand.Position position = new SpreadPlayersCommand.Position();
            position.randomize(random, minX, minZ, maxX, maxZ);
            result[i] = position;
        }
 
        return result;
    }
 
    private static class Position {
        private double x;
        private double z;
 
        double dist(SpreadPlayersCommand.Position target) {
            double dx = this.x - target.x;
            double dz = this.z - target.z;
            return Math.sqrt(dx * dx + dz * dz);
        }
 
        void normalize() {
            double dist = this.getLength();
            this.x /= dist;
            this.z /= dist;
        }
 
        double getLength() {
            return Math.sqrt(this.x * this.x + this.z * this.z);
        }
 
        public void moveAway(SpreadPlayersCommand.Position pos) {
            this.x = this.x - pos.x;
            this.z = this.z - pos.z;
        }
 
        public boolean clamp(double minX, double minZ, double maxX, double maxZ) {
            boolean changed = false;
            if (this.x < minX) {
                this.x = minX;
                changed = true;
            } else if (this.x > maxX) {
                this.x = maxX;
                changed = true;
            }
 
            if (this.z < minZ) {
                this.z = minZ;
                changed = true;
            } else if (this.z > maxZ) {
                this.z = maxZ;
                changed = true;
            }
 
            return changed;
        }
 
        public int getSpawnY(BlockGetter level, int maxHeight) {
            BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(this.x, (double)(maxHeight + 1), this.z);
            boolean air2Above = level.getBlockState(pos).isAir();
            pos.move(Direction.DOWN);
            boolean air1Above = level.getBlockState(pos).isAir();
 
            while (pos.getY() > level.getMinY()) {
                pos.move(Direction.DOWN);
                boolean currentIsAir = level.getBlockState(pos).isAir();
                if (!currentIsAir && air1Above && air2Above) {
                    return pos.getY() + 1;
                }
 
                air2Above = air1Above;
                air1Above = currentIsAir;
            }
 
            return maxHeight + 1;
        }
 
        public boolean isSafe(BlockGetter level, int maxHeight) {
            BlockPos pos = BlockPos.containing(this.x, this.getSpawnY(level, maxHeight) - 1, this.z);
            BlockState state = level.getBlockState(pos);
            return pos.getY() < maxHeight && !state.liquid() && !state.is(BlockTags.FIRE);
        }
 
        public void randomize(RandomSource random, double minX, double minZ, double maxX, double maxZ) {
            this.x = Mth.nextDouble(random, minX, maxX);
            this.z = Mth.nextDouble(random, minZ, maxZ);
        }
    }
}

引用的其他类

  • CommandSourceStack

    • 引用位置: 参数
  • Commands

    • 引用位置: 方法调用
    • 关联成员: Commands.argument(), Commands.hasPermission(), Commands.literal()
  • EntityArgument

    • 引用位置: 方法调用
    • 关联成员: EntityArgument.entities(), EntityArgument.getEntities()
  • Vec2Argument

    • 引用位置: 方法调用
    • 关联成员: Vec2Argument.getVec2(), Vec2Argument.vec2()
  • BlockPos

    • 引用位置: 方法调用/构造调用
    • 关联成员: BlockPos.MutableBlockPos(), BlockPos.containing(), MutableBlockPos()
  • Component

    • 引用位置: 方法调用
    • 关联成员: Component.translatable(), Component.translatableEscape()
  • ServerLevel

    • 引用位置: 参数
  • Mth

    • 引用位置: 方法调用
    • 关联成员: Mth.floor(), Mth.nextDouble()
  • RandomSource

    • 引用位置: 参数/方法调用
    • 关联成员: RandomSource.createThreadLocalInstance()
  • Entity

    • 引用位置: 参数
  • Vec2

    • 引用位置: 参数