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-10-15 15:54:11 +02:00
|
|
|
import net.kyori.adventure.text.format.NamedTextColor;
|
2021-10-31 19:29:41 +01:00
|
|
|
import net.kyori.adventure.title.TitlePart;
|
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-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;
|
2021-08-22 13:56:12 +02:00
|
|
|
import net.minestom.server.event.GlobalHandles;
|
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;
|
2021-11-01 18:04:00 +01:00
|
|
|
import net.minestom.server.instance.EntityTracker;
|
2020-04-24 03:25:58 +02:00
|
|
|
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;
|
2021-11-30 17:49:41 +01:00
|
|
|
import net.minestom.server.network.packet.client.ClientPacket;
|
2020-10-16 11:37:00 +02:00
|
|
|
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
|
2021-11-17 06:31:24 +01:00
|
|
|
import net.minestom.server.network.packet.server.SendablePacket;
|
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-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.player.PlayerConnection;
|
2021-08-08 19:11:47 +02:00
|
|
|
import net.minestom.server.network.player.PlayerSocketConnection;
|
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;
|
2021-07-28 14:29:28 +02:00
|
|
|
import net.minestom.server.statistic.PlayerStatistic;
|
2022-01-20 04:57:41 +01:00
|
|
|
import net.minestom.server.timer.SchedulerManager;
|
2021-07-25 06:30:49 +02:00
|
|
|
import net.minestom.server.utils.MathUtils;
|
|
|
|
import net.minestom.server.utils.PacketUtils;
|
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;
|
2021-11-01 18:04:00 +01:00
|
|
|
import net.minestom.server.utils.function.IntegerBiConsumer;
|
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-11-20 11:56:35 +01:00
|
|
|
import org.jctools.queues.MessagePassingQueue;
|
|
|
|
import org.jctools.queues.MpscUnboundedXaddArrayQueue;
|
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;
|
2021-12-13 16:41:30 +01:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
2021-11-30 17:49:41 +01:00
|
|
|
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
2020-12-02 21:28:36 +01:00
|
|
|
|
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;
|
2022-01-20 04:57:41 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
2020-12-13 23:01:01 +01:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2021-11-01 18:04:00 +01:00
|
|
|
import java.util.function.Consumer;
|
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,
|
2021-08-08 19:11:47 +02:00
|
|
|
* they are not necessary backed by a {@link PlayerSocketConnection} as shown by {@link FakePlayer}.
|
2020-12-13 23:01:01 +01:00
|
|
|
* <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
|
|
|
|
2021-10-15 15:54:11 +02:00
|
|
|
private static final Component REMOVE_MESSAGE = Component.text("You have been removed from the server without reason.", NamedTextColor.RED);
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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-11-01 18:04:00 +01:00
|
|
|
final IntegerBiConsumer chunkAdder = (chunkX, chunkZ) -> {
|
|
|
|
// Load new chunks
|
|
|
|
this.instance.loadOptionalChunk(chunkX, chunkZ).thenAccept(chunk -> {
|
|
|
|
try {
|
|
|
|
if (chunk != null) {
|
|
|
|
chunk.sendChunk(this);
|
|
|
|
GlobalHandles.PLAYER_CHUNK_LOAD.call(new PlayerChunkLoadEvent(this, chunkX, chunkZ));
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
MinecraftServer.getExceptionManager().handleException(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
final IntegerBiConsumer chunkRemover = (chunkX, chunkZ) -> {
|
|
|
|
// Unload old chunks
|
2021-11-19 06:06:32 +01:00
|
|
|
sendPacket(new UnloadChunkPacket(chunkX, chunkZ));
|
|
|
|
GlobalHandles.PLAYER_CHUNK_UNLOAD.call(new PlayerChunkUnloadEvent(this, chunkX, chunkZ));
|
2021-11-01 18:04:00 +01:00
|
|
|
};
|
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-11-30 17:49:41 +01:00
|
|
|
private final MessagePassingQueue<ClientPacket> packets = new MpscUnboundedXaddArrayQueue<>(32);
|
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;
|
|
|
|
|
2021-08-26 17:10:48 +02:00
|
|
|
private int permissionLevel;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
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-08-08 19:11:47 +02:00
|
|
|
* UNSAFE: Only meant to be used when a socket 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();
|
|
|
|
|
2021-12-13 16:49:54 +01:00
|
|
|
NBTCompound nbt = NBT.Compound(Map.of(
|
|
|
|
"minecraft:dimension_type", MinecraftServer.getDimensionTypeManager().toNBT(),
|
|
|
|
"minecraft:worldgen/biome", MinecraftServer.getBiomeManager().toNBT()));
|
2021-11-30 17:49:41 +01:00
|
|
|
final JoinGamePacket joinGamePacket = new JoinGamePacket(getEntityId(), gameMode.isHardcore(), gameMode, null,
|
|
|
|
List.of("minestom:world"), nbt, dimensionType.toNBT(), dimensionType.getName().asString(),
|
|
|
|
0, 0, MinecraftServer.getChunkViewDistance(), MinecraftServer.getChunkViewDistance(),
|
|
|
|
false, true, false, levelFlat);
|
2020-12-13 23:01:01 +01:00
|
|
|
playerConnection.sendPacket(joinGamePacket);
|
|
|
|
|
|
|
|
// Server brand name
|
2021-07-22 13:01:00 +02:00
|
|
|
playerConnection.sendPacket(PluginMessagePacket.getBrandPacket());
|
|
|
|
// Difficulty
|
|
|
|
playerConnection.sendPacket(new ServerDifficultyPacket(MinecraftServer.getDifficulty(), true));
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-07-22 13:01:00 +02:00
|
|
|
playerConnection.sendPacket(new SpawnPositionPacket(respawnPoint, 0));
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// 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
|
2021-08-11 14:18:04 +02:00
|
|
|
PacketUtils.broadcastPacket(getAddPlayerToList());
|
|
|
|
for (var player : MinecraftServer.getConnectionManager().getOnlinePlayers()) {
|
2021-12-27 12:33:39 +01:00
|
|
|
if (player != this) sendPacket(player.getAddPlayerToList());
|
2021-08-11 14:18:04 +02:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-08-12 03:53:49 +02:00
|
|
|
// Commands
|
|
|
|
refreshCommands();
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
// Recipes start
|
|
|
|
{
|
|
|
|
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
|
2021-11-30 17:49:41 +01:00
|
|
|
playerConnection.sendPacket(recipeManager.getDeclareRecipesPacket());
|
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()) {
|
2021-11-30 17:49:41 +01:00
|
|
|
UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket(0,
|
|
|
|
false, false,
|
|
|
|
false, false,
|
|
|
|
false, false,
|
|
|
|
false, false,
|
|
|
|
recipesIdentifier, recipesIdentifier);
|
2020-12-13 23:01:01 +01:00
|
|
|
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-10-21 21:08:51 +02:00
|
|
|
this.playerConnection.sendPacket(TagsPacket.DEFAULT_TAGS);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2021-07-22 13:01:00 +02:00
|
|
|
// Some client updates
|
2020-12-15 06:21:59 +01:00
|
|
|
this.playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
|
2022-01-29 14:56:45 +01:00
|
|
|
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
|
2020-12-13 23:01:01 +01:00
|
|
|
refreshHealth(); // Heal and send health packet
|
|
|
|
refreshAbilities(); // Send abilities packet
|
2021-08-15 00:52:07 +02:00
|
|
|
|
|
|
|
setInstance(spawnInstance);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to initialize the player connection
|
|
|
|
*/
|
|
|
|
protected void playerConnectionInit() {
|
2022-01-12 09:50:09 +01:00
|
|
|
PlayerConnection connection = playerConnection;
|
|
|
|
if (connection != null) connection.setPlayer(this);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void update(long time) {
|
|
|
|
// Network tick
|
|
|
|
this.playerConnection.update();
|
|
|
|
|
|
|
|
// Process received packets
|
2022-01-28 05:32:35 +01:00
|
|
|
interpretPacketQueue();
|
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);
|
2021-11-01 18:04:00 +01:00
|
|
|
this.instance.getEntityTracker().nearbyEntities(position, expandedBoundingBox.getWidth(),
|
|
|
|
EntityTracker.Target.EXPERIENCE_ORBS, experienceOrb -> {
|
|
|
|
final BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
|
|
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
|
|
|
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(this, experienceOrb);
|
|
|
|
EventDispatcher.callCancellable(pickupExperienceEvent, () -> {
|
|
|
|
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
|
|
|
experienceOrb.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-08-24 15:35:09 +02:00
|
|
|
GlobalHandles.PLAYER_TICK.call(new PlayerTickEvent(this));
|
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-11-30 17:49:41 +01:00
|
|
|
playerConnection.sendPacket(new DeathCombatEventPacket(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();
|
2021-11-30 17:49:41 +01:00
|
|
|
RespawnPacket respawnPacket = new RespawnPacket(getDimensionType(), getDimensionType().getName().asString(),
|
|
|
|
0, gameMode, gameMode, false, levelFlat, true);
|
2020-12-13 23:01:01 +01:00
|
|
|
getPlayerConnection().sendPacket(respawnPacket);
|
2022-01-29 14:56:45 +01:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this);
|
2021-06-04 03:48:51 +02:00
|
|
|
EventDispatcher.call(respawnEvent);
|
2021-08-02 13:02:29 +02:00
|
|
|
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
|
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
|
|
|
}
|
2021-08-15 00:52:07 +02:00
|
|
|
|
2022-01-29 14:56:45 +01:00
|
|
|
/**
|
|
|
|
* Sends necessary packets to synchronize player data after a {@link RespawnPacket}
|
|
|
|
*/
|
|
|
|
private void refreshClientStateAfterRespawn() {
|
|
|
|
this.playerConnection.sendPacket(new UpdateHealthPacket(this.getHealth(), food, foodSaturation));
|
|
|
|
this.playerConnection.sendPacket(new SetExperiencePacket(exp, level, 0));
|
|
|
|
triggerStatus((byte) (24 + permissionLevel)); // Set permission level
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
2021-08-12 03:53:49 +02:00
|
|
|
/**
|
|
|
|
* Refreshes the command list for this player. This checks the
|
|
|
|
* {@link net.minestom.server.command.builder.condition.CommandCondition}s
|
|
|
|
* again, and any changes will be visible to the player.
|
|
|
|
*/
|
|
|
|
public void refreshCommands() {
|
|
|
|
CommandManager commandManager = MinecraftServer.getCommandManager();
|
|
|
|
DeclareCommandsPacket declareCommandsPacket = commandManager.createDeclareCommandsPacket(this);
|
|
|
|
playerConnection.sendPacket(declareCommandsPacket);
|
|
|
|
}
|
2019-08-27 20:49:11 +02:00
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
@Override
|
|
|
|
public boolean isOnGround() {
|
|
|
|
return onGround;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void remove() {
|
2021-08-05 15:10:15 +02:00
|
|
|
if (isRemoved()) 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();
|
2021-10-15 15:54:11 +02:00
|
|
|
final Inventory currentInventory = getOpenInventory();
|
|
|
|
if (currentInventory != null) currentInventory.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
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
final Pos position = this.position;
|
|
|
|
final int chunkX = position.chunkX();
|
|
|
|
final int chunkZ = position.chunkZ();
|
2020-12-13 23:01:01 +01:00
|
|
|
// Clear all viewable chunks
|
2021-11-01 18:04:00 +01:00
|
|
|
ChunkUtils.forChunksInRange(chunkX, chunkZ, MinecraftServer.getChunkViewDistance(), chunkRemover);
|
2021-08-11 14:18:04 +02:00
|
|
|
// Remove from the tab-list
|
|
|
|
PacketUtils.broadcastPacket(getRemovePlayerToList());
|
2021-10-15 15:54:11 +02:00
|
|
|
|
|
|
|
// Prevent the player from being stuck in loading screen, or just unable to interact with the server
|
|
|
|
// This should be considered as a bug, since the player will ultimately time out anyway.
|
|
|
|
if (playerConnection.isOnline()) kick(REMOVE_MESSAGE);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-11-01 18:04:00 +01:00
|
|
|
public void updateOldViewer(@NotNull Player player) {
|
|
|
|
super.updateOldViewer(player);
|
2020-12-13 23:01:01 +01:00
|
|
|
// Team
|
2021-02-25 08:37:02 +01:00
|
|
|
if (this.getTeam() != null && this.getTeam().getMembers().size() == 1) {// If team only contains "this" player
|
2021-11-01 18:04:00 +01:00
|
|
|
player.sendPacket(this.getTeam().createTeamDestructionPacket());
|
2021-02-25 08:37:02 +01:00
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-25 18:07:05 +01:00
|
|
|
@Override
|
2021-11-17 06:31:24 +01:00
|
|
|
public void sendPacketToViewersAndSelf(@NotNull SendablePacket packet) {
|
2021-03-25 18:07:05 +01:00
|
|
|
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-28 16:36:21 +02:00
|
|
|
* @return a future called once the player instance changed
|
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) {
|
2021-08-15 00:52:07 +02:00
|
|
|
final Instance currentInstance = this.instance;
|
|
|
|
Check.argCondition(currentInstance == instance, "Instance should be different than the current one");
|
2021-11-01 18:04:00 +01:00
|
|
|
if (InstanceUtils.areLinked(currentInstance, instance) && spawnPosition.sameChunk(this.position)) {
|
2020-12-13 23:01:01 +01:00
|
|
|
// 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-12-27 17:58:53 +01:00
|
|
|
spawnPlayer(instance, spawnPosition, false, false, false);
|
2021-11-01 18:04:00 +01:00
|
|
|
return AsyncUtils.VOID_FUTURE;
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2021-11-01 18:04:00 +01:00
|
|
|
// Must update the player chunks
|
|
|
|
final boolean dimensionChange = !Objects.equals(dimensionType, instance.getDimensionType());
|
|
|
|
final Thread runThread = Thread.currentThread();
|
|
|
|
final Consumer<Instance> runnable = (i) -> spawnPlayer(i, spawnPosition,
|
|
|
|
currentInstance == null, dimensionChange, true);
|
|
|
|
// Wait for all surrounding chunks to load
|
|
|
|
List<CompletableFuture<Chunk>> futures = new ArrayList<>();
|
|
|
|
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(),
|
|
|
|
(chunkX, chunkZ) -> futures.add(instance.loadOptionalChunk(chunkX, chunkZ)));
|
2022-01-20 04:57:41 +01:00
|
|
|
|
|
|
|
SchedulerManager scheduler = MinecraftServer.getSchedulerManager();
|
|
|
|
AtomicBoolean join = new AtomicBoolean();
|
|
|
|
CompletableFuture<Void> future = new CompletableFuture<>() {
|
|
|
|
@Override
|
|
|
|
public Void join() {
|
|
|
|
// Prevent deadlock
|
|
|
|
scheduler.process();
|
|
|
|
join.set(true);
|
|
|
|
final Void result = super.join();
|
|
|
|
join.set(false);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
|
|
|
|
.thenRun(() -> {
|
2021-11-01 18:04:00 +01:00
|
|
|
if (runThread == Thread.currentThread()) {
|
|
|
|
runnable.accept(instance);
|
2022-01-20 04:57:41 +01:00
|
|
|
future.complete(null);
|
2021-11-01 18:04:00 +01:00
|
|
|
} else {
|
2022-01-20 04:57:41 +01:00
|
|
|
scheduler.scheduleNextProcess(() -> {
|
|
|
|
runnable.accept(instance);
|
2021-11-01 18:04:00 +01:00
|
|
|
future.complete(null);
|
|
|
|
});
|
2022-01-20 04:57:41 +01:00
|
|
|
if (join.compareAndSet(true, false))
|
|
|
|
scheduler.process();
|
2021-11-01 18:04:00 +01:00
|
|
|
}
|
|
|
|
});
|
2022-01-20 04:57:41 +01:00
|
|
|
return 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
|
|
|
*
|
2021-12-27 17:58:53 +01:00
|
|
|
* @param spawnPosition the position to teleport the player
|
|
|
|
* @param firstSpawn true if this is the player first spawn
|
|
|
|
* @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-11-01 18:04:00 +01:00
|
|
|
if (updateChunks)
|
|
|
|
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkRemover);
|
2021-08-04 16:58:33 +02:00
|
|
|
}
|
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
if (dimensionChange) sendDimension(instance.getDimensionType());
|
|
|
|
|
2021-09-13 09:05:16 +02:00
|
|
|
super.setInstance(instance, spawnPosition);
|
|
|
|
|
2021-05-10 00:51:35 +02:00
|
|
|
if (updateChunks) {
|
2021-11-01 18:04:00 +01:00
|
|
|
sendPacket(new UpdateViewPositionPacket(spawnPosition.chunkX(), spawnPosition.chunkZ()));
|
|
|
|
ChunkUtils.forChunksInRange(spawnPosition, MinecraftServer.getChunkViewDistance(), chunkAdder);
|
2021-03-07 23:18:32 +01:00
|
|
|
}
|
|
|
|
|
2021-09-13 09:05:16 +02:00
|
|
|
synchronizePosition(true); // So the player doesn't get stuck
|
|
|
|
|
2021-03-08 17:12:21 +01:00
|
|
|
if (dimensionChange || firstSpawn) {
|
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
|
|
|
|
2021-11-01 18:04:00 +01:00
|
|
|
EventDispatcher.call(new PlayerSpawnEvent(this, instance, firstSpawn));
|
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
|
|
|
|
*/
|
2021-07-22 13:01:00 +02:00
|
|
|
public void sendPluginMessage(@NotNull String channel, byte @NotNull [] data) {
|
|
|
|
playerConnection.sendPacket(new PluginMessagePacket(channel, data));
|
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 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) {
|
2021-07-22 13:01:00 +02:00
|
|
|
sendPluginMessage(channel, message.getBytes(StandardCharsets.UTF_8));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-08-15 13:38:57 +02:00
|
|
|
|
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) {
|
2021-11-30 17:49:41 +01:00
|
|
|
addPacketToQueue(new ClientChatMessagePacket(message));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
2020-10-16 11:37:00 +02: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) {
|
2021-11-30 17:49:41 +01:00
|
|
|
playerConnection.sendPacket(new EffectPacket(effect.getId(), new Vec(x, y, z), data, disableRelativeVolume));
|
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-07-27 06:55:08 +02:00
|
|
|
playerConnection.sendPacket(new PlayerListHeaderAndFooterPacket(header, footer));
|
2021-03-01 16:25:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-10-31 19:29:41 +01:00
|
|
|
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
|
|
|
|
playerConnection.sendPacket(AdventurePacketConvertor.createTitlePartPacket(part, value));
|
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
|
|
|
|
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-11-30 17:49:41 +01:00
|
|
|
playerConnection.sendPacket(new ClearTitlesPacket(false));
|
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-07-22 09:54:34 +02:00
|
|
|
final ItemStack writtenBook = ItemStack.builder(Material.WRITTEN_BOOK)
|
2021-04-10 18:55:26 +02:00
|
|
|
.meta(WrittenBookMeta.fromAdventure(book, this))
|
|
|
|
.build();
|
2021-03-02 16:56:20 +01:00
|
|
|
// Set book in offhand
|
2021-07-22 09:54:34 +02:00
|
|
|
playerConnection.sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, writtenBook));
|
2021-03-02 16:56:20 +01:00
|
|
|
// Open the book
|
2021-07-22 09:54:34 +02:00
|
|
|
playerConnection.sendPacket(new OpenBookPacket(Hand.OFF));
|
2021-03-02 16:56:20 +01:00
|
|
|
// Restore the item in offhand
|
2021-07-22 09:54:34 +02:00
|
|
|
playerConnection.sendPacket(new SetSlotPacket((byte) 0, 0, (short) PlayerInventoryUtils.OFFHAND_SLOT, getItemInOffHand()));
|
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);
|
2021-07-23 08:15:25 +02:00
|
|
|
this.playerConnection.sendPacket(new UpdateHealthPacket(health, food, foodSaturation));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
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;
|
2021-07-23 08:15:25 +02:00
|
|
|
this.playerConnection.sendPacket(new UpdateHealthPacket(getHealth(), food, foodSaturation));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2021-07-23 08:15:25 +02:00
|
|
|
this.playerConnection.sendPacket(new UpdateHealthPacket(getHealth(), food, foodSaturation));
|
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-07-27 06:55:08 +02:00
|
|
|
public @Nullable Component getDisplayName() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return displayName;
|
|
|
|
}
|
|
|
|
|
2021-03-03 20:27:33 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
2021-12-17 20:38:04 +01:00
|
|
|
sendPacketToViewersAndSelf(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME,
|
|
|
|
new PlayerInfoPacket.UpdateDisplayName(getUuid(), displayName)));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player skin.
|
|
|
|
*
|
|
|
|
* @return the player skin object,
|
|
|
|
* null means that the player has his {@link #getUuid()} default skin
|
|
|
|
*/
|
2021-07-27 06:55:08 +02:00
|
|
|
public @Nullable PlayerSkin getSkin() {
|
2020-12-13 23:01:01 +01:00
|
|
|
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();
|
|
|
|
|
2021-11-30 17:49:41 +01:00
|
|
|
RespawnPacket respawnPacket = new RespawnPacket(getDimensionType(), getDimensionType().getName().asString(),
|
|
|
|
0, gameMode, gameMode, false, levelFlat, true);
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
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);
|
2022-01-29 14:56:45 +01:00
|
|
|
refreshClientStateAfterRespawn();
|
2020-12-13 23:01:01 +01:00
|
|
|
|
|
|
|
{
|
|
|
|
// 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;
|
2021-12-17 20:38:04 +01:00
|
|
|
sendPacket(new ChangeGameStatePacket(ChangeGameStatePacket.Reason.ENABLE_RESPAWN_SCREEN, enableRespawnScreen ? 0 : 1));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
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() {
|
2021-12-17 20:53:00 +01:00
|
|
|
return Objects.requireNonNullElse(displayName, 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
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2021-12-17 20:53:00 +01:00
|
|
|
if (item.isAir()) return false;
|
2020-12-13 23:01:01 +01:00
|
|
|
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
|
|
|
|
*/
|
2021-07-25 06:30:49 +02:00
|
|
|
public void facePosition(@NotNull FacePoint facePoint, @NotNull Point targetPosition) {
|
2020-12-13 23:01:01 +01:00
|
|
|
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) {
|
2021-11-30 17:49:41 +01:00
|
|
|
final int entityId = entity != null ? entity.getEntityId() : 0;
|
|
|
|
playerConnection.sendPacket(new FacePlayerPacket(
|
|
|
|
facePoint == FacePoint.EYE ?
|
|
|
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET, targetPosition,
|
|
|
|
entityId,
|
|
|
|
targetPoint == FacePoint.EYE ?
|
|
|
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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-07-27 11:56:20 +02:00
|
|
|
sendPacketsToViewers(getEntityType().registry().spawnType().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
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
2021-07-23 08:15:25 +02:00
|
|
|
this.playerConnection.sendPacket(new SetExperiencePacket(exp, level, 0));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the level of the player displayed in the experience bar.
|
|
|
|
*
|
|
|
|
* @return the player level
|
|
|
|
*/
|
|
|
|
public int getLevel() {
|
|
|
|
return level;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to change the level of the player
|
|
|
|
* This cannot change the displayed percentage bar see {@link #setExp(float)}
|
|
|
|
*
|
|
|
|
* @param level the new level of the player
|
|
|
|
*/
|
|
|
|
public void setLevel(int level) {
|
|
|
|
this.level = level;
|
2021-07-23 08:15:25 +02:00
|
|
|
this.playerConnection.sendPacket(new SetExperiencePacket(exp, level, 0));
|
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
|
|
|
|
*/
|
2021-09-10 05:41:54 +02:00
|
|
|
public @NotNull PlayerConnection getPlayerConnection() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return playerConnection;
|
|
|
|
}
|
|
|
|
|
2021-09-10 05:41:54 +02:00
|
|
|
/**
|
2021-11-17 06:31:24 +01:00
|
|
|
* Shortcut for {@link PlayerConnection#sendPacket(SendablePacket)}.
|
2021-09-10 05:41:54 +02:00
|
|
|
*
|
|
|
|
* @param packet the packet to send
|
|
|
|
*/
|
|
|
|
@ApiStatus.Experimental
|
2021-11-17 06:31:24 +01:00
|
|
|
public void sendPacket(@NotNull SendablePacket packet) {
|
2021-09-10 05:41:54 +02:00
|
|
|
this.playerConnection.sendPacket(packet);
|
|
|
|
}
|
|
|
|
|
2021-10-13 06:33:44 +02:00
|
|
|
@ApiStatus.Experimental
|
2021-11-17 06:31:24 +01:00
|
|
|
public void sendPackets(@NotNull SendablePacket... packets) {
|
|
|
|
this.playerConnection.sendPackets(packets);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public void sendPackets(@NotNull Collection<SendablePacket> packets) {
|
|
|
|
this.playerConnection.sendPackets(packets);
|
2021-10-13 06:33:44 +02:00
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull PlayerSettings getSettings() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return settings;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player dimension.
|
|
|
|
*
|
|
|
|
* @return the player current dimension
|
|
|
|
*/
|
|
|
|
public DimensionType getDimensionType() {
|
|
|
|
return dimensionType;
|
|
|
|
}
|
|
|
|
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull PlayerInventory getInventory() {
|
2020-12-13 23:01:01 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-01-29 14:56:45 +01:00
|
|
|
* Changes the player {@link GameMode}
|
2020-12-13 23:01:01 +01:00
|
|
|
*
|
|
|
|
* @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()) {
|
2021-12-17 20:38:04 +01:00
|
|
|
sendPacket(new ChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId()));
|
|
|
|
sendPacketToViewersAndSelf(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE,
|
|
|
|
new PlayerInfoPacket.UpdateGameMode(getUuid(), gameMode)));
|
2020-12-18 00:57:23 +01:00
|
|
|
}
|
2022-01-29 14:56:45 +01:00
|
|
|
|
|
|
|
// The client updates their abilities based on the GameMode as follows
|
|
|
|
switch (gameMode) {
|
|
|
|
case CREATIVE -> {
|
|
|
|
this.allowFlying = true;
|
|
|
|
this.instantBreak = true;
|
|
|
|
this.invulnerable = true;
|
|
|
|
}
|
|
|
|
case SPECTATOR -> {
|
|
|
|
this.allowFlying = true;
|
|
|
|
this.instantBreak = false;
|
|
|
|
this.invulnerable = true;
|
|
|
|
this.flying = true;
|
|
|
|
}
|
|
|
|
default -> {
|
|
|
|
this.allowFlying = false;
|
|
|
|
this.instantBreak = false;
|
|
|
|
this.invulnerable = false;
|
|
|
|
this.flying = false;
|
|
|
|
}
|
|
|
|
}
|
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;
|
2021-11-30 17:49:41 +01:00
|
|
|
sendPacket(new RespawnPacket(dimensionType, dimensionType.getName().asString(),
|
|
|
|
0, gameMode, gameMode, false, levelFlat, true));
|
2022-01-29 14:56:45 +01:00
|
|
|
refreshClientStateAfterRespawn();
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-01 16:25:23 +01:00
|
|
|
/**
|
|
|
|
* 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-08-08 19:11:47 +02:00
|
|
|
if (playerConnection instanceof PlayerSocketConnection) {
|
|
|
|
((PlayerSocketConnection) playerConnection).writeAndFlush(disconnectPacket);
|
2021-03-14 21:34:38 +01:00
|
|
|
playerConnection.disconnect();
|
|
|
|
} else {
|
|
|
|
playerConnection.sendPacket(disconnectPacket);
|
|
|
|
playerConnection.refreshOnline(false);
|
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2021-07-27 06:55:08 +02:00
|
|
|
/**
|
|
|
|
* Kicks the player with a reason.
|
|
|
|
*
|
|
|
|
* @param message the kick reason
|
|
|
|
*/
|
|
|
|
public void kick(@NotNull String message) {
|
|
|
|
this.kick(Component.text(message));
|
|
|
|
}
|
|
|
|
|
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");
|
|
|
|
refreshHeldSlot(slot);
|
2021-07-23 08:15:25 +02:00
|
|
|
this.playerConnection.sendPacket(new HeldItemChangePacket(slot));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
*/
|
2021-09-10 05:50:08 +02:00
|
|
|
public @Nullable Inventory getOpenInventory() {
|
2020-12-13 23:01:01 +01:00
|
|
|
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-11-30 17:49:41 +01:00
|
|
|
playerConnection.sendPacket(new OpenWindowPacket(newInventory.getWindowId(),
|
|
|
|
newInventory.getInventoryType().getWindowType(), newInventory.getTitle()));
|
2020-12-13 23:01:01 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-30 17:49:41 +01:00
|
|
|
CloseWindowPacket closeWindowPacket;
|
2020-12-13 23:01:01 +01:00
|
|
|
if (openInventory == null) {
|
2021-11-30 17:49:41 +01:00
|
|
|
closeWindowPacket = new CloseWindowPacket((byte) 0);
|
2020-12-13 23:01:01 +01:00
|
|
|
} else {
|
2021-11-30 17:49:41 +01:00
|
|
|
closeWindowPacket = new CloseWindowPacket(openInventory.getWindowId());
|
2020-12-13 23:01:01 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-28 11:28:14 +02:00
|
|
|
public int getNextTeleportId() {
|
|
|
|
return teleportId.incrementAndGet();
|
|
|
|
}
|
|
|
|
|
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-08-28 11:28:14 +02:00
|
|
|
playerConnection.sendPacket(new PlayerPositionAndLookPacket(position, (byte) 0x00, getNextTeleportId(), 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");
|
|
|
|
|
2021-08-26 17:10:48 +02:00
|
|
|
this.permissionLevel = permissionLevel;
|
2020-12-13 23:01:01 +01:00
|
|
|
|
2022-01-29 14:56:45 +01:00
|
|
|
// Condition to prevent sending the packets before spawning the player
|
|
|
|
if (isActive()) {
|
|
|
|
// Magic values: https://wiki.vg/Entity_statuses#Player
|
|
|
|
// TODO remove magic values
|
|
|
|
final byte permissionLevelStatus = (byte) (24 + permissionLevel);
|
|
|
|
triggerStatus(permissionLevelStatus);
|
|
|
|
}
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
|
2021-09-14 11:37:57 +02:00
|
|
|
@Override
|
|
|
|
public void setSneaking(boolean sneaking) {
|
2021-09-20 19:34:43 +02:00
|
|
|
if (isFlying()) { //If we are flying, don't set the players pose to sneaking as this can clip them through blocks
|
2021-09-14 11:37:57 +02:00
|
|
|
this.entityMeta.setSneaking(sneaking);
|
|
|
|
} else {
|
|
|
|
super.setSneaking(sneaking);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* 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) {
|
2021-09-14 11:50:19 +02:00
|
|
|
refreshFlying(flying);
|
2020-12-13 23:01:01 +01:00
|
|
|
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) {
|
2021-09-14 11:50:19 +02:00
|
|
|
//When the player starts or stops flying, their pose needs to change
|
2021-09-20 19:34:43 +02:00
|
|
|
if (this.flying != flying) {
|
2021-09-14 11:50:19 +02:00
|
|
|
Pose pose = getPose();
|
|
|
|
|
2021-09-20 19:34:43 +02:00
|
|
|
if (this.isSneaking() && pose == Pose.STANDING) {
|
2021-09-14 11:50:19 +02:00
|
|
|
setPose(Pose.SNEAKING);
|
2021-09-20 19:34:43 +02:00
|
|
|
} else if (pose == Pose.SNEAKING) {
|
2021-09-14 11:50:19 +02:00
|
|
|
setPose(Pose.STANDING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
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
|
|
|
|
*/
|
2021-07-27 07:44:06 +02:00
|
|
|
public @NotNull Map<PlayerStatistic, Integer> getStatisticValueMap() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return statisticValueMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the player vehicle information.
|
|
|
|
*
|
|
|
|
* @return the player vehicle information
|
|
|
|
*/
|
2021-07-27 07:44:06 +02:00
|
|
|
public @NotNull PlayerVehicleInformation getVehicleInformation() {
|
2020-12-13 23:01:01 +01:00
|
|
|
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() {
|
2021-07-27 07:44:06 +02:00
|
|
|
byte flags = 0;
|
|
|
|
if (invulnerable)
|
2021-07-27 12:08:13 +02:00
|
|
|
flags |= PlayerAbilitiesPacket.FLAG_INVULNERABLE;
|
2021-07-27 07:44:06 +02:00
|
|
|
if (flying)
|
2021-07-27 12:08:13 +02:00
|
|
|
flags |= PlayerAbilitiesPacket.FLAG_FLYING;
|
2021-07-27 07:44:06 +02:00
|
|
|
if (allowFlying)
|
2021-07-27 12:08:13 +02:00
|
|
|
flags |= PlayerAbilitiesPacket.FLAG_ALLOW_FLYING;
|
2021-07-27 07:44:06 +02:00
|
|
|
if (instantBreak)
|
2021-07-27 12:08:13 +02:00
|
|
|
flags |= PlayerAbilitiesPacket.FLAG_INSTANT_BREAK;
|
2021-07-27 07:44:06 +02:00
|
|
|
playerConnection.sendPacket(new PlayerAbilitiesPacket(flags, flyingSpeed, fieldViewModifier));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-11-30 17:49:41 +01:00
|
|
|
public void addPacketToQueue(@NotNull ClientPacket packet) {
|
2021-11-20 11:56:35 +01:00
|
|
|
this.packets.offer(packet);
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
2022-01-28 05:32:35 +01:00
|
|
|
@ApiStatus.Internal
|
|
|
|
@ApiStatus.Experimental
|
|
|
|
public void interpretPacketQueue() {
|
|
|
|
// This method is NOT thread-safe
|
|
|
|
this.packets.drain(packet -> MinecraftServer.getPacketListenerManager().processClientPacket(packet, this));
|
|
|
|
}
|
|
|
|
|
2020-12-13 23:01:01 +01:00
|
|
|
/**
|
|
|
|
* Changes the storage player latency and update its tab value.
|
|
|
|
*
|
|
|
|
* @param latency the new player latency
|
|
|
|
*/
|
|
|
|
public void refreshLatency(int latency) {
|
|
|
|
this.latency = latency;
|
2021-12-17 20:38:04 +01:00
|
|
|
sendPacketToViewersAndSelf(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY,
|
|
|
|
new PlayerInfoPacket.UpdateLatency(getUuid(), latency)));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2021-09-10 05:50:08 +02:00
|
|
|
protected @NotNull PlayerInfoPacket getAddPlayerToList() {
|
2021-11-30 17:49:41 +01:00
|
|
|
final PlayerSkin skin = this.skin;
|
|
|
|
List<PlayerInfoPacket.AddPlayer.Property> prop = skin != null ?
|
|
|
|
List.of(new PlayerInfoPacket.AddPlayer.Property("textures", skin.textures(), skin.signature())) :
|
|
|
|
Collections.emptyList();
|
2021-12-17 20:38:04 +01:00
|
|
|
return new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER,
|
|
|
|
new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), prop, getGameMode(), getLatency(), displayName));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the packet to remove the player from the tab-list.
|
|
|
|
*
|
|
|
|
* @return a {@link PlayerInfoPacket} to remove the player
|
|
|
|
*/
|
2021-09-10 05:50:08 +02:00
|
|
|
protected @NotNull PlayerInfoPacket getRemovePlayerToList() {
|
2021-12-17 20:38:04 +01:00
|
|
|
return new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER, new PlayerInfoPacket.RemovePlayer(getUuid()));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends all the related packet to have the player sent to another with related data
|
|
|
|
* (create player, spawn position, velocity, metadata, equipments, passengers, team).
|
|
|
|
* <p>
|
|
|
|
* 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-07-27 11:56:20 +02:00
|
|
|
connection.sendPacket(getEntityType().registry().spawnType().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
|
2021-07-23 08:15:25 +02:00
|
|
|
if (this.getTeam() != null) {
|
2020-12-13 23:01:01 +01:00
|
|
|
connection.sendPacket(this.getTeam().createTeamsCreationPacket());
|
2021-07-23 08:15:25 +02:00
|
|
|
}
|
|
|
|
connection.sendPacket(new EntityHeadLookPacket(getEntityId(), position.yaw()));
|
2020-12-13 23:01:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull ItemStack getItemInMainHand() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return inventory.getItemInMainHand();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInMainHand(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setItemInMainHand(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull ItemStack getItemInOffHand() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return inventory.getItemInOffHand();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInOffHand(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setItemInOffHand(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull ItemStack getHelmet() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return inventory.getHelmet();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setHelmet(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setHelmet(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull ItemStack getChestplate() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return inventory.getChestplate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setChestplate(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setChestplate(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull ItemStack getLeggings() {
|
2020-12-13 23:01:01 +01:00
|
|
|
return inventory.getLeggings();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setLeggings(@NotNull ItemStack itemStack) {
|
|
|
|
inventory.setLeggings(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 05:50:08 +02:00
|
|
|
public @NotNull ItemStack getBoots() {
|
2020-12-13 23:01:01 +01:00
|
|
|
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() {
|
2021-09-08 00:54:00 +02:00
|
|
|
final String locale = settings.locale;
|
|
|
|
if (locale == null) return null;
|
|
|
|
return Locale.forLanguageTag(locale.replace("_", "-"));
|
2021-03-03 17:32:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-05-05 19:21:38 +02:00
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:25:24 +02:00
|
|
|
}
|