minecraft-src/net/minecraft/server/commands/SpreadPlayersCommand.java
2025-07-04 02:00:41 +03:00

353 lines
12 KiB
Java

package net.minecraft.server.commands;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
import com.mojang.brigadier.exceptions.Dynamic4CommandExceptionType;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.coordinates.Vec2Argument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.scores.Team;
public class SpreadPlayersCommand {
private static final int MAX_ITERATION_COUNT = 10000;
private static final Dynamic4CommandExceptionType ERROR_FAILED_TO_SPREAD_TEAMS = new Dynamic4CommandExceptionType(
(object, object2, object3, object4) -> Component.translatableEscape("commands.spreadplayers.failed.teams", object, object2, object3, object4)
);
private static final Dynamic4CommandExceptionType ERROR_FAILED_TO_SPREAD_ENTITIES = new Dynamic4CommandExceptionType(
(object, object2, object3, object4) -> Component.translatableEscape("commands.spreadplayers.failed.entities", object, object2, object3, object4)
);
private static final Dynamic2CommandExceptionType ERROR_INVALID_MAX_HEIGHT = new Dynamic2CommandExceptionType(
(object, object2) -> Component.translatableEscape("commands.spreadplayers.failed.invalid.height", object, object2)
);
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(
Commands.literal("spreadplayers")
.requires(commandSourceStack -> commandSourceStack.hasPermission(2))
.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(
commandContext -> spreadPlayers(
commandContext.getSource(),
Vec2Argument.getVec2(commandContext, "center"),
FloatArgumentType.getFloat(commandContext, "spreadDistance"),
FloatArgumentType.getFloat(commandContext, "maxRange"),
commandContext.getSource().getLevel().getMaxY() + 1,
BoolArgumentType.getBool(commandContext, "respectTeams"),
EntityArgument.getEntities(commandContext, "targets")
)
)
)
)
.then(
Commands.literal("under")
.then(
Commands.argument("maxHeight", IntegerArgumentType.integer())
.then(
Commands.argument("respectTeams", BoolArgumentType.bool())
.then(
Commands.argument("targets", EntityArgument.entities())
.executes(
commandContext -> spreadPlayers(
commandContext.getSource(),
Vec2Argument.getVec2(commandContext, "center"),
FloatArgumentType.getFloat(commandContext, "spreadDistance"),
FloatArgumentType.getFloat(commandContext, "maxRange"),
IntegerArgumentType.getInteger(commandContext, "maxHeight"),
BoolArgumentType.getBool(commandContext, "respectTeams"),
EntityArgument.getEntities(commandContext, "targets")
)
)
)
)
)
)
)
)
)
);
}
private static int spreadPlayers(
CommandSourceStack source, Vec2 center, float spreadDistance, float maxRange, int maxHeight, boolean respectTeams, Collection<? extends Entity> targets
) throws CommandSyntaxException {
ServerLevel serverLevel = source.getLevel();
int i = serverLevel.getMinY();
if (maxHeight < i) {
throw ERROR_INVALID_MAX_HEIGHT.create(maxHeight, i);
} else {
RandomSource randomSource = RandomSource.create();
double d = center.x - maxRange;
double e = center.y - maxRange;
double f = center.x + maxRange;
double g = center.y + maxRange;
SpreadPlayersCommand.Position[] positions = createInitialPositions(randomSource, respectTeams ? getNumberOfTeams(targets) : targets.size(), d, e, f, g);
spreadPositions(center, spreadDistance, serverLevel, randomSource, d, e, f, g, maxHeight, positions, respectTeams);
double h = setPlayerPositions(targets, serverLevel, positions, maxHeight, respectTeams);
source.sendSuccess(
() -> Component.translatable(
"commands.spreadplayers.success." + (respectTeams ? "teams" : "entities"), positions.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", h)
),
true
);
return positions.length;
}
}
/**
* Gets the number of unique teams for the given list of entities.
*/
private static int getNumberOfTeams(Collection<? extends Entity> entities) {
Set<Team> set = Sets.<Team>newHashSet();
for (Entity entity : entities) {
if (entity instanceof Player) {
set.add(entity.getTeam());
} else {
set.add(null);
}
}
return set.size();
}
private static void spreadPositions(
Vec2 center,
double spreadDistance,
ServerLevel level,
RandomSource random,
double minX,
double minZ,
double maxX,
double maxZ,
int maxHeight,
SpreadPlayersCommand.Position[] positions,
boolean respectTeams
) throws CommandSyntaxException {
boolean bl = true;
double d = Float.MAX_VALUE;
int i;
for (i = 0; i < 10000 && bl; i++) {
bl = false;
d = Float.MAX_VALUE;
for (int j = 0; j < positions.length; j++) {
SpreadPlayersCommand.Position position = positions[j];
int k = 0;
SpreadPlayersCommand.Position position2 = new SpreadPlayersCommand.Position();
for (int l = 0; l < positions.length; l++) {
if (j != l) {
SpreadPlayersCommand.Position position3 = positions[l];
double e = position.dist(position3);
d = Math.min(e, d);
if (e < spreadDistance) {
k++;
position2.x = position2.x + (position3.x - position.x);
position2.z = position2.z + (position3.z - position.z);
}
}
}
if (k > 0) {
position2.x /= k;
position2.z /= k;
double f = position2.getLength();
if (f > 0.0) {
position2.normalize();
position.moveAway(position2);
} else {
position.randomize(random, minX, minZ, maxX, maxZ);
}
bl = true;
}
if (position.clamp(minX, minZ, maxX, maxZ)) {
bl = true;
}
}
if (!bl) {
for (SpreadPlayersCommand.Position position2 : positions) {
if (!position2.isSafe(level, maxHeight)) {
position2.randomize(random, minX, minZ, maxX, maxZ);
bl = true;
}
}
}
}
if (d == Float.MAX_VALUE) {
d = 0.0;
}
if (i >= 10000) {
if (respectTeams) {
throw ERROR_FAILED_TO_SPREAD_TEAMS.create(positions.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", d));
} else {
throw ERROR_FAILED_TO_SPREAD_ENTITIES.create(positions.length, center.x, center.y, String.format(Locale.ROOT, "%.2f", d));
}
}
}
private static double setPlayerPositions(
Collection<? extends Entity> targets, ServerLevel level, SpreadPlayersCommand.Position[] positions, int maxHeight, boolean respectTeams
) {
double d = 0.0;
int i = 0;
Map<Team, SpreadPlayersCommand.Position> map = Maps.<Team, SpreadPlayersCommand.Position>newHashMap();
for (Entity entity : targets) {
SpreadPlayersCommand.Position position;
if (respectTeams) {
Team team = entity instanceof Player ? entity.getTeam() : null;
if (!map.containsKey(team)) {
map.put(team, positions[i++]);
}
position = (SpreadPlayersCommand.Position)map.get(team);
} else {
position = positions[i++];
}
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 e = Double.MAX_VALUE;
for (SpreadPlayersCommand.Position position2 : positions) {
if (position != position2) {
double f = position.dist(position2);
e = Math.min(f, e);
}
}
d += e;
}
return targets.size() < 2 ? 0.0 : d / targets.size();
}
private static SpreadPlayersCommand.Position[] createInitialPositions(RandomSource random, int count, double minX, double minZ, double maxX, double maxZ) {
SpreadPlayersCommand.Position[] positions = new SpreadPlayersCommand.Position[count];
for (int i = 0; i < positions.length; i++) {
SpreadPlayersCommand.Position position = new SpreadPlayersCommand.Position();
position.randomize(random, minX, minZ, maxX, maxZ);
positions[i] = position;
}
return positions;
}
static class Position {
double x;
double z;
double dist(SpreadPlayersCommand.Position other) {
double d = this.x - other.x;
double e = this.z - other.z;
return Math.sqrt(d * d + e * e);
}
void normalize() {
double d = this.getLength();
this.x /= d;
this.z /= d;
}
double getLength() {
return Math.sqrt(this.x * this.x + this.z * this.z);
}
public void moveAway(SpreadPlayersCommand.Position other) {
this.x = this.x - other.x;
this.z = this.z - other.z;
}
public boolean clamp(double minX, double minZ, double maxX, double maxZ) {
boolean bl = false;
if (this.x < minX) {
this.x = minX;
bl = true;
} else if (this.x > maxX) {
this.x = maxX;
bl = true;
}
if (this.z < minZ) {
this.z = minZ;
bl = true;
} else if (this.z > maxZ) {
this.z = maxZ;
bl = true;
}
return bl;
}
public int getSpawnY(BlockGetter level, int y) {
BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(this.x, (double)(y + 1), this.z);
boolean bl = level.getBlockState(mutableBlockPos).isAir();
mutableBlockPos.move(Direction.DOWN);
boolean bl2 = level.getBlockState(mutableBlockPos).isAir();
while (mutableBlockPos.getY() > level.getMinY()) {
mutableBlockPos.move(Direction.DOWN);
boolean bl3 = level.getBlockState(mutableBlockPos).isAir();
if (!bl3 && bl2 && bl) {
return mutableBlockPos.getY() + 1;
}
bl = bl2;
bl2 = bl3;
}
return y + 1;
}
public boolean isSafe(BlockGetter level, int y) {
BlockPos blockPos = BlockPos.containing(this.x, this.getSpawnY(level, y) - 1, this.z);
BlockState blockState = level.getBlockState(blockPos);
return blockPos.getY() < y && !blockState.liquid() && !blockState.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);
}
}
}