2020-04-24 03:25:58 +02:00
|
|
|
package net.minestom.server.entity;
|
2019-08-03 15:25:24 +02:00
|
|
|
|
2021-03-01 16:25:23 +01:00
|
|
|
import net.kyori.adventure.audience.MessageType;
|
2021-03-01 18:25:55 +01:00
|
|
|
import net.kyori.adventure.bossbar.BossBar;
|
2021-03-12 16:36:22 +01:00
|
|
|
import net.kyori.adventure.identity.Identified;
|
2021-03-01 16:25:23 +01:00
|
|
|
import net.kyori.adventure.identity.Identity;
|
|
|
|
import net.kyori.adventure.inventory.Book;
|
2021-06-10 14:53:34 +02:00
|
|
|
import net.kyori.adventure.pointer.Pointers;
|
2021-03-27 14:59:08 +01:00
|
|
|
import net.kyori.adventure.sound.Sound;
|
2021-03-02 16:56:20 +01:00
|
|
|
import net.kyori.adventure.sound.SoundStop;
|
2021-03-01 16:25:23 +01:00
|
|
|
import net.kyori.adventure.text.Component;
|
2021-03-11 18:07:04 +01:00
|
|
|
import net.kyori.adventure.text.event.HoverEvent;
|
|
|
|
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
|
|
|
|
import net.kyori.adventure.text.event.HoverEventSource;
|
2021-04-04 15:06:23 +02:00
|
|
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
2021-03-01 16:25:23 +01:00
|
|
|
import net.kyori.adventure.title.Title;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.MinecraftServer;
|
2020-10-25 12:28:06 +01:00
|
|
|
import net.minestom.server.advancements.AdvancementTab;
|
2021-03-05 20:59:28 +01:00
|
|
|
import net.minestom.server.adventure.AdventurePacketConvertor;
|
2021-03-03 17:32:51 +01:00
|
|
|
import net.minestom.server.adventure.Localizable;
|
2021-03-26 17:00:07 +01:00
|
|
|
import net.minestom.server.adventure.audience.Audiences;
|
2021-03-11 16:56:12 +01:00
|
|
|
import net.minestom.server.attribute.Attribute;
|
2020-07-01 01:17:58 +02:00
|
|
|
import net.minestom.server.chat.ChatParser;
|
2020-06-22 23:25:00 +02:00
|
|
|
import net.minestom.server.chat.ColoredText;
|
2020-10-06 03:43:57 +02:00
|
|
|
import net.minestom.server.chat.JsonMessage;
|
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;
|
2021-07-08 18:56:40 +02:00
|
|
|
import net.minestom.server.coordinate.Point;
|
|
|
|
import net.minestom.server.coordinate.Pos;
|
|
|
|
import net.minestom.server.coordinate.Vec;
|
2020-04-28 16:08:21 +02:00
|
|
|
import net.minestom.server.effects.Effects;
|
2020-04-27 20:33:08 +02:00
|
|
|
import net.minestom.server.entity.damage.DamageType;
|
2020-10-11 15:27:23 +02:00
|
|
|
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
2021-06-14 21:49:16 +02:00
|
|
|
import net.minestom.server.entity.metadata.PlayerMeta;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
|
2021-06-04 03:48:51 +02:00
|
|
|
import net.minestom.server.event.EventDispatcher;
|
2020-05-12 17:12:11 +02:00
|
|
|
import net.minestom.server.event.inventory.InventoryOpenEvent;
|
2020-05-07 15:46:21 +02:00
|
|
|
import net.minestom.server.event.item.ItemDropEvent;
|
2020-05-12 14:12:17 +02:00
|
|
|
import net.minestom.server.event.item.ItemUpdateStateEvent;
|
2020-05-07 15:46:21 +02:00
|
|
|
import net.minestom.server.event.item.PickupExperienceEvent;
|
2020-12-13 23:01:01 +01:00
|
|
|
import net.minestom.server.event.player.*;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.instance.Chunk;
|
|
|
|
import net.minestom.server.instance.Instance;
|
|
|
|
import net.minestom.server.inventory.Inventory;
|
|
|
|
import net.minestom.server.inventory.PlayerInventory;
|
|
|
|
import net.minestom.server.item.ItemStack;
|
2020-05-12 14:12:17 +02:00
|
|
|
import net.minestom.server.item.Material;
|
2021-04-11 19:13:50 +02:00
|
|
|
import net.minestom.server.item.metadata.WrittenBookMeta;
|
2021-05-31 18:53:57 +02:00
|
|
|
import net.minestom.server.message.ChatMessageType;
|
|
|
|
import net.minestom.server.message.ChatPosition;
|
2021-05-05 19:21:38 +02:00
|
|
|
import net.minestom.server.message.Messenger;
|
2020-10-11 15:27:23 +02:00
|
|
|
import net.minestom.server.network.ConnectionManager;
|
2021-01-06 19:02:35 +01:00
|
|
|
import net.minestom.server.network.ConnectionState;
|
2020-10-11 15:27:23 +02:00
|
|
|
import net.minestom.server.network.PlayerProvider;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
2020-10-16 11:37:00 +02:00
|
|
|
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;
|
2021-01-06 19:02:35 +01:00
|
|
|
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
2020-12-13 23:01:01 +01:00
|
|
|
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-05-25 02:37:57 +02:00
|
|
|
import net.minestom.server.recipe.Recipe;
|
|
|
|
import net.minestom.server.recipe.RecipeManager;
|
2020-05-31 19:53:59 +02:00
|
|
|
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.SoundCategory;
|
2021-04-07 18:35:19 +02:00
|
|
|
import net.minestom.server.sound.SoundEvent;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.stat.PlayerStatistic;
|
2020-12-13 23:31:37 +01:00
|
|
|
import net.minestom.server.utils.*;
|
2021-07-11 03:35:17 +02:00
|
|
|
import net.minestom.server.utils.async.AsyncUtils;
|
2020-05-25 13:46:48 +02:00
|
|
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
2020-12-23 10:40:50 +01:00
|
|
|
import net.minestom.server.utils.entity.EntityUtils;
|
2021-04-12 11:49:04 +02:00
|
|
|
import net.minestom.server.utils.identity.NamedAndIdentified;
|
2020-10-31 00:23:52 +01:00
|
|
|
import net.minestom.server.utils.instance.InstanceUtils;
|
2021-04-10 18:55:26 +02:00
|
|
|
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
|
2021-03-31 19:17:37 +02:00
|
|
|
import net.minestom.server.utils.time.Cooldown;
|
2020-11-14 09:02:29 +01:00
|
|
|
import net.minestom.server.utils.time.TimeUnit;
|
2020-05-23 04:20:01 +02:00
|
|
|
import net.minestom.server.utils.validate.Check;
|
2020-07-13 14:12:21 +02:00
|
|
|
import net.minestom.server.world.DimensionType;
|
2021-05-01 00:05:49 +02:00
|
|
|
import org.jetbrains.annotations.ApiStatus;
|
2020-12-02 21:28:36 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
|
2020-12-28 10:40:50 +01:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2021-06-30 01:31:09 +02:00
|
|
|
import java.time.Duration;
|
2020-12-13 23:01:01 +01:00
|
|
|
import java.util.*;
|
2021-07-11 02:59:24 +02:00
|
|
|
import java.util.concurrent.CompletableFuture;
|
2021-03-26 08:49:27 +01:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2021-06-20 22:03:14 +02:00
|
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
2020-12-13 23:01:01 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2021-03-11 18:07:04 +01:00
|
|
|
import java.util.function.UnaryOperator;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2020-10-11 15:27:23 +02:00
|
|
|
/**
|
2020-12-13 23:01:01 +01:00
|
|
|
* Those are the major actors of the server,
|
|
|
|
* they are not necessary backed by a {@link NettyPlayerConnection} as shown by {@link FakePlayer}.
|
|
|
|
* <p>
|
|
|
|
* You can easily create your own implementation of this and use it with {@link ConnectionManager#setPlayerProvider(PlayerProvider)}.
|
2020-10-11 15:27:23 +02:00
|
|
|
*/
|
2021-04-12 11:49:04 +02:00
|
|
|
public class Player extends LivingEntity implements CommandSender, Localizable, HoverEventSource<ShowEntity>, Identified, NamedAndIdentified {
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
private long lastKeepAlive;
|
|
|
|
private boolean answerKeepAlive;
|
|
|
|
|
|
|
|
private String username;
|
2021-04-12 16:08:27 +02:00
|
|
|
private Component usernameComponent;
|
2020-12-13 23:01:01 +01:00
|
|
|
protected final PlayerConnection playerConnection;
|
|
|
|
// All the entities that this player can see
|
2021-03-26 08:49:27 +01:00
|
|
|
protected final Set<Entity> viewableEntities = ConcurrentHashMap.newKeySet();
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
private int latency;
|
2021-03-03 20:27:33 +01:00
|
|
|
private Component displayName;
|
2020-12-13 23:01:01 +01:00
|
|
|
private PlayerSkin skin;
|
|
|
|
|
|
|
|
private DimensionType dimensionType;
|
|
|
|
private GameMode gameMode;
|
2021-03-16 00:52:35 +01:00
|
|
|
// Chunks that the player can view
|
2021-03-30 01:50:36 +02:00
|
|
|
protected final Set<Chunk> viewableChunks = ConcurrentHashMap.newKeySet();
|
2021-03-08 16:49:16 +01:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
private final AtomicInteger teleportId = new AtomicInteger();
|
2021-03-08 16:49:16 +01:00
|
|
|
private int receivedTeleportId;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-06-20 22:03:14 +02:00
|
|
|
private final Queue<ClientPlayPacket> packets = new ConcurrentLinkedQueue<>();
|
2020-12-13 23:01:01 +01:00
|
|
|
private final boolean levelFlat;
|
|
|
|
private final PlayerSettings settings;
|
|
|
|
private float exp;
|
|
|
|
private int level;
|
|
|
|
|
2020-12-29 16:42:07 +01:00
|
|
|
protected PlayerInventory inventory;
|
2020-12-13 23:01:01 +01:00
|
|
|
private Inventory openInventory;
|
|
|
|
// Used internally to allow the closing of inventory within the inventory listener
|
|
|
|
private boolean didCloseInventory;
|
|
|
|
|
|
|
|
private byte heldSlot;
|
|
|
|
|
2021-07-06 20:44:24 +02:00
|
|
|
private Pos respawnPoint;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
private int food;
|
|
|
|
private float foodSaturation;
|
|
|
|
private long startEatingTime;
|
|
|
|
private long defaultEatingTime = 1000L;
|
|
|
|
private long eatingTime;
|
2021-04-13 22:59:40 +02:00
|
|
|
private Hand eatingHand;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Game state (https://wiki.vg/Protocol#Change_Game_State)
|
|
|
|
private boolean enableRespawnScreen;
|
|
|
|
|
|
|
|
// Experience orb pickup
|
2021-06-30 01:31:09 +02:00
|
|
|
protected Cooldown experiencePickupCooldown = new Cooldown(Duration.of(10, TimeUnit.SERVER_TICK));
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
private BelowNameTag belowNameTag;
|
|
|
|
|
|
|
|
private int permissionLevel;
|
|
|
|
|
|
|
|
private boolean reducedDebugScreenInformation;
|
|
|
|
|
|
|
|
// Abilities
|
|
|
|
private boolean flying;
|
|
|
|
private boolean allowFlying;
|
|
|
|
private boolean instantBreak;
|
|
|
|
private float flyingSpeed = 0.05f;
|
2020-12-15 13:41:42 +01:00
|
|
|
private float fieldViewModifier = 0.1f;
|
2020-05-25 02:37:57 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
// Statistics
|
|
|
|
private final Map<PlayerStatistic, Integer> statisticValueMap = new Hashtable<>();
|
2020-05-29 02:11:41 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
// Vehicle
|
|
|
|
private final PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
|
2020-05-26 16:14:52 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
// Tick related
|
|
|
|
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
|
2020-05-26 16:14:52 +02:00
|
|
|
|
2021-03-15 14:53:06 +01:00
|
|
|
// Adventure
|
|
|
|
private Identity identity;
|
2021-06-15 15:06:56 +02:00
|
|
|
private final Pointers pointers;
|
2021-03-15 14:53:06 +01:00
|
|
|
|
2020-12-16 00:13:40 +01:00
|
|
|
public Player(@NotNull UUID uuid, @NotNull String username, @NotNull PlayerConnection playerConnection) {
|
2021-02-05 14:32:28 +01:00
|
|
|
super(EntityType.PLAYER, uuid);
|
2020-12-13 23:01:01 +01:00
|
|
|
this.username = username;
|
2021-04-22 18:21:34 +02:00
|
|
|
this.usernameComponent = Component.text(username);
|
2020-12-13 23:01:01 +01:00
|
|
|
this.playerConnection = playerConnection;
|
2020-05-25 02:37:57 +02:00
|
|
|
|
2020-12-28 20:16:43 +01:00
|
|
|
setBoundingBox(0.6f, 1.8f, 0.6f);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-07-06 20:44:24 +02:00
|
|
|
setRespawnPoint(Pos.ZERO);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
this.settings = new PlayerSettings();
|
|
|
|
this.inventory = new PlayerInventory(this);
|
|
|
|
|
|
|
|
setCanPickupItem(true); // By default
|
|
|
|
|
|
|
|
// Allow the server to send the next keep alive packet
|
|
|
|
refreshAnswerKeepAlive(true);
|
|
|
|
|
|
|
|
this.gameMode = GameMode.SURVIVAL;
|
2021-01-07 04:21:34 +01:00
|
|
|
this.dimensionType = DimensionType.OVERWORLD; // Default dimension
|
2020-12-13 23:01:01 +01:00
|
|
|
this.levelFlat = true;
|
2021-03-09 20:51:11 +01:00
|
|
|
getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.1f);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// FakePlayer init its connection there
|
|
|
|
playerConnectionInit();
|
2021-03-15 14:53:06 +01:00
|
|
|
|
|
|
|
this.identity = Identity.identity(uuid);
|
2021-06-15 15:06:56 +02:00
|
|
|
this.pointers = Pointers.builder()
|
|
|
|
.withDynamic(Identity.UUID, this::getUuid)
|
|
|
|
.withDynamic(Identity.NAME, this::getUsername)
|
|
|
|
.withDynamic(Identity.DISPLAY_NAME, this::getDisplayName)
|
|
|
|
.build();
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used when the player is created.
|
|
|
|
* Init the player and spawn him.
|
|
|
|
* <p>
|
|
|
|
* WARNING: executed in the main update thread
|
2021-01-18 16:44:12 +01:00
|
|
|
* UNSAFE: Only meant to be used when a netty player connects through the server.
|
2021-01-07 04:21:34 +01:00
|
|
|
*
|
|
|
|
* @param spawnInstance the player spawn instance (defined in {@link PlayerLoginEvent})
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-01-18 16:44:12 +01:00
|
|
|
public void UNSAFE_init(@NotNull Instance spawnInstance) {
|
2021-01-07 04:21:34 +01:00
|
|
|
this.dimensionType = spawnInstance.getDimensionType();
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
JoinGamePacket joinGamePacket = new JoinGamePacket();
|
|
|
|
joinGamePacket.entityId = getEntityId();
|
|
|
|
joinGamePacket.gameMode = gameMode;
|
|
|
|
joinGamePacket.dimensionType = dimensionType;
|
|
|
|
joinGamePacket.maxPlayers = 0; // Unused
|
|
|
|
joinGamePacket.viewDistance = MinecraftServer.getChunkViewDistance();
|
|
|
|
joinGamePacket.reducedDebugInfo = false;
|
|
|
|
joinGamePacket.isFlat = levelFlat;
|
|
|
|
playerConnection.sendPacket(joinGamePacket);
|
|
|
|
|
|
|
|
// Server brand name
|
|
|
|
{
|
|
|
|
playerConnection.sendPacket(PluginMessagePacket.getBrandPacket());
|
2020-07-02 15:56:43 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
ServerDifficultyPacket serverDifficultyPacket = new ServerDifficultyPacket();
|
|
|
|
serverDifficultyPacket.difficulty = MinecraftServer.getDifficulty();
|
|
|
|
serverDifficultyPacket.locked = true;
|
|
|
|
playerConnection.sendPacket(serverDifficultyPacket);
|
|
|
|
|
|
|
|
SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket();
|
2021-07-05 11:38:33 +02:00
|
|
|
spawnPositionPacket.position = respawnPoint;
|
2020-12-13 23:01:01 +01:00
|
|
|
playerConnection.sendPacket(spawnPositionPacket);
|
|
|
|
|
|
|
|
// Add player to list with spawning skin
|
|
|
|
PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this, skin);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(skinInitEvent);
|
2020-12-13 23:01:01 +01:00
|
|
|
this.skin = skinInitEvent.getSkin();
|
2021-02-06 03:51:17 +01:00
|
|
|
// FIXME: when using Geyser, this line remove the skin of the client
|
2020-12-13 23:01:01 +01:00
|
|
|
playerConnection.sendPacket(getAddPlayerToList());
|
|
|
|
|
|
|
|
// Commands start
|
|
|
|
{
|
|
|
|
CommandManager commandManager = MinecraftServer.getCommandManager();
|
|
|
|
DeclareCommandsPacket declareCommandsPacket = commandManager.createDeclareCommandsPacket(this);
|
|
|
|
|
|
|
|
playerConnection.sendPacket(declareCommandsPacket);
|
2020-05-25 02:37:57 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
// Commands end
|
|
|
|
|
|
|
|
|
|
|
|
// Recipes start
|
|
|
|
{
|
|
|
|
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
|
|
|
|
DeclareRecipesPacket declareRecipesPacket = recipeManager.getDeclareRecipesPacket();
|
|
|
|
if (declareRecipesPacket.recipes != null) {
|
|
|
|
playerConnection.sendPacket(declareRecipesPacket);
|
|
|
|
}
|
2020-05-25 02:37:57 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
List<String> recipesIdentifier = new ArrayList<>();
|
|
|
|
for (Recipe recipe : recipeManager.getRecipes()) {
|
|
|
|
if (!recipe.shouldShow(this))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
recipesIdentifier.add(recipe.getRecipeId());
|
|
|
|
}
|
|
|
|
if (!recipesIdentifier.isEmpty()) {
|
|
|
|
final String[] identifiers = recipesIdentifier.toArray(new String[0]);
|
|
|
|
UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket();
|
|
|
|
unlockRecipesPacket.mode = 0;
|
|
|
|
unlockRecipesPacket.recipesId = identifiers;
|
|
|
|
unlockRecipesPacket.initRecipesId = identifiers;
|
|
|
|
playerConnection.sendPacket(unlockRecipesPacket);
|
|
|
|
}
|
2020-05-25 02:37:57 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
// Recipes end
|
2020-05-25 03:17:24 +02:00
|
|
|
|
2021-06-11 16:00:14 +02:00
|
|
|
// Tags
|
2021-06-11 21:55:30 +02:00
|
|
|
this.playerConnection.sendPacket(TagsPacket.getRequiredTagsPacket());
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Some client update
|
2020-12-15 06:21:59 +01:00
|
|
|
this.playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
|
2020-12-13 23:01:01 +01:00
|
|
|
refreshHealth(); // Heal and send health packet
|
|
|
|
refreshAbilities(); // Send abilities packet
|
|
|
|
getInventory().update();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to initialize the player connection
|
|
|
|
*/
|
|
|
|
protected void playerConnectionInit() {
|
|
|
|
this.playerConnection.setPlayer(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void update(long time) {
|
|
|
|
// Network tick
|
|
|
|
this.playerConnection.update();
|
|
|
|
|
|
|
|
// Process received packets
|
|
|
|
ClientPlayPacket packet;
|
|
|
|
while ((packet = packets.poll()) != null) {
|
|
|
|
packet.process(this);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
2019-08-18 20:38:09 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
super.update(time); // Super update (item pickup/fire management)
|
|
|
|
|
|
|
|
// Experience orb pickup
|
2021-03-31 19:17:37 +02:00
|
|
|
if (experiencePickupCooldown.isReady(time)) {
|
|
|
|
experiencePickupCooldown.refreshLastUpdate(time);
|
2020-12-13 23:01:01 +01:00
|
|
|
final Chunk chunk = getChunk(); // TODO check surrounding chunks
|
|
|
|
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
|
|
|
for (Entity entity : entities) {
|
|
|
|
if (entity instanceof ExperienceOrb) {
|
|
|
|
final ExperienceOrb experienceOrb = (ExperienceOrb) entity;
|
|
|
|
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
|
|
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
|
|
|
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
|
|
|
|
continue;
|
|
|
|
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.callCancellable(pickupExperienceEvent, () -> {
|
2020-12-13 23:01:01 +01:00
|
|
|
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
|
|
|
entity.remove();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-31 07:54:53 +02:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
// Eating animation
|
|
|
|
if (isEating()) {
|
|
|
|
if (time - startEatingTime >= eatingTime) {
|
|
|
|
triggerStatus((byte) 9); // Mark item use as finished
|
2021-05-23 16:46:29 +02:00
|
|
|
ItemUpdateStateEvent itemUpdateStateEvent = callItemUpdateStateEvent(eatingHand);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
Check.notNull(itemUpdateStateEvent, "#callItemUpdateStateEvent returned null.");
|
|
|
|
|
|
|
|
// Refresh hand
|
|
|
|
final boolean isOffHand = itemUpdateStateEvent.getHand() == Player.Hand.OFF;
|
|
|
|
refreshActiveHand(false, isOffHand, false);
|
|
|
|
|
|
|
|
final ItemStack foodItem = itemUpdateStateEvent.getItemStack();
|
|
|
|
final boolean isFood = foodItem.getMaterial().isFood();
|
|
|
|
|
|
|
|
if (isFood) {
|
2021-04-13 22:59:40 +02:00
|
|
|
PlayerEatEvent playerEatEvent = new PlayerEatEvent(this, foodItem, eatingHand);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(playerEatEvent);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2021-04-13 22:59:40 +02:00
|
|
|
|
|
|
|
refreshEating(null);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-05-12 14:12:17 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Tick event
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(playerTickEvent);
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
@Override
|
|
|
|
public void kill() {
|
|
|
|
if (!isDead()) {
|
|
|
|
|
2021-03-03 20:27:33 +01:00
|
|
|
Component deathText;
|
|
|
|
Component chatMessage;
|
2020-12-22 05:26:37 +01:00
|
|
|
|
|
|
|
// get death screen text to the killed player
|
2020-12-13 23:01:01 +01:00
|
|
|
{
|
|
|
|
if (lastDamageSource != null) {
|
|
|
|
deathText = lastDamageSource.buildDeathScreenText(this);
|
|
|
|
} else { // may happen if killed by the server without applying damage
|
2021-03-03 20:27:33 +01:00
|
|
|
deathText = Component.text("Killed by poor programming.");
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-22 05:26:37 +01:00
|
|
|
// get death message to chat
|
2020-12-13 23:01:01 +01:00
|
|
|
{
|
|
|
|
if (lastDamageSource != null) {
|
|
|
|
chatMessage = lastDamageSource.buildDeathMessage(this);
|
|
|
|
} else { // may happen if killed by the server without applying damage
|
2021-03-03 20:27:33 +01:00
|
|
|
chatMessage = Component.text(getUsername() + " was killed by poor programming.");
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-12-22 05:26:37 +01:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2020-12-22 05:26:37 +01:00
|
|
|
// Call player death event
|
|
|
|
PlayerDeathEvent playerDeathEvent = new PlayerDeathEvent(this, deathText, chatMessage);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(playerDeathEvent);
|
2020-12-22 05:26:37 +01:00
|
|
|
|
|
|
|
deathText = playerDeathEvent.getDeathText();
|
|
|
|
chatMessage = playerDeathEvent.getChatMessage();
|
|
|
|
|
|
|
|
// #buildDeathScreenText can return null, check here
|
|
|
|
if (deathText != null) {
|
2021-05-31 18:36:43 +02:00
|
|
|
playerConnection.sendPacket(DeathCombatEventPacket.of(getEntityId(), -1, deathText));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-12-22 05:26:37 +01:00
|
|
|
|
|
|
|
// #buildDeathMessage can return null, check here
|
|
|
|
if (chatMessage != null) {
|
2021-03-26 20:28:07 +01:00
|
|
|
Audiences.players().sendMessage(chatMessage);
|
2020-12-22 05:26:37 +01:00
|
|
|
}
|
|
|
|
|
2020-04-27 21:12:42 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
super.kill();
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Respawns the player by sending a {@link RespawnPacket} to the player and teleporting him
|
2021-01-22 21:28:33 +01:00
|
|
|
* to {@link #getRespawnPoint()}. It also resets fire and health.
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
|
|
|
public void respawn() {
|
|
|
|
if (!isDead())
|
|
|
|
return;
|
|
|
|
|
|
|
|
setFireForDuration(0);
|
|
|
|
setOnFire(false);
|
|
|
|
refreshHealth();
|
|
|
|
RespawnPacket respawnPacket = new RespawnPacket();
|
|
|
|
respawnPacket.dimensionType = getDimensionType();
|
|
|
|
respawnPacket.gameMode = getGameMode();
|
|
|
|
respawnPacket.isFlat = levelFlat;
|
|
|
|
getPlayerConnection().sendPacket(respawnPacket);
|
|
|
|
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(respawnEvent);
|
2020-12-13 23:01:01 +01:00
|
|
|
refreshIsDead(false);
|
|
|
|
|
|
|
|
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
|
2021-07-11 02:54:02 +02:00
|
|
|
teleport(respawnEvent.getRespawnPosition()).thenRun(this::refreshAfterTeleport);
|
2019-08-27 20:49:11 +02:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
@Override
|
|
|
|
public void spawn() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isOnGround() {
|
|
|
|
return onGround;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void remove() {
|
2021-04-15 03:08:19 +02:00
|
|
|
if (isRemoved())
|
2021-04-15 01:44:08 +02:00
|
|
|
return;
|
|
|
|
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(new PlayerDisconnectEvent(this));
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
super.remove();
|
|
|
|
this.packets.clear();
|
|
|
|
if (getOpenInventory() != null)
|
|
|
|
getOpenInventory().removeViewer(this);
|
|
|
|
|
2021-03-26 18:06:10 +01:00
|
|
|
MinecraftServer.getBossBarManager().removeAllBossBars(this);
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
// Advancement tabs cache
|
|
|
|
{
|
|
|
|
Set<AdvancementTab> advancementTabs = AdvancementTab.getTabs(this);
|
|
|
|
if (advancementTabs != null) {
|
|
|
|
for (AdvancementTab advancementTab : advancementTabs) {
|
|
|
|
advancementTab.removeViewer(this);
|
|
|
|
}
|
2020-10-31 00:23:52 +01:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Clear all viewable entities
|
|
|
|
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
|
|
|
// Clear all viewable chunks
|
|
|
|
this.viewableChunks.forEach(chunk -> {
|
|
|
|
if (chunk.isLoaded())
|
|
|
|
chunk.removeViewer(this);
|
2020-12-13 15:00:48 +01:00
|
|
|
});
|
2020-12-13 23:01:01 +01:00
|
|
|
playerConnection.disconnect();
|
|
|
|
}
|
2020-11-24 22:56:12 +01:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
@Override
|
2021-04-23 21:06:47 +02:00
|
|
|
protected boolean addViewer0(@NotNull Player player) {
|
2021-03-22 14:54:52 +01:00
|
|
|
if (player == this) {
|
2020-12-13 23:01:01 +01:00
|
|
|
return false;
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
PlayerConnection viewerConnection = player.getPlayerConnection();
|
2021-03-22 14:54:52 +01:00
|
|
|
viewerConnection.sendPacket(getAddPlayerToList());
|
|
|
|
return super.addViewer0(player);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-04-23 21:06:47 +02:00
|
|
|
protected boolean removeViewer0(@NotNull Player player) {
|
2021-02-25 08:37:02 +01:00
|
|
|
if (player == this || !super.removeViewer0(player)) {
|
2020-12-13 23:01:01 +01:00
|
|
|
return false;
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
PlayerConnection viewerConnection = player.getPlayerConnection();
|
|
|
|
viewerConnection.sendPacket(getRemovePlayerToList());
|
|
|
|
|
|
|
|
// Team
|
2021-02-25 08:37:02 +01:00
|
|
|
if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) {// If team only contains "this" player
|
2020-12-13 23:01:01 +01:00
|
|
|
viewerConnection.sendPacket(this.getTeam().createTeamDestructionPacket());
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
|
|
|
return true;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-25 18:07:05 +01:00
|
|
|
@Override
|
|
|
|
public void sendPacketToViewersAndSelf(@NotNull ServerPacket packet) {
|
|
|
|
this.playerConnection.sendPacket(packet);
|
|
|
|
super.sendPacketToViewersAndSelf(packet);
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Changes the player instance and load surrounding chunks if needed.
|
|
|
|
* <p>
|
|
|
|
* Be aware that because chunk operations are expensive,
|
|
|
|
* it is possible for this method to be non-blocking when retrieving chunks is required.
|
2021-07-11 03:35:17 +02:00
|
|
|
*
|
|
|
|
* @param instance the new player instance
|
2021-01-06 19:06:37 +01:00
|
|
|
* @param spawnPosition the new position of the player
|
2021-07-11 02:59:24 +02:00
|
|
|
* @return
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-02-25 11:56:10 +01:00
|
|
|
@Override
|
2021-07-11 02:59:24 +02:00
|
|
|
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
2020-12-13 23:01:01 +01:00
|
|
|
Check.argCondition(this.instance == instance, "Instance should be different than the current one");
|
|
|
|
// true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance)
|
2021-01-08 17:17:36 +01:00
|
|
|
final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance) ||
|
2021-07-13 18:12:46 +02:00
|
|
|
!spawnPosition.sameChunk(this.position);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
if (needWorldRefresh) {
|
2021-03-21 19:47:22 +01:00
|
|
|
// TODO: Handle player reconnections, must be false in that case too
|
|
|
|
final boolean firstSpawn = this.instance == null;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-01-07 03:46:58 +01:00
|
|
|
// Send the new dimension if player isn't in any instance or if the dimension is different
|
2021-03-07 23:18:32 +01:00
|
|
|
final DimensionType instanceDimensionType = instance.getDimensionType();
|
|
|
|
final boolean dimensionChange = dimensionType != instanceDimensionType;
|
|
|
|
if (dimensionChange) {
|
|
|
|
sendDimension(instanceDimensionType);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2021-07-21 09:45:13 +02:00
|
|
|
return instance.loadOptionalChunk(spawnPosition)
|
2021-07-20 19:10:53 +02:00
|
|
|
.thenRun(() -> spawnPlayer(instance, spawnPosition, firstSpawn, dimensionChange, true));
|
2020-12-13 23:01:01 +01:00
|
|
|
} else {
|
|
|
|
// The player already has the good version of all the chunks.
|
|
|
|
// We just need to refresh his entity viewing list and add him to the instance
|
2021-05-10 00:51:35 +02:00
|
|
|
spawnPlayer(instance, spawnPosition, false, false, false);
|
2021-07-11 03:35:17 +02:00
|
|
|
return AsyncUtils.NULL_FUTURE;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player instance without changing its position (defaulted to {@link #getRespawnPoint()}
|
2021-01-06 19:06:37 +01:00
|
|
|
* if the player is not in any instance).
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @param instance the new player instance
|
2021-07-11 13:45:28 +02:00
|
|
|
* @return a {@link CompletableFuture} called once the entity's instance has been set,
|
|
|
|
* this is due to chunks needing to load for players
|
2021-07-06 20:44:24 +02:00
|
|
|
* @see #setInstance(Instance, Pos)
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
|
|
|
@Override
|
2021-07-11 13:45:28 +02:00
|
|
|
public CompletableFuture<Void> setInstance(@NotNull Instance instance) {
|
|
|
|
return setInstance(instance, this.instance != null ? getPosition() : getRespawnPoint());
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-11-25 12:12:58 +01:00
|
|
|
|
2020-12-13 23:01:01 +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>
|
2021-07-06 20:44:24 +02:00
|
|
|
* UNSAFE: only called with {@link #setInstance(Instance, Pos)}.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @param spawnPosition the position to teleport the player
|
|
|
|
* @param firstSpawn true if this is the player first spawn
|
2021-05-10 00:51:35 +02:00
|
|
|
* @param updateChunks true if chunks should be refreshed, false if the new instance shares the same
|
|
|
|
* chunks
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-07-06 20:44:24 +02:00
|
|
|
private void spawnPlayer(@NotNull Instance instance, @NotNull Pos spawnPosition,
|
2021-05-10 00:51:35 +02:00
|
|
|
boolean firstSpawn, boolean dimensionChange, boolean updateChunks) {
|
2021-02-19 13:52:27 +01:00
|
|
|
if (!firstSpawn) {
|
2021-05-10 00:51:35 +02:00
|
|
|
// Player instance changed, clear current viewable collections
|
2021-02-19 13:52:27 +01:00
|
|
|
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
|
|
|
|
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
|
|
|
}
|
2020-11-14 21:45:30 +01:00
|
|
|
|
2021-02-25 15:35:31 +01:00
|
|
|
super.setInstance(instance, spawnPosition);
|
2020-11-20 19:23:50 +01:00
|
|
|
|
2021-05-10 00:51:35 +02:00
|
|
|
if (updateChunks) {
|
|
|
|
refreshVisibleChunks();
|
2021-03-07 23:18:32 +01:00
|
|
|
}
|
|
|
|
|
2021-03-08 17:12:21 +01:00
|
|
|
if (dimensionChange || firstSpawn) {
|
2021-05-15 21:07:42 +02:00
|
|
|
synchronizePosition(true); // So the player doesn't get stuck
|
2021-03-07 23:18:32 +01:00
|
|
|
this.inventory.update();
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-11-14 21:45:30 +01:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(spawnEvent);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-10-31 00:23:52 +01:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Sends a plugin message to the player.
|
|
|
|
*
|
|
|
|
* @param channel the message channel
|
|
|
|
* @param data the message data
|
|
|
|
*/
|
|
|
|
public void sendPluginMessage(@NotNull String channel, @NotNull byte[] data) {
|
|
|
|
PluginMessagePacket pluginMessagePacket = new PluginMessagePacket();
|
|
|
|
pluginMessagePacket.channel = channel;
|
|
|
|
pluginMessagePacket.data = data;
|
|
|
|
playerConnection.sendPacket(pluginMessagePacket);
|
|
|
|
}
|
2020-08-15 13:38:57 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Sends a plugin message to the player.
|
2020-12-28 10:40:50 +01:00
|
|
|
* <p>
|
|
|
|
* Message encoded to UTF-8.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @param channel the message channel
|
|
|
|
* @param message the message
|
|
|
|
*/
|
|
|
|
public void sendPluginMessage(@NotNull String channel, @NotNull String message) {
|
2020-12-28 10:40:50 +01:00
|
|
|
final byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
|
|
|
|
sendPluginMessage(channel, bytes);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-08-15 13:38:57 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Sends a legacy message with the specified color char.
|
|
|
|
*
|
|
|
|
* @param text the text with the legacy color formatting
|
|
|
|
* @param colorChar the color character
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #sendMessage(Component)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public void sendLegacyMessage(@NotNull String text, char colorChar) {
|
|
|
|
ColoredText coloredText = ColoredText.ofLegacy(text, colorChar);
|
|
|
|
sendJsonMessage(coloredText.toString());
|
|
|
|
}
|
2020-06-23 22:46:22 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Sends a legacy message with the default color char {@link ChatParser#COLOR_CHAR}.
|
|
|
|
*
|
|
|
|
* @param text the text with the legacy color formatting
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #sendMessage(Component)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public void sendLegacyMessage(@NotNull String text) {
|
|
|
|
ColoredText coloredText = ColoredText.ofLegacy(text, ChatParser.COLOR_CHAR);
|
|
|
|
sendJsonMessage(coloredText.toString());
|
|
|
|
}
|
2020-06-30 01:25:23 +02:00
|
|
|
|
2021-03-01 16:25:23 +01:00
|
|
|
/**
|
|
|
|
* @deprecated Use {@link #sendMessage(Component)}
|
|
|
|
*/
|
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public void sendJsonMessage(@NotNull String json) {
|
2021-04-04 15:06:23 +02:00
|
|
|
this.sendMessage(GsonComponentSerializer.gson().deserialize(json));
|
2021-03-01 16:25:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) {
|
2021-05-05 19:21:38 +02:00
|
|
|
Messenger.sendMessage(this, message, ChatPosition.fromMessageType(type), source.uuid());
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-06-23 22:46:22 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Makes the player send a message (can be used for commands).
|
|
|
|
*
|
|
|
|
* @param message the message that the player will send
|
|
|
|
*/
|
|
|
|
public void chat(@NotNull String message) {
|
|
|
|
ClientChatMessagePacket chatMessagePacket = new ClientChatMessagePacket();
|
|
|
|
chatMessagePacket.message = message;
|
|
|
|
addPacketToQueue(chatMessagePacket);
|
|
|
|
}
|
2020-10-16 11:37:00 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
2021-03-21 22:38:34 +01:00
|
|
|
* Plays a sound from the {@link SoundEvent} enum.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @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
|
2021-03-02 16:56:20 +01:00
|
|
|
* @deprecated Use {@link #playSound(net.kyori.adventure.sound.Sound, double, double, double)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-02 16:56:20 +01:00
|
|
|
@Deprecated
|
2021-03-21 22:38:34 +01:00
|
|
|
public void playSound(@NotNull SoundEvent sound, @NotNull SoundCategory soundCategory, int x, int y, int z, float volume, float pitch) {
|
2020-12-13 23:01:01 +01:00
|
|
|
SoundEffectPacket soundEffectPacket = new SoundEffectPacket();
|
|
|
|
soundEffectPacket.soundId = sound.getId();
|
2021-03-05 20:59:28 +01:00
|
|
|
soundEffectPacket.soundSource = soundCategory.asSource();
|
2020-12-13 23:01:01 +01:00
|
|
|
soundEffectPacket.x = x;
|
|
|
|
soundEffectPacket.y = y;
|
|
|
|
soundEffectPacket.z = z;
|
|
|
|
soundEffectPacket.volume = volume;
|
|
|
|
soundEffectPacket.pitch = pitch;
|
|
|
|
playerConnection.sendPacket(soundEffectPacket);
|
|
|
|
}
|
2020-04-10 13:39:22 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* 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
|
2021-03-02 16:56:20 +01:00
|
|
|
* @deprecated Use {@link #playSound(net.kyori.adventure.sound.Sound, double, double, double)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-02 16:56:20 +01:00
|
|
|
@Deprecated
|
|
|
|
public void playSound(@NotNull String identifier, @NotNull SoundCategory soundCategory, int x, int y, int z, float volume, float pitch) {
|
2020-12-13 23:01:01 +01:00
|
|
|
NamedSoundEffectPacket namedSoundEffectPacket = new NamedSoundEffectPacket();
|
|
|
|
namedSoundEffectPacket.soundName = identifier;
|
2021-03-05 20:59:28 +01:00
|
|
|
namedSoundEffectPacket.soundSource = soundCategory.asSource();
|
2020-12-13 23:01:01 +01:00
|
|
|
namedSoundEffectPacket.x = x;
|
|
|
|
namedSoundEffectPacket.y = y;
|
|
|
|
namedSoundEffectPacket.z = z;
|
|
|
|
namedSoundEffectPacket.volume = volume;
|
|
|
|
namedSoundEffectPacket.pitch = pitch;
|
|
|
|
playerConnection.sendPacket(namedSoundEffectPacket);
|
|
|
|
}
|
2020-12-02 21:28:36 +01:00
|
|
|
|
2020-12-02 15:08:09 +01:00
|
|
|
/**
|
2020-12-13 23:01:01 +01:00
|
|
|
* Plays a sound directly to the player (constant volume).
|
2020-12-02 15:08:09 +01:00
|
|
|
*
|
2020-12-13 23:01:01 +01:00
|
|
|
* @param sound the sound to play
|
|
|
|
* @param soundCategory the sound category
|
|
|
|
* @param volume the volume of the sound (1 is 100%)
|
|
|
|
* @param pitch the pitch of the sound, between 0.5 and 2.0
|
2021-03-02 16:56:20 +01:00
|
|
|
* @deprecated Use {@link #playSound(net.kyori.adventure.sound.Sound)}
|
2020-12-02 15:08:09 +01:00
|
|
|
*/
|
2021-03-02 16:56:20 +01:00
|
|
|
@Deprecated
|
2021-03-21 22:38:34 +01:00
|
|
|
public void playSound(@NotNull SoundEvent sound, @NotNull SoundCategory soundCategory, float volume, float pitch) {
|
2020-12-13 23:01:01 +01:00
|
|
|
EntitySoundEffectPacket entitySoundEffectPacket = new EntitySoundEffectPacket();
|
|
|
|
entitySoundEffectPacket.entityId = getEntityId();
|
|
|
|
entitySoundEffectPacket.soundId = sound.getId();
|
2021-03-25 13:30:09 +01:00
|
|
|
entitySoundEffectPacket.soundSource = soundCategory.asSource();
|
2020-12-13 23:01:01 +01:00
|
|
|
entitySoundEffectPacket.volume = volume;
|
|
|
|
entitySoundEffectPacket.pitch = pitch;
|
|
|
|
playerConnection.sendPacket(entitySoundEffectPacket);
|
2020-12-02 15:08:09 +01:00
|
|
|
}
|
2020-10-26 11:18:44 +01:00
|
|
|
|
2021-03-02 16:56:20 +01:00
|
|
|
@Override
|
2021-03-27 14:59:08 +01:00
|
|
|
public void playSound(@NotNull Sound sound) {
|
2021-07-06 20:44:24 +02:00
|
|
|
this.playSound(sound, this.position.x(), this.position.y(), this.position.z());
|
2021-03-02 16:56:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-03-27 14:59:08 +01:00
|
|
|
public void playSound(@NotNull Sound sound, double x, double y, double z) {
|
2021-03-25 13:58:41 +01:00
|
|
|
playerConnection.sendPacket(AdventurePacketConvertor.createSoundPacket(sound, x, y, z));
|
2021-03-02 16:56:20 +01:00
|
|
|
}
|
|
|
|
|
2021-06-11 16:51:45 +02:00
|
|
|
@Override
|
|
|
|
public void playSound(@NotNull Sound sound, Sound.@NotNull Emitter emitter) {
|
|
|
|
final ServerPacket packet;
|
|
|
|
|
|
|
|
if (emitter == Sound.Emitter.self()) {
|
|
|
|
packet = AdventurePacketConvertor.createSoundPacket(sound, this);
|
|
|
|
} else {
|
|
|
|
packet = AdventurePacketConvertor.createSoundPacket(sound, emitter);
|
|
|
|
}
|
|
|
|
|
|
|
|
playerConnection.sendPacket(packet);
|
|
|
|
}
|
|
|
|
|
2021-03-02 16:56:20 +01:00
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void stopSound(@NotNull SoundStop stop) {
|
2021-03-25 13:58:41 +01:00
|
|
|
playerConnection.sendPacket(AdventurePacketConvertor.createSoundStopPacket(stop));
|
2021-03-02 16:56:20 +01:00
|
|
|
}
|
|
|
|
|
2020-04-28 16:08:21 +02:00
|
|
|
/**
|
2020-12-13 23:01:01 +01:00
|
|
|
* Plays a given effect at the given position for this player.
|
2020-04-28 17:58:34 +02:00
|
|
|
*
|
2020-12-13 23:01:01 +01:00
|
|
|
* @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
|
|
|
|
* @param disableRelativeVolume disable volume scaling based on distance
|
|
|
|
*/
|
|
|
|
public void playEffect(@NotNull Effects effect, int x, int y, int z, int data, boolean disableRelativeVolume) {
|
|
|
|
EffectPacket packet = new EffectPacket();
|
|
|
|
packet.effectId = effect.getId();
|
2021-07-08 18:56:40 +02:00
|
|
|
packet.position = new Vec(x, y, z);
|
2020-12-13 23:01:01 +01:00
|
|
|
packet.data = data;
|
|
|
|
packet.disableRelativeVolume = disableRelativeVolume;
|
|
|
|
playerConnection.sendPacket(packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a {@link StopSoundPacket} packet.
|
2021-03-29 11:01:32 +02:00
|
|
|
*
|
2021-03-02 16:56:20 +01:00
|
|
|
* @deprecated Use {@link #stopSound(SoundStop)} with {@link SoundStop#all()}
|
2020-05-29 23:17:14 +02:00
|
|
|
*/
|
2021-03-02 16:56:20 +01:00
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public void stopSound() {
|
|
|
|
StopSoundPacket stopSoundPacket = new StopSoundPacket();
|
|
|
|
stopSoundPacket.flags = 0x00;
|
|
|
|
playerConnection.sendPacket(stopSoundPacket);
|
2020-04-10 13:39:22 +02:00
|
|
|
}
|
|
|
|
|
2020-10-21 16:26:55 +02:00
|
|
|
/**
|
2020-12-13 23:01:01 +01:00
|
|
|
* Sets the header and footer of a player which will be displayed in his tab window.
|
2020-10-21 16:26:55 +02:00
|
|
|
*
|
2020-12-13 23:01:01 +01:00
|
|
|
* @param header the header text, null to set empty
|
|
|
|
* @param footer the footer text, null to set empty
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #sendPlayerListHeaderAndFooter(Component, Component)}
|
2020-10-21 16:26:55 +02:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void sendHeaderFooter(@Nullable JsonMessage header, @Nullable JsonMessage footer) {
|
2021-03-01 16:25:23 +01:00
|
|
|
this.sendPlayerListHeaderAndFooter(header == null ? Component.empty() : header.asComponent(),
|
|
|
|
footer == null ? Component.empty() : footer.asComponent());
|
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-03-01 16:25:23 +01:00
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void sendPlayerListHeaderAndFooter(@NotNull Component header, @NotNull Component footer) {
|
2021-03-12 16:33:19 +01:00
|
|
|
PlayerListHeaderAndFooterPacket packet = new PlayerListHeaderAndFooterPacket(header, footer);
|
2021-03-02 15:41:45 +01:00
|
|
|
playerConnection.sendPacket(packet);
|
2020-04-10 13:39:22 +02:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* 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
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #showTitle(Title)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void sendTitleSubtitleMessage(@NotNull JsonMessage title, @NotNull JsonMessage subtitle) {
|
2021-03-01 16:25:23 +01:00
|
|
|
this.showTitle(Title.title(title.asComponent(), subtitle.asComponent()));
|
2020-05-04 18:32:14 +02:00
|
|
|
}
|
|
|
|
|
2020-10-17 14:46:14 +02:00
|
|
|
/**
|
2020-12-13 23:01:01 +01:00
|
|
|
* Sends a title message.
|
2020-10-17 14:46:14 +02:00
|
|
|
*
|
2020-12-13 23:01:01 +01:00
|
|
|
* @param title the title message
|
|
|
|
* @see #sendTitleTime(int, int, int) to specify the display time
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #showTitle(Title)}
|
2020-10-17 14:46:14 +02:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void sendTitleMessage(@NotNull JsonMessage title) {
|
2021-03-01 16:25:23 +01:00
|
|
|
this.showTitle(Title.title(title.asComponent(), Component.empty()));
|
2020-05-04 18:32:14 +02:00
|
|
|
}
|
|
|
|
|
2020-10-17 14:46:14 +02:00
|
|
|
/**
|
2020-12-13 23:01:01 +01:00
|
|
|
* Sends a subtitle message.
|
2020-10-17 14:46:14 +02:00
|
|
|
*
|
2020-12-13 23:01:01 +01:00
|
|
|
* @param subtitle the subtitle message
|
|
|
|
* @see #sendTitleTime(int, int, int) to specify the display time
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #showTitle(Title)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void sendSubtitleMessage(@NotNull JsonMessage subtitle) {
|
2021-03-01 16:25:23 +01:00
|
|
|
this.showTitle(Title.title(Component.empty(), subtitle.asComponent()));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends an action bar message.
|
2020-10-17 14:46:14 +02:00
|
|
|
*
|
2020-12-13 23:01:01 +01:00
|
|
|
* @param actionBar the action bar message
|
|
|
|
* @see #sendTitleTime(int, int, int) to specify the display time
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #sendActionBar(Component)}
|
2020-10-17 14:46:14 +02:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void sendActionBarMessage(@NotNull JsonMessage actionBar) {
|
2021-03-01 16:25:23 +01:00
|
|
|
this.sendActionBar(actionBar.asComponent());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void showTitle(@NotNull Title title) {
|
2021-06-06 03:52:40 +02:00
|
|
|
playerConnection.sendPacket(new SetTitleTextPacket(title.title()));
|
|
|
|
playerConnection.sendPacket(new SetTitleSubTitlePacket(title.subtitle()));
|
|
|
|
final var times = title.times();
|
|
|
|
if (times != null) {
|
|
|
|
playerConnection.sendPacket(new SetTitleTimePacket(
|
|
|
|
TickUtils.fromDuration(times.fadeIn(), TickUtils.CLIENT_TICK_MS),
|
|
|
|
TickUtils.fromDuration(times.stay(), TickUtils.CLIENT_TICK_MS),
|
|
|
|
TickUtils.fromDuration(times.fadeOut(), TickUtils.CLIENT_TICK_MS)));
|
2021-03-01 16:25:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void sendActionBar(@NotNull Component message) {
|
2021-06-06 03:52:40 +02:00
|
|
|
playerConnection.sendPacket(new ActionBarPacket(message));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-05-04 18:32:14 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Specifies the display time of a title.
|
|
|
|
*
|
|
|
|
* @param fadeIn ticks to spend fading in
|
|
|
|
* @param stay ticks to keep the title displayed
|
|
|
|
* @param fadeOut ticks to spend out, not when to start fading out
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #showTitle(Title)}. Note that this will overwrite the
|
|
|
|
* existing title. This is expected behavior and will be the case in 1.17.
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public void sendTitleTime(int fadeIn, int stay, int fadeOut) {
|
2021-06-06 03:52:40 +02:00
|
|
|
playerConnection.sendPacket(new SetTitleTimePacket(fadeIn, stay, fadeOut));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-10-17 14:46:14 +02:00
|
|
|
|
2021-03-01 18:27:10 +01:00
|
|
|
@Override
|
2020-12-13 23:01:01 +01:00
|
|
|
public void resetTitle() {
|
2021-06-06 03:52:40 +02:00
|
|
|
playerConnection.sendPacket(new ClearTitlesPacket(true));
|
2021-03-01 16:25:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void clearTitle() {
|
2021-06-06 03:52:40 +02:00
|
|
|
playerConnection.sendPacket(new ClearTitlesPacket());
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-10-17 14:46:14 +02:00
|
|
|
|
2021-03-01 18:25:55 +01:00
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void showBossBar(@NotNull BossBar bar) {
|
2021-03-01 18:25:55 +01:00
|
|
|
MinecraftServer.getBossBarManager().addBossBar(this, bar);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void hideBossBar(@NotNull BossBar bar) {
|
2021-03-01 18:25:55 +01:00
|
|
|
MinecraftServer.getBossBarManager().removeBossBar(this, bar);
|
|
|
|
}
|
|
|
|
|
2021-03-01 16:25:23 +01:00
|
|
|
@Override
|
2021-03-02 18:47:56 +01:00
|
|
|
public void openBook(@NotNull Book book) {
|
2021-04-10 18:55:26 +02:00
|
|
|
ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK)
|
|
|
|
.meta(WrittenBookMeta.fromAdventure(book, this))
|
|
|
|
.build();
|
2021-03-02 16:56:20 +01:00
|
|
|
|
|
|
|
// Set book in offhand
|
|
|
|
SetSlotPacket setBookPacket = new SetSlotPacket();
|
|
|
|
setBookPacket.windowId = 0;
|
2021-04-10 18:55:26 +02:00
|
|
|
setBookPacket.slot = PlayerInventoryUtils.OFFHAND_SLOT;
|
2021-03-02 16:56:20 +01:00
|
|
|
setBookPacket.itemStack = writtenBook;
|
|
|
|
playerConnection.sendPacket(setBookPacket);
|
|
|
|
|
|
|
|
// Open the book
|
|
|
|
OpenBookPacket openBookPacket = new OpenBookPacket();
|
|
|
|
openBookPacket.hand = Hand.OFF;
|
|
|
|
playerConnection.sendPacket(openBookPacket);
|
|
|
|
|
|
|
|
// Restore the item in offhand
|
|
|
|
SetSlotPacket restoreItemPacket = new SetSlotPacket();
|
|
|
|
restoreItemPacket.windowId = 0;
|
2021-04-10 18:55:26 +02:00
|
|
|
restoreItemPacket.slot = PlayerInventoryUtils.OFFHAND_SLOT;
|
2021-03-02 16:56:20 +01:00
|
|
|
restoreItemPacket.itemStack = getItemInOffHand();
|
|
|
|
playerConnection.sendPacket(restoreItemPacket);
|
2021-03-01 16:25:23 +01:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
@Override
|
|
|
|
public boolean isImmune(@NotNull DamageType type) {
|
|
|
|
if (!getGameMode().canTakeDamage()) {
|
|
|
|
return type != DamageType.VOID;
|
2020-04-27 20:33:08 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
return super.isImmune(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setHealth(float health) {
|
|
|
|
super.setHealth(health);
|
|
|
|
sendUpdateHealthPacket();
|
|
|
|
}
|
|
|
|
|
2021-06-14 21:49:16 +02:00
|
|
|
@Override
|
|
|
|
public @NotNull PlayerMeta getEntityMeta() {
|
|
|
|
return (PlayerMeta) super.getEntityMeta();
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Gets the player additional hearts.
|
|
|
|
*
|
|
|
|
* @return the player additional hearts
|
|
|
|
*/
|
|
|
|
public float getAdditionalHearts() {
|
2021-06-14 21:49:16 +02:00
|
|
|
return getEntityMeta().getAdditionalHearts();
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-01-30 04:44:44 +01:00
|
|
|
* Changes the amount of additional hearts shown.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @param additionalHearts the count of additional hearts
|
|
|
|
*/
|
|
|
|
public void setAdditionalHearts(float additionalHearts) {
|
2021-06-14 21:49:16 +02:00
|
|
|
getEntityMeta().setAdditionalHearts(additionalHearts);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player food.
|
|
|
|
*
|
|
|
|
* @return the player food
|
|
|
|
*/
|
|
|
|
public int getFood() {
|
|
|
|
return food;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets and refresh client food bar.
|
|
|
|
*
|
|
|
|
* @param food the new food value
|
|
|
|
* @throws IllegalArgumentException if {@code food} is not between 0 and 20
|
|
|
|
*/
|
|
|
|
public void setFood(int food) {
|
2021-03-21 19:47:22 +01:00
|
|
|
Check.argCondition(!MathUtils.isBetween(food, 0, 20),
|
|
|
|
"Food has to be between 0 and 20");
|
2020-12-13 23:01:01 +01:00
|
|
|
this.food = food;
|
|
|
|
sendUpdateHealthPacket();
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getFoodSaturation() {
|
|
|
|
return foodSaturation;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets and refresh client food saturation.
|
|
|
|
*
|
|
|
|
* @param foodSaturation the food saturation
|
2021-03-21 19:47:22 +01:00
|
|
|
* @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 20
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
|
|
|
public void setFoodSaturation(float foodSaturation) {
|
2021-03-21 19:47:22 +01:00
|
|
|
Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 20),
|
|
|
|
"Food saturation has to be between 0 and 20");
|
2020-12-13 23:01:01 +01:00
|
|
|
this.foodSaturation = foodSaturation;
|
|
|
|
sendUpdateHealthPacket();
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player is eating.
|
|
|
|
*
|
|
|
|
* @return true if the player is eating, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isEating() {
|
2021-04-13 22:59:40 +02:00
|
|
|
return eatingHand != null;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-05-23 16:46:29 +02:00
|
|
|
/**
|
|
|
|
* Gets the hand which the player is eating from.
|
|
|
|
*
|
|
|
|
* @return the eating hand, null if none
|
|
|
|
*/
|
2021-05-23 20:19:46 +02:00
|
|
|
public @Nullable Hand getEatingHand() {
|
2021-05-23 16:46:29 +02:00
|
|
|
return eatingHand;
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Gets the player default eating time.
|
|
|
|
*
|
|
|
|
* @return the player default eating time
|
|
|
|
*/
|
|
|
|
public long getDefaultEatingTime() {
|
|
|
|
return defaultEatingTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to change the default eating time animation.
|
|
|
|
*
|
|
|
|
* @param defaultEatingTime the default eating time in milliseconds
|
|
|
|
*/
|
|
|
|
public void setDefaultEatingTime(long defaultEatingTime) {
|
|
|
|
this.defaultEatingTime = defaultEatingTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player display name in the tab-list.
|
|
|
|
*
|
|
|
|
* @return the player display name, null means that {@link #getUsername()} is displayed
|
2021-03-03 20:27:33 +01:00
|
|
|
* @deprecated Use {@link #getDisplayName()}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
|
|
|
@Nullable
|
2021-03-04 14:47:17 +01:00
|
|
|
@Deprecated
|
2021-03-03 20:27:33 +01:00
|
|
|
public JsonMessage getDisplayNameJson() {
|
|
|
|
return JsonMessage.fromComponent(displayName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player display name in the tab-list.
|
|
|
|
*
|
|
|
|
* @return the player display name, null means that {@link #getUsername()} is displayed
|
|
|
|
*/
|
|
|
|
@Nullable
|
|
|
|
public Component getDisplayName() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return displayName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player display name in the tab-list.
|
|
|
|
* <p>
|
|
|
|
* Sets to null to show the player username.
|
|
|
|
*
|
|
|
|
* @param displayName the display name, null to display the username
|
2021-03-03 20:27:33 +01:00
|
|
|
* @deprecated Use {@link #setDisplayName(Component)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-03 20:27:33 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void setDisplayName(@Nullable JsonMessage displayName) {
|
2021-03-03 20:27:33 +01:00
|
|
|
this.setDisplayName(displayName == null ? null : displayName.asComponent());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player display name in the tab-list.
|
|
|
|
* <p>
|
|
|
|
* Sets to null to show the player username.
|
|
|
|
*
|
|
|
|
* @param displayName the display name, null to display the username
|
|
|
|
*/
|
|
|
|
public void setDisplayName(@Nullable Component displayName) {
|
2020-12-13 23:01:01 +01:00
|
|
|
this.displayName = displayName;
|
|
|
|
|
|
|
|
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME);
|
2021-03-10 17:13:27 +01:00
|
|
|
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateDisplayName(getUuid(), displayName));
|
2020-12-13 23:01:01 +01:00
|
|
|
sendPacketToViewersAndSelf(infoPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player skin.
|
|
|
|
*
|
|
|
|
* @return the player skin object,
|
|
|
|
* null means that the player has his {@link #getUuid()} default skin
|
|
|
|
*/
|
|
|
|
@Nullable
|
|
|
|
public PlayerSkin getSkin() {
|
|
|
|
return skin;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player skin.
|
|
|
|
* <p>
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
public synchronized void setSkin(@Nullable PlayerSkin skin) {
|
|
|
|
this.skin = skin;
|
|
|
|
if (instance == null)
|
|
|
|
return;
|
|
|
|
|
2021-07-14 16:26:32 +02:00
|
|
|
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(getEntityId());
|
2020-12-13 23:01:01 +01: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);
|
2021-07-14 16:26:32 +02:00
|
|
|
playerConnection.sendPacket(destroyEntitiesPacket);
|
2020-12-13 23:01:01 +01:00
|
|
|
playerConnection.sendPacket(respawnPacket);
|
|
|
|
playerConnection.sendPacket(addPlayerPacket);
|
|
|
|
|
|
|
|
{
|
|
|
|
// Remove player
|
|
|
|
sendPacketToViewers(removePlayerPacket);
|
2021-07-14 16:26:32 +02:00
|
|
|
sendPacketToViewers(destroyEntitiesPacket);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Show player again
|
|
|
|
getViewers().forEach(player -> showPlayer(player.getPlayerConnection()));
|
|
|
|
}
|
|
|
|
|
|
|
|
getInventory().update();
|
|
|
|
teleport(getPosition());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player has the respawn screen enabled or disabled.
|
|
|
|
*
|
|
|
|
* @return true if the player has the respawn screen, false if he didn't
|
|
|
|
*/
|
|
|
|
public boolean isEnableRespawnScreen() {
|
|
|
|
return enableRespawnScreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables or disable the respawn screen.
|
|
|
|
*
|
|
|
|
* @param enableRespawnScreen true to enable the respawn screen, false to disable it
|
|
|
|
*/
|
|
|
|
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
|
|
|
|
this.enableRespawnScreen = enableRespawnScreen;
|
|
|
|
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.ENABLE_RESPAWN_SCREEN, enableRespawnScreen ? 0 : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-04-12 16:08:27 +02:00
|
|
|
* Gets the player's name as a component. This will either return the display name
|
|
|
|
* (if set) or a component holding the username.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
2021-04-12 16:08:27 +02:00
|
|
|
* @return the name
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-04-12 11:49:04 +02:00
|
|
|
@Override
|
2021-04-12 16:08:27 +02:00
|
|
|
public @NotNull Component getName() {
|
|
|
|
if (this.displayName != null) {
|
|
|
|
return this.displayName;
|
|
|
|
} else {
|
|
|
|
return this.usernameComponent;
|
|
|
|
}
|
2021-04-12 11:49:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player's username.
|
|
|
|
*
|
|
|
|
* @return the player's username
|
|
|
|
*/
|
|
|
|
public @NotNull String getUsername() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return username;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-12-27 22:16:19 +01:00
|
|
|
* Changes the internal player name, used for the {@link AsyncPlayerPreLoginEvent}
|
2020-12-13 23:01:01 +01:00
|
|
|
* mostly unsafe outside of it.
|
|
|
|
*
|
|
|
|
* @param username the new player name
|
|
|
|
*/
|
2021-01-06 19:02:35 +01:00
|
|
|
public void setUsernameField(@NotNull String username) {
|
2020-12-13 23:01:01 +01:00
|
|
|
this.username = username;
|
2021-04-22 18:21:34 +02:00
|
|
|
this.usernameComponent = Component.text(username);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void sendChangeGameStatePacket(@NotNull ChangeGameStatePacket.Reason reason, float value) {
|
|
|
|
ChangeGameStatePacket changeGameStatePacket = new ChangeGameStatePacket();
|
|
|
|
changeGameStatePacket.reason = reason;
|
|
|
|
changeGameStatePacket.value = value;
|
|
|
|
playerConnection.sendPacket(changeGameStatePacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calls an {@link ItemDropEvent} with a specified item.
|
|
|
|
* <p>
|
|
|
|
* Returns false if {@code item} is air.
|
|
|
|
*
|
|
|
|
* @param item the item to drop
|
|
|
|
* @return true if player can drop the item (event not cancelled), false otherwise
|
|
|
|
*/
|
|
|
|
public boolean dropItem(@NotNull ItemStack item) {
|
|
|
|
if (item.isAir()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ItemDropEvent itemDropEvent = new ItemDropEvent(this, item);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(itemDropEvent);
|
2020-12-13 23:01:01 +01:00
|
|
|
return !itemDropEvent.isCancelled();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the player resource pack.
|
|
|
|
*
|
|
|
|
* @param resourcePack the resource pack
|
|
|
|
*/
|
|
|
|
public void setResourcePack(@NotNull ResourcePack resourcePack) {
|
2021-06-11 12:32:24 +02:00
|
|
|
playerConnection.sendPacket(new ResourcePackSendPacket(resourcePack));
|
2020-12-13 23:01:01 +01: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
|
|
|
|
*/
|
|
|
|
public void facePosition(@NotNull FacePoint facePoint, @NotNull Position targetPosition) {
|
|
|
|
facePosition(facePoint, targetPosition, null, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
public void facePosition(@NotNull FacePoint facePoint, Entity entity, FacePoint targetPoint) {
|
|
|
|
facePosition(facePoint, entity.getPosition(), entity, targetPoint);
|
|
|
|
}
|
|
|
|
|
2021-07-06 20:44:24 +02:00
|
|
|
private void facePosition(@NotNull FacePoint facePoint, @NotNull Point targetPosition,
|
2020-12-13 23:01:01 +01:00
|
|
|
@Nullable Entity entity, @Nullable FacePoint targetPoint) {
|
|
|
|
FacePlayerPacket facePlayerPacket = new FacePlayerPacket();
|
|
|
|
facePlayerPacket.entityFacePosition = facePoint == FacePoint.EYE ?
|
|
|
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
|
2021-07-06 20:44:24 +02:00
|
|
|
facePlayerPacket.targetX = targetPosition.x();
|
|
|
|
facePlayerPacket.targetY = targetPosition.y();
|
|
|
|
facePlayerPacket.targetZ = targetPosition.z();
|
2020-12-13 23:01:01 +01:00
|
|
|
if (entity != null) {
|
|
|
|
facePlayerPacket.entityId = entity.getEntityId();
|
|
|
|
facePlayerPacket.entityFacePosition = targetPoint == FacePoint.EYE ?
|
|
|
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
|
|
|
|
}
|
|
|
|
playerConnection.sendPacket(facePlayerPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the camera at {@code entity} eyes.
|
|
|
|
*
|
|
|
|
* @param entity the entity to spectate
|
|
|
|
*/
|
|
|
|
public void spectate(@NotNull Entity entity) {
|
2021-06-21 15:01:50 +02:00
|
|
|
playerConnection.sendPacket(new CameraPacket(entity));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets the camera at the player.
|
|
|
|
*/
|
|
|
|
public void stopSpectating() {
|
|
|
|
spectate(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to retrieve the default spawn point.
|
|
|
|
* <p>
|
2021-07-06 20:44:24 +02:00
|
|
|
* Can be altered by the {@link PlayerRespawnEvent#setRespawnPosition(Pos)}.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @return a copy of the default respawn point
|
|
|
|
*/
|
2021-07-06 20:44:24 +02:00
|
|
|
public @NotNull Pos getRespawnPoint() {
|
|
|
|
return respawnPoint;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the default spawn point.
|
|
|
|
*
|
|
|
|
* @param respawnPoint the player respawn point
|
|
|
|
*/
|
2021-07-06 20:44:24 +02:00
|
|
|
public void setRespawnPoint(@NotNull Pos respawnPoint) {
|
2020-12-13 23:01:01 +01:00
|
|
|
this.respawnPoint = respawnPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called after the player teleportation to refresh his position
|
|
|
|
* and send data to his new viewers.
|
|
|
|
*/
|
|
|
|
protected void refreshAfterTeleport() {
|
|
|
|
|
2021-02-25 07:15:51 +01:00
|
|
|
sendPacketsToViewers(getEntityType().getSpawnType().getSpawnPacket(this));
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Update for viewers
|
|
|
|
sendPacketToViewersAndSelf(getVelocityPacket());
|
|
|
|
sendPacketToViewersAndSelf(getMetadataPacket());
|
2021-03-12 01:38:52 +01:00
|
|
|
sendPacketToViewersAndSelf(getPropertiesPacket());
|
|
|
|
sendPacketToViewersAndSelf(getEquipmentsPacket());
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-05-05 05:43:41 +02:00
|
|
|
getInventory().update();
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
{
|
|
|
|
// Send new chunks
|
2021-07-06 20:44:24 +02:00
|
|
|
final Chunk chunk = instance.getChunkAt(position);
|
2020-12-13 23:01:01 +01:00
|
|
|
Check.notNull(chunk, "Tried to interact with an unloaded chunk.");
|
|
|
|
refreshVisibleChunks(chunk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the player food and health values to their maximum.
|
|
|
|
*/
|
|
|
|
protected void refreshHealth() {
|
|
|
|
this.food = 20;
|
|
|
|
this.foodSaturation = 5;
|
|
|
|
// refresh health and send health packet
|
|
|
|
heal();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends an {@link UpdateHealthPacket} to refresh client-side information about health and food.
|
|
|
|
*/
|
|
|
|
protected void sendUpdateHealthPacket() {
|
|
|
|
UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket();
|
|
|
|
updateHealthPacket.health = getHealth();
|
|
|
|
updateHealthPacket.food = food;
|
|
|
|
updateHealthPacket.foodSaturation = foodSaturation;
|
|
|
|
playerConnection.sendPacket(updateHealthPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the percentage displayed in the experience bar.
|
|
|
|
*
|
|
|
|
* @return the exp percentage 0-1
|
|
|
|
*/
|
|
|
|
public float getExp() {
|
|
|
|
return exp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
* @throws IllegalArgumentException if {@code exp} is not between 0 and 1
|
|
|
|
*/
|
|
|
|
public void setExp(float exp) {
|
|
|
|
Check.argCondition(!MathUtils.isBetween(exp, 0, 1), "Exp should be between 0 and 1");
|
|
|
|
|
|
|
|
this.exp = exp;
|
|
|
|
sendExperienceUpdatePacket();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
public void setLevel(int level) {
|
|
|
|
this.level = level;
|
|
|
|
sendExperienceUpdatePacket();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a {@link SetExperiencePacket} to refresh client-side information about the experience bar.
|
|
|
|
*/
|
|
|
|
protected void sendExperienceUpdatePacket() {
|
|
|
|
SetExperiencePacket setExperiencePacket = new SetExperiencePacket();
|
|
|
|
setExperiencePacket.percentage = exp;
|
|
|
|
setExperiencePacket.level = level;
|
|
|
|
playerConnection.sendPacket(setExperiencePacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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()}.
|
|
|
|
* <p>
|
|
|
|
* 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}.
|
|
|
|
*
|
|
|
|
* @param newChunk the current/new player chunk (can be the current one)
|
|
|
|
*/
|
|
|
|
public void refreshVisibleChunks(@NotNull Chunk newChunk) {
|
|
|
|
// Previous chunks indexes
|
2021-07-21 22:21:43 +02:00
|
|
|
final long[] lastVisibleChunks = viewableChunks.stream().mapToLong(ChunkUtils::getChunkIndex).toArray();
|
2020-12-13 23:01:01 +01:00
|
|
|
// New chunks indexes
|
|
|
|
final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange());
|
|
|
|
|
2021-05-07 00:54:38 +02:00
|
|
|
// Update client render distance
|
|
|
|
updateViewPosition(newChunk.getChunkX(), newChunk.getChunkZ());
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
// Unload old chunks
|
2021-07-21 08:42:49 +02:00
|
|
|
ArrayUtils.forDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks, chunkIndex -> {
|
2020-12-13 23:01:01 +01:00
|
|
|
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
|
|
|
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
|
|
|
|
2021-05-07 00:54:38 +02:00
|
|
|
final UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket();
|
2020-12-13 23:01:01 +01:00
|
|
|
unloadChunkPacket.chunkX = chunkX;
|
|
|
|
unloadChunkPacket.chunkZ = chunkZ;
|
2021-06-24 23:40:09 +02:00
|
|
|
//playerConnection.sendPacket(unloadChunkPacket);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
|
2021-06-24 23:40:09 +02:00
|
|
|
if (chunk != null) {
|
2020-12-13 23:01:01 +01:00
|
|
|
chunk.removeViewer(this);
|
2021-06-24 23:40:09 +02:00
|
|
|
}
|
2021-07-21 08:42:49 +02:00
|
|
|
});
|
2020-12-13 23:01:01 +01:00
|
|
|
// Load new chunks
|
2021-07-21 08:42:49 +02:00
|
|
|
ArrayUtils.forDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks, chunkIndex -> {
|
2020-12-13 23:01:01 +01:00
|
|
|
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
|
|
|
|
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
|
2021-07-11 02:54:02 +02:00
|
|
|
this.instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(chunk -> {
|
2020-12-13 23:01:01 +01:00
|
|
|
if (chunk == null) {
|
|
|
|
// Cannot load chunk (auto load is not enabled)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
chunk.addViewer(this);
|
|
|
|
});
|
2021-07-21 08:42:49 +02:00
|
|
|
});
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-09 07:08:35 +01:00
|
|
|
public void refreshVisibleChunks() {
|
|
|
|
final Chunk chunk = getChunk();
|
|
|
|
if (chunk != null) {
|
|
|
|
refreshVisibleChunks(chunk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
2021-03-21 19:47:22 +01:00
|
|
|
* Refreshes the list of entities that the player should be able to see based
|
|
|
|
* on {@link MinecraftServer#getEntityViewDistance()} and {@link Entity#isAutoViewable()}.
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @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
|
2021-05-03 01:58:17 +02:00
|
|
|
this.viewableEntities.stream()
|
|
|
|
.filter(entity -> entity.getDistance(this) > maximalDistance)
|
|
|
|
.forEach(entity -> {
|
|
|
|
// Entity shouldn't be viewable anymore
|
|
|
|
if (isAutoViewable()) {
|
|
|
|
entity.removeViewer(this);
|
|
|
|
}
|
|
|
|
if (entity instanceof Player && entity.isAutoViewable()) {
|
|
|
|
removeViewer((Player) entity);
|
|
|
|
}
|
|
|
|
});
|
2020-12-13 23:01:01 +01:00
|
|
|
// Manage entities in unchecked chunks
|
2020-12-23 10:40:50 +01:00
|
|
|
EntityUtils.forEachRange(instance, newChunk.toPosition(), entityViewDistance, entity -> {
|
2020-12-23 10:51:59 +01:00
|
|
|
if (entity.isAutoViewable() && !entity.viewers.contains(this)) {
|
2020-12-23 10:40:50 +01:00
|
|
|
entity.addViewer(this);
|
|
|
|
}
|
2020-12-23 10:51:59 +01:00
|
|
|
if (entity instanceof Player && isAutoViewable() && !viewers.contains(entity)) {
|
2020-12-23 10:40:50 +01:00
|
|
|
addViewer((Player) entity);
|
|
|
|
}
|
|
|
|
});
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player connection.
|
|
|
|
* <p>
|
|
|
|
* Used to send packets and get stuff related to the connection.
|
|
|
|
*
|
|
|
|
* @return the player connection
|
|
|
|
*/
|
|
|
|
@NotNull
|
|
|
|
public PlayerConnection getPlayerConnection() {
|
|
|
|
return playerConnection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player is online or not.
|
|
|
|
*
|
|
|
|
* @return true if the player is online, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isOnline() {
|
|
|
|
return playerConnection.isOnline();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player settings.
|
|
|
|
*
|
|
|
|
* @return the player settings
|
|
|
|
*/
|
|
|
|
@NotNull
|
|
|
|
public PlayerSettings getSettings() {
|
|
|
|
return settings;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player dimension.
|
|
|
|
*
|
|
|
|
* @return the player current dimension
|
|
|
|
*/
|
|
|
|
public DimensionType getDimensionType() {
|
|
|
|
return dimensionType;
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
public PlayerInventory getInventory() {
|
|
|
|
return inventory;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to get the player latency,
|
|
|
|
* computed by seeing how long it takes the client to answer the {@link KeepAlivePacket} packet.
|
|
|
|
*
|
|
|
|
* @return the player latency
|
|
|
|
*/
|
|
|
|
public int getLatency() {
|
|
|
|
return latency;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player {@link GameMode}.
|
|
|
|
*
|
|
|
|
* @return the player current gamemode
|
|
|
|
*/
|
|
|
|
public GameMode getGameMode() {
|
|
|
|
return gameMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player {@link GameMode}.
|
|
|
|
*
|
|
|
|
* @param gameMode the new player GameMode
|
|
|
|
*/
|
|
|
|
public void setGameMode(@NotNull GameMode gameMode) {
|
|
|
|
this.gameMode = gameMode;
|
|
|
|
|
2020-12-18 00:57:23 +01:00
|
|
|
// Condition to prevent sending the packets before spawning the player
|
|
|
|
if (isActive()) {
|
|
|
|
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);
|
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if this player is in creative. Used for code readability.
|
|
|
|
*
|
|
|
|
* @return true if the player is in creative mode
|
|
|
|
*/
|
|
|
|
public boolean isCreative() {
|
|
|
|
return gameMode == GameMode.CREATIVE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the dimension of the player.
|
|
|
|
* Mostly unsafe since it requires sending chunks after.
|
|
|
|
*
|
|
|
|
* @param dimensionType the new player dimension
|
|
|
|
*/
|
|
|
|
protected void sendDimension(@NotNull DimensionType dimensionType) {
|
2021-03-21 19:47:22 +01:00
|
|
|
Check.argCondition(dimensionType.equals(getDimensionType()),
|
|
|
|
"The dimension needs to be different than the current one!");
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
this.dimensionType = dimensionType;
|
|
|
|
RespawnPacket respawnPacket = new RespawnPacket();
|
|
|
|
respawnPacket.dimensionType = dimensionType;
|
|
|
|
respawnPacket.gameMode = gameMode;
|
|
|
|
respawnPacket.isFlat = levelFlat;
|
|
|
|
playerConnection.sendPacket(respawnPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kicks the player with a reason.
|
|
|
|
*
|
|
|
|
* @param text the kick reason
|
2021-03-01 16:25:23 +01:00
|
|
|
* @deprecated Use {@link #kick(Component)}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-03-01 16:25:23 +01:00
|
|
|
@Deprecated
|
2020-12-31 19:50:26 +01:00
|
|
|
public void kick(@NotNull JsonMessage text) {
|
2021-03-01 16:25:23 +01:00
|
|
|
this.kick(text.asComponent());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kicks the player with a reason.
|
|
|
|
*
|
|
|
|
* @param message the kick reason
|
|
|
|
* @deprecated Use {@link #kick(Component)}
|
|
|
|
*/
|
|
|
|
@Deprecated
|
|
|
|
public void kick(@NotNull String message) {
|
|
|
|
this.kick(Component.text(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kicks the player with a reason.
|
|
|
|
*
|
|
|
|
* @param component the reason
|
|
|
|
*/
|
|
|
|
public void kick(@NotNull Component component) {
|
2021-01-06 19:02:35 +01:00
|
|
|
final ConnectionState connectionState = playerConnection.getConnectionState();
|
|
|
|
|
|
|
|
// Packet type depends on the current player connection state
|
|
|
|
final ServerPacket disconnectPacket;
|
|
|
|
if (connectionState == ConnectionState.LOGIN) {
|
2021-03-12 16:33:19 +01:00
|
|
|
disconnectPacket = new LoginDisconnectPacket(component);
|
2021-01-06 19:02:35 +01:00
|
|
|
} else {
|
2021-03-12 16:33:19 +01:00
|
|
|
disconnectPacket = new DisconnectPacket(component);
|
2021-01-06 19:02:35 +01:00
|
|
|
}
|
|
|
|
|
2021-03-14 21:34:38 +01:00
|
|
|
if (playerConnection instanceof NettyPlayerConnection) {
|
|
|
|
((NettyPlayerConnection) playerConnection).writeAndFlush(disconnectPacket);
|
|
|
|
playerConnection.disconnect();
|
|
|
|
} else {
|
|
|
|
playerConnection.sendPacket(disconnectPacket);
|
|
|
|
playerConnection.refreshOnline(false);
|
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the current held slot for the player.
|
|
|
|
*
|
|
|
|
* @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) {
|
|
|
|
Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "Slot has to be between 0 and 8");
|
|
|
|
|
|
|
|
HeldItemChangePacket heldItemChangePacket = new HeldItemChangePacket();
|
|
|
|
heldItemChangePacket.slot = slot;
|
|
|
|
playerConnection.sendPacket(heldItemChangePacket);
|
|
|
|
refreshHeldSlot(slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player held slot (0-8).
|
|
|
|
*
|
|
|
|
* @return the current held slot for the player
|
|
|
|
*/
|
|
|
|
public byte getHeldSlot() {
|
|
|
|
return heldSlot;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setTeam(Team team) {
|
|
|
|
super.setTeam(team);
|
2021-03-21 19:47:22 +01:00
|
|
|
if (team != null) {
|
|
|
|
var players = MinecraftServer.getConnectionManager().getOnlinePlayers();
|
|
|
|
PacketUtils.sendGroupedPacket(players, team.createTeamsCreationPacket());
|
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the tag below the name.
|
|
|
|
*
|
|
|
|
* @param belowNameTag The new below name tag
|
|
|
|
*/
|
|
|
|
public void setBelowNameTag(BelowNameTag belowNameTag) {
|
|
|
|
if (this.belowNameTag == belowNameTag) return;
|
|
|
|
|
|
|
|
if (this.belowNameTag != null) {
|
|
|
|
this.belowNameTag.removeViewer(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.belowNameTag = belowNameTag;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player open inventory.
|
|
|
|
*
|
|
|
|
* @return the currently open inventory, null if there is not (player inventory is not detected)
|
|
|
|
*/
|
|
|
|
@Nullable
|
|
|
|
public Inventory getOpenInventory() {
|
|
|
|
return openInventory;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Opens the specified Inventory, close the previous inventory if existing.
|
|
|
|
*
|
|
|
|
* @param inventory the inventory to open
|
|
|
|
* @return true if the inventory has been opened/sent to the player, false otherwise (cancelled by event)
|
|
|
|
*/
|
|
|
|
public boolean openInventory(@NotNull Inventory inventory) {
|
|
|
|
|
2020-12-16 03:21:59 +01:00
|
|
|
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(inventory, this);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.callCancellable(inventoryOpenEvent, () -> {
|
2021-04-24 19:14:19 +02:00
|
|
|
Inventory openInventory = getOpenInventory();
|
|
|
|
if (openInventory != null) {
|
|
|
|
openInventory.removeViewer(this);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Inventory newInventory = inventoryOpenEvent.getInventory();
|
|
|
|
|
|
|
|
if (newInventory == null) {
|
|
|
|
// just close the inventory
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-17 19:04:48 +01:00
|
|
|
OpenWindowPacket openWindowPacket = new OpenWindowPacket(newInventory.getTitle());
|
2020-12-13 23:01:01 +01:00
|
|
|
openWindowPacket.windowId = newInventory.getWindowId();
|
|
|
|
openWindowPacket.windowType = newInventory.getInventoryType().getWindowType();
|
|
|
|
playerConnection.sendPacket(openWindowPacket);
|
|
|
|
newInventory.addViewer(this);
|
|
|
|
this.openInventory = newInventory;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return !inventoryOpenEvent.isCancelled();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Closes the current inventory if there is any.
|
|
|
|
* It closes the player inventory (when opened) if {@link #getOpenInventory()} returns null.
|
|
|
|
*/
|
|
|
|
public void closeInventory() {
|
|
|
|
Inventory openInventory = getOpenInventory();
|
|
|
|
|
|
|
|
// Drop cursor item when closing inventory
|
|
|
|
ItemStack cursorItem;
|
|
|
|
if (openInventory == null) {
|
|
|
|
cursorItem = getInventory().getCursorItem();
|
2021-04-02 18:13:02 +02:00
|
|
|
getInventory().setCursorItem(ItemStack.AIR);
|
2020-12-13 23:01:01 +01:00
|
|
|
} else {
|
|
|
|
cursorItem = openInventory.getCursorItem(this);
|
2021-04-02 18:13:02 +02:00
|
|
|
openInventory.setCursorItem(this, ItemStack.AIR);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
if (!cursorItem.isAir()) {
|
|
|
|
// Add item to inventory if he hasn't been able to drop it
|
|
|
|
if (!dropItem(cursorItem)) {
|
|
|
|
getInventory().addItemStack(cursorItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseWindowPacket closeWindowPacket = new CloseWindowPacket();
|
|
|
|
if (openInventory == null) {
|
|
|
|
closeWindowPacket.windowId = 0;
|
|
|
|
} else {
|
|
|
|
closeWindowPacket.windowId = openInventory.getWindowId();
|
|
|
|
openInventory.removeViewer(this); // Clear cache
|
|
|
|
this.openInventory = null;
|
|
|
|
}
|
|
|
|
playerConnection.sendPacket(closeWindowPacket);
|
|
|
|
inventory.update();
|
|
|
|
this.didCloseInventory = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used internally to prevent an inventory click to be processed
|
|
|
|
* when the inventory listeners closed the inventory.
|
|
|
|
* <p>
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used internally to reset the didCloseInventory field.
|
|
|
|
* <p>
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player viewable chunks.
|
|
|
|
* <p>
|
|
|
|
* WARNING: adding or removing a chunk there will not load/unload it,
|
|
|
|
* use {@link Chunk#addViewer(Player)} or {@link Chunk#removeViewer(Player)}.
|
|
|
|
*
|
|
|
|
* @return a {@link Set} containing all the chunks that the player sees
|
|
|
|
*/
|
|
|
|
public Set<Chunk> getViewableChunks() {
|
|
|
|
return viewableChunks;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a {@link UpdateViewPositionPacket} to the player.
|
|
|
|
*
|
|
|
|
* @param chunkX the chunk X
|
|
|
|
* @param chunkZ the chunk Z
|
|
|
|
*/
|
|
|
|
public void updateViewPosition(int chunkX, int chunkZ) {
|
|
|
|
UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket();
|
|
|
|
updateViewPositionPacket.chunkX = chunkX;
|
|
|
|
updateViewPositionPacket.chunkZ = chunkZ;
|
|
|
|
playerConnection.sendPacket(updateViewPositionPacket);
|
|
|
|
}
|
|
|
|
|
2021-05-01 02:21:11 +02:00
|
|
|
public int getNextTeleportId() {
|
|
|
|
return teleportId.getAndIncrement();
|
|
|
|
}
|
|
|
|
|
2021-03-08 16:49:16 +01:00
|
|
|
public int getLastSentTeleportId() {
|
|
|
|
return teleportId.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getLastReceivedTeleportId() {
|
|
|
|
return receivedTeleportId;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void refreshReceivedTeleportId(int receivedTeleportId) {
|
|
|
|
this.receivedTeleportId = receivedTeleportId;
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
2021-05-15 21:07:42 +02:00
|
|
|
* @see Entity#synchronizePosition(boolean)
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-05-01 00:05:49 +02:00
|
|
|
@Override
|
|
|
|
@ApiStatus.Internal
|
2021-05-15 21:07:42 +02:00
|
|
|
protected void synchronizePosition(boolean includeSelf) {
|
2021-05-15 21:11:48 +02:00
|
|
|
if (includeSelf) {
|
2021-07-20 03:06:27 +02:00
|
|
|
playerConnection.sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, teleportId.incrementAndGet(), false));
|
2021-05-15 21:11:48 +02:00
|
|
|
}
|
2021-05-15 21:07:42 +02:00
|
|
|
super.synchronizePosition(includeSelf);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player permission level.
|
|
|
|
*
|
|
|
|
* @return the player permission level
|
|
|
|
*/
|
|
|
|
public int getPermissionLevel() {
|
|
|
|
return permissionLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player permission level.
|
|
|
|
*
|
|
|
|
* @param permissionLevel the new player permission level
|
|
|
|
* @throws IllegalArgumentException if {@code permissionLevel} is not between 0 and 4
|
|
|
|
*/
|
|
|
|
public void setPermissionLevel(int permissionLevel) {
|
|
|
|
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
|
|
|
|
// TODO remove magic values
|
|
|
|
final byte permissionLevelStatus = (byte) (24 + permissionLevel);
|
|
|
|
triggerStatus(permissionLevelStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets or remove the reduced debug screen.
|
|
|
|
*
|
|
|
|
* @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
|
|
|
|
// TODO remove magic values
|
|
|
|
final byte debugScreenStatus = (byte) (reduced ? 22 : 23);
|
|
|
|
triggerStatus(debugScreenStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player has the reduced debug screen.
|
|
|
|
*
|
|
|
|
* @return true if the player has the reduced debug screen, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean hasReducedDebugScreenInformation() {
|
|
|
|
return reducedDebugScreenInformation;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The invulnerable field appear in the {@link PlayerAbilitiesPacket} packet.
|
|
|
|
*
|
|
|
|
* @return true if the player is invulnerable, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isInvulnerable() {
|
|
|
|
return super.isInvulnerable();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This do update the {@code invulnerable} field in the packet {@link PlayerAbilitiesPacket}
|
|
|
|
* and prevent the player from receiving damage.
|
|
|
|
*
|
|
|
|
* @param invulnerable should the player be invulnerable
|
|
|
|
*/
|
|
|
|
public void setInvulnerable(boolean invulnerable) {
|
|
|
|
super.setInvulnerable(invulnerable);
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player is currently flying.
|
|
|
|
*
|
|
|
|
* @return true if the player if flying, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isFlying() {
|
|
|
|
return flying;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the player flying.
|
|
|
|
*
|
|
|
|
* @param flying should the player fly
|
|
|
|
*/
|
|
|
|
public void setFlying(boolean flying) {
|
|
|
|
this.flying = flying;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the internal flying field.
|
|
|
|
* <p>
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player is allowed to fly.
|
|
|
|
*
|
|
|
|
* @return true if the player if allowed to fly, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean isAllowFlying() {
|
|
|
|
return allowFlying;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allows or forbid the player to fly.
|
|
|
|
*
|
|
|
|
* @param allowFlying should the player be allowed to fly
|
|
|
|
*/
|
|
|
|
public void setAllowFlying(boolean allowFlying) {
|
|
|
|
this.allowFlying = allowFlying;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isInstantBreak() {
|
|
|
|
return instantBreak;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player ability "Creative Mode".
|
|
|
|
*
|
|
|
|
* @param instantBreak true to allow instant break
|
2021-06-17 15:50:28 +02:00
|
|
|
* @see <a href="https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29">player abilities</a>
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
|
|
|
public void setInstantBreak(boolean instantBreak) {
|
|
|
|
this.instantBreak = instantBreak;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player flying speed.
|
|
|
|
*
|
|
|
|
* @return the flying speed of the player
|
|
|
|
*/
|
|
|
|
public float getFlyingSpeed() {
|
|
|
|
return flyingSpeed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the internal field and send a {@link PlayerAbilitiesPacket} with the new flying speed.
|
|
|
|
*
|
|
|
|
* @param flyingSpeed the new flying speed of the player
|
|
|
|
*/
|
|
|
|
public void setFlyingSpeed(float flyingSpeed) {
|
|
|
|
this.flyingSpeed = flyingSpeed;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
2020-12-15 13:41:42 +01:00
|
|
|
public float getFieldViewModifier() {
|
|
|
|
return fieldViewModifier;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2020-12-15 13:41:42 +01:00
|
|
|
public void setFieldViewModifier(float fieldViewModifier) {
|
|
|
|
this.fieldViewModifier = fieldViewModifier;
|
2020-12-13 23:01:01 +01:00
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is the map used to send the statistic packet.
|
|
|
|
* It is possible to add/remove/change statistic value directly into it.
|
|
|
|
*
|
|
|
|
* @return the modifiable statistic map
|
|
|
|
*/
|
|
|
|
@NotNull
|
|
|
|
public Map<PlayerStatistic, Integer> getStatisticValueMap() {
|
|
|
|
return statisticValueMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player vehicle information.
|
|
|
|
*
|
|
|
|
* @return the player vehicle information
|
|
|
|
*/
|
|
|
|
@NotNull
|
|
|
|
public PlayerVehicleInformation getVehicleInformation() {
|
|
|
|
return vehicleInformation;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-12-15 13:41:42 +01:00
|
|
|
* Sends to the player a {@link PlayerAbilitiesPacket} with all the updated fields.
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
|
|
|
protected void refreshAbilities() {
|
|
|
|
PlayerAbilitiesPacket playerAbilitiesPacket = new PlayerAbilitiesPacket();
|
|
|
|
playerAbilitiesPacket.invulnerable = invulnerable;
|
|
|
|
playerAbilitiesPacket.flying = flying;
|
|
|
|
playerAbilitiesPacket.allowFlying = allowFlying;
|
|
|
|
playerAbilitiesPacket.instantBreak = instantBreak;
|
|
|
|
playerAbilitiesPacket.flyingSpeed = flyingSpeed;
|
2020-12-15 13:41:42 +01:00
|
|
|
playerAbilitiesPacket.fieldViewModifier = fieldViewModifier;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
playerConnection.sendPacket(playerAbilitiesPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* All packets in the queue are executed in the {@link #update(long)} method
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* @param packet the packet to add in the queue
|
|
|
|
*/
|
|
|
|
public void addPacketToQueue(@NotNull ClientPlayPacket packet) {
|
|
|
|
this.packets.add(packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the storage player latency and update its tab value.
|
|
|
|
*
|
|
|
|
* @param latency the new player latency
|
|
|
|
*/
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void refreshOnGround(boolean onGround) {
|
|
|
|
this.onGround = onGround;
|
2021-05-31 18:53:57 +02:00
|
|
|
if (this.onGround && this.isFlyingWithElytra()) {
|
2021-05-14 19:00:07 +02:00
|
|
|
this.setFlyingWithElytra(false);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(new PlayerStopFlyingWithElytraEvent(this));
|
2021-05-14 19:00:07 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to change internally the last sent last keep alive id.
|
|
|
|
* <p>
|
|
|
|
* Warning: could lead to have the player kicked because of a wrong keep alive packet.
|
|
|
|
*
|
|
|
|
* @param lastKeepAlive the new lastKeepAlive id
|
|
|
|
*/
|
|
|
|
public void refreshKeepAlive(long lastKeepAlive) {
|
|
|
|
this.lastKeepAlive = lastKeepAlive;
|
|
|
|
this.answerKeepAlive = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean didAnswerKeepAlive() {
|
|
|
|
return answerKeepAlive;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void refreshAnswerKeepAlive(boolean answerKeepAlive) {
|
|
|
|
this.answerKeepAlive = answerKeepAlive;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the held item for the player viewers
|
|
|
|
* Also cancel eating if {@link #isEating()} was true.
|
|
|
|
* <p>
|
|
|
|
* Warning: the player will not be noticed by this chance, only his viewers,
|
|
|
|
* see instead: {@link #setHeldItemSlot(byte)}.
|
|
|
|
*
|
|
|
|
* @param slot the new held slot
|
|
|
|
*/
|
|
|
|
public void refreshHeldSlot(byte slot) {
|
|
|
|
this.heldSlot = slot;
|
2021-05-11 14:10:45 +02:00
|
|
|
syncEquipment(EquipmentSlot.MAIN_HAND);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-04-13 22:59:40 +02:00
|
|
|
refreshEating(null);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 22:59:40 +02:00
|
|
|
public void refreshEating(@Nullable Hand eatingHand, long eatingTime) {
|
|
|
|
this.eatingHand = eatingHand;
|
|
|
|
if (eatingHand != null) {
|
2020-12-13 23:01:01 +01:00
|
|
|
this.startEatingTime = System.currentTimeMillis();
|
|
|
|
this.eatingTime = eatingTime;
|
|
|
|
} else {
|
|
|
|
this.startEatingTime = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 22:59:40 +02:00
|
|
|
public void refreshEating(@Nullable Hand eatingHand) {
|
|
|
|
refreshEating(eatingHand, defaultEatingTime);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to call {@link ItemUpdateStateEvent} with the proper item
|
|
|
|
* It does check which hand to get the item to update.
|
|
|
|
*
|
|
|
|
* @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
|
2021-05-23 20:36:51 +02:00
|
|
|
* @deprecated Use {@link #callItemUpdateStateEvent(Hand)} instead
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-05-23 20:36:51 +02:00
|
|
|
@Deprecated
|
2021-04-13 22:59:40 +02:00
|
|
|
public @Nullable ItemUpdateStateEvent callItemUpdateStateEvent(boolean allowFood, @Nullable Hand hand) {
|
|
|
|
if (hand == null)
|
2020-12-13 23:01:01 +01:00
|
|
|
return null;
|
|
|
|
|
2021-04-13 22:59:40 +02:00
|
|
|
final ItemStack updatedItem = getItemInHand(hand);
|
2020-12-13 23:01:01 +01:00
|
|
|
final boolean isFood = updatedItem.getMaterial().isFood();
|
|
|
|
|
|
|
|
if (isFood && !allowFood)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
ItemUpdateStateEvent itemUpdateStateEvent = new ItemUpdateStateEvent(this, hand, updatedItem);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(itemUpdateStateEvent);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
return itemUpdateStateEvent;
|
|
|
|
}
|
|
|
|
|
2021-05-23 20:19:46 +02:00
|
|
|
/**
|
|
|
|
* Used to call {@link ItemUpdateStateEvent} with the proper item
|
|
|
|
* It does check which hand to get the item to update. Allows food.
|
|
|
|
*
|
|
|
|
* @return the called {@link ItemUpdateStateEvent},
|
|
|
|
* null if there is no item to update the state
|
|
|
|
*/
|
|
|
|
public @Nullable ItemUpdateStateEvent callItemUpdateStateEvent(@Nullable Hand hand) {
|
|
|
|
return callItemUpdateStateEvent(true, hand);
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
public void refreshVehicleSteer(float sideways, float forward, boolean jump, boolean unmount) {
|
|
|
|
this.vehicleInformation.refresh(sideways, forward, jump, unmount);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the chunk range of the viewers,
|
|
|
|
* which is {@link MinecraftServer#getChunkViewDistance()} or {@link PlayerSettings#getViewDistance()}
|
|
|
|
* based on which one is the lowest
|
|
|
|
*/
|
|
|
|
public int getChunkRange() {
|
2021-05-05 18:11:24 +02:00
|
|
|
return Math.min(getSettings().viewDistance, MinecraftServer.getChunkViewDistance());
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the last sent keep alive id.
|
|
|
|
*
|
|
|
|
* @return the last keep alive id sent to the player
|
|
|
|
*/
|
|
|
|
public long getLastKeepAlive() {
|
|
|
|
return lastKeepAlive;
|
|
|
|
}
|
|
|
|
|
2021-03-11 18:07:04 +01:00
|
|
|
@Override
|
|
|
|
public @NotNull HoverEvent<ShowEntity> asHoverEvent(@NotNull UnaryOperator<ShowEntity> op) {
|
|
|
|
return HoverEvent.showEntity(ShowEntity.of(EntityType.PLAYER, this.uuid, this.displayName));
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Gets the packet to add the player from the tab-list.
|
|
|
|
*
|
|
|
|
* @return a {@link PlayerInfoPacket} to add the player
|
|
|
|
*/
|
|
|
|
@NotNull
|
|
|
|
protected PlayerInfoPacket getAddPlayerToList() {
|
|
|
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
|
|
|
|
|
|
|
|
PlayerInfoPacket.AddPlayer addPlayer =
|
|
|
|
new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), getLatency());
|
2021-03-10 17:13:27 +01:00
|
|
|
addPlayer.displayName = displayName;
|
2020-12-13 23:01:01 +01: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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the packet to remove the player from the tab-list.
|
|
|
|
*
|
|
|
|
* @return a {@link PlayerInfoPacket} to remove the player
|
|
|
|
*/
|
|
|
|
@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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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>
|
|
|
|
* WARNING: this alone does not sync the player, please use {@link #addViewer(Player)}.
|
|
|
|
*
|
|
|
|
* @param connection the connection to show the player to
|
|
|
|
*/
|
|
|
|
protected void showPlayer(@NotNull PlayerConnection connection) {
|
|
|
|
connection.sendPacket(getAddPlayerToList());
|
|
|
|
|
2021-02-25 07:15:51 +01:00
|
|
|
connection.sendPacket(getEntityType().getSpawnType().getSpawnPacket(this));
|
2020-12-13 23:01:01 +01:00
|
|
|
connection.sendPacket(getVelocityPacket());
|
|
|
|
connection.sendPacket(getMetadataPacket());
|
2021-03-12 01:38:52 +01:00
|
|
|
connection.sendPacket(getEquipmentsPacket());
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
if (hasPassenger()) {
|
|
|
|
connection.sendPacket(getPassengersPacket());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Team
|
|
|
|
if (this.getTeam() != null)
|
|
|
|
connection.sendPacket(this.getTeam().createTeamsCreationPacket());
|
|
|
|
|
|
|
|
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
|
|
|
entityHeadLookPacket.entityId = getEntityId();
|
2021-07-06 20:44:24 +02:00
|
|
|
entityHeadLookPacket.yaw = position.yaw();
|
2020-12-13 23:01:01 +01:00
|
|
|
connection.sendPacket(entityHeadLookPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public ItemStack getItemInMainHand() {
|
|
|
|
return inventory.getItemInMainHand();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInMainHand(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setItemInMainHand(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public ItemStack getItemInOffHand() {
|
|
|
|
return inventory.getItemInOffHand();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInOffHand(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setItemInOffHand(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public ItemStack getHelmet() {
|
|
|
|
return inventory.getHelmet();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setHelmet(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setHelmet(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public ItemStack getChestplate() {
|
|
|
|
return inventory.getChestplate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setChestplate(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setChestplate(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public ItemStack getLeggings() {
|
|
|
|
return inventory.getLeggings();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setLeggings(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setLeggings(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
@Override
|
|
|
|
public ItemStack getBoots() {
|
|
|
|
return inventory.getBoots();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setBoots(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setBoots(itemStack);
|
|
|
|
}
|
|
|
|
|
2021-03-03 17:32:51 +01:00
|
|
|
@Override
|
|
|
|
public Locale getLocale() {
|
|
|
|
return settings.locale == null ? null : Locale.forLanguageTag(settings.locale);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the player's locale. This will only set the locale of the player as it
|
|
|
|
* is stored in the server. This will also be reset if the settings are refreshed.
|
|
|
|
*
|
|
|
|
* @param locale the new locale
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void setLocale(@Nullable Locale locale) {
|
|
|
|
settings.locale = locale == null ? null : locale.toLanguageTag();
|
|
|
|
}
|
|
|
|
|
2021-03-12 16:36:22 +01:00
|
|
|
@Override
|
2021-03-12 18:26:57 +01:00
|
|
|
public @NotNull Identity identity() {
|
2021-03-15 14:53:06 +01:00
|
|
|
return this.identity;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-06-10 14:53:34 +02:00
|
|
|
public @NotNull Pointers pointers() {
|
|
|
|
return this.pointers;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-03-15 14:53:06 +01:00
|
|
|
public void setUuid(@NotNull UUID uuid) {
|
|
|
|
super.setUuid(uuid);
|
|
|
|
// update identity
|
|
|
|
this.identity = Identity.identity(uuid);
|
2021-03-12 16:36:22 +01:00
|
|
|
}
|
|
|
|
|
2021-04-04 15:10:06 +02:00
|
|
|
@Override
|
|
|
|
public boolean isPlayer() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Player asPlayer() {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Represents the main or off hand of the player.
|
|
|
|
*/
|
|
|
|
public enum Hand {
|
|
|
|
MAIN,
|
|
|
|
OFF
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum FacePoint {
|
|
|
|
FEET,
|
|
|
|
EYE
|
|
|
|
}
|
|
|
|
|
|
|
|
// Settings enum
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents where is located the main hand of the player (can be changed in Minecraft option).
|
|
|
|
*/
|
|
|
|
public enum MainHand {
|
|
|
|
LEFT,
|
|
|
|
RIGHT
|
|
|
|
}
|
|
|
|
|
2021-05-05 19:21:38 +02:00
|
|
|
/**
|
|
|
|
* @deprecated See {@link ChatMessageType}
|
|
|
|
*/
|
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public enum ChatMode {
|
|
|
|
ENABLED,
|
|
|
|
COMMANDS_ONLY,
|
|
|
|
HIDDEN
|
|
|
|
}
|
|
|
|
|
|
|
|
public class PlayerSettings {
|
|
|
|
|
|
|
|
private String locale;
|
|
|
|
private byte viewDistance;
|
2021-05-05 19:21:38 +02:00
|
|
|
private ChatMessageType chatMessageType;
|
2020-12-13 23:01:01 +01:00
|
|
|
private boolean chatColors;
|
|
|
|
private byte displayedSkinParts;
|
|
|
|
private MainHand mainHand;
|
|
|
|
|
2021-05-07 02:24:28 +02:00
|
|
|
public PlayerSettings() {
|
|
|
|
viewDistance = 2;
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* The player game language.
|
|
|
|
*
|
|
|
|
* @return the player locale
|
|
|
|
*/
|
|
|
|
public String getLocale() {
|
|
|
|
return locale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player view distance.
|
|
|
|
*
|
|
|
|
* @return the player view distance
|
|
|
|
*/
|
|
|
|
public byte getViewDistance() {
|
|
|
|
return viewDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player chat mode.
|
|
|
|
*
|
|
|
|
* @return the player chat mode
|
2021-05-06 17:12:46 +02:00
|
|
|
* @deprecated Use {@link #getChatMessageType()}
|
2020-12-13 23:01:01 +01:00
|
|
|
*/
|
2021-05-06 17:12:46 +02:00
|
|
|
@Deprecated
|
2020-12-13 23:01:01 +01:00
|
|
|
public ChatMode getChatMode() {
|
2021-05-05 19:21:38 +02:00
|
|
|
return ChatMode.values()[chatMessageType.ordinal()];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the messages this player wants to receive.
|
|
|
|
*
|
|
|
|
* @return the messages
|
|
|
|
*/
|
2021-05-06 17:12:46 +02:00
|
|
|
public @Nullable ChatMessageType getChatMessageType() {
|
2021-05-05 19:21:38 +02:00
|
|
|
return chatMessageType;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets if the player has chat colors enabled.
|
|
|
|
*
|
|
|
|
* @return true if chat colors are enabled, false otherwise
|
|
|
|
*/
|
|
|
|
public boolean hasChatColors() {
|
|
|
|
return chatColors;
|
|
|
|
}
|
|
|
|
|
|
|
|
public byte getDisplayedSkinParts() {
|
|
|
|
return displayedSkinParts;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player main hand.
|
|
|
|
*
|
|
|
|
* @return the player main hand
|
|
|
|
*/
|
|
|
|
public MainHand getMainHand() {
|
|
|
|
return mainHand;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the player settings internally.
|
|
|
|
* <p>
|
|
|
|
* WARNING: the player will not be noticed by this change, probably unsafe.
|
|
|
|
*
|
|
|
|
* @param locale the player locale
|
|
|
|
* @param viewDistance the player view distance
|
2021-05-31 18:53:57 +02:00
|
|
|
* @param chatMessageType the chat messages the player wishes to receive
|
2021-05-05 19:21:38 +02:00
|
|
|
* @param chatColors if chat colors should be displayed
|
2020-12-13 23:01:01 +01:00
|
|
|
* @param displayedSkinParts the player displayed skin parts
|
|
|
|
* @param mainHand the player main hand
|
|
|
|
*/
|
2021-05-05 19:21:38 +02:00
|
|
|
public void refresh(String locale, byte viewDistance, ChatMessageType chatMessageType, boolean chatColors,
|
2020-12-13 23:01:01 +01:00
|
|
|
byte displayedSkinParts, MainHand mainHand) {
|
|
|
|
|
2021-03-09 07:08:35 +01:00
|
|
|
final boolean viewDistanceChanged = this.viewDistance != viewDistance;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
this.locale = locale;
|
|
|
|
this.viewDistance = viewDistance;
|
2021-05-05 19:21:38 +02:00
|
|
|
this.chatMessageType = chatMessageType;
|
2020-12-13 23:01:01 +01:00
|
|
|
this.chatColors = chatColors;
|
|
|
|
this.displayedSkinParts = displayedSkinParts;
|
|
|
|
this.mainHand = mainHand;
|
2021-01-30 04:44:44 +01:00
|
|
|
|
2021-06-09 20:02:22 +02:00
|
|
|
// TODO: Use the metadata object here
|
|
|
|
metadata.setIndex((byte) 17, Metadata.Byte(displayedSkinParts));
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Client changed his view distance in the settings
|
|
|
|
if (viewDistanceChanged) {
|
2021-03-09 07:08:35 +01:00
|
|
|
refreshVisibleChunks();
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:25:24 +02:00
|
|
|
}
|