900 lines
34 KiB
Java
900 lines
34 KiB
Java
package net.minecraft.server.players;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.collect.Sets;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.Dynamic;
|
|
import java.io.File;
|
|
import java.net.SocketAddress;
|
|
import java.nio.file.Path;
|
|
import java.text.SimpleDateFormat;
|
|
import java.time.Instant;
|
|
import java.util.EnumSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.function.Function;
|
|
import java.util.function.Predicate;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.FileUtil;
|
|
import net.minecraft.commands.CommandSourceStack;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.LayeredRegistryAccess;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.network.Connection;
|
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.network.chat.MutableComponent;
|
|
import net.minecraft.network.chat.OutgoingChatMessage;
|
|
import net.minecraft.network.chat.PlayerChatMessage;
|
|
import net.minecraft.network.chat.ChatType.Bound;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundInitializeBorderPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundRespawnPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
|
|
import net.minecraft.network.protocol.game.GameProtocols;
|
|
import net.minecraft.network.protocol.status.ServerStatus;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.PlayerAdvancements;
|
|
import net.minecraft.server.RegistryLayer;
|
|
import net.minecraft.server.ServerScoreboard;
|
|
import net.minecraft.server.level.ClientInformation;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.server.network.CommonListenerCookie;
|
|
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.sounds.SoundSource;
|
|
import net.minecraft.stats.ServerStatsCounter;
|
|
import net.minecraft.stats.Stats;
|
|
import net.minecraft.tags.TagNetworkSerialization;
|
|
import net.minecraft.world.effect.MobEffectInstance;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.entity.projectile.ThrownEnderpearl;
|
|
import net.minecraft.world.item.crafting.RecipeManager;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.border.BorderChangeListener;
|
|
import net.minecraft.world.level.border.WorldBorder;
|
|
import net.minecraft.world.level.dimension.DimensionType;
|
|
import net.minecraft.world.level.portal.TeleportTransition;
|
|
import net.minecraft.world.level.storage.LevelData;
|
|
import net.minecraft.world.level.storage.LevelResource;
|
|
import net.minecraft.world.level.storage.PlayerDataStorage;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraft.world.scores.DisplaySlot;
|
|
import net.minecraft.world.scores.Objective;
|
|
import net.minecraft.world.scores.PlayerTeam;
|
|
import net.minecraft.world.scores.Team;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public abstract class PlayerList {
|
|
public static final File USERBANLIST_FILE = new File("banned-players.json");
|
|
public static final File IPBANLIST_FILE = new File("banned-ips.json");
|
|
public static final File OPLIST_FILE = new File("ops.json");
|
|
public static final File WHITELIST_FILE = new File("whitelist.json");
|
|
public static final Component CHAT_FILTERED_FULL = Component.translatable("chat.filtered_full");
|
|
public static final Component DUPLICATE_LOGIN_DISCONNECT_MESSAGE = Component.translatable("multiplayer.disconnect.duplicate_login");
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int SEND_PLAYER_INFO_INTERVAL = 600;
|
|
private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
|
|
private final MinecraftServer server;
|
|
private final List<ServerPlayer> players = Lists.<ServerPlayer>newArrayList();
|
|
/**
|
|
* A map containing the key-value pairs for UUIDs and their EntityPlayerMP objects.
|
|
*/
|
|
private final Map<UUID, ServerPlayer> playersByUUID = Maps.<UUID, ServerPlayer>newHashMap();
|
|
private final UserBanList bans = new UserBanList(USERBANLIST_FILE);
|
|
private final IpBanList ipBans = new IpBanList(IPBANLIST_FILE);
|
|
private final ServerOpList ops = new ServerOpList(OPLIST_FILE);
|
|
private final UserWhiteList whitelist = new UserWhiteList(WHITELIST_FILE);
|
|
private final Map<UUID, ServerStatsCounter> stats = Maps.<UUID, ServerStatsCounter>newHashMap();
|
|
private final Map<UUID, PlayerAdvancements> advancements = Maps.<UUID, PlayerAdvancements>newHashMap();
|
|
private final PlayerDataStorage playerIo;
|
|
private boolean doWhiteList;
|
|
private final LayeredRegistryAccess<RegistryLayer> registries;
|
|
protected final int maxPlayers;
|
|
private int viewDistance;
|
|
private int simulationDistance;
|
|
private boolean allowCommandsForAllPlayers;
|
|
private static final boolean ALLOW_LOGOUTIVATOR = false;
|
|
private int sendAllPlayerInfoIn;
|
|
|
|
public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registries, PlayerDataStorage playerIo, int maxPlayers) {
|
|
this.server = server;
|
|
this.registries = registries;
|
|
this.maxPlayers = maxPlayers;
|
|
this.playerIo = playerIo;
|
|
}
|
|
|
|
public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) {
|
|
GameProfile gameProfile = player.getGameProfile();
|
|
GameProfileCache gameProfileCache = this.server.getProfileCache();
|
|
String string;
|
|
if (gameProfileCache != null) {
|
|
Optional<GameProfile> optional = gameProfileCache.get(gameProfile.getId());
|
|
string = (String)optional.map(GameProfile::getName).orElse(gameProfile.getName());
|
|
gameProfileCache.add(gameProfile);
|
|
} else {
|
|
string = gameProfile.getName();
|
|
}
|
|
|
|
Optional<CompoundTag> optional = this.load(player);
|
|
ResourceKey<Level> resourceKey = (ResourceKey<Level>)optional.flatMap(
|
|
compoundTag -> DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundTag.get("Dimension"))).resultOrPartial(LOGGER::error)
|
|
)
|
|
.orElse(Level.OVERWORLD);
|
|
ServerLevel serverLevel = this.server.getLevel(resourceKey);
|
|
ServerLevel serverLevel2;
|
|
if (serverLevel == null) {
|
|
LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourceKey);
|
|
serverLevel2 = this.server.overworld();
|
|
} else {
|
|
serverLevel2 = serverLevel;
|
|
}
|
|
|
|
player.setServerLevel(serverLevel2);
|
|
String string2 = connection.getLoggableAddress(this.server.logIPs());
|
|
LOGGER.info(
|
|
"{}[{}] logged in with entity id {} at ({}, {}, {})", player.getName().getString(), string2, player.getId(), player.getX(), player.getY(), player.getZ()
|
|
);
|
|
LevelData levelData = serverLevel2.getLevelData();
|
|
player.loadGameTypes((CompoundTag)optional.orElse(null));
|
|
ServerGamePacketListenerImpl serverGamePacketListenerImpl = new ServerGamePacketListenerImpl(this.server, connection, player, cookie);
|
|
connection.setupInboundProtocol(
|
|
GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), serverGamePacketListenerImpl),
|
|
serverGamePacketListenerImpl
|
|
);
|
|
GameRules gameRules = serverLevel2.getGameRules();
|
|
boolean bl = gameRules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN);
|
|
boolean bl2 = gameRules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
|
|
boolean bl3 = gameRules.getBoolean(GameRules.RULE_LIMITED_CRAFTING);
|
|
serverGamePacketListenerImpl.send(
|
|
new ClientboundLoginPacket(
|
|
player.getId(),
|
|
levelData.isHardcore(),
|
|
this.server.levelKeys(),
|
|
this.getMaxPlayers(),
|
|
this.viewDistance,
|
|
this.simulationDistance,
|
|
bl2,
|
|
!bl,
|
|
bl3,
|
|
player.createCommonSpawnInfo(serverLevel2),
|
|
this.server.enforceSecureProfile()
|
|
)
|
|
);
|
|
serverGamePacketListenerImpl.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
|
|
serverGamePacketListenerImpl.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities()));
|
|
serverGamePacketListenerImpl.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot()));
|
|
RecipeManager recipeManager = this.server.getRecipeManager();
|
|
serverGamePacketListenerImpl.send(
|
|
new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes())
|
|
);
|
|
this.sendPlayerPermissionLevel(player);
|
|
player.getStats().markAllDirty();
|
|
player.getRecipeBook().sendInitialRecipeBook(player);
|
|
this.updateEntireScoreboard(serverLevel2.getScoreboard(), player);
|
|
this.server.invalidateStatus();
|
|
MutableComponent mutableComponent;
|
|
if (player.getGameProfile().getName().equalsIgnoreCase(string)) {
|
|
mutableComponent = Component.translatable("multiplayer.player.joined", player.getDisplayName());
|
|
} else {
|
|
mutableComponent = Component.translatable("multiplayer.player.joined.renamed", player.getDisplayName(), string);
|
|
}
|
|
|
|
this.broadcastSystemMessage(mutableComponent.withStyle(ChatFormatting.YELLOW), false);
|
|
serverGamePacketListenerImpl.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
|
|
ServerStatus serverStatus = this.server.getStatus();
|
|
if (serverStatus != null && !cookie.transferred()) {
|
|
player.sendServerStatus(serverStatus);
|
|
}
|
|
|
|
player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players));
|
|
this.players.add(player);
|
|
this.playersByUUID.put(player.getUUID(), player);
|
|
this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)));
|
|
this.sendLevelInfo(player, serverLevel2);
|
|
serverLevel2.addNewPlayer(player);
|
|
this.server.getCustomBossEvents().onPlayerConnect(player);
|
|
this.sendActivePlayerEffects(player);
|
|
optional.ifPresent(compoundTag -> {
|
|
player.loadAndSpawnEnderPearls(compoundTag);
|
|
player.loadAndSpawnParentVehicle(compoundTag);
|
|
});
|
|
player.initInventoryMenu();
|
|
}
|
|
|
|
protected void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) {
|
|
Set<Objective> set = Sets.<Objective>newHashSet();
|
|
|
|
for (PlayerTeam playerTeam : scoreboard.getPlayerTeams()) {
|
|
player.connection.send(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true));
|
|
}
|
|
|
|
for (DisplaySlot displaySlot : DisplaySlot.values()) {
|
|
Objective objective = scoreboard.getDisplayObjective(displaySlot);
|
|
if (objective != null && !set.contains(objective)) {
|
|
for (Packet<?> packet : scoreboard.getStartTrackingPackets(objective)) {
|
|
player.connection.send(packet);
|
|
}
|
|
|
|
set.add(objective);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void addWorldborderListener(ServerLevel level) {
|
|
level.getWorldBorder().addListener(new BorderChangeListener() {
|
|
@Override
|
|
public void onBorderSizeSet(WorldBorder border, double size) {
|
|
PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSizeLerping(WorldBorder border, double oldSize, double newSize, long time) {
|
|
PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderCenterSet(WorldBorder border, double x, double z) {
|
|
PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
|
|
PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetWarningBlocks(WorldBorder border, int warningBlocks) {
|
|
PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetDamageSafeZOne(WorldBorder border, double damageSafeZone) {
|
|
}
|
|
});
|
|
}
|
|
|
|
public Optional<CompoundTag> load(ServerPlayer player) {
|
|
CompoundTag compoundTag = this.server.getWorldData().getLoadedPlayerTag();
|
|
Optional<CompoundTag> optional;
|
|
if (this.server.isSingleplayerOwner(player.getGameProfile()) && compoundTag != null) {
|
|
optional = Optional.of(compoundTag);
|
|
player.load(compoundTag);
|
|
LOGGER.debug("loading single player");
|
|
} else {
|
|
optional = this.playerIo.load(player);
|
|
}
|
|
|
|
return optional;
|
|
}
|
|
|
|
/**
|
|
* Also stores the NBTTags if this is an IntegratedPlayerList.
|
|
*/
|
|
protected void save(ServerPlayer player) {
|
|
this.playerIo.save(player);
|
|
ServerStatsCounter serverStatsCounter = (ServerStatsCounter)this.stats.get(player.getUUID());
|
|
if (serverStatsCounter != null) {
|
|
serverStatsCounter.save();
|
|
}
|
|
|
|
PlayerAdvancements playerAdvancements = (PlayerAdvancements)this.advancements.get(player.getUUID());
|
|
if (playerAdvancements != null) {
|
|
playerAdvancements.save();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a player disconnects from the game. Writes player data to disk and removes them from the world.
|
|
*/
|
|
public void remove(ServerPlayer player) {
|
|
ServerLevel serverLevel = player.serverLevel();
|
|
player.awardStat(Stats.LEAVE_GAME);
|
|
this.save(player);
|
|
if (player.isPassenger()) {
|
|
Entity entity = player.getRootVehicle();
|
|
if (entity.hasExactlyOnePlayerPassenger()) {
|
|
LOGGER.debug("Removing player mount");
|
|
player.stopRiding();
|
|
entity.getPassengersAndSelf().forEach(entityx -> entityx.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER));
|
|
}
|
|
}
|
|
|
|
player.unRide();
|
|
|
|
for (ThrownEnderpearl thrownEnderpearl : player.getEnderPearls()) {
|
|
thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
|
|
}
|
|
|
|
serverLevel.removePlayerImmediately(player, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
|
|
player.getAdvancements().stopListening();
|
|
this.players.remove(player);
|
|
this.server.getCustomBossEvents().onPlayerDisconnect(player);
|
|
UUID uUID = player.getUUID();
|
|
ServerPlayer serverPlayer = (ServerPlayer)this.playersByUUID.get(uUID);
|
|
if (serverPlayer == player) {
|
|
this.playersByUUID.remove(uUID);
|
|
this.stats.remove(uUID);
|
|
this.advancements.remove(uUID);
|
|
}
|
|
|
|
this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID())));
|
|
}
|
|
|
|
@Nullable
|
|
public Component canPlayerLogin(SocketAddress socketAddress, GameProfile gameProfile) {
|
|
if (this.bans.isBanned(gameProfile)) {
|
|
UserBanListEntry userBanListEntry = this.bans.get(gameProfile);
|
|
MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason());
|
|
if (userBanListEntry.getExpires() != null) {
|
|
mutableComponent.append(Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(userBanListEntry.getExpires())));
|
|
}
|
|
|
|
return mutableComponent;
|
|
} else if (!this.isWhiteListed(gameProfile)) {
|
|
return Component.translatable("multiplayer.disconnect.not_whitelisted");
|
|
} else if (this.ipBans.isBanned(socketAddress)) {
|
|
IpBanListEntry ipBanListEntry = this.ipBans.get(socketAddress);
|
|
MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipBanListEntry.getReason());
|
|
if (ipBanListEntry.getExpires() != null) {
|
|
mutableComponent.append(Component.translatable("multiplayer.disconnect.banned_ip.expiration", BAN_DATE_FORMAT.format(ipBanListEntry.getExpires())));
|
|
}
|
|
|
|
return mutableComponent;
|
|
} else {
|
|
return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)
|
|
? Component.translatable("multiplayer.disconnect.server_full")
|
|
: null;
|
|
}
|
|
}
|
|
|
|
public ServerPlayer getPlayerForLogin(GameProfile gameProfile, ClientInformation clientInformation) {
|
|
return new ServerPlayer(this.server, this.server.overworld(), gameProfile, clientInformation);
|
|
}
|
|
|
|
public boolean disconnectAllPlayersWithProfile(GameProfile gameProfile) {
|
|
UUID uUID = gameProfile.getId();
|
|
Set<ServerPlayer> set = Sets.newIdentityHashSet();
|
|
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
if (serverPlayer.getUUID().equals(uUID)) {
|
|
set.add(serverPlayer);
|
|
}
|
|
}
|
|
|
|
ServerPlayer serverPlayer2 = (ServerPlayer)this.playersByUUID.get(gameProfile.getId());
|
|
if (serverPlayer2 != null) {
|
|
set.add(serverPlayer2);
|
|
}
|
|
|
|
for (ServerPlayer serverPlayer3 : set) {
|
|
serverPlayer3.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE);
|
|
}
|
|
|
|
return !set.isEmpty();
|
|
}
|
|
|
|
public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason) {
|
|
this.players.remove(player);
|
|
player.serverLevel().removePlayerImmediately(player, reason);
|
|
TeleportTransition teleportTransition = player.findRespawnPositionAndUseSpawnBlock(!keepInventory, TeleportTransition.DO_NOTHING);
|
|
ServerLevel serverLevel = teleportTransition.newLevel();
|
|
ServerPlayer serverPlayer = new ServerPlayer(this.server, serverLevel, player.getGameProfile(), player.clientInformation());
|
|
serverPlayer.connection = player.connection;
|
|
serverPlayer.restoreFrom(player, keepInventory);
|
|
serverPlayer.setId(player.getId());
|
|
serverPlayer.setMainArm(player.getMainArm());
|
|
if (!teleportTransition.missingRespawnBlock()) {
|
|
serverPlayer.copyRespawnPosition(player);
|
|
}
|
|
|
|
for (String string : player.getTags()) {
|
|
serverPlayer.addTag(string);
|
|
}
|
|
|
|
Vec3 vec3 = teleportTransition.position();
|
|
serverPlayer.snapTo(vec3.x, vec3.y, vec3.z, teleportTransition.yRot(), teleportTransition.xRot());
|
|
if (teleportTransition.missingRespawnBlock()) {
|
|
serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
|
|
}
|
|
|
|
byte b = (byte)(keepInventory ? 1 : 0);
|
|
ServerLevel serverLevel2 = serverPlayer.serverLevel();
|
|
LevelData levelData = serverLevel2.getLevelData();
|
|
serverPlayer.connection.send(new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(serverLevel2), b));
|
|
serverPlayer.connection.teleport(serverPlayer.getX(), serverPlayer.getY(), serverPlayer.getZ(), serverPlayer.getYRot(), serverPlayer.getXRot());
|
|
serverPlayer.connection.send(new ClientboundSetDefaultSpawnPositionPacket(serverLevel.getSharedSpawnPos(), serverLevel.getSharedSpawnAngle()));
|
|
serverPlayer.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
|
|
serverPlayer.connection.send(new ClientboundSetExperiencePacket(serverPlayer.experienceProgress, serverPlayer.totalExperience, serverPlayer.experienceLevel));
|
|
this.sendActivePlayerEffects(serverPlayer);
|
|
this.sendLevelInfo(serverPlayer, serverLevel);
|
|
this.sendPlayerPermissionLevel(serverPlayer);
|
|
serverLevel.addRespawnedPlayer(serverPlayer);
|
|
this.players.add(serverPlayer);
|
|
this.playersByUUID.put(serverPlayer.getUUID(), serverPlayer);
|
|
serverPlayer.initInventoryMenu();
|
|
serverPlayer.setHealth(serverPlayer.getHealth());
|
|
ServerPlayer.RespawnConfig respawnConfig = serverPlayer.getRespawnConfig();
|
|
if (!keepInventory && respawnConfig != null) {
|
|
ServerLevel serverLevel3 = this.server.getLevel(respawnConfig.dimension());
|
|
if (serverLevel3 != null) {
|
|
BlockPos blockPos = respawnConfig.pos();
|
|
BlockState blockState = serverLevel3.getBlockState(blockPos);
|
|
if (blockState.is(Blocks.RESPAWN_ANCHOR)) {
|
|
serverPlayer.connection
|
|
.send(
|
|
new ClientboundSoundPacket(
|
|
SoundEvents.RESPAWN_ANCHOR_DEPLETE,
|
|
SoundSource.BLOCKS,
|
|
blockPos.getX(),
|
|
blockPos.getY(),
|
|
blockPos.getZ(),
|
|
1.0F,
|
|
1.0F,
|
|
serverLevel.getRandom().nextLong()
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return serverPlayer;
|
|
}
|
|
|
|
public void sendActivePlayerEffects(ServerPlayer player) {
|
|
this.sendActiveEffects(player, player.connection);
|
|
}
|
|
|
|
public void sendActiveEffects(LivingEntity entity, ServerGamePacketListenerImpl connection) {
|
|
for (MobEffectInstance mobEffectInstance : entity.getActiveEffects()) {
|
|
connection.send(new ClientboundUpdateMobEffectPacket(entity.getId(), mobEffectInstance, false));
|
|
}
|
|
}
|
|
|
|
public void sendPlayerPermissionLevel(ServerPlayer player) {
|
|
GameProfile gameProfile = player.getGameProfile();
|
|
int i = this.server.getProfilePermissions(gameProfile);
|
|
this.sendPlayerPermissionLevel(player, i);
|
|
}
|
|
|
|
public void tick() {
|
|
if (++this.sendAllPlayerInfoIn > 600) {
|
|
this.broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players));
|
|
this.sendAllPlayerInfoIn = 0;
|
|
}
|
|
}
|
|
|
|
public void broadcastAll(Packet<?> packet) {
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
serverPlayer.connection.send(packet);
|
|
}
|
|
}
|
|
|
|
public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) {
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
if (serverPlayer.level().dimension() == dimension) {
|
|
serverPlayer.connection.send(packet);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void broadcastSystemToTeam(Player player, Component message) {
|
|
Team team = player.getTeam();
|
|
if (team != null) {
|
|
for (String string : team.getPlayers()) {
|
|
ServerPlayer serverPlayer = this.getPlayerByName(string);
|
|
if (serverPlayer != null && serverPlayer != player) {
|
|
serverPlayer.sendSystemMessage(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void broadcastSystemToAllExceptTeam(Player player, Component message) {
|
|
Team team = player.getTeam();
|
|
if (team == null) {
|
|
this.broadcastSystemMessage(message, false);
|
|
} else {
|
|
for (int i = 0; i < this.players.size(); i++) {
|
|
ServerPlayer serverPlayer = (ServerPlayer)this.players.get(i);
|
|
if (serverPlayer.getTeam() != team) {
|
|
serverPlayer.sendSystemMessage(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an array of the usernames of all the connected players.
|
|
*/
|
|
public String[] getPlayerNamesArray() {
|
|
String[] strings = new String[this.players.size()];
|
|
|
|
for (int i = 0; i < this.players.size(); i++) {
|
|
strings[i] = ((ServerPlayer)this.players.get(i)).getGameProfile().getName();
|
|
}
|
|
|
|
return strings;
|
|
}
|
|
|
|
public UserBanList getBans() {
|
|
return this.bans;
|
|
}
|
|
|
|
public IpBanList getIpBans() {
|
|
return this.ipBans;
|
|
}
|
|
|
|
public void op(GameProfile profile) {
|
|
this.ops.add(new ServerOpListEntry(profile, this.server.getOperatorUserPermissionLevel(), this.ops.canBypassPlayerLimit(profile)));
|
|
ServerPlayer serverPlayer = this.getPlayer(profile.getId());
|
|
if (serverPlayer != null) {
|
|
this.sendPlayerPermissionLevel(serverPlayer);
|
|
}
|
|
}
|
|
|
|
public void deop(GameProfile profile) {
|
|
this.ops.remove(profile);
|
|
ServerPlayer serverPlayer = this.getPlayer(profile.getId());
|
|
if (serverPlayer != null) {
|
|
this.sendPlayerPermissionLevel(serverPlayer);
|
|
}
|
|
}
|
|
|
|
private void sendPlayerPermissionLevel(ServerPlayer player, int permLevel) {
|
|
if (player.connection != null) {
|
|
byte b;
|
|
if (permLevel <= 0) {
|
|
b = 24;
|
|
} else if (permLevel >= 4) {
|
|
b = 28;
|
|
} else {
|
|
b = (byte)(24 + permLevel);
|
|
}
|
|
|
|
player.connection.send(new ClientboundEntityEventPacket(player, b));
|
|
}
|
|
|
|
this.server.getCommands().sendCommands(player);
|
|
}
|
|
|
|
public boolean isWhiteListed(GameProfile profile) {
|
|
return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile);
|
|
}
|
|
|
|
public boolean isOp(GameProfile profile) {
|
|
return this.ops.contains(profile)
|
|
|| this.server.isSingleplayerOwner(profile) && this.server.getWorldData().isAllowCommands()
|
|
|| this.allowCommandsForAllPlayers;
|
|
}
|
|
|
|
@Nullable
|
|
public ServerPlayer getPlayerByName(String username) {
|
|
int i = this.players.size();
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
ServerPlayer serverPlayer = (ServerPlayer)this.players.get(j);
|
|
if (serverPlayer.getGameProfile().getName().equalsIgnoreCase(username)) {
|
|
return serverPlayer;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey<Level> dimension, Packet<?> packet) {
|
|
for (int i = 0; i < this.players.size(); i++) {
|
|
ServerPlayer serverPlayer = (ServerPlayer)this.players.get(i);
|
|
if (serverPlayer != except && serverPlayer.level().dimension() == dimension) {
|
|
double d = x - serverPlayer.getX();
|
|
double e = y - serverPlayer.getY();
|
|
double f = z - serverPlayer.getZ();
|
|
if (d * d + e * e + f * f < radius * radius) {
|
|
serverPlayer.connection.send(packet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves all of the players' current states.
|
|
*/
|
|
public void saveAll() {
|
|
for (int i = 0; i < this.players.size(); i++) {
|
|
this.save((ServerPlayer)this.players.get(i));
|
|
}
|
|
}
|
|
|
|
public UserWhiteList getWhiteList() {
|
|
return this.whitelist;
|
|
}
|
|
|
|
public String[] getWhiteListNames() {
|
|
return this.whitelist.getUserList();
|
|
}
|
|
|
|
public ServerOpList getOps() {
|
|
return this.ops;
|
|
}
|
|
|
|
public String[] getOpNames() {
|
|
return this.ops.getUserList();
|
|
}
|
|
|
|
public void reloadWhiteList() {
|
|
}
|
|
|
|
/**
|
|
* Updates the time and weather for the given player to those of the given world
|
|
*/
|
|
public void sendLevelInfo(ServerPlayer player, ServerLevel level) {
|
|
WorldBorder worldBorder = this.server.overworld().getWorldBorder();
|
|
player.connection.send(new ClientboundInitializeBorderPacket(worldBorder));
|
|
player.connection.send(new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
|
|
player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle()));
|
|
if (level.isRaining()) {
|
|
player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
|
|
player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, level.getRainLevel(1.0F)));
|
|
player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, level.getThunderLevel(1.0F)));
|
|
}
|
|
|
|
player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F));
|
|
this.server.tickRateManager().updateJoiningPlayer(player);
|
|
}
|
|
|
|
/**
|
|
* Sends the players inventory to himself.
|
|
*/
|
|
public void sendAllPlayerInfo(ServerPlayer player) {
|
|
player.inventoryMenu.sendAllDataToRemote();
|
|
player.resetSentInfo();
|
|
player.connection.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot()));
|
|
}
|
|
|
|
/**
|
|
* Returns the number of players currently on the server.
|
|
*/
|
|
public int getPlayerCount() {
|
|
return this.players.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum number of players allowed on the server.
|
|
*/
|
|
public int getMaxPlayers() {
|
|
return this.maxPlayers;
|
|
}
|
|
|
|
public boolean isUsingWhitelist() {
|
|
return this.doWhiteList;
|
|
}
|
|
|
|
public void setUsingWhiteList(boolean whitelistEnabled) {
|
|
this.doWhiteList = whitelistEnabled;
|
|
}
|
|
|
|
public List<ServerPlayer> getPlayersWithAddress(String address) {
|
|
List<ServerPlayer> list = Lists.<ServerPlayer>newArrayList();
|
|
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
if (serverPlayer.getIpAddress().equals(address)) {
|
|
list.add(serverPlayer);
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Gets the view distance, in chunks.
|
|
*/
|
|
public int getViewDistance() {
|
|
return this.viewDistance;
|
|
}
|
|
|
|
public int getSimulationDistance() {
|
|
return this.simulationDistance;
|
|
}
|
|
|
|
public MinecraftServer getServer() {
|
|
return this.server;
|
|
}
|
|
|
|
/**
|
|
* On integrated servers, returns the host's player data to be written to level.dat.
|
|
*/
|
|
@Nullable
|
|
public CompoundTag getSingleplayerData() {
|
|
return null;
|
|
}
|
|
|
|
public void setAllowCommandsForAllPlayers(boolean allowCommandsForAllPlayers) {
|
|
this.allowCommandsForAllPlayers = allowCommandsForAllPlayers;
|
|
}
|
|
|
|
/**
|
|
* Kicks everyone with "Server closed" as reason.
|
|
*/
|
|
public void removeAll() {
|
|
for (int i = 0; i < this.players.size(); i++) {
|
|
((ServerPlayer)this.players.get(i)).connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
|
|
}
|
|
}
|
|
|
|
public void broadcastSystemMessage(Component message, boolean bypassHiddenChat) {
|
|
this.broadcastSystemMessage(message, serverPlayer -> message, bypassHiddenChat);
|
|
}
|
|
|
|
public void broadcastSystemMessage(Component serverMessage, Function<ServerPlayer, Component> playerMessageFactory, boolean bypassHiddenChat) {
|
|
this.server.sendSystemMessage(serverMessage);
|
|
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
Component component = (Component)playerMessageFactory.apply(serverPlayer);
|
|
if (component != null) {
|
|
serverPlayer.sendSystemMessage(component, bypassHiddenChat);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void broadcastChatMessage(PlayerChatMessage message, CommandSourceStack sender, Bound boundChatType) {
|
|
this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender.getPlayer(), boundChatType);
|
|
}
|
|
|
|
public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, Bound boundChatType) {
|
|
this.broadcastChatMessage(message, sender::shouldFilterMessageTo, sender, boundChatType);
|
|
}
|
|
|
|
private void broadcastChatMessage(PlayerChatMessage message, Predicate<ServerPlayer> shouldFilterMessageTo, @Nullable ServerPlayer sender, Bound boundChatType) {
|
|
boolean bl = this.verifyChatTrusted(message);
|
|
this.server.logChatMessage(message.decoratedContent(), boundChatType, bl ? null : "Not Secure");
|
|
OutgoingChatMessage outgoingChatMessage = OutgoingChatMessage.create(message);
|
|
boolean bl2 = false;
|
|
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
boolean bl3 = shouldFilterMessageTo.test(serverPlayer);
|
|
serverPlayer.sendChatMessage(outgoingChatMessage, bl3, boundChatType);
|
|
bl2 |= bl3 && message.isFullyFiltered();
|
|
}
|
|
|
|
if (bl2 && sender != null) {
|
|
sender.sendSystemMessage(CHAT_FILTERED_FULL);
|
|
}
|
|
}
|
|
|
|
private boolean verifyChatTrusted(PlayerChatMessage message) {
|
|
return message.hasSignature() && !message.hasExpiredServer(Instant.now());
|
|
}
|
|
|
|
public ServerStatsCounter getPlayerStats(Player player) {
|
|
UUID uUID = player.getUUID();
|
|
ServerStatsCounter serverStatsCounter = (ServerStatsCounter)this.stats.get(uUID);
|
|
if (serverStatsCounter == null) {
|
|
File file = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile();
|
|
File file2 = new File(file, uUID + ".json");
|
|
if (!file2.exists()) {
|
|
File file3 = new File(file, player.getName().getString() + ".json");
|
|
Path path = file3.toPath();
|
|
if (FileUtil.isPathNormalized(path) && FileUtil.isPathPortable(path) && path.startsWith(file.getPath()) && file3.isFile()) {
|
|
file3.renameTo(file2);
|
|
}
|
|
}
|
|
|
|
serverStatsCounter = new ServerStatsCounter(this.server, file2);
|
|
this.stats.put(uUID, serverStatsCounter);
|
|
}
|
|
|
|
return serverStatsCounter;
|
|
}
|
|
|
|
public PlayerAdvancements getPlayerAdvancements(ServerPlayer player) {
|
|
UUID uUID = player.getUUID();
|
|
PlayerAdvancements playerAdvancements = (PlayerAdvancements)this.advancements.get(uUID);
|
|
if (playerAdvancements == null) {
|
|
Path path = this.server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).resolve(uUID + ".json");
|
|
playerAdvancements = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, player);
|
|
this.advancements.put(uUID, playerAdvancements);
|
|
}
|
|
|
|
playerAdvancements.setPlayer(player);
|
|
return playerAdvancements;
|
|
}
|
|
|
|
public void setViewDistance(int viewDistance) {
|
|
this.viewDistance = viewDistance;
|
|
this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
|
|
|
|
for (ServerLevel serverLevel : this.server.getAllLevels()) {
|
|
if (serverLevel != null) {
|
|
serverLevel.getChunkSource().setViewDistance(viewDistance);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
this.simulationDistance = simulationDistance;
|
|
this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance));
|
|
|
|
for (ServerLevel serverLevel : this.server.getAllLevels()) {
|
|
if (serverLevel != null) {
|
|
serverLevel.getChunkSource().setSimulationDistance(simulationDistance);
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<ServerPlayer> getPlayers() {
|
|
return this.players;
|
|
}
|
|
|
|
/**
|
|
* Gets the ServerPlayer object representing the player with the UUID.
|
|
*/
|
|
@Nullable
|
|
public ServerPlayer getPlayer(UUID playerUUID) {
|
|
return (ServerPlayer)this.playersByUUID.get(playerUUID);
|
|
}
|
|
|
|
public boolean canBypassPlayerLimit(GameProfile profile) {
|
|
return false;
|
|
}
|
|
|
|
public void reloadResources() {
|
|
for (PlayerAdvancements playerAdvancements : this.advancements.values()) {
|
|
playerAdvancements.reload(this.server.getAdvancements());
|
|
}
|
|
|
|
this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
|
|
RecipeManager recipeManager = this.server.getRecipeManager();
|
|
ClientboundUpdateRecipesPacket clientboundUpdateRecipesPacket = new ClientboundUpdateRecipesPacket(
|
|
recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes()
|
|
);
|
|
|
|
for (ServerPlayer serverPlayer : this.players) {
|
|
serverPlayer.connection.send(clientboundUpdateRecipesPacket);
|
|
serverPlayer.getRecipeBook().sendInitialRecipeBook(serverPlayer);
|
|
}
|
|
}
|
|
|
|
public boolean isAllowCommandsForAllPlayers() {
|
|
return this.allowCommandsForAllPlayers;
|
|
}
|
|
}
|