2020-04-24 03:25:58 +02:00
|
|
|
package net.minestom.server.entity;
|
2019-08-03 15:25:24 +02:00
|
|
|
|
2020-05-03 15:00:53 +02:00
|
|
|
import net.kyori.text.Component;
|
2020-05-03 13:59:53 +02:00
|
|
|
import net.kyori.text.TextComponent;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.MinecraftServer;
|
|
|
|
import net.minestom.server.bossbar.BossBar;
|
|
|
|
import net.minestom.server.chat.Chat;
|
|
|
|
import net.minestom.server.collision.BoundingBox;
|
2020-05-25 02:37:57 +02:00
|
|
|
import net.minestom.server.command.CommandManager;
|
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-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.entity.property.Attribute;
|
|
|
|
import net.minestom.server.entity.vehicle.PlayerVehicleInformation;
|
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-05-12 14:12:17 +02:00
|
|
|
import net.minestom.server.event.player.*;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.instance.Chunk;
|
|
|
|
import net.minestom.server.instance.Instance;
|
|
|
|
import net.minestom.server.instance.block.CustomBlock;
|
|
|
|
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;
|
2020-05-26 22:53:58 +02:00
|
|
|
import net.minestom.server.network.packet.PacketWriter;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
|
|
|
import net.minestom.server.network.packet.server.ServerPacket;
|
2020-05-25 02:37:57 +02:00
|
|
|
import net.minestom.server.network.packet.server.login.JoinGamePacket;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.*;
|
|
|
|
import net.minestom.server.network.player.PlayerConnection;
|
2020-05-25 02:37:57 +02:00
|
|
|
import net.minestom.server.recipe.Recipe;
|
|
|
|
import net.minestom.server.recipe.RecipeManager;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.scoreboard.BelowNameScoreboard;
|
|
|
|
import net.minestom.server.scoreboard.Team;
|
|
|
|
import net.minestom.server.sound.Sound;
|
|
|
|
import net.minestom.server.sound.SoundCategory;
|
|
|
|
import net.minestom.server.stat.PlayerStatistic;
|
2020-05-25 13:46:48 +02:00
|
|
|
import net.minestom.server.utils.ArrayUtils;
|
|
|
|
import net.minestom.server.utils.BlockPosition;
|
|
|
|
import net.minestom.server.utils.MathUtils;
|
|
|
|
import net.minestom.server.utils.Position;
|
|
|
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
2020-05-23 04:20:01 +02:00
|
|
|
import net.minestom.server.utils.validate.Check;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.world.Dimension;
|
|
|
|
import net.minestom.server.world.LevelType;
|
2019-08-03 15:25:24 +02:00
|
|
|
|
2020-04-17 15:58:07 +02:00
|
|
|
import java.util.*;
|
2019-08-23 15:37:38 +02:00
|
|
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
2019-08-19 17:04:19 +02:00
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
2019-09-01 06:18:41 +02:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
import java.util.function.Consumer;
|
2019-08-11 03:40:34 +02:00
|
|
|
|
2019-08-10 08:44:35 +02:00
|
|
|
public class Player extends LivingEntity {
|
|
|
|
|
2019-08-10 04:47:19 +02:00
|
|
|
private long lastKeepAlive;
|
2019-08-10 04:16:01 +02:00
|
|
|
|
2019-08-11 03:40:34 +02:00
|
|
|
private String username;
|
2020-05-25 02:37:57 +02:00
|
|
|
protected PlayerConnection playerConnection;
|
2019-08-23 15:37:38 +02:00
|
|
|
private ConcurrentLinkedQueue<ClientPlayPacket> packets = new ConcurrentLinkedQueue<>();
|
2019-08-03 15:25:24 +02:00
|
|
|
|
2020-04-26 06:34:08 +02:00
|
|
|
private int latency;
|
2020-05-26 15:35:48 +02:00
|
|
|
private String displayName;
|
2020-04-26 06:34:08 +02:00
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
private Dimension dimension;
|
2019-08-12 08:30:59 +02:00
|
|
|
private GameMode gameMode;
|
2019-08-21 16:50:52 +02:00
|
|
|
private LevelType levelType;
|
2020-05-08 18:42:04 +02:00
|
|
|
private int teleportId = 0;
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
protected boolean onGround;
|
|
|
|
|
2019-09-01 06:18:41 +02:00
|
|
|
protected Set<Entity> viewableEntities = new CopyOnWriteArraySet<>();
|
|
|
|
protected Set<Chunk> viewableChunks = new CopyOnWriteArraySet<>();
|
2019-08-25 20:03:43 +02:00
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
private PlayerSettings settings;
|
2019-08-31 07:54:53 +02:00
|
|
|
private float exp;
|
|
|
|
private int level;
|
2019-08-12 08:30:59 +02:00
|
|
|
private PlayerInventory inventory;
|
|
|
|
private short heldSlot;
|
2019-08-13 17:52:09 +02:00
|
|
|
private Inventory openInventory;
|
2019-08-26 00:29:40 +02:00
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
private Position respawnPoint;
|
|
|
|
|
2020-05-26 22:53:58 +02:00
|
|
|
private float additionalHearts;
|
2019-08-25 20:03:43 +02:00
|
|
|
private int food;
|
2019-08-26 00:29:40 +02:00
|
|
|
private float foodSaturation;
|
2020-05-12 14:12:17 +02:00
|
|
|
private long startEatingTime;
|
2020-05-12 18:40:04 +02:00
|
|
|
private long defaultEatingTime = 1000L;
|
|
|
|
private long eatingTime;
|
2020-05-12 14:12:17 +02:00
|
|
|
private boolean isEating;
|
2019-08-12 08:30:59 +02:00
|
|
|
|
2020-05-25 19:54:36 +02:00
|
|
|
// Game state (https://wiki.vg/Protocol#Change_Game_State)
|
|
|
|
private boolean enableRespawnScreen;
|
|
|
|
|
2020-05-12 14:12:17 +02:00
|
|
|
// CustomBlock break delay
|
2019-08-18 23:52:11 +02:00
|
|
|
private CustomBlock targetCustomBlock;
|
2019-08-21 16:50:52 +02:00
|
|
|
private BlockPosition targetBlockPosition;
|
2019-08-18 20:38:09 +02:00
|
|
|
private long targetBlockTime;
|
2019-08-22 14:52:32 +02:00
|
|
|
private byte targetLastStage;
|
2020-05-12 14:12:17 +02:00
|
|
|
private int blockBreakTime;
|
2019-08-18 20:38:09 +02:00
|
|
|
|
2019-08-19 17:04:19 +02:00
|
|
|
private Set<BossBar> bossBars = new CopyOnWriteArraySet<>();
|
2019-09-21 20:42:27 +02:00
|
|
|
private Team team;
|
|
|
|
private BelowNameScoreboard belowNameScoreboard;
|
2019-08-19 17:04:19 +02:00
|
|
|
|
2020-04-27 21:12:42 +02:00
|
|
|
/**
|
|
|
|
* Last damage source to hit this player, used to display the death message.
|
|
|
|
*/
|
|
|
|
private DamageType lastDamageSource;
|
|
|
|
|
2020-05-13 18:43:54 +02:00
|
|
|
private int permissionLevel;
|
|
|
|
|
|
|
|
private boolean reducedDebugScreenInformation;
|
|
|
|
|
2019-09-23 19:56:08 +02:00
|
|
|
// Abilities
|
|
|
|
private boolean invulnerable;
|
|
|
|
private boolean flying;
|
|
|
|
private boolean allowFlying;
|
|
|
|
private boolean instantBreak;
|
|
|
|
private float flyingSpeed = 0.05f;
|
|
|
|
private float fieldViewModifier = 0.1f;
|
|
|
|
|
2020-04-17 15:58:07 +02:00
|
|
|
// Statistics
|
2020-04-23 12:30:49 +02:00
|
|
|
private Map<PlayerStatistic, Integer> statisticValueMap = new Hashtable<>();
|
2020-04-17 15:58:07 +02:00
|
|
|
|
2019-08-20 17:41:07 +02:00
|
|
|
// Vehicle
|
2020-04-23 12:30:49 +02:00
|
|
|
private PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
|
2019-08-20 17:41:07 +02:00
|
|
|
|
2020-05-25 03:39:57 +02:00
|
|
|
// Tick related
|
2020-05-27 01:04:39 +02:00
|
|
|
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
|
2020-05-25 03:39:57 +02:00
|
|
|
|
2019-08-11 03:40:34 +02:00
|
|
|
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
|
2020-05-25 01:12:12 +02:00
|
|
|
super(EntityType.PLAYER);
|
2020-05-26 22:53:58 +02:00
|
|
|
this.uuid = uuid; // Override Entity#uuid defined in the constructor
|
2019-08-11 03:40:34 +02:00
|
|
|
this.username = username;
|
2019-08-10 04:47:19 +02:00
|
|
|
this.playerConnection = playerConnection;
|
2019-08-12 08:30:59 +02:00
|
|
|
|
2020-04-22 13:49:52 +02:00
|
|
|
setBoundingBox(0.69f, 1.8f, 0.69f);
|
2019-08-30 01:17:46 +02:00
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
setRespawnPoint(new Position(0, 0, 0));
|
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
this.settings = new PlayerSettings();
|
2019-08-12 08:30:59 +02:00
|
|
|
this.inventory = new PlayerInventory(this);
|
2019-08-20 17:41:07 +02:00
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
setCanPickupItem(true); // By default
|
2020-05-25 02:37:57 +02:00
|
|
|
|
2020-05-26 16:14:52 +02:00
|
|
|
this.gameMode = GameMode.SURVIVAL;
|
|
|
|
this.dimension = Dimension.OVERWORLD;
|
|
|
|
this.levelType = LevelType.DEFAULT;
|
|
|
|
refreshPosition(0, 0, 0);
|
|
|
|
|
|
|
|
// FakePlayer init its connection there
|
|
|
|
playerConnectionInit();
|
|
|
|
|
2020-05-25 02:37:57 +02:00
|
|
|
MinecraftServer.getEntityManager().addWaitingPlayer(this);
|
|
|
|
}
|
|
|
|
|
2020-05-26 15:35:48 +02:00
|
|
|
/**
|
2020-05-26 16:14:52 +02:00
|
|
|
* Used when the player is created ({@link EntityManager#waitingPlayersTick()})
|
2020-05-26 15:35:48 +02:00
|
|
|
* Init the player and spawn him
|
|
|
|
*/
|
2020-05-25 02:37:57 +02:00
|
|
|
protected void init() {
|
|
|
|
|
|
|
|
// TODO complete login sequence with optionals packets
|
|
|
|
JoinGamePacket joinGamePacket = new JoinGamePacket();
|
|
|
|
joinGamePacket.entityId = getEntityId();
|
|
|
|
joinGamePacket.gameMode = gameMode;
|
|
|
|
joinGamePacket.dimension = dimension;
|
|
|
|
joinGamePacket.maxPlayers = 0; // Unused
|
|
|
|
joinGamePacket.levelType = levelType;
|
|
|
|
joinGamePacket.viewDistance = MinecraftServer.CHUNK_VIEW_DISTANCE;
|
|
|
|
joinGamePacket.reducedDebugInfo = false;
|
|
|
|
playerConnection.sendPacket(joinGamePacket);
|
|
|
|
|
|
|
|
// TODO minecraft:brand plugin message
|
|
|
|
|
|
|
|
ServerDifficultyPacket serverDifficultyPacket = new ServerDifficultyPacket();
|
|
|
|
serverDifficultyPacket.difficulty = MinecraftServer.getDifficulty();
|
|
|
|
serverDifficultyPacket.locked = true;
|
|
|
|
playerConnection.sendPacket(serverDifficultyPacket);
|
|
|
|
|
|
|
|
SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket();
|
|
|
|
spawnPositionPacket.x = 0;
|
|
|
|
spawnPositionPacket.y = 0;
|
|
|
|
spawnPositionPacket.z = 0;
|
|
|
|
playerConnection.sendPacket(spawnPositionPacket);
|
|
|
|
|
2020-05-25 03:17:24 +02:00
|
|
|
// Add player to list
|
2020-05-26 15:35:48 +02:00
|
|
|
String jsonDisplayName = displayName != null ? Chat.toJsonString(Chat.fromLegacyText(displayName)) : null;
|
|
|
|
String property = "";
|
2020-05-25 02:37:57 +02:00
|
|
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
|
2020-05-26 15:35:48 +02:00
|
|
|
PlayerInfoPacket.AddPlayer addPlayer = new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), getLatency());
|
|
|
|
addPlayer.displayName = jsonDisplayName;
|
2020-05-26 20:00:41 +02:00
|
|
|
PlayerInfoPacket.AddPlayer.Property prop = new PlayerInfoPacket.AddPlayer.Property("textures", property);
|
|
|
|
addPlayer.properties.add(prop);
|
2020-05-25 02:37:57 +02:00
|
|
|
playerInfoPacket.playerInfos.add(addPlayer);
|
|
|
|
playerConnection.sendPacket(playerInfoPacket);
|
|
|
|
|
2020-05-25 03:17:24 +02:00
|
|
|
// Init player
|
2020-05-25 02:37:57 +02:00
|
|
|
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
|
|
|
|
playerInitialization.accept(this);
|
|
|
|
}
|
|
|
|
|
2020-05-25 03:17:24 +02:00
|
|
|
// Commands start
|
2020-05-25 02:37:57 +02:00
|
|
|
{
|
|
|
|
CommandManager commandManager = MinecraftServer.getCommandManager();
|
|
|
|
DeclareCommandsPacket declareCommandsPacket = commandManager.createDeclareCommandsPacket(this);
|
|
|
|
|
|
|
|
playerConnection.sendPacket(declareCommandsPacket);
|
|
|
|
}
|
2020-05-25 03:17:24 +02:00
|
|
|
// Commands end
|
2020-05-25 02:37:57 +02:00
|
|
|
|
|
|
|
|
2020-05-25 03:17:24 +02:00
|
|
|
// Recipes start
|
2020-05-25 02:37:57 +02:00
|
|
|
{
|
|
|
|
RecipeManager recipeManager = MinecraftServer.getRecipeManager();
|
|
|
|
DeclareRecipesPacket declareRecipesPacket = recipeManager.getDeclareRecipesPacket();
|
|
|
|
if (declareRecipesPacket.recipes != null) {
|
|
|
|
playerConnection.sendPacket(declareRecipesPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<String> recipesIdentifier = new ArrayList<>();
|
|
|
|
for (Recipe recipe : recipeManager.getRecipes()) {
|
|
|
|
if (!recipe.shouldShow(this))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
recipesIdentifier.add(recipe.getRecipeId());
|
|
|
|
}
|
|
|
|
if (!recipesIdentifier.isEmpty()) {
|
2020-05-25 12:25:39 +02:00
|
|
|
String[] identifiers = recipesIdentifier.toArray(new String[0]);
|
2020-05-25 02:37:57 +02:00
|
|
|
UnlockRecipesPacket unlockRecipesPacket = new UnlockRecipesPacket();
|
|
|
|
unlockRecipesPacket.mode = 0;
|
|
|
|
unlockRecipesPacket.recipesId = identifiers;
|
|
|
|
unlockRecipesPacket.initRecipesId = identifiers;
|
|
|
|
playerConnection.sendPacket(unlockRecipesPacket);
|
|
|
|
}
|
|
|
|
}
|
2020-05-25 03:17:24 +02:00
|
|
|
// Recipes end
|
2020-05-26 16:14:52 +02:00
|
|
|
|
|
|
|
// Some client update
|
|
|
|
playerConnection.sendPacket(getPropertiesPacket()); // Send default properties
|
2020-05-26 20:00:41 +02:00
|
|
|
refreshHealth(); // Heal and send health packet
|
|
|
|
refreshAbilities(); // Send abilities packet
|
2020-05-25 03:17:24 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 13:46:48 +02:00
|
|
|
/**
|
|
|
|
* Used to initialize the player connection
|
|
|
|
* mostly used by {@link net.minestom.server.entity.fakeplayer.FakePlayer}
|
|
|
|
*/
|
2020-05-25 03:17:24 +02:00
|
|
|
protected void playerConnectionInit() {
|
2019-08-10 04:47:19 +02:00
|
|
|
}
|
|
|
|
|
2020-04-27 21:12:42 +02:00
|
|
|
@Override
|
2020-04-27 23:03:21 +02:00
|
|
|
public boolean damage(DamageType type, float value) {
|
2020-05-27 19:43:08 +02:00
|
|
|
if (isInvulnerable())
|
|
|
|
return false;
|
|
|
|
|
2020-05-26 23:15:09 +02:00
|
|
|
// Compute final heart based on health and additional hearts
|
2020-04-27 23:03:21 +02:00
|
|
|
boolean result = super.damage(type, value);
|
|
|
|
if (result) {
|
2020-04-27 21:12:42 +02:00
|
|
|
lastDamageSource = type;
|
|
|
|
}
|
2020-04-27 23:03:21 +02:00
|
|
|
return result;
|
2020-04-27 21:12:42 +02:00
|
|
|
}
|
|
|
|
|
2019-08-10 08:44:35 +02:00
|
|
|
@Override
|
|
|
|
public void update() {
|
2019-09-07 11:42:33 +02:00
|
|
|
// Flush all pending packets
|
2019-08-24 20:34:01 +02:00
|
|
|
playerConnection.flush();
|
|
|
|
|
2020-02-09 15:34:09 +01:00
|
|
|
// Process received packets
|
2019-08-24 20:34:01 +02:00
|
|
|
ClientPlayPacket packet;
|
2019-08-23 15:37:38 +02:00
|
|
|
while ((packet = packets.poll()) != null) {
|
|
|
|
packet.process(this);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
2019-08-18 20:38:09 +02:00
|
|
|
|
2020-05-26 20:00:41 +02:00
|
|
|
super.update(); // Super update (item pickup/fire management)
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2019-08-18 20:38:09 +02:00
|
|
|
// Target block stage
|
2019-08-23 23:55:09 +02:00
|
|
|
if (targetCustomBlock != null) {
|
2020-05-26 20:00:41 +02:00
|
|
|
final int animationCount = 10;
|
2019-08-18 20:38:09 +02:00
|
|
|
long since = System.currentTimeMillis() - targetBlockTime;
|
2020-05-23 04:20:01 +02:00
|
|
|
byte stage = (byte) (since / (blockBreakTime / animationCount) - 1);
|
2019-08-22 14:52:32 +02:00
|
|
|
if (stage != targetLastStage) {
|
|
|
|
sendBlockBreakAnimation(targetBlockPosition, stage);
|
|
|
|
}
|
|
|
|
this.targetLastStage = stage;
|
2019-08-18 20:38:09 +02:00
|
|
|
if (stage > 9) {
|
2019-08-25 20:03:43 +02:00
|
|
|
instance.breakBlock(this, targetBlockPosition);
|
2019-08-18 23:52:11 +02:00
|
|
|
resetTargetBlock();
|
2019-08-18 20:38:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-31 07:54:53 +02:00
|
|
|
// Experience orb pickup
|
|
|
|
Chunk chunk = instance.getChunkAt(getPosition()); // TODO check surrounding chunks
|
|
|
|
Set<Entity> entities = instance.getChunkEntities(chunk);
|
|
|
|
for (Entity entity : entities) {
|
|
|
|
if (entity instanceof ExperienceOrb) {
|
|
|
|
ExperienceOrb experienceOrb = (ExperienceOrb) entity;
|
|
|
|
BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
|
2020-05-26 20:00:41 +02:00
|
|
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
2019-08-31 07:54:53 +02:00
|
|
|
synchronized (experienceOrb) {
|
|
|
|
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
|
|
|
|
continue;
|
2020-05-26 20:00:41 +02:00
|
|
|
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb);
|
2019-08-31 07:54:53 +02:00
|
|
|
callCancellableEvent(PickupExperienceEvent.class, pickupExperienceEvent, () -> {
|
|
|
|
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
|
|
|
|
entity.remove();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-12 14:12:17 +02:00
|
|
|
// Eating animation
|
|
|
|
if (isEating()) {
|
|
|
|
if (System.currentTimeMillis() - startEatingTime >= eatingTime) {
|
|
|
|
refreshEating(false);
|
|
|
|
|
|
|
|
triggerStatus((byte) 9); // Mark item use as finished
|
|
|
|
ItemUpdateStateEvent itemUpdateStateEvent = callItemUpdateStateEvent(true);
|
|
|
|
|
2020-05-12 14:19:45 +02:00
|
|
|
// Refresh hand
|
|
|
|
boolean isOffHand = itemUpdateStateEvent.getHand() == Player.Hand.OFF;
|
|
|
|
refreshActiveHand(false, isOffHand, false);
|
|
|
|
|
2020-05-12 14:12:17 +02:00
|
|
|
ItemStack foodItem = itemUpdateStateEvent.getItemStack();
|
|
|
|
boolean isFood = foodItem.getMaterial().isFood();
|
|
|
|
|
|
|
|
if (isFood) {
|
|
|
|
PlayerEatEvent playerEatEvent = new PlayerEatEvent(foodItem);
|
|
|
|
callEvent(PlayerEatEvent.class, playerEatEvent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 06:18:41 +02:00
|
|
|
// Tick event
|
2020-05-25 03:39:57 +02:00
|
|
|
callEvent(PlayerTickEvent.class, playerTickEvent);
|
2019-09-01 06:18:41 +02:00
|
|
|
|
2019-08-18 20:38:09 +02:00
|
|
|
|
|
|
|
// Multiplayer sync
|
2019-09-07 11:42:33 +02:00
|
|
|
if (!getViewers().isEmpty()) {
|
2020-05-26 20:00:41 +02:00
|
|
|
final boolean positionChanged = position.getX() != lastX || position.getZ() != lastZ || position.getY() != lastY;
|
|
|
|
final boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch;
|
2019-09-07 11:42:33 +02:00
|
|
|
ServerPacket updatePacket = null;
|
|
|
|
ServerPacket optionalUpdatePacket = null;
|
|
|
|
if (positionChanged && viewChanged) {
|
2020-02-11 16:48:06 +01:00
|
|
|
EntityPositionAndRotationPacket entityPositionAndRotationPacket = new EntityPositionAndRotationPacket();
|
|
|
|
entityPositionAndRotationPacket.entityId = getEntityId();
|
|
|
|
entityPositionAndRotationPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
|
|
|
|
entityPositionAndRotationPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
|
|
|
entityPositionAndRotationPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
|
|
|
entityPositionAndRotationPacket.yaw = position.getYaw();
|
|
|
|
entityPositionAndRotationPacket.pitch = position.getPitch();
|
|
|
|
entityPositionAndRotationPacket.onGround = onGround;
|
2019-09-07 11:42:33 +02:00
|
|
|
|
|
|
|
lastX = position.getX();
|
|
|
|
lastY = position.getY();
|
|
|
|
lastZ = position.getZ();
|
|
|
|
lastYaw = position.getYaw();
|
|
|
|
lastPitch = position.getPitch();
|
2020-02-11 16:48:06 +01:00
|
|
|
updatePacket = entityPositionAndRotationPacket;
|
2019-09-07 11:42:33 +02:00
|
|
|
} else if (positionChanged) {
|
2020-02-11 16:48:06 +01:00
|
|
|
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
|
|
|
|
entityPositionPacket.entityId = getEntityId();
|
|
|
|
entityPositionPacket.deltaX = (short) ((position.getX() * 32 - lastX * 32) * 128);
|
|
|
|
entityPositionPacket.deltaY = (short) ((position.getY() * 32 - lastY * 32) * 128);
|
|
|
|
entityPositionPacket.deltaZ = (short) ((position.getZ() * 32 - lastZ * 32) * 128);
|
|
|
|
entityPositionPacket.onGround = onGround;
|
2019-09-07 11:42:33 +02:00
|
|
|
lastX = position.getX();
|
|
|
|
lastY = position.getY();
|
|
|
|
lastZ = position.getZ();
|
2020-02-11 16:48:06 +01:00
|
|
|
updatePacket = entityPositionPacket;
|
2019-09-07 11:42:33 +02:00
|
|
|
} else if (viewChanged) {
|
2020-02-11 16:48:06 +01:00
|
|
|
EntityRotationPacket entityRotationPacket = new EntityRotationPacket();
|
|
|
|
entityRotationPacket.entityId = getEntityId();
|
|
|
|
entityRotationPacket.yaw = position.getYaw();
|
|
|
|
entityRotationPacket.pitch = position.getPitch();
|
|
|
|
entityRotationPacket.onGround = onGround;
|
2019-09-07 11:42:33 +02:00
|
|
|
|
|
|
|
lastYaw = position.getYaw();
|
|
|
|
lastPitch = position.getPitch();
|
2020-02-11 16:48:06 +01:00
|
|
|
updatePacket = entityRotationPacket;
|
2019-09-07 11:42:33 +02:00
|
|
|
}
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2019-09-07 11:42:33 +02:00
|
|
|
if (viewChanged) {
|
|
|
|
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
|
|
|
entityHeadLookPacket.entityId = getEntityId();
|
|
|
|
entityHeadLookPacket.yaw = position.getYaw();
|
|
|
|
optionalUpdatePacket = entityHeadLookPacket;
|
|
|
|
}
|
2019-08-24 20:34:01 +02:00
|
|
|
|
2019-09-07 11:42:33 +02:00
|
|
|
if (updatePacket != null) {
|
|
|
|
if (optionalUpdatePacket != null) {
|
|
|
|
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
|
|
|
|
} else {
|
|
|
|
sendPacketToViewers(updatePacket);
|
|
|
|
}
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
2019-08-11 03:40:34 +02:00
|
|
|
}
|
2019-09-07 11:42:33 +02:00
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
}
|
|
|
|
|
2020-04-27 21:12:42 +02:00
|
|
|
@Override
|
|
|
|
public void kill() {
|
2020-04-27 22:38:11 +02:00
|
|
|
if (!isDead()) {
|
2020-04-27 21:12:42 +02:00
|
|
|
// send death message to player
|
2020-05-03 15:00:53 +02:00
|
|
|
Component deathMessage;
|
2020-04-27 22:38:11 +02:00
|
|
|
if (lastDamageSource != null) {
|
2020-05-02 15:26:28 +02:00
|
|
|
deathMessage = lastDamageSource.buildDeathScreenMessage(this);
|
2020-04-27 21:12:42 +02:00
|
|
|
} else { // may happen if killed by the server without applying damage
|
2020-05-03 13:59:53 +02:00
|
|
|
deathMessage = TextComponent.of("Killed by poor programming.");
|
2020-04-27 21:12:42 +02:00
|
|
|
}
|
|
|
|
CombatEventPacket deathPacket = CombatEventPacket.death(this, Optional.empty(), deathMessage);
|
|
|
|
playerConnection.sendPacket(deathPacket);
|
|
|
|
|
|
|
|
// send death message to chat
|
2020-05-03 15:00:53 +02:00
|
|
|
Component chatMessage;
|
2020-04-27 22:38:11 +02:00
|
|
|
if (lastDamageSource != null) {
|
2020-04-27 21:12:42 +02:00
|
|
|
chatMessage = lastDamageSource.buildChatMessage(this);
|
|
|
|
} else { // may happen if killed by the server without applying damage
|
2020-05-03 13:59:53 +02:00
|
|
|
chatMessage = TextComponent.of(getUsername() + " was killed by poor programming.");
|
2020-04-27 21:12:42 +02:00
|
|
|
}
|
|
|
|
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player -> {
|
|
|
|
player.sendMessage(chatMessage);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
super.kill();
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:54:36 +02:00
|
|
|
public void respawn() {
|
|
|
|
if (!isDead())
|
|
|
|
return;
|
|
|
|
|
|
|
|
setFireForDuration(0);
|
|
|
|
setOnFire(false);
|
|
|
|
refreshHealth();
|
|
|
|
RespawnPacket respawnPacket = new RespawnPacket();
|
|
|
|
respawnPacket.dimension = getDimension();
|
|
|
|
respawnPacket.gameMode = getGameMode();
|
|
|
|
respawnPacket.levelType = getLevelType();
|
|
|
|
getPlayerConnection().sendPacket(respawnPacket);
|
|
|
|
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getRespawnPoint());
|
|
|
|
callEvent(PlayerRespawnEvent.class, respawnEvent);
|
|
|
|
refreshIsDead(false);
|
|
|
|
|
|
|
|
// Runnable called when teleportation is successful (after loading and sending necessary chunk)
|
|
|
|
teleport(respawnEvent.getRespawnPosition(), this::refreshAfterTeleport);
|
|
|
|
}
|
|
|
|
|
2019-08-24 20:34:01 +02:00
|
|
|
@Override
|
|
|
|
public void spawn() {
|
2019-08-20 22:40:57 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
@Override
|
|
|
|
public boolean isOnGround() {
|
|
|
|
return onGround;
|
|
|
|
}
|
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
@Override
|
|
|
|
public void remove() {
|
2020-05-09 20:00:59 +02:00
|
|
|
super.remove();
|
2019-08-25 20:03:43 +02:00
|
|
|
clearBossBars();
|
|
|
|
if (getOpenInventory() != null)
|
|
|
|
getOpenInventory().removeViewer(this);
|
2019-09-01 06:18:41 +02:00
|
|
|
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
2020-05-25 01:12:12 +02:00
|
|
|
this.viewableChunks.forEach(chunk -> {
|
|
|
|
if (chunk.isLoaded())
|
|
|
|
chunk.removeViewer(this);
|
|
|
|
});
|
2019-09-07 11:42:33 +02:00
|
|
|
resetTargetBlock();
|
|
|
|
callEvent(PlayerDisconnectEvent.class, new PlayerDisconnectEvent());
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
|
2019-08-20 22:40:57 +02:00
|
|
|
@Override
|
2020-05-23 17:57:56 +02:00
|
|
|
public boolean addViewer(Player player) {
|
2019-08-31 12:10:46 +02:00
|
|
|
if (player == this)
|
2020-05-23 17:57:56 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
boolean result = super.addViewer(player);
|
2019-09-21 20:42:27 +02:00
|
|
|
PlayerConnection viewerConnection = player.getPlayerConnection();
|
2019-08-20 22:40:57 +02:00
|
|
|
String property = "eyJ0aW1lc3RhbXAiOjE1NjU0ODMwODQwOTYsInByb2ZpbGVJZCI6ImFiNzBlY2I0MjM0NjRjMTRhNTJkN2EwOTE1MDdjMjRlIiwicHJvZmlsZU5hbWUiOiJUaGVNb2RlOTExIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RkOTE2NzJiNTE0MmJhN2Y3MjA2ZTRjN2IwOTBkNzhlM2Y1ZDc2NDdiNWFmZDIyNjFhZDk4OGM0MWI2ZjcwYTEifX19";
|
|
|
|
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
|
|
|
|
spawnPlayerPacket.entityId = getEntityId();
|
|
|
|
spawnPlayerPacket.playerUuid = getUuid();
|
2019-08-21 16:50:52 +02:00
|
|
|
spawnPlayerPacket.position = getPosition();
|
2019-08-20 22:40:57 +02:00
|
|
|
|
|
|
|
PlayerInfoPacket pInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER);
|
2020-02-09 15:34:09 +01:00
|
|
|
PlayerInfoPacket.AddPlayer addP = new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), getGameMode(), 10);
|
2019-08-20 22:40:57 +02:00
|
|
|
PlayerInfoPacket.AddPlayer.Property p = new PlayerInfoPacket.AddPlayer.Property("textures", property);//new PlayerInfoPacket.AddPlayer.Property("textures", properties.get(onlinePlayer.getUsername()));
|
|
|
|
addP.properties.add(p);
|
|
|
|
pInfoPacket.playerInfos.add(addP);
|
|
|
|
|
2019-09-21 20:42:27 +02:00
|
|
|
viewerConnection.sendPacket(pInfoPacket);
|
|
|
|
viewerConnection.sendPacket(spawnPlayerPacket);
|
2020-05-24 19:22:58 +02:00
|
|
|
viewerConnection.sendPacket(getVelocityPacket());
|
|
|
|
viewerConnection.sendPacket(getMetadataPacket());
|
|
|
|
|
2020-05-25 01:12:12 +02:00
|
|
|
// Equipments synchronization
|
|
|
|
syncEquipments();
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
if (hasPassenger()) {
|
|
|
|
viewerConnection.sendPacket(getPassengersPacket());
|
|
|
|
}
|
2019-09-21 20:42:27 +02:00
|
|
|
|
|
|
|
// Team
|
|
|
|
if (team != null)
|
|
|
|
viewerConnection.sendPacket(team.getTeamsCreationPacket());
|
2020-05-23 17:57:56 +02:00
|
|
|
return result;
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-05-23 17:57:56 +02:00
|
|
|
public boolean removeViewer(Player player) {
|
2019-08-31 12:10:46 +02:00
|
|
|
if (player == this)
|
2020-05-23 17:57:56 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
boolean result = super.removeViewer(player);
|
2019-09-21 20:42:27 +02:00
|
|
|
PlayerConnection viewerConnection = player.getPlayerConnection();
|
2019-08-20 22:40:57 +02:00
|
|
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
|
|
|
|
playerInfoPacket.playerInfos.add(new PlayerInfoPacket.RemovePlayer(getUuid()));
|
2019-09-21 20:42:27 +02:00
|
|
|
viewerConnection.sendPacket(playerInfoPacket);
|
|
|
|
|
|
|
|
// Team
|
|
|
|
if (team != null && team.getPlayers().size() == 1) // If team only contains "this" player
|
|
|
|
viewerConnection.sendPacket(team.createTeamDestructionPacket());
|
2020-05-23 17:57:56 +02:00
|
|
|
return result;
|
2019-08-11 03:40:34 +02:00
|
|
|
}
|
|
|
|
|
2019-08-23 23:55:09 +02:00
|
|
|
@Override
|
|
|
|
public void setInstance(Instance instance) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.notNull(instance, "instance cannot be null!");
|
|
|
|
Check.argCondition(this.instance == instance, "Instance should be different than the current one");
|
2019-09-01 06:18:41 +02:00
|
|
|
|
2020-05-08 18:03:39 +02:00
|
|
|
boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too
|
2019-09-01 06:18:41 +02:00
|
|
|
for (Chunk viewableChunk : viewableChunks) {
|
|
|
|
viewableChunk.removeViewer(this);
|
|
|
|
}
|
|
|
|
viewableChunks.clear();
|
2019-08-23 23:55:09 +02:00
|
|
|
|
2020-04-27 18:46:39 +02:00
|
|
|
if (this.instance != null) {
|
|
|
|
Dimension instanceDimension = instance.getDimension();
|
|
|
|
if (dimension != instanceDimension)
|
|
|
|
sendDimension(instanceDimension);
|
|
|
|
}
|
|
|
|
|
2019-09-01 06:18:41 +02:00
|
|
|
long[] visibleChunks = ChunkUtils.getChunksInRange(position, getChunkRange());
|
|
|
|
int length = visibleChunks.length;
|
|
|
|
|
|
|
|
AtomicInteger counter = new AtomicInteger(0);
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunks[i]);
|
|
|
|
int chunkX = chunkPos[0];
|
|
|
|
int chunkZ = chunkPos[1];
|
|
|
|
Consumer<Chunk> callback = (chunk) -> {
|
|
|
|
if (chunk != null) {
|
|
|
|
viewableChunks.add(chunk);
|
|
|
|
chunk.addViewer(this);
|
2020-05-08 21:49:04 +02:00
|
|
|
instance.sendChunk(this, chunk);
|
2020-05-17 01:42:07 +02:00
|
|
|
updateViewPosition(chunk);
|
2019-09-01 06:18:41 +02:00
|
|
|
}
|
|
|
|
boolean isLast = counter.get() == length - 1;
|
|
|
|
if (isLast) {
|
2020-04-16 14:51:21 +02:00
|
|
|
// This is the last chunk to be loaded , spawn player
|
2019-09-01 06:18:41 +02:00
|
|
|
super.setInstance(instance);
|
2020-05-08 18:03:39 +02:00
|
|
|
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(instance, firstSpawn);
|
2019-09-01 06:18:41 +02:00
|
|
|
callEvent(PlayerSpawnEvent.class, spawnEvent);
|
|
|
|
} else {
|
|
|
|
// Increment the counter of current loaded chunks
|
|
|
|
counter.incrementAndGet();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// WARNING: if auto load is disabled and no chunks are loaded beforehand, player will be stuck.
|
|
|
|
instance.loadOptionalChunk(chunkX, chunkZ, callback);
|
|
|
|
}
|
2019-08-23 23:55:09 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 22:53:58 +02:00
|
|
|
@Override
|
|
|
|
public Consumer<PacketWriter> getMetadataConsumer() {
|
|
|
|
return packet -> {
|
|
|
|
super.getMetadataConsumer().accept(packet);
|
|
|
|
fillMetadataIndex(packet, 14);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void fillMetadataIndex(PacketWriter packet, int index) {
|
|
|
|
super.fillMetadataIndex(packet, index);
|
|
|
|
if (index == 14) {
|
|
|
|
packet.writeByte((byte) 14);
|
|
|
|
packet.writeByte(METADATA_FLOAT);
|
|
|
|
packet.writeFloat(additionalHearts);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
|
2019-08-18 23:52:11 +02:00
|
|
|
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
|
|
|
|
breakAnimationPacket.entityId = getEntityId() + 1;
|
|
|
|
breakAnimationPacket.blockPosition = blockPosition;
|
|
|
|
breakAnimationPacket.destroyStage = destroyStage;
|
2019-08-22 14:52:32 +02:00
|
|
|
sendPacketToViewersAndSelf(breakAnimationPacket);
|
2019-08-18 23:52:11 +02:00
|
|
|
}
|
|
|
|
|
2019-09-10 06:59:15 +02:00
|
|
|
// Use legacy color formatting
|
2019-08-18 20:38:09 +02:00
|
|
|
public void sendMessage(String message) {
|
2020-05-03 14:17:32 +02:00
|
|
|
sendMessage(Chat.fromLegacyText(message));
|
2019-09-10 06:59:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void sendMessage(String message, char colorChar) {
|
2020-05-03 14:17:32 +02:00
|
|
|
sendMessage(Chat.fromLegacyText(message, colorChar));
|
2019-09-10 06:59:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-03 15:00:53 +02:00
|
|
|
public void sendMessage(Component textObject) {
|
2020-05-04 18:00:21 +02:00
|
|
|
String json = Chat.toJsonString(textObject);
|
2020-05-03 14:17:32 +02:00
|
|
|
playerConnection.sendPacket(new ChatMessagePacket(json, ChatMessagePacket.Position.CHAT));
|
2019-09-10 06:59:15 +02:00
|
|
|
}
|
|
|
|
|
2020-04-10 13:39:22 +02:00
|
|
|
public void playSound(Sound sound, SoundCategory soundCategory, int x, int y, int z, float volume, float pitch) {
|
|
|
|
SoundEffectPacket soundEffectPacket = new SoundEffectPacket();
|
|
|
|
soundEffectPacket.soundId = sound.getId();
|
|
|
|
soundEffectPacket.soundCategory = soundCategory;
|
2020-05-06 22:35:32 +02:00
|
|
|
soundEffectPacket.x = x * 8;
|
|
|
|
soundEffectPacket.y = y * 8;
|
|
|
|
soundEffectPacket.z = z * 8;
|
2020-04-10 13:39:22 +02:00
|
|
|
soundEffectPacket.volume = volume;
|
|
|
|
soundEffectPacket.pitch = pitch;
|
|
|
|
playerConnection.sendPacket(soundEffectPacket);
|
|
|
|
}
|
|
|
|
|
2020-04-28 16:08:21 +02:00
|
|
|
/**
|
|
|
|
* Plays a given effect at the given position for this player
|
2020-04-28 17:58:34 +02: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
|
2020-04-28 16:08:21 +02:00
|
|
|
* @param disableRelativeVolume disable volume scaling based on distance
|
|
|
|
*/
|
|
|
|
public void playEffect(Effects effect, int x, int y, int z, int data, boolean disableRelativeVolume) {
|
|
|
|
EffectPacket packet = new EffectPacket();
|
|
|
|
packet.effectId = effect.getId();
|
|
|
|
packet.position = new BlockPosition(x, y, z);
|
|
|
|
packet.data = data;
|
|
|
|
packet.disableRelativeVolume = disableRelativeVolume;
|
|
|
|
playerConnection.sendPacket(packet);
|
|
|
|
}
|
|
|
|
|
2020-04-10 13:39:22 +02:00
|
|
|
public void stopSound() {
|
|
|
|
StopSoundPacket stopSoundPacket = new StopSoundPacket();
|
|
|
|
stopSoundPacket.flags = 0x00;
|
|
|
|
playerConnection.sendPacket(stopSoundPacket);
|
|
|
|
}
|
|
|
|
|
2020-05-04 18:00:21 +02:00
|
|
|
public void sendHeaderFooter(Component header, Component footer) {
|
2020-04-10 13:39:22 +02:00
|
|
|
PlayerListHeaderAndFooterPacket playerListHeaderAndFooterPacket = new PlayerListHeaderAndFooterPacket();
|
|
|
|
playerListHeaderAndFooterPacket.emptyHeader = header == null;
|
|
|
|
playerListHeaderAndFooterPacket.emptyFooter = footer == null;
|
2020-05-04 18:00:21 +02:00
|
|
|
playerListHeaderAndFooterPacket.header = Chat.toJsonString(header);
|
|
|
|
playerListHeaderAndFooterPacket.footer = Chat.toJsonString(footer);
|
2020-04-10 13:39:22 +02:00
|
|
|
|
|
|
|
playerConnection.sendPacket(playerListHeaderAndFooterPacket);
|
|
|
|
}
|
|
|
|
|
2020-05-04 18:00:21 +02:00
|
|
|
public void sendHeaderFooter(String header, String footer, char colorChar) {
|
|
|
|
sendHeaderFooter(Chat.fromLegacyText(header, colorChar), Chat.fromLegacyText(footer, colorChar));
|
|
|
|
}
|
|
|
|
|
2020-05-04 18:32:14 +02:00
|
|
|
private void sendTitle(Component title, TitlePacket.Action action) {
|
2020-02-16 19:11:36 +01:00
|
|
|
TitlePacket titlePacket = new TitlePacket();
|
2020-05-04 18:32:14 +02:00
|
|
|
titlePacket.action = action;
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
case SET_TITLE:
|
|
|
|
titlePacket.titleText = Chat.toJsonString(title);
|
|
|
|
break;
|
|
|
|
case SET_SUBTITLE:
|
|
|
|
titlePacket.subtitleText = Chat.toJsonString(title);
|
|
|
|
break;
|
|
|
|
case SET_ACTION_BAR:
|
|
|
|
titlePacket.actionBarText = Chat.toJsonString(title);
|
|
|
|
default:
|
|
|
|
throw new UnsupportedOperationException("Invalid TitlePacket.Action type!");
|
|
|
|
}
|
|
|
|
|
2020-02-16 19:11:36 +01:00
|
|
|
playerConnection.sendPacket(titlePacket);
|
|
|
|
}
|
|
|
|
|
2020-05-04 18:32:14 +02:00
|
|
|
public void sendTitleSubtitleMessage(Component title, Component subtitle) {
|
|
|
|
sendTitle(title, TitlePacket.Action.SET_TITLE);
|
|
|
|
sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendTitleMessage(Component title) {
|
|
|
|
sendTitle(title, TitlePacket.Action.SET_TITLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendTitleMessage(String title, char colorChar) {
|
|
|
|
sendTitleMessage(Chat.fromLegacyText(title, colorChar));
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendTitleMessage(String title) {
|
|
|
|
sendTitleMessage(title, Chat.COLOR_CHAR);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendSubtitleMessage(Component subtitle) {
|
|
|
|
sendTitle(subtitle, TitlePacket.Action.SET_SUBTITLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendSubtitleMessage(String subtitle, char colorChar) {
|
|
|
|
sendSubtitleMessage(Chat.fromLegacyText(subtitle, colorChar));
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendSubtitleMessage(String subtitle) {
|
|
|
|
sendSubtitleMessage(subtitle, Chat.COLOR_CHAR);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendActionBarMessage(Component actionBar) {
|
|
|
|
sendTitle(actionBar, TitlePacket.Action.SET_ACTION_BAR);
|
|
|
|
}
|
|
|
|
|
2020-05-04 18:00:21 +02:00
|
|
|
public void sendActionBarMessage(String message, char colorChar) {
|
|
|
|
sendActionBarMessage(Chat.fromLegacyText(message, colorChar));
|
|
|
|
}
|
|
|
|
|
2020-02-16 19:11:36 +01:00
|
|
|
public void sendActionBarMessage(String message) {
|
|
|
|
sendActionBarMessage(message, Chat.COLOR_CHAR);
|
|
|
|
}
|
|
|
|
|
2019-08-27 20:49:11 +02:00
|
|
|
@Override
|
2020-04-27 20:33:08 +02:00
|
|
|
public boolean isImmune(DamageType type) {
|
2020-05-05 15:55:21 +02:00
|
|
|
if (!getGameMode().canTakeDamage()) {
|
2020-04-27 20:33:08 +02:00
|
|
|
return type != DamageType.VOID;
|
|
|
|
}
|
|
|
|
return super.isImmune(type);
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 00:29:40 +02:00
|
|
|
@Override
|
|
|
|
public void setAttribute(Attribute attribute, float value) {
|
|
|
|
super.setAttribute(attribute, value);
|
|
|
|
if (playerConnection != null)
|
|
|
|
playerConnection.sendPacket(getPropertiesPacket());
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 00:29:40 +02:00
|
|
|
@Override
|
2019-08-25 20:03:43 +02:00
|
|
|
public void setHealth(float health) {
|
2019-08-26 00:29:40 +02:00
|
|
|
super.setHealth(health);
|
2019-08-25 20:03:43 +02:00
|
|
|
sendUpdateHealthPacket();
|
|
|
|
}
|
2020-05-26 22:53:58 +02:00
|
|
|
|
|
|
|
public float getAdditionalHearts() {
|
|
|
|
return additionalHearts;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setAdditionalHearts(float additionalHearts) {
|
|
|
|
this.additionalHearts = additionalHearts;
|
|
|
|
sendMetadataIndex(14);
|
|
|
|
}
|
2019-08-25 20:03:43 +02:00
|
|
|
|
|
|
|
public int getFood() {
|
|
|
|
return food;
|
|
|
|
}
|
|
|
|
|
2020-05-26 17:48:46 +02:00
|
|
|
/**
|
|
|
|
* Set and refresh client food bar
|
|
|
|
*
|
|
|
|
* @param food the new food value
|
|
|
|
*/
|
2019-08-25 20:03:43 +02:00
|
|
|
public void setFood(int food) {
|
2020-05-26 18:25:35 +02:00
|
|
|
Check.argCondition(!MathUtils.isBetween(food, 0, 20), "Food has to be between 0 and 20");
|
2019-08-25 20:03:43 +02:00
|
|
|
this.food = food;
|
|
|
|
sendUpdateHealthPacket();
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getFoodSaturation() {
|
|
|
|
return foodSaturation;
|
|
|
|
}
|
|
|
|
|
2020-05-26 17:48:46 +02:00
|
|
|
/**
|
|
|
|
* Set and refresh client food saturation
|
|
|
|
*
|
|
|
|
* @param foodSaturation the food saturation
|
|
|
|
*/
|
2019-08-25 20:03:43 +02:00
|
|
|
public void setFoodSaturation(float foodSaturation) {
|
2020-05-26 17:48:46 +02:00
|
|
|
Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 5), "Food saturation has to be between 0 and 5");
|
2019-08-25 20:03:43 +02:00
|
|
|
this.foodSaturation = foodSaturation;
|
|
|
|
sendUpdateHealthPacket();
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* @return true if the player is currently eating, false otherwise
|
|
|
|
*/
|
2020-05-12 14:12:17 +02:00
|
|
|
public boolean isEating() {
|
|
|
|
return isEating;
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* @return the default eating time for the player
|
|
|
|
*/
|
2020-05-12 18:40:04 +02:00
|
|
|
public long getDefaultEatingTime() {
|
|
|
|
return defaultEatingTime;
|
|
|
|
}
|
|
|
|
|
2020-05-12 14:12:17 +02:00
|
|
|
/**
|
2020-05-12 18:40:04 +02:00
|
|
|
* Used to change the default eating time animation
|
2020-05-12 14:12:17 +02:00
|
|
|
*
|
2020-05-12 18:40:04 +02:00
|
|
|
* @param defaultEatingTime the default eating time in milliseconds
|
2020-05-12 14:12:17 +02:00
|
|
|
*/
|
2020-05-12 18:40:04 +02:00
|
|
|
public void setDefaultEatingTime(long defaultEatingTime) {
|
|
|
|
this.defaultEatingTime = defaultEatingTime;
|
2020-05-12 14:12:17 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 19:54:36 +02:00
|
|
|
/**
|
|
|
|
* Used to change the player gamemode
|
|
|
|
*
|
|
|
|
* @param gameMode the new player gamemode
|
|
|
|
*/
|
|
|
|
public void setGameMode(GameMode gameMode) {
|
|
|
|
Check.notNull(gameMode, "GameMode cannot be null");
|
|
|
|
this.gameMode = gameMode;
|
|
|
|
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.getId());
|
2020-05-26 15:35:48 +02:00
|
|
|
|
|
|
|
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE);
|
|
|
|
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateGamemode(getUuid(), gameMode));
|
|
|
|
sendPacketToViewersAndSelf(infoPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return the displayed name of the player in the tab-list,
|
|
|
|
* null means that {@link #getUsername()} is display
|
|
|
|
*/
|
|
|
|
public String getDisplayName() {
|
|
|
|
return displayName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param displayName the new displayed name of the player in the tab-list,
|
|
|
|
* set to null to show the player username
|
|
|
|
*/
|
|
|
|
public void setDisplayName(String displayName) {
|
|
|
|
this.displayName = displayName;
|
|
|
|
|
|
|
|
String jsonDisplayName = displayName != null ? Chat.toJsonString(Chat.fromLegacyText(displayName)) : null;
|
|
|
|
PlayerInfoPacket infoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME);
|
|
|
|
infoPacket.playerInfos.add(new PlayerInfoPacket.UpdateDisplayName(getUuid(), jsonDisplayName));
|
|
|
|
sendPacketToViewersAndSelf(infoPacket);
|
2020-05-25 19:54:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isEnableRespawnScreen() {
|
|
|
|
return enableRespawnScreen;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setEnableRespawnScreen(boolean enableRespawnScreen) {
|
|
|
|
this.enableRespawnScreen = enableRespawnScreen;
|
|
|
|
sendChangeGameStatePacket(ChangeGameStatePacket.Reason.ENABLE_RESPAWN_SCREEN, enableRespawnScreen ? 0 : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sendChangeGameStatePacket(ChangeGameStatePacket.Reason reason, float value) {
|
|
|
|
ChangeGameStatePacket changeGameStatePacket = new ChangeGameStatePacket();
|
|
|
|
changeGameStatePacket.reason = reason;
|
|
|
|
changeGameStatePacket.value = value;
|
|
|
|
playerConnection.sendPacket(changeGameStatePacket);
|
|
|
|
}
|
|
|
|
|
2020-05-17 04:41:32 +02:00
|
|
|
/**
|
|
|
|
* @param item the item to drop
|
|
|
|
* @return true if player can drop the item (event not cancelled), false otherwise
|
|
|
|
*/
|
2020-04-20 18:46:39 +02:00
|
|
|
public boolean dropItem(ItemStack item) {
|
|
|
|
ItemDropEvent itemDropEvent = new ItemDropEvent(item);
|
|
|
|
callEvent(ItemDropEvent.class, itemDropEvent);
|
|
|
|
return !itemDropEvent.isCancelled();
|
|
|
|
}
|
|
|
|
|
2020-05-26 18:25:35 +02:00
|
|
|
/**
|
|
|
|
* Rotate the player to face {@code targetPosition}
|
|
|
|
*
|
|
|
|
* @param facePoint the point from where the player should aim
|
|
|
|
* @param targetPosition the target position to face
|
|
|
|
*/
|
|
|
|
public void facePosition(FacePoint facePoint, Position targetPosition) {
|
|
|
|
facePosition(facePoint, targetPosition, null, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rotate 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(FacePoint facePoint, Entity entity, FacePoint targetPoint) {
|
|
|
|
facePosition(facePoint, entity.getPosition(), entity, targetPoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void facePosition(FacePoint facePoint, Position targetPosition, Entity entity, FacePoint targetPoint) {
|
|
|
|
FacePlayerPacket facePlayerPacket = new FacePlayerPacket();
|
|
|
|
facePlayerPacket.entityFacePosition = facePoint == FacePoint.EYE ?
|
|
|
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
|
|
|
|
facePlayerPacket.targetX = targetPosition.getX();
|
|
|
|
facePlayerPacket.targetY = targetPosition.getY();
|
|
|
|
facePlayerPacket.targetZ = targetPosition.getZ();
|
|
|
|
if (entity != null) {
|
|
|
|
facePlayerPacket.entityId = entity.getEntityId();
|
|
|
|
facePlayerPacket.entityFacePosition = targetPoint == FacePoint.EYE ?
|
|
|
|
FacePlayerPacket.FacePosition.EYES : FacePlayerPacket.FacePosition.FEET;
|
|
|
|
}
|
|
|
|
playerConnection.sendPacket(facePlayerPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the camera at {@code entity} eyes
|
|
|
|
*
|
|
|
|
* @param entity the entity to spectate
|
|
|
|
*/
|
|
|
|
public void spectate(Entity entity) {
|
|
|
|
CameraPacket cameraPacket = new CameraPacket();
|
|
|
|
cameraPacket.cameraId = entity.getEntityId();
|
|
|
|
playerConnection.sendPacket(cameraPacket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset the camera at the player
|
|
|
|
*/
|
|
|
|
public void stopSpectating() {
|
|
|
|
spectate(this);
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Used to retrieve the default spawn point
|
|
|
|
* can be altered by the {@link PlayerRespawnEvent#setRespawnPosition(Position)}
|
|
|
|
*
|
|
|
|
* @return the default respawn point
|
|
|
|
*/
|
2020-04-27 20:33:08 +02:00
|
|
|
public Position getRespawnPoint() {
|
2020-05-15 18:03:28 +02:00
|
|
|
return respawnPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Change the default spawn point
|
|
|
|
*
|
|
|
|
* @param respawnPoint
|
|
|
|
*/
|
|
|
|
public void setRespawnPoint(Position respawnPoint) {
|
|
|
|
this.respawnPoint = respawnPoint;
|
2020-04-27 20:33:08 +02:00
|
|
|
}
|
|
|
|
|
2020-05-08 18:42:04 +02:00
|
|
|
public void refreshAfterTeleport() {
|
|
|
|
getInventory().update();
|
|
|
|
|
|
|
|
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
|
|
|
|
spawnPlayerPacket.entityId = getEntityId();
|
|
|
|
spawnPlayerPacket.playerUuid = getUuid();
|
|
|
|
spawnPlayerPacket.position = getPosition();
|
|
|
|
sendPacketToViewers(spawnPlayerPacket);
|
2020-05-25 20:32:47 +02:00
|
|
|
sendPacketToViewersAndSelf(getMetadataPacket());
|
2020-05-08 18:42:04 +02:00
|
|
|
playerConnection.sendPacket(getPropertiesPacket());
|
|
|
|
sendUpdateHealthPacket();
|
|
|
|
syncEquipments();
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
|
2020-04-05 17:46:29 +02:00
|
|
|
protected void refreshHealth() {
|
2019-08-25 20:03:43 +02:00
|
|
|
this.food = 20;
|
|
|
|
this.foodSaturation = 5;
|
2020-05-26 17:48:46 +02:00
|
|
|
// refresh health and send health packet
|
|
|
|
heal();
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected void sendUpdateHealthPacket() {
|
|
|
|
UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket();
|
2019-08-26 04:39:58 +02:00
|
|
|
updateHealthPacket.health = getHealth();
|
2019-08-25 20:03:43 +02:00
|
|
|
updateHealthPacket.food = food;
|
|
|
|
updateHealthPacket.foodSaturation = foodSaturation;
|
|
|
|
playerConnection.sendPacket(updateHealthPacket);
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
/**
|
|
|
|
* Used to change the percentage experience bar
|
|
|
|
* This cannot change the displayed level, see {@link #setLevel(int)}
|
|
|
|
*
|
|
|
|
* @param exp a percentage between 0 and 1
|
|
|
|
*/
|
2019-08-31 07:54:53 +02:00
|
|
|
public void setExp(float exp) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.argCondition(!MathUtils.isBetween(exp, 0, 1), "Exp should be between 0 and 1");
|
|
|
|
|
2019-08-31 07:54:53 +02:00
|
|
|
this.exp = exp;
|
|
|
|
sendExperienceUpdatePacket();
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
/**
|
|
|
|
* Used to change the level of the player
|
|
|
|
* This cannot change the displayed percentage bar see {@link #setExp(float)}
|
|
|
|
*
|
|
|
|
* @param level the new level of the player
|
|
|
|
*/
|
2019-08-31 07:54:53 +02:00
|
|
|
public void setLevel(int level) {
|
|
|
|
this.level = level;
|
|
|
|
sendExperienceUpdatePacket();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected void sendExperienceUpdatePacket() {
|
|
|
|
SetExperiencePacket setExperiencePacket = new SetExperiencePacket();
|
|
|
|
setExperiencePacket.percentage = exp;
|
|
|
|
setExperiencePacket.level = level;
|
|
|
|
playerConnection.sendPacket(setExperiencePacket);
|
|
|
|
}
|
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
protected void onChunkChange(Chunk lastChunk, Chunk newChunk) {
|
2020-02-17 17:33:53 +01:00
|
|
|
long[] lastVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), MinecraftServer.CHUNK_VIEW_DISTANCE);
|
|
|
|
long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), MinecraftServer.CHUNK_VIEW_DISTANCE);
|
2019-08-26 04:39:58 +02:00
|
|
|
int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks);
|
|
|
|
int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks);
|
2019-08-25 20:03:43 +02:00
|
|
|
|
|
|
|
// Unload old chunks
|
2019-08-26 04:39:58 +02:00
|
|
|
for (int index : oldChunks) {
|
|
|
|
int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunks[index]);
|
|
|
|
UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket();
|
|
|
|
unloadChunkPacket.chunkX = chunkPos[0];
|
|
|
|
unloadChunkPacket.chunkZ = chunkPos[1];
|
|
|
|
playerConnection.sendPacket(unloadChunkPacket);
|
2019-09-01 06:18:41 +02:00
|
|
|
|
|
|
|
Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]);
|
|
|
|
if (chunk != null)
|
|
|
|
chunk.removeViewer(this);
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 04:39:58 +02:00
|
|
|
updateViewPosition(newChunk);
|
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
// Load new chunks
|
2019-08-26 04:39:58 +02:00
|
|
|
for (int i = 0; i < newChunks.length; i++) {
|
|
|
|
int index = newChunks[i];
|
|
|
|
int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunks[index]);
|
|
|
|
instance.loadOptionalChunk(chunkPos[0], chunkPos[1], chunk -> {
|
|
|
|
if (chunk == null) {
|
2020-05-24 22:21:38 +02:00
|
|
|
// Cannot load chunk (auto load is not enabled)
|
|
|
|
return;
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
2019-09-01 06:18:41 +02:00
|
|
|
this.viewableChunks.add(chunk);
|
|
|
|
chunk.addViewer(this);
|
2019-08-26 04:39:58 +02:00
|
|
|
instance.sendChunk(this, chunk);
|
|
|
|
});
|
2019-08-25 20:03:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
@Override
|
2019-08-25 20:03:43 +02:00
|
|
|
public void teleport(Position position, Runnable callback) {
|
|
|
|
super.teleport(position, () -> {
|
2019-08-26 04:39:58 +02:00
|
|
|
updatePlayerPosition();
|
2019-08-25 20:03:43 +02:00
|
|
|
if (callback != null)
|
|
|
|
callback.run();
|
|
|
|
});
|
|
|
|
}
|
2019-08-22 14:52:32 +02:00
|
|
|
|
2019-08-25 20:03:43 +02:00
|
|
|
@Override
|
|
|
|
public void teleport(Position position) {
|
|
|
|
teleport(position, null);
|
2019-08-11 09:33:27 +02:00
|
|
|
}
|
|
|
|
|
2019-08-11 03:40:34 +02:00
|
|
|
public String getUsername() {
|
|
|
|
return username;
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
|
|
|
|
2019-08-03 15:25:24 +02:00
|
|
|
public PlayerConnection getPlayerConnection() {
|
|
|
|
return playerConnection;
|
|
|
|
}
|
2019-08-10 04:16:01 +02:00
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @return true if the player is online, false otherwise
|
|
|
|
*/
|
2019-09-03 07:36:04 +02:00
|
|
|
public boolean isOnline() {
|
|
|
|
return playerConnection.isOnline();
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @return the player settings
|
|
|
|
*/
|
2019-08-21 16:50:52 +02:00
|
|
|
public PlayerSettings getSettings() {
|
|
|
|
return settings;
|
|
|
|
}
|
|
|
|
|
2019-08-12 08:30:59 +02:00
|
|
|
public PlayerInventory getInventory() {
|
|
|
|
return inventory;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* Used to get the player latency,
|
|
|
|
* computed by seeing how long it takes the client to answer the {@link KeepAlivePacket} packet
|
|
|
|
*
|
|
|
|
* @return the player latency
|
|
|
|
*/
|
2020-04-26 06:34:08 +02:00
|
|
|
public int getLatency() {
|
|
|
|
return latency;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @return the player current dimension
|
|
|
|
*/
|
2019-08-21 16:50:52 +02:00
|
|
|
public Dimension getDimension() {
|
|
|
|
return dimension;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @return the player current gamemode
|
|
|
|
*/
|
2019-08-12 08:30:59 +02:00
|
|
|
public GameMode getGameMode() {
|
|
|
|
return gameMode;
|
|
|
|
}
|
|
|
|
|
2020-04-27 20:33:08 +02:00
|
|
|
/**
|
|
|
|
* Returns true iff this player is in creative. Used for code readability
|
2020-04-27 22:38:11 +02:00
|
|
|
*
|
2020-05-24 19:22:58 +02:00
|
|
|
* @return true if the player is in creative mode, false otherwise
|
2020-04-27 20:33:08 +02:00
|
|
|
*/
|
|
|
|
public boolean isCreative() {
|
|
|
|
return gameMode == GameMode.CREATIVE;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
/**
|
|
|
|
* Change the dimension of the player
|
|
|
|
* Mostly unsafe since it requires sending chunks after
|
|
|
|
*
|
|
|
|
* @param dimension the new player dimension
|
|
|
|
*/
|
2020-04-27 18:46:39 +02:00
|
|
|
public void sendDimension(Dimension dimension) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.notNull(dimension, "Dimension cannot be null!");
|
|
|
|
Check.argCondition(dimension.equals(getDimension()), "The dimension need to be different than the current one!");
|
2020-04-22 02:42:58 +02:00
|
|
|
|
2020-05-25 19:54:36 +02:00
|
|
|
this.dimension = dimension;
|
2019-08-21 16:50:52 +02:00
|
|
|
RespawnPacket respawnPacket = new RespawnPacket();
|
|
|
|
respawnPacket.dimension = dimension;
|
|
|
|
respawnPacket.gameMode = gameMode;
|
|
|
|
respawnPacket.levelType = levelType;
|
|
|
|
playerConnection.sendPacket(respawnPacket);
|
|
|
|
}
|
|
|
|
|
2020-05-04 18:10:19 +02:00
|
|
|
public void kick(Component message) {
|
2019-08-20 22:40:57 +02:00
|
|
|
DisconnectPacket disconnectPacket = new DisconnectPacket();
|
2020-05-04 18:10:19 +02:00
|
|
|
disconnectPacket.message = Chat.toJsonString(message);
|
2019-08-20 22:40:57 +02:00
|
|
|
playerConnection.sendPacket(disconnectPacket);
|
2020-05-25 00:29:37 +02:00
|
|
|
playerConnection.disconnect();
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-04 18:10:19 +02:00
|
|
|
public void kick(String message) {
|
|
|
|
kick(Chat.fromLegacyText(message));
|
|
|
|
}
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
public LevelType getLevelType() {
|
|
|
|
return levelType;
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* Change 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
|
|
|
|
*/
|
2019-08-12 08:30:59 +02:00
|
|
|
public void setHeldItemSlot(short slot) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.argCondition(!MathUtils.isBetween(slot, 0, 8), "Slot has to be between 0 and 8");
|
|
|
|
|
2019-08-12 08:30:59 +02:00
|
|
|
HeldItemChangePacket heldItemChangePacket = new HeldItemChangePacket();
|
|
|
|
heldItemChangePacket.slot = slot;
|
|
|
|
playerConnection.sendPacket(heldItemChangePacket);
|
|
|
|
refreshHeldSlot(slot);
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* @return the current held slot for the player
|
|
|
|
*/
|
|
|
|
public short getHeldSlot() {
|
|
|
|
return heldSlot;
|
|
|
|
}
|
|
|
|
|
2019-09-21 20:42:27 +02:00
|
|
|
public void setTeam(Team team) {
|
|
|
|
if (this.team == team)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (this.team != null) {
|
|
|
|
this.team.removePlayer(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.team = team;
|
|
|
|
if (team != null) {
|
|
|
|
team.addPlayer(this);
|
|
|
|
sendPacketToViewers(team.getTeamsCreationPacket()); // FIXME: only if viewer hasn't already register this team
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setBelowNameScoreboard(BelowNameScoreboard belowNameScoreboard) {
|
|
|
|
if (this.belowNameScoreboard == belowNameScoreboard)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (this.belowNameScoreboard != null) {
|
|
|
|
this.belowNameScoreboard.removeViewer(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.belowNameScoreboard = belowNameScoreboard;
|
|
|
|
if (belowNameScoreboard != null) {
|
|
|
|
belowNameScoreboard.addViewer(this);
|
2019-09-23 19:56:08 +02:00
|
|
|
belowNameScoreboard.displayScoreboard(this);
|
2019-09-21 20:42:27 +02:00
|
|
|
getViewers().forEach(player -> belowNameScoreboard.addViewer(player));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* @return the currently open inventory, null if there is not (player inventory is not detected)
|
|
|
|
*/
|
2019-08-13 17:52:09 +02:00
|
|
|
public Inventory getOpenInventory() {
|
|
|
|
return openInventory;
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* Used to get the {@link CustomBlock} that the player is currently mining
|
|
|
|
*
|
|
|
|
* @return the currently mined {@link CustomBlock} by the player, null if there is not
|
|
|
|
*/
|
2019-08-18 23:52:11 +02:00
|
|
|
public CustomBlock getCustomBlockTarget() {
|
|
|
|
return targetCustomBlock;
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* @return an unmodifiable {@link Set} containing all the current player viewable boss bars
|
|
|
|
*/
|
2019-08-19 17:04:19 +02:00
|
|
|
public Set<BossBar> getBossBars() {
|
|
|
|
return Collections.unmodifiableSet(bossBars);
|
|
|
|
}
|
|
|
|
|
2020-05-12 17:12:11 +02:00
|
|
|
/**
|
2020-05-15 18:03:28 +02:00
|
|
|
* Open the specified Inventory, close the previous inventory if existing
|
2020-05-12 17:12:11 +02:00
|
|
|
*
|
|
|
|
* @param inventory the inventory to open
|
|
|
|
* @return true if the inventory has been opened/sent to the player, false otherwise (cancelled by event)
|
|
|
|
*/
|
|
|
|
public boolean openInventory(Inventory inventory) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.notNull(inventory, "Inventory cannot be null, use Player#closeInventory() to close current");
|
2019-08-13 17:52:09 +02:00
|
|
|
|
2020-05-12 17:12:11 +02:00
|
|
|
InventoryOpenEvent inventoryOpenEvent = new InventoryOpenEvent(this, inventory);
|
|
|
|
|
|
|
|
callCancellableEvent(InventoryOpenEvent.class, inventoryOpenEvent, () -> {
|
|
|
|
|
|
|
|
if (getOpenInventory() != null) {
|
|
|
|
closeInventory();
|
|
|
|
}
|
|
|
|
|
2020-05-12 18:40:04 +02:00
|
|
|
Inventory newInventory = inventoryOpenEvent.getInventory();
|
|
|
|
|
2020-05-12 17:12:11 +02:00
|
|
|
OpenWindowPacket openWindowPacket = new OpenWindowPacket();
|
2020-05-12 18:40:04 +02:00
|
|
|
openWindowPacket.windowId = newInventory.getWindowId();
|
|
|
|
openWindowPacket.windowType = newInventory.getInventoryType().getWindowType();
|
|
|
|
openWindowPacket.title = newInventory.getTitle();
|
2020-05-12 17:12:11 +02:00
|
|
|
playerConnection.sendPacket(openWindowPacket);
|
2020-05-12 18:40:04 +02:00
|
|
|
newInventory.addViewer(this);
|
|
|
|
refreshOpenInventory(newInventory);
|
2020-05-12 17:12:11 +02:00
|
|
|
|
|
|
|
});
|
2019-08-13 17:52:09 +02:00
|
|
|
|
2020-05-12 17:12:11 +02:00
|
|
|
return !inventoryOpenEvent.isCancelled();
|
2019-08-13 17:52:09 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* Close the current inventory if there is any
|
|
|
|
* It closes the player inventory if {@link #getOpenInventory()} returns null
|
|
|
|
*/
|
2019-08-13 17:52:09 +02:00
|
|
|
public void closeInventory() {
|
|
|
|
Inventory openInventory = getOpenInventory();
|
2020-05-17 11:40:49 +02:00
|
|
|
|
|
|
|
// Drop cursor item when closing inventory
|
|
|
|
ItemStack cursorItem;
|
|
|
|
if (openInventory == null) {
|
|
|
|
cursorItem = getInventory().getCursorItem();
|
|
|
|
getInventory().setCursorItem(ItemStack.getAirItem());
|
|
|
|
} else {
|
|
|
|
cursorItem = openInventory.getCursorItem(this);
|
|
|
|
}
|
|
|
|
if (!cursorItem.isAir()) {
|
2020-05-17 11:44:34 +02:00
|
|
|
// Add item to inventory if he hasn't been able to drop it
|
|
|
|
if (!dropItem(cursorItem)) {
|
|
|
|
getInventory().addItemStack(cursorItem);
|
|
|
|
}
|
2020-05-17 11:40:49 +02:00
|
|
|
}
|
|
|
|
|
2019-08-13 17:52:09 +02:00
|
|
|
CloseWindowPacket closeWindowPacket = new CloseWindowPacket();
|
|
|
|
if (openInventory == null) {
|
|
|
|
closeWindowPacket.windowId = 0;
|
|
|
|
} else {
|
2020-04-28 19:22:47 +02:00
|
|
|
closeWindowPacket.windowId = openInventory.getWindowId();
|
2020-05-17 11:40:49 +02:00
|
|
|
openInventory.removeViewer(this); // Clear cache
|
2019-08-13 17:52:09 +02:00
|
|
|
refreshOpenInventory(null);
|
|
|
|
}
|
|
|
|
playerConnection.sendPacket(closeWindowPacket);
|
2019-08-14 06:50:03 +02:00
|
|
|
inventory.update();
|
2019-08-13 17:52:09 +02:00
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* @return an unmodifiable {@link Set} containing all the chunks that the player sees
|
|
|
|
*/
|
2019-09-01 06:18:41 +02:00
|
|
|
public Set<Chunk> getViewableChunks() {
|
|
|
|
return Collections.unmodifiableSet(viewableChunks);
|
|
|
|
}
|
|
|
|
|
2020-05-23 04:20:01 +02:00
|
|
|
/**
|
|
|
|
* Remove all the boss bars that the player has
|
|
|
|
*/
|
2019-08-25 20:03:43 +02:00
|
|
|
public void clearBossBars() {
|
|
|
|
this.bossBars.forEach(bossBar -> bossBar.removeViewer(this));
|
|
|
|
}
|
|
|
|
|
2019-08-27 05:23:25 +02:00
|
|
|
public void updateViewPosition(Chunk chunk) {
|
2019-08-26 00:29:40 +02:00
|
|
|
UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(chunk);
|
|
|
|
playerConnection.sendPacket(updateViewPositionPacket);
|
2019-08-20 22:40:57 +02:00
|
|
|
}
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
/**
|
|
|
|
* Used for synchronization purpose, mainly for teleportation
|
|
|
|
*/
|
2019-08-26 04:39:58 +02:00
|
|
|
protected void updatePlayerPosition() {
|
|
|
|
PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket();
|
|
|
|
positionAndLookPacket.position = position;
|
|
|
|
positionAndLookPacket.flags = 0x00;
|
2020-05-08 18:42:04 +02:00
|
|
|
positionAndLookPacket.teleportId = teleportId++;
|
2019-08-26 04:39:58 +02:00
|
|
|
playerConnection.sendPacket(positionAndLookPacket);
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
/**
|
|
|
|
* @return the player permission level
|
|
|
|
*/
|
2020-05-13 18:43:54 +02:00
|
|
|
public int getPermissionLevel() {
|
|
|
|
return permissionLevel;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
/**
|
|
|
|
* Change the player permission level
|
|
|
|
*
|
|
|
|
* @param permissionLevel the new player permission level
|
|
|
|
*/
|
2020-05-13 18:43:54 +02:00
|
|
|
public void setPermissionLevel(int permissionLevel) {
|
2020-05-23 04:20:01 +02:00
|
|
|
Check.argCondition(!MathUtils.isBetween(permissionLevel, 0, 4), "permissionLevel has to be between 0 and 4");
|
2020-05-13 18:43:54 +02:00
|
|
|
|
|
|
|
this.permissionLevel = permissionLevel;
|
|
|
|
|
|
|
|
// Magic values: https://wiki.vg/Entity_statuses#Player
|
|
|
|
byte permissionLevelStatus = (byte) (24 + permissionLevel);
|
|
|
|
triggerStatus(permissionLevelStatus);
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* @param reduced should the player has the reduced debug screen
|
|
|
|
*/
|
2020-05-13 18:43:54 +02:00
|
|
|
public void setReducedDebugScreenInformation(boolean reduced) {
|
|
|
|
this.reducedDebugScreenInformation = reduced;
|
|
|
|
|
|
|
|
// Magic values: https://wiki.vg/Entity_statuses#Player
|
|
|
|
byte debugScreenStatus = (byte) (reduced ? 22 : 23);
|
|
|
|
triggerStatus(debugScreenStatus);
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* @return true if the player has the reduced debug screen, false otherwise
|
|
|
|
*/
|
2020-05-13 18:43:54 +02:00
|
|
|
public boolean hasReducedDebugScreenInformation() {
|
|
|
|
return reducedDebugScreenInformation;
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* The invulnerable field appear in the {@link PlayerAbilitiesPacket} packet
|
|
|
|
*
|
|
|
|
* @return true if the player is invulnerable, false otherwise
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public boolean isInvulnerable() {
|
|
|
|
return invulnerable;
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public void setInvulnerable(boolean invulnerable) {
|
|
|
|
this.invulnerable = invulnerable;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @return true if the player if flying, false otherwise
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public boolean isFlying() {
|
|
|
|
return flying;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @param flying should the player fly
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public void setFlying(boolean flying) {
|
2020-05-27 19:43:08 +02:00
|
|
|
this.flying = flying;
|
2019-09-23 19:56:08 +02:00
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* Update 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
|
|
|
|
*/
|
2020-05-24 22:27:58 +02:00
|
|
|
public void refreshFlying(boolean flying) {
|
|
|
|
this.flying = flying;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @return true if the player if allowed to fly, false otherwise
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public boolean isAllowFlying() {
|
|
|
|
return allowFlying;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @param allowFlying should the player be allowed to fly
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public void setAllowFlying(boolean allowFlying) {
|
|
|
|
this.allowFlying = allowFlying;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isInstantBreak() {
|
|
|
|
return instantBreak;
|
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* Change the player ability "Creative Mode"
|
|
|
|
* <a href="https://wiki.vg/Protocol#Player_Abilities_.28clientbound.29">see</a>
|
|
|
|
* <p>
|
|
|
|
* WARNING: this has nothing to do with {@link CustomBlock#getBreakDelay(Player, BlockPosition)}
|
|
|
|
*
|
|
|
|
* @param instantBreak
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public void setInstantBreak(boolean instantBreak) {
|
|
|
|
this.instantBreak = instantBreak;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* @return the flying speed of the player
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public float getFlyingSpeed() {
|
|
|
|
return flyingSpeed;
|
|
|
|
}
|
|
|
|
|
2020-05-27 19:43:08 +02:00
|
|
|
/**
|
|
|
|
* Update the internal field and send a {@link PlayerAbilitiesPacket} with the new flying speed
|
|
|
|
*
|
|
|
|
* @param flyingSpeed the new flying speed of the player
|
|
|
|
*/
|
2019-09-23 19:56:08 +02:00
|
|
|
public void setFlyingSpeed(float flyingSpeed) {
|
|
|
|
this.flyingSpeed = flyingSpeed;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getFieldViewModifier() {
|
|
|
|
return fieldViewModifier;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setFieldViewModifier(float fieldViewModifier) {
|
|
|
|
this.fieldViewModifier = fieldViewModifier;
|
|
|
|
refreshAbilities();
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2020-04-17 15:58:07 +02:00
|
|
|
public Map<PlayerStatistic, Integer> getStatisticValueMap() {
|
|
|
|
return statisticValueMap;
|
|
|
|
}
|
|
|
|
|
2020-03-29 20:58:30 +02:00
|
|
|
public PlayerVehicleInformation getVehicleInformation() {
|
|
|
|
return vehicleInformation;
|
|
|
|
}
|
|
|
|
|
2020-04-05 17:46:29 +02:00
|
|
|
protected void refreshAbilities() {
|
2019-09-23 19:56:08 +02:00
|
|
|
PlayerAbilitiesPacket playerAbilitiesPacket = new PlayerAbilitiesPacket();
|
|
|
|
playerAbilitiesPacket.invulnerable = invulnerable;
|
|
|
|
playerAbilitiesPacket.flying = flying;
|
|
|
|
playerAbilitiesPacket.allowFlying = allowFlying;
|
|
|
|
playerAbilitiesPacket.instantBreak = instantBreak;
|
|
|
|
playerAbilitiesPacket.flyingSpeed = flyingSpeed;
|
|
|
|
playerAbilitiesPacket.fieldViewModifier = fieldViewModifier;
|
|
|
|
|
|
|
|
playerConnection.sendPacket(playerAbilitiesPacket);
|
|
|
|
}
|
|
|
|
|
2020-05-15 18:03:28 +02:00
|
|
|
/**
|
|
|
|
* All packets in the queue are executed in the {@link #update()} 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
|
|
|
|
*/
|
2019-08-20 17:41:07 +02:00
|
|
|
public void addPacketToQueue(ClientPlayPacket packet) {
|
2019-08-23 15:37:38 +02:00
|
|
|
this.packets.add(packet);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
2020-04-26 06:34:08 +02:00
|
|
|
public void refreshLatency(int latency) {
|
|
|
|
this.latency = latency;
|
|
|
|
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY);
|
|
|
|
playerInfoPacket.playerInfos.add(new PlayerInfoPacket.UpdateLatency(getUuid(), latency));
|
|
|
|
sendPacketToViewersAndSelf(playerInfoPacket);
|
|
|
|
}
|
|
|
|
|
2019-08-10 04:16:01 +02:00
|
|
|
public void refreshOnGround(boolean onGround) {
|
|
|
|
this.onGround = onGround;
|
|
|
|
}
|
|
|
|
|
2019-08-10 04:47:19 +02:00
|
|
|
public void refreshKeepAlive(long lastKeepAlive) {
|
|
|
|
this.lastKeepAlive = lastKeepAlive;
|
|
|
|
}
|
|
|
|
|
2019-08-12 08:30:59 +02:00
|
|
|
public void refreshHeldSlot(short slot) {
|
|
|
|
this.heldSlot = slot;
|
2019-08-20 22:40:57 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND);
|
2020-05-12 14:12:17 +02:00
|
|
|
|
|
|
|
refreshEating(false);
|
2019-08-12 08:30:59 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 20:32:47 +02:00
|
|
|
protected void refreshOpenInventory(Inventory openInventory) {
|
2019-08-13 17:52:09 +02:00
|
|
|
this.openInventory = openInventory;
|
|
|
|
}
|
|
|
|
|
2020-05-12 18:40:04 +02:00
|
|
|
public void refreshEating(boolean isEating, long eatingTime) {
|
2020-05-12 14:12:17 +02:00
|
|
|
this.isEating = isEating;
|
|
|
|
if (isEating) {
|
|
|
|
this.startEatingTime = System.currentTimeMillis();
|
2020-05-12 18:40:04 +02:00
|
|
|
this.eatingTime = eatingTime;
|
2020-05-12 14:12:17 +02:00
|
|
|
} else {
|
|
|
|
this.startEatingTime = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-12 18:40:04 +02:00
|
|
|
public void refreshEating(boolean isEating) {
|
|
|
|
refreshEating(isEating, defaultEatingTime);
|
|
|
|
}
|
|
|
|
|
2020-05-12 14:12:17 +02:00
|
|
|
public ItemUpdateStateEvent callItemUpdateStateEvent(boolean allowFood) {
|
|
|
|
Material mainHandMat = Material.fromId(getItemInMainHand().getMaterialId());
|
|
|
|
Material offHandMat = Material.fromId(getItemInOffHand().getMaterialId());
|
|
|
|
boolean isOffhand = offHandMat.hasState();
|
|
|
|
|
|
|
|
ItemStack updatedItem = isOffhand ? getItemInOffHand() :
|
|
|
|
mainHandMat.hasState() ? getItemInMainHand() : null;
|
|
|
|
if (updatedItem == null) // No item with state, cancel
|
|
|
|
return null;
|
|
|
|
|
|
|
|
boolean isFood = updatedItem.getMaterial().isFood();
|
|
|
|
|
|
|
|
if (isFood && !allowFood)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
ItemUpdateStateEvent itemUpdateStateEvent = new ItemUpdateStateEvent(updatedItem,
|
|
|
|
isOffhand ? Hand.OFF : Hand.MAIN);
|
|
|
|
callEvent(ItemUpdateStateEvent.class, itemUpdateStateEvent);
|
|
|
|
|
|
|
|
return itemUpdateStateEvent;
|
|
|
|
}
|
|
|
|
|
2020-04-28 17:58:34 +02:00
|
|
|
public void refreshTargetBlock(CustomBlock targetCustomBlock, BlockPosition targetBlockPosition, int breakTime) {
|
2019-08-18 23:52:11 +02:00
|
|
|
this.targetCustomBlock = targetCustomBlock;
|
2019-08-18 20:38:09 +02:00
|
|
|
this.targetBlockPosition = targetBlockPosition;
|
|
|
|
this.targetBlockTime = targetBlockPosition == null ? 0 : System.currentTimeMillis();
|
2020-05-12 14:12:17 +02:00
|
|
|
this.blockBreakTime = breakTime;
|
2019-08-18 20:38:09 +02:00
|
|
|
}
|
|
|
|
|
2019-08-18 23:52:11 +02:00
|
|
|
public void resetTargetBlock() {
|
2019-09-07 11:42:33 +02:00
|
|
|
if (targetBlockPosition != null)
|
|
|
|
sendBlockBreakAnimation(targetBlockPosition, (byte) -1); // Clear the break animation
|
2019-08-18 23:52:11 +02:00
|
|
|
this.targetCustomBlock = null;
|
|
|
|
this.targetBlockPosition = null;
|
|
|
|
this.targetBlockTime = 0;
|
|
|
|
}
|
|
|
|
|
2019-08-19 17:04:19 +02:00
|
|
|
public void refreshAddBossbar(BossBar bossBar) {
|
|
|
|
this.bossBars.add(bossBar);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void refreshRemoveBossbar(BossBar bossBar) {
|
|
|
|
this.bossBars.remove(bossBar);
|
|
|
|
}
|
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
public void refreshVehicleSteer(float sideways, float forward, boolean jump, boolean unmount) {
|
2020-03-29 20:58:30 +02:00
|
|
|
this.vehicleInformation.refresh(sideways, forward, jump, unmount);
|
2019-08-20 17:41:07 +02:00
|
|
|
}
|
|
|
|
|
2019-09-01 06:18:41 +02:00
|
|
|
public int getChunkRange() {
|
2020-02-17 17:33:53 +01:00
|
|
|
int serverRange = MinecraftServer.CHUNK_VIEW_DISTANCE;
|
2019-09-01 06:18:41 +02:00
|
|
|
int playerRange = getSettings().viewDistance;
|
2019-09-02 06:02:12 +02:00
|
|
|
if (playerRange == 0) {
|
2020-04-16 14:51:21 +02:00
|
|
|
return serverRange; // Didn't receive settings packet yet (is the case on login)
|
2019-09-02 06:02:12 +02:00
|
|
|
} else {
|
2020-04-16 14:51:21 +02:00
|
|
|
return Math.min(playerRange, serverRange);
|
2019-09-02 06:02:12 +02:00
|
|
|
}
|
2019-09-01 06:18:41 +02:00
|
|
|
}
|
|
|
|
|
2019-08-10 04:47:19 +02:00
|
|
|
public long getLastKeepAlive() {
|
|
|
|
return lastKeepAlive;
|
|
|
|
}
|
2019-08-11 03:40:34 +02:00
|
|
|
|
2020-05-06 22:35:32 +02:00
|
|
|
@Override
|
|
|
|
public ItemStack getItemInMainHand() {
|
|
|
|
return inventory.getItemInMainHand();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInMainHand(ItemStack itemStack) {
|
|
|
|
inventory.setItemInMainHand(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getItemInOffHand() {
|
|
|
|
return inventory.getItemInOffHand();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInOffHand(ItemStack itemStack) {
|
|
|
|
inventory.setItemInOffHand(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getHelmet() {
|
|
|
|
return inventory.getHelmet();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setHelmet(ItemStack itemStack) {
|
|
|
|
inventory.setHelmet(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getChestplate() {
|
|
|
|
return inventory.getChestplate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setChestplate(ItemStack itemStack) {
|
|
|
|
inventory.setChestplate(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getLeggings() {
|
|
|
|
return inventory.getLeggings();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setLeggings(ItemStack itemStack) {
|
|
|
|
inventory.setLeggings(itemStack);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getBoots() {
|
|
|
|
return inventory.getBoots();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setBoots(ItemStack itemStack) {
|
|
|
|
inventory.setBoots(itemStack);
|
|
|
|
}
|
|
|
|
|
2019-08-11 03:40:34 +02:00
|
|
|
public enum Hand {
|
|
|
|
MAIN,
|
|
|
|
OFF
|
|
|
|
}
|
2019-08-21 16:50:52 +02:00
|
|
|
|
2020-04-16 14:51:21 +02:00
|
|
|
// Settings enum
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
public enum MainHand {
|
|
|
|
LEFT,
|
2020-04-10 12:45:04 +02:00
|
|
|
RIGHT
|
2019-08-21 16:50:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public enum ChatMode {
|
|
|
|
ENABLED,
|
|
|
|
COMMANDS_ONLY,
|
2020-04-10 12:45:04 +02:00
|
|
|
HIDDEN
|
2019-08-21 16:50:52 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 18:25:35 +02:00
|
|
|
public enum FacePoint {
|
|
|
|
FEET,
|
|
|
|
EYE
|
|
|
|
}
|
|
|
|
|
2019-08-21 16:50:52 +02:00
|
|
|
public class PlayerSettings {
|
|
|
|
|
|
|
|
private String locale;
|
|
|
|
private byte viewDistance;
|
|
|
|
private ChatMode chatMode;
|
|
|
|
private boolean chatColors;
|
|
|
|
private byte displayedSkinParts;
|
|
|
|
private MainHand mainHand;
|
|
|
|
|
|
|
|
public String getLocale() {
|
|
|
|
return locale;
|
|
|
|
}
|
|
|
|
|
|
|
|
public byte getViewDistance() {
|
|
|
|
return viewDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
public ChatMode getChatMode() {
|
|
|
|
return chatMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean hasChatColors() {
|
|
|
|
return chatColors;
|
|
|
|
}
|
|
|
|
|
|
|
|
public byte getDisplayedSkinParts() {
|
|
|
|
return displayedSkinParts;
|
|
|
|
}
|
|
|
|
|
|
|
|
public MainHand getMainHand() {
|
|
|
|
return mainHand;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void refresh(String locale, byte viewDistance, ChatMode chatMode, boolean chatColors, byte displayedSkinParts, MainHand mainHand) {
|
|
|
|
this.locale = locale;
|
|
|
|
this.viewDistance = viewDistance;
|
|
|
|
this.chatMode = chatMode;
|
|
|
|
this.chatColors = chatColors;
|
|
|
|
this.displayedSkinParts = displayedSkinParts;
|
|
|
|
this.mainHand = mainHand;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-03 15:25:24 +02:00
|
|
|
}
|