Minestom/src/main/java/net/minestom/server/entity/Player.java

2533 lines
87 KiB
Java
Raw Normal View History

2020-04-24 03:25:58 +02:00
package net.minestom.server.entity;
2019-08-03 15:25:24 +02:00
2020-04-24 03:25:58 +02:00
import net.minestom.server.MinecraftServer;
import net.minestom.server.advancements.AdvancementTab;
2020-05-30 01:39:52 +02:00
import net.minestom.server.attribute.Attribute;
import net.minestom.server.bossbar.BossBar;
import net.minestom.server.chat.ChatParser;
2020-06-22 23:25:00 +02:00
import net.minestom.server.chat.ColoredText;
import net.minestom.server.chat.JsonMessage;
2020-06-22 23:25:00 +02:00
import net.minestom.server.chat.RichMessage;
2020-04-24 03:25:58 +02:00
import net.minestom.server.collision.BoundingBox;
2020-05-25 02:37:57 +02:00
import net.minestom.server.command.CommandManager;
2020-06-22 01:25:50 +02:00
import net.minestom.server.command.CommandSender;
2020-04-28 16:08:21 +02:00
import net.minestom.server.effects.Effects;
import net.minestom.server.entity.damage.DamageType;
2020-10-11 15:27:23 +02:00
import net.minestom.server.entity.fakeplayer.FakePlayer;
2020-04-24 03:25:58 +02:00
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
2020-05-12 17:12:11 +02:00
import net.minestom.server.event.inventory.InventoryOpenEvent;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.item.PickupExperienceEvent;
import net.minestom.server.event.player.*;
2020-06-30 20:38:42 +02:00
import net.minestom.server.gamedata.tags.TagManager;
2020-04-24 03:25:58 +02:00
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.listener.PlayerDiggingListener;
2020-10-11 15:27:23 +02:00
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.PlayerProvider;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.packet.client.ClientPlayPacket;
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.*;
2020-10-11 15:27:23 +02:00
import net.minestom.server.network.player.NettyPlayerConnection;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.player.PlayerConnection;
2020-07-31 22:31:58 +02:00
import net.minestom.server.permission.Permission;
2020-05-25 02:37:57 +02:00
import net.minestom.server.recipe.Recipe;
import net.minestom.server.recipe.RecipeManager;
import net.minestom.server.resourcepack.ResourcePack;
2020-08-09 17:10:58 +02:00
import net.minestom.server.scoreboard.BelowNameTag;
2020-04-24 03:25:58 +02:00
import net.minestom.server.scoreboard.Team;
import net.minestom.server.sound.Sound;
import net.minestom.server.sound.SoundCategory;
import net.minestom.server.stat.PlayerStatistic;
2020-08-16 00:53:42 +02:00
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.binary.BinaryWriter;
2020-10-22 22:55:40 +02:00
import net.minestom.server.utils.callback.OptionalCallback;
import net.minestom.server.utils.chunk.ChunkCallback;
import net.minestom.server.utils.chunk.ChunkUtils;
2020-10-31 00:23:52 +01:00
import net.minestom.server.utils.instance.InstanceUtils;
2020-05-23 04:20:01 +02:00
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.DimensionType;
2020-10-24 10:46:23 +02:00
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
2019-08-03 15:25:24 +02:00
2020-04-17 15:58:07 +02:00
import java.util.*;
2019-08-23 15:37:38 +02:00
import java.util.concurrent.ConcurrentLinkedQueue;
2019-08-19 17:04:19 +02:00
import java.util.concurrent.CopyOnWriteArraySet;
2019-09-01 06:18:41 +02:00
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
2020-10-11 15:27:23 +02:00
/**
2020-10-15 08:48:13 +02:00
* Those are the major actors of the server,
* they are not necessary backed by a {@link NettyPlayerConnection} as shown by {@link FakePlayer}
2020-10-11 15:27:23 +02:00
* <p>
* You can easily create your own implementation of this and use it with {@link ConnectionManager#setPlayerProvider(PlayerProvider)}.
*/
2020-06-21 14:01:03 +02:00
public class Player extends LivingEntity implements CommandSender {
2019-08-10 08:44:35 +02:00
private long lastKeepAlive;
2020-05-29 02:11:41 +02:00
private boolean answerKeepAlive;
2019-08-10 04:16:01 +02:00
private String username;
2020-10-22 12:55:53 +02:00
protected final PlayerConnection playerConnection;
// All the entities that this player can see
protected final Set<Entity> viewableEntities = new CopyOnWriteArraySet<>();
2019-08-03 15:25:24 +02:00
2020-04-26 06:34:08 +02:00
private int latency;
2020-06-22 23:25:00 +02:00
private ColoredText displayName;
private PlayerSkin skin;
2020-04-26 06:34:08 +02:00
private DimensionType dimensionType;
2019-08-12 08:30:59 +02:00
private GameMode gameMode;
protected final Set<Chunk> viewableChunks = new CopyOnWriteArraySet<>();
2020-10-12 06:41:47 +02:00
private final AtomicInteger teleportId = new AtomicInteger();
2019-08-24 20:34:01 +02:00
2019-08-27 20:49:11 +02:00
protected boolean onGround;
private final ConcurrentLinkedQueue<ClientPlayPacket> packets = new ConcurrentLinkedQueue<>();
private final boolean levelFlat;
private final PlayerSettings settings;
2019-08-31 07:54:53 +02:00
private float exp;
private int level;
private final PlayerInventory inventory;
2019-08-13 17:52:09 +02:00
private Inventory openInventory;
// Used internally to allow the closing of inventory within the inventory listener
private boolean didCloseInventory;
private byte heldSlot;
2020-05-15 18:03:28 +02:00
private Position respawnPoint;
private float additionalHearts;
2019-08-25 20:03:43 +02:00
private int food;
private float foodSaturation;
private long startEatingTime;
private long defaultEatingTime = 1000L;
private long eatingTime;
private boolean isEating;
2019-08-12 08:30:59 +02:00
2020-05-25 19:54:36 +02:00
// Game state (https://wiki.vg/Protocol#Change_Game_State)
private boolean enableRespawnScreen;
// CustomBlock break delay
2019-08-18 23:52:11 +02:00
private CustomBlock targetCustomBlock;
2019-08-21 16:50:52 +02:00
private BlockPosition targetBlockPosition;
private long targetBreakDelay; // The last break delay requested
private long targetBlockBreakCount; // Number of tick since the last stage change
private byte targetStage; // The current stage of the target block, only if multi player breaking is disabled
private final Set<Player> targetBreakers = new HashSet<>(1); // Only used if multi player breaking is disabled, contains only this player
2019-08-18 20:38:09 +02:00
2020-08-09 17:10:58 +02:00
private BelowNameTag belowNameTag;
2019-08-19 17:04:19 +02:00
private int permissionLevel;
private boolean reducedDebugScreenInformation;
2019-09-23 19:56:08 +02:00
// Abilities
private boolean flying;
private boolean allowFlying;
private boolean instantBreak;
private float flyingSpeed = 0.05f;
2020-06-25 21:05:58 +02:00
private float walkingSpeed = 0.1f;
2019-09-23 19:56:08 +02:00
2020-04-17 15:58:07 +02:00
// Statistics
private final Map<PlayerStatistic, Integer> statisticValueMap = new Hashtable<>();
2020-04-17 15:58:07 +02:00
// Vehicle
private final PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
2020-05-25 03:39:57 +02:00
// Tick related
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
2020-05-25 03:39:57 +02:00
2020-07-31 22:31:58 +02:00
private final List<Permission> permissions = new LinkedList<>();
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
super(EntityType.PLAYER);
this.uuid = uuid; // Override Entity#uuid defined in the constructor
this.username = username;
this.playerConnection = playerConnection;
2019-08-12 08:30:59 +02:00
setBoundingBox(0.69f, 1.8f, 0.69f);
2019-08-30 01:17:46 +02:00
2020-05-15 18:03:28 +02:00
setRespawnPoint(new Position(0, 0, 0));
2019-08-24 20:34:01 +02:00
this.settings = new PlayerSettings();
2019-08-12 08:30:59 +02:00
this.inventory = new PlayerInventory(this);
2019-08-24 20:34:01 +02:00
setCanPickupItem(true); // By default
2020-05-25 02:37:57 +02:00
2020-05-29 02:11:41 +02:00
// Allow the server to send the next keep alive packet
refreshAnswerKeepAlive(true);
2020-05-26 16:14:52 +02:00
this.gameMode = GameMode.SURVIVAL;
this.dimensionType = DimensionType.OVERWORLD;
this.levelFlat = true;
2020-05-26 16:14:52 +02:00
refreshPosition(0, 0, 0);
// Used to cache the breaker for single custom block breaking
this.targetBreakers.add(this);
2020-05-26 16:14:52 +02:00
// FakePlayer init its connection there
playerConnectionInit();
2020-05-25 02:37:57 +02:00
MinecraftServer.getEntityManager().addWaitingPlayer(this);
}
/**
* Used when the player is created.
* Init the player and spawn him.
* <p>
* WARNING: executed in the main update thread
*/
2020-05-25 02:37:57 +02:00
protected void init() {
JoinGamePacket joinGamePacket = new JoinGamePacket();
joinGamePacket.entityId = getEntityId();
joinGamePacket.gameMode = gameMode;
joinGamePacket.dimensionType = dimensionType;
2020-05-25 02:37:57 +02:00
joinGamePacket.maxPlayers = 0; // Unused
joinGamePacket.viewDistance = MinecraftServer.getChunkViewDistance();
2020-05-25 02:37:57 +02:00
joinGamePacket.reducedDebugInfo = false;
joinGamePacket.isFlat = levelFlat;
2020-05-25 02:37:57 +02:00
playerConnection.sendPacket(joinGamePacket);
// Server brand name
{
playerConnection.sendPacket(PluginMessagePacket.getBrandPacket());
}
2020-05-25 02:37:57 +02:00
ServerDifficultyPacket serverDifficultyPacket = new ServerDifficultyPacket();
serverDifficultyPacket.difficulty = MinecraftServer.getDifficulty();
serverDifficultyPacket.locked = true;
playerConnection.sendPacket(serverDifficultyPacket);
SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket();
spawnPositionPacket.x = 0;
spawnPositionPacket.y = 0;
spawnPositionPacket.z = 0;
playerConnection.sendPacket(spawnPositionPacket);
// Add player to list with spawning skin
PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this);
callEvent(PlayerSkinInitEvent.class, skinInitEvent);
this.skin = skinInitEvent.getSkin();
2020-05-30 19:51:45 +02:00
playerConnection.sendPacket(getAddPlayerToList());
2020-05-25 02:37:57 +02:00
// Commands start
2020-05-25 02:37:57 +02:00
{
CommandManager commandManager = MinecraftServer.getCommandManager();
DeclareCommandsPacket declareCommandsPacket = commandManager.createDeclareCommandsPacket(this);
playerConnection.sendPacket(declareCommandsPacket);
}
// Commands end
2020-05-25 02:37:57 +02:00
// Recipes start
2020-05-25 02:37:57 +02:00
{
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
DeclareRecipesPacket declareRecipesPacket = recipeManager.getDeclareRecipesPacket();
if (declareRecipesPacket.recipes != null) {
playerConnection.sendPacket(declareRecipesPacket);
}
List<String> recipesIdentifier = new ArrayList<>();
for (Recipe recipe : recipeManager.getRecipes()) {
if (!recipe.shouldShow(this))
continue;
recipesIdentifier.add(recipe.getRecipeId());
}
if (!recipesIdentifier.isEmpty()) {
2020-08-14 15:24:57 +02:00
final String[] identifiers = recipesIdentifier.toArray(new String[0]);
2020-05-25 02:37:57 +02:00
UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket();
unlockRecipesPacket.mode = 0;
unlockRecipesPacket.recipesId = identifiers;
unlockRecipesPacket.initRecipesId = identifiers;
playerConnection.sendPacket(unlockRecipesPacket);
}
}
// Recipes end
2020-05-26 16:14:52 +02:00
2020-06-30 20:38:42 +02:00
// Send server tags
TagsPacket tags = new TagsPacket();
TagManager tagManager = MinecraftServer.getTagManager();
tagManager.addRequiredTagsToPacket(tags);
UpdateTagListEvent event = new UpdateTagListEvent(tags);
callEvent(UpdateTagListEvent.class, event);
2020-06-30 20:38:42 +02:00
2020-06-30 21:06:06 +02:00
getPlayerConnection().sendPacket(tags);
2020-06-30 20:38:42 +02:00
2020-05-26 16:14:52 +02:00
// Some client update
playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
2020-05-26 20:00:41 +02:00
refreshHealth(); // Heal and send health packet
refreshAbilities(); // Send abilities packet
getInventory().update();
}
/**
* Used to initialize the player connection
*/
protected void playerConnectionInit() {
2020-05-29 15:31:11 +02:00
this.playerConnection.setPlayer(this);
}
2020-06-25 21:05:58 +02:00
@Override
2020-10-24 16:58:27 +02:00
public float getAttributeValue(@NotNull Attribute attribute) {
2020-06-25 21:05:58 +02:00
if (attribute == Attribute.MOVEMENT_SPEED) {
return walkingSpeed;
}
return super.getAttributeValue(attribute);
}
2019-08-10 08:44:35 +02:00
@Override
public void update(long time) {
2020-08-13 20:51:01 +02:00
playerConnection.updateStats();
2020-02-09 15:34:09 +01:00
// Process received packets
2019-08-24 20:34:01 +02:00
ClientPlayPacket packet;
2019-08-23 15:37:38 +02:00
while ((packet = packets.poll()) != null) {
packet.process(this);
}
2019-08-18 20:38:09 +02:00
super.update(time); // Super update (item pickup/fire management)
2019-08-24 20:34:01 +02:00
2019-08-18 20:38:09 +02:00
// Target block stage
if (targetCustomBlock != null) {
this.targetBlockBreakCount++;
final boolean processStage = targetBreakDelay < 0 || targetBlockBreakCount >= targetBreakDelay;
// Check if the player did finish his current break delay
if (processStage) {
// Negative value should skip abs(value) stage
final byte stageIncrease = (byte) (targetBreakDelay > 0 ? 1 : Math.abs(targetBreakDelay));
// Should increment the target block stage
if (targetCustomBlock.enableMultiPlayerBreaking()) {
// Let the custom block object manages the breaking
final boolean canContinue = this.targetCustomBlock.processStage(instance, targetBlockPosition, this, stageIncrease);
if (canContinue) {
final Set<Player> breakers = targetCustomBlock.getBreakers(instance, targetBlockPosition);
refreshBreakDelay(breakers);
} else {
resetTargetBlock();
}
} else {
// Let the player object manages the breaking
// The custom block doesn't support multi player breaking
if (targetStage + stageIncrease >= CustomBlock.MAX_STAGE) {
// Break the block
instance.breakBlock(this, targetBlockPosition);
resetTargetBlock();
} else {
// Send the new block break animation packet and refresh data
final Chunk chunk = instance.getChunkAt(targetBlockPosition);
final int entityId = targetCustomBlock.getBreakEntityId(this);
final BlockBreakAnimationPacket blockBreakAnimationPacket = new BlockBreakAnimationPacket(entityId, targetBlockPosition, targetStage);
Check.notNull(chunk, "Tried to interact with an unloaded chunk.");
chunk.sendPacketToViewers(blockBreakAnimationPacket);
refreshBreakDelay(targetBreakers);
this.targetStage += stageIncrease;
}
}
2019-08-18 20:38:09 +02:00
}
}
2019-08-31 07:54:53 +02:00
// Experience orb pickup
2020-07-22 17:39:48 +02:00
final Chunk chunk = instance.getChunkAt(getPosition()); // TODO check surrounding chunks
final Set<Entity> entities = instance.getChunkEntities(chunk);
2019-08-31 07:54:53 +02:00
for (Entity entity : entities) {
if (entity instanceof ExperienceOrb) {
2020-07-22 17:39:48 +02:00
final ExperienceOrb experienceOrb = (ExperienceOrb) entity;
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
2020-05-26 20:00:41 +02:00
if (expandedBoundingBox.intersect(itemBoundingBox)) {
2020-08-14 15:24:57 +02:00
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
continue;
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
callCancellableEvent(PickupExperienceEvent.class, pickupExperienceEvent, () -> {
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
entity.remove();
});
2019-08-31 07:54:53 +02:00
}
}
}
// Eating animation
if (isEating()) {
if (time - startEatingTime >= eatingTime) {
refreshEating(false);
triggerStatus((byte) 9); // Mark item use as finished
ItemUpdateStateEvent itemUpdateStateEvent = callItemUpdateStateEvent(true);
Check.notNull(itemUpdateStateEvent, "#callItemUpdateStateEvent returned null.");
2020-05-12 14:19:45 +02:00
// Refresh hand
2020-07-22 17:39:48 +02:00
final boolean isOffHand = itemUpdateStateEvent.getHand() == Player.Hand.OFF;
2020-05-12 14:19:45 +02:00
refreshActiveHand(false, isOffHand, false);
2020-07-22 17:39:48 +02:00
final ItemStack foodItem = itemUpdateStateEvent.getItemStack();
final boolean isFood = foodItem.getMaterial().isFood();
if (isFood) {
2020-05-30 22:32:12 +02:00
PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, foodItem);
callEvent(PlayerEatEvent.class, playerEatEvent);
}
}
}
2019-09-01 06:18:41 +02:00
// Tick event
2020-05-25 03:39:57 +02:00
callEvent(PlayerTickEvent.class, playerTickEvent);
2019-09-01 06:18:41 +02:00
2019-08-18 20:38:09 +02:00
// Multiplayer sync
2020-05-29 02:11:41 +02:00
final boolean positionChanged = position.getX() != lastX || position.getY() != lastY || position.getZ() != lastZ;
final boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch;
if (!viewers.isEmpty()) {
if (positionChanged || viewChanged) {
// Player moved since last time
ServerPacket updatePacket;
ServerPacket optionalUpdatePacket = null;
if (positionChanged && viewChanged) {
EntityPositionAndRotationPacket entityPositionAndRotationPacket = new EntityPositionAndRotationPacket();
entityPositionAndRotationPacket.entityId = getEntityId();
entityPositionAndRotationPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
entityPositionAndRotationPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
entityPositionAndRotationPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
entityPositionAndRotationPacket.yaw = position.getYaw();
entityPositionAndRotationPacket.pitch = position.getPitch();
entityPositionAndRotationPacket.onGround = onGround;
lastX = position.getX();
lastY = position.getY();
lastZ = position.getZ();
lastYaw = position.getYaw();
lastPitch = position.getPitch();
updatePacket = entityPositionAndRotationPacket;
} else if (positionChanged) {
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
entityPositionPacket.entityId = getEntityId();
entityPositionPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
entityPositionPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
entityPositionPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
entityPositionPacket.onGround = onGround;
lastX = position.getX();
lastY = position.getY();
lastZ = position.getZ();
updatePacket = entityPositionPacket;
} else {
// View changed
EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
entityRotationPacket.entityId = getEntityId();
entityRotationPacket.yaw = position.getYaw();
entityRotationPacket.pitch = position.getPitch();
entityRotationPacket.onGround = onGround;
lastYaw = position.getYaw();
lastPitch = position.getPitch();
updatePacket = entityRotationPacket;
}
2019-08-24 20:34:01 +02:00
if (viewChanged) {
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
optionalUpdatePacket = entityHeadLookPacket;
}
// Send the update packet
if (optionalUpdatePacket != null) {
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
} else {
sendPacketToViewers(updatePacket);
}
2019-08-24 20:34:01 +02:00
} else {
// Player did not move since last time
EntityMovementPacket entityMovementPacket = new EntityMovementPacket();
entityMovementPacket.entityId = getEntityId();
sendPacketToViewers(entityMovementPacket);
2019-08-20 22:40:57 +02:00
}
}
2019-09-07 11:42:33 +02:00
2019-08-24 20:34:01 +02:00
}
@Override
public void kill() {
2020-04-27 22:38:11 +02:00
if (!isDead()) {
2020-10-22 12:21:50 +02:00
// send death screen text to the killed player
{
ColoredText deathText;
if (lastDamageSource != null) {
deathText = lastDamageSource.buildDeathScreenText(this);
} else { // may happen if killed by the server without applying damage
deathText = ColoredText.of("Killed by poor programming.");
}
// #buildDeathScreenText can return null, check here
if (deathText != null) {
CombatEventPacket deathPacket = CombatEventPacket.death(this, Optional.empty(), deathText);
playerConnection.sendPacket(deathPacket);
}
}
// send death message to chat
2020-10-22 12:21:50 +02:00
{
JsonMessage chatMessage;
if (lastDamageSource != null) {
chatMessage = lastDamageSource.buildDeathMessage(this);
} else { // may happen if killed by the server without applying damage
chatMessage = ColoredText.of(getUsername() + " was killed by poor programming.");
}
// #buildDeathMessage can return null, check here
if (chatMessage != null) {
MinecraftServer.getConnectionManager().broadcastMessage(chatMessage);
}
}
}
super.kill();
}
2020-05-27 20:30:13 +02:00
/**
2020-10-22 12:21:50 +02:00
* Respawns the player by sending a {@link RespawnPacket} to the player and teleporting him
2020-10-22 12:55:53 +02:00
* to {@link #getRespawnPoint()}. It also resets fire and his health
2020-05-27 20:30:13 +02:00
*/
2020-05-25 19:54:36 +02:00
public void respawn() {
if (!isDead())
return;
setFireForDuration(0);
setOnFire(false);
refreshHealth();
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimensionType = getDimensionType();
2020-05-25 19:54:36 +02:00
respawnPacket.gameMode = getGameMode();
respawnPacket.isFlat = levelFlat;
2020-05-25 19:54:36 +02:00
getPlayerConnection().sendPacket(respawnPacket);
2020-07-27 02:28:03 +02:00
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
2020-05-25 19:54:36 +02:00
callEvent(PlayerRespawnEvent.class, respawnEvent);
refreshIsDead(false);
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
teleport(respawnEvent.getRespawnPosition(), this::refreshAfterTeleport);
}
2019-08-24 20:34:01 +02:00
@Override
public void spawn() {
2019-08-20 22:40:57 +02:00
}
2019-08-27 20:49:11 +02:00
@Override
public boolean isOnGround() {
return onGround;
}
2019-08-25 20:03:43 +02:00
@Override
public void remove() {
callEvent(PlayerDisconnectEvent.class, new PlayerDisconnectEvent(this));
2020-05-09 20:00:59 +02:00
super.remove();
2020-05-29 15:31:11 +02:00
this.packets.clear();
2019-08-25 20:03:43 +02:00
if (getOpenInventory() != null)
getOpenInventory().removeViewer(this);
// Boss bars cache
{
Set<BossBar> bossBars = BossBar.getBossBars(this);
if (bossBars != null) {
for (BossBar bossBar : bossBars) {
bossBar.removeViewer(this);
}
}
}
// Advancement tabs cache
{
Set<AdvancementTab> advancementTabs = AdvancementTab.getTabs(this);
if (advancementTabs != null) {
for (AdvancementTab advancementTab : advancementTabs) {
advancementTab.removeViewer(this);
}
}
}
2020-08-25 20:55:40 +02:00
// Clear all viewable entities
2019-09-01 06:18:41 +02:00
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
2020-08-25 20:55:40 +02:00
// Clear all viewable chunks
this.viewableChunks.forEach(chunk -> {
if (chunk.isLoaded())
chunk.removeViewer(this);
});
2019-09-07 11:42:33 +02:00
resetTargetBlock();
2020-05-29 17:20:30 +02:00
playerConnection.disconnect();
2019-08-25 20:03:43 +02:00
}
2019-08-20 22:40:57 +02:00
@Override
2020-10-24 10:46:23 +02:00
public boolean addViewer(@NotNull Player player) {
if (player == this)
return false;
2020-07-22 17:39:48 +02:00
final boolean result = super.addViewer(player);
2020-05-29 02:11:41 +02:00
if (!result)
return false;
2019-09-21 20:42:27 +02:00
PlayerConnection viewerConnection = player.getPlayerConnection();
showPlayer(viewerConnection);
return true;
2019-08-20 22:40:57 +02:00
}
@Override
2020-10-24 10:46:23 +02:00
public boolean removeViewer(@NotNull Player player) {
if (player == this)
return false;
boolean result = super.removeViewer(player);
2019-09-21 20:42:27 +02:00
PlayerConnection viewerConnection = player.getPlayerConnection();
viewerConnection.sendPacket(getRemovePlayerToList());
2019-09-21 20:42:27 +02:00
// Team
2020-08-09 17:10:58 +02:00
if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) // If team only contains "this" player
viewerConnection.sendPacket(this.getTeam().createTeamDestructionPacket());
return result;
}
@Override
2020-10-24 10:46:23 +02:00
public void setInstance(@NotNull Instance instance) {
2020-05-23 04:20:01 +02:00
Check.notNull(instance, "instance cannot be null!");
Check.argCondition(this.instance == instance, "Instance should be different than the current one");
2019-09-01 06:18:41 +02:00
2020-07-22 17:39:48 +02:00
final boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too
// true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance)
2020-10-31 00:23:52 +01:00
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance);
2020-04-27 18:46:39 +02:00
2020-10-31 00:23:52 +01:00
if (needWorldRefresh) {
// Remove all previous viewable chunks (from the previous instance)
for (Chunk viewableChunk : viewableChunks) {
viewableChunk.removeViewer(this);
}
2019-09-01 06:18:41 +02:00
2020-10-31 00:23:52 +01:00
if (this.instance != null) {
final DimensionType instanceDimensionType = instance.getDimensionType();
if (dimensionType != instanceDimensionType)
sendDimension(instanceDimensionType);
}
2020-10-31 00:23:52 +01:00
final long[] visibleChunks = ChunkUtils.getChunksInRange(position, getChunkRange());
final int length = visibleChunks.length;
AtomicInteger counter = new AtomicInteger(0);
for (long visibleChunk : visibleChunks) {
final int chunkX = ChunkUtils.getChunkCoordX(visibleChunk);
final int chunkZ = ChunkUtils.getChunkCoordZ(visibleChunk);
2019-09-01 06:18:41 +02:00
2020-10-31 00:23:52 +01:00
final ChunkCallback callback = (chunk) -> {
if (chunk != null) {
chunk.addViewer(this);
if (chunk.getChunkX() == Math.floorDiv((int) getPosition().getX(), 16) && chunk.getChunkZ() == Math.floorDiv((int) getPosition().getZ(), 16))
updateViewPosition(chunk);
}
final boolean isLast = counter.get() == length - 1;
if (isLast) {
// This is the last chunk to be loaded , spawn player
spawnPlayer(instance, firstSpawn);
} else {
// Increment the counter of current loaded chunks
counter.incrementAndGet();
}
};
// WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck.
instance.loadOptionalChunk(chunkX, chunkZ, callback);
}
} else {
spawnPlayer(instance, firstSpawn);
2019-09-01 06:18:41 +02:00
}
}
2020-10-31 00:23:52 +01:00
/**
* Used to spawn the player once the client has all the required chunks.
* <p>
* Does add the player to {@code instance}, remove all viewable entities and call {@link PlayerSpawnEvent}.
* <p>
* UNSAFE: only called with {@link #setInstance(Instance)}.
*
* @param firstSpawn true if this is the player first spawn
*/
private void spawnPlayer(Instance instance, boolean firstSpawn) {
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
super.setInstance(instance);
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
callEvent(PlayerSpawnEvent.class, spawnEvent);
}
2020-10-24 10:46:23 +02:00
@NotNull
2020-06-30 01:25:23 +02:00
@Override
public Consumer<BinaryWriter> getMetadataConsumer() {
2020-06-30 01:25:23 +02:00
return packet -> {
super.getMetadataConsumer().accept(packet);
fillMetadataIndex(packet, 14);
fillMetadataIndex(packet, 16);
};
}
@Override
2020-10-24 10:46:23 +02:00
protected void fillMetadataIndex(@NotNull BinaryWriter packet, int index) {
2020-06-30 01:25:23 +02:00
super.fillMetadataIndex(packet, index);
if (index == 14) {
packet.writeByte((byte) 14);
packet.writeByte(METADATA_FLOAT);
packet.writeFloat(additionalHearts);
} else if (index == 16) {
packet.writeByte((byte) 16);
packet.writeByte(METADATA_BYTE);
packet.writeByte(getSettings().getDisplayedSkinParts());
}
}
2020-08-15 13:38:57 +02:00
/**
2020-10-24 10:46:23 +02:00
* Sends a plugin message to the player.
2020-08-15 13:38:57 +02:00
*
* @param channel the message channel
* @param data the message data
*/
2020-10-24 10:46:23 +02:00
public void sendPluginMessage(@NotNull String channel, @NotNull byte[] data) {
2020-08-15 13:38:57 +02:00
PluginMessagePacket pluginMessagePacket = new PluginMessagePacket();
pluginMessagePacket.channel = channel;
pluginMessagePacket.data = data;
playerConnection.sendPacket(pluginMessagePacket);
}
/**
2020-10-24 10:46:23 +02:00
* Sends a plugin message to the player.
2020-08-15 13:38:57 +02:00
*
* @param channel the message channel
* @param message the message
*/
2020-10-24 10:46:23 +02:00
public void sendPluginMessage(@NotNull String channel, @NotNull String message) {
2020-08-15 13:42:27 +02:00
// Write the data
BinaryWriter writer = new BinaryWriter();
2020-08-15 13:42:27 +02:00
writer.writeSizedString(message);
// Retrieve the data
final byte[] data = writer.toByteArray();
sendPluginMessage(channel, data);
2020-08-15 13:38:57 +02:00
}
2020-06-21 14:01:03 +02:00
@Override
2020-10-24 10:46:23 +02:00
public void sendMessage(@NotNull String message) {
2020-06-22 23:25:00 +02:00
sendMessage(ColoredText.of(message));
2019-09-10 06:59:15 +02:00
}
2020-05-27 20:30:13 +02:00
/**
* Sends a message to the player.
2020-05-27 20:30:13 +02:00
*
* @param message the message to send,
* you can use {@link ColoredText} and/or {@link RichMessage} to create it easily
2020-05-27 20:30:13 +02:00
*/
2020-10-24 10:46:23 +02:00
public void sendMessage(@NotNull JsonMessage message) {
sendJsonMessage(message.toString());
2019-09-10 06:59:15 +02:00
}
2020-06-23 22:46:22 +02:00
/**
* Sends a legacy message with the specified color char.
2020-06-23 22:46:22 +02:00
*
* @param text the text with the legacy color formatting
2020-10-22 12:55:53 +02:00
* @param colorChar the color character
2020-06-23 22:46:22 +02:00
*/
2020-10-24 10:46:23 +02:00
public void sendLegacyMessage(@NotNull String text, char colorChar) {
2020-06-23 22:46:22 +02:00
ColoredText coloredText = ColoredText.ofLegacy(text, colorChar);
2020-06-30 01:25:23 +02:00
sendJsonMessage(coloredText.toString());
2020-06-23 22:46:22 +02:00
}
/**
* Sends a legacy message with the default color char {@link ChatParser#COLOR_CHAR}.
2020-06-23 22:46:22 +02:00
*
* @param text the text with the legacy color formatting
*/
2020-10-24 10:46:23 +02:00
public void sendLegacyMessage(@NotNull String text) {
ColoredText coloredText = ColoredText.ofLegacy(text, ChatParser.COLOR_CHAR);
2020-06-30 01:25:23 +02:00
sendJsonMessage(coloredText.toString());
}
2020-10-24 10:46:23 +02:00
public void sendJsonMessage(@NotNull String json) {
2020-06-30 01:25:23 +02:00
ChatMessagePacket chatMessagePacket =
new ChatMessagePacket(json, ChatMessagePacket.Position.CHAT);
playerConnection.sendPacket(chatMessagePacket);
2020-06-23 22:46:22 +02:00
}
/**
* Makes the player send a message (can be used for commands).
*
* @param message the message that the player will send
*/
2020-10-24 10:46:23 +02:00
public void chat(@NotNull String message) {
ClientChatMessagePacket chatMessagePacket = new ClientChatMessagePacket();
chatMessagePacket.message = message;
addPacketToQueue(chatMessagePacket);
}
/**
* Plays a sound from the {@link Sound} enum.
*
* @param sound the sound to play
* @param soundCategory the sound category
* @param x the effect X
* @param y the effect Y
* @param z the effect Z
* @param volume the volume of the sound (1 is 100%)
* @param pitch the pitch of the sound, between 0.5 and 2.0
*/
2020-10-24 10:46:23 +02:00
public void playSound(@NotNull Sound sound, @NotNull SoundCategory soundCategory, int x, int y, int z, float volume, float pitch) {
2020-04-10 13:39:22 +02:00
SoundEffectPacket soundEffectPacket = new SoundEffectPacket();
soundEffectPacket.soundId = sound.getId();
soundEffectPacket.soundCategory = soundCategory;
soundEffectPacket.x = x * 8;
soundEffectPacket.y = y * 8;
soundEffectPacket.z = z * 8;
2020-04-10 13:39:22 +02:00
soundEffectPacket.volume = volume;
soundEffectPacket.pitch = pitch;
playerConnection.sendPacket(soundEffectPacket);
}
/**
* Plays a sound from an identifier (represents a custom sound in a resource pack).
*
* @param identifier the identifier of the sound to play
* @param soundCategory the sound category
* @param x the effect X
* @param y the effect Y
* @param z the effect Z
* @param volume the volume of the sound (1 is 100%)
* @param pitch the pitch of the sound, between 0.5 and 2.0
*/
public void playSound(@NotNull String identifier, @NotNull SoundCategory soundCategory, int x, int y, int z, float volume, float pitch) {
NamedSoundEffectPacket namedSoundEffectPacket = new NamedSoundEffectPacket();
namedSoundEffectPacket.soundName = identifier;
namedSoundEffectPacket.soundCategory = soundCategory;
namedSoundEffectPacket.x = x * 8;
namedSoundEffectPacket.y = y * 8;
namedSoundEffectPacket.z = z * 8;
namedSoundEffectPacket.volume = volume;
namedSoundEffectPacket.pitch = pitch;
playerConnection.sendPacket(namedSoundEffectPacket);
}
2020-04-28 16:08:21 +02:00
/**
* Plays a given effect at the given position for this player.
*
* @param effect the effect to play
* @param x x position of the effect
* @param y y position of the effect
* @param z z position of the effect
* @param data data for the effect
2020-04-28 16:08:21 +02:00
* @param disableRelativeVolume disable volume scaling based on distance
*/
2020-10-24 10:46:23 +02:00
public void playEffect(@NotNull Effects effect, int x, int y, int z, int data, boolean disableRelativeVolume) {
2020-04-28 16:08:21 +02:00
EffectPacket packet = new EffectPacket();
packet.effectId = effect.getId();
packet.position = new BlockPosition(x, y, z);
packet.data = data;
packet.disableRelativeVolume = disableRelativeVolume;
playerConnection.sendPacket(packet);
}
2020-05-29 23:17:14 +02:00
/**
* Sends a {@link StopSoundPacket} packet.
2020-05-29 23:17:14 +02:00
*/
2020-04-10 13:39:22 +02:00
public void stopSound() {
StopSoundPacket stopSoundPacket = new StopSoundPacket();
stopSoundPacket.flags = 0x00;
playerConnection.sendPacket(stopSoundPacket);
}
/**
* Sets the header and footer of a player which will be displayed in his tab window.
*
2020-10-24 16:58:27 +02:00
* @param header the header text, null to set empty
* @param footer the footer text, null to set empty
*/
2020-10-24 16:58:27 +02:00
public void sendHeaderFooter(@Nullable ColoredText header, @Nullable ColoredText footer) {
2020-04-10 13:39:22 +02:00
PlayerListHeaderAndFooterPacket playerListHeaderAndFooterPacket = new PlayerListHeaderAndFooterPacket();
playerListHeaderAndFooterPacket.emptyHeader = header == null;
playerListHeaderAndFooterPacket.emptyFooter = footer == null;
2020-08-05 18:25:11 +02:00
playerListHeaderAndFooterPacket.header = header;
playerListHeaderAndFooterPacket.footer = footer;
2020-04-10 13:39:22 +02:00
playerConnection.sendPacket(playerListHeaderAndFooterPacket);
}
/**
* Common method to send a title.
*
* @param text the text of the title
* @param action the action of the title (where to show it)
* @see #sendTitleTime(int, int, int) to specify the display time
*/
2020-10-24 10:46:23 +02:00
private void sendTitle(@NotNull ColoredText text, @NotNull TitlePacket.Action action) {
2020-02-16 19:11:36 +01:00
TitlePacket titlePacket = new TitlePacket();
titlePacket.action = action;
switch (action) {
case SET_TITLE:
2020-08-05 18:25:11 +02:00
titlePacket.titleText = text;
break;
case SET_SUBTITLE:
2020-08-05 18:25:11 +02:00
titlePacket.subtitleText = text;
break;
case SET_ACTION_BAR:
2020-08-05 18:25:11 +02:00
titlePacket.actionBarText = text;
break;
default:
throw new UnsupportedOperationException("Invalid TitlePacket.Action type!");
}
2020-02-16 19:11:36 +01:00
playerConnection.sendPacket(titlePacket);
}
/**
* Sends a title and subtitle message.
*
* @param title the title message
* @param subtitle the subtitle message
* @see #sendTitleTime(int, int, int) to specify the display time
*/
2020-10-24 10:46:23 +02:00
public void sendTitleSubtitleMessage(@NotNull ColoredText title, @NotNull ColoredText subtitle) {
sendTitle(title, TitlePacket.Action.SET_TITLE);
sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE);
}
/**
* Sends a title message.
*
* @param title the title message
* @see #sendTitleTime(int, int, int) to specify the display time
*/
2020-10-24 10:46:23 +02:00
public void sendTitleMessage(@NotNull ColoredText title) {
sendTitle(title, TitlePacket.Action.SET_TITLE);
}
/**
* Sends a subtitle message.
*
* @param subtitle the subtitle message
* @see #sendTitleTime(int, int, int) to specify the display time
*/
2020-10-24 10:46:23 +02:00
public void sendSubtitleMessage(@NotNull ColoredText subtitle) {
sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE);
}
/**
* Sends an action bar message.
*
* @param actionBar the action bar message
* @see #sendTitleTime(int, int, int) to specify the display time
*/
2020-10-24 10:46:23 +02:00
public void sendActionBarMessage(@NotNull ColoredText actionBar) {
sendTitle(actionBar, TitlePacket.Action.SET_ACTION_BAR);
}
/**
* Specifies the display time of a title.
*
* @param fadeIn ticks to spend fading in
* @param stay ticks to keep the title displayed
2020-10-17 16:30:37 +02:00
* @param fadeOut ticks to spend out, not when to start fading out
*/
public void sendTitleTime(int fadeIn, int stay, int fadeOut) {
TitlePacket titlePacket = new TitlePacket();
titlePacket.action = TitlePacket.Action.SET_TIMES_AND_DISPLAY;
titlePacket.fadeIn = fadeIn;
titlePacket.stay = stay;
titlePacket.fadeOut = fadeOut;
playerConnection.sendPacket(titlePacket);
}
/**
2020-10-17 16:30:37 +02:00
* Hides the previous title.
*/
public void hideTitle() {
TitlePacket titlePacket = new TitlePacket();
titlePacket.action = TitlePacket.Action.HIDE;
playerConnection.sendPacket(titlePacket);
}
/**
2020-10-17 16:30:37 +02:00
* Resets the previous title.
*/
public void resetTitle() {
TitlePacket titlePacket = new TitlePacket();
titlePacket.action = TitlePacket.Action.RESET;
playerConnection.sendPacket(titlePacket);
}
2019-08-27 20:49:11 +02:00
@Override
2020-10-24 10:46:23 +02:00
public boolean isImmune(@NotNull DamageType type) {
if (!getGameMode().canTakeDamage()) {
return type != DamageType.VOID;
}
return super.isImmune(type);
2019-08-25 20:03:43 +02:00
}
@Override
2020-10-24 10:46:23 +02:00
public void setAttribute(@NotNull Attribute attribute, float value) {
super.setAttribute(attribute, value);
if (playerConnection != null)
playerConnection.sendPacket(getPropertiesPacket());
2019-08-25 20:03:43 +02:00
}
@Override
2019-08-25 20:03:43 +02:00
public void setHealth(float health) {
super.setHealth(health);
2019-08-25 20:03:43 +02:00
sendUpdateHealthPacket();
}
2020-05-27 20:30:13 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player additional hearts.
2020-05-29 23:17:14 +02:00
*
* @return the player additional hearts
2020-05-27 20:30:13 +02:00
*/
public float getAdditionalHearts() {
return additionalHearts;
}
2020-05-27 20:30:13 +02:00
/**
2020-10-15 21:16:31 +02:00
* Updates the internal field and send the appropriate {@link EntityMetaDataPacket}.
2020-05-27 20:30:13 +02:00
*
* @param additionalHearts the count of additional hearts
2020-05-27 20:30:13 +02:00
*/
public void setAdditionalHearts(float additionalHearts) {
this.additionalHearts = additionalHearts;
sendMetadataIndex(14);
}
2019-08-25 20:03:43 +02:00
2020-05-27 20:30:13 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player food.
2020-05-29 23:17:14 +02:00
*
* @return the player food
2020-05-27 20:30:13 +02:00
*/
2019-08-25 20:03:43 +02:00
public int getFood() {
return food;
}
2020-05-26 17:48:46 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sets and refresh client food bar.
2020-05-26 17:48:46 +02:00
*
* @param food the new food value
2020-11-07 04:42:48 +01:00
* @throws IllegalArgumentException if {@code food} is not between 0 and 20
2020-05-26 17:48:46 +02:00
*/
2019-08-25 20:03:43 +02:00
public void setFood(int food) {
Check.argCondition(!MathUtils.isBetween(food, 0, 20), "Food has to be between 0 and 20");
2019-08-25 20:03:43 +02:00
this.food = food;
sendUpdateHealthPacket();
}
public float getFoodSaturation() {
return foodSaturation;
}
2020-05-26 17:48:46 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sets and refresh client food saturation.
2020-05-26 17:48:46 +02:00
*
* @param foodSaturation the food saturation
2020-11-07 04:42:48 +01:00
* @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 5
2020-05-26 17:48:46 +02:00
*/
2019-08-25 20:03:43 +02:00
public void setFoodSaturation(float foodSaturation) {
2020-05-26 17:48:46 +02:00
Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 5), "Food saturation has to be between 0 and 5");
2019-08-25 20:03:43 +02:00
this.foodSaturation = foodSaturation;
sendUpdateHealthPacket();
}
2020-05-23 04:20:01 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the player is eating.
2020-05-29 23:17:14 +02:00
*
* @return true if the player is eating, false otherwise
2020-05-23 04:20:01 +02:00
*/
public boolean isEating() {
return isEating;
}
2020-05-23 04:20:01 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player default eating time.
2020-05-29 23:17:14 +02:00
*
* @return the player default eating time
2020-05-23 04:20:01 +02:00
*/
public long getDefaultEatingTime() {
return defaultEatingTime;
}
/**
2020-10-15 21:16:31 +02:00
* Used to change the default eating time animation.
*
* @param defaultEatingTime the default eating time in milliseconds
*/
public void setDefaultEatingTime(long defaultEatingTime) {
this.defaultEatingTime = defaultEatingTime;
}
2020-05-25 19:54:36 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player display name in the tab-list.
2020-05-25 19:54:36 +02:00
*
2020-05-29 23:17:14 +02:00
* @return the player display name,
* null means that {@link #getUsername()} is displayed
*/
2020-10-27 12:45:37 +01:00
@Nullable
2020-06-22 23:25:00 +02:00
public ColoredText getDisplayName() {
return displayName;
}
/**
2020-10-15 21:16:31 +02:00
* Changes the player display name in the tab-list.
2020-05-29 23:17:14 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* Sets to null to show the player username.
2020-05-29 23:17:14 +02:00
*
2020-10-27 12:45:37 +01:00
* @param displayName the display name, null to display the username
*/
2020-10-27 12:45:37 +01:00
public void setDisplayName(@Nullable ColoredText displayName) {
this.displayName = displayName;
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME);
2020-07-06 19:15:13 +02:00
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateDisplayName(getUuid(), displayName));
sendPacketToViewersAndSelf(infoPacket);
2020-05-25 19:54:36 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Gets the player skin.
*
* @return the player skin object,
* null means that the player has his {@link #getUuid()} default skin
*/
2020-10-24 10:46:23 +02:00
@Nullable
public PlayerSkin getSkin() {
return skin;
}
/**
2020-10-15 21:16:31 +02:00
* Changes the player skin.
2020-07-22 17:39:48 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* This does remove the player for all viewers to spawn it again with the correct new skin.
*
* @param skin the player skin, null to reset it to his {@link #getUuid()} default skin
* @see PlayerSkinInitEvent if you want to apply the skin at connection
*/
2020-10-24 10:46:23 +02:00
public synchronized void setSkin(@Nullable PlayerSkin skin) {
this.skin = skin;
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
destroyEntitiesPacket.entityIds = new int[]{getEntityId()};
2020-07-22 17:39:48 +02:00
final PlayerInfoPacket removePlayerPacket = getRemovePlayerToList();
final PlayerInfoPacket addPlayerPacket = getAddPlayerToList();
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimensionType = getDimensionType();
respawnPacket.gameMode = getGameMode();
respawnPacket.isFlat = levelFlat;
playerConnection.sendPacket(removePlayerPacket);
playerConnection.sendPacket(destroyEntitiesPacket);
playerConnection.sendPacket(respawnPacket);
playerConnection.sendPacket(addPlayerPacket);
for (Player viewer : getViewers()) {
final PlayerConnection connection = viewer.getPlayerConnection();
connection.sendPacket(removePlayerPacket);
connection.sendPacket(destroyEntitiesPacket);
showPlayer(connection);
}
getInventory().update();
teleport(getPosition());
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the player has the respawn screen enabled or disabled.
2020-05-29 23:17:14 +02:00
*
* @return true if the player has the respawn screen, false if he didn't
*/
2020-05-25 19:54:36 +02:00
public boolean isEnableRespawnScreen() {
return enableRespawnScreen;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Enables or disable the respawn screen.
2020-05-29 23:17:14 +02:00
*
* @param enableRespawnScreen true to enable the respawn screen, false to disable it
*/
2020-05-25 19:54:36 +02:00
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
this.enableRespawnScreen = enableRespawnScreen;
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.ENABLE_RESPAWN_SCREEN, enableRespawnScreen ? 0 : 1);
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player username.
2020-05-29 23:17:14 +02:00
*
* @return the player username
*/
2020-10-24 10:46:23 +02:00
@NotNull
2020-05-29 23:17:14 +02:00
public String getUsername() {
return username;
}
2020-06-21 22:11:56 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the internal player name, used for the {@link PlayerPreLoginEvent}
* mostly unsafe outside of it.
2020-06-21 22:11:56 +02:00
*
* @param username the new player name
*/
2020-10-24 10:46:23 +02:00
protected void setUsername(@NotNull String username) {
2020-06-21 22:11:56 +02:00
this.username = username;
}
2020-10-24 10:46:23 +02:00
private void sendChangeGameStatePacket(@NotNull ChangeGameStatePacket.Reason reason, float value) {
2020-05-25 19:54:36 +02:00
ChangeGameStatePacket changeGameStatePacket = new ChangeGameStatePacket();
changeGameStatePacket.reason = reason;
changeGameStatePacket.value = value;
playerConnection.sendPacket(changeGameStatePacket);
}
/**
* Calls an {@link ItemDropEvent} with a specified item.
*
* @param item the item to drop
* @return true if player can drop the item (event not cancelled), false otherwise
*/
2020-10-24 10:46:23 +02:00
public boolean dropItem(@NotNull ItemStack item) {
2020-04-20 18:46:39 +02:00
ItemDropEvent itemDropEvent = new ItemDropEvent(item);
callEvent(ItemDropEvent.class, itemDropEvent);
return !itemDropEvent.isCancelled();
}
/**
2020-10-15 21:16:31 +02:00
* Sets the player resource pack.
*
* @param resourcePack the resource pack
*/
2020-11-07 04:42:48 +01:00
public void setResourcePack(@NotNull ResourcePack resourcePack) {
Check.notNull(resourcePack, "The resource pack cannot be null");
final String url = resourcePack.getUrl();
2020-05-31 20:09:42 +02:00
final String hash = resourcePack.getHash();
ResourcePackSendPacket resourcePackSendPacket = new ResourcePackSendPacket();
resourcePackSendPacket.url = url;
resourcePackSendPacket.hash = hash;
playerConnection.sendPacket(resourcePackSendPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Rotates the player to face {@code targetPosition}.
*
* @param facePoint the point from where the player should aim
* @param targetPosition the target position to face
*/
2020-10-24 10:46:23 +02:00
public void facePosition(@NotNull FacePoint facePoint, @NotNull Position targetPosition) {
facePosition(facePoint, targetPosition, null, null);
}
/**
2020-10-15 21:16:31 +02:00
* Rotates the player to face {@code entity}.
*
* @param facePoint the point from where the player should aim
* @param entity the entity to face
* @param targetPoint the point to aim at {@code entity} position
*/
2020-10-24 10:46:23 +02:00
public void facePosition(@NotNull FacePoint facePoint, Entity entity, FacePoint targetPoint) {
facePosition(facePoint, entity.getPosition(), entity, targetPoint);
}
2020-10-24 10:46:23 +02:00
private void facePosition(@NotNull FacePoint facePoint, @NotNull Position targetPosition, @Nullable Entity entity, @Nullable FacePoint targetPoint) {
FacePlayerPacket facePlayerPacket = new FacePlayerPacket();
facePlayerPacket.entityFacePosition = facePoint == FacePoint.EYE ?
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
facePlayerPacket.targetX = targetPosition.getX();
facePlayerPacket.targetY = targetPosition.getY();
facePlayerPacket.targetZ = targetPosition.getZ();
if (entity != null) {
facePlayerPacket.entityId = entity.getEntityId();
facePlayerPacket.entityFacePosition = targetPoint == FacePoint.EYE ?
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
}
playerConnection.sendPacket(facePlayerPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Sets the camera at {@code entity} eyes.
*
* @param entity the entity to spectate
*/
2020-10-24 10:46:23 +02:00
public void spectate(@NotNull Entity entity) {
CameraPacket cameraPacket = new CameraPacket();
cameraPacket.cameraId = entity.getEntityId();
playerConnection.sendPacket(cameraPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Resets the camera at the player.
*/
public void stopSpectating() {
spectate(this);
}
2020-05-15 18:03:28 +02:00
/**
* Used to retrieve the default spawn point.
* <p>
* Can be altered by the {@link PlayerRespawnEvent#setRespawnPosition(Position)}.
2020-05-15 18:03:28 +02:00
*
* @return the default respawn point
*/
2020-10-24 10:46:23 +02:00
@NotNull
public Position getRespawnPoint() {
2020-05-15 18:03:28 +02:00
return respawnPoint;
}
/**
2020-10-15 21:16:31 +02:00
* Changes the default spawn point.
2020-05-15 18:03:28 +02:00
*
2020-05-30 01:39:52 +02:00
* @param respawnPoint the player respawn point
2020-05-15 18:03:28 +02:00
*/
2020-10-24 10:46:23 +02:00
public void setRespawnPoint(@NotNull Position respawnPoint) {
2020-05-15 18:03:28 +02:00
this.respawnPoint = respawnPoint;
}
2020-05-30 01:39:52 +02:00
/**
* Called after the player teleportation to refresh his position
2020-10-15 21:16:31 +02:00
* and send data to his new viewers.
2020-05-30 01:39:52 +02:00
*/
protected void refreshAfterTeleport() {
getInventory().update();
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
spawnPlayerPacket.entityId = getEntityId();
spawnPlayerPacket.playerUuid = getUuid();
spawnPlayerPacket.position = getPosition();
sendPacketToViewers(spawnPlayerPacket);
2020-05-30 01:39:52 +02:00
// Update for viewers
sendPacketToViewersAndSelf(getVelocityPacket());
sendPacketToViewersAndSelf(getMetadataPacket());
playerConnection.sendPacket(getPropertiesPacket());
syncEquipments();
2020-08-25 20:55:40 +02:00
{
// Send new chunks
final BlockPosition pos = position.toBlockPosition();
final Chunk chunk = instance.getChunk(pos.getX() >> 4, pos.getZ() >> 4);
Check.notNull(chunk, "Tried to interact with an unloaded chunk.");
refreshVisibleChunks(chunk);
2020-08-25 20:55:40 +02:00
}
2019-08-25 20:03:43 +02:00
}
/**
* Sets the player food and health values to their maximum.
*/
2020-04-05 17:46:29 +02:00
protected void refreshHealth() {
2019-08-25 20:03:43 +02:00
this.food = 20;
this.foodSaturation = 5;
2020-05-26 17:48:46 +02:00
// refresh health and send health packet
heal();
2019-08-25 20:03:43 +02:00
}
/**
* Sends an {@link UpdateHealthPacket} to refresh client-side information about health and food.
*/
2019-08-25 20:03:43 +02:00
protected void sendUpdateHealthPacket() {
UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket();
2019-08-26 04:39:58 +02:00
updateHealthPacket.health = getHealth();
2019-08-25 20:03:43 +02:00
updateHealthPacket.food = food;
updateHealthPacket.foodSaturation = foodSaturation;
playerConnection.sendPacket(updateHealthPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Gets the percentage displayed in the experience bar.
*
* @return the exp percentage 0-1
*/
public float getExp() {
return exp;
}
/**
2020-10-15 21:16:31 +02:00
* Used to change the percentage experience bar.
* This cannot change the displayed level, see {@link #setLevel(int)}.
*
* @param exp a percentage between 0 and 1
2020-11-07 04:42:48 +01:00
* @throws IllegalArgumentException if {@code exp} is not between 0 and 1
*/
2019-08-31 07:54:53 +02:00
public void setExp(float exp) {
2020-05-23 04:20:01 +02:00
Check.argCondition(!MathUtils.isBetween(exp, 0, 1), "Exp should be between 0 and 1");
2019-08-31 07:54:53 +02:00
this.exp = exp;
sendExperienceUpdatePacket();
}
/**
2020-10-15 21:16:31 +02:00
* Gets the level of the player displayed in the experience bar.
*
* @return the player level
*/
public int getLevel() {
return level;
}
/**
* Used to change the level of the player
* This cannot change the displayed percentage bar see {@link #setExp(float)}
*
* @param level the new level of the player
*/
2019-08-31 07:54:53 +02:00
public void setLevel(int level) {
this.level = level;
sendExperienceUpdatePacket();
}
/**
* Sends a {@link SetExperiencePacket} to refresh client-side information about the experience bar.
*/
2019-08-31 07:54:53 +02:00
protected void sendExperienceUpdatePacket() {
SetExperiencePacket setExperiencePacket = new SetExperiencePacket();
setExperiencePacket.percentage = exp;
setExperiencePacket.level = level;
playerConnection.sendPacket(setExperiencePacket);
}
2020-07-22 17:39:48 +02:00
/**
2020-10-15 21:16:31 +02:00
* Called when the player changes chunk (move from one to another).
* Can also be used to refresh the list of chunks that the client should see based on {@link #getChunkRange()}.
2020-07-22 17:39:48 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* It does remove and add the player from the chunks viewers list when removed or added.
* It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent}.
2020-07-22 17:39:48 +02:00
*
* @param newChunk the current/new player chunk (can be the current one)
2020-07-22 17:39:48 +02:00
*/
public void refreshVisibleChunks(@NotNull Chunk newChunk) {
2020-08-25 20:55:40 +02:00
// Previous chunks indexes
final long[] lastVisibleChunks = viewableChunks.stream().mapToLong(viewableChunks ->
ChunkUtils.getChunkIndex(viewableChunks.getChunkX(), viewableChunks.getChunkZ())
).toArray();
// New chunks indexes
final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange());
2020-08-25 20:55:40 +02:00
// Find the difference between the two arrays¬
2020-07-22 17:39:48 +02:00
final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks);
final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks);
2019-08-25 20:03:43 +02:00
// Unload old chunks
2019-08-26 04:39:58 +02:00
for (int index : oldChunks) {
final long chunkIndex = lastVisibleChunks[index];
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
2019-08-26 04:39:58 +02:00
UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket();
unloadChunkPacket.chunkX = chunkX;
unloadChunkPacket.chunkZ = chunkZ;
2019-08-26 04:39:58 +02:00
playerConnection.sendPacket(unloadChunkPacket);
2019-09-01 06:18:41 +02:00
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
2019-09-01 06:18:41 +02:00
if (chunk != null)
chunk.removeViewer(this);
2019-08-25 20:03:43 +02:00
}
2020-11-08 15:57:00 +01:00
// Update client render distance
2019-08-26 04:39:58 +02:00
updateViewPosition(newChunk);
2019-08-25 20:03:43 +02:00
// Load new chunks
for (int index : newChunks) {
final long chunkIndex = updatedVisibleChunks[index];
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
instance.loadOptionalChunk(chunkX, chunkZ, chunk -> {
2019-08-26 04:39:58 +02:00
if (chunk == null) {
// Cannot load chunk (auto load is not enabled)
return;
2019-08-25 20:03:43 +02:00
}
2019-09-01 06:18:41 +02:00
chunk.addViewer(this);
2019-08-26 04:39:58 +02:00
});
2019-08-25 20:03:43 +02:00
}
}
/**
* Refreshes the list of entities that the player should be able to see based on {@link MinecraftServer#getEntityViewDistance()}
* and {@link Entity#isAutoViewable()}.
*
* @param newChunk the new chunk of the player (can be the current one)
*/
public void refreshVisibleEntities(@NotNull Chunk newChunk) {
final int entityViewDistance = MinecraftServer.getEntityViewDistance();
final float maximalDistance = entityViewDistance * Chunk.CHUNK_SECTION_SIZE;
// Manage already viewable entities
this.viewableEntities.forEach(entity -> {
final float distance = entity.getDistance(this);
if (distance > maximalDistance) {
// Entity shouldn't be viewable anymore
if (isAutoViewable()) {
entity.removeViewer(this);
}
if (entity instanceof Player && entity.isAutoViewable()) {
removeViewer((Player) entity);
}
}
});
// Manage entities in unchecked chunks
final long[] chunksInRange = ChunkUtils.getChunksInRange(newChunk.toPosition(), entityViewDistance);
for (long chunkIndex : chunksInRange) {
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
if (chunk == null)
continue;
instance.getChunkEntities(chunk).forEach(entity -> {
if (isAutoViewable() && !entity.viewers.contains(this)) {
entity.addViewer(this);
}
if (entity instanceof Player && entity.isAutoViewable() && !viewers.contains(entity)) {
addViewer((Player) entity);
}
});
}
}
2019-08-21 16:50:52 +02:00
@Override
2020-10-24 10:46:23 +02:00
public void teleport(@NotNull Position position, @Nullable Runnable callback) {
2019-08-25 20:03:43 +02:00
super.teleport(position, () -> {
2019-08-26 04:39:58 +02:00
updatePlayerPosition();
2020-10-22 22:55:40 +02:00
OptionalCallback.execute(callback);
2019-08-25 20:03:43 +02:00
});
}
2019-08-22 14:52:32 +02:00
2019-08-25 20:03:43 +02:00
@Override
2020-10-24 10:46:23 +02:00
public void teleport(@NotNull Position position) {
2019-08-25 20:03:43 +02:00
teleport(position, null);
2019-08-11 09:33:27 +02:00
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player connection.
2020-05-29 23:17:14 +02:00
* <p>
* Used to send packets and get stuff related to the connection.
2020-05-29 23:17:14 +02:00
*
* @return the player connection
*/
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-03 15:25:24 +02:00
public PlayerConnection getPlayerConnection() {
return playerConnection;
}
2019-08-10 04:16:01 +02:00
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the player is online or not.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return true if the player is online, false otherwise
*/
2019-09-03 07:36:04 +02:00
public boolean isOnline() {
return playerConnection.isOnline();
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player settings.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return the player settings
*/
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-21 16:50:52 +02:00
public PlayerSettings getSettings() {
return settings;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player dimension.
2020-05-29 23:17:14 +02:00
*
* @return the player current dimension
*/
public DimensionType getDimensionType() {
return dimensionType;
2020-05-29 23:17:14 +02:00
}
2020-10-24 10:46:23 +02:00
@NotNull
2019-08-12 08:30:59 +02:00
public PlayerInventory getInventory() {
return inventory;
}
2020-05-24 19:59:50 +02:00
/**
* Used to get the player latency,
2020-10-15 21:16:31 +02:00
* computed by seeing how long it takes the client to answer the {@link KeepAlivePacket} packet.
2020-05-24 19:59:50 +02:00
*
* @return the player latency
*/
2020-04-26 06:34:08 +02:00
public int getLatency() {
return latency;
}
2020-05-24 19:59:50 +02:00
/**
* Gets the player {@link GameMode}.
2020-05-29 23:17:14 +02:00
*
* @return the player current gamemode
2020-05-24 19:59:50 +02:00
*/
2020-05-29 23:17:14 +02:00
public GameMode getGameMode() {
return gameMode;
2019-08-21 16:50:52 +02:00
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the player {@link GameMode}.
2020-05-29 23:17:14 +02:00
*
* @param gameMode the new player GameMode
2020-05-24 19:59:50 +02:00
*/
2020-10-24 10:46:23 +02:00
public void setGameMode(@NotNull GameMode gameMode) {
2020-05-29 23:17:14 +02:00
Check.notNull(gameMode, "GameMode cannot be null");
this.gameMode = gameMode;
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId());
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE);
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateGamemode(getUuid(), gameMode));
sendPacketToViewersAndSelf(infoPacket);
2019-08-12 08:30:59 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Gets if this player is in creative. Used for code readability.
2020-04-27 22:38:11 +02:00
*
2020-09-24 01:03:13 +02:00
* @return true if the player is in creative mode
*/
public boolean isCreative() {
return gameMode == GameMode.CREATIVE;
}
/**
2020-10-15 21:16:31 +02:00
* Changes the dimension of the player.
* Mostly unsafe since it requires sending chunks after.
*
* @param dimensionType the new player dimension
*/
2020-10-24 10:46:23 +02:00
protected void sendDimension(@NotNull DimensionType dimensionType) {
Check.notNull(dimensionType, "Dimension cannot be null!");
2020-09-24 01:50:25 +02:00
Check.argCondition(dimensionType.equals(getDimensionType()), "The dimension needs to be different than the current one!");
2020-04-22 02:42:58 +02:00
this.dimensionType = dimensionType;
2019-08-21 16:50:52 +02:00
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimensionType = dimensionType;
2019-08-21 16:50:52 +02:00
respawnPacket.gameMode = gameMode;
respawnPacket.isFlat = levelFlat;
2019-08-21 16:50:52 +02:00
playerConnection.sendPacket(respawnPacket);
}
2020-05-29 23:17:14 +02:00
/**
* Kicks the player with a reason.
2020-05-29 23:17:14 +02:00
*
2020-06-22 23:25:00 +02:00
* @param text the kick reason
2020-05-29 23:17:14 +02:00
*/
2020-10-24 10:46:23 +02:00
public void kick(@NotNull ColoredText text) {
2019-08-20 22:40:57 +02:00
DisconnectPacket disconnectPacket = new DisconnectPacket();
2020-08-05 18:25:11 +02:00
disconnectPacket.message = text;
2019-08-20 22:40:57 +02:00
playerConnection.sendPacket(disconnectPacket);
playerConnection.disconnect();
2020-06-21 22:11:56 +02:00
playerConnection.refreshOnline(false);
2019-08-20 22:40:57 +02:00
}
2020-05-29 23:17:14 +02:00
/**
* Kicks the player with a reason.
2020-05-29 23:17:14 +02:00
*
* @param message the kick reason
*/
2020-10-24 10:46:23 +02:00
public void kick(@NotNull String message) {
2020-06-22 23:25:00 +02:00
kick(ColoredText.of(message));
2020-05-04 18:10:19 +02:00
}
2020-05-23 04:20:01 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the current held slot for the player.
2020-05-23 04:20:01 +02:00
*
* @param slot the slot that the player has to held
* @throws IllegalArgumentException if {@code slot} is not between 0 and 8
*/
public void setHeldItemSlot(byte slot) {
2020-05-23 04:20:01 +02:00
Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "Slot has to be between 0 and 8");
2019-08-12 08:30:59 +02:00
HeldItemChangePacket heldItemChangePacket = new HeldItemChangePacket();
heldItemChangePacket.slot = slot;
playerConnection.sendPacket(heldItemChangePacket);
refreshHeldSlot(slot);
}
2020-05-23 04:20:01 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player held slot (0-8).
2020-05-29 23:17:14 +02:00
*
2020-05-23 04:20:01 +02:00
* @return the current held slot for the player
*/
public byte getHeldSlot() {
2020-05-23 04:20:01 +02:00
return heldSlot;
}
2019-09-21 20:42:27 +02:00
public void setTeam(Team team) {
2020-08-09 17:10:58 +02:00
super.setTeam(team);
2020-08-10 13:55:06 +02:00
if (team != null)
getPlayerConnection().sendPacket(team.createTeamsCreationPacket());
2019-09-21 20:42:27 +02:00
}
2020-08-09 17:10:58 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the tag below the name.
2020-08-09 17:10:58 +02:00
*
* @param belowNameTag The new below name tag
*/
public void setBelowNameTag(BelowNameTag belowNameTag) {
if (this.belowNameTag == belowNameTag) return;
2019-09-21 20:42:27 +02:00
2020-08-09 17:10:58 +02:00
if (this.belowNameTag != null) {
this.belowNameTag.removeViewer(this);
2019-09-21 20:42:27 +02:00
}
2020-08-09 17:10:58 +02:00
this.belowNameTag = belowNameTag;
2019-09-21 20:42:27 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Gets if the player is sneaking.
* <p>
2020-10-15 21:16:31 +02:00
* WARNING: this can be bypassed by hacked client, this is only what the client told the server.
*
* @return true if the player is sneaking
*/
public boolean isSneaking() {
return crouched;
}
/**
2020-10-15 21:16:31 +02:00
* Gets if the player is sprinting.
* <p>
2020-10-15 21:16:31 +02:00
* WARNING: this can be bypassed by hacked client, this is only what the client told the server.
*
* @return true if the player is sprinting
*/
public boolean isSprinting() {
return sprinting;
}
2020-05-23 04:20:01 +02:00
/**
2020-10-15 21:16:31 +02:00
* Used to get the {@link CustomBlock} that the player is currently mining.
2020-05-23 04:20:01 +02:00
*
* @return the currently mined {@link CustomBlock} by the player, null if there is not
*/
2020-10-24 10:46:23 +02:00
@Nullable
2019-08-18 23:52:11 +02:00
public CustomBlock getCustomBlockTarget() {
return targetCustomBlock;
}
/**
2020-10-15 21:16:31 +02:00
* Gets the player open inventory.
*
* @return the currently open inventory, null if there is not (player inventory is not detected)
*/
2020-10-24 10:46:23 +02:00
@Nullable
public Inventory getOpenInventory() {
return openInventory;
}
2020-05-12 17:12:11 +02:00
/**
2020-10-15 21:16:31 +02:00
* Opens the specified Inventory, close the previous inventory if existing.
2020-05-12 17:12:11 +02:00
*
* @param inventory the inventory to open
* @return true if the inventory has been opened/sent to the player, false otherwise (cancelled by event)
*/
2020-10-24 10:46:23 +02:00
public boolean openInventory(@NotNull Inventory inventory) {
2020-05-23 04:20:01 +02:00
Check.notNull(inventory, "Inventory cannot be null, use Player#closeInventory() to close current");
2019-08-13 17:52:09 +02:00
2020-05-12 17:12:11 +02:00
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(this, inventory);
callCancellableEvent(InventoryOpenEvent.class, inventoryOpenEvent, () -> {
if (getOpenInventory() != null) {
closeInventory();
}
Inventory newInventory = inventoryOpenEvent.getInventory();
2020-10-24 16:58:27 +02:00
if (newInventory == null) {
2020-10-24 16:33:13 +02:00
// just close the inventory
return;
}
2020-05-12 17:12:11 +02:00
OpenWindowPacket openWindowPacket = new OpenWindowPacket();
openWindowPacket.windowId = newInventory.getWindowId();
openWindowPacket.windowType = newInventory.getInventoryType().getWindowType();
openWindowPacket.title = newInventory.getTitle();
2020-05-12 17:12:11 +02:00
playerConnection.sendPacket(openWindowPacket);
newInventory.addViewer(this);
2020-05-28 20:09:52 +02:00
this.openInventory = newInventory;
2020-05-12 17:12:11 +02:00
});
2019-08-13 17:52:09 +02:00
2020-05-12 17:12:11 +02:00
return !inventoryOpenEvent.isCancelled();
2019-08-13 17:52:09 +02:00
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* Closes the current inventory if there is any.
* It closes the player inventory (when opened) if {@link #getOpenInventory()} returns null.
2020-05-15 18:03:28 +02:00
*/
2019-08-13 17:52:09 +02:00
public void closeInventory() {
Inventory openInventory = getOpenInventory();
// Drop cursor item when closing inventory
ItemStack cursorItem;
if (openInventory == null) {
cursorItem = getInventory().getCursorItem();
getInventory().setCursorItem(ItemStack.getAirItem());
} else {
cursorItem = openInventory.getCursorItem(this);
openInventory.setCursorItem(this, ItemStack.getAirItem());
}
if (!cursorItem.isAir()) {
// Add item to inventory if he hasn't been able to drop it
if (!dropItem(cursorItem)) {
getInventory().addItemStack(cursorItem);
}
}
2019-08-13 17:52:09 +02:00
CloseWindowPacket closeWindowPacket = new CloseWindowPacket();
if (openInventory == null) {
closeWindowPacket.windowId = 0;
} else {
2020-04-28 19:22:47 +02:00
closeWindowPacket.windowId = openInventory.getWindowId();
openInventory.removeViewer(this); // Clear cache
2020-05-28 20:09:52 +02:00
this.openInventory = null;
2019-08-13 17:52:09 +02:00
}
playerConnection.sendPacket(closeWindowPacket);
2019-08-14 06:50:03 +02:00
inventory.update();
this.didCloseInventory = true;
}
/**
* Used internally to prevent an inventory click to be processed
2020-10-15 21:16:31 +02:00
* when the inventory listeners closed the inventory.
* <p>
2020-10-15 21:16:31 +02:00
* Should only be used within an inventory listener (event or condition).
*
* @return true if the inventory has been closed, false otherwise
*/
public boolean didCloseInventory() {
return didCloseInventory;
}
/**
2020-10-15 21:16:31 +02:00
* Used internally to reset the didCloseInventory field.
* <p>
2020-10-15 21:16:31 +02:00
* Shouldn't be used externally without proper understanding of its consequence.
*
* @param didCloseInventory the new didCloseInventory field
*/
public void UNSAFE_changeDidCloseInventory(boolean didCloseInventory) {
this.didCloseInventory = didCloseInventory;
2019-08-13 17:52:09 +02:00
}
2020-05-23 04:20:01 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player viewable chunks.
* <p>
* WARNING: adding or removing a chunk there will not load/unload it,
2020-10-15 21:16:31 +02:00
* use {@link Chunk#addViewer(Player)} or {@link Chunk#removeViewer(Player)}.
2020-05-29 23:17:14 +02:00
*
* @return a {@link Set} containing all the chunks that the player sees
2020-05-23 04:20:01 +02:00
*/
2019-09-01 06:18:41 +02:00
public Set<Chunk> getViewableChunks() {
return viewableChunks;
2019-09-01 06:18:41 +02:00
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sends a {@link UpdateViewPositionPacket} to the player.
2020-05-28 20:09:52 +02:00
*
* @param chunk the chunk to update the view
*/
2020-10-24 10:46:23 +02:00
public void updateViewPosition(@NotNull Chunk chunk) {
2020-05-28 20:09:52 +02:00
UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket();
updateViewPositionPacket.chunkX = chunk.getChunkX();
updateViewPositionPacket.chunkZ = chunk.getChunkZ();
playerConnection.sendPacket(updateViewPositionPacket);
2019-08-20 22:40:57 +02:00
}
/**
* Used for synchronization purpose, mainly for teleportation
*/
2019-08-26 04:39:58 +02:00
protected void updatePlayerPosition() {
PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket();
2020-10-31 18:03:15 +01:00
positionAndLookPacket.position = position.copy(); // clone needed to prevent synchronization issue
2019-08-26 04:39:58 +02:00
positionAndLookPacket.flags = 0x00;
2020-10-12 06:41:47 +02:00
positionAndLookPacket.teleportId = teleportId.incrementAndGet();
2019-08-26 04:39:58 +02:00
playerConnection.sendPacket(positionAndLookPacket);
}
/**
2020-10-15 21:16:31 +02:00
* Gets the player permission level.
2020-05-29 23:17:14 +02:00
*
* @return the player permission level
*/
public int getPermissionLevel() {
return permissionLevel;
}
/**
2020-10-15 21:16:31 +02:00
* Changes the player permission level.
*
* @param permissionLevel the new player permission level
2020-11-07 04:42:48 +01:00
* @throws IllegalArgumentException if {@code permissionLevel} is not between 0 and 4
*/
public void setPermissionLevel(int permissionLevel) {
2020-05-23 04:20:01 +02:00
Check.argCondition(!MathUtils.isBetween(permissionLevel, 0, 4), "permissionLevel has to be between 0 and 4");
this.permissionLevel = permissionLevel;
// Magic values: https://wiki.vg/Entity_statuses#Player
2020-07-22 17:39:48 +02:00
// TODO remove magic values
final byte permissionLevelStatus = (byte) (24 + permissionLevel);
triggerStatus(permissionLevelStatus);
}
/**
2020-10-15 21:16:31 +02:00
* Sets or remove the reduced debug screen.
2020-05-29 23:17:14 +02:00
*
* @param reduced should the player has the reduced debug screen
*/
public void setReducedDebugScreenInformation(boolean reduced) {
this.reducedDebugScreenInformation = reduced;
// Magic values: https://wiki.vg/Entity_statuses#Player
2020-07-22 17:39:48 +02:00
// TODO remove magic values
final byte debugScreenStatus = (byte) (reduced ? 22 : 23);
triggerStatus(debugScreenStatus);
}
/**
2020-10-15 21:16:31 +02:00
* Gets if the player has the reduced debug screen.
2020-05-29 23:17:14 +02:00
*
* @return true if the player has the reduced debug screen, false otherwise
*/
public boolean hasReducedDebugScreenInformation() {
return reducedDebugScreenInformation;
}
/**
2020-10-15 21:16:31 +02:00
* The invulnerable field appear in the {@link PlayerAbilitiesPacket} packet.
*
* @return true if the player is invulnerable, false otherwise
*/
2019-09-23 19:56:08 +02:00
public boolean isInvulnerable() {
return super.isInvulnerable();
2019-09-23 19:56:08 +02:00
}
/**
* This do update the {@code invulnerable} field in the packet {@link PlayerAbilitiesPacket}
2020-10-15 21:16:31 +02:00
* and prevent the player from receiving damage.
*
* @param invulnerable should the player be invulnerable
*/
2019-09-23 19:56:08 +02:00
public void setInvulnerable(boolean invulnerable) {
super.setInvulnerable(invulnerable);
2019-09-23 19:56:08 +02:00
refreshAbilities();
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the player is currently flying.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return true if the player if flying, false otherwise
*/
2019-09-23 19:56:08 +02:00
public boolean isFlying() {
return flying;
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sets the player flying.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @param flying should the player fly
*/
2019-09-23 19:56:08 +02:00
public void setFlying(boolean flying) {
this.flying = flying;
2019-09-23 19:56:08 +02:00
refreshAbilities();
}
/**
2020-10-15 21:16:31 +02:00
* Updates the internal flying field.
* <p>
2020-10-15 21:16:31 +02:00
* Mostly unsafe since there is nothing to backup the value, used internally for creative players.
*
* @param flying the new flying field
* @see #setFlying(boolean) instead
*/
public void refreshFlying(boolean flying) {
this.flying = flying;
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the player is allowed to fly.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @return true if the player if allowed to fly, false otherwise
*/
2019-09-23 19:56:08 +02:00
public boolean isAllowFlying() {
return allowFlying;
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Allows or forbid the player to fly.
2020-05-29 23:17:14 +02:00
*
2020-05-24 19:59:50 +02:00
* @param allowFlying should the player be allowed to fly
*/
2019-09-23 19:56:08 +02:00
public void setAllowFlying(boolean allowFlying) {
this.allowFlying = allowFlying;
refreshAbilities();
}
public boolean isInstantBreak() {
return instantBreak;
}
2020-05-24 19:59:50 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the player ability "Creative Mode".
2020-05-24 19:59:50 +02:00
* <a href="https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29">see</a>
* <p>
2020-10-15 21:16:31 +02:00
* WARNING: this has nothing to do with {@link CustomBlock#getBreakDelay(Player, BlockPosition, byte, Set)}.
2020-05-24 19:59:50 +02:00
*
2020-08-07 09:14:50 +02:00
* @param instantBreak true to allow instant break
2020-05-24 19:59:50 +02:00
*/
2019-09-23 19:56:08 +02:00
public void setInstantBreak(boolean instantBreak) {
this.instantBreak = instantBreak;
refreshAbilities();
}
/**
2020-10-15 21:16:31 +02:00
* Gets the player flying speed.
2020-05-29 23:17:14 +02:00
*
* @return the flying speed of the player
*/
2019-09-23 19:56:08 +02:00
public float getFlyingSpeed() {
return flyingSpeed;
}
/**
2020-10-15 21:16:31 +02:00
* Updates the internal field and send a {@link PlayerAbilitiesPacket} with the new flying speed.
*
* @param flyingSpeed the new flying speed of the player
*/
2019-09-23 19:56:08 +02:00
public void setFlyingSpeed(float flyingSpeed) {
this.flyingSpeed = flyingSpeed;
refreshAbilities();
}
2020-06-25 21:05:58 +02:00
public float getWalkingSpeed() {
return walkingSpeed;
2019-09-23 19:56:08 +02:00
}
2020-06-25 21:05:58 +02:00
public void setWalkingSpeed(float walkingSpeed) {
this.walkingSpeed = walkingSpeed;
2019-09-23 19:56:08 +02:00
refreshAbilities();
}
2020-05-15 18:03:28 +02:00
/**
2020-10-15 21:16:31 +02:00
* This is the map used to send the statistic packet.
* It is possible to add/remove/change statistic value directly into it.
2020-05-15 18:03:28 +02:00
*
* @return the modifiable statistic map
*/
@NotNull
2020-04-17 15:58:07 +02:00
public Map<PlayerStatistic, Integer> getStatisticValueMap() {
return statisticValueMap;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player vehicle information.
2020-05-29 23:17:14 +02:00
*
* @return the player vehicle information
*/
@NotNull
2020-03-29 20:58:30 +02:00
public PlayerVehicleInformation getVehicleInformation() {
return vehicleInformation;
}
2020-10-05 03:59:47 +02:00
/**
2020-10-15 21:16:31 +02:00
* Sends to the player a {@link PlayerAbilitiesPacket} with all the updated fields
* (walkingSpeed set to 0.1).
2020-10-05 03:59:47 +02:00
*/
2020-04-05 17:46:29 +02:00
protected void refreshAbilities() {
2019-09-23 19:56:08 +02:00
PlayerAbilitiesPacket playerAbilitiesPacket = new PlayerAbilitiesPacket();
playerAbilitiesPacket.invulnerable = invulnerable;
playerAbilitiesPacket.flying = flying;
playerAbilitiesPacket.allowFlying = allowFlying;
playerAbilitiesPacket.instantBreak = instantBreak;
playerAbilitiesPacket.flyingSpeed = flyingSpeed;
2020-06-26 01:40:33 +02:00
playerAbilitiesPacket.walkingSpeed = 0.1f;
2019-09-23 19:56:08 +02:00
playerConnection.sendPacket(playerAbilitiesPacket);
}
2020-05-15 18:03:28 +02:00
/**
2020-06-01 17:11:43 +02:00
* All packets in the queue are executed in the {@link #update(long)} method
2020-10-15 21:16:31 +02:00
* It is used internally to add all received packet from the client.
* Could be used to "simulate" a received packet, but to use at your own risk.
2020-05-15 18:03:28 +02:00
*
* @param packet the packet to add in the queue
*/
2020-10-24 11:19:54 +02:00
public void addPacketToQueue(@NotNull ClientPlayPacket packet) {
2019-08-23 15:37:38 +02:00
this.packets.add(packet);
}
2020-05-28 19:15:55 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the storage player latency and update its tab value.
2020-05-28 19:15:55 +02:00
*
* @param latency the new player latency
*/
2020-04-26 06:34:08 +02:00
public void refreshLatency(int latency) {
this.latency = latency;
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY);
playerInfoPacket.playerInfos.add(new PlayerInfoPacket.UpdateLatency(getUuid(), latency));
sendPacketToViewersAndSelf(playerInfoPacket);
}
2019-08-10 04:16:01 +02:00
public void refreshOnGround(boolean onGround) {
this.onGround = onGround;
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Used to change internally the last sent last keep alive id.
2020-05-28 20:09:52 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* Warning: could lead to have the player kicked because of a wrong keep alive packet.
2020-05-28 20:09:52 +02:00
*
* @param lastKeepAlive the new lastKeepAlive id
*/
public void refreshKeepAlive(long lastKeepAlive) {
this.lastKeepAlive = lastKeepAlive;
2020-05-29 02:11:41 +02:00
this.answerKeepAlive = false;
}
public boolean didAnswerKeepAlive() {
return answerKeepAlive;
}
public void refreshAnswerKeepAlive(boolean answerKeepAlive) {
this.answerKeepAlive = answerKeepAlive;
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the held item for the player viewers
* Also cancel eating if {@link #isEating()} was true.
2020-05-28 20:09:52 +02:00
* <p>
* Warning: the player will not be noticed by this chance, only his viewers,
2020-10-15 21:16:31 +02:00
* see instead: {@link #setHeldItemSlot(byte)}.
2020-05-28 20:09:52 +02:00
*
* @param slot the new held slot
*/
public void refreshHeldSlot(byte slot) {
2019-08-12 08:30:59 +02:00
this.heldSlot = slot;
2019-08-20 22:40:57 +02:00
syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND);
refreshEating(false);
2019-08-12 08:30:59 +02:00
}
public void refreshEating(boolean isEating, long eatingTime) {
this.isEating = isEating;
if (isEating) {
this.startEatingTime = System.currentTimeMillis();
this.eatingTime = eatingTime;
} else {
this.startEatingTime = 0;
}
}
public void refreshEating(boolean isEating) {
refreshEating(isEating, defaultEatingTime);
}
2020-05-28 20:09:52 +02:00
/**
* Used to call {@link ItemUpdateStateEvent} with the proper item
2020-10-15 21:16:31 +02:00
* It does check which hand to get the item to update.
2020-05-28 20:09:52 +02:00
*
* @param allowFood true if food should be updated, false otherwise
* @return the called {@link ItemUpdateStateEvent},
* null if there is no item to update the state
*/
2020-10-24 11:19:54 +02:00
@Nullable
public ItemUpdateStateEvent callItemUpdateStateEvent(boolean allowFood) {
2020-07-23 05:36:15 +02:00
final Material mainHandMat = getItemInMainHand().getMaterial();
final Material offHandMat = getItemInOffHand().getMaterial();
final boolean isOffhand = offHandMat.hasState();
2020-07-23 05:36:15 +02:00
final ItemStack updatedItem = isOffhand ? getItemInOffHand() :
mainHandMat.hasState() ? getItemInMainHand() : null;
if (updatedItem == null) // No item with state, cancel
return null;
2020-07-23 05:36:15 +02:00
final boolean isFood = updatedItem.getMaterial().isFood();
if (isFood && !allowFood)
return null;
2020-08-19 01:24:51 +02:00
final Hand hand = isOffhand ? Hand.OFF : Hand.MAIN;
ItemUpdateStateEvent itemUpdateStateEvent = new ItemUpdateStateEvent(this, hand, updatedItem);
callEvent(ItemUpdateStateEvent.class, itemUpdateStateEvent);
return itemUpdateStateEvent;
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Makes the player digging a custom block, see {@link #resetTargetBlock()} to rewind.
2020-05-28 20:09:52 +02:00
*
* @param targetCustomBlock the custom block to dig
* @param targetBlockPosition the custom block position
* @param breakers the breakers of the block, can be null if {@code this} is the only breaker
2020-05-28 20:09:52 +02:00
*/
2020-10-24 11:19:54 +02:00
public void setTargetBlock(@NotNull CustomBlock targetCustomBlock, @NotNull BlockPosition targetBlockPosition, @Nullable Set<Player> breakers) {
2019-08-18 23:52:11 +02:00
this.targetCustomBlock = targetCustomBlock;
2019-08-18 20:38:09 +02:00
this.targetBlockPosition = targetBlockPosition;
refreshBreakDelay(breakers);
}
/**
2020-10-15 21:16:31 +02:00
* Refreshes the break delay for the next block break stage.
*
* @param breakers the list of breakers, can be null if {@code this} is the only breaker
*/
2020-10-24 11:19:54 +02:00
private void refreshBreakDelay(@Nullable Set<Player> breakers) {
breakers = breakers == null ? targetBreakers : breakers;
// Refresh the last tick update
this.targetBlockBreakCount = 0;
// Get if multi player breaking is enabled
final boolean multiPlayerBreaking = targetCustomBlock.enableMultiPlayerBreaking();
2020-10-15 08:48:13 +02:00
// Get the stage from the custom block object if it is, otherwise use the local field
final byte stage = multiPlayerBreaking ? targetCustomBlock.getBreakStage(instance, targetBlockPosition) : targetStage;
// Retrieve the break delay for the current stage
2020-09-24 01:50:25 +02:00
this.targetBreakDelay = targetCustomBlock.getBreakDelay(this, targetBlockPosition, stage, breakers);
2019-08-18 20:38:09 +02:00
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Resets data from the current block the player is mining.
* If the currently mined block (or if there isn't any) is not a {@link CustomBlock}, nothing happen.
2020-05-28 20:09:52 +02:00
*/
2019-08-18 23:52:11 +02:00
public void resetTargetBlock() {
// Remove effect
PlayerDiggingListener.removeEffect(this);
if (targetCustomBlock != null) {
targetCustomBlock.stopDigging(instance, targetBlockPosition, this);
2020-05-28 19:29:29 +02:00
this.targetCustomBlock = null;
this.targetBlockPosition = null;
this.targetBreakDelay = 0;
this.targetBlockBreakCount = 0;
this.targetStage = 0;
2020-05-28 19:29:29 +02:00
}
2019-08-18 23:52:11 +02:00
}
2019-08-30 01:17:46 +02:00
public void refreshVehicleSteer(float sideways, float forward, boolean jump, boolean unmount) {
2020-03-29 20:58:30 +02:00
this.vehicleInformation.refresh(sideways, forward, jump, unmount);
}
2020-05-28 20:09:52 +02:00
/**
* @return the chunk range of the viewers,
* which is {@link MinecraftServer#getChunkViewDistance()} or {@link PlayerSettings#getViewDistance()}
2020-05-28 20:09:52 +02:00
* based on which one is the lowest
*/
2019-09-01 06:18:41 +02:00
public int getChunkRange() {
final int serverRange = MinecraftServer.getChunkViewDistance();
2020-07-23 05:36:15 +02:00
final int playerRange = getSettings().viewDistance;
2019-09-02 06:02:12 +02:00
if (playerRange == 0) {
2020-04-16 14:51:21 +02:00
return serverRange; // Didn't receive settings packet yet (is the case on login)
2019-09-02 06:02:12 +02:00
} else {
2020-04-16 14:51:21 +02:00
return Math.min(playerRange, serverRange);
2019-09-02 06:02:12 +02:00
}
2019-09-01 06:18:41 +02:00
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the last sent keep alive id.
2020-05-29 23:17:14 +02:00
*
2020-05-28 20:09:52 +02:00
* @return the last keep alive id sent to the player
*/
public long getLastKeepAlive() {
return lastKeepAlive;
}
/**
2020-10-15 21:16:31 +02:00
* Gets the packet to add the player from the tab-list.
*
* @return a {@link PlayerInfoPacket} to add the player
*/
2020-10-24 11:19:54 +02:00
@NotNull
2020-05-30 19:51:45 +02:00
protected PlayerInfoPacket getAddPlayerToList() {
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
PlayerInfoPacket.AddPlayer addPlayer =
new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), getLatency());
2020-07-06 19:15:13 +02:00
addPlayer.displayName = displayName;
2020-08-20 19:06:55 +02:00
// Skin support
if (skin != null) {
final String textures = skin.getTextures();
final String signature = skin.getSignature();
PlayerInfoPacket.AddPlayer.Property prop =
new PlayerInfoPacket.AddPlayer.Property("textures", textures, signature);
addPlayer.properties.add(prop);
}
playerInfoPacket.playerInfos.add(addPlayer);
return playerInfoPacket;
}
/**
2020-10-15 21:16:31 +02:00
* Gets the packet to remove the player from the tab-list.
*
* @return a {@link PlayerInfoPacket} to remove the player
*/
2020-10-24 11:19:54 +02:00
@NotNull
protected PlayerInfoPacket getRemovePlayerToList() {
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
PlayerInfoPacket.RemovePlayer removePlayer =
new PlayerInfoPacket.RemovePlayer(getUuid());
playerInfoPacket.playerInfos.add(removePlayer);
return playerInfoPacket;
}
/**
2020-10-15 21:16:31 +02:00
* Sends all the related packet to have the player sent to another with related data
* (create player, spawn position, velocity, metadata, equipments, passengers, team).
* <p>
2020-10-15 21:16:31 +02:00
* WARNING: this alone does not sync the player, please use {@link #addViewer(Player)}.
*
* @param connection the connection to show the player to
*/
2020-10-24 11:19:54 +02:00
protected void showPlayer(@NotNull PlayerConnection connection) {
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
spawnPlayerPacket.entityId = getEntityId();
spawnPlayerPacket.playerUuid = getUuid();
spawnPlayerPacket.position = getPosition();
2020-05-30 19:51:45 +02:00
connection.sendPacket(getAddPlayerToList());
connection.sendPacket(spawnPlayerPacket);
connection.sendPacket(getVelocityPacket());
connection.sendPacket(getMetadataPacket());
// Equipments synchronization
syncEquipments(connection);
if (hasPassenger()) {
connection.sendPacket(getPassengersPacket());
}
2020-06-30 01:25:23 +02:00
// Team
2020-08-09 17:10:58 +02:00
if (this.getTeam() != null)
connection.sendPacket(this.getTeam().createTeamsCreationPacket());
2020-06-30 01:25:23 +02:00
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
connection.sendPacket(entityHeadLookPacket);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getItemInMainHand() {
return inventory.getItemInMainHand();
}
@Override
2020-10-24 11:19:54 +02:00
public void setItemInMainHand(@NotNull ItemStack itemStack) {
inventory.setItemInMainHand(itemStack);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getItemInOffHand() {
return inventory.getItemInOffHand();
}
@Override
2020-10-24 11:19:54 +02:00
public void setItemInOffHand(@NotNull ItemStack itemStack) {
inventory.setItemInOffHand(itemStack);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getHelmet() {
return inventory.getHelmet();
}
@Override
2020-10-24 11:19:54 +02:00
public void setHelmet(@NotNull ItemStack itemStack) {
inventory.setHelmet(itemStack);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getChestplate() {
return inventory.getChestplate();
}
@Override
2020-10-24 11:19:54 +02:00
public void setChestplate(@NotNull ItemStack itemStack) {
inventory.setChestplate(itemStack);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getLeggings() {
return inventory.getLeggings();
}
@Override
2020-10-24 11:19:54 +02:00
public void setLeggings(@NotNull ItemStack itemStack) {
inventory.setLeggings(itemStack);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getBoots() {
return inventory.getBoots();
}
@Override
2020-10-24 11:19:54 +02:00
public void setBoots(@NotNull ItemStack itemStack) {
inventory.setBoots(itemStack);
}
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Represents the main or off hand of the player.
2020-05-28 20:09:52 +02:00
*/
public enum Hand {
MAIN,
OFF
}
2019-08-21 16:50:52 +02:00
2020-05-28 20:09:52 +02:00
public enum FacePoint {
FEET,
EYE
}
2020-04-16 14:51:21 +02:00
// Settings enum
2020-05-28 20:09:52 +02:00
/**
2020-10-15 21:16:31 +02:00
* Represents where is located the main hand of the player (can be changed in Minecraft option).
2020-05-28 20:09:52 +02:00
*/
2019-08-21 16:50:52 +02:00
public enum MainHand {
LEFT,
2020-04-10 12:45:04 +02:00
RIGHT
2019-08-21 16:50:52 +02:00
}
public enum ChatMode {
ENABLED,
COMMANDS_ONLY,
2020-04-10 12:45:04 +02:00
HIDDEN
2019-08-21 16:50:52 +02:00
}
public class PlayerSettings {
private String locale;
private byte viewDistance;
private ChatMode chatMode;
private boolean chatColors;
private byte displayedSkinParts;
private MainHand mainHand;
private boolean firstRefresh = true;
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* The player game language.
2020-05-29 23:17:14 +02:00
*
* @return the player locale
*/
2019-08-21 16:50:52 +02:00
public String getLocale() {
return locale;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player view distance.
2020-05-29 23:17:14 +02:00
*
* @return the player view distance
*/
2019-08-21 16:50:52 +02:00
public byte getViewDistance() {
return viewDistance;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player chat mode.
2020-05-29 23:17:14 +02:00
*
* @return the player chat mode
*/
2019-08-21 16:50:52 +02:00
public ChatMode getChatMode() {
return chatMode;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets if the player has chat colors enabled.
2020-05-29 23:17:14 +02:00
*
* @return true if chat colors are enabled, false otherwise
*/
2019-08-21 16:50:52 +02:00
public boolean hasChatColors() {
return chatColors;
}
public byte getDisplayedSkinParts() {
return displayedSkinParts;
}
2020-05-29 23:17:14 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the player main hand.
2020-05-29 23:17:14 +02:00
*
* @return the player main hand
*/
2019-08-21 16:50:52 +02:00
public MainHand getMainHand() {
return mainHand;
}
2020-06-30 01:25:23 +02:00
/**
2020-10-15 21:16:31 +02:00
* Changes the player settings internally.
2020-06-30 01:25:23 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* WARNING: the player will not be noticed by this change, probably unsafe.
2020-06-30 01:25:23 +02:00
*
* @param locale the player locale
* @param viewDistance the player view distance
* @param chatMode the player chat mode
* @param chatColors the player chat colors
* @param displayedSkinParts the player displayed skin parts
2020-07-27 02:28:03 +02:00
* @param mainHand the player main hand
2020-06-30 01:25:23 +02:00
*/
public void refresh(String locale, byte viewDistance, ChatMode chatMode, boolean chatColors, byte displayedSkinParts, MainHand mainHand) {
final boolean viewDistanceChanged = !firstRefresh && this.viewDistance != viewDistance;
2020-06-30 01:25:23 +02:00
this.locale = locale;
this.viewDistance = viewDistance;
this.chatMode = chatMode;
this.chatColors = chatColors;
this.displayedSkinParts = displayedSkinParts;
this.mainHand = mainHand;
sendMetadataIndex(16);
this.firstRefresh = false;
// Client changed his view distance in the settings
if (viewDistanceChanged) {
final Chunk playerChunk = getChunk();
if (playerChunk != null) {
refreshVisibleChunks(playerChunk);
}
}
2020-06-30 01:25:23 +02:00
}
}
2019-08-21 16:50:52 +02:00
2019-08-03 15:25:24 +02:00
}