* try to resolve merge conflict
* feat: TU19 (Dec 2014) Features & Content (#32)
* December 2014 files
* Working release build
* Fix compilation issues
* Add sound to Windows64Media
* Add DLC content and force Tutorial DLC
* Revert "Add DLC content and force Tutorial DLC"
This reverts commit 97a4399472.
* Disable broken light packing
* Disable breakpoint during DLC texture map load
Allows DLC loading but the DLC textures are still broken
* Fix post build not working
* ...
* fix vs2022 build
* fix cmake build
---------
Co-authored-by: Loki <lokirautio@gmail.com>
328 lines
11 KiB
C
328 lines
11 KiB
C
/*
|
|
package net.minecraft.commands.common;
|
|
|
|
import java.util.*;
|
|
|
|
import net.minecraft.commands.*;
|
|
import net.minecraft.commands.exceptions.*;
|
|
import net.minecraft.network.chat.ChatMessageComponent;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.material.Material;
|
|
import net.minecraft.world.level.tile.Tile;
|
|
import net.minecraft.world.scores.Team;
|
|
|
|
import com.google.common.collect.*;
|
|
|
|
public class SpreadPlayersCommand extends BaseCommand {
|
|
private static final int MAX_ITERATION_COUNT = 10000;
|
|
|
|
@Override
|
|
public String getName() {
|
|
return "spreadplayers";
|
|
}
|
|
|
|
@Override
|
|
public int getPermissionLevel() {
|
|
return LEVEL_GAMEMASTERS;
|
|
}
|
|
|
|
@Override
|
|
public String getUsage(CommandSender source) {
|
|
return "commands.spreadplayers.usage";
|
|
}
|
|
|
|
@Override
|
|
public void execute(CommandSender source, String[] args) {
|
|
if (args.length < 6) throw new UsageException("commands.spreadplayers.usage");
|
|
int index = 0;
|
|
double x = convertArgToCoordinate(source, Double.NaN, args[index++]);
|
|
double z = convertArgToCoordinate(source, Double.NaN, args[index++]);
|
|
double minDist = convertArgToDouble(source, args[index++], 0);
|
|
double maxDist = convertArgToDouble(source, args[index++], minDist + 1);
|
|
boolean respectTeams = convertArgToBoolean(source, args[index++]);
|
|
|
|
List<LivingEntity> players = Lists.newArrayList();
|
|
|
|
while (index < args.length) {
|
|
String arg = args[index++];
|
|
|
|
if (PlayerSelector.isPattern(arg)) {
|
|
ServerPlayer[] result = PlayerSelector.getPlayers(source, arg);
|
|
|
|
if (result != null && result.length != 0) {
|
|
Collections.addAll(players, result);
|
|
} else {
|
|
throw new PlayerNotFoundException();
|
|
}
|
|
} else {
|
|
Player player = MinecraftServer.getInstance().getPlayers().getPlayer(arg);
|
|
|
|
if (player != null) {
|
|
players.add(player);
|
|
} else {
|
|
throw new PlayerNotFoundException();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (players.isEmpty()) {
|
|
throw new PlayerNotFoundException();
|
|
}
|
|
|
|
source.sendMessage(ChatMessageComponent.forTranslation("commands.spreadplayers.spreading." + (respectTeams ? "teams" : "players"), joinPlayerNames(players), x, z, minDist, maxDist));
|
|
|
|
spreadPlayers(source, players, new Position(x, z), minDist, maxDist, players.get(0).level, respectTeams);
|
|
}
|
|
|
|
private void spreadPlayers(CommandSender source, List<LivingEntity> players, Position center, double spreadDist, double maxDistFromCenter, Level level, boolean respectTeams) {
|
|
Random random = new Random();
|
|
double minX = center.x - maxDistFromCenter;
|
|
double minZ = center.z - maxDistFromCenter;
|
|
double maxX = center.x + maxDistFromCenter;
|
|
double maxZ = center.z + maxDistFromCenter;
|
|
|
|
Position[] positions = createInitialPositions(random, respectTeams ? getNumberOfTeams(players) : players.size(), minX, minZ, maxX, maxZ);
|
|
int iterations = spreadPositions(center, spreadDist, level, random, minX, minZ, maxX, maxZ, positions, respectTeams);
|
|
double avgDistance = setPlayerPositions(players, level, positions, respectTeams);
|
|
|
|
logAdminAction(source, "commands.spreadplayers.success." + (respectTeams ? "teams" : "players"), positions.length, center.x, center.z);
|
|
if (positions.length > 1) source.sendMessage(ChatMessageComponent.forTranslation("commands.spreadplayers.info." + (respectTeams ? "teams" : "players"), String.format("%.2f", avgDistance),
|
|
iterations));
|
|
}
|
|
|
|
private int getNumberOfTeams(List<LivingEntity> players) {
|
|
Set<Team> teams = Sets.newHashSet();
|
|
|
|
for (LivingEntity player : players) {
|
|
if (player instanceof Player) {
|
|
teams.add(((Player) player).getTeam());
|
|
} else {
|
|
teams.add(null);
|
|
}
|
|
}
|
|
|
|
return teams.size();
|
|
}
|
|
|
|
private int spreadPositions(Position center, double spreadDist, Level level, Random random, double minX, double minZ, double maxX, double maxZ, Position[] positions, boolean respectTeams) {
|
|
boolean hasCollisions = true;
|
|
int iteration;
|
|
double minDistance = Float.MAX_VALUE;
|
|
|
|
for (iteration = 0; iteration < MAX_ITERATION_COUNT && hasCollisions; iteration++) {
|
|
hasCollisions = false;
|
|
minDistance = Float.MAX_VALUE;
|
|
|
|
for (int i = 0; i < positions.length; i++) {
|
|
Position position = positions[i];
|
|
int neighbourCount = 0;
|
|
Position averageNeighbourPos = new Position();
|
|
|
|
for (int j = 0; j < positions.length; j++) {
|
|
if (i == j) continue;
|
|
Position neighbour = positions[j];
|
|
|
|
double dist = position.dist(neighbour);
|
|
minDistance = Math.min(dist, minDistance);
|
|
if (dist < spreadDist) {
|
|
neighbourCount++;
|
|
averageNeighbourPos.x += neighbour.x - position.x;
|
|
averageNeighbourPos.z += neighbour.z - position.z;
|
|
}
|
|
}
|
|
|
|
if (neighbourCount > 0) {
|
|
averageNeighbourPos.x /= neighbourCount;
|
|
averageNeighbourPos.z /= neighbourCount;
|
|
double length = averageNeighbourPos.getLength();
|
|
|
|
if (length > 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 (Position position : positions) {
|
|
if (!position.isSafe(level)) {
|
|
position.randomize(random, minX, minZ, maxX, maxZ);
|
|
hasCollisions = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iteration >= MAX_ITERATION_COUNT) {
|
|
throw new CommandException("commands.spreadplayers.failure." + (respectTeams ? "teams" : "players"), positions.length, center.x, center.z, String.format("%.2f", minDistance));
|
|
}
|
|
|
|
return iteration;
|
|
}
|
|
|
|
private double setPlayerPositions(List<LivingEntity> players, Level level, Position[] positions, boolean respectTeams) {
|
|
double avgDistance = 0;
|
|
int positionIndex = 0;
|
|
Map<Team, Position> teamPositions = Maps.newHashMap();
|
|
|
|
for (int i = 0; i < players.size(); i++) {
|
|
LivingEntity player = players.get(i);
|
|
Position position;
|
|
|
|
if (respectTeams) {
|
|
Team team = player instanceof Player ? ((Player) player).getTeam() : null;
|
|
|
|
if (!teamPositions.containsKey(team)) {
|
|
teamPositions.put(team, positions[positionIndex++]);
|
|
}
|
|
|
|
position = teamPositions.get(team);
|
|
} else {
|
|
position = positions[positionIndex++];
|
|
}
|
|
|
|
player.teleportTo(Mth.floor(position.x) + 0.5f, position.getSpawnY(level), Mth.floor(position.z) + 0.5);
|
|
|
|
double closest = Double.MAX_VALUE;
|
|
for (int j = 0; j < positions.length; j++) {
|
|
if (position == positions[j]) continue;
|
|
|
|
double dist = position.dist(positions[j]);
|
|
closest = Math.min(dist, closest);
|
|
}
|
|
avgDistance += closest;
|
|
}
|
|
|
|
avgDistance /= players.size();
|
|
return avgDistance;
|
|
}
|
|
|
|
private Position[] createInitialPositions(Random random, int count, double minX, double minZ, double maxX, double maxZ) {
|
|
Position[] result = new Position[count];
|
|
|
|
for (int i = 0; i < result.length; i++) {
|
|
Position position = new Position();
|
|
|
|
position.randomize(random, minX, minZ, maxX, maxZ);
|
|
|
|
result[i] = position;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static class Position {
|
|
double x;
|
|
double z;
|
|
|
|
Position() {
|
|
}
|
|
|
|
Position(double x, double z) {
|
|
this.x = x;
|
|
this.z = z;
|
|
}
|
|
|
|
void set(double x, double z) {
|
|
this.x = x;
|
|
this.z = z;
|
|
}
|
|
|
|
double dist(Position target) {
|
|
double dx = x - target.x;
|
|
double dz = z - target.z;
|
|
|
|
return Math.sqrt(dx * dx + dz * dz);
|
|
}
|
|
|
|
void normalize() {
|
|
double dist = (double) getLength();
|
|
x /= dist;
|
|
z /= dist;
|
|
}
|
|
|
|
float getLength() {
|
|
return Mth.sqrt(x * x + z * z);
|
|
}
|
|
|
|
public void moveAway(Position pos) {
|
|
x -= pos.x;
|
|
z -= pos.z;
|
|
}
|
|
|
|
public boolean clamp(double minX, double minZ, double maxX, double maxZ) {
|
|
boolean changed = false;
|
|
|
|
if (x < minX) {
|
|
x = minX;
|
|
changed = true;
|
|
} else if (x > maxX) {
|
|
x = maxX;
|
|
changed = true;
|
|
}
|
|
|
|
if (z < minZ) {
|
|
z = minZ;
|
|
changed = true;
|
|
} else if (z > maxZ) {
|
|
z = maxZ;
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
public int getSpawnY(Level level) {
|
|
int xt = Mth.floor(x);
|
|
int zt = Mth.floor(z);
|
|
|
|
for (int y = Level.maxBuildHeight; y > 0; y--) {
|
|
int tile = level.getTile(xt, y, zt);
|
|
|
|
if (tile != 0) {
|
|
return y + 1;
|
|
}
|
|
}
|
|
|
|
return Level.maxBuildHeight + 1;
|
|
}
|
|
|
|
public boolean isSafe(Level level) {
|
|
int xt = Mth.floor(x);
|
|
int zt = Mth.floor(z);
|
|
|
|
for (int y = Level.maxBuildHeight; y > 0; y--) {
|
|
int tile = level.getTile(xt, y, zt);
|
|
|
|
if (tile != 0) {
|
|
Material material = Tile.tiles[tile].material;
|
|
|
|
return !material.isLiquid() && material != Material.fire;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void randomize(Random random, double minX, double minZ, double maxX, double maxZ) {
|
|
x = Mth.nextDouble(random, minX, maxX);
|
|
z = Mth.nextDouble(random, minZ, maxZ);
|
|
}
|
|
}
|
|
}
|
|
|
|
*/ |