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

995 lines
35 KiB
Java
Raw Normal View History

2020-04-24 03:25:58 +02:00
package net.minestom.server.entity;
2019-08-03 15:25:24 +02:00
2019-09-10 06:59:15 +02:00
import club.thectm.minecraft.text.TextObject;
import com.google.gson.JsonObject;
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;
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;
import net.minestom.server.event.*;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.network.packet.client.ClientPlayPacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.network.player.PlayerConnection;
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;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.ChunkUtils;
import net.minestom.server.utils.Position;
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-10 08:44:35 +02:00
public class Player extends LivingEntity {
private long lastKeepAlive;
2019-08-10 04:16:01 +02:00
private String username;
2019-08-03 15:25:24 +02:00
private 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;
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;
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-25 20:03:43 +02:00
private int food;
private float foodSaturation;
2019-08-12 08:30:59 +02:00
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;
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
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
// Vehicle
2020-04-23 12:30:49 +02:00
private PlayerVehicleInformation vehicleInformation = new PlayerVehicleInformation();
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
2020-04-10 12:45:04 +02:00
super(EntityType.PLAYER.getId());
this.uuid = uuid;
this.username = username;
this.playerConnection = playerConnection;
2019-08-12 08:30:59 +02:00
setBoundingBox(0.69f, 1.8f, 0.69f);
2019-08-30 01:17:46 +02:00
2020-04-05 17:46:29 +02:00
// Some client update
getPlayerConnection().sendPacket(getPropertiesPacket()); // Send default properties
2019-08-25 20:03:43 +02:00
refreshHealth();
2020-04-05 10:15:21 +02:00
refreshAbilities();
2019-08-24 20:34:01 +02:00
this.settings = new PlayerSettings();
2019-08-12 08:30:59 +02:00
this.inventory = new PlayerInventory(this);
2019-08-24 20:34:01 +02:00
setCanPickupItem(true); // By default
}
2019-08-10 08:44:35 +02:00
@Override
public void update() {
2019-08-24 20:34:01 +02:00
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-18 20:38:09 +02:00
2019-08-24 20:34:01 +02:00
super.update(); // Super update (item pickup)
2019-08-18 20:38:09 +02:00
// Target block stage
if (targetCustomBlock != null) {
2019-08-18 23:52:11 +02:00
int timeBreak = targetCustomBlock.getBreakDelay(this);
2019-08-18 20:38:09 +02:00
int animationCount = 10;
long since = System.currentTimeMillis() - targetBlockTime;
byte stage = (byte) (since / (timeBreak / animationCount));
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);
BoundingBox livingBoundingBox = getBoundingBox().expand(1, 0.5f, 1);
for (Entity entity : entities) {
if (entity instanceof ExperienceOrb) {
ExperienceOrb experienceOrb = (ExperienceOrb) entity;
BoundingBox itemBoundingBox = experienceOrb.getBoundingBox();
if (livingBoundingBox.intersect(itemBoundingBox)) {
synchronized (experienceOrb) {
if (experienceOrb.shouldRemove() || experienceOrb.isRemoveScheduled())
continue;
PickupExperienceEvent pickupExperienceEvent = new PickupExperienceEvent(experienceOrb.getExperienceCount());
callCancellableEvent(PickupExperienceEvent.class, pickupExperienceEvent, () -> {
short experienceCount = pickupExperienceEvent.getExperienceCount(); // TODO give to player
entity.remove();
});
}
}
}
}
2019-09-01 06:18:41 +02:00
// Tick event
callEvent(PlayerTickEvent.class, new PlayerTickEvent());
2019-08-18 20:38:09 +02:00
// Multiplayer sync
2019-09-07 11:42:33 +02:00
if (!getViewers().isEmpty()) {
Position position = getPosition();
boolean positionChanged = position.getX() != lastX || position.getZ() != lastZ || position.getY() != lastY;
boolean viewChanged = position.getYaw() != lastYaw || position.getPitch() != lastPitch;
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-09-07 11:42:33 +02:00
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() {
clearBossBars();
if (getOpenInventory() != null)
getOpenInventory().removeViewer(this);
2019-09-01 06:18:41 +02:00
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
2019-09-03 07:36:04 +02:00
this.viewableChunks.forEach(chunk -> 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
super.remove();
}
2019-08-20 22:40:57 +02:00
@Override
public void addViewer(Player player) {
if (player == this)
return;
2019-08-20 22:40:57 +02:00
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);
viewerConnection.sendPacket(getMetadataPacket());
2019-08-20 22:40:57 +02:00
for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) {
2019-09-21 20:42:27 +02:00
viewerConnection.sendPacket(getEquipmentPacket(slot));
2019-08-20 22:40:57 +02:00
}
2019-09-21 20:42:27 +02:00
// Team
if (team != null)
viewerConnection.sendPacket(team.getTeamsCreationPacket());
2019-08-20 22:40:57 +02:00
}
@Override
public void removeViewer(Player player) {
if (player == this)
return;
2019-08-20 22:40:57 +02:00
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());
}
@Override
public void setInstance(Instance instance) {
2019-09-01 06:18:41 +02:00
if (instance == null)
throw new IllegalArgumentException("instance cannot be null!");
if (this.instance == instance)
throw new IllegalArgumentException("Instance should be different than the current one");
for (Chunk viewableChunk : viewableChunks) {
viewableChunk.removeViewer(this);
}
viewableChunks.clear();
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);
}
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);
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(instance);
callEvent(PlayerSpawnEvent.class, spawnEvent);
updateViewPosition(chunk);
} 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-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) {
2019-09-10 06:59:15 +02:00
sendMessage(Chat.legacyText(message));
}
public void sendMessage(String message, char colorChar) {
sendMessage(Chat.legacyText(message, colorChar));
}
public void sendMessage(JsonObject jsonObject) {
ChatMessagePacket chatMessagePacket = new ChatMessagePacket(jsonObject.toString(), ChatMessagePacket.Position.CHAT);
2019-08-18 20:38:09 +02:00
playerConnection.sendPacket(chatMessagePacket);
}
2019-09-10 06:59:15 +02:00
public void sendMessage(TextObject textObject) {
sendMessage(textObject.toJson());
}
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;
soundEffectPacket.x = x;
soundEffectPacket.y = y;
soundEffectPacket.z = z;
soundEffectPacket.volume = volume;
soundEffectPacket.pitch = pitch;
playerConnection.sendPacket(soundEffectPacket);
}
public void stopSound() {
StopSoundPacket stopSoundPacket = new StopSoundPacket();
stopSoundPacket.flags = 0x00;
playerConnection.sendPacket(stopSoundPacket);
}
public void sendHeaderFooter(String header, String footer, char colorChar) {
PlayerListHeaderAndFooterPacket playerListHeaderAndFooterPacket = new PlayerListHeaderAndFooterPacket();
playerListHeaderAndFooterPacket.emptyHeader = header == null;
playerListHeaderAndFooterPacket.emptyFooter = footer == null;
playerListHeaderAndFooterPacket.header = Chat.legacyText(header, colorChar).toJson().toString();
playerListHeaderAndFooterPacket.footer = Chat.legacyText(footer, colorChar).toJson().toString();
playerConnection.sendPacket(playerListHeaderAndFooterPacket);
}
2020-02-16 19:11:36 +01:00
public void sendActionBarMessage(String message, char colorChar) {
TitlePacket titlePacket = new TitlePacket();
titlePacket.action = TitlePacket.Action.SET_ACTION_BAR;
2020-04-10 13:39:22 +02:00
titlePacket.actionBarText = Chat.legacyText(message, colorChar).toJson().toString();
2020-02-16 19:11:36 +01:00
playerConnection.sendPacket(titlePacket);
}
public void sendActionBarMessage(String message) {
sendActionBarMessage(message, Chat.COLOR_CHAR);
}
2019-08-27 20:49:11 +02:00
@Override
public boolean isImmune(DamageType type) {
if(getGameMode().canTakeDamage()) {
return type != DamageType.VOID;
}
return super.isImmune(type);
2019-08-25 20:03:43 +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
}
@Override
2019-08-25 20:03:43 +02:00
public void setHealth(float health) {
super.setHealth(health);
2019-08-25 20:03:43 +02:00
sendUpdateHealthPacket();
}
public int getFood() {
return food;
}
public void setFood(int food) {
this.food = food;
sendUpdateHealthPacket();
}
public float getFoodSaturation() {
return foodSaturation;
}
public void setFoodSaturation(float foodSaturation) {
this.foodSaturation = foodSaturation;
sendUpdateHealthPacket();
}
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();
}
public Position getRespawnPoint() {
// TODO: Custom
return new Position(0f, 70f, 0f);
}
2019-08-25 20:03:43 +02:00
public void respawn() {
if (!isDead())
return;
refreshHealth();
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimension = getDimension();
respawnPacket.gameMode = getGameMode();
respawnPacket.levelType = getLevelType();
getPlayerConnection().sendPacket(respawnPacket);
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getRespawnPoint());
2019-08-25 20:03:43 +02:00
callEvent(PlayerRespawnEvent.class, respawnEvent);
refreshIsDead(false);
// Runnable called when teleportation is successfull (after loading and sending necessary chunk)
teleport(respawnEvent.getRespawnPosition(), () -> {
getInventory().update();
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
spawnPlayerPacket.entityId = getEntityId();
spawnPlayerPacket.playerUuid = getUuid();
spawnPlayerPacket.position = getPosition();
sendPacketToViewers(spawnPlayerPacket);
playerConnection.sendPacket(getPropertiesPacket());
sendUpdateHealthPacket();
2019-09-02 06:02:12 +02:00
syncEquipments();
2019-08-25 20:03:43 +02:00
});
}
2020-04-05 17:46:29 +02:00
protected void refreshHealth() {
heal();
2019-08-25 20:03:43 +02:00
this.food = 20;
this.foodSaturation = 5;
}
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);
}
2019-08-31 07:54:53 +02:00
public void setExp(float exp) {
if (exp < 0 || exp > 1)
throw new IllegalArgumentException("Exp should be between 0 and 1");
this.exp = exp;
sendExperienceUpdatePacket();
}
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) {
2019-08-26 04:39:58 +02:00
float dx = newChunk.getChunkX() - lastChunk.getChunkX();
float dz = newChunk.getChunkZ() - lastChunk.getChunkZ();
double distance = Math.sqrt(dx * dx + dz * dz);
2020-02-17 17:33:53 +01:00
boolean isFar = distance >= MinecraftServer.CHUNK_VIEW_DISTANCE / 2;
2019-08-26 04:39:58 +02:00
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++) {
boolean isLast = i == newChunks.length - 1;
int index = newChunks[i];
int[] chunkPos = ChunkUtils.getChunkCoord(updatedVisibleChunks[index]);
instance.loadOptionalChunk(chunkPos[0], chunkPos[1], chunk -> {
if (chunk == null) {
return; // Cannot load chunk (auto load is not enabled)
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-27 05:23:25 +02:00
if (isFar && isLast) {
2019-08-26 04:39:58 +02:00
updatePlayerPosition();
2019-08-27 05:23:25 +02:00
}
2019-08-26 04:39:58 +02:00
});
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
}
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
2019-09-03 07:36:04 +02:00
public boolean isOnline() {
return playerConnection.isOnline();
}
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-04-26 06:34:08 +02:00
public int getLatency() {
return latency;
}
2019-08-21 16:50:52 +02:00
public Dimension getDimension() {
return dimension;
}
2019-08-12 08:30:59 +02:00
public GameMode getGameMode() {
return gameMode;
}
/**
* Returns true iff this player is in creative. Used for code readability
* @return
*/
public boolean isCreative() {
return gameMode == GameMode.CREATIVE;
}
2019-08-21 16:50:52 +02:00
public void setDimension(Dimension dimension) {
if (dimension == null)
throw new IllegalArgumentException("Dimension cannot be null!");
if (dimension.equals(getDimension()))
2020-04-22 02:42:58 +02:00
throw new IllegalArgumentException("The dimension need to be different than the current one!");
2019-08-21 16:50:52 +02:00
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimension = dimension;
respawnPacket.gameMode = gameMode;
respawnPacket.levelType = levelType;
playerConnection.sendPacket(respawnPacket);
}
2019-08-20 22:40:57 +02:00
public void kick(String message) {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.message = message;
playerConnection.sendPacket(disconnectPacket);
2020-04-17 01:16:02 +02:00
playerConnection.getChannel().close();
2019-08-20 22:40:57 +02:00
}
2019-08-21 16:50:52 +02:00
public LevelType getLevelType() {
return levelType;
}
2019-08-12 08:30:59 +02:00
public void setGameMode(GameMode gameMode) {
ChangeGameStatePacket changeGameStatePacket = new ChangeGameStatePacket();
changeGameStatePacket.reason = ChangeGameStatePacket.Reason.CHANGE_GAMEMODE;
changeGameStatePacket.value = gameMode.getId();
playerConnection.sendPacket(changeGameStatePacket);
refreshGameMode(gameMode);
}
public void setHeldItemSlot(short slot) {
if (slot < 0 || slot > 8)
throw new IllegalArgumentException("Slot has to be between 0 and 8");
HeldItemChangePacket heldItemChangePacket = new HeldItemChangePacket();
heldItemChangePacket.slot = slot;
playerConnection.sendPacket(heldItemChangePacket);
refreshHeldSlot(slot);
}
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));
}
}
2019-08-12 08:30:59 +02:00
public short getHeldSlot() {
return heldSlot;
}
2019-08-13 17:52:09 +02:00
public Inventory getOpenInventory() {
return openInventory;
}
2019-08-18 23:52:11 +02:00
public CustomBlock getCustomBlockTarget() {
return targetCustomBlock;
}
2019-08-19 17:04:19 +02:00
public Set<BossBar> getBossBars() {
return Collections.unmodifiableSet(bossBars);
}
2019-08-13 17:52:09 +02:00
public void openInventory(Inventory inventory) {
if (inventory == null)
throw new IllegalArgumentException("Inventory cannot be null, use Player#closeInventory() to close current");
if (getOpenInventory() != null) {
getOpenInventory().removeViewer(this);
}
OpenWindowPacket openWindowPacket = new OpenWindowPacket();
2019-08-27 05:23:25 +02:00
openWindowPacket.windowId = inventory.getWindowId();
2019-08-13 17:52:09 +02:00
openWindowPacket.windowType = inventory.getInventoryType().getWindowType();
openWindowPacket.title = inventory.getTitle();
playerConnection.sendPacket(openWindowPacket);
inventory.addViewer(this);
refreshOpenInventory(inventory);
}
public void closeInventory() {
Inventory openInventory = getOpenInventory();
CloseWindowPacket closeWindowPacket = new CloseWindowPacket();
if (openInventory == null) {
closeWindowPacket.windowId = 0;
} else {
2019-08-27 05:23:25 +02:00
closeWindowPacket.windowId = openInventory.getWindowId();
2019-08-13 17:52:09 +02:00
openInventory.removeViewer(this);
refreshOpenInventory(null);
}
playerConnection.sendPacket(closeWindowPacket);
2019-08-14 06:50:03 +02:00
inventory.update();
2019-08-13 17:52:09 +02:00
}
2019-09-01 06:18:41 +02:00
public Set<Chunk> getViewableChunks() {
return Collections.unmodifiableSet(viewableChunks);
}
2019-08-25 20:03:43 +02:00
public void clearBossBars() {
this.bossBars.forEach(bossBar -> bossBar.removeViewer(this));
}
2019-08-20 22:40:57 +02:00
public void syncEquipment(EntityEquipmentPacket.Slot slot) {
sendPacketToViewers(getEquipmentPacket(slot));
}
2019-09-02 06:02:12 +02:00
public void syncEquipments() {
for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) {
syncEquipment(slot);
}
}
protected EntityEquipmentPacket getEquipmentPacket(EntityEquipmentPacket.Slot slot) {
2019-08-20 22:40:57 +02:00
EntityEquipmentPacket equipmentPacket = new EntityEquipmentPacket();
equipmentPacket.entityId = getEntityId();
equipmentPacket.slot = slot;
equipmentPacket.itemStack = inventory.getEquipment(slot);
return equipmentPacket;
}
2019-08-27 05:23:25 +02:00
public void updateViewPosition(Chunk chunk) {
UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(chunk);
playerConnection.sendPacket(updateViewPositionPacket);
2019-08-20 22:40:57 +02:00
}
2019-08-26 04:39:58 +02:00
protected void updatePlayerPosition() {
PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket();
positionAndLookPacket.position = position;
positionAndLookPacket.flags = 0x00;
positionAndLookPacket.teleportId = 67;
playerConnection.sendPacket(positionAndLookPacket);
}
2019-09-23 19:56:08 +02:00
public boolean isInvulnerable() {
return invulnerable;
}
public void setInvulnerable(boolean invulnerable) {
this.invulnerable = invulnerable;
refreshAbilities();
}
public boolean isFlying() {
return flying;
}
public void setFlying(boolean flying) {
this.flying = flying;
refreshAbilities();
}
public boolean isAllowFlying() {
return allowFlying;
}
public void setAllowFlying(boolean allowFlying) {
this.allowFlying = allowFlying;
refreshAbilities();
}
public boolean isInstantBreak() {
return instantBreak;
}
public void setInstantBreak(boolean instantBreak) {
this.instantBreak = instantBreak;
refreshAbilities();
}
public float getFlyingSpeed() {
return flyingSpeed;
}
public void setFlyingSpeed(float flyingSpeed) {
this.flyingSpeed = flyingSpeed;
refreshAbilities();
}
public float getFieldViewModifier() {
return fieldViewModifier;
}
public void setFieldViewModifier(float fieldViewModifier) {
this.fieldViewModifier = fieldViewModifier;
refreshAbilities();
}
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);
}
public void addPacketToQueue(ClientPlayPacket packet) {
2019-08-23 15:37:38 +02:00
this.packets.add(packet);
}
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-21 16:50:52 +02:00
public void refreshDimension(Dimension dimension) {
this.dimension = dimension;
}
2019-08-12 08:30:59 +02:00
public void refreshGameMode(GameMode gameMode) {
this.gameMode = gameMode;
}
2019-08-21 16:50:52 +02:00
public void refreshLevelType(LevelType levelType) {
this.levelType = levelType;
}
2019-08-10 04:16:01 +02:00
public void refreshOnGround(boolean onGround) {
this.onGround = onGround;
}
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);
2019-08-12 08:30:59 +02:00
}
2019-08-13 17:52:09 +02:00
public void refreshOpenInventory(Inventory openInventory) {
this.openInventory = openInventory;
}
2019-08-21 16:50:52 +02:00
public void refreshTargetBlock(CustomBlock targetCustomBlock, BlockPosition targetBlockPosition) {
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();
}
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-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
}
public long getLastKeepAlive() {
return lastKeepAlive;
}
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
}
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
}